@agentshouse/kit 0.2.1-cbdf76b9a90a3 → 0.2.1-cbff58027f078
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 +28 -28
- package/dist/acp.js +58 -7
- package/dist/afk.js +17 -0
- package/dist/agents.js +55 -32
- package/dist/api.js +1 -1
- package/dist/authority-main.js +2 -2
- package/dist/bridge.js +1 -1
- package/dist/building.js +50 -0
- package/dist/calls.js +71 -0
- package/dist/chunks.js +49 -0
- package/dist/clis.js +45 -40
- package/dist/{stream.js → connection.js} +28 -46
- package/dist/conversations.js +330 -125
- package/dist/copies.js +286 -149
- package/dist/end.js +11 -5
- package/dist/events.js +66 -8
- package/dist/files.js +139 -37
- package/dist/git.js +47 -13
- package/dist/home.js +0 -7
- package/dist/house-cli.js +95 -87
- package/dist/house-main.js +3 -3
- package/dist/instructions.js +29 -9
- package/dist/kit-cli.js +4 -4
- package/dist/kit-main.js +3 -3
- package/dist/login.js +4 -4
- package/dist/mcp.js +6 -2
- package/dist/parts.js +10 -0
- package/dist/placement.js +10 -18
- package/dist/relay.js +21 -0
- package/dist/resident.js +49 -38
- package/dist/scope.js +12 -4
- package/dist/scripts.js +255 -0
- package/dist/service.js +1 -1
- package/dist/sign-in.js +22 -21
- package/dist/silence.js +28 -0
- package/dist/skills.js +42 -97
- package/dist/transcription.js +83 -43
- package/dist/update.js +113 -124
- package/dist/wire.js +23 -5
- package/package.json +20 -21
- package/dist/bootstrap.json +0 -24
- package/dist/clis-main.js +0 -23
- package/dist/inputs.js +0 -63
- package/how-we-work.md +0 -33
- package/skills/ask-matt/LICENSE +0 -21
- package/skills/ask-matt/PHASE-BOUNDARIES.md +0 -55
- package/skills/ask-matt/SKILL.md +0 -97
- package/skills/ask-matt/agents/openai.yaml +0 -5
- package/skills/code-review/LICENSE +0 -21
- package/skills/code-review/SKILL.md +0 -89
- package/skills/code-review/agents/openai.yaml +0 -3
- package/skills/codebase-design/DEEPENING.md +0 -37
- package/skills/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/skills/codebase-design/LICENSE +0 -21
- package/skills/codebase-design/SKILL.md +0 -114
- package/skills/codebase-design/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/LICENSE +0 -21
- package/skills/diagnosing-bugs/SKILL.md +0 -138
- package/skills/diagnosing-bugs/agents/openai.yaml +0 -3
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +0 -44
- package/skills/domain-modeling/ADR-FORMAT.md +0 -47
- package/skills/domain-modeling/GLOSSARY-FORMAT.md +0 -60
- package/skills/domain-modeling/LICENSE +0 -21
- package/skills/domain-modeling/SKILL.md +0 -74
- package/skills/domain-modeling/agents/openai.yaml +0 -3
- package/skills/grill-me/LICENSE +0 -21
- package/skills/grill-me/SKILL.md +0 -7
- package/skills/grill-me/agents/openai.yaml +0 -5
- package/skills/grill-with-docs/LICENSE +0 -21
- package/skills/grill-with-docs/SKILL.md +0 -7
- package/skills/grill-with-docs/agents/openai.yaml +0 -5
- package/skills/grilling/LICENSE +0 -21
- package/skills/grilling/SKILL.md +0 -30
- package/skills/grilling/agents/openai.yaml +0 -3
- package/skills/handoff/LICENSE +0 -21
- package/skills/handoff/SKILL.md +0 -16
- package/skills/handoff/agents/openai.yaml +0 -5
- package/skills/implement/LICENSE +0 -21
- package/skills/implement/SKILL.md +0 -17
- package/skills/implement/agents/openai.yaml +0 -5
- package/skills/implement-spec/LICENSE +0 -21
- package/skills/implement-spec/SKILL.md +0 -40
- package/skills/implement-spec/agents/openai.yaml +0 -5
- package/skills/improve-codebase-architecture/HTML-REPORT.md +0 -123
- package/skills/improve-codebase-architecture/LICENSE +0 -21
- package/skills/improve-codebase-architecture/SKILL.md +0 -71
- package/skills/improve-codebase-architecture/agents/openai.yaml +0 -5
- package/skills/pr/CREDITS.md +0 -3
- package/skills/pr/LICENSE +0 -21
- package/skills/pr/SKILL.md +0 -170
- package/skills/pr/agents/openai.yaml +0 -3
- package/skills/prototype/LICENSE +0 -21
- package/skills/prototype/LOGIC.md +0 -67
- package/skills/prototype/SKILL.md +0 -26
- package/skills/prototype/UI.md +0 -112
- package/skills/prototype/agents/openai.yaml +0 -3
- package/skills/research/LICENSE +0 -21
- package/skills/research/SKILL.md +0 -12
- package/skills/research/agents/openai.yaml +0 -3
- package/skills/retro/LICENSE +0 -21
- package/skills/retro/SKILL.md +0 -44
- package/skills/retro/agents/openai.yaml +0 -5
- package/skills/tdd/LICENSE +0 -21
- package/skills/tdd/SKILL.md +0 -38
- package/skills/tdd/agents/openai.yaml +0 -3
- package/skills/tdd/mocking.md +0 -59
- package/skills/tdd/tests.md +0 -77
- package/skills/teach/GLOSSARY-FORMAT.md +0 -35
- package/skills/teach/LEARNING-RECORD-FORMAT.md +0 -46
- package/skills/teach/LICENSE +0 -21
- package/skills/teach/MISSION-FORMAT.md +0 -31
- package/skills/teach/RESOURCES-FORMAT.md +0 -32
- package/skills/teach/SKILL.md +0 -140
- package/skills/teach/agents/openai.yaml +0 -5
- package/skills/to-questionnaire/LICENSE +0 -21
- package/skills/to-questionnaire/SKILL.md +0 -54
- package/skills/to-questionnaire/agents/openai.yaml +0 -5
- package/skills/to-spec/LICENSE +0 -21
- package/skills/to-spec/SKILL.md +0 -75
- package/skills/to-spec/agents/openai.yaml +0 -5
- package/skills/to-tickets/LICENSE +0 -21
- package/skills/to-tickets/SKILL.md +0 -105
- package/skills/to-tickets/agents/openai.yaml +0 -5
- package/skills/triage/AGENT-BRIEF.md +0 -207
- package/skills/triage/LICENSE +0 -21
- package/skills/triage/OUT-OF-SCOPE.md +0 -105
- package/skills/triage/SKILL.md +0 -112
- package/skills/triage/agents/openai.yaml +0 -5
- package/skills/wait-what/LICENSE +0 -21
- package/skills/wait-what/SKILL.md +0 -7
- package/skills/wait-what/agents/openai.yaml +0 -5
- package/skills/wayfinder/LICENSE +0 -21
- package/skills/wayfinder/SKILL.md +0 -128
- package/skills/wayfinder/agents/openai.yaml +0 -5
- package/skills/wizard/LICENSE +0 -21
- package/skills/wizard/SKILL.md +0 -44
- package/skills/wizard/agents/openai.yaml +0 -3
- package/skills/wizard/template.sh +0 -223
- package/skills/writing-for-agents/LICENSE +0 -21
- package/skills/writing-for-agents/SKILL-MECHANICS.md +0 -22
- package/skills/writing-for-agents/SKILL.md +0 -81
- package/skills/writing-for-agents/agents/openai.yaml +0 -3
package/dist/copies.js
CHANGED
|
@@ -1,17 +1,31 @@
|
|
|
1
1
|
import { parse } from '@agentshouse/mdmodel';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { mkdir, readFile, readdir, realpath, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { finalRefusal, HouseRefusal, retrying } from './api.js';
|
|
6
|
+
import { chunkTexts, stagedChunks } from './chunks.js';
|
|
6
7
|
import { blobId, blobText, git, isText, HOUSE_REF, importCommits, localCopies, revision, run, succeeded, tree, } from './git.js';
|
|
8
|
+
import { paused } from './files.js';
|
|
7
9
|
import { kitHome } from './home.js';
|
|
8
10
|
import { operationId } from './operation.js';
|
|
9
11
|
import { refusalOf } from './refusals.js';
|
|
10
12
|
import { changed } from './translate.js';
|
|
11
|
-
import { relayed } from './relay.js';
|
|
12
13
|
const BRANCH = 'main';
|
|
13
14
|
const PROVENANCE = '_provenance';
|
|
14
|
-
const IDENTITY = { GIT_COMMITTER_NAME: '
|
|
15
|
+
const IDENTITY = { GIT_COMMITTER_NAME: 'Kit', GIT_COMMITTER_EMAIL: 'house-kit@localhost' };
|
|
16
|
+
const ROOMS = 'rooms/';
|
|
17
|
+
const ENUMERATE = '/kit/door/enumerate';
|
|
18
|
+
const NOT_READY = 'not ready yet; Kit copies it once House has built it';
|
|
19
|
+
const STAGED_CHUNKS = 'house-chunks';
|
|
20
|
+
// A Room answered not ready is asked again on this schedule: its chunk build usually catches up within seconds, so the first asks come soon and the last repeats until it does.
|
|
21
|
+
const ASKED_AGAIN_MS = [1000, 2000, 3000, 5000, 8000, 11_000, 15_000];
|
|
22
|
+
class NotReady extends Error {
|
|
23
|
+
rooms;
|
|
24
|
+
constructor(rooms) {
|
|
25
|
+
super(NOT_READY);
|
|
26
|
+
this.rooms = rooms;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
15
29
|
function causeOf(error) {
|
|
16
30
|
return error instanceof Error ? error.message : String(error);
|
|
17
31
|
}
|
|
@@ -72,6 +86,9 @@ function sidecar(path) {
|
|
|
72
86
|
function recordOf(key) {
|
|
73
87
|
return join(kitHome(), 'local-copies', `${key}.json`);
|
|
74
88
|
}
|
|
89
|
+
function stagingPrefix() {
|
|
90
|
+
return `.${createHash('sha256').update(kitHome()).digest('hex').slice(0, 16)}-`;
|
|
91
|
+
}
|
|
75
92
|
function rootOf(handle) {
|
|
76
93
|
return `/rooms/${handle}`;
|
|
77
94
|
}
|
|
@@ -99,32 +116,59 @@ async function removeFile(repository, path) {
|
|
|
99
116
|
return;
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
|
-
async function
|
|
119
|
+
async function readOnlyFiles(repository, held) {
|
|
103
120
|
const tracked = new Set((await git(repository, ['ls-files', '-z'])).split('\0'));
|
|
104
121
|
const holding = new Set(held);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
return {
|
|
123
|
+
remove: async (path) => {
|
|
124
|
+
if (!holding.has(path))
|
|
125
|
+
return;
|
|
126
|
+
if (!tracked.has(path))
|
|
127
|
+
await removeFile(repository, path);
|
|
128
|
+
holding.delete(path);
|
|
129
|
+
},
|
|
130
|
+
place: async (path, content) => {
|
|
131
|
+
if (tracked.has(path))
|
|
132
|
+
return;
|
|
133
|
+
if (!holding.has(path) && (await exists(join(repository, path))))
|
|
134
|
+
return;
|
|
135
|
+
await writeText(join(repository, path), content);
|
|
136
|
+
holding.add(path);
|
|
137
|
+
},
|
|
138
|
+
held: () => [...holding].sort(),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function readOnlyPlaced(repository, held, written, removed) {
|
|
142
|
+
const files = await readOnlyFiles(repository, held);
|
|
143
|
+
for (const path of removed)
|
|
144
|
+
await files.remove(path);
|
|
145
|
+
for (const [path, content] of written)
|
|
146
|
+
await files.place(path, content);
|
|
147
|
+
return files.held();
|
|
148
|
+
}
|
|
149
|
+
async function importedWhole(repository, scope, texts, commit, readOnly) {
|
|
150
|
+
const revisions = {};
|
|
151
|
+
async function* tracked() {
|
|
152
|
+
for await (const { path, content, revision: held, provenance } of texts) {
|
|
153
|
+
if (declaredWritable(path, scope)) {
|
|
154
|
+
revisions[path] = held;
|
|
155
|
+
yield [path, content];
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
await readOnly.place(path, content);
|
|
159
|
+
}
|
|
160
|
+
if (provenance !== undefined)
|
|
161
|
+
await readOnly.place(sidecar(path), provenance);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { house: await importCommits(repository, [{ ...commit, files: tracked() }]), revisions };
|
|
121
165
|
}
|
|
122
166
|
async function ruled(path, rules) {
|
|
123
167
|
if ((await readFile(path, 'utf8').catch(() => null)) !== rules)
|
|
124
168
|
await writeFile(path, rules);
|
|
125
169
|
}
|
|
126
170
|
async function ignoreRules(repository, writable, copy) {
|
|
127
|
-
const readOnly =
|
|
171
|
+
const readOnly = copy.read_only.filter((path) => !path.split('/').includes(PROVENANCE));
|
|
128
172
|
const protectedPath = (path) => copy.protected.some((prefix) => prefix !== '' && covers(path, prefix));
|
|
129
173
|
const whole = copy.protected.filter((prefix) => prefix !== '' && !writable.some((path) => covers(path, prefix)));
|
|
130
174
|
const house = [
|
|
@@ -133,7 +177,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
133
177
|
].map(pattern);
|
|
134
178
|
const granted = readOnly.filter((path) => !protectedPath(path)).map(pattern);
|
|
135
179
|
await ruled(join(repository, '.gitignore'), [
|
|
136
|
-
'#
|
|
180
|
+
'# Kit writes this file and .ignore. The paths below are read-only House',
|
|
137
181
|
'# content: Git does not track them, and House refuses a change to them even',
|
|
138
182
|
'# when this file is edited or a path is force-added.',
|
|
139
183
|
'/.gitignore',
|
|
@@ -148,7 +192,7 @@ async function ignoreRules(repository, writable, copy) {
|
|
|
148
192
|
'',
|
|
149
193
|
].join('\n'));
|
|
150
194
|
await ruled(join(repository, '.ignore'), [
|
|
151
|
-
'#
|
|
195
|
+
'# Kit writes this file so native search still reads the read-only House',
|
|
152
196
|
'# content that .gitignore keeps out of Git.',
|
|
153
197
|
`!${PROVENANCE}`,
|
|
154
198
|
...[...house, ...granted].map((rule) => `!${rule}`),
|
|
@@ -184,6 +228,7 @@ export class LocalCopies {
|
|
|
184
228
|
house;
|
|
185
229
|
copies = new Map();
|
|
186
230
|
queues = new Map();
|
|
231
|
+
waiting = new Set();
|
|
187
232
|
answering = Promise.resolve();
|
|
188
233
|
constructor(house) {
|
|
189
234
|
this.house = house;
|
|
@@ -215,10 +260,17 @@ export class LocalCopies {
|
|
|
215
260
|
this.copies.set(copy.key, copy);
|
|
216
261
|
}
|
|
217
262
|
}
|
|
263
|
+
async swept() {
|
|
264
|
+
const rooms = join(localCopies(), 'rooms');
|
|
265
|
+
for (const name of await readdir(rooms).catch(() => [])) {
|
|
266
|
+
if (name.startsWith(stagingPrefix()))
|
|
267
|
+
await rm(join(rooms, name), { recursive: true, force: true });
|
|
268
|
+
}
|
|
269
|
+
}
|
|
218
270
|
async mapping() {
|
|
219
271
|
await this.answering;
|
|
220
272
|
return [...this.copies.values()]
|
|
221
|
-
.filter((copy) => copy.
|
|
273
|
+
.filter((copy) => copy.unfollowed === undefined)
|
|
222
274
|
.map((copy) => `${rootOf(copy.room_handle)}: ${copy.repository}`)
|
|
223
275
|
.sort();
|
|
224
276
|
}
|
|
@@ -244,62 +296,99 @@ export class LocalCopies {
|
|
|
244
296
|
}
|
|
245
297
|
async fetched(refreshing, rooms) {
|
|
246
298
|
const { local_copy: answer } = await this.house.post('/kit/local-copy/selection', rooms === undefined ? {} : { rooms });
|
|
247
|
-
const synced = new Map(answer.rooms.map((room) => [
|
|
248
|
-
const named = rooms === undefined ? null : new Set(rooms.map((room) =>
|
|
299
|
+
const synced = new Map(answer.rooms.map((room) => [`${ROOMS}${room.room_ref}`, room.room_handle]));
|
|
300
|
+
const named = rooms === undefined ? null : new Set(rooms.map((room) => `${ROOMS}${room}`));
|
|
249
301
|
const held = () => [...this.copies.values()].filter((copy) => named === null || named.has(copy.key));
|
|
250
302
|
return this.holding([...new Set([...held().map((copy) => copy.key), ...synced.keys()])].sort(), async () => {
|
|
251
303
|
const said = [];
|
|
252
304
|
let refused = false;
|
|
305
|
+
const mark = refreshing ? 'said' : 'unsaid';
|
|
253
306
|
for (const copy of held()) {
|
|
254
|
-
|
|
255
|
-
if (returned && copy.erasing === undefined)
|
|
307
|
+
if (synced.has(copy.key) || copy.unfollowed === 'said' || copy.unfollowed === mark)
|
|
256
308
|
continue;
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
said.push(`${rootOf(copy.room_handle)}:
|
|
260
|
-
}
|
|
261
|
-
catch (error) {
|
|
262
|
-
refused = true;
|
|
263
|
-
said.push(`${rootOf(copy.room_handle)}: ${causeOf(error)}`);
|
|
264
|
-
}
|
|
309
|
+
await this.save({ ...copy, unfollowed: mark });
|
|
310
|
+
if (refreshing)
|
|
311
|
+
said.push(`${rootOf(copy.room_handle)}: no longer followed; kept as it is`);
|
|
265
312
|
}
|
|
266
313
|
const current = [];
|
|
314
|
+
const wanted = new Map();
|
|
267
315
|
for (const [key, handle] of synced) {
|
|
268
316
|
try {
|
|
269
317
|
const held = this.copies.get(key);
|
|
270
|
-
if (held?.erasing)
|
|
271
|
-
continue;
|
|
272
318
|
if (held === undefined || !(await exists(held.repository))) {
|
|
273
|
-
|
|
319
|
+
wanted.set(key, handle);
|
|
274
320
|
continue;
|
|
275
321
|
}
|
|
276
|
-
|
|
322
|
+
const { unfollowed, ...followed } = held;
|
|
323
|
+
current.push(unfollowed === undefined && held.room_handle === handle ? held : await this.save({ ...followed, room_handle: handle }));
|
|
277
324
|
}
|
|
278
325
|
catch (error) {
|
|
279
326
|
refused = true;
|
|
280
327
|
said.push(`${rootOf(handle)}: ${causeOf(error)}`);
|
|
281
328
|
}
|
|
282
329
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
330
|
+
const unready = [];
|
|
331
|
+
const unsettled = [];
|
|
332
|
+
if (wanted.size > 0) {
|
|
333
|
+
const { authorities } = await this.discovered([...wanted.values()]);
|
|
334
|
+
for (const [key, handle] of wanted) {
|
|
335
|
+
const authority = authorities.find((entry) => `${ROOMS}${entry.room_ref}` === key);
|
|
336
|
+
try {
|
|
337
|
+
if (authority === undefined)
|
|
338
|
+
throw new Error('House serves nothing of it to copy');
|
|
339
|
+
said.push(await this.copied({ key, handle, authority }));
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
if (error instanceof NotReady) {
|
|
343
|
+
unready.push(...error.rooms);
|
|
344
|
+
said.push(`${rootOf(handle)}: ${NOT_READY}`);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
refused = true;
|
|
348
|
+
unsettled.push(key.slice(ROOMS.length));
|
|
349
|
+
said.push(`${rootOf(handle)}: ${causeOf(error)}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const reading = refreshing ? current : current.filter((copy) => this.waiting.has(copy.room_ref));
|
|
355
|
+
if (reading.length > 0) {
|
|
356
|
+
const failed = await this.refreshed(reading, unready).then(() => null, houseCause);
|
|
357
|
+
for (const copy of reading) {
|
|
358
|
+
said.push(...(unready.includes(copy.room_ref) ? [`${rootOf(copy.room_handle)}: ${NOT_READY}`] : moved(copy, this.copies.get(copy.key))));
|
|
359
|
+
}
|
|
287
360
|
if (failed !== null) {
|
|
288
361
|
refused = true;
|
|
362
|
+
unsettled.push(...reading.map((copy) => copy.room_ref));
|
|
289
363
|
said.push(failed);
|
|
290
364
|
}
|
|
291
365
|
}
|
|
366
|
+
for (const room of rooms ?? [...this.waiting]) {
|
|
367
|
+
if (!unready.includes(room) && !unsettled.includes(room))
|
|
368
|
+
this.waiting.delete(room);
|
|
369
|
+
}
|
|
370
|
+
const due = unready.filter((room) => !this.waiting.has(room));
|
|
371
|
+
for (const room of due)
|
|
372
|
+
this.waiting.add(room);
|
|
373
|
+
if (due.length > 0)
|
|
374
|
+
void this.askedAgain(due);
|
|
292
375
|
return { refused, text: said.length === 0 ? 'Nothing new from House.' : said.join('\n') };
|
|
293
376
|
});
|
|
294
377
|
}
|
|
378
|
+
async askedAgain(rooms) {
|
|
379
|
+
let asking = rooms;
|
|
380
|
+
for (let step = 0; asking.length > 0; step++) {
|
|
381
|
+
await paused(ASKED_AGAIN_MS[Math.min(step, ASKED_AGAIN_MS.length - 1)]);
|
|
382
|
+
await this.selected().catch(logged);
|
|
383
|
+
asking = asking.filter((room) => this.waiting.has(room));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
295
386
|
async observed(report) {
|
|
296
387
|
const key = await this.keyAt(dirname(report.common));
|
|
297
388
|
if (key === undefined)
|
|
298
389
|
return;
|
|
299
390
|
await this.exclusive(key, async () => {
|
|
300
391
|
const copy = this.copies.get(key);
|
|
301
|
-
if (copy === undefined || copy.erasing)
|
|
302
|
-
return;
|
|
303
392
|
let moves = copy.moves;
|
|
304
393
|
for (const { from, to } of report.moves) {
|
|
305
394
|
const held = moves.find((move) => move.worktree === report.worktree && move.to === from);
|
|
@@ -355,138 +444,170 @@ export class LocalCopies {
|
|
|
355
444
|
this.copies.set(copy.key, copy);
|
|
356
445
|
return copy;
|
|
357
446
|
}
|
|
358
|
-
async
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
write: authority.operations.write,
|
|
380
|
-
protected: authority.protected_paths,
|
|
381
|
-
writable: new Map(),
|
|
382
|
-
revisions: {},
|
|
383
|
-
readOnly: new Map(),
|
|
384
|
-
};
|
|
385
|
-
for (const file of view.manifest.files) {
|
|
386
|
-
const content = contents.get(file.path);
|
|
387
|
-
if (file.writable) {
|
|
388
|
-
read.writable.set(file.path, content);
|
|
389
|
-
read.revisions[file.path] = file.revision;
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
read.readOnly.set(file.path, content);
|
|
447
|
+
async discovered(handles) {
|
|
448
|
+
const discover = (group) => this.house.post('/kit/door/discover', { prefixes: group.map(rootOf) });
|
|
449
|
+
const [first, ...rest] = handles;
|
|
450
|
+
const { bounds, authorities } = await discover([first]);
|
|
451
|
+
for (const group of grouped(rest, bounds.max_prefixes))
|
|
452
|
+
authorities.push(...(await discover(group)).authorities);
|
|
453
|
+
return { bounds, authorities };
|
|
454
|
+
}
|
|
455
|
+
async chunked(root, room, directory) {
|
|
456
|
+
await mkdir(directory, { recursive: true });
|
|
457
|
+
for (let renewed = false;; renewed = true) {
|
|
458
|
+
const { rooms } = await this.house.post('/kit/door/room-chunks/list', { prefixes: [root] });
|
|
459
|
+
const list = rooms.find((entry) => entry.room_ref === room);
|
|
460
|
+
if (list === undefined)
|
|
461
|
+
throw new Error('House serves nothing of it to copy');
|
|
462
|
+
if (!list.ready)
|
|
463
|
+
throw new NotReady([room]);
|
|
464
|
+
const held = new Set(await readdir(directory));
|
|
465
|
+
const failure = await stagedChunks(directory, list.chunks.filter(({ hash }) => !held.has(hash)));
|
|
466
|
+
if (failure === undefined) {
|
|
467
|
+
return { position: list.position, log_epoch: list.log_epoch, texts: chunkTexts(directory, list.chunks, list.models) };
|
|
393
468
|
}
|
|
394
|
-
if (
|
|
395
|
-
|
|
469
|
+
if (renewed && (await readdir(directory)).length === held.size)
|
|
470
|
+
throw failure;
|
|
396
471
|
}
|
|
397
|
-
return read;
|
|
398
472
|
}
|
|
399
|
-
async
|
|
400
|
-
const
|
|
401
|
-
if (read === null)
|
|
402
|
-
throw new Error('House serves nothing of it to copy');
|
|
403
|
-
const repository = join(localCopies(), 'rooms', read.anchor);
|
|
473
|
+
async materialized({ key, handle, authority }, whole) {
|
|
474
|
+
const repository = join(localCopies(), 'rooms', authority.room_ref);
|
|
404
475
|
if (await exists(repository))
|
|
405
|
-
throw new Error(`${repository} already exists and
|
|
406
|
-
const staging = join(dirname(repository),
|
|
476
|
+
throw new Error(`${repository} already exists and Kit leaves it as it is`);
|
|
477
|
+
const staging = join(dirname(repository), `${stagingPrefix()}${randomUUID()}`);
|
|
478
|
+
const scope = { write: authority.operations.write, protected: authority.protected_paths };
|
|
407
479
|
await mkdir(staging, { recursive: true });
|
|
408
|
-
let
|
|
409
|
-
let readOnly;
|
|
480
|
+
let copy;
|
|
410
481
|
try {
|
|
411
482
|
await git(staging, ['init', '-q', '-b', BRANCH]);
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
483
|
+
const read = await whole(join(staging, '.git', STAGED_CHUNKS));
|
|
484
|
+
const readOnly = await readOnlyFiles(staging, []);
|
|
485
|
+
const { house, revisions } = await importedWhole(staging, scope, read.texts, { message: `House ${rootOf(handle)}`, from: null }, readOnly);
|
|
415
486
|
await git(staging, ['update-ref', `refs/heads/${BRANCH}`, house]);
|
|
416
487
|
await git(staging, ['reset', '-q', '--hard']);
|
|
417
|
-
|
|
418
|
-
|
|
488
|
+
copy = {
|
|
489
|
+
key,
|
|
490
|
+
room_ref: authority.room_ref,
|
|
491
|
+
room_handle: handle,
|
|
492
|
+
repository,
|
|
493
|
+
house,
|
|
494
|
+
position: read.position,
|
|
495
|
+
log_epoch: read.log_epoch,
|
|
496
|
+
...scope,
|
|
497
|
+
read_only: readOnly.held(),
|
|
498
|
+
manifests: { [house]: { parent: null, files: revisions } },
|
|
499
|
+
moves: [],
|
|
500
|
+
};
|
|
501
|
+
await ignoreRules(staging, Object.keys(revisions), copy);
|
|
502
|
+
await rm(join(staging, '.git', STAGED_CHUNKS), { recursive: true, force: true });
|
|
419
503
|
await rename(staging, repository);
|
|
420
504
|
}
|
|
421
505
|
catch (error) {
|
|
422
506
|
await rm(staging, { recursive: true, force: true });
|
|
423
507
|
throw error;
|
|
424
508
|
}
|
|
425
|
-
|
|
426
|
-
key,
|
|
427
|
-
room_ref: read.anchor,
|
|
428
|
-
room_handle: handle,
|
|
429
|
-
repository,
|
|
430
|
-
house,
|
|
431
|
-
position: read.position,
|
|
432
|
-
log_epoch: read.log_epoch,
|
|
433
|
-
write: read.write,
|
|
434
|
-
protected: read.protected,
|
|
435
|
-
read_only: readOnly,
|
|
436
|
-
manifests: { [house]: { parent: null, files: read.revisions } },
|
|
437
|
-
moves: [],
|
|
438
|
-
});
|
|
439
|
-
return `${rootOf(handle)}: copied to ${repository}`;
|
|
509
|
+
return this.save(copy);
|
|
440
510
|
}
|
|
441
|
-
async
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
511
|
+
async copied(wanted) {
|
|
512
|
+
const { handle, authority } = wanted;
|
|
513
|
+
const root = rootOf(handle);
|
|
514
|
+
let answer = null;
|
|
515
|
+
try {
|
|
516
|
+
if (!this.waiting.has(authority.room_ref)) {
|
|
517
|
+
answer = await this.house.post(ENUMERATE, {
|
|
518
|
+
prefixes: [root],
|
|
519
|
+
positions: [
|
|
520
|
+
{ authority: authority.room_ref, position: '0', log_epoch: authority.log_epoch, page: { through: authority.position, read: 0 } },
|
|
521
|
+
],
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (error) {
|
|
526
|
+
if (expiredAuthority(error) !== authority.room_ref)
|
|
527
|
+
throw error;
|
|
528
|
+
}
|
|
529
|
+
const view = answer?.authorities.find((entry) => entry.authority === authority.room_ref);
|
|
530
|
+
if (answer !== null && view === undefined)
|
|
531
|
+
throw new Error('House serves nothing of it to copy');
|
|
532
|
+
if (view !== undefined && view.next === undefined) {
|
|
533
|
+
const texts = view.changes.filter((change) => !('removed' in change));
|
|
534
|
+
const copy = await this.materialized(wanted, async () => ({ position: view.position, log_epoch: view.log_epoch, texts }));
|
|
535
|
+
return `${root}: copied to ${copy.repository}`;
|
|
536
|
+
}
|
|
537
|
+
const copy = await this.materialized(wanted, (directory) => this.chunked(root, authority.room_ref, directory));
|
|
538
|
+
const [caught] = await this.caughtUp([copy], true);
|
|
539
|
+
await succeeded(copy.repository, ['merge', '--ff-only', '-q', caught.house]);
|
|
540
|
+
return `${root}: copied to ${copy.repository}`;
|
|
462
541
|
}
|
|
463
|
-
async
|
|
464
|
-
const
|
|
465
|
-
const
|
|
466
|
-
const {
|
|
467
|
-
|
|
468
|
-
|
|
542
|
+
async received(copy, authority) {
|
|
543
|
+
const root = rootOf(copy.room_handle);
|
|
544
|
+
const directory = join(copy.repository, '.git', STAGED_CHUNKS);
|
|
545
|
+
const scope = { write: authority.operations.write, protected: authority.protected_paths };
|
|
546
|
+
try {
|
|
547
|
+
const read = await this.chunked(root, copy.room_ref, directory);
|
|
548
|
+
const readOnly = await readOnlyFiles(copy.repository, copy.read_only);
|
|
549
|
+
for (const path of copy.read_only)
|
|
550
|
+
await readOnly.remove(path);
|
|
551
|
+
const commit = { message: `House ${root} at position ${read.position}`, from: copy.house, replaces: true };
|
|
552
|
+
const { house, revisions } = await importedWhole(copy.repository, scope, read.texts, commit, readOnly);
|
|
553
|
+
const next = {
|
|
554
|
+
...copy,
|
|
555
|
+
...scope,
|
|
556
|
+
house,
|
|
557
|
+
position: read.position,
|
|
558
|
+
log_epoch: read.log_epoch,
|
|
559
|
+
read_only: readOnly.held(),
|
|
560
|
+
manifests: { ...copy.manifests, [house]: { parent: null, files: revisions } },
|
|
561
|
+
};
|
|
562
|
+
await ignoreRules(copy.repository, Object.keys(revisions), next);
|
|
563
|
+
return next;
|
|
564
|
+
}
|
|
565
|
+
finally {
|
|
566
|
+
await rm(directory, { recursive: true, force: true });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
async reread(copy) {
|
|
570
|
+
const { authorities } = await this.discovered([copy.room_handle]);
|
|
571
|
+
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
572
|
+
if (authority === undefined)
|
|
573
|
+
throw new NotReady([copy.room_ref]);
|
|
574
|
+
return this.received(copy, authority);
|
|
575
|
+
}
|
|
576
|
+
async refreshed(copies, unready) {
|
|
577
|
+
const { bounds, authorities } = await this.discovered(copies.map((copy) => copy.room_handle));
|
|
578
|
+
const read = [];
|
|
469
579
|
const unchanged = [];
|
|
470
580
|
for (const copy of copies) {
|
|
471
581
|
const authority = authorities.find((entry) => entry.room_ref === copy.room_ref);
|
|
472
582
|
if (authority !== undefined &&
|
|
473
583
|
JSON.stringify([authority.operations.write, authority.protected_paths]) !== JSON.stringify([copy.write, copy.protected])) {
|
|
474
|
-
|
|
584
|
+
try {
|
|
585
|
+
read.push(await this.save(await this.received(copy, authority)));
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
if (!(error instanceof NotReady))
|
|
589
|
+
throw error;
|
|
590
|
+
unready.push(...error.rooms);
|
|
591
|
+
}
|
|
475
592
|
}
|
|
476
593
|
else {
|
|
477
594
|
unchanged.push(copy);
|
|
478
595
|
}
|
|
479
596
|
}
|
|
480
597
|
for (const group of grouped(unchanged, bounds.max_prefixes))
|
|
481
|
-
await this.caughtUp(group, false);
|
|
598
|
+
await this.caughtUp(group, false, unready);
|
|
599
|
+
for (const group of grouped(read, bounds.max_prefixes))
|
|
600
|
+
await this.caughtUp(group, true, unready);
|
|
482
601
|
}
|
|
483
|
-
async caughtUp(copies, whole) {
|
|
602
|
+
async caughtUp(copies, whole, unready) {
|
|
484
603
|
const caught = new Map(copies.map((copy) => [copy.key, copy]));
|
|
604
|
+
const read = new Set();
|
|
605
|
+
const waiting = unready ?? [];
|
|
485
606
|
let asking = [...copies];
|
|
486
607
|
while (asking.length > 0) {
|
|
487
608
|
let answer;
|
|
488
609
|
try {
|
|
489
|
-
answer = await this.house.post(
|
|
610
|
+
answer = await this.house.post(ENUMERATE, {
|
|
490
611
|
prefixes: asking.map((copy) => rootOf(copy.room_handle)),
|
|
491
612
|
positions: asking.map((copy) => ({ authority: copy.room_ref, position: copy.position, log_epoch: copy.log_epoch })),
|
|
492
613
|
});
|
|
@@ -495,8 +616,22 @@ export class LocalCopies {
|
|
|
495
616
|
const expired = asking.find((copy) => copy.room_ref === expiredAuthority(error));
|
|
496
617
|
if (expired === undefined)
|
|
497
618
|
throw error;
|
|
498
|
-
caught.set(expired.key, await this.save(await this.received(expired)));
|
|
499
619
|
asking = asking.filter((copy) => copy !== expired);
|
|
620
|
+
if (read.has(expired.key)) {
|
|
621
|
+
waiting.push(expired.room_ref);
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
read.add(expired.key);
|
|
625
|
+
try {
|
|
626
|
+
const next = await this.save(await this.reread(expired));
|
|
627
|
+
caught.set(expired.key, next);
|
|
628
|
+
asking.push(next);
|
|
629
|
+
}
|
|
630
|
+
catch (refused) {
|
|
631
|
+
if (!(refused instanceof NotReady))
|
|
632
|
+
throw refused;
|
|
633
|
+
waiting.push(expired.room_ref);
|
|
634
|
+
}
|
|
500
635
|
continue;
|
|
501
636
|
}
|
|
502
637
|
const advanced = [];
|
|
@@ -508,8 +643,10 @@ export class LocalCopies {
|
|
|
508
643
|
caught.set(copy.key, next);
|
|
509
644
|
advanced.push(next);
|
|
510
645
|
}
|
|
511
|
-
asking =
|
|
646
|
+
asking = advanced.filter((copy) => whole || read.has(copy.key));
|
|
512
647
|
}
|
|
648
|
+
if (unready === undefined && waiting.length > 0)
|
|
649
|
+
throw new NotReady(waiting);
|
|
513
650
|
return copies.map((copy) => caught.get(copy.key));
|
|
514
651
|
}
|
|
515
652
|
async incorporated(copy, changes, position, logEpoch) {
|
|
@@ -560,8 +697,6 @@ export class LocalCopies {
|
|
|
560
697
|
}
|
|
561
698
|
async pushed(key, worktree, selector, caller) {
|
|
562
699
|
let copy = this.copies.get(key);
|
|
563
|
-
if (copy === undefined || copy.erasing)
|
|
564
|
-
return outside(worktree);
|
|
565
700
|
if (copy.push?.state === 'submitted')
|
|
566
701
|
return this.settle(copy, caller);
|
|
567
702
|
const selected = await run(worktree, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${selector}^{commit}`]);
|
|
@@ -676,8 +811,10 @@ export class LocalCopies {
|
|
|
676
811
|
}
|
|
677
812
|
if (outcome.refused.code === 'expired')
|
|
678
813
|
return this.recovered(copy);
|
|
679
|
-
const rebased = rebaseFixes(outcome.refused);
|
|
680
814
|
let settled = await this.save(without(copy));
|
|
815
|
+
if (settled.unfollowed !== undefined)
|
|
816
|
+
return { refused: true, text: outcome.refused.text };
|
|
817
|
+
const rebased = rebaseFixes(outcome.refused);
|
|
681
818
|
try {
|
|
682
819
|
settled = (await this.caughtUp([settled], true))[0];
|
|
683
820
|
}
|
package/dist/end.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { KIT_RELEASE } from './clis.js';
|
|
2
2
|
import { forgetRejected } from './home.js';
|
|
3
3
|
import { installCommand } from './placement.js';
|
|
4
4
|
import { killDescendants } from './scope.js';
|
|
5
|
-
export const REPLACED = 'another Kit started for this Environment';
|
|
5
|
+
export const REPLACED = 'another Kit process started for this Environment';
|
|
6
6
|
function exit(reason, status) {
|
|
7
7
|
process.stderr.write(`kit: ${reason}\n`);
|
|
8
8
|
killDescendants();
|
|
@@ -14,12 +14,18 @@ export function end(reason) {
|
|
|
14
14
|
export function restart(reason) {
|
|
15
15
|
exit(reason, 1);
|
|
16
16
|
}
|
|
17
|
-
export function
|
|
17
|
+
export function updateLine(lead) {
|
|
18
18
|
const command = installCommand();
|
|
19
|
-
|
|
19
|
+
return command === null ? `${lead}; install it again.` : `${lead}. Update it: ${command}`;
|
|
20
|
+
}
|
|
21
|
+
export function updateRequired() {
|
|
22
|
+
end(updateLine('This Kit is too old for House'));
|
|
23
|
+
}
|
|
24
|
+
export function unknownMethod(kind) {
|
|
25
|
+
return updateLine(`This Kit does not know ${kind}`);
|
|
20
26
|
}
|
|
21
27
|
export function newerThanHouse(release, command) {
|
|
22
|
-
end(`Kit ${
|
|
28
|
+
end(`Kit ${KIT_RELEASE} is newer than this Building supports (Kit ${release}). Install Kit ${release}: ${command}`);
|
|
23
29
|
}
|
|
24
30
|
export async function signInAgain(rejected) {
|
|
25
31
|
await forgetRejected(rejected);
|