@plinth-music/cli 0.23.0 → 0.23.2
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/CHANGELOG.md +86 -0
- package/dist/build-stamp.json +2 -2
- package/dist/cli.js +1113 -491
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -7584,7 +7584,7 @@ function _getBuiltinFlags(long, short, userNames, userAliases) {
|
|
|
7584
7584
|
// package.json
|
|
7585
7585
|
var package_default = {
|
|
7586
7586
|
name: "@plinth-music/cli",
|
|
7587
|
-
version: "0.23.
|
|
7587
|
+
version: "0.23.2",
|
|
7588
7588
|
description: "Plinth capstone sync daemon and CLI",
|
|
7589
7589
|
type: "module",
|
|
7590
7590
|
private: false,
|
|
@@ -7843,8 +7843,48 @@ function emptyCorpusCounts() {
|
|
|
7843
7843
|
return { nonMarkdown: 0, unreadable: 0 };
|
|
7844
7844
|
}
|
|
7845
7845
|
|
|
7846
|
+
// src/lib/matter.ts
|
|
7847
|
+
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
7848
|
+
var FRONTMATTER_IS_CODE = "it is marked as code, which Plinth never runs";
|
|
7849
|
+
var PATCHED = Symbol.for("plinth.matter.engines-before-patch");
|
|
7850
|
+
var marked = (table) => table[PATCHED];
|
|
7851
|
+
var refuse = {
|
|
7852
|
+
parse() {
|
|
7853
|
+
throw new Error(FRONTMATTER_IS_CODE);
|
|
7854
|
+
},
|
|
7855
|
+
stringify() {
|
|
7856
|
+
throw new Error(FRONTMATTER_IS_CODE);
|
|
7857
|
+
}
|
|
7858
|
+
};
|
|
7859
|
+
var engines = import_gray_matter.default.engines;
|
|
7860
|
+
if (marked(engines) === undefined) {
|
|
7861
|
+
const before = Object.keys(engines).sort();
|
|
7862
|
+
const safeLoad = engines.yaml.parse;
|
|
7863
|
+
Object.assign(engines, {
|
|
7864
|
+
javascript: refuse,
|
|
7865
|
+
coffee: refuse,
|
|
7866
|
+
yaml: { parse: (input) => safeLoad(input), stringify: engines.yaml.stringify }
|
|
7867
|
+
});
|
|
7868
|
+
Object.defineProperty(engines, PATCHED, { value: before });
|
|
7869
|
+
}
|
|
7870
|
+
var ENGINES_BEFORE_PATCH = marked(engines);
|
|
7871
|
+
function matter(input, read) {
|
|
7872
|
+
return read === undefined ? import_gray_matter.default(input) : import_gray_matter.default(input, { cache: false });
|
|
7873
|
+
}
|
|
7874
|
+
function stringifyFrontmatter(body, data) {
|
|
7875
|
+
const out = import_gray_matter.default.stringify({ content: body }, data);
|
|
7876
|
+
const bodyOnly = body.endsWith(`
|
|
7877
|
+
`) ? body : `${body}
|
|
7878
|
+
`;
|
|
7879
|
+
if (out === bodyOnly && import_gray_matter.default.test(body.replace(/^/, ""))) {
|
|
7880
|
+
return `---
|
|
7881
|
+
---
|
|
7882
|
+
${bodyOnly}`;
|
|
7883
|
+
}
|
|
7884
|
+
return out;
|
|
7885
|
+
}
|
|
7886
|
+
|
|
7846
7887
|
// src/lib/backlinks/scan.ts
|
|
7847
|
-
var import_gray_matter4 = __toESM(require_gray_matter(), 1);
|
|
7848
7888
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
7849
7889
|
import { relative, sep as sep2 } from "node:path";
|
|
7850
7890
|
|
|
@@ -7860,9 +7900,6 @@ import {
|
|
|
7860
7900
|
} from "node:fs";
|
|
7861
7901
|
import { dirname, join as join2, resolve, sep } from "node:path";
|
|
7862
7902
|
|
|
7863
|
-
// src/lib/entities/_helpers.ts
|
|
7864
|
-
var import_gray_matter = __toESM(require_gray_matter(), 1);
|
|
7865
|
-
|
|
7866
7903
|
// node_modules/zod/v4/classic/external.js
|
|
7867
7904
|
var exports_external = {};
|
|
7868
7905
|
__export(exports_external, {
|
|
@@ -22168,7 +22205,8 @@ var TombstoneSchema = exports_external.object({
|
|
|
22168
22205
|
function makePageResponseSchema(responseKey, liveSchema) {
|
|
22169
22206
|
return exports_external.object({
|
|
22170
22207
|
[responseKey]: exports_external.array(exports_external.union([liveSchema, TombstoneSchema])),
|
|
22171
|
-
next_cursor: exports_external.string().nullable()
|
|
22208
|
+
next_cursor: exports_external.string().nullable(),
|
|
22209
|
+
high_water: exports_external.string().nullable().optional()
|
|
22172
22210
|
});
|
|
22173
22211
|
}
|
|
22174
22212
|
function makeParsePageResponse(schema, responseKey) {
|
|
@@ -22176,7 +22214,8 @@ function makeParsePageResponse(schema, responseKey) {
|
|
|
22176
22214
|
const parsed = schema.parse(json2);
|
|
22177
22215
|
return {
|
|
22178
22216
|
rows: parsed[responseKey],
|
|
22179
|
-
next_cursor: parsed.next_cursor
|
|
22217
|
+
next_cursor: parsed.next_cursor,
|
|
22218
|
+
high_water: parsed.high_water ?? null
|
|
22180
22219
|
};
|
|
22181
22220
|
};
|
|
22182
22221
|
}
|
|
@@ -22187,7 +22226,7 @@ function buildFrontmatter(doc2, projectionKeys) {
|
|
|
22187
22226
|
return { ...doc2.frontmatter, ...projection };
|
|
22188
22227
|
}
|
|
22189
22228
|
function serialiseRow(doc2, projectionKeys, bodyAccessor) {
|
|
22190
|
-
const out =
|
|
22229
|
+
const out = stringifyFrontmatter(bodyAccessor(doc2), buildFrontmatter(doc2, projectionKeys));
|
|
22191
22230
|
return out.endsWith(`
|
|
22192
22231
|
`) ? out : out + `
|
|
22193
22232
|
`;
|
|
@@ -22827,7 +22866,6 @@ var documentsStrategy = {
|
|
|
22827
22866
|
};
|
|
22828
22867
|
|
|
22829
22868
|
// src/lib/entities/meetings.ts
|
|
22830
|
-
var import_gray_matter2 = __toESM(require_gray_matter(), 1);
|
|
22831
22869
|
var MeetingLiveSchema = exports_external.object({
|
|
22832
22870
|
...BASE_ROW,
|
|
22833
22871
|
deleted_at: exports_external.null(),
|
|
@@ -22982,7 +23020,7 @@ function serialise(doc2) {
|
|
|
22982
23020
|
decisions: doc2.decisions,
|
|
22983
23021
|
follow_up_summary: doc2.follow_up_summary
|
|
22984
23022
|
});
|
|
22985
|
-
const out =
|
|
23023
|
+
const out = stringifyFrontmatter(body, fm);
|
|
22986
23024
|
return out.endsWith(`
|
|
22987
23025
|
`) ? out : out + `
|
|
22988
23026
|
`;
|
|
@@ -24473,7 +24511,6 @@ function readContextFile(workspaceSlug, relativePath) {
|
|
|
24473
24511
|
}
|
|
24474
24512
|
|
|
24475
24513
|
// src/lib/backlinks/parse.ts
|
|
24476
|
-
var import_gray_matter3 = __toESM(require_gray_matter(), 1);
|
|
24477
24514
|
var RENDERED_BLOCKS = [
|
|
24478
24515
|
{ name: "tasks", begin: TASKS_BLOCK_BEGIN, end: TASKS_BLOCK_END },
|
|
24479
24516
|
{ name: "comments", begin: COMMENTS_BLOCK_BEGIN, end: COMMENTS_BLOCK_END },
|
|
@@ -24555,7 +24592,7 @@ function frontmatterLinks(body) {
|
|
|
24555
24592
|
return null;
|
|
24556
24593
|
let data;
|
|
24557
24594
|
try {
|
|
24558
|
-
data =
|
|
24595
|
+
data = matter(body).data;
|
|
24559
24596
|
} catch {
|
|
24560
24597
|
return null;
|
|
24561
24598
|
}
|
|
@@ -24823,7 +24860,7 @@ function buildArtistIndex(relPaths, root, readFile) {
|
|
|
24823
24860
|
const slug = m[1];
|
|
24824
24861
|
let name = null;
|
|
24825
24862
|
try {
|
|
24826
|
-
const raw =
|
|
24863
|
+
const raw = matter(readFile(`${root}${sep2}${rel}`)).data;
|
|
24827
24864
|
if (typeof raw.name === "string" && raw.name.trim().length > 0) {
|
|
24828
24865
|
name = raw.name.trim();
|
|
24829
24866
|
}
|
|
@@ -25345,7 +25382,8 @@ var WorkspaceEntrySchema = exports_external.object({
|
|
|
25345
25382
|
last_synced_at: exports_external.string().datetime({ offset: true }).nullable(),
|
|
25346
25383
|
workspace_name: exports_external.string().min(1).optional(),
|
|
25347
25384
|
member: MemberPrincipalSchema.optional(),
|
|
25348
|
-
sync_cursors: SyncCursorsSchema.optional()
|
|
25385
|
+
sync_cursors: SyncCursorsSchema.optional(),
|
|
25386
|
+
sync_high_water: SyncCursorsSchema.optional()
|
|
25349
25387
|
});
|
|
25350
25388
|
var ConfigFileSchema = exports_external.object({
|
|
25351
25389
|
version: exports_external.literal(1),
|
|
@@ -25407,7 +25445,8 @@ function carryForwardSyncState(prior, entry) {
|
|
|
25407
25445
|
return {
|
|
25408
25446
|
...entry,
|
|
25409
25447
|
last_synced_at: prior.last_synced_at,
|
|
25410
|
-
...prior.sync_cursors !== undefined ? { sync_cursors: prior.sync_cursors } : {}
|
|
25448
|
+
...prior.sync_cursors !== undefined ? { sync_cursors: prior.sync_cursors } : {},
|
|
25449
|
+
...prior.sync_high_water !== undefined ? { sync_high_water: prior.sync_high_water } : {}
|
|
25411
25450
|
};
|
|
25412
25451
|
}
|
|
25413
25452
|
function upsertWorkspace(slug, entry, options2 = {}) {
|
|
@@ -25448,6 +25487,23 @@ function updateSyncCursor(slug, entityType, cursor) {
|
|
|
25448
25487
|
writeAtomic(next);
|
|
25449
25488
|
return next;
|
|
25450
25489
|
}
|
|
25490
|
+
function updateSyncHighWater(slug, entityType, mark) {
|
|
25491
|
+
const existing = loadConfig();
|
|
25492
|
+
if (!existing) {
|
|
25493
|
+
throw new Error(`No config. Run \`plinth login --workspace ${slug}\` first.`);
|
|
25494
|
+
}
|
|
25495
|
+
const entry = requireWorkspace(existing, slug);
|
|
25496
|
+
if ((entry.sync_high_water?.[entityType] ?? null) === mark)
|
|
25497
|
+
return;
|
|
25498
|
+
const nextEntry = {
|
|
25499
|
+
...entry,
|
|
25500
|
+
sync_high_water: { ...entry.sync_high_water, [entityType]: mark }
|
|
25501
|
+
};
|
|
25502
|
+
writeAtomic({
|
|
25503
|
+
...existing,
|
|
25504
|
+
workspaces: { ...existing.workspaces, [slug]: nextEntry }
|
|
25505
|
+
});
|
|
25506
|
+
}
|
|
25451
25507
|
function setMemberPrincipal(slug, member) {
|
|
25452
25508
|
const existing = loadConfig();
|
|
25453
25509
|
if (!existing)
|
|
@@ -25613,7 +25669,8 @@ var TripReasonSchema = exports_external.object({
|
|
|
25613
25669
|
entityType: exports_external.string().optional(),
|
|
25614
25670
|
typeCount: exports_external.number().int().nonnegative().optional(),
|
|
25615
25671
|
typePopulation: exports_external.number().int().nonnegative().optional(),
|
|
25616
|
-
pct: exports_external.number().optional()
|
|
25672
|
+
pct: exports_external.number().optional(),
|
|
25673
|
+
loneRemoved: exports_external.number().int().nonnegative().optional()
|
|
25617
25674
|
});
|
|
25618
25675
|
var PendingBatchFileSchema = exports_external.object({
|
|
25619
25676
|
version: exports_external.literal(1),
|
|
@@ -25722,6 +25779,9 @@ function describeTrip(reason) {
|
|
|
25722
25779
|
const pct = reason.pct !== undefined ? ` (${Math.round(reason.pct * 100)}%)` : "";
|
|
25723
25780
|
return `${reason.typeCount}/${reason.typePopulation} ${reason.entityType}${pct} — proportion threshold`;
|
|
25724
25781
|
}
|
|
25782
|
+
if (reason.loneRemoved !== undefined) {
|
|
25783
|
+
return reason.count === 1 ? "1 large overwrite — size threshold" : `${reason.count} destructive ops, including a large overwrite — size threshold`;
|
|
25784
|
+
}
|
|
25725
25785
|
return `${reason.count} destructive ops — count threshold`;
|
|
25726
25786
|
}
|
|
25727
25787
|
function formatBytes(n) {
|
|
@@ -26735,7 +26795,7 @@ function allFileEntries(state) {
|
|
|
26735
26795
|
return Object.values(state.files ?? {});
|
|
26736
26796
|
}
|
|
26737
26797
|
function fileEntriesEqual(a, b) {
|
|
26738
|
-
return a.id === b.id && a.path === b.path && a.content_hash === b.content_hash && a.size_bytes === b.size_bytes && a.mime_type === b.mime_type && a.updated_at === b.updated_at && a.inode === b.inode;
|
|
26798
|
+
return a.id === b.id && a.path === b.path && a.content_hash === b.content_hash && a.size_bytes === b.size_bytes && a.mime_type === b.mime_type && a.updated_at === b.updated_at && a.inode === b.inode && a.text_hash === b.text_hash;
|
|
26739
26799
|
}
|
|
26740
26800
|
function clearFrontmatterIdBackfillMarker(state) {
|
|
26741
26801
|
if (state.frontmatter_id_backfill_version === undefined) {
|
|
@@ -27609,7 +27669,6 @@ function decideRegen(input) {
|
|
|
27609
27669
|
}
|
|
27610
27670
|
|
|
27611
27671
|
// src/lib/context/roster.ts
|
|
27612
|
-
var import_gray_matter5 = __toESM(require_gray_matter(), 1);
|
|
27613
27672
|
import { readdirSync as readdirSync3, readFileSync as readFileSync8 } from "node:fs";
|
|
27614
27673
|
import { join as join7 } from "node:path";
|
|
27615
27674
|
function readRoster(workspaceSlug) {
|
|
@@ -27634,7 +27693,7 @@ function readRoster(workspaceSlug) {
|
|
|
27634
27693
|
continue;
|
|
27635
27694
|
}
|
|
27636
27695
|
try {
|
|
27637
|
-
const fm =
|
|
27696
|
+
const fm = matter(raw).data;
|
|
27638
27697
|
const name = typeof fm.name === "string" && fm.name.length > 0 ? fm.name : slug;
|
|
27639
27698
|
const status = typeof fm.status === "string" ? fm.status : null;
|
|
27640
27699
|
roster.push({ slug, name, status });
|
|
@@ -29024,7 +29083,6 @@ var SKILL_LOCATIONS = [
|
|
|
29024
29083
|
var SECURITY_NOTE = "Scoped to a single Plinth workspace: do not read or write across " + "workspace folders; each has its own token and isolation boundary.";
|
|
29025
29084
|
|
|
29026
29085
|
// src/lib/context/artist-data.ts
|
|
29027
|
-
var import_gray_matter6 = __toESM(require_gray_matter(), 1);
|
|
29028
29086
|
import { readFileSync as readFileSync10 } from "node:fs";
|
|
29029
29087
|
function artistUuidBySlug(state) {
|
|
29030
29088
|
const map2 = new Map;
|
|
@@ -29151,7 +29209,7 @@ function readEntityFrontmatter(workspaceSlug, entityType, slug) {
|
|
|
29151
29209
|
return null;
|
|
29152
29210
|
}
|
|
29153
29211
|
try {
|
|
29154
|
-
return
|
|
29212
|
+
return matter(raw).data;
|
|
29155
29213
|
} catch {
|
|
29156
29214
|
return null;
|
|
29157
29215
|
}
|
|
@@ -30195,7 +30253,8 @@ var FileTombstoneSchema = exports_external.object({
|
|
|
30195
30253
|
var FileRowSchema = exports_external.union([FileLiveSchema, FileTombstoneSchema]);
|
|
30196
30254
|
var FilesPageSchema = exports_external.object({
|
|
30197
30255
|
files: exports_external.array(FileRowSchema),
|
|
30198
|
-
next_cursor: exports_external.string().nullable()
|
|
30256
|
+
next_cursor: exports_external.string().nullable(),
|
|
30257
|
+
high_water: exports_external.string().nullable().optional()
|
|
30199
30258
|
});
|
|
30200
30259
|
var FileWithDownloadSchema = FileLiveSchema.extend({
|
|
30201
30260
|
download_url: exports_external.string().url().nullable()
|
|
@@ -30248,7 +30307,11 @@ async function syncFilesPage(args) {
|
|
|
30248
30307
|
if (!parsed.success) {
|
|
30249
30308
|
throw new ApiError(res.status, `sync files response did not match the expected shape: ${parsed.error.message}`);
|
|
30250
30309
|
}
|
|
30251
|
-
return {
|
|
30310
|
+
return {
|
|
30311
|
+
rows: parsed.data.files,
|
|
30312
|
+
next_cursor: parsed.data.next_cursor,
|
|
30313
|
+
high_water: parsed.data.high_water ?? null
|
|
30314
|
+
};
|
|
30252
30315
|
}
|
|
30253
30316
|
async function getFile(args) {
|
|
30254
30317
|
const { baseUrl, pat, id, fetchImpl = fetch } = args;
|
|
@@ -30519,6 +30582,15 @@ function fileDeleteSuppressedLine(f) {
|
|
|
30519
30582
|
function entityDeleteSuppressedLine(f) {
|
|
30520
30583
|
return `[ENTITY_DELETE_SUPPRESSED entityType=${f.entityType} slug=${f.slug} reason=${f.reason}]`;
|
|
30521
30584
|
}
|
|
30585
|
+
function entityPushHeldLine(f) {
|
|
30586
|
+
return `[PUSH_HELD entityType=${f.entityType} slug=${f.slug} reason=${f.reason}]`;
|
|
30587
|
+
}
|
|
30588
|
+
function entityRestoreDeferredLine(f) {
|
|
30589
|
+
return `[ENTITY_RESTORE_DEFERRED entityType=${f.entityType} slug=${f.slug} wait_s=${f.waitSeconds}]`;
|
|
30590
|
+
}
|
|
30591
|
+
function fileRestoreDeferredLine(f) {
|
|
30592
|
+
return `[FILE_RESTORE_DEFERRED path=${f.path} wait_s=${f.waitSeconds}]`;
|
|
30593
|
+
}
|
|
30522
30594
|
function fileOverCapLine(f) {
|
|
30523
30595
|
return `[FILE_OVER_CAP path=${f.path} size=${f.size} max=${f.max} max_from=${f.source}]`;
|
|
30524
30596
|
}
|
|
@@ -30707,6 +30779,34 @@ function recordEntityRejected(args) {
|
|
|
30707
30779
|
rejected_at: args.now().toISOString()
|
|
30708
30780
|
}));
|
|
30709
30781
|
}
|
|
30782
|
+
var FRONTMATTER_UNREADABLE_REASON = "frontmatter_unreadable";
|
|
30783
|
+
function frontmatterUnreadableSentence(why) {
|
|
30784
|
+
const reason = why.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
30785
|
+
return `not synced, because its frontmatter could not be read (${reason}). ` + `Fix the frontmatter and it syncs.`;
|
|
30786
|
+
}
|
|
30787
|
+
function recordEntityFrontmatterUnreadable(args) {
|
|
30788
|
+
let path;
|
|
30789
|
+
try {
|
|
30790
|
+
path = relative4(workspaceMirrorPath(args.workspaceSlug), args.absolutePath);
|
|
30791
|
+
} catch {
|
|
30792
|
+
path = args.absolutePath;
|
|
30793
|
+
}
|
|
30794
|
+
const message = frontmatterUnreadableSentence(args.why);
|
|
30795
|
+
const withoutStale = removeUnsynced(args.state, "entity", path);
|
|
30796
|
+
const existing = listRejected(args.state).find((o) => o.target === "entity" && o.path === path);
|
|
30797
|
+
if (!withoutStale.changed && existing?.reason === FRONTMATTER_UNREADABLE_REASON && existing.message === message) {
|
|
30798
|
+
return args.state;
|
|
30799
|
+
}
|
|
30800
|
+
return commitState(args.state, upsertRejected(withoutStale.state, {
|
|
30801
|
+
target: "entity",
|
|
30802
|
+
op: "upsert",
|
|
30803
|
+
path,
|
|
30804
|
+
status: 0,
|
|
30805
|
+
reason: FRONTMATTER_UNREADABLE_REASON,
|
|
30806
|
+
message,
|
|
30807
|
+
rejected_at: args.now().toISOString()
|
|
30808
|
+
}).state);
|
|
30809
|
+
}
|
|
30710
30810
|
function recordFileRejected(args) {
|
|
30711
30811
|
const cat = categorizeError(args.err);
|
|
30712
30812
|
const withoutStale = removeUnsynced(args.state, "file", args.relPath).state;
|
|
@@ -34287,8 +34387,7 @@ var sessionCommand = defineCommand({
|
|
|
34287
34387
|
});
|
|
34288
34388
|
|
|
34289
34389
|
// src/commands/start.ts
|
|
34290
|
-
|
|
34291
|
-
import { existsSync as existsSync10, readFileSync as readFileSync30, statSync as statSync18 } from "node:fs";
|
|
34390
|
+
import { existsSync as existsSync10, readFileSync as readFileSync31, statSync as statSync18 } from "node:fs";
|
|
34292
34391
|
|
|
34293
34392
|
// src/lib/daemon-log.ts
|
|
34294
34393
|
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9, renameSync as renameSync7, statSync as statSync4 } from "node:fs";
|
|
@@ -34354,9 +34453,311 @@ class DaemonLog {
|
|
|
34354
34453
|
}
|
|
34355
34454
|
|
|
34356
34455
|
// src/lib/destructive-gate.ts
|
|
34357
|
-
|
|
34358
|
-
import { readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
|
|
34456
|
+
import { readFileSync as readFileSync16, statSync as statSync6 } from "node:fs";
|
|
34359
34457
|
import { basename as basename4 } from "node:path";
|
|
34458
|
+
|
|
34459
|
+
// src/lib/pull-write.ts
|
|
34460
|
+
import { readFileSync as readFileSync15 } from "node:fs";
|
|
34461
|
+
|
|
34462
|
+
// src/lib/conflicts.ts
|
|
34463
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync14, readdirSync as readdirSync6, statSync as statSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
34464
|
+
import { dirname as dirname9, join as join12 } from "node:path";
|
|
34465
|
+
function conflictsRoot(workspaceSlug) {
|
|
34466
|
+
return join12(plinthHome(), "_conflicts", workspaceSlug);
|
|
34467
|
+
}
|
|
34468
|
+
function conflictFilePath(args) {
|
|
34469
|
+
const { workspaceSlug, entityType, entitySlug, timestamp, side } = args;
|
|
34470
|
+
const iso = timestamp.toISOString().replace(/[:.]/g, "-");
|
|
34471
|
+
return join12(conflictsRoot(workspaceSlug), `${entityType}.${entitySlug}.${iso}.${side}.md`);
|
|
34472
|
+
}
|
|
34473
|
+
function stashConflict(args) {
|
|
34474
|
+
const target = conflictFilePath(args);
|
|
34475
|
+
mkdirSync10(dirname9(target), { recursive: true, mode: 448 });
|
|
34476
|
+
writeFileSync7(target, args.contents, { mode: 384 });
|
|
34477
|
+
return target;
|
|
34478
|
+
}
|
|
34479
|
+
function stashEntityConflictOnce(args) {
|
|
34480
|
+
const dir = conflictsRoot(args.workspaceSlug);
|
|
34481
|
+
const head = `${args.entityType}.${args.entitySlug}.`;
|
|
34482
|
+
const tail = new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z\\.${args.side}\\.md$`);
|
|
34483
|
+
let names = [];
|
|
34484
|
+
try {
|
|
34485
|
+
names = readdirSync6(dir);
|
|
34486
|
+
} catch {
|
|
34487
|
+
names = [];
|
|
34488
|
+
}
|
|
34489
|
+
const newest = names.filter((name) => name.startsWith(head) && tail.test(name.slice(head.length))).sort().pop();
|
|
34490
|
+
if (newest !== undefined) {
|
|
34491
|
+
const path5 = join12(dir, newest);
|
|
34492
|
+
try {
|
|
34493
|
+
if (readFileSync14(path5, "utf8") === args.contents)
|
|
34494
|
+
return { path: path5, reused: true };
|
|
34495
|
+
} catch {}
|
|
34496
|
+
}
|
|
34497
|
+
return { path: stashConflict(args), reused: false };
|
|
34498
|
+
}
|
|
34499
|
+
function fileConflictPath(args) {
|
|
34500
|
+
const { workspaceSlug, relPath, timestamp, side } = args;
|
|
34501
|
+
const iso = timestamp.toISOString().replace(/[:.]/g, "-");
|
|
34502
|
+
return join12(conflictsRoot(workspaceSlug), `${fileConflictStem(relPath)}.${iso}.${side}`);
|
|
34503
|
+
}
|
|
34504
|
+
function fileConflictStem(relPath) {
|
|
34505
|
+
return `files.${relPath.replace(/[/\\]/g, "__")}`;
|
|
34506
|
+
}
|
|
34507
|
+
function stashFileConflict(args) {
|
|
34508
|
+
const target = fileConflictPath(args);
|
|
34509
|
+
mkdirSync10(dirname9(target), { recursive: true, mode: 448 });
|
|
34510
|
+
writeFileSync7(target, args.bytes, { mode: 384 });
|
|
34511
|
+
return target;
|
|
34512
|
+
}
|
|
34513
|
+
function stashFileConflictOnce(args) {
|
|
34514
|
+
const dir = conflictsRoot(args.workspaceSlug);
|
|
34515
|
+
const head = `${fileConflictStem(args.relPath)}.`;
|
|
34516
|
+
const tail = new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z\\.${args.side}$`);
|
|
34517
|
+
let names = [];
|
|
34518
|
+
try {
|
|
34519
|
+
names = readdirSync6(dir);
|
|
34520
|
+
} catch {
|
|
34521
|
+
names = [];
|
|
34522
|
+
}
|
|
34523
|
+
const newest = names.filter((n) => n.startsWith(head) && tail.test(n.slice(head.length))).sort().pop();
|
|
34524
|
+
if (newest !== undefined) {
|
|
34525
|
+
const path5 = join12(dir, newest);
|
|
34526
|
+
try {
|
|
34527
|
+
if (Buffer.compare(args.bytes, readFileSync14(path5)) === 0)
|
|
34528
|
+
return { path: path5, reused: true };
|
|
34529
|
+
} catch {}
|
|
34530
|
+
}
|
|
34531
|
+
return { path: stashFileConflict(args), reused: false };
|
|
34532
|
+
}
|
|
34533
|
+
var STAMP_RE2 = /\.(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)(?:\.(local|cloud))?(?:\.md)?$/;
|
|
34534
|
+
function parseStamp(stamp) {
|
|
34535
|
+
const iso = stamp.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "T$1:$2:$3.$4Z");
|
|
34536
|
+
const d = new Date(iso);
|
|
34537
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
34538
|
+
}
|
|
34539
|
+
function labelFor(file2) {
|
|
34540
|
+
const m = STAMP_RE2.exec(file2);
|
|
34541
|
+
if (!m)
|
|
34542
|
+
return file2;
|
|
34543
|
+
const head = file2.slice(0, m.index);
|
|
34544
|
+
if (head.startsWith("files.")) {
|
|
34545
|
+
return `files/${head.slice("files.".length).replace(/__/g, "/")}`;
|
|
34546
|
+
}
|
|
34547
|
+
const dot = head.indexOf(".");
|
|
34548
|
+
if (dot === -1)
|
|
34549
|
+
return head;
|
|
34550
|
+
return `${head.slice(0, dot)}/${head.slice(dot + 1)}`;
|
|
34551
|
+
}
|
|
34552
|
+
function listConflictStashes(workspaceSlug) {
|
|
34553
|
+
const dir = conflictsRoot(workspaceSlug);
|
|
34554
|
+
let names;
|
|
34555
|
+
try {
|
|
34556
|
+
names = readdirSync6(dir);
|
|
34557
|
+
} catch {
|
|
34558
|
+
return [];
|
|
34559
|
+
}
|
|
34560
|
+
const out = [];
|
|
34561
|
+
for (const file2 of names) {
|
|
34562
|
+
const absPath = join12(dir, file2);
|
|
34563
|
+
let st;
|
|
34564
|
+
try {
|
|
34565
|
+
st = statSync5(absPath);
|
|
34566
|
+
} catch {
|
|
34567
|
+
continue;
|
|
34568
|
+
}
|
|
34569
|
+
if (!st.isFile())
|
|
34570
|
+
continue;
|
|
34571
|
+
if (file2.startsWith("."))
|
|
34572
|
+
continue;
|
|
34573
|
+
const m = STAMP_RE2.exec(file2);
|
|
34574
|
+
const side = m?.[2];
|
|
34575
|
+
out.push({
|
|
34576
|
+
file: file2,
|
|
34577
|
+
absPath,
|
|
34578
|
+
label: labelFor(file2),
|
|
34579
|
+
stashedAt: m ? parseStamp(m[1]) : null,
|
|
34580
|
+
side: side === "local" || side === "cloud" ? side : null,
|
|
34581
|
+
mtimeMs: st.mtimeMs
|
|
34582
|
+
});
|
|
34583
|
+
}
|
|
34584
|
+
return out.sort((a, b) => (b.stashedAt?.getTime() ?? b.mtimeMs) - (a.stashedAt?.getTime() ?? a.mtimeMs));
|
|
34585
|
+
}
|
|
34586
|
+
|
|
34587
|
+
// src/lib/pull-write.ts
|
|
34588
|
+
var UNCACHED = { cache: false };
|
|
34589
|
+
function parseUncached(raw) {
|
|
34590
|
+
return matter(raw, UNCACHED);
|
|
34591
|
+
}
|
|
34592
|
+
var NOT_A_MAPPING = "it is not a list of `key: value` lines";
|
|
34593
|
+
var SELF_REFERENCE = "an alias in it refers to itself";
|
|
34594
|
+
function readFrontmatter(raw) {
|
|
34595
|
+
let parsed;
|
|
34596
|
+
try {
|
|
34597
|
+
parsed = parseUncached(raw);
|
|
34598
|
+
} catch (err) {
|
|
34599
|
+
return { ok: false, why: yamlReason(err) };
|
|
34600
|
+
}
|
|
34601
|
+
if (Object.prototype.toString.call(parsed.data) !== "[object Object]") {
|
|
34602
|
+
return { ok: false, why: NOT_A_MAPPING };
|
|
34603
|
+
}
|
|
34604
|
+
try {
|
|
34605
|
+
JSON.stringify(parsed.data);
|
|
34606
|
+
} catch {
|
|
34607
|
+
return { ok: false, why: SELF_REFERENCE };
|
|
34608
|
+
}
|
|
34609
|
+
return { ok: true, parsed };
|
|
34610
|
+
}
|
|
34611
|
+
function yamlReason(err) {
|
|
34612
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
34613
|
+
return (message.split(`
|
|
34614
|
+
`)[0] ?? "").replace(/:\s*$/, "").trim();
|
|
34615
|
+
}
|
|
34616
|
+
function frontmatterParseError(raw) {
|
|
34617
|
+
const read = readFrontmatter(raw);
|
|
34618
|
+
return read.ok ? null : read.why;
|
|
34619
|
+
}
|
|
34620
|
+
function computeMirrorHashes(strategy, content) {
|
|
34621
|
+
const parsed = matter(content);
|
|
34622
|
+
const fm = parsed.data ?? {};
|
|
34623
|
+
const lifted = splitUserFrontmatter(strategy, fm);
|
|
34624
|
+
const userFm = userFrontmatter(strategy, lifted.frontmatter);
|
|
34625
|
+
const hashBody = hashableBody(strategy, parsed.content);
|
|
34626
|
+
return {
|
|
34627
|
+
bodyHash: stableHash(hashBody),
|
|
34628
|
+
frontmatterHash: stableHash({ ...userFm, ...lifted.topLevel }),
|
|
34629
|
+
bodyLen: hashBody.length,
|
|
34630
|
+
frontmatterId: fm["id"]
|
|
34631
|
+
};
|
|
34632
|
+
}
|
|
34633
|
+
function hashableBody(strategy, body) {
|
|
34634
|
+
return strategy.stripLocalBlocks?.(body) ?? body;
|
|
34635
|
+
}
|
|
34636
|
+
function relocateForSlugChange(args) {
|
|
34637
|
+
const { workspaceSlug, strategy, fromSlug, toSlug, acked, echo, now } = args;
|
|
34638
|
+
if (fromSlug === toSlug)
|
|
34639
|
+
return { kind: "not_needed", writeSlug: toSlug };
|
|
34640
|
+
if (!echo) {
|
|
34641
|
+
return { kind: "skipped", writeSlug: toSlug, fromSlug, reason: "unguarded" };
|
|
34642
|
+
}
|
|
34643
|
+
if (strategy.mirrorRelativePath(fromSlug).split("/").length !== 2) {
|
|
34644
|
+
return { kind: "skipped", writeSlug: toSlug, fromSlug, reason: "foldered" };
|
|
34645
|
+
}
|
|
34646
|
+
let fromPath;
|
|
34647
|
+
try {
|
|
34648
|
+
fromPath = entityFilePath(workspaceSlug, strategy.type, fromSlug);
|
|
34649
|
+
entityFilePath(workspaceSlug, strategy.type, toSlug);
|
|
34650
|
+
} catch (err) {
|
|
34651
|
+
if (err instanceof MirrorPathError) {
|
|
34652
|
+
return {
|
|
34653
|
+
kind: "skipped",
|
|
34654
|
+
writeSlug: toSlug,
|
|
34655
|
+
fromSlug,
|
|
34656
|
+
reason: "unresolvable"
|
|
34657
|
+
};
|
|
34658
|
+
}
|
|
34659
|
+
throw err;
|
|
34660
|
+
}
|
|
34661
|
+
let raw = null;
|
|
34662
|
+
try {
|
|
34663
|
+
raw = readFileSync15(fromPath, "utf8");
|
|
34664
|
+
} catch (err) {
|
|
34665
|
+
const code = err?.code;
|
|
34666
|
+
if (code === "ENOENT") {
|
|
34667
|
+
return { kind: "absent", writeSlug: toSlug, fromSlug };
|
|
34668
|
+
}
|
|
34669
|
+
return {
|
|
34670
|
+
kind: "refused",
|
|
34671
|
+
writeSlug: fromSlug,
|
|
34672
|
+
fromSlug,
|
|
34673
|
+
reason: "source_unreadable"
|
|
34674
|
+
};
|
|
34675
|
+
}
|
|
34676
|
+
let diverged = true;
|
|
34677
|
+
try {
|
|
34678
|
+
const hashes = computeMirrorHashes(strategy, raw);
|
|
34679
|
+
diverged = hashes.bodyHash !== acked.bodyHash || hashes.frontmatterHash !== acked.frontmatterHash;
|
|
34680
|
+
} catch {
|
|
34681
|
+
diverged = true;
|
|
34682
|
+
}
|
|
34683
|
+
const stashPath = diverged ? stashConflict({
|
|
34684
|
+
workspaceSlug,
|
|
34685
|
+
entityType: strategy.type,
|
|
34686
|
+
entitySlug: fromSlug,
|
|
34687
|
+
timestamp: now(),
|
|
34688
|
+
contents: raw,
|
|
34689
|
+
side: "local"
|
|
34690
|
+
}) : undefined;
|
|
34691
|
+
try {
|
|
34692
|
+
let registered = false;
|
|
34693
|
+
const result = moveEntityFile(workspaceSlug, strategy.type, fromSlug, toSlug, () => {
|
|
34694
|
+
if (echo.watcher === "live") {
|
|
34695
|
+
echo.suppressor.registerDelete(fromPath);
|
|
34696
|
+
registered = true;
|
|
34697
|
+
}
|
|
34698
|
+
});
|
|
34699
|
+
if (result === "moved") {
|
|
34700
|
+
return { kind: "moved", writeSlug: toSlug, fromSlug, stashPath };
|
|
34701
|
+
}
|
|
34702
|
+
if (registered && echo.watcher === "live") {
|
|
34703
|
+
echo.suppressor.cancelDelete(fromPath);
|
|
34704
|
+
}
|
|
34705
|
+
return { kind: "absent", writeSlug: toSlug, fromSlug };
|
|
34706
|
+
} catch (err) {
|
|
34707
|
+
if (err instanceof MirrorDestinationOccupiedError) {
|
|
34708
|
+
return {
|
|
34709
|
+
kind: "refused",
|
|
34710
|
+
writeSlug: fromSlug,
|
|
34711
|
+
fromSlug,
|
|
34712
|
+
reason: "destination_occupied",
|
|
34713
|
+
stashPath
|
|
34714
|
+
};
|
|
34715
|
+
}
|
|
34716
|
+
if (err instanceof MirrorPathError) {
|
|
34717
|
+
return {
|
|
34718
|
+
kind: "skipped",
|
|
34719
|
+
writeSlug: toSlug,
|
|
34720
|
+
fromSlug,
|
|
34721
|
+
reason: "unresolvable"
|
|
34722
|
+
};
|
|
34723
|
+
}
|
|
34724
|
+
throw err;
|
|
34725
|
+
}
|
|
34726
|
+
}
|
|
34727
|
+
function writeMirrorWithBodyPreserve(workspaceSlug, strategy, doc2, suppressor, pathSlug) {
|
|
34728
|
+
const targetSlug = pathSlug ?? doc2.slug;
|
|
34729
|
+
const content = renderMirrorWithBodyPreserve(workspaceSlug, strategy, doc2, targetSlug);
|
|
34730
|
+
if (suppressor) {
|
|
34731
|
+
const target = entityFilePath(workspaceSlug, strategy.type, targetSlug);
|
|
34732
|
+
suppressor.registerWrite(target, computeMirrorHashes(strategy, content));
|
|
34733
|
+
}
|
|
34734
|
+
writeMirrorFile(workspaceSlug, strategy.type, targetSlug, content);
|
|
34735
|
+
return content;
|
|
34736
|
+
}
|
|
34737
|
+
function renderMirrorWithBodyPreserve(workspaceSlug, strategy, doc2, targetSlug) {
|
|
34738
|
+
let content = strategy.serialise(doc2);
|
|
34739
|
+
if (strategy.preserveLocalBodyOnPull) {
|
|
34740
|
+
const target = entityFilePath(workspaceSlug, strategy.type, targetSlug);
|
|
34741
|
+
let existing = null;
|
|
34742
|
+
try {
|
|
34743
|
+
existing = readFileSync15(target, "utf8");
|
|
34744
|
+
} catch (err) {
|
|
34745
|
+
if (err?.code !== "ENOENT")
|
|
34746
|
+
throw err;
|
|
34747
|
+
}
|
|
34748
|
+
if (existing !== null) {
|
|
34749
|
+
const existingParsed = matter(existing);
|
|
34750
|
+
const cloudParsed = matter(content);
|
|
34751
|
+
const stitched = stringifyFrontmatter(existingParsed.content, cloudParsed.data);
|
|
34752
|
+
content = stitched.endsWith(`
|
|
34753
|
+
`) ? stitched : stitched + `
|
|
34754
|
+
`;
|
|
34755
|
+
}
|
|
34756
|
+
}
|
|
34757
|
+
return content;
|
|
34758
|
+
}
|
|
34759
|
+
|
|
34760
|
+
// src/lib/destructive-gate.ts
|
|
34360
34761
|
var GUTTING_RATIO_DEFAULT = 0.5;
|
|
34361
34762
|
var GUTTING_MIN_PRIOR_BYTES = 200;
|
|
34362
34763
|
function envFloat(name, def) {
|
|
@@ -34399,10 +34800,10 @@ function classifyDestructive(event, entityType, state, deps = {}) {
|
|
|
34399
34800
|
absolutePath: event.absolutePath
|
|
34400
34801
|
};
|
|
34401
34802
|
}
|
|
34402
|
-
const readFile = deps.readFile ?? ((p) =>
|
|
34803
|
+
const readFile = deps.readFile ?? ((p) => readFileSync16(p, "utf8"));
|
|
34403
34804
|
const statInode = deps.statInode ?? ((p) => {
|
|
34404
34805
|
try {
|
|
34405
|
-
return
|
|
34806
|
+
return statSync6(p).ino;
|
|
34406
34807
|
} catch {
|
|
34407
34808
|
return;
|
|
34408
34809
|
}
|
|
@@ -34413,13 +34814,11 @@ function classifyDestructive(event, entityType, state, deps = {}) {
|
|
|
34413
34814
|
} catch {
|
|
34414
34815
|
return null;
|
|
34415
34816
|
}
|
|
34416
|
-
|
|
34417
|
-
|
|
34418
|
-
const body = import_gray_matter7.default(raw).content;
|
|
34419
|
-
newLen = (getStrategy(entityType)?.stripLocalBlocks?.(body) ?? body).length;
|
|
34420
|
-
} catch {
|
|
34817
|
+
const read = readFrontmatter(raw);
|
|
34818
|
+
if (!read.ok)
|
|
34421
34819
|
return null;
|
|
34422
|
-
|
|
34820
|
+
const body = read.parsed.content;
|
|
34821
|
+
const newLen = (getStrategy(entityType)?.stripLocalBlocks?.(body) ?? body).length;
|
|
34423
34822
|
const liveInode = statInode(event.absolutePath);
|
|
34424
34823
|
const slug = basename4(event.absolutePath, ".md");
|
|
34425
34824
|
const entry2 = (liveInode !== undefined ? getEntryByInode(state, entityType, liveInode) : undefined) ?? getEntryBySlug(state, entityType, slug);
|
|
@@ -34438,13 +34837,26 @@ function classifyDestructive(event, entityType, state, deps = {}) {
|
|
|
34438
34837
|
newLen
|
|
34439
34838
|
};
|
|
34440
34839
|
}
|
|
34840
|
+
var LONE_MIN_REMOVED_DEFAULT = 5000;
|
|
34841
|
+
var LONE_MIN_REMOVED_BYTES_DEFAULT = 100 * 1024;
|
|
34441
34842
|
function defaultThresholds() {
|
|
34442
34843
|
return {
|
|
34443
34844
|
absCount: envInt("PLINTH_GATE_ABS_COUNT", 3),
|
|
34444
34845
|
propMinOps: envInt("PLINTH_GATE_PROP_MIN_OPS", 2),
|
|
34445
|
-
propPct: envFloat("PLINTH_GATE_PROP_PCT", 0.5)
|
|
34846
|
+
propPct: envFloat("PLINTH_GATE_PROP_PCT", 0.5),
|
|
34847
|
+
loneMinRemoved: envInt("PLINTH_GATE_LONE_MIN_REMOVED", LONE_MIN_REMOVED_DEFAULT),
|
|
34848
|
+
loneMinRemovedBytes: envInt("PLINTH_GATE_LONE_MIN_REMOVED_BYTES", LONE_MIN_REMOVED_BYTES_DEFAULT)
|
|
34446
34849
|
};
|
|
34447
34850
|
}
|
|
34851
|
+
function tripReasonToken(reason) {
|
|
34852
|
+
return reason.loneRemoved !== undefined ? "size" : reason.kind;
|
|
34853
|
+
}
|
|
34854
|
+
function evaluateLoneOverwrite(count, removed, floor) {
|
|
34855
|
+
const loneRemoved = Math.max(0, ...removed);
|
|
34856
|
+
if (loneRemoved < floor)
|
|
34857
|
+
return { tripped: false };
|
|
34858
|
+
return { tripped: true, reason: { kind: "count", count, loneRemoved } };
|
|
34859
|
+
}
|
|
34448
34860
|
function evaluateBatch(ops, typePopulations, thresholds = defaultThresholds()) {
|
|
34449
34861
|
const count = ops.length;
|
|
34450
34862
|
if (count === 0)
|
|
@@ -34472,7 +34884,7 @@ function evaluateBatch(ops, typePopulations, thresholds = defaultThresholds()) {
|
|
|
34472
34884
|
};
|
|
34473
34885
|
}
|
|
34474
34886
|
}
|
|
34475
|
-
return
|
|
34887
|
+
return evaluateLoneOverwrite(count, ops.map((op) => op.kind === "overwrite" ? op.priorLen - op.newLen : 0), thresholds.loneMinRemoved);
|
|
34476
34888
|
}
|
|
34477
34889
|
function dedupeOps(ops) {
|
|
34478
34890
|
const byKey = new Map;
|
|
@@ -34532,135 +34944,10 @@ class DestructiveGate {
|
|
|
34532
34944
|
}
|
|
34533
34945
|
|
|
34534
34946
|
// src/lib/resolve-pending.ts
|
|
34535
|
-
import { readFileSync as
|
|
34536
|
-
|
|
34537
|
-
// src/lib/conflicts.ts
|
|
34538
|
-
import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, readdirSync as readdirSync6, statSync as statSync6, writeFileSync as writeFileSync7 } from "node:fs";
|
|
34539
|
-
import { dirname as dirname9, join as join12 } from "node:path";
|
|
34540
|
-
function conflictsRoot(workspaceSlug) {
|
|
34541
|
-
return join12(plinthHome(), "_conflicts", workspaceSlug);
|
|
34542
|
-
}
|
|
34543
|
-
function conflictFilePath(args) {
|
|
34544
|
-
const { workspaceSlug, entityType, entitySlug, timestamp, side } = args;
|
|
34545
|
-
const iso = timestamp.toISOString().replace(/[:.]/g, "-");
|
|
34546
|
-
return join12(conflictsRoot(workspaceSlug), `${entityType}.${entitySlug}.${iso}.${side}.md`);
|
|
34547
|
-
}
|
|
34548
|
-
function stashConflict(args) {
|
|
34549
|
-
const target = conflictFilePath(args);
|
|
34550
|
-
mkdirSync10(dirname9(target), { recursive: true, mode: 448 });
|
|
34551
|
-
writeFileSync7(target, args.contents, { mode: 384 });
|
|
34552
|
-
return target;
|
|
34553
|
-
}
|
|
34554
|
-
function stashEntityConflictOnce(args) {
|
|
34555
|
-
const dir = conflictsRoot(args.workspaceSlug);
|
|
34556
|
-
const head = `${args.entityType}.${args.entitySlug}.`;
|
|
34557
|
-
const tail = new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z\\.${args.side}\\.md$`);
|
|
34558
|
-
let names = [];
|
|
34559
|
-
try {
|
|
34560
|
-
names = readdirSync6(dir);
|
|
34561
|
-
} catch {
|
|
34562
|
-
names = [];
|
|
34563
|
-
}
|
|
34564
|
-
const newest = names.filter((name) => name.startsWith(head) && tail.test(name.slice(head.length))).sort().pop();
|
|
34565
|
-
if (newest !== undefined) {
|
|
34566
|
-
const path5 = join12(dir, newest);
|
|
34567
|
-
try {
|
|
34568
|
-
if (readFileSync15(path5, "utf8") === args.contents)
|
|
34569
|
-
return { path: path5, reused: true };
|
|
34570
|
-
} catch {}
|
|
34571
|
-
}
|
|
34572
|
-
return { path: stashConflict(args), reused: false };
|
|
34573
|
-
}
|
|
34574
|
-
function fileConflictPath(args) {
|
|
34575
|
-
const { workspaceSlug, relPath, timestamp, side } = args;
|
|
34576
|
-
const iso = timestamp.toISOString().replace(/[:.]/g, "-");
|
|
34577
|
-
return join12(conflictsRoot(workspaceSlug), `${fileConflictStem(relPath)}.${iso}.${side}`);
|
|
34578
|
-
}
|
|
34579
|
-
function fileConflictStem(relPath) {
|
|
34580
|
-
return `files.${relPath.replace(/[/\\]/g, "__")}`;
|
|
34581
|
-
}
|
|
34582
|
-
function stashFileConflict(args) {
|
|
34583
|
-
const target = fileConflictPath(args);
|
|
34584
|
-
mkdirSync10(dirname9(target), { recursive: true, mode: 448 });
|
|
34585
|
-
writeFileSync7(target, args.bytes, { mode: 384 });
|
|
34586
|
-
return target;
|
|
34587
|
-
}
|
|
34588
|
-
function stashFileConflictOnce(args) {
|
|
34589
|
-
const dir = conflictsRoot(args.workspaceSlug);
|
|
34590
|
-
const head = `${fileConflictStem(args.relPath)}.`;
|
|
34591
|
-
const tail = new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}Z\\.${args.side}$`);
|
|
34592
|
-
let names = [];
|
|
34593
|
-
try {
|
|
34594
|
-
names = readdirSync6(dir);
|
|
34595
|
-
} catch {
|
|
34596
|
-
names = [];
|
|
34597
|
-
}
|
|
34598
|
-
const newest = names.filter((n) => n.startsWith(head) && tail.test(n.slice(head.length))).sort().pop();
|
|
34599
|
-
if (newest !== undefined) {
|
|
34600
|
-
const path5 = join12(dir, newest);
|
|
34601
|
-
try {
|
|
34602
|
-
if (Buffer.compare(args.bytes, readFileSync15(path5)) === 0)
|
|
34603
|
-
return { path: path5, reused: true };
|
|
34604
|
-
} catch {}
|
|
34605
|
-
}
|
|
34606
|
-
return { path: stashFileConflict(args), reused: false };
|
|
34607
|
-
}
|
|
34608
|
-
var STAMP_RE2 = /\.(\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}-\d{3}Z)(?:\.(local|cloud))?(?:\.md)?$/;
|
|
34609
|
-
function parseStamp(stamp) {
|
|
34610
|
-
const iso = stamp.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "T$1:$2:$3.$4Z");
|
|
34611
|
-
const d = new Date(iso);
|
|
34612
|
-
return Number.isNaN(d.getTime()) ? null : d;
|
|
34613
|
-
}
|
|
34614
|
-
function labelFor(file2) {
|
|
34615
|
-
const m = STAMP_RE2.exec(file2);
|
|
34616
|
-
if (!m)
|
|
34617
|
-
return file2;
|
|
34618
|
-
const head = file2.slice(0, m.index);
|
|
34619
|
-
if (head.startsWith("files.")) {
|
|
34620
|
-
return `files/${head.slice("files.".length).replace(/__/g, "/")}`;
|
|
34621
|
-
}
|
|
34622
|
-
const dot = head.indexOf(".");
|
|
34623
|
-
if (dot === -1)
|
|
34624
|
-
return head;
|
|
34625
|
-
return `${head.slice(0, dot)}/${head.slice(dot + 1)}`;
|
|
34626
|
-
}
|
|
34627
|
-
function listConflictStashes(workspaceSlug) {
|
|
34628
|
-
const dir = conflictsRoot(workspaceSlug);
|
|
34629
|
-
let names;
|
|
34630
|
-
try {
|
|
34631
|
-
names = readdirSync6(dir);
|
|
34632
|
-
} catch {
|
|
34633
|
-
return [];
|
|
34634
|
-
}
|
|
34635
|
-
const out = [];
|
|
34636
|
-
for (const file2 of names) {
|
|
34637
|
-
const absPath = join12(dir, file2);
|
|
34638
|
-
let st;
|
|
34639
|
-
try {
|
|
34640
|
-
st = statSync6(absPath);
|
|
34641
|
-
} catch {
|
|
34642
|
-
continue;
|
|
34643
|
-
}
|
|
34644
|
-
if (!st.isFile())
|
|
34645
|
-
continue;
|
|
34646
|
-
if (file2.startsWith("."))
|
|
34647
|
-
continue;
|
|
34648
|
-
const m = STAMP_RE2.exec(file2);
|
|
34649
|
-
const side = m?.[2];
|
|
34650
|
-
out.push({
|
|
34651
|
-
file: file2,
|
|
34652
|
-
absPath,
|
|
34653
|
-
label: labelFor(file2),
|
|
34654
|
-
stashedAt: m ? parseStamp(m[1]) : null,
|
|
34655
|
-
side: side === "local" || side === "cloud" ? side : null,
|
|
34656
|
-
mtimeMs: st.mtimeMs
|
|
34657
|
-
});
|
|
34658
|
-
}
|
|
34659
|
-
return out.sort((a, b) => (b.stashedAt?.getTime() ?? b.mtimeMs) - (a.stashedAt?.getTime() ?? a.mtimeMs));
|
|
34660
|
-
}
|
|
34947
|
+
import { readFileSync as readFileSync21 } from "node:fs";
|
|
34661
34948
|
|
|
34662
34949
|
// src/lib/files/push.ts
|
|
34663
|
-
import { readFileSync as
|
|
34950
|
+
import { readFileSync as readFileSync18, statSync as statSync7 } from "node:fs";
|
|
34664
34951
|
|
|
34665
34952
|
// src/lib/permanent-deletions.ts
|
|
34666
34953
|
import { createHash as createHash3, randomUUID } from "node:crypto";
|
|
@@ -34674,7 +34961,7 @@ import {
|
|
|
34674
34961
|
lstatSync as lstatSync4,
|
|
34675
34962
|
mkdirSync as mkdirSync11,
|
|
34676
34963
|
openSync as openSync4,
|
|
34677
|
-
readFileSync as
|
|
34964
|
+
readFileSync as readFileSync17,
|
|
34678
34965
|
renameSync as renameSync8,
|
|
34679
34966
|
unlinkSync as unlinkSync8,
|
|
34680
34967
|
writeSync as writeSync2
|
|
@@ -34865,7 +35152,7 @@ function loadPermanentDeletionJournal(workspaceSlug) {
|
|
|
34865
35152
|
return emptyJournal(workspaceSlug);
|
|
34866
35153
|
assertPrivateDirectory(dirname10(target));
|
|
34867
35154
|
assertOwnerOnlyFile(target);
|
|
34868
|
-
const journal = parseJournal(workspaceSlug,
|
|
35155
|
+
const journal = parseJournal(workspaceSlug, readFileSync17(target, "utf8"));
|
|
34869
35156
|
try {
|
|
34870
35157
|
fsyncDirectory(dirname10(target));
|
|
34871
35158
|
} catch (error52) {
|
|
@@ -34932,7 +35219,7 @@ function writePermanentDeletionJournal(prior, next) {
|
|
|
34932
35219
|
let installed2 = false;
|
|
34933
35220
|
try {
|
|
34934
35221
|
assertOwnerOnlyFile(target);
|
|
34935
|
-
const candidate = parseJournal(body.workspace_slug,
|
|
35222
|
+
const candidate = parseJournal(body.workspace_slug, readFileSync17(target, "utf8"));
|
|
34936
35223
|
installed2 = candidate.revision === body.revision && checksum(candidate) === checksum(body);
|
|
34937
35224
|
} catch {
|
|
34938
35225
|
installed2 = false;
|
|
@@ -34946,7 +35233,7 @@ function writePermanentDeletionJournal(prior, next) {
|
|
|
34946
35233
|
}
|
|
34947
35234
|
}
|
|
34948
35235
|
assertOwnerOnlyFile(target);
|
|
34949
|
-
const installed = parseJournal(body.workspace_slug,
|
|
35236
|
+
const installed = parseJournal(body.workspace_slug, readFileSync17(target, "utf8"));
|
|
34950
35237
|
if (installed.revision !== body.revision || checksum(installed) !== checksum(body)) {
|
|
34951
35238
|
throw new PermanentDeletionJournalError("renamed journal revision did not validate", "durability_indeterminate");
|
|
34952
35239
|
}
|
|
@@ -34954,7 +35241,7 @@ function writePermanentDeletionJournal(prior, next) {
|
|
|
34954
35241
|
fsyncDirectory(parent);
|
|
34955
35242
|
} catch (firstError) {
|
|
34956
35243
|
assertOwnerOnlyFile(target);
|
|
34957
|
-
const reopened = parseJournal(body.workspace_slug,
|
|
35244
|
+
const reopened = parseJournal(body.workspace_slug, readFileSync17(target, "utf8"));
|
|
34958
35245
|
if (reopened.revision !== body.revision || checksum(reopened) !== checksum(body)) {
|
|
34959
35246
|
throw new PermanentDeletionJournalError("journal changed after rename", "durability_indeterminate");
|
|
34960
35247
|
}
|
|
@@ -35453,7 +35740,7 @@ async function pushFilesUpsertLocked(args) {
|
|
|
35453
35740
|
state: next
|
|
35454
35741
|
};
|
|
35455
35742
|
}
|
|
35456
|
-
bytes =
|
|
35743
|
+
bytes = readFileSync18(absolutePath);
|
|
35457
35744
|
hash2 = sha256Bytes(bytes);
|
|
35458
35745
|
inode = st.ino;
|
|
35459
35746
|
}
|
|
@@ -35715,12 +36002,64 @@ async function pushFilesDelete(args) {
|
|
|
35715
36002
|
}
|
|
35716
36003
|
|
|
35717
36004
|
// src/lib/push.ts
|
|
35718
|
-
|
|
35719
|
-
import { existsSync as existsSync5, readFileSync as readFileSync19, statSync as statSync8 } from "node:fs";
|
|
36005
|
+
import { existsSync as existsSync5, readFileSync as readFileSync20, statSync as statSync8 } from "node:fs";
|
|
35720
36006
|
import { basename as basename5, relative as relative5 } from "node:path";
|
|
35721
36007
|
|
|
36008
|
+
// src/lib/frontmatter-held.ts
|
|
36009
|
+
import { lstatSync as lstatSync5, readFileSync as readFileSync19 } from "node:fs";
|
|
36010
|
+
import { resolve as resolve4, sep as sep5 } from "node:path";
|
|
36011
|
+
function holdUnreadableFrontmatter(args) {
|
|
36012
|
+
const next = recordEntityFrontmatterUnreadable(args);
|
|
36013
|
+
args.output?.log(entityPushHeldLine({
|
|
36014
|
+
entityType: args.entityType,
|
|
36015
|
+
slug: args.slug,
|
|
36016
|
+
reason: FRONTMATTER_UNREADABLE_REASON
|
|
36017
|
+
}));
|
|
36018
|
+
if (next !== args.state) {
|
|
36019
|
+
args.output?.warn(`${args.entityType}/${args.slug}: ${frontmatterUnreadableSentence(args.why)}`);
|
|
36020
|
+
}
|
|
36021
|
+
return next;
|
|
36022
|
+
}
|
|
36023
|
+
function settleFrontmatterHolds(state, workspaceSlug) {
|
|
36024
|
+
const held = listRejected(state).filter(isFrontmatterHold);
|
|
36025
|
+
if (held.length === 0)
|
|
36026
|
+
return state;
|
|
36027
|
+
let root;
|
|
36028
|
+
try {
|
|
36029
|
+
root = workspaceMirrorPath(workspaceSlug);
|
|
36030
|
+
} catch {
|
|
36031
|
+
return state;
|
|
36032
|
+
}
|
|
36033
|
+
const settled = new Set(held.map((record2) => record2.path).filter((path5) => !stillHeld(workspaceSlug, root, path5)));
|
|
36034
|
+
if (settled.size === 0)
|
|
36035
|
+
return state;
|
|
36036
|
+
return commitState(state, {
|
|
36037
|
+
...state,
|
|
36038
|
+
rejected: listRejected(state).filter((o) => !(isFrontmatterHold(o) && settled.has(o.path)))
|
|
36039
|
+
});
|
|
36040
|
+
}
|
|
36041
|
+
function isFrontmatterHold(record2) {
|
|
36042
|
+
return record2.target === "entity" && record2.reason === FRONTMATTER_UNREADABLE_REASON;
|
|
36043
|
+
}
|
|
36044
|
+
function stillHeld(workspaceSlug, root, recordPath) {
|
|
36045
|
+
const absolutePath = resolve4(root, recordPath);
|
|
36046
|
+
if (!absolutePath.startsWith(root + sep5))
|
|
36047
|
+
return true;
|
|
36048
|
+
if (hasSymlinkedComponent(workspaceSlug, absolutePath))
|
|
36049
|
+
return true;
|
|
36050
|
+
let raw;
|
|
36051
|
+
try {
|
|
36052
|
+
if (!lstatSync5(absolutePath).isFile())
|
|
36053
|
+
return true;
|
|
36054
|
+
raw = readFileSync19(absolutePath, "utf8");
|
|
36055
|
+
} catch (err) {
|
|
36056
|
+
const code = err.code;
|
|
36057
|
+
return code !== "ENOENT" && code !== "ENOTDIR";
|
|
36058
|
+
}
|
|
36059
|
+
return frontmatterParseError(raw) !== null;
|
|
36060
|
+
}
|
|
36061
|
+
|
|
35722
36062
|
// src/lib/frontmatter-id.ts
|
|
35723
|
-
var import_gray_matter8 = __toESM(require_gray_matter(), 1);
|
|
35724
36063
|
function eolOf(content) {
|
|
35725
36064
|
return /\r\n/.test(content) ? `\r
|
|
35726
36065
|
` : `
|
|
@@ -35757,158 +36096,16 @@ function withFrontmatterId(raw, parsed, id) {
|
|
|
35757
36096
|
}
|
|
35758
36097
|
function frontmatterIdWriteIsSound(next, id, expectedBody) {
|
|
35759
36098
|
try {
|
|
35760
|
-
const check2 =
|
|
36099
|
+
const check2 = matter(next);
|
|
35761
36100
|
return check2.data?.["id"] === id && check2.content === expectedBody;
|
|
35762
36101
|
} catch {
|
|
35763
36102
|
return false;
|
|
35764
36103
|
}
|
|
35765
36104
|
}
|
|
35766
36105
|
|
|
35767
|
-
// src/lib/pull-write.ts
|
|
35768
|
-
var import_gray_matter9 = __toESM(require_gray_matter(), 1);
|
|
35769
|
-
import { readFileSync as readFileSync18 } from "node:fs";
|
|
35770
|
-
function computeMirrorHashes(strategy, content) {
|
|
35771
|
-
const parsed = import_gray_matter9.default(content);
|
|
35772
|
-
const fm = parsed.data ?? {};
|
|
35773
|
-
const lifted = splitUserFrontmatter(strategy, fm);
|
|
35774
|
-
const userFm = userFrontmatter(strategy, lifted.frontmatter);
|
|
35775
|
-
const hashBody = hashableBody(strategy, parsed.content);
|
|
35776
|
-
return {
|
|
35777
|
-
bodyHash: stableHash(hashBody),
|
|
35778
|
-
frontmatterHash: stableHash({ ...userFm, ...lifted.topLevel }),
|
|
35779
|
-
bodyLen: hashBody.length,
|
|
35780
|
-
frontmatterId: fm["id"]
|
|
35781
|
-
};
|
|
35782
|
-
}
|
|
35783
|
-
function hashableBody(strategy, body) {
|
|
35784
|
-
return strategy.stripLocalBlocks?.(body) ?? body;
|
|
35785
|
-
}
|
|
35786
|
-
function relocateForSlugChange(args) {
|
|
35787
|
-
const { workspaceSlug, strategy, fromSlug, toSlug, acked, echo, now } = args;
|
|
35788
|
-
if (fromSlug === toSlug)
|
|
35789
|
-
return { kind: "not_needed", writeSlug: toSlug };
|
|
35790
|
-
if (!echo) {
|
|
35791
|
-
return { kind: "skipped", writeSlug: toSlug, fromSlug, reason: "unguarded" };
|
|
35792
|
-
}
|
|
35793
|
-
if (strategy.mirrorRelativePath(fromSlug).split("/").length !== 2) {
|
|
35794
|
-
return { kind: "skipped", writeSlug: toSlug, fromSlug, reason: "foldered" };
|
|
35795
|
-
}
|
|
35796
|
-
let fromPath;
|
|
35797
|
-
try {
|
|
35798
|
-
fromPath = entityFilePath(workspaceSlug, strategy.type, fromSlug);
|
|
35799
|
-
entityFilePath(workspaceSlug, strategy.type, toSlug);
|
|
35800
|
-
} catch (err) {
|
|
35801
|
-
if (err instanceof MirrorPathError) {
|
|
35802
|
-
return {
|
|
35803
|
-
kind: "skipped",
|
|
35804
|
-
writeSlug: toSlug,
|
|
35805
|
-
fromSlug,
|
|
35806
|
-
reason: "unresolvable"
|
|
35807
|
-
};
|
|
35808
|
-
}
|
|
35809
|
-
throw err;
|
|
35810
|
-
}
|
|
35811
|
-
let raw = null;
|
|
35812
|
-
try {
|
|
35813
|
-
raw = readFileSync18(fromPath, "utf8");
|
|
35814
|
-
} catch (err) {
|
|
35815
|
-
const code = err?.code;
|
|
35816
|
-
if (code === "ENOENT") {
|
|
35817
|
-
return { kind: "absent", writeSlug: toSlug, fromSlug };
|
|
35818
|
-
}
|
|
35819
|
-
return {
|
|
35820
|
-
kind: "refused",
|
|
35821
|
-
writeSlug: fromSlug,
|
|
35822
|
-
fromSlug,
|
|
35823
|
-
reason: "source_unreadable"
|
|
35824
|
-
};
|
|
35825
|
-
}
|
|
35826
|
-
let diverged = true;
|
|
35827
|
-
try {
|
|
35828
|
-
const hashes = computeMirrorHashes(strategy, raw);
|
|
35829
|
-
diverged = hashes.bodyHash !== acked.bodyHash || hashes.frontmatterHash !== acked.frontmatterHash;
|
|
35830
|
-
} catch {
|
|
35831
|
-
diverged = true;
|
|
35832
|
-
}
|
|
35833
|
-
const stashPath = diverged ? stashConflict({
|
|
35834
|
-
workspaceSlug,
|
|
35835
|
-
entityType: strategy.type,
|
|
35836
|
-
entitySlug: fromSlug,
|
|
35837
|
-
timestamp: now(),
|
|
35838
|
-
contents: raw,
|
|
35839
|
-
side: "local"
|
|
35840
|
-
}) : undefined;
|
|
35841
|
-
try {
|
|
35842
|
-
let registered = false;
|
|
35843
|
-
const result = moveEntityFile(workspaceSlug, strategy.type, fromSlug, toSlug, () => {
|
|
35844
|
-
if (echo.watcher === "live") {
|
|
35845
|
-
echo.suppressor.registerDelete(fromPath);
|
|
35846
|
-
registered = true;
|
|
35847
|
-
}
|
|
35848
|
-
});
|
|
35849
|
-
if (result === "moved") {
|
|
35850
|
-
return { kind: "moved", writeSlug: toSlug, fromSlug, stashPath };
|
|
35851
|
-
}
|
|
35852
|
-
if (registered && echo.watcher === "live") {
|
|
35853
|
-
echo.suppressor.cancelDelete(fromPath);
|
|
35854
|
-
}
|
|
35855
|
-
return { kind: "absent", writeSlug: toSlug, fromSlug };
|
|
35856
|
-
} catch (err) {
|
|
35857
|
-
if (err instanceof MirrorDestinationOccupiedError) {
|
|
35858
|
-
return {
|
|
35859
|
-
kind: "refused",
|
|
35860
|
-
writeSlug: fromSlug,
|
|
35861
|
-
fromSlug,
|
|
35862
|
-
reason: "destination_occupied",
|
|
35863
|
-
stashPath
|
|
35864
|
-
};
|
|
35865
|
-
}
|
|
35866
|
-
if (err instanceof MirrorPathError) {
|
|
35867
|
-
return {
|
|
35868
|
-
kind: "skipped",
|
|
35869
|
-
writeSlug: toSlug,
|
|
35870
|
-
fromSlug,
|
|
35871
|
-
reason: "unresolvable"
|
|
35872
|
-
};
|
|
35873
|
-
}
|
|
35874
|
-
throw err;
|
|
35875
|
-
}
|
|
35876
|
-
}
|
|
35877
|
-
function writeMirrorWithBodyPreserve(workspaceSlug, strategy, doc2, suppressor, pathSlug) {
|
|
35878
|
-
const targetSlug = pathSlug ?? doc2.slug;
|
|
35879
|
-
const content = renderMirrorWithBodyPreserve(workspaceSlug, strategy, doc2, targetSlug);
|
|
35880
|
-
if (suppressor) {
|
|
35881
|
-
const target = entityFilePath(workspaceSlug, strategy.type, targetSlug);
|
|
35882
|
-
suppressor.registerWrite(target, computeMirrorHashes(strategy, content));
|
|
35883
|
-
}
|
|
35884
|
-
writeMirrorFile(workspaceSlug, strategy.type, targetSlug, content);
|
|
35885
|
-
return content;
|
|
35886
|
-
}
|
|
35887
|
-
function renderMirrorWithBodyPreserve(workspaceSlug, strategy, doc2, targetSlug) {
|
|
35888
|
-
let content = strategy.serialise(doc2);
|
|
35889
|
-
if (strategy.preserveLocalBodyOnPull) {
|
|
35890
|
-
const target = entityFilePath(workspaceSlug, strategy.type, targetSlug);
|
|
35891
|
-
let existing = null;
|
|
35892
|
-
try {
|
|
35893
|
-
existing = readFileSync18(target, "utf8");
|
|
35894
|
-
} catch (err) {
|
|
35895
|
-
if (err?.code !== "ENOENT")
|
|
35896
|
-
throw err;
|
|
35897
|
-
}
|
|
35898
|
-
if (existing !== null) {
|
|
35899
|
-
const existingParsed = import_gray_matter9.default(existing);
|
|
35900
|
-
const cloudParsed = import_gray_matter9.default(content);
|
|
35901
|
-
const stitched = import_gray_matter9.default.stringify(existingParsed.content, cloudParsed.data);
|
|
35902
|
-
content = stitched.endsWith(`
|
|
35903
|
-
`) ? stitched : stitched + `
|
|
35904
|
-
`;
|
|
35905
|
-
}
|
|
35906
|
-
}
|
|
35907
|
-
return content;
|
|
35908
|
-
}
|
|
35909
|
-
|
|
35910
36106
|
// src/lib/push.ts
|
|
35911
36107
|
var DELETE_DECLINED_PATH_EXISTS = "the file is on disk again — a newer write owns this row";
|
|
36108
|
+
var FRONTMATTER_PARSE_ERROR = "frontmatter parse error";
|
|
35912
36109
|
function deleteDeclinedSentence(relPath) {
|
|
35913
36110
|
return `Not deleted: ${relPath} is on disk again, so the row was left alone. If you meant to delete it, remove the file and it will be retried.`;
|
|
35914
36111
|
}
|
|
@@ -36017,8 +36214,8 @@ function stampStateFromMirror(args) {
|
|
|
36017
36214
|
const { strategy, workspaceSlug, doc: doc2, state, priorMemoryHash } = args;
|
|
36018
36215
|
const targetSlug = args.pathSlug ?? doc2.slug;
|
|
36019
36216
|
const mirrorPath = entityFilePath(workspaceSlug, strategy.type, targetSlug);
|
|
36020
|
-
const mirrorRaw =
|
|
36021
|
-
const mirrorParsed =
|
|
36217
|
+
const mirrorRaw = readFileSync20(mirrorPath, "utf8");
|
|
36218
|
+
const mirrorParsed = matter(mirrorRaw);
|
|
36022
36219
|
const mirrorFm = mirrorParsed.data ?? {};
|
|
36023
36220
|
const mirrorLifted = splitUserFrontmatter(strategy, mirrorFm);
|
|
36024
36221
|
const mirrorUserFm = userFrontmatter(strategy, mirrorLifted.frontmatter);
|
|
@@ -36170,7 +36367,7 @@ async function pushFile(args) {
|
|
|
36170
36367
|
}
|
|
36171
36368
|
let raw;
|
|
36172
36369
|
try {
|
|
36173
|
-
raw =
|
|
36370
|
+
raw = readFileSync20(absolutePath, "utf8");
|
|
36174
36371
|
} catch (err) {
|
|
36175
36372
|
if (err?.code === "ENOENT") {
|
|
36176
36373
|
return runPushDelete({
|
|
@@ -36190,23 +36387,32 @@ async function pushFile(args) {
|
|
|
36190
36387
|
}
|
|
36191
36388
|
throw err;
|
|
36192
36389
|
}
|
|
36193
|
-
let parsed;
|
|
36194
|
-
try {
|
|
36195
|
-
parsed = import_gray_matter10.default(raw);
|
|
36196
|
-
} catch {
|
|
36197
|
-
return {
|
|
36198
|
-
outcome: { kind: "ignored", absolutePath, reason: "frontmatter parse error" },
|
|
36199
|
-
state
|
|
36200
|
-
};
|
|
36201
|
-
}
|
|
36202
|
-
const fm = parsed.data ?? {};
|
|
36203
|
-
const slugFromFm = typeof fm.slug === "string" && fm.slug.length > 0 ? fm.slug : null;
|
|
36204
36390
|
let parsedSlug = null;
|
|
36205
36391
|
try {
|
|
36206
36392
|
const rel = relative5(workspaceMirrorPath(workspaceSlug), absolutePath);
|
|
36207
36393
|
parsedSlug = strategy.parseMirrorPath(rel)?.slug ?? null;
|
|
36208
36394
|
} catch {}
|
|
36209
36395
|
const slugFromPath = parsedSlug ?? basename5(absolutePath, ".md");
|
|
36396
|
+
const read = readFrontmatter(raw);
|
|
36397
|
+
if (!read.ok) {
|
|
36398
|
+
return {
|
|
36399
|
+
outcome: { kind: "ignored", absolutePath, reason: FRONTMATTER_PARSE_ERROR },
|
|
36400
|
+
state: holdUnreadableFrontmatter({
|
|
36401
|
+
state,
|
|
36402
|
+
workspaceSlug,
|
|
36403
|
+
absolutePath,
|
|
36404
|
+
entityType,
|
|
36405
|
+
slug: slugFromPath,
|
|
36406
|
+
why: read.why,
|
|
36407
|
+
now,
|
|
36408
|
+
output
|
|
36409
|
+
}),
|
|
36410
|
+
base: state
|
|
36411
|
+
};
|
|
36412
|
+
}
|
|
36413
|
+
let parsed = read.parsed;
|
|
36414
|
+
const fm = parsed.data ?? {};
|
|
36415
|
+
const slugFromFm = typeof fm.slug === "string" && fm.slug.length > 0 ? fm.slug : null;
|
|
36210
36416
|
const slug = identity === "path" ? slugFromPath : slugFromFm ?? slugFromPath;
|
|
36211
36417
|
if (!slug) {
|
|
36212
36418
|
return {
|
|
@@ -36220,14 +36426,14 @@ async function pushFile(args) {
|
|
|
36220
36426
|
if (strategy.classifyFieldErrors) {
|
|
36221
36427
|
const desiredBody = spliceSyncErrorBlock(parsed.content, fieldErrors);
|
|
36222
36428
|
if (desiredBody.replace(/\s+$/, "") !== parsed.content.replace(/\s+$/, "")) {
|
|
36223
|
-
let rewritten =
|
|
36429
|
+
let rewritten = stringifyFrontmatter(desiredBody, parsed.data ?? {});
|
|
36224
36430
|
if (!rewritten.endsWith(`
|
|
36225
36431
|
`))
|
|
36226
36432
|
rewritten += `
|
|
36227
36433
|
`;
|
|
36228
36434
|
writeMirrorFile(workspaceSlug, entityType, slug, rewritten);
|
|
36229
36435
|
raw = rewritten;
|
|
36230
|
-
parsed =
|
|
36436
|
+
parsed = parseUncached(rewritten);
|
|
36231
36437
|
}
|
|
36232
36438
|
}
|
|
36233
36439
|
const liveInode = safeInode2(absolutePath);
|
|
@@ -36505,7 +36711,7 @@ async function runPushUpsert(args) {
|
|
|
36505
36711
|
}
|
|
36506
36712
|
const wireProjection = (md) => {
|
|
36507
36713
|
try {
|
|
36508
|
-
const p =
|
|
36714
|
+
const p = matter(md);
|
|
36509
36715
|
const f = p.data ?? {};
|
|
36510
36716
|
const l = splitUserFrontmatter(strategy, f);
|
|
36511
36717
|
const u = userFrontmatter(strategy, l.frontmatter);
|
|
@@ -36520,7 +36726,7 @@ async function runPushUpsert(args) {
|
|
|
36520
36726
|
if (liveEntry !== snapshotEntry) {
|
|
36521
36727
|
let onDisk = null;
|
|
36522
36728
|
try {
|
|
36523
|
-
onDisk =
|
|
36729
|
+
onDisk = readFileSync20(args.absolutePath, "utf8");
|
|
36524
36730
|
} catch {
|
|
36525
36731
|
onDisk = null;
|
|
36526
36732
|
}
|
|
@@ -36982,7 +37188,7 @@ function writeCreatedIdBack(args) {
|
|
|
36982
37188
|
if (entityFilePath(args.workspaceSlug, args.strategy.type, args.slug) !== args.absolutePath) {
|
|
36983
37189
|
return;
|
|
36984
37190
|
}
|
|
36985
|
-
const readBack = args.readBack ?? ((p) =>
|
|
37191
|
+
const readBack = args.readBack ?? ((p) => readFileSync20(p, "utf8"));
|
|
36986
37192
|
if (readBack(args.absolutePath) !== args.raw)
|
|
36987
37193
|
return;
|
|
36988
37194
|
args.suppressor?.registerWrite(args.absolutePath, computeMirrorHashes(args.strategy, write.content));
|
|
@@ -37454,8 +37660,8 @@ async function pushRename(args) {
|
|
|
37454
37660
|
};
|
|
37455
37661
|
if (!updated.body_hash || !updated.frontmatter_hash) {
|
|
37456
37662
|
try {
|
|
37457
|
-
const mirrorRaw =
|
|
37458
|
-
const mirrorParsed =
|
|
37663
|
+
const mirrorRaw = readFileSync20(toPath, "utf8");
|
|
37664
|
+
const mirrorParsed = matter(mirrorRaw);
|
|
37459
37665
|
const renameFm = mirrorParsed.data ?? {};
|
|
37460
37666
|
const renameLifted = splitUserFrontmatter(strategy, renameFm);
|
|
37461
37667
|
const renameUserFm = userFrontmatter(strategy, renameLifted.frontmatter);
|
|
@@ -37491,7 +37697,7 @@ async function pushRename(args) {
|
|
|
37491
37697
|
if (patchResult.reason === "slug_conflict") {
|
|
37492
37698
|
let contents2 = "";
|
|
37493
37699
|
try {
|
|
37494
|
-
contents2 =
|
|
37700
|
+
contents2 = readFileSync20(toPath, "utf8");
|
|
37495
37701
|
} catch {}
|
|
37496
37702
|
const stashPath2 = stashConflict({
|
|
37497
37703
|
workspaceSlug,
|
|
@@ -37539,7 +37745,7 @@ async function pushRename(args) {
|
|
|
37539
37745
|
}
|
|
37540
37746
|
let contents = "";
|
|
37541
37747
|
try {
|
|
37542
|
-
contents =
|
|
37748
|
+
contents = readFileSync20(toPath, "utf8");
|
|
37543
37749
|
} catch {}
|
|
37544
37750
|
const stashPath = stashConflict({
|
|
37545
37751
|
workspaceSlug,
|
|
@@ -37672,7 +37878,7 @@ async function discardBatchOps(batch, deps) {
|
|
|
37672
37878
|
const path5 = opPath(deps.workspaceSlug, op);
|
|
37673
37879
|
let local = null;
|
|
37674
37880
|
try {
|
|
37675
|
-
local =
|
|
37881
|
+
local = readFileSync21(path5, "utf8");
|
|
37676
37882
|
} catch {
|
|
37677
37883
|
local = null;
|
|
37678
37884
|
}
|
|
@@ -37711,7 +37917,7 @@ async function discardFileOp(op, deps) {
|
|
|
37711
37917
|
const abs = filesSubtreePath(deps.workspaceSlug, op.path);
|
|
37712
37918
|
let local = null;
|
|
37713
37919
|
try {
|
|
37714
|
-
local =
|
|
37920
|
+
local = readFileSync21(abs);
|
|
37715
37921
|
} catch {
|
|
37716
37922
|
local = null;
|
|
37717
37923
|
}
|
|
@@ -37748,7 +37954,7 @@ function opPath(workspaceSlug, op) {
|
|
|
37748
37954
|
}
|
|
37749
37955
|
|
|
37750
37956
|
// src/lib/files/classify.ts
|
|
37751
|
-
import { relative as relative6, sep as
|
|
37957
|
+
import { relative as relative6, sep as sep6 } from "node:path";
|
|
37752
37958
|
var FILES_SUBTREE = "files";
|
|
37753
37959
|
function classifyFilesPath(args) {
|
|
37754
37960
|
let root;
|
|
@@ -37758,20 +37964,20 @@ function classifyFilesPath(args) {
|
|
|
37758
37964
|
return null;
|
|
37759
37965
|
}
|
|
37760
37966
|
const rel = relative6(root, args.absolutePath);
|
|
37761
|
-
if (rel.startsWith("..") || rel.startsWith(
|
|
37967
|
+
if (rel.startsWith("..") || rel.startsWith(sep6))
|
|
37762
37968
|
return null;
|
|
37763
|
-
const prefix = FILES_SUBTREE +
|
|
37969
|
+
const prefix = FILES_SUBTREE + sep6;
|
|
37764
37970
|
if (!rel.startsWith(prefix))
|
|
37765
37971
|
return null;
|
|
37766
37972
|
const sub = rel.slice(prefix.length);
|
|
37767
37973
|
if (sub.length === 0)
|
|
37768
37974
|
return null;
|
|
37769
|
-
const posix =
|
|
37975
|
+
const posix = sep6 === "/" ? sub : sub.split(sep6).join("/");
|
|
37770
37976
|
return validateFilesRelPath(posix) ? { relPath: posix } : null;
|
|
37771
37977
|
}
|
|
37772
37978
|
|
|
37773
37979
|
// src/lib/memory-facts/sync.ts
|
|
37774
|
-
import { isAbsolute as isAbsolute2, relative as relative7, sep as
|
|
37980
|
+
import { isAbsolute as isAbsolute2, relative as relative7, sep as sep7 } from "node:path";
|
|
37775
37981
|
|
|
37776
37982
|
// src/lib/memory-facts/api.ts
|
|
37777
37983
|
var FactCommonSchema = exports_external.object({
|
|
@@ -38054,7 +38260,7 @@ function unknownBecause(reason) {
|
|
|
38054
38260
|
case "no_watermark":
|
|
38055
38261
|
return "The last download carried no freshness marker, so there is nothing to compare against.";
|
|
38056
38262
|
case "incomplete_apply":
|
|
38057
|
-
return "The last download did not fully land, so this copy is not
|
|
38263
|
+
return "The last download either did not fully land or carried a correction trail this copy could not use, so this copy is not one the freshness marker can vouch for.";
|
|
38058
38264
|
case "check_failed":
|
|
38059
38265
|
return "The freshness check did not complete.";
|
|
38060
38266
|
default:
|
|
@@ -38211,6 +38417,9 @@ function resolveTrail(row, slugById) {
|
|
|
38211
38417
|
consistent: true
|
|
38212
38418
|
};
|
|
38213
38419
|
}
|
|
38420
|
+
if (outcome === "current" && successor === row.id) {
|
|
38421
|
+
return { trail: "current", consistent: true };
|
|
38422
|
+
}
|
|
38214
38423
|
if (successor !== null)
|
|
38215
38424
|
return { trail: UNKNOWN, consistent: false };
|
|
38216
38425
|
return { trail: outcome, consistent: true };
|
|
@@ -38512,7 +38721,7 @@ function classifyMemoryFactPath(args) {
|
|
|
38512
38721
|
const rel = relative7(root, args.absolutePath);
|
|
38513
38722
|
if (rel === "" || rel.startsWith("..") || isAbsolute2(rel))
|
|
38514
38723
|
return null;
|
|
38515
|
-
const mirrorRel = `${MEMORY_FACTS_DIR}/${rel.split(
|
|
38724
|
+
const mirrorRel = `${MEMORY_FACTS_DIR}/${rel.split(sep7).join("/")}`;
|
|
38516
38725
|
return { relPath: mirrorRel, fact: parseMemoryFactRelPath(mirrorRel) };
|
|
38517
38726
|
}
|
|
38518
38727
|
function classifyMemoryFactEvent(args) {
|
|
@@ -38609,7 +38818,7 @@ function evaluateFileBatch(ops, population, thresholds = defaultThresholds()) {
|
|
|
38609
38818
|
}
|
|
38610
38819
|
};
|
|
38611
38820
|
}
|
|
38612
|
-
return
|
|
38821
|
+
return evaluateLoneOverwrite(count, ops.map((op) => op.kind === "overwrite" ? op.priorSize - op.newSize : 0), thresholds.loneMinRemovedBytes);
|
|
38613
38822
|
}
|
|
38614
38823
|
function dedupeFileOps(ops) {
|
|
38615
38824
|
const byKey = new Map;
|
|
@@ -38680,7 +38889,7 @@ class FileDestructiveGate {
|
|
|
38680
38889
|
}
|
|
38681
38890
|
|
|
38682
38891
|
// src/lib/files/reconcile.ts
|
|
38683
|
-
import { readFileSync as
|
|
38892
|
+
import { readFileSync as readFileSync22, statSync as statSync10 } from "node:fs";
|
|
38684
38893
|
|
|
38685
38894
|
// src/lib/unsynced.ts
|
|
38686
38895
|
import { join as join14, relative as relative8 } from "node:path";
|
|
@@ -39676,7 +39885,7 @@ class NodeFsHandler {
|
|
|
39676
39885
|
this._addToNodeFs(path5, initialAdd, wh, depth + 1);
|
|
39677
39886
|
}
|
|
39678
39887
|
}).on(EV.ERROR, this._boundHandleError);
|
|
39679
|
-
return new Promise((
|
|
39888
|
+
return new Promise((resolve6, reject) => {
|
|
39680
39889
|
if (!stream)
|
|
39681
39890
|
return reject();
|
|
39682
39891
|
stream.once(STR_END, () => {
|
|
@@ -39685,7 +39894,7 @@ class NodeFsHandler {
|
|
|
39685
39894
|
return;
|
|
39686
39895
|
}
|
|
39687
39896
|
const wasThrottled = throttler ? throttler.clear() : false;
|
|
39688
|
-
|
|
39897
|
+
resolve6(undefined);
|
|
39689
39898
|
previous.getChildren().filter((item) => {
|
|
39690
39899
|
return item !== directory && !current.has(item);
|
|
39691
39900
|
}).forEach((item) => {
|
|
@@ -40444,7 +40653,7 @@ var chokidar_default = { watch, FSWatcher };
|
|
|
40444
40653
|
|
|
40445
40654
|
// src/lib/watcher.ts
|
|
40446
40655
|
import { statSync as statSync9 } from "node:fs";
|
|
40447
|
-
import { sep as
|
|
40656
|
+
import { sep as sep8 } from "node:path";
|
|
40448
40657
|
|
|
40449
40658
|
// src/lib/rename-classifier.ts
|
|
40450
40659
|
class RenameClassifier {
|
|
@@ -40575,7 +40784,7 @@ function watcherIgnoreMatcher(workspaceRoot) {
|
|
|
40575
40784
|
return (absolutePath) => patterns.some((re) => re.test(absolutePath));
|
|
40576
40785
|
}
|
|
40577
40786
|
function escSep() {
|
|
40578
|
-
return
|
|
40787
|
+
return sep8 === "/" ? "/" : "\\\\";
|
|
40579
40788
|
}
|
|
40580
40789
|
|
|
40581
40790
|
class DebounceMap {
|
|
@@ -40675,7 +40884,12 @@ function startWatcher(opts, handler) {
|
|
|
40675
40884
|
});
|
|
40676
40885
|
watcher.on("add", (p) => classifier.onAdd(p));
|
|
40677
40886
|
watcher.on("change", (p) => classifier.onChange(p));
|
|
40678
|
-
watcher.on("unlink", (p) =>
|
|
40887
|
+
watcher.on("unlink", (p) => {
|
|
40888
|
+
try {
|
|
40889
|
+
opts.onUnlink?.(p);
|
|
40890
|
+
} catch {}
|
|
40891
|
+
classifier.onUnlink(p);
|
|
40892
|
+
});
|
|
40679
40893
|
return {
|
|
40680
40894
|
async stop() {
|
|
40681
40895
|
classifier.shutdown();
|
|
@@ -40714,7 +40928,7 @@ function readBlobForPush(args) {
|
|
|
40714
40928
|
}
|
|
40715
40929
|
let bytes;
|
|
40716
40930
|
try {
|
|
40717
|
-
bytes =
|
|
40931
|
+
bytes = readFileSync22(absolutePath);
|
|
40718
40932
|
} catch (err) {
|
|
40719
40933
|
if (err?.code === "ENOENT")
|
|
40720
40934
|
return { kind: "gone" };
|
|
@@ -40857,7 +41071,7 @@ async function reconcileFilesSubtree(args) {
|
|
|
40857
41071
|
ops: pendingOps
|
|
40858
41072
|
});
|
|
40859
41073
|
quarantined = ops.length;
|
|
40860
|
-
output?.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${ops.length} reason=${result.reason
|
|
41074
|
+
output?.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${ops.length} reason=${tripReasonToken(result.reason)} source=files-reconcile]`);
|
|
40861
41075
|
} else {
|
|
40862
41076
|
for (const c of guttingCandidates) {
|
|
40863
41077
|
await pushDivergedBlob(c.op.relPath, c.op.absolutePath, c.prepared);
|
|
@@ -41024,7 +41238,7 @@ function applyArtistMemoryOnPull(args) {
|
|
|
41024
41238
|
const cloudMemory = live.memory;
|
|
41025
41239
|
const generated = live.memory_generated === true;
|
|
41026
41240
|
const local = readArtistMemoryFile(workspaceSlug, slug);
|
|
41027
|
-
const
|
|
41241
|
+
const refuse2 = (localContent, canonical2) => {
|
|
41028
41242
|
const refused = refuseGeneratedMemoryEdit({
|
|
41029
41243
|
state,
|
|
41030
41244
|
workspaceSlug,
|
|
@@ -41053,7 +41267,7 @@ function applyArtistMemoryOnPull(args) {
|
|
|
41053
41267
|
const cloudHash = stableHash(cloudMemory);
|
|
41054
41268
|
if (cloudHash === ackedMemoryHash && local !== null) {
|
|
41055
41269
|
if (generated && stableHash(local) !== ackedMemoryHash) {
|
|
41056
|
-
|
|
41270
|
+
refuse2(local, cloudMemory);
|
|
41057
41271
|
return { memoryHash: cloudHash, state };
|
|
41058
41272
|
}
|
|
41059
41273
|
if (generated) {
|
|
@@ -41065,7 +41279,7 @@ function applyArtistMemoryOnPull(args) {
|
|
|
41065
41279
|
const localHash = stableHash(local);
|
|
41066
41280
|
if (localHash !== ackedMemoryHash && localHash !== cloudHash) {
|
|
41067
41281
|
if (generated) {
|
|
41068
|
-
|
|
41282
|
+
refuse2(local, cloudMemory);
|
|
41069
41283
|
return { memoryHash: cloudHash, state };
|
|
41070
41284
|
}
|
|
41071
41285
|
const stashPath = stashConflict({
|
|
@@ -41775,7 +41989,7 @@ function anyUnknown(state) {
|
|
|
41775
41989
|
}
|
|
41776
41990
|
|
|
41777
41991
|
// src/lib/memory/write.ts
|
|
41778
|
-
import { readFileSync as
|
|
41992
|
+
import { readFileSync as readFileSync23, statSync as statSync11 } from "node:fs";
|
|
41779
41993
|
|
|
41780
41994
|
// src/lib/files/extracted-text.ts
|
|
41781
41995
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -41856,7 +42070,6 @@ function extractedTextHash(rendered) {
|
|
|
41856
42070
|
}
|
|
41857
42071
|
|
|
41858
42072
|
// src/lib/memory/splice.ts
|
|
41859
|
-
var import_gray_matter11 = __toESM(require_gray_matter(), 1);
|
|
41860
42073
|
var OPEN_RE = /^---\r?\n/;
|
|
41861
42074
|
function findFrontmatter(content) {
|
|
41862
42075
|
const open2 = OPEN_RE.exec(content);
|
|
@@ -41942,7 +42155,7 @@ function spliceAnnotation(content, lines) {
|
|
|
41942
42155
|
}
|
|
41943
42156
|
function parsesAsFrontmatter(content) {
|
|
41944
42157
|
try {
|
|
41945
|
-
|
|
42158
|
+
matter(content, { cache: false });
|
|
41946
42159
|
return true;
|
|
41947
42160
|
} catch {
|
|
41948
42161
|
return false;
|
|
@@ -41970,7 +42183,7 @@ function writeDocumentAnnotation(args) {
|
|
|
41970
42183
|
}
|
|
41971
42184
|
let current;
|
|
41972
42185
|
try {
|
|
41973
|
-
current =
|
|
42186
|
+
current = readFileSync23(target, "utf8");
|
|
41974
42187
|
} catch {
|
|
41975
42188
|
return { kind: "absent" };
|
|
41976
42189
|
}
|
|
@@ -42008,18 +42221,32 @@ function writeFileTextAnnotation(args) {
|
|
|
42008
42221
|
const current = readExtractedText(args.workspaceSlug, args.relPath);
|
|
42009
42222
|
if (current === null)
|
|
42010
42223
|
return { kind: "absent" };
|
|
42011
|
-
|
|
42224
|
+
const onDisk = extractedTextHash(current);
|
|
42225
|
+
const acked = args.textHash;
|
|
42226
|
+
const offAck = acked !== undefined && onDisk !== acked;
|
|
42227
|
+
if (acked !== undefined && offAck && !isOwnRender(current, acked, args.priorLines)) {
|
|
42012
42228
|
return { kind: "diverged", relPath: extractedTextRelPath(args.relPath) };
|
|
42013
42229
|
}
|
|
42014
42230
|
const spliced = spliceAnnotation(current, args.lines);
|
|
42015
|
-
if (!spliced.changed)
|
|
42016
|
-
return { kind: "unchanged" };
|
|
42231
|
+
if (!spliced.changed) {
|
|
42232
|
+
return offAck ? { kind: "unchanged", textHash: onDisk } : { kind: "unchanged" };
|
|
42233
|
+
}
|
|
42017
42234
|
writeExtractedText(args.workspaceSlug, args.relPath, spliced.content);
|
|
42018
42235
|
return { kind: "written", textHash: extractedTextHash(spliced.content) };
|
|
42019
42236
|
}
|
|
42237
|
+
function isOwnRender(current, ackedHash, priorLines) {
|
|
42238
|
+
if (extractedTextHash(spliceAnnotation(current, []).content) === ackedHash)
|
|
42239
|
+
return true;
|
|
42240
|
+
if (priorLines === undefined)
|
|
42241
|
+
return false;
|
|
42242
|
+
return extractedTextHash(spliceAnnotation(current, priorLines).content) === ackedHash;
|
|
42243
|
+
}
|
|
42020
42244
|
function divergedNotice(relPath, state) {
|
|
42021
42245
|
return `${relPath}: its memory context is now "${state}", but the file has local ` + `edits so the marker was not written into it. Your edits are untouched. ` + `Resolve the edit and the next sync writes the marker.`;
|
|
42022
42246
|
}
|
|
42247
|
+
function generatedDivergedNotice(relPath) {
|
|
42248
|
+
return `${relPath}: this generated file does not match Plinth's record of it, ` + `so its memory marker was not written. Plinth rewrites the file when its ` + `source next changes; changes made to it here are not synced.`;
|
|
42249
|
+
}
|
|
42023
42250
|
function unreadableNotice(relPath, why) {
|
|
42024
42251
|
const reason = why.replace(/\s+/g, " ").trim().slice(0, 120);
|
|
42025
42252
|
return `${relPath}: its memory context could not be written because the file's ` + `frontmatter could not be read (${reason}). The file is untouched. Fix the ` + `frontmatter and the next sync writes the marker.`;
|
|
@@ -42049,6 +42276,10 @@ function heldFileTexts(state) {
|
|
|
42049
42276
|
textHash: f.text_hash
|
|
42050
42277
|
}));
|
|
42051
42278
|
}
|
|
42279
|
+
function priorFileLines(state, id) {
|
|
42280
|
+
return getMemorySourceEntries(state, "workspace_files")[id]?.rendered.split(`
|
|
42281
|
+
`);
|
|
42282
|
+
}
|
|
42052
42283
|
async function writeChanged(args) {
|
|
42053
42284
|
const docs = new Map(heldDocuments(args.state).map((d) => [d.id, d.slug]));
|
|
42054
42285
|
const texts = new Map(heldFileTexts(args.state).map((f) => [f.id, f]));
|
|
@@ -42148,7 +42379,8 @@ async function writeChanged(args) {
|
|
|
42148
42379
|
workspaceSlug: args.workspaceSlug,
|
|
42149
42380
|
relPath: file2.relPath,
|
|
42150
42381
|
textHash: args.freshTextHashes?.get(answer2.id) ?? file2.textHash,
|
|
42151
|
-
lines: answer2.lines
|
|
42382
|
+
lines: answer2.lines,
|
|
42383
|
+
priorLines: priorFileLines(args.state, answer2.id)
|
|
42152
42384
|
});
|
|
42153
42385
|
if (res.kind !== "diverged" && res.kind !== "absent") {
|
|
42154
42386
|
args.onWritten?.("workspace_files", answer2.id, res.textHash);
|
|
@@ -42175,7 +42407,7 @@ async function writeChanged(args) {
|
|
|
42175
42407
|
}
|
|
42176
42408
|
const textPath = extractedTextRelPath(file2.relPath);
|
|
42177
42409
|
if (outcome.kind === "diverged") {
|
|
42178
|
-
report.push({ path: textPath, record:
|
|
42410
|
+
report.push({ path: textPath, record: generatedDivergedNotice(textPath) });
|
|
42179
42411
|
} else {
|
|
42180
42412
|
clear.push(textPath);
|
|
42181
42413
|
}
|
|
@@ -42184,9 +42416,11 @@ async function writeChanged(args) {
|
|
|
42184
42416
|
if (outcome.textHash !== undefined) {
|
|
42185
42417
|
textHashes.set(file2.relPath, { to: outcome.textHash });
|
|
42186
42418
|
}
|
|
42419
|
+
} else if (outcome.kind === "unchanged" && outcome.textHash !== undefined) {
|
|
42420
|
+
textHashes.set(file2.relPath, { to: outcome.textHash });
|
|
42187
42421
|
} else if (outcome.kind === "diverged") {
|
|
42188
42422
|
diverged++;
|
|
42189
|
-
notices.push(
|
|
42423
|
+
notices.push(generatedDivergedNotice(outcome.relPath));
|
|
42190
42424
|
}
|
|
42191
42425
|
}
|
|
42192
42426
|
if (args.abortIfStale?.() === true) {
|
|
@@ -42799,6 +43033,109 @@ class PullLoop {
|
|
|
42799
43033
|
}
|
|
42800
43034
|
}
|
|
42801
43035
|
|
|
43036
|
+
// src/lib/high-water.ts
|
|
43037
|
+
var HIGH_WATER_BASE_READ_MS = 60 * 60 * 1000;
|
|
43038
|
+
function baseReadIntervalMs(env2 = process.env) {
|
|
43039
|
+
const raw = Number(env2.PLINTH_HIGH_WATER_BASE_READ_MS);
|
|
43040
|
+
return Number.isFinite(raw) && raw > 0 ? raw : HIGH_WATER_BASE_READ_MS;
|
|
43041
|
+
}
|
|
43042
|
+
function provenMark(args) {
|
|
43043
|
+
if (!args.clean)
|
|
43044
|
+
return null;
|
|
43045
|
+
return args.finalHighWater ?? args.advancedTo;
|
|
43046
|
+
}
|
|
43047
|
+
function createBaseReadSchedule(bootMs, intervalMs = HIGH_WATER_BASE_READ_MS) {
|
|
43048
|
+
let lastBaseReadMs = bootMs;
|
|
43049
|
+
let restoresWaiting = false;
|
|
43050
|
+
let lastUsedHighWater = false;
|
|
43051
|
+
return {
|
|
43052
|
+
useHighWater(nowMs) {
|
|
43053
|
+
if (restoresWaiting)
|
|
43054
|
+
return lastUsedHighWater;
|
|
43055
|
+
return nowMs - lastBaseReadMs < intervalMs;
|
|
43056
|
+
},
|
|
43057
|
+
completed(nowMs, usedHighWater, ok, restoresDeferred = 0) {
|
|
43058
|
+
if (!ok)
|
|
43059
|
+
return;
|
|
43060
|
+
if (!usedHighWater)
|
|
43061
|
+
lastBaseReadMs = nowMs;
|
|
43062
|
+
restoresWaiting = restoresDeferred > 0;
|
|
43063
|
+
lastUsedHighWater = usedHighWater;
|
|
43064
|
+
}
|
|
43065
|
+
};
|
|
43066
|
+
}
|
|
43067
|
+
|
|
43068
|
+
// src/lib/restore-deferral.ts
|
|
43069
|
+
var RESTORE_DEFERRAL_MS = 5 * 60000;
|
|
43070
|
+
var UNEXPLAINED_GRACE_MS = 5000;
|
|
43071
|
+
function keyOf(absolutePath) {
|
|
43072
|
+
return absolutePath.normalize("NFC");
|
|
43073
|
+
}
|
|
43074
|
+
function sightingKey(key, version2) {
|
|
43075
|
+
return `${key}\x00${version2}`;
|
|
43076
|
+
}
|
|
43077
|
+
function forgetSightings(recent, key) {
|
|
43078
|
+
const prefix = `${key}\x00`;
|
|
43079
|
+
for (const k of recent.keys())
|
|
43080
|
+
if (k.startsWith(prefix))
|
|
43081
|
+
recent.delete(k);
|
|
43082
|
+
}
|
|
43083
|
+
function noteUnlink(recent, absolutePath, nowMs) {
|
|
43084
|
+
forgetExpired(recent, nowMs);
|
|
43085
|
+
const key = keyOf(absolutePath);
|
|
43086
|
+
forgetSightings(recent, key);
|
|
43087
|
+
recent.set(key, { kind: "unlink", at: nowMs, logged: false });
|
|
43088
|
+
}
|
|
43089
|
+
function deferRestore(recent, absolutePath, version2, nowMs) {
|
|
43090
|
+
if (!recent)
|
|
43091
|
+
return { defer: false, first: false };
|
|
43092
|
+
forgetExpired(recent, nowMs);
|
|
43093
|
+
const key = keyOf(absolutePath);
|
|
43094
|
+
const unlinked = recent.get(key);
|
|
43095
|
+
if (unlinked?.kind === "unlink") {
|
|
43096
|
+
if (unlinked.logged)
|
|
43097
|
+
return { defer: true, first: false };
|
|
43098
|
+
recent.set(key, { ...unlinked, logged: true });
|
|
43099
|
+
return { defer: true, first: true };
|
|
43100
|
+
}
|
|
43101
|
+
const sighting = sightingKey(key, version2);
|
|
43102
|
+
const seen = recent.get(sighting);
|
|
43103
|
+
if (seen === undefined) {
|
|
43104
|
+
recent.set(sighting, { kind: "missing", at: nowMs });
|
|
43105
|
+
return { defer: true, first: false };
|
|
43106
|
+
}
|
|
43107
|
+
if (nowMs - seen.at < UNEXPLAINED_GRACE_MS)
|
|
43108
|
+
return { defer: true, first: false };
|
|
43109
|
+
recent.delete(sighting);
|
|
43110
|
+
return { defer: false, first: false };
|
|
43111
|
+
}
|
|
43112
|
+
function presentAgain(recent, absolutePath) {
|
|
43113
|
+
if (recent)
|
|
43114
|
+
forgetSightings(recent, keyOf(absolutePath));
|
|
43115
|
+
}
|
|
43116
|
+
function forgetExpired(recent, nowMs) {
|
|
43117
|
+
for (const [key, seen] of recent) {
|
|
43118
|
+
if (seen.kind === "unlink" && nowMs - seen.at >= RESTORE_DEFERRAL_MS)
|
|
43119
|
+
recent.delete(key);
|
|
43120
|
+
}
|
|
43121
|
+
}
|
|
43122
|
+
|
|
43123
|
+
// src/lib/ambient-poll.ts
|
|
43124
|
+
function createAmbientPollWiring(opts) {
|
|
43125
|
+
const recentUnlinks = new Map;
|
|
43126
|
+
const baseReads = createBaseReadSchedule(opts.now(), opts.baseReadIntervalMs);
|
|
43127
|
+
return {
|
|
43128
|
+
onUnlink: (absolutePath) => noteUnlink(recentUnlinks, absolutePath, opts.now()),
|
|
43129
|
+
async poll(run) {
|
|
43130
|
+
const startMs = opts.now();
|
|
43131
|
+
const useHighWater = baseReads.useHighWater(startMs);
|
|
43132
|
+
const result = await run({ useHighWater, recentUnlinks });
|
|
43133
|
+
baseReads.completed(startMs, useHighWater, result.exitCode === 0, result.restoresDeferred ?? 0);
|
|
43134
|
+
return result;
|
|
43135
|
+
}
|
|
43136
|
+
};
|
|
43137
|
+
}
|
|
43138
|
+
|
|
42802
43139
|
// src/lib/calendar-loop.ts
|
|
42803
43140
|
var DEFAULT_CALENDAR_INTERVAL_MS = 900000;
|
|
42804
43141
|
var CALENDAR_BACKOFF_CAP_MS = 3600000;
|
|
@@ -42959,6 +43296,12 @@ var CALENDAR_STATUSES = [
|
|
|
42959
43296
|
"token-expired",
|
|
42960
43297
|
"google-error"
|
|
42961
43298
|
];
|
|
43299
|
+
var KNOWN_TRUNCATED_REASONS = ["page_limit", "time_limit"];
|
|
43300
|
+
var TruncatedReasonSchema = exports_external.string().nullish().transform((v) => {
|
|
43301
|
+
if (v === null || v === undefined)
|
|
43302
|
+
return null;
|
|
43303
|
+
return KNOWN_TRUNCATED_REASONS.includes(v) ? v : "other";
|
|
43304
|
+
});
|
|
42962
43305
|
var CalendarRowSchema = exports_external.object({
|
|
42963
43306
|
artist_id: exports_external.string().nullable(),
|
|
42964
43307
|
artist_slug: exports_external.string().nullable(),
|
|
@@ -42967,8 +43310,13 @@ var CalendarRowSchema = exports_external.object({
|
|
|
42967
43310
|
calendar_name: exports_external.string(),
|
|
42968
43311
|
status: exports_external.enum(CALENDAR_STATUSES),
|
|
42969
43312
|
events: exports_external.array(CalendarEventSchema).nullable(),
|
|
42970
|
-
error: exports_external.object({
|
|
42971
|
-
|
|
43313
|
+
error: exports_external.object({
|
|
43314
|
+
upstream_status: exports_external.number().nullable(),
|
|
43315
|
+
message: exports_external.string()
|
|
43316
|
+
}).nullish().transform((v) => v ?? null),
|
|
43317
|
+
truncated: exports_external.boolean().optional().default(false),
|
|
43318
|
+
truncated_reason: TruncatedReasonSchema,
|
|
43319
|
+
resume_from: exports_external.string().nullish().transform((v) => v ?? null)
|
|
42972
43320
|
});
|
|
42973
43321
|
var CalendarResponseSchema = exports_external.object({
|
|
42974
43322
|
generated_at: exports_external.string().min(1),
|
|
@@ -43156,6 +43504,62 @@ function readAsOf(text, today) {
|
|
|
43156
43504
|
return { parts, yearInferred: first.yearInferred };
|
|
43157
43505
|
}
|
|
43158
43506
|
|
|
43507
|
+
// src/lib/calendar/limits.ts
|
|
43508
|
+
var CALENDAR_RANGE_TOOL = "list_calendar_events";
|
|
43509
|
+
function spanPhrase(timeMin, timeMax) {
|
|
43510
|
+
const ms = Date.parse(timeMax) - Date.parse(timeMin);
|
|
43511
|
+
if (!Number.isFinite(ms) || ms <= 0)
|
|
43512
|
+
return null;
|
|
43513
|
+
const weeks = ms / (7 * DAY_MS);
|
|
43514
|
+
if (Number.isInteger(weeks))
|
|
43515
|
+
return `${weeks} ${weeks === 1 ? "week" : "weeks"}`;
|
|
43516
|
+
const days = ms / DAY_MS;
|
|
43517
|
+
if (Number.isInteger(days))
|
|
43518
|
+
return `${days} ${days === 1 ? "day" : "days"}`;
|
|
43519
|
+
return null;
|
|
43520
|
+
}
|
|
43521
|
+
function coverageLine(timeMin, timeMax) {
|
|
43522
|
+
const span = spanPhrase(timeMin, timeMax);
|
|
43523
|
+
return `**This file holds only the window above${span ? ` (${span})` : ""}.** ` + `When a question needs a date outside it, past or future, call the Plinth ` + `tool \`${CALENDAR_RANGE_TOOL}\` for just that range.`;
|
|
43524
|
+
}
|
|
43525
|
+
var TRUNCATION_CAUSE = {
|
|
43526
|
+
page_limit: "Google had more entries in this window than one refresh reads.",
|
|
43527
|
+
time_limit: "The read from Google ran out of time.",
|
|
43528
|
+
other: "The read stopped before the end of the window."
|
|
43529
|
+
};
|
|
43530
|
+
var TRUNCATION_SHORT = {
|
|
43531
|
+
page_limit: "stopped at the page limit",
|
|
43532
|
+
time_limit: "ran out of time",
|
|
43533
|
+
other: "stopped before the end of the window"
|
|
43534
|
+
};
|
|
43535
|
+
var OFFSET_TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})$/i;
|
|
43536
|
+
function resumeRange(resumeFrom, window2) {
|
|
43537
|
+
if (resumeFrom === null || !OFFSET_TIMESTAMP_RE.test(resumeFrom))
|
|
43538
|
+
return null;
|
|
43539
|
+
if (!OFFSET_TIMESTAMP_RE.test(window2.time_min) || !OFFSET_TIMESTAMP_RE.test(window2.time_max)) {
|
|
43540
|
+
return null;
|
|
43541
|
+
}
|
|
43542
|
+
const at = Date.parse(resumeFrom);
|
|
43543
|
+
const min = Date.parse(window2.time_min);
|
|
43544
|
+
const max = Date.parse(window2.time_max);
|
|
43545
|
+
if (!(at > min && at < max))
|
|
43546
|
+
return null;
|
|
43547
|
+
return { from: new Date(at).toISOString(), to: new Date(max).toISOString() };
|
|
43548
|
+
}
|
|
43549
|
+
function incompleteLine(args) {
|
|
43550
|
+
const { stamp: stamp2, eventCount, reason, resumeFrom, window: window2 } = args;
|
|
43551
|
+
const kept = eventCount === 0 ? "No entries were collected before the read stopped, so this is NOT a clear diary." : `${eventCount} ${eventCount === 1 ? "entry was" : "entries were"} collected before the read stopped.`;
|
|
43552
|
+
const range = resumeRange(resumeFrom, window2);
|
|
43553
|
+
const pointer = range ? ` For the rest, call the Plinth tool \`${CALENDAR_RANGE_TOOL}\` from ` + `\`${range.from}\` to \`${range.to}\` (a few entries repeat).` : ` For the rest, call the Plinth tool \`${CALENDAR_RANGE_TOOL}\` for a ` + `narrower range inside the window.`;
|
|
43554
|
+
return `**Read as of ${stamp2}, but this list is INCOMPLETE.** ` + `${TRUNCATION_CAUSE[reason ?? "other"]} ${kept} ` + `The rest of the window is not included: unknown, not absent.${pointer}`;
|
|
43555
|
+
}
|
|
43556
|
+
function sentence(text) {
|
|
43557
|
+
return /[.!?\u2026]$/.test(text) ? text : `${text}.`;
|
|
43558
|
+
}
|
|
43559
|
+
function truncationShort(reason) {
|
|
43560
|
+
return TRUNCATION_SHORT[reason ?? "other"];
|
|
43561
|
+
}
|
|
43562
|
+
|
|
43159
43563
|
// src/lib/calendar/render.ts
|
|
43160
43564
|
var CALENDAR_BODY_MARKER = "<!-- PLINTH:CALENDAR-BODY -->";
|
|
43161
43565
|
var OWNERSHIP_NOTE2 = [
|
|
@@ -43259,37 +43663,61 @@ function windowLine(timeMin, timeMax) {
|
|
|
43259
43663
|
return `${timeMin} to ${timeMax}`;
|
|
43260
43664
|
return `${formatDay(from, -1)} to ${formatDay(to, -1)}`;
|
|
43261
43665
|
}
|
|
43666
|
+
function isRefusal(error52) {
|
|
43667
|
+
return error52.upstream_status !== null && error52.upstream_status >= 400;
|
|
43668
|
+
}
|
|
43669
|
+
function googleErrorLine(error52) {
|
|
43670
|
+
if (error52 !== null && !isRefusal(error52)) {
|
|
43671
|
+
return `**NOT READ - the read from Google did not complete.** ` + `${sentence(oneLine2(error52.message, 300))} The next refresh retries; until then ` + `nothing below is current, so treat this calendar as unknown rather than empty. ` + `For a date on it you need now, call the Plinth tool \`${CALENDAR_RANGE_TOOL}\` ` + `for a narrower range.`;
|
|
43672
|
+
}
|
|
43673
|
+
return `**NOT READ - Google refused the request` + `${error52 ? ` (HTTP ${error52.upstream_status})` : ""}.** ` + `${error52 ? sentence(oneLine2(error52.message, 300)) : "No detail was returned."} ` + `A calendar that was unshared or deleted in Google reads exactly like this. ` + `Nothing below is current; treat this calendar as unknown rather than empty.`;
|
|
43674
|
+
}
|
|
43262
43675
|
function statusLine(args) {
|
|
43263
43676
|
const { status, eventCount, error: error52, truncated, stamp: stamp2, userLabel } = args;
|
|
43264
43677
|
switch (status) {
|
|
43265
43678
|
case "fetched": {
|
|
43679
|
+
if (truncated) {
|
|
43680
|
+
return incompleteLine({
|
|
43681
|
+
stamp: stamp2,
|
|
43682
|
+
eventCount,
|
|
43683
|
+
reason: args.truncatedReason,
|
|
43684
|
+
resumeFrom: args.resumeFrom,
|
|
43685
|
+
window: args.window
|
|
43686
|
+
});
|
|
43687
|
+
}
|
|
43266
43688
|
if (eventCount === 0) {
|
|
43267
43689
|
return `**Read fine as of ${stamp2}. Nothing in this window.** The diary really ` + `is clear across the dates above. This is a successful read of an empty ` + `calendar, not a failed read - if it had failed, this line would say so.`;
|
|
43268
43690
|
}
|
|
43269
|
-
|
|
43270
|
-
return `**Read fine as of ${stamp2}.** ${eventCount} ` + `${eventCount === 1 ? "entry" : "entries"} in the window.${truncatedNote}`;
|
|
43691
|
+
return `**Read fine as of ${stamp2}.** ${eventCount} ` + `${eventCount === 1 ? "entry" : "entries"} in the window.`;
|
|
43271
43692
|
}
|
|
43272
43693
|
case "no-token":
|
|
43273
43694
|
return `**NOT READ - Google is not connected for ${userLabel}.** Nothing is listed ` + `below because Plinth could not look, NOT because the diary is clear. ` + `Connect Google in Plinth to fill this in, and until then treat this ` + `calendar as unknown rather than empty.`;
|
|
43274
43695
|
case "token-expired":
|
|
43275
43696
|
return `**NOT READ - the Google connection for ${userLabel} has expired.** Plinth ` + `still holds a connection but could not refresh it, so nothing below is ` + `current. Reconnect Google in Plinth. Treat this calendar as unknown ` + `rather than empty.`;
|
|
43276
43697
|
case "google-error":
|
|
43277
|
-
return
|
|
43698
|
+
return googleErrorLine(error52);
|
|
43278
43699
|
}
|
|
43279
43700
|
}
|
|
43280
43701
|
function shortStatus(row) {
|
|
43281
43702
|
switch (row.status) {
|
|
43282
43703
|
case "fetched": {
|
|
43283
43704
|
const n = row.events?.length ?? 0;
|
|
43705
|
+
const count = `${n} ${n === 1 ? "entry" : "entries"}`;
|
|
43706
|
+
if (row.truncated) {
|
|
43707
|
+
return `read, ${count}, INCOMPLETE (${truncationShort(row.truncated_reason)}); ` + `the rest of the window is unknown, not empty`;
|
|
43708
|
+
}
|
|
43284
43709
|
if (n === 0)
|
|
43285
43710
|
return "read fine, nothing in this window";
|
|
43286
|
-
return `read fine, ${
|
|
43711
|
+
return `read fine, ${count}`;
|
|
43287
43712
|
}
|
|
43288
43713
|
case "no-token":
|
|
43289
43714
|
return "NOT READ - Google is not connected; unknown, not empty";
|
|
43290
43715
|
case "token-expired":
|
|
43291
43716
|
return "NOT READ - the Google connection has expired; unknown, not empty";
|
|
43292
43717
|
case "google-error":
|
|
43718
|
+
if (row.error !== null && !isRefusal(row.error)) {
|
|
43719
|
+
return `NOT READ - the read from Google did not complete: ` + `${oneLine2(row.error.message, 120)}; the next refresh retries; unknown, not empty`;
|
|
43720
|
+
}
|
|
43293
43721
|
return `NOT READ - Google refused` + `${row.error ? ` (HTTP ${row.error.upstream_status}): ${oneLine2(row.error.message, 120)}` : ""}` + `; unknown, not empty`;
|
|
43294
43722
|
}
|
|
43295
43723
|
}
|
|
@@ -43347,21 +43775,25 @@ function renderArtistCalendar(args) {
|
|
|
43347
43775
|
eventCount: events.length,
|
|
43348
43776
|
error: row.error,
|
|
43349
43777
|
truncated: row.truncated,
|
|
43778
|
+
truncatedReason: row.truncated_reason,
|
|
43779
|
+
resumeFrom: row.resume_from,
|
|
43780
|
+
window: window2,
|
|
43350
43781
|
stamp: stamp2,
|
|
43351
43782
|
userLabel
|
|
43352
43783
|
}),
|
|
43353
43784
|
"",
|
|
43354
43785
|
`**Window:** ${windowLine(window2.time_min, window2.time_max)}.`,
|
|
43355
43786
|
"",
|
|
43787
|
+
...row.status === "fetched" ? [coverageLine(window2.time_min, window2.time_max), ""] : [],
|
|
43356
43788
|
"**Dates are read here and changed in Plinth's calendar view, by a person.** If something below needs moving, say what the change is and hand it over.",
|
|
43357
43789
|
"",
|
|
43358
|
-
renderBands(events, generatedAt, referenceYear, "")
|
|
43790
|
+
renderBands(events, generatedAt, referenceYear, "", row.truncated)
|
|
43359
43791
|
].join(`
|
|
43360
43792
|
`);
|
|
43361
43793
|
return `${header}${CALENDAR_BODY_MARKER}
|
|
43362
43794
|
${body}`;
|
|
43363
43795
|
}
|
|
43364
|
-
function renderBands(events, generatedAt, referenceYear, artistPrefix) {
|
|
43796
|
+
function renderBands(events, generatedAt, referenceYear, artistPrefix, truncated) {
|
|
43365
43797
|
if (events.length === 0)
|
|
43366
43798
|
return "";
|
|
43367
43799
|
const { full, rest } = splitBands(events, generatedAt);
|
|
@@ -43375,7 +43807,7 @@ function renderBands(events, generatedAt, referenceYear, artistPrefix) {
|
|
|
43375
43807
|
}
|
|
43376
43808
|
out.push("", "## Rest of the window", "");
|
|
43377
43809
|
if (rest.length === 0) {
|
|
43378
|
-
out.push("_Nothing further in the window._");
|
|
43810
|
+
out.push(truncated ? "_Nothing further was collected: the read stopped before the end of the window._" : "_Nothing further in the window._");
|
|
43379
43811
|
} else {
|
|
43380
43812
|
for (const e of rest)
|
|
43381
43813
|
out.push(compactLine(e, referenceYear, artistPrefix));
|
|
@@ -43384,6 +43816,9 @@ function renderBands(events, generatedAt, referenceYear, artistPrefix) {
|
|
|
43384
43816
|
return out.join(`
|
|
43385
43817
|
`);
|
|
43386
43818
|
}
|
|
43819
|
+
function lacksConnection(row) {
|
|
43820
|
+
return row.status === "no-token" || row.status === "token-expired";
|
|
43821
|
+
}
|
|
43387
43822
|
function renderUpcoming(args) {
|
|
43388
43823
|
const {
|
|
43389
43824
|
rows,
|
|
@@ -43399,6 +43834,7 @@ function renderUpcoming(args) {
|
|
|
43399
43834
|
const fetched = rows.filter((r) => r.status === "fetched");
|
|
43400
43835
|
const failed = rows.filter((r) => r.status !== "fetched");
|
|
43401
43836
|
const truncatedAnywhere = rows.some((r) => r.truncated);
|
|
43837
|
+
const cutShort = fetched.filter((r) => r.truncated).length;
|
|
43402
43838
|
const merged = fetched.flatMap((row) => (row.events ?? []).map((event) => ({ event, row }))).sort((a, b) => sortInstant(a.event) - sortInstant(b.event));
|
|
43403
43839
|
const { full, rest } = splitBands(merged.map((m) => m.event), generatedAt);
|
|
43404
43840
|
const fullSet = new Set(full);
|
|
@@ -43429,7 +43865,12 @@ function renderUpcoming(args) {
|
|
|
43429
43865
|
"",
|
|
43430
43866
|
`**As of ${stamp2}.** Window ${windowLine(window2.time_min, window2.time_max)}.`,
|
|
43431
43867
|
"",
|
|
43868
|
+
...rows.length > 0 && !rows.every(lacksConnection) ? [coverageLine(window2.time_min, window2.time_max), ""] : [],
|
|
43432
43869
|
failed.length === 0 ? `**All ${fetched.length} mapped ${fetched.length === 1 ? "calendar was" : "calendars were"} read.**` : `**${fetched.length} of ${rows.length} mapped ${rows.length === 1 ? "calendar" : "calendars"} read; ${failed.length} could NOT be read.**` + ` Anything on an unread calendar is missing from the lists below - unknown, not absent.`,
|
|
43870
|
+
...cutShort === 0 ? [] : [
|
|
43871
|
+
"",
|
|
43872
|
+
`**${cutShort} of the calendars read ${cutShort === 1 ? "was" : "were"} cut short** before the end ` + `of the window, so entries past the point where ${cutShort === 1 ? "it" : "each"} stopped are ` + `missing from the lists below - unknown, not absent. The status list says which.`
|
|
43873
|
+
],
|
|
43433
43874
|
"",
|
|
43434
43875
|
`${artistCount} ${artistCount === 1 ? "artist" : "artists"} in the roster; ` + `${mappedArtistCount} ${mappedArtistCount === 1 ? "has" : "have"} a calendar mapped in Plinth.`,
|
|
43435
43876
|
"",
|
|
@@ -43446,7 +43887,7 @@ function renderUpcoming(args) {
|
|
|
43446
43887
|
body.push(`## Next ${FULL_BAND_DAYS} days`, "", "_Links are in the per-artist file named beside each calendar above._", "");
|
|
43447
43888
|
const fullMerged = merged.filter((m) => fullSet.has(m.event));
|
|
43448
43889
|
if (fullMerged.length === 0) {
|
|
43449
|
-
body.push(fetched.length === 0 ? `_No calendar could be read, so nothing can be listed for the next ${FULL_BAND_DAYS} days._` : `_Nothing in the next ${FULL_BAND_DAYS} days on any calendar that was read._`);
|
|
43890
|
+
body.push(fetched.length === 0 ? `_No calendar could be read, so nothing can be listed for the next ${FULL_BAND_DAYS} days._` : cutShort > 0 ? `_Nothing collected for the next ${FULL_BAND_DAYS} days on any calendar that was read; the ones cut short may be missing entries._` : `_Nothing in the next ${FULL_BAND_DAYS} days on any calendar that was read._`);
|
|
43450
43891
|
} else {
|
|
43451
43892
|
for (const { event, row } of fullMerged) {
|
|
43452
43893
|
body.push(fullLine(event, referenceYear, `**${rowDisplayName(row)}**`, false));
|
|
@@ -43455,7 +43896,7 @@ function renderUpcoming(args) {
|
|
|
43455
43896
|
body.push("", "## Rest of the window", "");
|
|
43456
43897
|
const restMerged = merged.filter((m) => restSet.has(m.event));
|
|
43457
43898
|
if (restMerged.length === 0) {
|
|
43458
|
-
body.push(fetched.length === 0 ? "_No calendar could be read, so nothing can be listed._" : "_Nothing further in the window on any calendar that was read._");
|
|
43899
|
+
body.push(fetched.length === 0 ? "_No calendar could be read, so nothing can be listed._" : cutShort > 0 ? "_Nothing further was collected on any calendar that was read; the ones cut short may be missing entries past where they stopped._" : "_Nothing further in the window on any calendar that was read._");
|
|
43459
43900
|
} else {
|
|
43460
43901
|
for (const { event, row } of restMerged) {
|
|
43461
43902
|
body.push(compactLine(event, referenceYear, `**${rowDisplayName(row)}**`));
|
|
@@ -43701,8 +44142,7 @@ class PullSuppressor {
|
|
|
43701
44142
|
}
|
|
43702
44143
|
|
|
43703
44144
|
// src/lib/discover-entities.ts
|
|
43704
|
-
|
|
43705
|
-
import { readFileSync as readFileSync23, statSync as statSync12 } from "node:fs";
|
|
44145
|
+
import { readFileSync as readFileSync24, statSync as statSync12 } from "node:fs";
|
|
43706
44146
|
var DISCOVERY_WRITE_CAP_DEFAULT = 10;
|
|
43707
44147
|
function discoveryWriteCap() {
|
|
43708
44148
|
const raw = process.env.PLINTH_DISCOVERY_WRITE_CAP;
|
|
@@ -43729,6 +44169,11 @@ function classifyUntrackedEntity(args) {
|
|
|
43729
44169
|
}
|
|
43730
44170
|
return { kind: "create" };
|
|
43731
44171
|
}
|
|
44172
|
+
function holdsUnreadableUntracked(args) {
|
|
44173
|
+
if (args.readable)
|
|
44174
|
+
return false;
|
|
44175
|
+
return classifyUntrackedEntity({ ...args, frontmatter: {} }).kind === "create";
|
|
44176
|
+
}
|
|
43732
44177
|
function classifyRenameDestination(args) {
|
|
43733
44178
|
const occupant = getEntryBySlug(args.state, args.entityType, args.entitySlug);
|
|
43734
44179
|
if (occupant && occupant.id !== args.movedEntryId) {
|
|
@@ -43799,13 +44244,20 @@ function planDiscoveryWrites(args) {
|
|
|
43799
44244
|
if (getEntryBySlug(state, entityType, entitySlug))
|
|
43800
44245
|
continue;
|
|
43801
44246
|
counts.discovered++;
|
|
43802
|
-
let
|
|
44247
|
+
let raw;
|
|
43803
44248
|
try {
|
|
43804
|
-
|
|
44249
|
+
raw = readFileSync24(absolutePath, "utf8");
|
|
43805
44250
|
} catch {
|
|
43806
44251
|
counts.discovered--;
|
|
43807
44252
|
continue;
|
|
43808
44253
|
}
|
|
44254
|
+
const read = readFrontmatter(raw);
|
|
44255
|
+
if (!read.ok && holdsUnreadableUntracked({ entityType, entitySlug, readable: false })) {
|
|
44256
|
+
counts.discovered--;
|
|
44257
|
+
planned.push({ kind: "unreadable", entityType, entitySlug, absolutePath, why: read.why });
|
|
44258
|
+
continue;
|
|
44259
|
+
}
|
|
44260
|
+
const frontmatter = read.ok ? read.parsed.data : {};
|
|
43809
44261
|
const verdict = classifyUntrackedEntity({ entityType, entitySlug, frontmatter });
|
|
43810
44262
|
if (verdict.kind === "hold") {
|
|
43811
44263
|
if (verdict.held === "artist")
|
|
@@ -43846,7 +44298,7 @@ async function discoverUntrackedEntities(args) {
|
|
|
43846
44298
|
}
|
|
43847
44299
|
const cap = discoveryWriteCap();
|
|
43848
44300
|
const creates = planned.filter((w) => w.kind === "create");
|
|
43849
|
-
const toRun = creates.length > cap ? planned.filter((w) => w.kind
|
|
44301
|
+
const toRun = creates.length > cap ? planned.filter((w) => w.kind !== "create") : planned;
|
|
43850
44302
|
if (creates.length > cap) {
|
|
43851
44303
|
counts.heldCap = creates.length;
|
|
43852
44304
|
output?.log(`[DISCOVERY_HELD reason=cap creates=${creates.length} cap=${cap} source=entity-discovery]`);
|
|
@@ -43869,6 +44321,19 @@ async function runPlannedWrites(args) {
|
|
|
43869
44321
|
let state = args.state;
|
|
43870
44322
|
for (const write of planned) {
|
|
43871
44323
|
try {
|
|
44324
|
+
if (write.kind === "unreadable") {
|
|
44325
|
+
state = holdUnreadableFrontmatter({
|
|
44326
|
+
state,
|
|
44327
|
+
workspaceSlug,
|
|
44328
|
+
absolutePath: write.absolutePath,
|
|
44329
|
+
entityType: write.entityType,
|
|
44330
|
+
slug: write.entitySlug,
|
|
44331
|
+
why: write.why,
|
|
44332
|
+
now,
|
|
44333
|
+
output
|
|
44334
|
+
});
|
|
44335
|
+
continue;
|
|
44336
|
+
}
|
|
43872
44337
|
if (write.kind === "rename") {
|
|
43873
44338
|
state = await runDiscoveredRename({
|
|
43874
44339
|
write,
|
|
@@ -44029,8 +44494,7 @@ function recordDiscoveryFailure(args) {
|
|
|
44029
44494
|
}
|
|
44030
44495
|
|
|
44031
44496
|
// src/lib/frontmatter-id-backfill.ts
|
|
44032
|
-
|
|
44033
|
-
import { readFileSync as readFileSync24, statSync as statSync13 } from "node:fs";
|
|
44497
|
+
import { readFileSync as readFileSync25, statSync as statSync13 } from "node:fs";
|
|
44034
44498
|
var FRONTMATTER_ID_BACKFILL_VERSION = 1;
|
|
44035
44499
|
function backfillWorthReporting(r) {
|
|
44036
44500
|
return r.written > 0 || r.failed > 0 || r.skipped.foreignId > 0 || r.skipped.unwritable > 0 || r.skipped.slugCollision > 0;
|
|
@@ -44073,7 +44537,7 @@ function safeInode3(path5) {
|
|
|
44073
44537
|
function backfillFrontmatterIds(args) {
|
|
44074
44538
|
const { workspaceSlug, state } = args;
|
|
44075
44539
|
const compose = args.compose ?? withFrontmatterId;
|
|
44076
|
-
const readRaw = args.readFile ?? ((p) =>
|
|
44540
|
+
const readRaw = args.readFile ?? ((p) => readFileSync25(p, "utf8"));
|
|
44077
44541
|
const result = emptyResult();
|
|
44078
44542
|
if (state.frontmatter_id_backfill_version === FRONTMATTER_ID_BACKFILL_VERSION) {
|
|
44079
44543
|
return result;
|
|
@@ -44122,7 +44586,7 @@ function backfillFrontmatterIds(args) {
|
|
|
44122
44586
|
let rawBody;
|
|
44123
44587
|
let parsedData;
|
|
44124
44588
|
try {
|
|
44125
|
-
const parsed =
|
|
44589
|
+
const parsed = parseUncached(raw);
|
|
44126
44590
|
parsedData = parsed.data ?? {};
|
|
44127
44591
|
existing = parsedData["id"];
|
|
44128
44592
|
rawBody = parsed.content;
|
|
@@ -44201,11 +44665,11 @@ function backfillFrontmatterIds(args) {
|
|
|
44201
44665
|
}
|
|
44202
44666
|
|
|
44203
44667
|
// src/lib/reconcile.ts
|
|
44204
|
-
import { readFileSync as
|
|
44668
|
+
import { readFileSync as readFileSync26, statSync as statSync14 } from "node:fs";
|
|
44205
44669
|
async function reconcileDivergedRows(args) {
|
|
44206
44670
|
const { workspaceSlug, baseUrl, pat, fetchImpl, output, isFrozen } = args;
|
|
44207
44671
|
const now = args.now ?? (() => new Date);
|
|
44208
|
-
let state = args.state;
|
|
44672
|
+
let state = settleFrontmatterHolds(args.state, workspaceSlug);
|
|
44209
44673
|
let scanned = 0;
|
|
44210
44674
|
let diverged = 0;
|
|
44211
44675
|
let pushed = 0;
|
|
@@ -44313,7 +44777,7 @@ async function reconcileDivergedRows(args) {
|
|
|
44313
44777
|
}
|
|
44314
44778
|
let content;
|
|
44315
44779
|
try {
|
|
44316
|
-
content =
|
|
44780
|
+
content = readFileSync26(absolutePath, "utf8");
|
|
44317
44781
|
} catch (err) {
|
|
44318
44782
|
if (err?.code === "ENOENT") {
|
|
44319
44783
|
state = clearEntityCloudRowDeleted(state, strategy.mirrorRelativePath(entry2.slug));
|
|
@@ -44321,6 +44785,20 @@ async function reconcileDivergedRows(args) {
|
|
|
44321
44785
|
}
|
|
44322
44786
|
throw err;
|
|
44323
44787
|
}
|
|
44788
|
+
const unreadable = frontmatterParseError(content);
|
|
44789
|
+
if (unreadable !== null) {
|
|
44790
|
+
state = holdUnreadableFrontmatter({
|
|
44791
|
+
state,
|
|
44792
|
+
workspaceSlug,
|
|
44793
|
+
absolutePath,
|
|
44794
|
+
entityType,
|
|
44795
|
+
slug: entry2.slug,
|
|
44796
|
+
why: unreadable,
|
|
44797
|
+
now,
|
|
44798
|
+
output
|
|
44799
|
+
});
|
|
44800
|
+
continue;
|
|
44801
|
+
}
|
|
44324
44802
|
const hashes = computeMirrorHashes(strategy, content);
|
|
44325
44803
|
if (hashes.frontmatterId === undefined)
|
|
44326
44804
|
sawTrackedFileWithoutId = true;
|
|
@@ -44371,7 +44849,7 @@ async function reconcileDivergedRows(args) {
|
|
|
44371
44849
|
ops: pendingOps
|
|
44372
44850
|
});
|
|
44373
44851
|
quarantined = ops.length;
|
|
44374
|
-
output?.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${ops.length} reason=${result.reason
|
|
44852
|
+
output?.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${ops.length} reason=${tripReasonToken(result.reason)} source=reconcile]`);
|
|
44375
44853
|
} else {
|
|
44376
44854
|
for (const c of guttingCandidates) {
|
|
44377
44855
|
const op = c.op;
|
|
@@ -45602,7 +46080,7 @@ class Semaphore {
|
|
|
45602
46080
|
this.active += 1;
|
|
45603
46081
|
return;
|
|
45604
46082
|
}
|
|
45605
|
-
await new Promise((
|
|
46083
|
+
await new Promise((resolve7) => this.waiters.push(resolve7));
|
|
45606
46084
|
}
|
|
45607
46085
|
release() {
|
|
45608
46086
|
const next = this.waiters.shift();
|
|
@@ -45664,9 +46142,9 @@ function createThrottledFetch(baseFetch, opts = {}) {
|
|
|
45664
46142
|
// src/commands/sync.ts
|
|
45665
46143
|
import {
|
|
45666
46144
|
existsSync as existsSync9,
|
|
45667
|
-
lstatSync as
|
|
46145
|
+
lstatSync as lstatSync7,
|
|
45668
46146
|
readdirSync as readdirSync7,
|
|
45669
|
-
readFileSync as
|
|
46147
|
+
readFileSync as readFileSync30,
|
|
45670
46148
|
statSync as statSync17,
|
|
45671
46149
|
unlinkSync as unlinkSync9
|
|
45672
46150
|
} from "node:fs";
|
|
@@ -45850,17 +46328,17 @@ function refreshConflictsView(workspaceSlug) {
|
|
|
45850
46328
|
}
|
|
45851
46329
|
|
|
45852
46330
|
// src/lib/files/pull.ts
|
|
45853
|
-
import { existsSync as existsSync8, lstatSync as
|
|
46331
|
+
import { existsSync as existsSync8, lstatSync as lstatSync6, readFileSync as readFileSync28, statSync as statSync15 } from "node:fs";
|
|
45854
46332
|
import { dirname as dirname13 } from "node:path";
|
|
45855
46333
|
|
|
45856
46334
|
// src/lib/permanent-disposition.ts
|
|
45857
46335
|
import { createHash as createHash6 } from "node:crypto";
|
|
45858
|
-
import { readFileSync as
|
|
46336
|
+
import { readFileSync as readFileSync27 } from "node:fs";
|
|
45859
46337
|
function sha256(value) {
|
|
45860
46338
|
return createHash6("sha256").update(value).digest("hex");
|
|
45861
46339
|
}
|
|
45862
46340
|
function verifyStash(path5, expectedHash) {
|
|
45863
|
-
const actual = sha256(
|
|
46341
|
+
const actual = sha256(readFileSync27(path5));
|
|
45864
46342
|
if (actual !== expectedHash) {
|
|
45865
46343
|
throw new Error(`permanent deletion stash verification failed for ${path5}`);
|
|
45866
46344
|
}
|
|
@@ -45904,7 +46382,7 @@ function disposePermanentDocumentLocked(args) {
|
|
|
45904
46382
|
const target = entityFilePath(args.workspaceSlug, args.strategy.type, retiredEntry?.slug ?? t.logicalKey);
|
|
45905
46383
|
let raw;
|
|
45906
46384
|
try {
|
|
45907
|
-
raw =
|
|
46385
|
+
raw = readFileSync27(target, "utf8");
|
|
45908
46386
|
} catch (error52) {
|
|
45909
46387
|
if (error52?.code !== "ENOENT")
|
|
45910
46388
|
throw error52;
|
|
@@ -46002,7 +46480,7 @@ function disposePermanentFileLocked(args) {
|
|
|
46002
46480
|
const target = filesSubtreePath(args.workspaceSlug, t.logicalKey);
|
|
46003
46481
|
let bytes;
|
|
46004
46482
|
try {
|
|
46005
|
-
bytes =
|
|
46483
|
+
bytes = readFileSync27(target);
|
|
46006
46484
|
} catch (error52) {
|
|
46007
46485
|
if (error52?.code !== "ENOENT")
|
|
46008
46486
|
throw error52;
|
|
@@ -46061,7 +46539,8 @@ async function replayPermanentFiles(args, markers) {
|
|
|
46061
46539
|
const now = args.now ?? (() => new Date);
|
|
46062
46540
|
const rows = [];
|
|
46063
46541
|
let cursor = null;
|
|
46064
|
-
|
|
46542
|
+
const savedCursor = args.baseCursor === undefined ? args.cursor : args.baseCursor;
|
|
46543
|
+
let resultingCursor = savedCursor;
|
|
46065
46544
|
while (true) {
|
|
46066
46545
|
const page = await syncFilesPage({
|
|
46067
46546
|
baseUrl: args.baseUrl,
|
|
@@ -46084,6 +46563,7 @@ async function replayPermanentFiles(args, markers) {
|
|
|
46084
46563
|
let pointerRows = 0;
|
|
46085
46564
|
let symlinkRefused = 0;
|
|
46086
46565
|
let stashFailed = 0;
|
|
46566
|
+
let restoresDeferred = 0;
|
|
46087
46567
|
let completePass = true;
|
|
46088
46568
|
let lastCommittedState = args.state;
|
|
46089
46569
|
const completed = [];
|
|
@@ -46172,7 +46652,8 @@ async function replayPermanentFiles(args, markers) {
|
|
|
46172
46652
|
isFileFrozen: args.isFileFrozen,
|
|
46173
46653
|
loggedTombstoneSkips: args.loggedTombstoneSkips,
|
|
46174
46654
|
forceTextRewalk: args.forceTextRewalk,
|
|
46175
|
-
loggedStashNotices: args.loggedStashNotices
|
|
46655
|
+
loggedStashNotices: args.loggedStashNotices,
|
|
46656
|
+
recentUnlinks: args.recentUnlinks
|
|
46176
46657
|
});
|
|
46177
46658
|
state = outcome.state;
|
|
46178
46659
|
if (outcome.action === "written")
|
|
@@ -46189,11 +46670,15 @@ async function replayPermanentFiles(args, markers) {
|
|
|
46189
46670
|
symlinkRefused++;
|
|
46190
46671
|
completePass = false;
|
|
46191
46672
|
}
|
|
46192
|
-
if (outcome.stashFailed || outcome.textFailed) {
|
|
46673
|
+
if (outcome.stashFailed || outcome.textFailed || outcome.integrityFailed) {
|
|
46193
46674
|
if (outcome.stashFailed)
|
|
46194
46675
|
stashFailed++;
|
|
46195
46676
|
completePass = false;
|
|
46196
46677
|
}
|
|
46678
|
+
if (outcome.restoreDeferred) {
|
|
46679
|
+
restoresDeferred++;
|
|
46680
|
+
completePass = false;
|
|
46681
|
+
}
|
|
46197
46682
|
}
|
|
46198
46683
|
if (args.commit && state !== lastCommittedState)
|
|
46199
46684
|
state = args.commit(lastCommittedState, state);
|
|
@@ -46208,7 +46693,8 @@ async function replayPermanentFiles(args, markers) {
|
|
|
46208
46693
|
pointerRows,
|
|
46209
46694
|
symlinkRefused,
|
|
46210
46695
|
stashFailed,
|
|
46211
|
-
lastCursor: completePass ? resultingCursor :
|
|
46696
|
+
lastCursor: completePass ? resultingCursor : savedCursor,
|
|
46697
|
+
restoresDeferred,
|
|
46212
46698
|
permanentRefreshes: completed,
|
|
46213
46699
|
permanentReplayComplete: completePass
|
|
46214
46700
|
};
|
|
@@ -46232,12 +46718,27 @@ async function pullFiles(args) {
|
|
|
46232
46718
|
let pointerRows = 0;
|
|
46233
46719
|
let symlinkRefused = 0;
|
|
46234
46720
|
let stashFailed = 0;
|
|
46721
|
+
let restoresDeferred = 0;
|
|
46235
46722
|
let permanentEncountered = false;
|
|
46723
|
+
const baseCursor = args.baseCursor === undefined ? args.cursor : args.baseCursor;
|
|
46236
46724
|
let cursor = args.cursor;
|
|
46237
46725
|
let lastCursor = args.cursor;
|
|
46726
|
+
let finalHighWater = null;
|
|
46727
|
+
let markRefused = false;
|
|
46238
46728
|
let cursorBlocked = permanentJournalBlocked;
|
|
46239
46729
|
while (true) {
|
|
46240
|
-
|
|
46730
|
+
let page;
|
|
46731
|
+
try {
|
|
46732
|
+
page = await syncFilesPage({ baseUrl, pat, cursor, fetchImpl });
|
|
46733
|
+
} catch (err) {
|
|
46734
|
+
if (!markRefused && cursor === args.cursor && args.cursor !== baseCursor && err instanceof ApiError && err.status === 400) {
|
|
46735
|
+
markRefused = true;
|
|
46736
|
+
cursor = baseCursor;
|
|
46737
|
+
lastCursor = baseCursor;
|
|
46738
|
+
continue;
|
|
46739
|
+
}
|
|
46740
|
+
throw err;
|
|
46741
|
+
}
|
|
46241
46742
|
const pageBase = state;
|
|
46242
46743
|
let pageTextFailed = false;
|
|
46243
46744
|
for (const row of page.rows) {
|
|
@@ -46271,7 +46772,8 @@ async function pullFiles(args) {
|
|
|
46271
46772
|
isFileFrozen: args.isFileFrozen,
|
|
46272
46773
|
loggedTombstoneSkips: args.loggedTombstoneSkips,
|
|
46273
46774
|
forceTextRewalk: args.forceTextRewalk,
|
|
46274
|
-
loggedStashNotices: args.loggedStashNotices
|
|
46775
|
+
loggedStashNotices: args.loggedStashNotices,
|
|
46776
|
+
recentUnlinks: args.recentUnlinks
|
|
46275
46777
|
});
|
|
46276
46778
|
state = outcome.state;
|
|
46277
46779
|
if (!outcome.symlinkRefused) {
|
|
@@ -46297,13 +46799,21 @@ async function pullFiles(args) {
|
|
|
46297
46799
|
stashFailed++;
|
|
46298
46800
|
pageTextFailed = true;
|
|
46299
46801
|
}
|
|
46802
|
+
if (outcome.integrityFailed)
|
|
46803
|
+
pageTextFailed = true;
|
|
46804
|
+
if (outcome.restoreDeferred) {
|
|
46805
|
+
restoresDeferred++;
|
|
46806
|
+
pageTextFailed = true;
|
|
46807
|
+
}
|
|
46300
46808
|
}
|
|
46301
46809
|
if (args.commit && state !== pageBase)
|
|
46302
46810
|
state = args.commit(pageBase, state);
|
|
46303
46811
|
if (pageTextFailed)
|
|
46304
46812
|
cursorBlocked = true;
|
|
46305
|
-
if (page.next_cursor === null)
|
|
46813
|
+
if (page.next_cursor === null) {
|
|
46814
|
+
finalHighWater = page.high_water ?? null;
|
|
46306
46815
|
break;
|
|
46816
|
+
}
|
|
46307
46817
|
if (!cursorBlocked)
|
|
46308
46818
|
lastCursor = page.next_cursor;
|
|
46309
46819
|
cursor = page.next_cursor;
|
|
@@ -46340,7 +46850,14 @@ async function pullFiles(args) {
|
|
|
46340
46850
|
pointerRows,
|
|
46341
46851
|
symlinkRefused,
|
|
46342
46852
|
stashFailed,
|
|
46343
|
-
lastCursor
|
|
46853
|
+
lastCursor,
|
|
46854
|
+
highWater: provenMark({
|
|
46855
|
+
clean: !cursorBlocked && textUnknown === 0,
|
|
46856
|
+
finalHighWater,
|
|
46857
|
+
advancedTo: lastCursor !== (markRefused ? baseCursor : args.cursor) ? lastCursor : null
|
|
46858
|
+
}),
|
|
46859
|
+
markRefused,
|
|
46860
|
+
restoresDeferred
|
|
46344
46861
|
};
|
|
46345
46862
|
}
|
|
46346
46863
|
async function applyExtractedText(args) {
|
|
@@ -46455,7 +46972,7 @@ async function applyFileRow(args) {
|
|
|
46455
46972
|
}
|
|
46456
46973
|
let localBytes;
|
|
46457
46974
|
try {
|
|
46458
|
-
localBytes =
|
|
46975
|
+
localBytes = readFileSync28(abs);
|
|
46459
46976
|
} catch {
|
|
46460
46977
|
localBytes = undefined;
|
|
46461
46978
|
}
|
|
@@ -46501,6 +47018,17 @@ async function applyFileRow(args) {
|
|
|
46501
47018
|
output
|
|
46502
47019
|
});
|
|
46503
47020
|
}
|
|
47021
|
+
if (existing && existing.content_hash === row.content_hash && !existsSync8(abs)) {
|
|
47022
|
+
const verdict = deferRestore(args.recentUnlinks, abs, row.updated_at, now().getTime());
|
|
47023
|
+
if (verdict.defer) {
|
|
47024
|
+
if (verdict.first) {
|
|
47025
|
+
output?.log(fileRestoreDeferredLine({ path: relPath, waitSeconds: RESTORE_DEFERRAL_MS / 1000 }));
|
|
47026
|
+
}
|
|
47027
|
+
return { state, action: "noop", textUnknown, restoreDeferred: true };
|
|
47028
|
+
}
|
|
47029
|
+
} else {
|
|
47030
|
+
presentAgain(args.recentUnlinks, abs);
|
|
47031
|
+
}
|
|
46504
47032
|
if (existing && existing.content_hash === row.content_hash && existsSync8(abs)) {
|
|
46505
47033
|
if (!args.forceTextRewalk && textIsCurrent(existing, row, workspaceSlug, relPath)) {
|
|
46506
47034
|
return { state, action: "noop", textUnknown };
|
|
@@ -46553,7 +47081,7 @@ async function applyFileRow(args) {
|
|
|
46553
47081
|
const bytes = await downloadBytes(got.row.download_url, fetchImpl);
|
|
46554
47082
|
if (sha256Bytes(bytes) !== row.content_hash) {
|
|
46555
47083
|
output?.warn(`Integrity check failed for files/${relPath} (hash mismatch) — skipped this pull.`);
|
|
46556
|
-
return { state, action: "skipped" };
|
|
47084
|
+
return { state, action: "skipped", integrityFailed: true };
|
|
46557
47085
|
}
|
|
46558
47086
|
const stashFailure = stashUnpushedLocalEdit({
|
|
46559
47087
|
workspaceSlug,
|
|
@@ -46661,7 +47189,7 @@ function stashUnpushedLocalEdit(args) {
|
|
|
46661
47189
|
return { reason: "symlink" };
|
|
46662
47190
|
let st;
|
|
46663
47191
|
try {
|
|
46664
|
-
st =
|
|
47192
|
+
st = lstatSync6(abs);
|
|
46665
47193
|
} catch (err) {
|
|
46666
47194
|
if (err?.code === "ENOENT")
|
|
46667
47195
|
return null;
|
|
@@ -46671,7 +47199,7 @@ function stashUnpushedLocalEdit(args) {
|
|
|
46671
47199
|
return { reason: "not_a_file" };
|
|
46672
47200
|
let localBytes;
|
|
46673
47201
|
try {
|
|
46674
|
-
localBytes =
|
|
47202
|
+
localBytes = readFileSync28(abs);
|
|
46675
47203
|
} catch (err) {
|
|
46676
47204
|
return { reason: "unreadable", code: errCode(err) };
|
|
46677
47205
|
}
|
|
@@ -46712,6 +47240,8 @@ function textIsCurrent(existing, row, workspaceSlug, relPath) {
|
|
|
46712
47240
|
}
|
|
46713
47241
|
if (existing.text_hash === undefined)
|
|
46714
47242
|
return false;
|
|
47243
|
+
if (existing.updated_at !== row.updated_at)
|
|
47244
|
+
return false;
|
|
46715
47245
|
const onDisk = readExtractedText(workspaceSlug, relPath);
|
|
46716
47246
|
if (onDisk === null)
|
|
46717
47247
|
return false;
|
|
@@ -46719,7 +47249,7 @@ function textIsCurrent(existing, row, workspaceSlug, relPath) {
|
|
|
46719
47249
|
}
|
|
46720
47250
|
|
|
46721
47251
|
// src/lib/files/rejection-sync.ts
|
|
46722
|
-
import { readFileSync as
|
|
47252
|
+
import { readFileSync as readFileSync29, statSync as statSync16 } from "node:fs";
|
|
46723
47253
|
|
|
46724
47254
|
// src/lib/files/rejections-api.ts
|
|
46725
47255
|
var RejectionsFeedSchema = exports_external.object({
|
|
@@ -46852,7 +47382,7 @@ function settledDisposition(args) {
|
|
|
46852
47382
|
return null;
|
|
46853
47383
|
let bytes;
|
|
46854
47384
|
try {
|
|
46855
|
-
bytes =
|
|
47385
|
+
bytes = readFileSync29(absolute2);
|
|
46856
47386
|
} catch {
|
|
46857
47387
|
return null;
|
|
46858
47388
|
}
|
|
@@ -46968,7 +47498,7 @@ function unlinkIfProvablyOurs(workspaceSlug, strategy, entry2, suppressor) {
|
|
|
46968
47498
|
}
|
|
46969
47499
|
let raw;
|
|
46970
47500
|
try {
|
|
46971
|
-
raw =
|
|
47501
|
+
raw = readFileSync30(target, "utf8");
|
|
46972
47502
|
} catch (err) {
|
|
46973
47503
|
if (err?.code === "ENOENT") {
|
|
46974
47504
|
return { didUnlink: false };
|
|
@@ -47023,6 +47553,7 @@ async function replayPermanentDocuments(args) {
|
|
|
47023
47553
|
let state = args.state;
|
|
47024
47554
|
const completed = [];
|
|
47025
47555
|
let completePass = true;
|
|
47556
|
+
let restoresDeferred = 0;
|
|
47026
47557
|
for (const marker of args.markers) {
|
|
47027
47558
|
const candidates = rows.filter((row) => row.deleted_at !== null && row.slug === marker.logical_key && row.permanent_delete === true && typeof row.purge_generation === "string" && typeof row.retirement_sequence === "number");
|
|
47028
47559
|
candidates.sort((a, b) => b.retirement_sequence - a.retirement_sequence);
|
|
@@ -47092,7 +47623,7 @@ async function replayPermanentDocuments(args) {
|
|
|
47092
47623
|
for (const row of rows) {
|
|
47093
47624
|
if (row.deleted_at !== null && row.permanent_delete === true)
|
|
47094
47625
|
continue;
|
|
47095
|
-
const outcome = applyRow(args.workspaceSlug, args.strategy, row, state, args.output, args.suppressor, args.now, args.isFrozen, args.loggedTombstoneSkips);
|
|
47626
|
+
const outcome = applyRow(args.workspaceSlug, args.strategy, row, state, args.output, args.suppressor, args.now, args.isFrozen, args.loggedTombstoneSkips, args.recentUnlinks);
|
|
47096
47627
|
state = outcome.state;
|
|
47097
47628
|
if (outcome.action === "written")
|
|
47098
47629
|
counters.written++;
|
|
@@ -47103,9 +47634,12 @@ async function replayPermanentDocuments(args) {
|
|
|
47103
47634
|
else if (outcome.action === "slug_refused") {
|
|
47104
47635
|
counters.refused = (counters.refused ?? 0) + 1;
|
|
47105
47636
|
completePass = false;
|
|
47637
|
+
} else if (outcome.action === "restore_deferred") {
|
|
47638
|
+
restoresDeferred++;
|
|
47639
|
+
completePass = false;
|
|
47106
47640
|
}
|
|
47107
47641
|
}
|
|
47108
|
-
return { state, counters, cursor: resultingCursor, completed, completePass };
|
|
47642
|
+
return { state, counters, cursor: resultingCursor, completed, completePass, restoresDeferred };
|
|
47109
47643
|
}
|
|
47110
47644
|
async function runSync(workspaceSlug, deps = {}) {
|
|
47111
47645
|
const out = new Output({
|
|
@@ -47226,6 +47760,7 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47226
47760
|
committed = state;
|
|
47227
47761
|
};
|
|
47228
47762
|
const counters = {};
|
|
47763
|
+
let restoresDeferred = 0;
|
|
47229
47764
|
try {
|
|
47230
47765
|
for (const entityType of allEntityTypes()) {
|
|
47231
47766
|
const strategy = requireStrategy(entityType);
|
|
@@ -47247,6 +47782,7 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47247
47782
|
now,
|
|
47248
47783
|
isFrozen: deps.isFrozen,
|
|
47249
47784
|
loggedTombstoneSkips: deps.loggedTombstoneSkips,
|
|
47785
|
+
recentUnlinks: deps.recentUnlinks,
|
|
47250
47786
|
commit: (before, after) => {
|
|
47251
47787
|
const next = commitState(before, after);
|
|
47252
47788
|
committed = next;
|
|
@@ -47256,8 +47792,10 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47256
47792
|
state = replay.state;
|
|
47257
47793
|
commitPending();
|
|
47258
47794
|
counters[entityType] = replay.counters;
|
|
47795
|
+
restoresDeferred += replay.restoresDeferred;
|
|
47259
47796
|
if (replay.completePass) {
|
|
47260
47797
|
updateSyncCursor(slug, entityType, replay.cursor);
|
|
47798
|
+
updateSyncHighWater(slug, entityType, null);
|
|
47261
47799
|
for (const item of replay.completed) {
|
|
47262
47800
|
clearFeedRefresh({
|
|
47263
47801
|
workspaceSlug: slug,
|
|
@@ -47270,8 +47808,11 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47270
47808
|
continue;
|
|
47271
47809
|
}
|
|
47272
47810
|
}
|
|
47273
|
-
|
|
47274
|
-
let
|
|
47811
|
+
const storedMark = entry2.sync_high_water?.[entityType] ?? null;
|
|
47812
|
+
let walkStart = deps.useHighWater === true && storedMark !== null ? storedMark : startCursor;
|
|
47813
|
+
let cursor = walkStart;
|
|
47814
|
+
let lastAdvancedCursor = walkStart;
|
|
47815
|
+
let finalHighWater = null;
|
|
47275
47816
|
let cursorBlocked = false;
|
|
47276
47817
|
let permanentEncountered = false;
|
|
47277
47818
|
const permanentJournalBlocked = entityType === "documents" && dueDocumentRefreshMarkers(slug, now()) === null;
|
|
@@ -47287,13 +47828,26 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47287
47828
|
refused: 0
|
|
47288
47829
|
};
|
|
47289
47830
|
while (true) {
|
|
47290
|
-
|
|
47291
|
-
|
|
47292
|
-
|
|
47293
|
-
|
|
47294
|
-
|
|
47295
|
-
|
|
47296
|
-
|
|
47831
|
+
let page;
|
|
47832
|
+
try {
|
|
47833
|
+
page = await syncEntity({
|
|
47834
|
+
strategy,
|
|
47835
|
+
baseUrl: entry2.api_base_url,
|
|
47836
|
+
pat,
|
|
47837
|
+
cursor,
|
|
47838
|
+
fetchImpl
|
|
47839
|
+
});
|
|
47840
|
+
} catch (err) {
|
|
47841
|
+
if (cursor === walkStart && walkStart !== startCursor && err instanceof ApiError && err.status === 400) {
|
|
47842
|
+
updateSyncHighWater(slug, entityType, null);
|
|
47843
|
+
out.warn(`${entityType}: the stored high-water mark was refused; reading from the cursor instead.`);
|
|
47844
|
+
walkStart = startCursor;
|
|
47845
|
+
cursor = startCursor;
|
|
47846
|
+
lastAdvancedCursor = startCursor;
|
|
47847
|
+
continue;
|
|
47848
|
+
}
|
|
47849
|
+
throw err;
|
|
47850
|
+
}
|
|
47297
47851
|
for (const row of page.rows) {
|
|
47298
47852
|
ctr.fetched++;
|
|
47299
47853
|
if (entityType === "documents" && row.deleted_at !== null && row.permanent_delete === true && row.purge_generation !== undefined) {
|
|
@@ -47314,7 +47868,7 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47314
47868
|
}
|
|
47315
47869
|
let outcome;
|
|
47316
47870
|
try {
|
|
47317
|
-
outcome = applyRow(slug, strategy, row, state, out, deps.suppressor, now, deps.isFrozen, deps.loggedTombstoneSkips);
|
|
47871
|
+
outcome = applyRow(slug, strategy, row, state, out, deps.suppressor, now, deps.isFrozen, deps.loggedTombstoneSkips, deps.recentUnlinks);
|
|
47318
47872
|
} catch (err) {
|
|
47319
47873
|
if (!(err instanceof MirrorSymlinkError))
|
|
47320
47874
|
throw err;
|
|
@@ -47337,14 +47891,19 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47337
47891
|
ctr.deleted++;
|
|
47338
47892
|
else if (outcome.action === "moved")
|
|
47339
47893
|
ctr.moved = (ctr.moved ?? 0) + 1;
|
|
47340
|
-
else if (outcome.action === "
|
|
47894
|
+
else if (outcome.action === "restore_deferred") {
|
|
47895
|
+
cursorBlocked = true;
|
|
47896
|
+
restoresDeferred++;
|
|
47897
|
+
} else if (outcome.action === "slug_refused") {
|
|
47341
47898
|
ctr.refused = (ctr.refused ?? 0) + 1;
|
|
47342
47899
|
cursorBlocked = true;
|
|
47343
47900
|
}
|
|
47344
47901
|
}
|
|
47345
47902
|
commitPending();
|
|
47346
|
-
if (page.next_cursor === null)
|
|
47903
|
+
if (page.next_cursor === null) {
|
|
47904
|
+
finalHighWater = page.high_water ?? null;
|
|
47347
47905
|
break;
|
|
47906
|
+
}
|
|
47348
47907
|
if (!cursorBlocked)
|
|
47349
47908
|
lastAdvancedCursor = page.next_cursor;
|
|
47350
47909
|
cursor = page.next_cursor;
|
|
@@ -47368,6 +47927,7 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47368
47927
|
now,
|
|
47369
47928
|
isFrozen: deps.isFrozen,
|
|
47370
47929
|
loggedTombstoneSkips: deps.loggedTombstoneSkips,
|
|
47930
|
+
recentUnlinks: deps.recentUnlinks,
|
|
47371
47931
|
commit: (before, after) => {
|
|
47372
47932
|
const next = commitState(before, after);
|
|
47373
47933
|
committed = next;
|
|
@@ -47377,8 +47937,10 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47377
47937
|
state = replay.state;
|
|
47378
47938
|
commitPending();
|
|
47379
47939
|
counters[entityType] = replay.counters;
|
|
47940
|
+
restoresDeferred += replay.restoresDeferred;
|
|
47380
47941
|
if (replay.completePass) {
|
|
47381
47942
|
updateSyncCursor(slug, entityType, replay.cursor);
|
|
47943
|
+
updateSyncHighWater(slug, entityType, null);
|
|
47382
47944
|
for (const item of replay.completed) {
|
|
47383
47945
|
clearFeedRefresh({
|
|
47384
47946
|
workspaceSlug: slug,
|
|
@@ -47390,12 +47952,21 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47390
47952
|
}
|
|
47391
47953
|
continue;
|
|
47392
47954
|
}
|
|
47393
|
-
if (lastAdvancedCursor !==
|
|
47955
|
+
if (walkStart === startCursor && lastAdvancedCursor !== walkStart) {
|
|
47394
47956
|
updateSyncCursor(slug, entityType, lastAdvancedCursor);
|
|
47395
47957
|
}
|
|
47958
|
+
const mark = provenMark({
|
|
47959
|
+
clean: !cursorBlocked,
|
|
47960
|
+
finalHighWater,
|
|
47961
|
+
advancedTo: lastAdvancedCursor !== walkStart ? lastAdvancedCursor : null
|
|
47962
|
+
});
|
|
47963
|
+
if (mark !== null)
|
|
47964
|
+
updateSyncHighWater(slug, entityType, mark);
|
|
47396
47965
|
}
|
|
47397
47966
|
const needsDerivedTextRewalk = state.derived_text_version !== DERIVED_TEXT_VERSION;
|
|
47398
|
-
const
|
|
47967
|
+
const filesBaseCursor = needsDerivedTextRewalk ? null : entry2.sync_cursors?.["files"] ?? null;
|
|
47968
|
+
const filesMark = entry2.sync_high_water?.["files"] ?? null;
|
|
47969
|
+
const filesStartCursor = !needsDerivedTextRewalk && deps.useHighWater === true && filesMark !== null ? filesMark : filesBaseCursor;
|
|
47399
47970
|
if (needsDerivedTextRewalk) {
|
|
47400
47971
|
out.info("Re-reading the file index once to refresh extracted text (template changed).");
|
|
47401
47972
|
}
|
|
@@ -47405,6 +47976,8 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47405
47976
|
pat,
|
|
47406
47977
|
state,
|
|
47407
47978
|
cursor: filesStartCursor,
|
|
47979
|
+
baseCursor: filesBaseCursor,
|
|
47980
|
+
recentUnlinks: deps.recentUnlinks,
|
|
47408
47981
|
fetchImpl,
|
|
47409
47982
|
now,
|
|
47410
47983
|
output: out,
|
|
@@ -47418,7 +47991,8 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47418
47991
|
if (filesResult.state !== state) {
|
|
47419
47992
|
state = filesResult.state;
|
|
47420
47993
|
}
|
|
47421
|
-
|
|
47994
|
+
restoresDeferred += filesResult.restoresDeferred ?? 0;
|
|
47995
|
+
if (needsDerivedTextRewalk && filesResult.textUnknown === 0 && (filesResult.restoresDeferred ?? 0) === 0) {
|
|
47422
47996
|
state = { ...state, derived_text_version: DERIVED_TEXT_VERSION };
|
|
47423
47997
|
}
|
|
47424
47998
|
counters["files"] = {
|
|
@@ -47427,10 +48001,19 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47427
48001
|
deleted: filesResult.deleted + filesResult.textDeleted
|
|
47428
48002
|
};
|
|
47429
48003
|
commitPending();
|
|
47430
|
-
|
|
48004
|
+
const filesWalkStart = filesResult.markRefused ? filesBaseCursor : filesStartCursor;
|
|
48005
|
+
const filesWalkedFromCursor = filesWalkStart === filesBaseCursor || filesResult.permanentReplayComplete === true;
|
|
48006
|
+
if (filesWalkedFromCursor && filesResult.lastCursor !== filesBaseCursor) {
|
|
47431
48007
|
updateSyncCursor(slug, "files", filesResult.lastCursor);
|
|
47432
48008
|
}
|
|
48009
|
+
if (filesResult.markRefused) {
|
|
48010
|
+
updateSyncHighWater(slug, "files", null);
|
|
48011
|
+
out.warn("files: the stored high-water mark was refused; reading from the cursor instead.");
|
|
48012
|
+
}
|
|
48013
|
+
if (filesResult.highWater)
|
|
48014
|
+
updateSyncHighWater(slug, "files", filesResult.highWater);
|
|
47433
48015
|
if (filesResult.permanentReplayComplete) {
|
|
48016
|
+
updateSyncHighWater(slug, "files", null);
|
|
47434
48017
|
for (const item of filesResult.permanentRefreshes ?? []) {
|
|
47435
48018
|
clearFeedRefresh({
|
|
47436
48019
|
workspaceSlug: slug,
|
|
@@ -47618,7 +48201,7 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47618
48201
|
const totalRefused = Object.values(counters).reduce((sum, c) => sum + (c.refused ?? 0), 0);
|
|
47619
48202
|
if (totalWritten === 0 && totalDeleted === 0 && totalMoved === 0 && totalRefused === 0) {
|
|
47620
48203
|
out.success("Already up to date.");
|
|
47621
|
-
return { exitCode: 0, applied: 0 };
|
|
48204
|
+
return { exitCode: 0, applied: 0, restoresDeferred };
|
|
47622
48205
|
}
|
|
47623
48206
|
const elapsed = ((now().getTime() - startedAt.getTime()) / 1000).toFixed(1);
|
|
47624
48207
|
for (const [type, c] of Object.entries(counters)) {
|
|
@@ -47636,9 +48219,9 @@ async function runSync(workspaceSlug, deps = {}) {
|
|
|
47636
48219
|
out.info(` ${type}: ${parts.join(", ")}`);
|
|
47637
48220
|
}
|
|
47638
48221
|
out.success(`Done in ${elapsed}s.`);
|
|
47639
|
-
return { exitCode: 0, applied: totalWritten + totalMoved + totalDeleted };
|
|
48222
|
+
return { exitCode: 0, applied: totalWritten + totalMoved + totalDeleted, restoresDeferred };
|
|
47640
48223
|
}
|
|
47641
|
-
function applyRow(workspaceSlug, strategy, row, state, out, suppressor, now = () => new Date, isFrozen, loggedTombstoneSkips) {
|
|
48224
|
+
function applyRow(workspaceSlug, strategy, row, state, out, suppressor, now = () => new Date, isFrozen, loggedTombstoneSkips, recentUnlinks) {
|
|
47642
48225
|
if (isFrozen?.(strategy.type, row.id)) {
|
|
47643
48226
|
return { state, action: "skipped" };
|
|
47644
48227
|
}
|
|
@@ -47750,7 +48333,22 @@ function applyRow(workspaceSlug, strategy, row, state, out, suppressor, now = ()
|
|
|
47750
48333
|
const writeSlug = relocation.writeSlug;
|
|
47751
48334
|
if (existingEntry && existingEntry.updated_at === live.updated_at && relocation.kind === "not_needed") {
|
|
47752
48335
|
const path5 = entityFilePath(workspaceSlug, strategy.type, live.slug);
|
|
47753
|
-
|
|
48336
|
+
const present2 = existsSync9(path5);
|
|
48337
|
+
if (!present2) {
|
|
48338
|
+
const verdict = deferRestore(recentUnlinks, path5, live.updated_at, now().getTime());
|
|
48339
|
+
if (verdict.defer) {
|
|
48340
|
+
if (verdict.first) {
|
|
48341
|
+
out.log(entityRestoreDeferredLine({
|
|
48342
|
+
entityType: strategy.type,
|
|
48343
|
+
slug: live.slug,
|
|
48344
|
+
waitSeconds: RESTORE_DEFERRAL_MS / 1000
|
|
48345
|
+
}));
|
|
48346
|
+
}
|
|
48347
|
+
return { state, action: "restore_deferred" };
|
|
48348
|
+
}
|
|
48349
|
+
}
|
|
48350
|
+
if (present2) {
|
|
48351
|
+
presentAgain(recentUnlinks, path5);
|
|
47754
48352
|
if (strategy.type === "artists") {
|
|
47755
48353
|
const artistLive = live;
|
|
47756
48354
|
state = applyArtistMemoryOnPull({
|
|
@@ -47777,13 +48375,16 @@ function applyRow(workspaceSlug, strategy, row, state, out, suppressor, now = ()
|
|
|
47777
48375
|
if (!suppressor && existingEntry && existingEntry.updated_at !== live.updated_at && relocation.kind !== "moved") {
|
|
47778
48376
|
let localRaw = null;
|
|
47779
48377
|
try {
|
|
47780
|
-
localRaw =
|
|
48378
|
+
localRaw = readFileSync30(entityFilePath(workspaceSlug, strategy.type, writeSlug), "utf8");
|
|
47781
48379
|
} catch {
|
|
47782
48380
|
localRaw = null;
|
|
47783
48381
|
}
|
|
47784
48382
|
if (localRaw !== null) {
|
|
47785
|
-
|
|
47786
|
-
|
|
48383
|
+
let diverged = !strategy.preserveLocalBodyOnPull && frontmatterParseError(localRaw) !== null;
|
|
48384
|
+
if (!diverged) {
|
|
48385
|
+
const localHashes = computeMirrorHashes(strategy, localRaw);
|
|
48386
|
+
diverged = localHashes.bodyHash !== existingEntry.body_hash || localHashes.frontmatterHash !== existingEntry.frontmatter_hash;
|
|
48387
|
+
}
|
|
47787
48388
|
if (diverged) {
|
|
47788
48389
|
const stashPath = stashConflict({
|
|
47789
48390
|
workspaceSlug,
|
|
@@ -47801,7 +48402,7 @@ function applyRow(workspaceSlug, strategy, row, state, out, suppressor, now = ()
|
|
|
47801
48402
|
const target = entityFilePath(workspaceSlug, strategy.type, writeSlug);
|
|
47802
48403
|
let localRaw = null;
|
|
47803
48404
|
try {
|
|
47804
|
-
localRaw =
|
|
48405
|
+
localRaw = lstatSync7(target).isFile() ? readFileSync30(target, "utf8") : null;
|
|
47805
48406
|
} catch {
|
|
47806
48407
|
localRaw = null;
|
|
47807
48408
|
}
|
|
@@ -47967,7 +48568,7 @@ function backfillStateInodes(workspaceSlug, state, statFn = (p) => statSync18(p)
|
|
|
47967
48568
|
return { state, changed: false };
|
|
47968
48569
|
return { state: { ...state, entries: nextEntries }, changed: true };
|
|
47969
48570
|
}
|
|
47970
|
-
function backfillBodyLengths(workspaceSlug, state, readFn = (p) =>
|
|
48571
|
+
function backfillBodyLengths(workspaceSlug, state, readFn = (p) => readFileSync31(p, "utf8")) {
|
|
47971
48572
|
let changed = false;
|
|
47972
48573
|
const nextEntries = {};
|
|
47973
48574
|
for (const [entityType, bucket] of Object.entries(state.entries)) {
|
|
@@ -47979,7 +48580,7 @@ function backfillBodyLengths(workspaceSlug, state, readFn = (p) => readFileSync3
|
|
|
47979
48580
|
}
|
|
47980
48581
|
try {
|
|
47981
48582
|
const path5 = entityFilePath(workspaceSlug, entityType, entry2.slug);
|
|
47982
|
-
const bodyLen =
|
|
48583
|
+
const bodyLen = parseUncached(readFn(path5)).content.length;
|
|
47983
48584
|
nextBucket[id] = { ...entry2, body_len: bodyLen };
|
|
47984
48585
|
changed = true;
|
|
47985
48586
|
} catch {
|
|
@@ -47992,6 +48593,18 @@ function backfillBodyLengths(workspaceSlug, state, readFn = (p) => readFileSync3
|
|
|
47992
48593
|
return { state, changed: false };
|
|
47993
48594
|
return { state: { ...state, entries: nextEntries }, changed: true };
|
|
47994
48595
|
}
|
|
48596
|
+
function isOwnWriteEcho(suppressor, absolutePath, strategy, raw) {
|
|
48597
|
+
if (frontmatterParseError(raw) !== null)
|
|
48598
|
+
return false;
|
|
48599
|
+
return suppressor.shouldSuppressUpsert(absolutePath, computeMirrorHashes(strategy, raw));
|
|
48600
|
+
}
|
|
48601
|
+
function destinationIsUnreadable(absolutePath) {
|
|
48602
|
+
try {
|
|
48603
|
+
return frontmatterParseError(readFileSync31(absolutePath, "utf8")) !== null;
|
|
48604
|
+
} catch {
|
|
48605
|
+
return false;
|
|
48606
|
+
}
|
|
48607
|
+
}
|
|
47995
48608
|
function makeWatchHandler(deps) {
|
|
47996
48609
|
const {
|
|
47997
48610
|
workspaceSlug,
|
|
@@ -48108,7 +48721,7 @@ function makeWatchHandler(deps) {
|
|
|
48108
48721
|
spinner.update(`Watching ${workspaceSlug} · ${watchingSummary(getState())}`);
|
|
48109
48722
|
return;
|
|
48110
48723
|
}
|
|
48111
|
-
const bytes =
|
|
48724
|
+
const bytes = readFileSync31(absolutePath);
|
|
48112
48725
|
const hash2 = sha256Bytes(bytes);
|
|
48113
48726
|
if (suppressor?.shouldSuppressUpsert(absolutePath, {
|
|
48114
48727
|
bodyHash: hash2,
|
|
@@ -48172,7 +48785,7 @@ function makeWatchHandler(deps) {
|
|
|
48172
48785
|
if (suppressor) {
|
|
48173
48786
|
let raw = null;
|
|
48174
48787
|
try {
|
|
48175
|
-
raw =
|
|
48788
|
+
raw = readFileSync31(absolutePath, "utf8");
|
|
48176
48789
|
} catch {
|
|
48177
48790
|
raw = null;
|
|
48178
48791
|
}
|
|
@@ -48211,7 +48824,7 @@ function makeWatchHandler(deps) {
|
|
|
48211
48824
|
if (suppressor) {
|
|
48212
48825
|
let raw = null;
|
|
48213
48826
|
try {
|
|
48214
|
-
raw =
|
|
48827
|
+
raw = readFileSync31(absolutePath, "utf8");
|
|
48215
48828
|
} catch {
|
|
48216
48829
|
raw = null;
|
|
48217
48830
|
}
|
|
@@ -48250,7 +48863,7 @@ function makeWatchHandler(deps) {
|
|
|
48250
48863
|
if (suppressor) {
|
|
48251
48864
|
let raw = null;
|
|
48252
48865
|
try {
|
|
48253
|
-
raw =
|
|
48866
|
+
raw = readFileSync31(absolutePath, "utf8");
|
|
48254
48867
|
} catch {
|
|
48255
48868
|
raw = null;
|
|
48256
48869
|
}
|
|
@@ -48289,7 +48902,7 @@ function makeWatchHandler(deps) {
|
|
|
48289
48902
|
if (suppressor) {
|
|
48290
48903
|
let raw = null;
|
|
48291
48904
|
try {
|
|
48292
|
-
raw =
|
|
48905
|
+
raw = readFileSync31(absolutePath, "utf8");
|
|
48293
48906
|
} catch {
|
|
48294
48907
|
raw = null;
|
|
48295
48908
|
}
|
|
@@ -48328,7 +48941,7 @@ function makeWatchHandler(deps) {
|
|
|
48328
48941
|
if (suppressor) {
|
|
48329
48942
|
let raw = null;
|
|
48330
48943
|
try {
|
|
48331
|
-
raw =
|
|
48944
|
+
raw = readFileSync31(absolutePath, "utf8");
|
|
48332
48945
|
} catch {
|
|
48333
48946
|
raw = null;
|
|
48334
48947
|
}
|
|
@@ -48374,11 +48987,11 @@ function makeWatchHandler(deps) {
|
|
|
48374
48987
|
const strategy = getStrategy(classifiedFrom.entityType);
|
|
48375
48988
|
let toRaw = null;
|
|
48376
48989
|
try {
|
|
48377
|
-
toRaw =
|
|
48990
|
+
toRaw = readFileSync31(event.toPath, "utf8");
|
|
48378
48991
|
} catch {
|
|
48379
48992
|
toRaw = null;
|
|
48380
48993
|
}
|
|
48381
|
-
const isEcho = strategy !== undefined && toRaw !== null && suppressor
|
|
48994
|
+
const isEcho = strategy !== undefined && toRaw !== null && isOwnWriteEcho(suppressor, event.toPath, strategy, toRaw);
|
|
48382
48995
|
if (isEcho) {
|
|
48383
48996
|
output?.log(`[RENAME_SUPPRESSED entityType=${classifiedFrom.entityType} ` + `from=${event.fromSlug} id=${event.id} reason=pull_echo]`);
|
|
48384
48997
|
return;
|
|
@@ -48386,6 +48999,9 @@ function makeWatchHandler(deps) {
|
|
|
48386
48999
|
await handle({ kind: "upsert", absolutePath: event.toPath });
|
|
48387
49000
|
return;
|
|
48388
49001
|
}
|
|
49002
|
+
if (destinationIsUnreadable(event.toPath)) {
|
|
49003
|
+
await handle({ kind: "upsert", absolutePath: event.toPath });
|
|
49004
|
+
}
|
|
48389
49005
|
const base3 = getState();
|
|
48390
49006
|
const result2 = await pushRename({
|
|
48391
49007
|
workspaceSlug,
|
|
@@ -48443,7 +49059,7 @@ function makeWatchHandler(deps) {
|
|
|
48443
49059
|
if (event.kind === "upsert") {
|
|
48444
49060
|
try {
|
|
48445
49061
|
ownEcho = suppressor?.shouldSuppressUpsert(absolutePath, {
|
|
48446
|
-
bodyHash: stableHash(
|
|
49062
|
+
bodyHash: stableHash(readFileSync31(absolutePath, "utf8")),
|
|
48447
49063
|
frontmatterHash: ""
|
|
48448
49064
|
}) === true;
|
|
48449
49065
|
} catch {
|
|
@@ -48498,11 +49114,11 @@ function makeWatchHandler(deps) {
|
|
|
48498
49114
|
if (strategy) {
|
|
48499
49115
|
let raw = null;
|
|
48500
49116
|
try {
|
|
48501
|
-
raw =
|
|
49117
|
+
raw = readFileSync31(absolutePath, "utf8");
|
|
48502
49118
|
} catch {
|
|
48503
49119
|
raw = null;
|
|
48504
49120
|
}
|
|
48505
|
-
if (raw !== null && suppressor
|
|
49121
|
+
if (raw !== null && isOwnWriteEcho(suppressor, absolutePath, strategy, raw)) {
|
|
48506
49122
|
return;
|
|
48507
49123
|
}
|
|
48508
49124
|
}
|
|
@@ -48553,7 +49169,8 @@ function makeWatchHandler(deps) {
|
|
|
48553
49169
|
echo: suppressor ? { watcher: "live", suppressor } : undefined
|
|
48554
49170
|
}
|
|
48555
49171
|
});
|
|
48556
|
-
|
|
49172
|
+
const heldUnreadable = result.outcome.kind === "ignored" && result.outcome.reason === FRONTMATTER_PARSE_ERROR;
|
|
49173
|
+
applyState(result.base ?? base2, heldUnreadable ? result.state : clearEntityRejected(result.state, workspaceSlug, absolutePath));
|
|
48557
49174
|
reportOutcome(spinner, workspaceSlug, getState(), result.outcome);
|
|
48558
49175
|
} catch (err) {
|
|
48559
49176
|
const msg = err instanceof Error ? err.message : "unknown push error";
|
|
@@ -49101,7 +49718,7 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
49101
49718
|
return;
|
|
49102
49719
|
}
|
|
49103
49720
|
refreshFrozen();
|
|
49104
|
-
out.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${batch.length} reason=${result.reason
|
|
49721
|
+
out.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${batch.length} reason=${tripReasonToken(result.reason)}]`);
|
|
49105
49722
|
spinner.warn(`Held ${batch.length} destructive op${batch.length === 1 ? "" : "s"} pending confirm → ${stashPath}. Run \`plinth confirm\` to review.`);
|
|
49106
49723
|
spinner.update(`Watching ${slug} · ${watchingSummary(state)}`);
|
|
49107
49724
|
}
|
|
@@ -49157,7 +49774,7 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
49157
49774
|
return;
|
|
49158
49775
|
}
|
|
49159
49776
|
refreshFrozen();
|
|
49160
|
-
out.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${batch.length} reason=${result.reason
|
|
49777
|
+
out.log(`[DESTRUCTIVE_HELD batch=${batchId} ops=${batch.length} reason=${tripReasonToken(result.reason)} source=files-watch]`);
|
|
49161
49778
|
spinner.warn(`Held ${batch.length} destructive file op${batch.length === 1 ? "" : "s"} pending confirm → ${stashPath}. Run \`plinth confirm\` to review.`);
|
|
49162
49779
|
spinner.update(`Watching ${slug} · ${watchingSummary(state)}`);
|
|
49163
49780
|
}
|
|
@@ -49187,13 +49804,18 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
49187
49804
|
return { exitCode: 0 };
|
|
49188
49805
|
}
|
|
49189
49806
|
writeDaemonPid(slug, process.pid, (deps.now ?? (() => new Date))());
|
|
49190
|
-
const
|
|
49807
|
+
const ambient = createAmbientPollWiring({
|
|
49808
|
+
now: () => (deps.now?.() ?? new Date).getTime(),
|
|
49809
|
+
baseReadIntervalMs: baseReadIntervalMs()
|
|
49810
|
+
});
|
|
49811
|
+
const watcher = startWatcher({ workspaceSlug: slug, getStateEntry, classifyPath, onUnlink: ambient.onUnlink }, handler);
|
|
49191
49812
|
const quietSink = discardSink();
|
|
49192
49813
|
let draining = false;
|
|
49193
49814
|
let lastHeartbeatMs = 0;
|
|
49194
49815
|
const pullLoop = new PullLoop({
|
|
49195
49816
|
runOnce: async () => {
|
|
49196
|
-
const result = await runSync(slug, {
|
|
49817
|
+
const result = await ambient.poll((sc2) => runSync(slug, {
|
|
49818
|
+
...sc2,
|
|
49197
49819
|
fetchImpl,
|
|
49198
49820
|
keychain,
|
|
49199
49821
|
now: deps.now,
|
|
@@ -49205,7 +49827,7 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
49205
49827
|
isFileFrozen,
|
|
49206
49828
|
loggedTombstoneSkips,
|
|
49207
49829
|
loggedStashNotices
|
|
49208
|
-
});
|
|
49830
|
+
}));
|
|
49209
49831
|
if (result.exitCode === 0) {
|
|
49210
49832
|
const applied = result.applied ?? 0;
|
|
49211
49833
|
if (applied > 0) {
|
|
@@ -49244,6 +49866,9 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
49244
49866
|
afterPoll: () => {
|
|
49245
49867
|
(async () => {
|
|
49246
49868
|
state = loadState(slug);
|
|
49869
|
+
const unsettled = state;
|
|
49870
|
+
const settled = settleFrontmatterHolds(unsettled, slug);
|
|
49871
|
+
state = rebaseState(state, unsettled, settled);
|
|
49247
49872
|
if (draining)
|
|
49248
49873
|
return;
|
|
49249
49874
|
draining = true;
|
|
@@ -49465,12 +50090,12 @@ async function runStart(workspaceSlug, deps = {}) {
|
|
|
49465
50090
|
try {
|
|
49466
50091
|
await Promise.race([
|
|
49467
50092
|
watcher.drainPending().then(() => Promise.all([gate.drain(), fileGate.drain()])),
|
|
49468
|
-
new Promise((
|
|
50093
|
+
new Promise((resolve7) => setTimeout(() => resolve7(-1), SHUTDOWN_DRAIN_TIMEOUT_MS))
|
|
49469
50094
|
]);
|
|
49470
50095
|
} finally {
|
|
49471
50096
|
await Promise.race([
|
|
49472
50097
|
watcher.stop(),
|
|
49473
|
-
new Promise((
|
|
50098
|
+
new Promise((resolve7) => setTimeout(resolve7, SHUTDOWN_DRAIN_TIMEOUT_MS))
|
|
49474
50099
|
]);
|
|
49475
50100
|
clearDaemonPid(slug);
|
|
49476
50101
|
releaseDaemonLock(lockHandle);
|
|
@@ -50025,7 +50650,7 @@ function assessContextWalls(workspaceSlug, deps = {}) {
|
|
|
50025
50650
|
|
|
50026
50651
|
// src/lib/build-stamp.ts
|
|
50027
50652
|
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
50028
|
-
import { existsSync as existsSync11, mkdirSync as mkdirSync12, readFileSync as
|
|
50653
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync12, readFileSync as readFileSync32, writeFileSync as writeFileSync8 } from "node:fs";
|
|
50029
50654
|
import { dirname as dirname14, join as join19 } from "node:path";
|
|
50030
50655
|
import { fileURLToPath } from "node:url";
|
|
50031
50656
|
var BUILD_STAMP_FILENAME = "build-stamp.json";
|
|
@@ -50049,7 +50674,7 @@ function readHead(repoDir) {
|
|
|
50049
50674
|
}
|
|
50050
50675
|
function readBuildStamp(distDir) {
|
|
50051
50676
|
try {
|
|
50052
|
-
const parsed = JSON.parse(
|
|
50677
|
+
const parsed = JSON.parse(readFileSync32(stampPath(distDir), "utf8"));
|
|
50053
50678
|
if (parsed && typeof parsed.sha === "string" && parsed.sha.length > 0 && typeof parsed.dirty === "boolean" && typeof parsed.built_at === "string") {
|
|
50054
50679
|
return { sha: parsed.sha, dirty: parsed.dirty, built_at: parsed.built_at };
|
|
50055
50680
|
}
|
|
@@ -50110,12 +50735,12 @@ function checkBuildStaleness(deps = {}) {
|
|
|
50110
50735
|
}
|
|
50111
50736
|
|
|
50112
50737
|
// src/lib/memory-links.ts
|
|
50113
|
-
import { readFileSync as
|
|
50114
|
-
import { relative as relative15, sep as
|
|
50738
|
+
import { readFileSync as readFileSync33 } from "node:fs";
|
|
50739
|
+
import { relative as relative15, sep as sep9 } from "node:path";
|
|
50115
50740
|
var MEMORY_FILE_RE = /^(workspace-memory\.md|user-memory\.md|artists\/[^/]+\/memory\.md|memory\/[^/]+\.md|memory\/private\/[^/]+\.md)$/i;
|
|
50116
50741
|
function scanMemoryLinks(deps) {
|
|
50117
50742
|
const listFiles = deps.listFiles ?? ((r) => walkSubtreeFiles(r, isVcsInternal));
|
|
50118
|
-
const readFile = deps.readFile ?? ((p) =>
|
|
50743
|
+
const readFile = deps.readFile ?? ((p) => readFileSync33(p, "utf8"));
|
|
50119
50744
|
let root;
|
|
50120
50745
|
let absPaths;
|
|
50121
50746
|
try {
|
|
@@ -50124,7 +50749,7 @@ function scanMemoryLinks(deps) {
|
|
|
50124
50749
|
} catch {
|
|
50125
50750
|
return { scanned: 0, unreadable: [], dead: [] };
|
|
50126
50751
|
}
|
|
50127
|
-
const relPaths = absPaths.map((p) => relative15(root, p).split(
|
|
50752
|
+
const relPaths = absPaths.map((p) => relative15(root, p).split(sep9).join("/"));
|
|
50128
50753
|
const artists = buildArtistIndex(relPaths, root, (abs) => readFile(abs));
|
|
50129
50754
|
const dead = [];
|
|
50130
50755
|
const unreadable = [];
|
|
@@ -50159,9 +50784,8 @@ function scanMemoryLinks(deps) {
|
|
|
50159
50784
|
init_output();
|
|
50160
50785
|
|
|
50161
50786
|
// src/lib/pending-writes.ts
|
|
50162
|
-
import { readFileSync as
|
|
50787
|
+
import { readFileSync as readFileSync34, statSync as statSync19 } from "node:fs";
|
|
50163
50788
|
import { relative as relative16 } from "node:path";
|
|
50164
|
-
var import_gray_matter15 = __toESM(require_gray_matter(), 1);
|
|
50165
50789
|
var EMPTY3 = { writes: [], count: 0 };
|
|
50166
50790
|
function reportedElsewhere(state) {
|
|
50167
50791
|
const entities = new Set;
|
|
@@ -50207,7 +50831,7 @@ function scanPendingWrites(args) {
|
|
|
50207
50831
|
continue;
|
|
50208
50832
|
let hash2;
|
|
50209
50833
|
try {
|
|
50210
|
-
hash2 = sha256Bytes(
|
|
50834
|
+
hash2 = sha256Bytes(readFileSync34(absolutePath));
|
|
50211
50835
|
} catch {
|
|
50212
50836
|
continue;
|
|
50213
50837
|
}
|
|
@@ -50238,17 +50862,15 @@ function scanPendingWrites(args) {
|
|
|
50238
50862
|
continue;
|
|
50239
50863
|
let content;
|
|
50240
50864
|
try {
|
|
50241
|
-
content =
|
|
50865
|
+
content = readFileSync34(absolutePath, "utf8");
|
|
50242
50866
|
} catch {
|
|
50243
50867
|
continue;
|
|
50244
50868
|
}
|
|
50245
50869
|
if (!entry2) {
|
|
50246
|
-
|
|
50247
|
-
|
|
50248
|
-
frontmatter = import_gray_matter15.default(content).data ?? {};
|
|
50249
|
-
} catch {
|
|
50870
|
+
const read = readFrontmatter(content);
|
|
50871
|
+
if (holdsUnreadableUntracked({ entityType, entitySlug, readable: read.ok }))
|
|
50250
50872
|
continue;
|
|
50251
|
-
}
|
|
50873
|
+
const frontmatter = read.ok ? read.parsed.data : {};
|
|
50252
50874
|
const verdict = classifyUntrackedEntity({
|
|
50253
50875
|
entityType,
|
|
50254
50876
|
entitySlug,
|
|
@@ -50704,7 +51326,7 @@ async function runStatus(args, deps = {}) {
|
|
|
50704
51326
|
}
|
|
50705
51327
|
}
|
|
50706
51328
|
if (freshness.length > 0) {
|
|
50707
|
-
out.info(row("Memory context", `${freshness.length} file${freshness.length === 1 ? "" : "s"} could not be marked with ${freshness.length === 1 ? "its" : "their"} current memory state
|
|
51329
|
+
out.info(row("Memory context", `${freshness.length} file${freshness.length === 1 ? "" : "s"} could not be marked with ${freshness.length === 1 ? "its" : "their"} current memory state.`));
|
|
50708
51330
|
for (const r of freshness.slice(0, MAX_SHOWN)) {
|
|
50709
51331
|
out.info(cont(r.message));
|
|
50710
51332
|
}
|
|
@@ -51149,7 +51771,7 @@ var versionCommand = defineCommand({
|
|
|
51149
51771
|
});
|
|
51150
51772
|
|
|
51151
51773
|
// src/commands/voice-gate.ts
|
|
51152
|
-
import { readFileSync as
|
|
51774
|
+
import { readFileSync as readFileSync37 } from "node:fs";
|
|
51153
51775
|
|
|
51154
51776
|
// src/lib/voice-gate/api.ts
|
|
51155
51777
|
var VoiceGateSurfaces = [
|
|
@@ -51254,7 +51876,7 @@ async function callVoiceGate(req) {
|
|
|
51254
51876
|
import { createHash as createHash7 } from "node:crypto";
|
|
51255
51877
|
import {
|
|
51256
51878
|
mkdirSync as mkdirSync13,
|
|
51257
|
-
readFileSync as
|
|
51879
|
+
readFileSync as readFileSync35,
|
|
51258
51880
|
renameSync as renameSync9,
|
|
51259
51881
|
unlinkSync as unlinkSync10,
|
|
51260
51882
|
writeFileSync as writeFileSync9
|
|
@@ -51285,7 +51907,7 @@ function emptyFile() {
|
|
|
51285
51907
|
}
|
|
51286
51908
|
function load2(workspaceSlug) {
|
|
51287
51909
|
try {
|
|
51288
|
-
const raw =
|
|
51910
|
+
const raw = readFileSync35(attemptsPath(workspaceSlug), "utf8");
|
|
51289
51911
|
const parsed = AttemptsFileSchema.safeParse(JSON.parse(raw));
|
|
51290
51912
|
return parsed.success ? parsed.data : emptyFile();
|
|
51291
51913
|
} catch {
|
|
@@ -51448,9 +52070,9 @@ function denyUngated(detail) {
|
|
|
51448
52070
|
}
|
|
51449
52071
|
|
|
51450
52072
|
// src/lib/voice-gate/payload.ts
|
|
51451
|
-
import { readFileSync as
|
|
52073
|
+
import { readFileSync as readFileSync36, statSync as statSync21 } from "node:fs";
|
|
51452
52074
|
import { homedir as homedir2 } from "node:os";
|
|
51453
|
-
import { isAbsolute as isAbsolute4, resolve as
|
|
52075
|
+
import { isAbsolute as isAbsolute4, resolve as resolve7 } from "node:path";
|
|
51454
52076
|
var MAX_BODY_BYTES = 256 * 1024;
|
|
51455
52077
|
var HELPER_BASENAME = "push_gmail_draft.py";
|
|
51456
52078
|
var DRAFT_FLAGS = ["--to", "--body-file", "--body-stdin"];
|
|
@@ -51609,7 +52231,7 @@ function defaultReadBodyFile(path5) {
|
|
|
51609
52231
|
if (stat4.size > MAX_BODY_BYTES) {
|
|
51610
52232
|
throw new Error(`draft body is ${stat4.size} bytes, over the ${MAX_BODY_BYTES}-byte gate limit`);
|
|
51611
52233
|
}
|
|
51612
|
-
return
|
|
52234
|
+
return readFileSync36(path5, "utf8");
|
|
51613
52235
|
}
|
|
51614
52236
|
function parseRecipients(...raw) {
|
|
51615
52237
|
const all = raw.filter((v) => typeof v === "string").flatMap((v) => v.split(",")).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0);
|
|
@@ -51644,13 +52266,13 @@ function applyCd(tokens, state) {
|
|
|
51644
52266
|
return { dir: expanded, known: true };
|
|
51645
52267
|
if (!state.known)
|
|
51646
52268
|
return state;
|
|
51647
|
-
return { dir:
|
|
52269
|
+
return { dir: resolve7(state.dir, expanded), known: true };
|
|
51648
52270
|
}
|
|
51649
52271
|
function expandHome(path5) {
|
|
51650
52272
|
if (path5 === "~")
|
|
51651
52273
|
return homedir2();
|
|
51652
52274
|
if (path5.startsWith("~/"))
|
|
51653
|
-
return
|
|
52275
|
+
return resolve7(homedir2(), path5.slice(2));
|
|
51654
52276
|
return path5;
|
|
51655
52277
|
}
|
|
51656
52278
|
function classifyHelperSegment(tokens, dirState, readBodyFile) {
|
|
@@ -51676,7 +52298,7 @@ function classifyHelperSegment(tokens, dirState, readBodyFile) {
|
|
|
51676
52298
|
unreadable: `the draft push uses a relative \`--body-file\` (${bodyFile}) after a \`cd\` ` + `the voice gate cannot resolve, so it cannot tell which file ships. Pass an ` + `absolute \`--body-file\` path.`
|
|
51677
52299
|
};
|
|
51678
52300
|
}
|
|
51679
|
-
const resolved = isAbsolute4(expanded) ? expanded :
|
|
52301
|
+
const resolved = isAbsolute4(expanded) ? expanded : resolve7(dirState.dir, expanded);
|
|
51680
52302
|
let body;
|
|
51681
52303
|
try {
|
|
51682
52304
|
body = readBodyFile(resolved);
|
|
@@ -51969,7 +52591,7 @@ async function runVoiceGateDirect(args, deps = {}) {
|
|
|
51969
52591
|
let text;
|
|
51970
52592
|
if (args.file) {
|
|
51971
52593
|
try {
|
|
51972
|
-
text =
|
|
52594
|
+
text = readFileSync37(args.file, "utf8");
|
|
51973
52595
|
} catch (err) {
|
|
51974
52596
|
const msg = err instanceof Error ? err.message : "unknown error";
|
|
51975
52597
|
out.error(`Could not read ${args.file}: ${msg}`);
|