@agentshouse/kit 0.1.0-alpha.4 → 0.1.0-alpha.40
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/README.md +41 -1671
- package/bin/connect-linux.sh +501 -53
- package/bin/connect-windows.ps1 +430 -0
- package/bin/kit.js +200 -159
- package/bootstrap.json +28 -0
- package/docs/configuration.md +104 -0
- package/docs/google.md +33 -0
- package/docs/linux.md +77 -0
- package/docs/macos.md +26 -0
- package/docs/telegram.md +29 -0
- package/docs/windows.md +36 -0
- package/package.json +7 -2
- package/src/adapter-mcp.js +92 -12
- package/src/agents.js +63 -8
- package/src/binding.js +52 -0
- package/src/blocks.js +2 -26
- package/src/bridge.js +28 -14
- package/src/bytes.js +19 -8
- package/src/configuration.js +14 -211
- package/src/connector-login.js +3 -84
- package/src/contract.js +44 -12
- package/src/conversation-originals.js +1 -3
- package/src/delivery.js +4 -7
- package/src/directory.js +6 -26
- package/src/draft.js +4 -82
- package/src/git-main.js +138 -0
- package/src/git.js +62 -0
- package/src/google/calendar.js +39 -45
- package/src/google/drive.js +61 -186
- package/src/google/entries.js +349 -0
- package/src/google/gateway.js +3 -6
- package/src/google/gmail.js +33 -23
- package/src/google/oauth.js +106 -66
- package/src/google/source.js +21 -23
- package/src/host.js +6 -2
- package/src/house-main.js +65 -8
- package/src/house.js +446 -127
- package/src/how-we-work.md +25 -0
- package/src/inbound.js +2 -18
- package/src/kit-work-stream.js +120 -0
- package/src/launcher.js +46 -10
- package/src/local-files.js +3 -11
- package/src/login.js +27 -14
- package/src/mcp.js +18 -0
- package/src/mount.js +9 -18
- package/src/original.js +4 -4
- package/src/pacing.js +41 -0
- package/src/plan.js +37 -45
- package/src/repository.js +3 -61
- package/src/session-bin/git +2 -0
- package/src/setup.js +14 -15
- package/src/skill-set/ask-matt/PHASE-BOUNDARIES.md +55 -0
- package/src/skill-set/ask-matt/SKILL.md +91 -0
- package/src/skill-set/ask-matt/agents/openai.yaml +4 -0
- package/src/skill-set/code-review/LICENSE +21 -0
- package/src/skill-set/code-review/SKILL.md +89 -0
- package/src/skill-set/code-review/agents/openai.yaml +3 -0
- package/src/skill-set/codebase-design/DEEPENING.md +37 -0
- package/src/skill-set/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/src/skill-set/codebase-design/LICENSE +21 -0
- package/src/skill-set/codebase-design/SKILL.md +116 -0
- package/src/skill-set/codebase-design/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/LICENSE +21 -0
- package/src/skill-set/diagnosing-bugs/SKILL.md +140 -0
- package/src/skill-set/diagnosing-bugs/agents/openai.yaml +3 -0
- package/src/skill-set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/src/skill-set/domain-modeling/ADR-FORMAT.md +47 -0
- package/src/skill-set/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/src/skill-set/domain-modeling/LICENSE +21 -0
- package/src/skill-set/domain-modeling/SKILL.md +76 -0
- package/src/skill-set/domain-modeling/agents/openai.yaml +3 -0
- package/src/skill-set/grill-me/LICENSE +21 -0
- package/src/skill-set/grill-me/SKILL.md +8 -0
- package/src/skill-set/grill-me/agents/openai.yaml +4 -0
- package/src/skill-set/grill-with-docs/LICENSE +21 -0
- package/src/skill-set/grill-with-docs/SKILL.md +8 -0
- package/src/skill-set/grill-with-docs/agents/openai.yaml +4 -0
- package/src/skill-set/grilling/LICENSE +21 -0
- package/src/{starter-set → skill-set}/grilling/SKILL.md +1 -1
- package/src/skill-set/handoff/LICENSE +21 -0
- package/src/skill-set/handoff/SKILL.md +17 -0
- package/src/skill-set/handoff/agents/openai.yaml +4 -0
- package/src/skill-set/implement/LICENSE +21 -0
- package/src/skill-set/implement/SKILL.md +16 -0
- package/src/skill-set/implement/agents/openai.yaml +4 -0
- package/src/skill-set/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/src/skill-set/improve-codebase-architecture/LICENSE +21 -0
- package/src/skill-set/improve-codebase-architecture/SKILL.md +72 -0
- package/src/skill-set/improve-codebase-architecture/agents/openai.yaml +4 -0
- package/src/skill-set/prototype/LICENSE +21 -0
- package/src/skill-set/prototype/LOGIC.md +67 -0
- package/src/skill-set/prototype/SKILL.md +28 -0
- package/src/skill-set/prototype/UI.md +112 -0
- package/src/skill-set/prototype/agents/openai.yaml +3 -0
- package/src/skill-set/research/LICENSE +21 -0
- package/src/skill-set/research/SKILL.md +14 -0
- package/src/skill-set/research/agents/openai.yaml +3 -0
- package/src/skill-set/resolving-merge-conflicts/LICENSE +21 -0
- package/src/skill-set/resolving-merge-conflicts/SKILL.md +16 -0
- package/src/skill-set/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/LICENSE +21 -0
- package/src/skill-set/tdd/SKILL.md +40 -0
- package/src/skill-set/tdd/agents/openai.yaml +3 -0
- package/src/skill-set/tdd/mocking.md +59 -0
- package/src/skill-set/tdd/tests.md +77 -0
- package/src/skill-set/teach/GLOSSARY-FORMAT.md +35 -0
- package/src/skill-set/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/src/skill-set/teach/LICENSE +21 -0
- package/src/skill-set/teach/MISSION-FORMAT.md +31 -0
- package/src/skill-set/teach/RESOURCES-FORMAT.md +32 -0
- package/src/skill-set/teach/SKILL.md +141 -0
- package/src/skill-set/teach/agents/openai.yaml +4 -0
- package/src/skill-set/to-questionnaire/LICENSE +21 -0
- package/src/skill-set/to-questionnaire/SKILL.md +55 -0
- package/src/skill-set/to-questionnaire/agents/openai.yaml +4 -0
- package/src/skill-set/to-spec/LICENSE +21 -0
- package/src/skill-set/to-spec/SKILL.md +76 -0
- package/src/skill-set/to-spec/agents/openai.yaml +4 -0
- package/src/skill-set/to-tickets/LICENSE +21 -0
- package/src/skill-set/to-tickets/SKILL.md +106 -0
- package/src/skill-set/to-tickets/agents/openai.yaml +4 -0
- package/src/skill-set/wait-what/LICENSE +21 -0
- package/src/skill-set/wait-what/SKILL.md +8 -0
- package/src/skill-set/wait-what/agents/openai.yaml +4 -0
- package/src/skill-set/wayfinder/LICENSE +21 -0
- package/src/skill-set/wayfinder/SKILL.md +129 -0
- package/src/skill-set/wayfinder/agents/openai.yaml +4 -0
- package/src/skill-set/writing-for-agents/LICENSE +21 -0
- package/src/skill-set/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/src/skill-set/writing-for-agents/SKILL.md +83 -0
- package/src/skill-set/writing-for-agents/agents/openai.yaml +3 -0
- package/src/skill-set.js +85 -0
- package/src/source.js +93 -54
- package/src/state.js +19 -1
- package/src/sync-library.js +7 -11
- package/src/sync-state.js +12 -3
- package/src/sync.js +142 -43
- package/src/tasks.js +92 -0
- package/src/telegram-view.js +217 -0
- package/src/telegram.js +204 -69
- package/src/turn-frames.js +69 -0
- package/src/worker.js +738 -189
- package/src/working-copy.js +1274 -0
- package/src/workspace.js +299 -88
- package/src/google/declared.js +0 -146
- package/src/google/id.js +0 -1
- package/src/instance/google.js +0 -1
- package/src/starter-set.js +0 -17
- /package/src/{starter-set/grilling → skill-set/ask-matt}/LICENSE +0 -0
- /package/src/{starter-set → skill-set}/grilling/agents/openai.yaml +0 -0
package/src/skill-set.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { cp, lstat, mkdir, readdir, realpath, rm, symlink } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { KitRefusal } from "./refusal.js";
|
|
6
|
+
import { readNote, writeNote, writeText } from "./state.js";
|
|
7
|
+
|
|
8
|
+
export const SKILL_SET_SOURCE = fileURLToPath(new URL("skill-set/", import.meta.url));
|
|
9
|
+
const KIT_VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
10
|
+
|
|
11
|
+
function recordPath(directory) {
|
|
12
|
+
return join(directory, "state", "skill-set.json");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function howWeWorkNotePath(directory) {
|
|
16
|
+
return join(directory, "state", "how-we-work");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function placed(home, name) {
|
|
20
|
+
return {
|
|
21
|
+
skill: join(home, ".agents", "skills", name),
|
|
22
|
+
link: join(home, ".claude", "skills", name),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function present(path) {
|
|
27
|
+
try {
|
|
28
|
+
await lstat(path);
|
|
29
|
+
return true;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.code === "ENOENT") return false;
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function removeRecorded(home, names) {
|
|
37
|
+
for (const name of names) {
|
|
38
|
+
const { skill, link } = placed(home, name);
|
|
39
|
+
await rm(link, { recursive: true, force: true });
|
|
40
|
+
await rm(skill, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function installed(directory, home, version) {
|
|
45
|
+
const names = [];
|
|
46
|
+
for (const name of (await readdir(SKILL_SET_SOURCE)).sort()) {
|
|
47
|
+
const { skill, link } = placed(home, name);
|
|
48
|
+
if (!(await present(skill)) && !(await present(link))) names.push(name);
|
|
49
|
+
}
|
|
50
|
+
await writeNote(recordPath(directory), { names });
|
|
51
|
+
await mkdir(join(home, ".agents", "skills"), { recursive: true });
|
|
52
|
+
await mkdir(join(home, ".claude", "skills"), { recursive: true });
|
|
53
|
+
const shared =
|
|
54
|
+
(await realpath(join(home, ".claude", "skills"))) === (await realpath(join(home, ".agents", "skills")));
|
|
55
|
+
for (const name of names) {
|
|
56
|
+
const { skill, link } = placed(home, name);
|
|
57
|
+
await cp(join(SKILL_SET_SOURCE, name), skill, { recursive: true });
|
|
58
|
+
if (!shared) await symlink(join("..", "..", ".agents", "skills", name), link);
|
|
59
|
+
}
|
|
60
|
+
await writeNote(recordPath(directory), { version, names });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function reconcileSkillSet({ directory, home, accepted, version = KIT_VERSION }) {
|
|
64
|
+
try {
|
|
65
|
+
const record = await readNote(recordPath(directory), null);
|
|
66
|
+
if (accepted && record?.version === version) return;
|
|
67
|
+
if (record !== null) await removeRecorded(home, record.names);
|
|
68
|
+
if (accepted) await installed(directory, home, version);
|
|
69
|
+
else await rm(recordPath(directory), { force: true });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof KitRefusal) throw error;
|
|
72
|
+
throw new KitRefusal("kit_skill_set_unavailable", `${error.path ?? home} (${error.code ?? "unknown"})`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function howWeWorkOwed(directory) {
|
|
77
|
+
return (
|
|
78
|
+
(await readNote(recordPath(directory), null)) !== null &&
|
|
79
|
+
!(await present(howWeWorkNotePath(directory)))
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function noteHowWeWork(directory) {
|
|
84
|
+
await writeText(howWeWorkNotePath(directory), "");
|
|
85
|
+
}
|
package/src/source.js
CHANGED
|
@@ -12,8 +12,9 @@ import {
|
|
|
12
12
|
newCheckpoint,
|
|
13
13
|
stallOf,
|
|
14
14
|
} from "./delivery.js";
|
|
15
|
-
import { captureBatch, openSession } from "./house.js";
|
|
16
|
-
import {
|
|
15
|
+
import { captureBatch, maintainControl, openSession } from "./house.js";
|
|
16
|
+
import { GOOGLE_SOURCES } from "./google/oauth.js";
|
|
17
|
+
import { googlePage, recordAccountEmail } from "./google/source.js";
|
|
17
18
|
import { houseAnswered, houseAnsweredFailure, houseFailed, KitRefusal } from "./refusal.js";
|
|
18
19
|
import { recordAcceptedOriginals, sameReference, saveSourceOriginals } from "./source-originals.js";
|
|
19
20
|
import {
|
|
@@ -29,14 +30,15 @@ import {
|
|
|
29
30
|
writeCheckpoint,
|
|
30
31
|
} from "./state.js";
|
|
31
32
|
|
|
33
|
+
const GOOGLE = Object.freeze({ id: GOOGLE_SOURCES, preset: "google" });
|
|
34
|
+
|
|
32
35
|
async function acquire(context, service, checkpoint, limit) {
|
|
33
|
-
if (
|
|
36
|
+
if (service.google !== undefined) {
|
|
34
37
|
return googlePage(
|
|
35
38
|
{
|
|
36
39
|
directory: context.directory,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
service,
|
|
40
|
+
account: service.google.account,
|
|
41
|
+
service: service.google.service,
|
|
40
42
|
cursor: checkpoint.cursor,
|
|
41
43
|
limit,
|
|
42
44
|
backfillFloor:
|
|
@@ -169,6 +171,7 @@ async function deliverService(context, service) {
|
|
|
169
171
|
context.instance.preset === "custom"
|
|
170
172
|
? { ...context, environment: await resolveEnvironment(service.connector) }
|
|
171
173
|
: context;
|
|
174
|
+
if (service.google !== undefined) await recordAccountEmail(context, service.google);
|
|
172
175
|
await runDeliveryLoop(resolved, service, progress);
|
|
173
176
|
} catch (refusal) {
|
|
174
177
|
if (!(refusal instanceof KitRefusal) || refusal.code === "kit_command_cancelled") throw refusal;
|
|
@@ -197,25 +200,70 @@ async function deliverService(context, service) {
|
|
|
197
200
|
};
|
|
198
201
|
}
|
|
199
202
|
|
|
203
|
+
function initialBackfill(initial) {
|
|
204
|
+
if (initial.new === true) return { kind: "days", bound: 0 };
|
|
205
|
+
if (initial.days !== undefined) return { kind: "days", bound: initial.days };
|
|
206
|
+
if (initial.items !== undefined) return { kind: "count", bound: initial.items };
|
|
207
|
+
return { kind: "all" };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function captureEntry(context, entry) {
|
|
211
|
+
const delivered = await deliverService(
|
|
212
|
+
{ ...context, instance: GOOGLE },
|
|
213
|
+
{
|
|
214
|
+
service: entry.source,
|
|
215
|
+
sourceRef: entry.source,
|
|
216
|
+
rooms: entry.rooms,
|
|
217
|
+
backfill: initialBackfill(entry.initial),
|
|
218
|
+
google: { account: entry.account, service: entry.service },
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
return { ...delivered, service: entry.service };
|
|
222
|
+
}
|
|
223
|
+
|
|
200
224
|
export async function captureSource(options, dependencies = {}) {
|
|
201
225
|
const configuration = await readConfiguration(options.configuration);
|
|
202
226
|
const instance = sourceInstance(configuration, options.instance);
|
|
203
227
|
const services = declaredServices(instance, options.service ?? null);
|
|
204
|
-
const googleOAuthClient =
|
|
205
|
-
instance.preset === "google" ? dependencies.googleOAuthClient() : null;
|
|
206
228
|
const session = await openSession(options, dependencies);
|
|
207
|
-
|
|
208
|
-
instance,
|
|
229
|
+
return maintainControl(
|
|
209
230
|
session,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
231
|
+
async (signal) => {
|
|
232
|
+
const context = {
|
|
233
|
+
instance,
|
|
234
|
+
session,
|
|
235
|
+
directory: options.directory,
|
|
236
|
+
signal,
|
|
237
|
+
now: session.now,
|
|
238
|
+
dependencies,
|
|
239
|
+
};
|
|
240
|
+
const delivered = [];
|
|
241
|
+
for (const service of services) delivered.push(await deliverService(context, service));
|
|
242
|
+
return { instance: instance.id, services: delivered };
|
|
243
|
+
},
|
|
244
|
+
options.signal,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function localServiceState(directory, instance, service) {
|
|
249
|
+
return {
|
|
250
|
+
health: (await readHealth(directory, instance, service)) ?? {
|
|
251
|
+
state: "pending",
|
|
252
|
+
category: null,
|
|
253
|
+
observed_at: null,
|
|
254
|
+
},
|
|
255
|
+
checkpoint: await readCheckpoint(directory, instance, service),
|
|
256
|
+
gaps: await readCoverageGaps(directory, instance, service),
|
|
257
|
+
dropped: await readDroppedItems(directory, instance, service),
|
|
258
|
+
originals: (await readSourceOriginals(directory, instance, service)).map((reference) => ({
|
|
259
|
+
room: reference.room,
|
|
260
|
+
record: reference.record,
|
|
261
|
+
position: reference.position,
|
|
262
|
+
native_object_id: reference.native_object_id,
|
|
263
|
+
save: reference.save,
|
|
264
|
+
error: reference.error,
|
|
265
|
+
})),
|
|
215
266
|
};
|
|
216
|
-
const delivered = [];
|
|
217
|
-
for (const service of services) delivered.push(await deliverService(context, service));
|
|
218
|
-
return { instance: instance.id, services: delivered };
|
|
219
267
|
}
|
|
220
268
|
|
|
221
269
|
export async function sourceStatus(options) {
|
|
@@ -228,24 +276,7 @@ export async function sourceStatus(options) {
|
|
|
228
276
|
service: service.service,
|
|
229
277
|
source_ref: service.sourceRef,
|
|
230
278
|
rooms: service.rooms,
|
|
231
|
-
|
|
232
|
-
state: "pending",
|
|
233
|
-
category: null,
|
|
234
|
-
observed_at: null,
|
|
235
|
-
},
|
|
236
|
-
checkpoint: await readCheckpoint(options.directory, instance.id, service.service),
|
|
237
|
-
gaps: await readCoverageGaps(options.directory, instance.id, service.service),
|
|
238
|
-
dropped: await readDroppedItems(options.directory, instance.id, service.service),
|
|
239
|
-
originals: (await readSourceOriginals(options.directory, instance.id, service.service)).map(
|
|
240
|
-
(reference) => ({
|
|
241
|
-
room: reference.room,
|
|
242
|
-
record: reference.record,
|
|
243
|
-
position: reference.position,
|
|
244
|
-
native_object_id: reference.native_object_id,
|
|
245
|
-
save: reference.save,
|
|
246
|
-
error: reference.error,
|
|
247
|
-
}),
|
|
248
|
-
),
|
|
279
|
+
...(await localServiceState(options.directory, instance.id, service.service)),
|
|
249
280
|
});
|
|
250
281
|
}
|
|
251
282
|
return { instance: instance.id, preset: instance.preset, services: reported };
|
|
@@ -255,32 +286,40 @@ export async function retrySourceOriginals(options, dependencies = {}) {
|
|
|
255
286
|
const configuration = await readConfiguration(options.configuration);
|
|
256
287
|
const instance = sourceInstance(configuration, options.instance);
|
|
257
288
|
const services = declaredServices(instance, options.service ?? null);
|
|
258
|
-
const context = { instance, directory: options.directory, signal: options.signal };
|
|
259
289
|
const outstanding = [];
|
|
260
290
|
for (const service of services) {
|
|
261
291
|
outstanding.push((await readSourceOriginals(options.directory, instance.id, service.service)).length);
|
|
262
292
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
293
|
+
const retry = async (context) => {
|
|
294
|
+
const retried = [];
|
|
295
|
+
for (const [position, service] of services.entries()) {
|
|
296
|
+
retried.push({
|
|
297
|
+
service: service.service,
|
|
298
|
+
originals:
|
|
299
|
+
outstanding[position] === 0
|
|
300
|
+
? []
|
|
301
|
+
: await saveSourceOriginals(context, serviceBinding(context, service)),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return { instance: instance.id, services: retried };
|
|
305
|
+
};
|
|
306
|
+
const context = { instance, directory: options.directory, signal: options.signal };
|
|
307
|
+
if (outstanding.every((count) => count === 0)) return retry(context);
|
|
308
|
+
const session = await openSession(options, dependencies);
|
|
309
|
+
return maintainControl(session, (signal) => retry({ ...context, session, signal }), options.signal);
|
|
275
310
|
}
|
|
276
311
|
|
|
277
312
|
export async function resetSource(options, dependencies = {}) {
|
|
278
313
|
const configuration = await readConfiguration(options.configuration);
|
|
279
314
|
const instance = sourceInstance(configuration, options.instance);
|
|
280
315
|
declaredServices(instance, options.service);
|
|
281
|
-
await openSession(options, dependencies);
|
|
282
|
-
return
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
316
|
+
const session = await openSession(options, dependencies);
|
|
317
|
+
return maintainControl(
|
|
318
|
+
session,
|
|
319
|
+
async () => ({
|
|
320
|
+
instance: instance.id,
|
|
321
|
+
services: await resetCheckpoints(options.directory, instance.id, options.service),
|
|
322
|
+
}),
|
|
323
|
+
options.signal,
|
|
324
|
+
);
|
|
286
325
|
}
|
package/src/state.js
CHANGED
|
@@ -9,10 +9,28 @@ function segment(name) {
|
|
|
9
9
|
return name;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
function notePath(directory, instance, service, kind) {
|
|
12
|
+
export function notePath(directory, instance, service, kind) {
|
|
13
13
|
return join(directory, "state", segment(instance), kind, `${segment(service)}.json`);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export async function noteNames(directory, instance, kind) {
|
|
17
|
+
let present;
|
|
18
|
+
try {
|
|
19
|
+
present = await readdir(join(directory, "state", segment(instance), kind));
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error.code === "ENOENT") return [];
|
|
22
|
+
throw new KitRefusal("kit_state_unreadable", kind);
|
|
23
|
+
}
|
|
24
|
+
return present
|
|
25
|
+
.filter((name) => name.endsWith(".json"))
|
|
26
|
+
.map((name) => name.slice(0, -".json".length))
|
|
27
|
+
.sort();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function removeNotes(directory, instance, service, kinds) {
|
|
31
|
+
for (const kind of kinds) await rm(notePath(directory, instance, service, kind), { force: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
16
34
|
function repositoryPath(directory, key) {
|
|
17
35
|
if (!/^[a-f0-9]{64}$/.test(key)) throw new KitRefusal("kit_state_name_invalid", key);
|
|
18
36
|
return join(directory, "state", "repositories", `${key}.json`);
|
package/src/sync-library.js
CHANGED
|
@@ -100,13 +100,12 @@ function recordedMap(authority) {
|
|
|
100
100
|
return new Map(authority.files.map((file) => [file.path, file]));
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function structuredOrReplace(root, relative, recorded, content) {
|
|
103
|
+
export function structuredOrReplace(root, relative, recorded, content) {
|
|
104
104
|
const path = housePath(root, relative);
|
|
105
|
+
const whole = [{ op: "replace", path, base: recorded.revision, content }];
|
|
105
106
|
const before = parse(recorded.content, relative);
|
|
106
107
|
const after = parse(content, relative);
|
|
107
|
-
if (!before.ok || !after.ok)
|
|
108
|
-
return [{ op: "replace", path, base: recorded.revision, content }];
|
|
109
|
-
}
|
|
108
|
+
if (!before.ok || !after.ok) return whole;
|
|
110
109
|
const ops = [];
|
|
111
110
|
if (before.document.preamble !== after.document.preamble) {
|
|
112
111
|
ops.push({
|
|
@@ -124,9 +123,7 @@ function structuredOrReplace(root, relative, recorded, content) {
|
|
|
124
123
|
if (JSON.stringify(before.document.fields[name]) === JSON.stringify(after.document.fields[name])) {
|
|
125
124
|
continue;
|
|
126
125
|
}
|
|
127
|
-
if (after.document.fields[name] === undefined)
|
|
128
|
-
return [{ op: "replace", path, base: recorded.revision, content }];
|
|
129
|
-
}
|
|
126
|
+
if (after.document.fields[name] === undefined) return whole;
|
|
130
127
|
const change = { op: "set_field", path, field: name, value: after.document.fields[name] };
|
|
131
128
|
if (before.document.fields[name] !== undefined) change.base = before.document.fields[name];
|
|
132
129
|
ops.push(change);
|
|
@@ -139,12 +136,11 @@ function structuredOrReplace(root, relative, recorded, content) {
|
|
|
139
136
|
const prior = before.document.sections[name];
|
|
140
137
|
const next = after.document.sections[name];
|
|
141
138
|
if (prior === next) continue;
|
|
142
|
-
if (prior === undefined || next === undefined)
|
|
143
|
-
return [{ op: "replace", path, base: recorded.revision, content }];
|
|
144
|
-
}
|
|
139
|
+
if (prior === undefined || next === undefined) return whole;
|
|
145
140
|
ops.push({ op: "replace_section", path, section: name, base: prior, content: next });
|
|
146
141
|
}
|
|
147
|
-
|
|
142
|
+
if (ops.length === 0) return whole;
|
|
143
|
+
return applyChanges({ [relative]: recorded.content }, ops, root)[relative] === content ? ops : whole;
|
|
148
144
|
}
|
|
149
145
|
|
|
150
146
|
export function deriveChanges(state, authority, localFiles) {
|
package/src/sync-state.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { mkdir, readFile, readdir, realpath, rm } from "node:fs/promises";
|
|
3
|
-
import { join, relative, resolve } from "node:path";
|
|
2
|
+
import { mkdir, readFile, readdir, realpath, rm, rmdir } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
4
|
import { EXPLICIT_SYNC_VERSION } from "./contract.js";
|
|
5
5
|
import { KitRefusal } from "./refusal.js";
|
|
6
6
|
import { readNote, writeNote, writeText } from "./state.js";
|
|
@@ -85,12 +85,21 @@ export async function writeReplicaFile(path, content, signal) {
|
|
|
85
85
|
return writeText(path, content, signal, 0o644);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
export async function removeReplicaFile(
|
|
88
|
+
export async function removeReplicaFile(replica, rootPath, file) {
|
|
89
|
+
const root = locate(replica, rootPath);
|
|
90
|
+
const path = locate(replica, rootPath, file);
|
|
89
91
|
try {
|
|
90
92
|
await rm(path);
|
|
91
93
|
} catch (error) {
|
|
92
94
|
if (error.code !== "ENOENT") throw new KitRefusal("kit_state_unreadable", path);
|
|
93
95
|
}
|
|
96
|
+
for (let directory = dirname(path); directory.startsWith(`${root}/`); directory = dirname(directory)) {
|
|
97
|
+
try {
|
|
98
|
+
await rmdir(directory);
|
|
99
|
+
} catch (error) {
|
|
100
|
+
if (error.code !== "ENOENT") return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
94
103
|
}
|
|
95
104
|
|
|
96
105
|
export async function listFiles(root) {
|