cloving 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -14
- package/dist/src/authority/commands.js +10 -2
- package/dist/src/authority/reconciliation.d.ts +2 -0
- package/dist/src/authority/reconciliation.js +10 -1
- package/dist/src/authority/transitions.d.ts +1 -1
- package/dist/src/authority/transitions.js +2 -0
- package/dist/src/broker/actor/authority-session.d.ts +2 -0
- package/dist/src/broker/actor/authority-session.js +3 -4
- package/dist/src/chats/analysis-batches.d.ts +24 -0
- package/dist/src/chats/analysis-batches.js +83 -0
- package/dist/src/chats/analysis-contract.d.ts +80 -0
- package/dist/src/chats/analysis-contract.js +16 -0
- package/dist/src/chats/analysis-data.d.ts +3 -0
- package/dist/src/chats/analysis-data.js +85 -0
- package/dist/src/chats/analysis-input.d.ts +12 -0
- package/dist/src/chats/analysis-input.js +83 -0
- package/dist/src/chats/analysis-links.d.ts +2 -0
- package/dist/src/chats/analysis-links.js +25 -0
- package/dist/src/chats/analysis-prompt.d.ts +10 -0
- package/dist/src/chats/analysis-prompt.js +16 -0
- package/dist/src/chats/analysis-proposal.d.ts +2 -0
- package/dist/src/chats/analysis-proposal.js +32 -0
- package/dist/src/chats/analysis-provenance.d.ts +4 -0
- package/dist/src/chats/analysis-provenance.js +99 -0
- package/dist/src/chats/analysis-shape.d.ts +3 -0
- package/dist/src/chats/analysis-shape.js +228 -0
- package/dist/src/chats/analysis.d.ts +3 -0
- package/dist/src/chats/analysis.js +42 -0
- package/dist/src/chats/corpus-input.d.ts +5 -0
- package/dist/src/chats/corpus-input.js +288 -0
- package/dist/src/chats/destination.js +3 -1
- package/dist/src/chats/import.d.ts +8 -6
- package/dist/src/chats/import.js +195 -23
- package/dist/src/chats/input-file.d.ts +3 -0
- package/dist/src/chats/input-file.js +32 -0
- package/dist/src/chats/normalize.d.ts +11 -2
- package/dist/src/chats/normalize.js +18 -2
- package/dist/src/chats/previous-import.d.ts +5 -0
- package/dist/src/chats/previous-import.js +34 -0
- package/dist/src/chats/reader.d.ts +7 -1
- package/dist/src/chats/reader.js +46 -15
- package/dist/src/chats/redact-import.d.ts +14 -0
- package/dist/src/chats/redact-import.js +141 -0
- package/dist/src/chats/redaction.d.ts +41 -0
- package/dist/src/chats/redaction.js +161 -0
- package/dist/src/chats/revision-input.d.ts +3 -0
- package/dist/src/chats/revision-input.js +180 -0
- package/dist/src/chats/revision.d.ts +48 -0
- package/dist/src/chats/revision.js +94 -0
- package/dist/src/chats/selection.d.ts +25 -0
- package/dist/src/chats/selection.js +109 -0
- package/dist/src/chats/source.d.ts +23 -0
- package/dist/src/chats/source.js +135 -0
- package/dist/src/chats/structure.d.ts +57 -0
- package/dist/src/chats/structure.js +216 -0
- package/dist/src/cli/arguments.d.ts +8 -0
- package/dist/src/cli/arguments.js +94 -13
- package/dist/src/cli/client.d.ts +1 -0
- package/dist/src/cli/client.js +38 -1
- package/dist/src/cli/commands.d.ts +3 -0
- package/dist/src/cli/commands.js +29 -11
- package/dist/src/cli/main.d.ts +2 -1
- package/dist/src/cli/main.js +61 -14
- package/dist/src/cli/render.d.ts +2 -0
- package/dist/src/cli/render.js +70 -2
- package/dist/src/cli/usage-render.d.ts +3 -0
- package/dist/src/cli/usage-render.js +28 -0
- package/dist/src/cli/usage.d.ts +3 -2
- package/dist/src/cli/usage.js +38 -6
- package/dist/src/cli/watch-actions.d.ts +10 -0
- package/dist/src/cli/watch-actions.js +58 -0
- package/dist/src/cli/watch-board-view.d.ts +32 -0
- package/dist/src/cli/watch-board-view.js +88 -15
- package/dist/src/cli/watch-board.js +6 -1
- package/dist/src/cli/watch-card-client.d.ts +1 -0
- package/dist/src/cli/watch-card-client.js +44 -0
- package/dist/src/cli/watch-card-forms.d.ts +2 -0
- package/dist/src/cli/watch-card-forms.js +33 -0
- package/dist/src/cli/watch-card-reply.d.ts +5 -0
- package/dist/src/cli/watch-card-reply.js +34 -0
- package/dist/src/cli/watch-card-view.d.ts +40 -0
- package/dist/src/cli/watch-card-view.js +86 -0
- package/dist/src/cli/watch-client.d.ts +1 -0
- package/dist/src/cli/watch-client.js +118 -0
- package/dist/src/cli/watch-model.js +31 -14
- package/dist/src/cli/watch-page.d.ts +1 -1
- package/dist/src/cli/watch-page.js +71 -85
- package/dist/src/cli/watch-pr-board.d.ts +3 -0
- package/dist/src/cli/watch-pr-board.js +26 -0
- package/dist/src/cli/watch-pr-command.d.ts +15 -0
- package/dist/src/cli/watch-pr-command.js +37 -0
- package/dist/src/cli/watch-pr-identity.d.ts +20 -0
- package/dist/src/cli/watch-pr-identity.js +62 -0
- package/dist/src/cli/watch-pr-observer.d.ts +19 -0
- package/dist/src/cli/watch-pr-observer.js +108 -0
- package/dist/src/cli/watch-pr-settings.d.ts +8 -0
- package/dist/src/cli/watch-pr-settings.js +47 -0
- package/dist/src/cli/watch-reads.js +4 -0
- package/dist/src/cli/watch-render.d.ts +16 -2
- package/dist/src/cli/watch-render.js +10 -3
- package/dist/src/cli/watch-run-input.d.ts +14 -0
- package/dist/src/cli/watch-run-input.js +49 -0
- package/dist/src/cli/watch-server.d.ts +3 -0
- package/dist/src/cli/watch-server.js +44 -6
- package/dist/src/cli/watch-start-view.d.ts +18 -0
- package/dist/src/cli/watch-start-view.js +77 -0
- package/dist/src/cli/watch-turn.d.ts +1 -0
- package/dist/src/cli/watch-turn.js +7 -2
- package/dist/src/cli/watch-usage.d.ts +6 -0
- package/dist/src/cli/watch-usage.js +65 -0
- package/dist/src/cli/watch-view.d.ts +15 -0
- package/dist/src/cli/watch-view.js +61 -17
- package/dist/src/cli.js +48 -6
- package/dist/src/daemon/analysis-launch.d.ts +5 -0
- package/dist/src/daemon/analysis-launch.js +81 -0
- package/dist/src/daemon/analysis-token.d.ts +6 -0
- package/dist/src/daemon/analysis-token.js +88 -0
- package/dist/src/daemon/analysis-worker.d.ts +22 -0
- package/dist/src/daemon/analysis-worker.js +219 -0
- package/dist/src/daemon/chats-files.d.ts +15 -0
- package/dist/src/daemon/chats-files.js +113 -0
- package/dist/src/daemon/chats-worker.d.ts +12 -0
- package/dist/src/daemon/chats-worker.js +200 -0
- package/dist/src/daemon/claude-turn.d.ts +10 -0
- package/dist/src/daemon/claude-turn.js +96 -15
- package/dist/src/daemon/claude-worker.d.ts +4 -1
- package/dist/src/daemon/claude-worker.js +99 -47
- package/dist/src/daemon/codex-stream.js +16 -0
- package/dist/src/daemon/daemon.d.ts +3 -0
- package/dist/src/daemon/daemon.js +16 -7
- package/dist/src/daemon/github-methods.js +4 -0
- package/dist/src/daemon/github-ticket-status.d.ts +3 -0
- package/dist/src/daemon/github-ticket-status.js +268 -0
- package/dist/src/daemon/issue-queue.d.ts +1 -0
- package/dist/src/daemon/issue-queue.js +115 -24
- package/dist/src/daemon/operation-journal.d.ts +1 -0
- package/dist/src/daemon/operation-journal.js +4 -2
- package/dist/src/daemon/retrying-turn.d.ts +9 -0
- package/dist/src/daemon/retrying-turn.js +185 -0
- package/dist/src/host/world-reconciliation.d.ts +2 -0
- package/dist/src/host/world-reconciliation.js +6 -0
- package/dist/src/host/world-runs.js +9 -1
- package/dist/src/service/run-handlers.js +15 -23
- package/dist/src/service/worker-totals.d.ts +3 -0
- package/dist/src/service/worker-totals.js +28 -0
- package/dist/src/ticket-status.d.ts +8 -0
- package/dist/src/ticket-status.js +45 -0
- package/dist/src/token-prices.d.ts +19 -0
- package/dist/src/token-prices.js +76 -0
- package/dist/src/worker-usage.d.ts +22 -0
- package/dist/src/worker-usage.js +44 -0
- package/dist/src/workflow-candidate.d.ts +29 -0
- package/dist/src/workflow-candidate.js +59 -0
- package/dist/src/workflow-contracts.d.ts +2 -0
- package/dist/src/workflow-contracts.js +27 -5
- package/dist/src/workflow-declaration.d.ts +1 -1
- package/dist/src/workflow-declaration.js +3 -1
- package/dist/src/workflows.d.ts +1 -0
- package/dist/src/workflows.js +1 -0
- package/dist/tools/self-host-processes.js +7 -2
- package/dist/tools/self-host-workflows.d.ts +1 -1
- package/dist/tools/self-host-workflows.js +2 -2
- package/dist/tools/self-host.js +6 -6
- package/dist/tools/self-update-host.d.ts +7 -0
- package/dist/tools/self-update-host.js +48 -1
- package/dist/tools/self-update.d.ts +1 -0
- package/dist/tools/self-update.js +85 -13
- package/dist/workflows/analyze-chats.d.ts +10 -0
- package/dist/workflows/analyze-chats.declaration.json +9 -0
- package/dist/workflows/analyze-chats.js +73 -0
- package/dist/workflows/develop-ticket.d.ts +1 -7
- package/dist/workflows/develop-ticket.js +42 -141
- package/dist/workflows/issue-labels.d.ts +3 -1
- package/dist/workflows/issue-labels.js +34 -0
- package/dist/workflows/ticket-prompts.d.ts +35 -0
- package/dist/workflows/ticket-prompts.js +133 -0
- package/package.json +5 -3
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
2
|
+
import { dirname, isAbsolute, join, relative, sep } from "node:path";
|
|
3
|
+
export function contained(root, path) {
|
|
4
|
+
const suffix = relative(root, path);
|
|
5
|
+
return (!isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${sep}`));
|
|
6
|
+
}
|
|
7
|
+
async function inspectComponents(path) {
|
|
8
|
+
const identities = [];
|
|
9
|
+
let ancestor = path;
|
|
10
|
+
const ancestors = [];
|
|
11
|
+
for (;;) {
|
|
12
|
+
ancestors.unshift(ancestor);
|
|
13
|
+
const parent = dirname(ancestor);
|
|
14
|
+
if (parent === ancestor)
|
|
15
|
+
break;
|
|
16
|
+
ancestor = parent;
|
|
17
|
+
}
|
|
18
|
+
let stats;
|
|
19
|
+
for (const component of ancestors) {
|
|
20
|
+
const next = await lstat(noFollowPath(component));
|
|
21
|
+
if (next.isSymbolicLink())
|
|
22
|
+
throw new Error("symbolic-link");
|
|
23
|
+
identities.unshift({ path: component, device: next.dev, inode: next.ino });
|
|
24
|
+
stats = next;
|
|
25
|
+
}
|
|
26
|
+
if (stats === undefined)
|
|
27
|
+
throw new Error("source-unreadable");
|
|
28
|
+
return { identities, stats };
|
|
29
|
+
}
|
|
30
|
+
export async function inspectScanSource(path, root) {
|
|
31
|
+
const { identities, stats } = await inspectComponents(path);
|
|
32
|
+
const canonicalPath = await realpath(path);
|
|
33
|
+
const canonicalRoot = path === root ? canonicalPath : root;
|
|
34
|
+
if (!contained(canonicalRoot, canonicalPath))
|
|
35
|
+
throw new Error("outside-root");
|
|
36
|
+
const canonicalStats = await lstat(noFollowPath(canonicalPath));
|
|
37
|
+
if (canonicalStats.dev !== stats.dev || canonicalStats.ino !== stats.ino)
|
|
38
|
+
throw new Error("source-changed");
|
|
39
|
+
const confirmation = await inspectComponents(path);
|
|
40
|
+
if (JSON.stringify(confirmation.identities) !== JSON.stringify(identities))
|
|
41
|
+
throw new Error("source-changed");
|
|
42
|
+
if (!stats.isDirectory() && !stats.isFile())
|
|
43
|
+
throw new Error("nonregular-file");
|
|
44
|
+
return {
|
|
45
|
+
path,
|
|
46
|
+
root: canonicalRoot,
|
|
47
|
+
identities,
|
|
48
|
+
size: stats.size,
|
|
49
|
+
modified: stats.mtimeMs,
|
|
50
|
+
changed: stats.ctimeMs,
|
|
51
|
+
kind: stats.isDirectory() ? "subtree" : "file",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function noFollowPath(path) {
|
|
55
|
+
if (process.platform !== "darwin")
|
|
56
|
+
throw new Error("platform-unsupported");
|
|
57
|
+
if (!isAbsolute(path))
|
|
58
|
+
throw new Error("source-unreadable");
|
|
59
|
+
return `/.nofollow${path}`;
|
|
60
|
+
}
|
|
61
|
+
async function metadata(path) {
|
|
62
|
+
try {
|
|
63
|
+
return await lstat(noFollowPath(path));
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT")
|
|
67
|
+
return;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function repositoryMarker(path) {
|
|
72
|
+
if (await metadata(join(path, ".git")))
|
|
73
|
+
return true;
|
|
74
|
+
const [head, objects, references] = await Promise.all([
|
|
75
|
+
metadata(join(path, "HEAD")),
|
|
76
|
+
metadata(join(path, "objects")),
|
|
77
|
+
metadata(join(path, "refs")),
|
|
78
|
+
]);
|
|
79
|
+
return (head?.isFile() === true &&
|
|
80
|
+
objects?.isDirectory() === true &&
|
|
81
|
+
references?.isDirectory() === true);
|
|
82
|
+
}
|
|
83
|
+
export async function refuseRepositoryRoot(path) {
|
|
84
|
+
let ancestor = path;
|
|
85
|
+
for (;;) {
|
|
86
|
+
if (await repositoryMarker(ancestor))
|
|
87
|
+
throw new Error("repository-root");
|
|
88
|
+
const parent = dirname(ancestor);
|
|
89
|
+
if (parent === ancestor)
|
|
90
|
+
return;
|
|
91
|
+
ancestor = parent;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function sourceFailure(error) {
|
|
95
|
+
if (error instanceof Error && "code" in error && error.code === "ELOOP")
|
|
96
|
+
return "symbolic-link";
|
|
97
|
+
const reason = error instanceof Error ? error.message : "";
|
|
98
|
+
return [
|
|
99
|
+
"symbolic-link",
|
|
100
|
+
"outside-root",
|
|
101
|
+
"nonregular-file",
|
|
102
|
+
"repository-root",
|
|
103
|
+
"source-changed",
|
|
104
|
+
"non-directory-root",
|
|
105
|
+
].includes(reason)
|
|
106
|
+
? reason
|
|
107
|
+
: "source-unreadable";
|
|
108
|
+
}
|
|
109
|
+
export async function verifyScanSource(expected) {
|
|
110
|
+
const current = await inspectScanSource(expected.path, expected.root);
|
|
111
|
+
if (JSON.stringify(current.identities) !== JSON.stringify(expected.identities))
|
|
112
|
+
throw new Error("source-changed");
|
|
113
|
+
if (expected.kind === "file" &&
|
|
114
|
+
(current.size !== expected.size ||
|
|
115
|
+
current.modified !== expected.modified ||
|
|
116
|
+
current.changed !== expected.changed))
|
|
117
|
+
throw new Error("source-changed");
|
|
118
|
+
}
|
|
119
|
+
export function verifyOpenSource(expected, stats) {
|
|
120
|
+
const identity = expected.identities[0];
|
|
121
|
+
if (!stats.isFile() ||
|
|
122
|
+
identity === undefined ||
|
|
123
|
+
stats.dev !== identity.device ||
|
|
124
|
+
stats.ino !== identity.inode ||
|
|
125
|
+
stats.size !== expected.size ||
|
|
126
|
+
stats.mtimeMs !== expected.modified ||
|
|
127
|
+
stats.ctimeMs !== expected.changed)
|
|
128
|
+
throw new Error("source-changed");
|
|
129
|
+
}
|
|
130
|
+
export function verifyScanAncestor(expected, current) {
|
|
131
|
+
const ancestors = current.identities.slice(-expected.identities.length);
|
|
132
|
+
if (expected.identities.some((identity, index) => identity.device !== ancestors[index]?.device ||
|
|
133
|
+
identity.inode !== ancestors[index].inode))
|
|
134
|
+
throw new Error("source-changed");
|
|
135
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { MessageRecord } from "./normalize.ts";
|
|
2
|
+
export interface OccurrenceReference {
|
|
3
|
+
readonly recordIndex: number;
|
|
4
|
+
readonly sourceId: string;
|
|
5
|
+
}
|
|
6
|
+
export interface Occurrence extends OccurrenceReference {
|
|
7
|
+
readonly uuid: string | null;
|
|
8
|
+
readonly sessionId: string | null;
|
|
9
|
+
readonly isSidechain: boolean | null;
|
|
10
|
+
readonly parentUuid?: string | null;
|
|
11
|
+
readonly copyOf?: OccurrenceReference;
|
|
12
|
+
}
|
|
13
|
+
interface ConflictGroup {
|
|
14
|
+
readonly uuid: string;
|
|
15
|
+
readonly variants: readonly (readonly OccurrenceReference[])[];
|
|
16
|
+
}
|
|
17
|
+
interface ParentLink {
|
|
18
|
+
readonly childUuid: string | null;
|
|
19
|
+
readonly parentUuid: string | null;
|
|
20
|
+
readonly occurrences: readonly OccurrenceReference[];
|
|
21
|
+
readonly status: "root" | "resolved" | "missing" | "ambiguous";
|
|
22
|
+
}
|
|
23
|
+
interface SessionNode {
|
|
24
|
+
readonly uuid: string | null;
|
|
25
|
+
readonly occurrences: readonly OccurrenceReference[];
|
|
26
|
+
}
|
|
27
|
+
interface SessionStructure {
|
|
28
|
+
readonly sessionId: string;
|
|
29
|
+
readonly nodes: readonly SessionNode[];
|
|
30
|
+
readonly links: readonly ParentLink[];
|
|
31
|
+
readonly forkPoints: readonly {
|
|
32
|
+
parentUuid: string;
|
|
33
|
+
childUuids: readonly string[];
|
|
34
|
+
}[];
|
|
35
|
+
readonly unknownParents: readonly OccurrenceReference[];
|
|
36
|
+
readonly missingParentTargets: readonly string[];
|
|
37
|
+
readonly cycles: readonly (readonly string[])[];
|
|
38
|
+
}
|
|
39
|
+
export interface StructureCounts {
|
|
40
|
+
readonly records: number;
|
|
41
|
+
readonly knownSessions: number;
|
|
42
|
+
readonly forks: number;
|
|
43
|
+
readonly copies: number;
|
|
44
|
+
readonly conflictGroups: number;
|
|
45
|
+
readonly unknownIdentities: number;
|
|
46
|
+
readonly missingParentTargets: number;
|
|
47
|
+
}
|
|
48
|
+
export interface ImportStructure {
|
|
49
|
+
readonly historyCompleteness: "unknown";
|
|
50
|
+
readonly counts: StructureCounts;
|
|
51
|
+
readonly occurrences: readonly Occurrence[];
|
|
52
|
+
readonly conflictGroups: readonly ConflictGroup[];
|
|
53
|
+
readonly sessions: readonly SessionStructure[];
|
|
54
|
+
readonly unknownSessions: readonly OccurrenceReference[];
|
|
55
|
+
}
|
|
56
|
+
export declare function structureImport(records: readonly MessageRecord[]): ImportStructure;
|
|
57
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
function reference(occurrence) {
|
|
2
|
+
return { recordIndex: occurrence.recordIndex, sourceId: occurrence.sourceId };
|
|
3
|
+
}
|
|
4
|
+
function rawJsonApi(value) {
|
|
5
|
+
return "isRawJSON" in value && typeof value.isRawJSON === "function";
|
|
6
|
+
}
|
|
7
|
+
function canonical(value) {
|
|
8
|
+
if (rawJsonApi(JSON) && JSON.isRawJSON(value))
|
|
9
|
+
return JSON.stringify(value);
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return `[${value.map(canonical).join(",")}]`;
|
|
12
|
+
if (value === null || typeof value !== "object")
|
|
13
|
+
return JSON.stringify(value);
|
|
14
|
+
return `{${Object.entries(value)
|
|
15
|
+
.sort(([left], [right]) => (left < right ? -1 : Number(left > right)))
|
|
16
|
+
.map(([key, member]) => `${JSON.stringify(key)}:${canonical(member)}`)
|
|
17
|
+
.join(",")}}`;
|
|
18
|
+
}
|
|
19
|
+
function occurrence(record, context) {
|
|
20
|
+
const { recordIndex, identities } = context;
|
|
21
|
+
const metadata = record.schemaVersion === 2
|
|
22
|
+
? {
|
|
23
|
+
uuid: record.uuid,
|
|
24
|
+
isSidechain: record.isSidechain,
|
|
25
|
+
...(Object.hasOwn(record, "parentUuid")
|
|
26
|
+
? { parentUuid: record.parentUuid }
|
|
27
|
+
: {}),
|
|
28
|
+
}
|
|
29
|
+
: { uuid: null, isSidechain: null };
|
|
30
|
+
const current = {
|
|
31
|
+
...reference({ recordIndex, sourceId: record.sourceId }),
|
|
32
|
+
sessionId: record.sessionId,
|
|
33
|
+
...metadata,
|
|
34
|
+
};
|
|
35
|
+
if (current.uuid === null)
|
|
36
|
+
return current;
|
|
37
|
+
const variants = identities.get(current.uuid) ?? new Map();
|
|
38
|
+
identities.set(current.uuid, variants);
|
|
39
|
+
const content = canonical([record.role, record.text, record.blocks]);
|
|
40
|
+
const matches = variants.get(content) ?? [];
|
|
41
|
+
variants.set(content, matches);
|
|
42
|
+
const first = matches[0];
|
|
43
|
+
matches.push(reference(current));
|
|
44
|
+
return first === undefined ? current : { ...current, copyOf: { ...first } };
|
|
45
|
+
}
|
|
46
|
+
function groupNodes(occurrences) {
|
|
47
|
+
const groups = new Map();
|
|
48
|
+
for (const item of occurrences) {
|
|
49
|
+
const key = item.uuid ?? item.recordIndex;
|
|
50
|
+
const group = groups.get(key) ?? [];
|
|
51
|
+
groups.set(key, group);
|
|
52
|
+
group.push(item);
|
|
53
|
+
}
|
|
54
|
+
return [...groups.values()];
|
|
55
|
+
}
|
|
56
|
+
function parentLinks(group, known) {
|
|
57
|
+
const parents = new Map();
|
|
58
|
+
for (const item of group) {
|
|
59
|
+
if (item.parentUuid === undefined)
|
|
60
|
+
continue;
|
|
61
|
+
const references = parents.get(item.parentUuid) ?? [];
|
|
62
|
+
parents.set(item.parentUuid, references);
|
|
63
|
+
references.push(reference(item));
|
|
64
|
+
}
|
|
65
|
+
const childUuid = group[0]?.uuid ?? null;
|
|
66
|
+
return [...parents].map(([parentUuid, occurrences]) => {
|
|
67
|
+
let status = "resolved";
|
|
68
|
+
if (parentUuid === null)
|
|
69
|
+
status = "root";
|
|
70
|
+
if (parentUuid !== null && !known.has(parentUuid))
|
|
71
|
+
status = "missing";
|
|
72
|
+
if (childUuid === null || parents.size > 1)
|
|
73
|
+
status = "ambiguous";
|
|
74
|
+
return { childUuid, parentUuid, occurrences, status };
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function forkPoints(links) {
|
|
78
|
+
const children = new Map();
|
|
79
|
+
for (const link of links) {
|
|
80
|
+
if (link.status === "ambiguous" ||
|
|
81
|
+
link.parentUuid === null ||
|
|
82
|
+
link.childUuid === null)
|
|
83
|
+
continue;
|
|
84
|
+
const ids = children.get(link.parentUuid) ?? new Set();
|
|
85
|
+
children.set(link.parentUuid, ids);
|
|
86
|
+
ids.add(link.childUuid);
|
|
87
|
+
}
|
|
88
|
+
return [...children]
|
|
89
|
+
.filter(([, ids]) => ids.size > 1)
|
|
90
|
+
.map(([parentUuid, ids]) => ({ parentUuid, childUuids: [...ids] }));
|
|
91
|
+
}
|
|
92
|
+
function finishFrom(graph, state) {
|
|
93
|
+
const { visited, order } = state;
|
|
94
|
+
const stack = [{ id: state.start, expanded: false }];
|
|
95
|
+
while (stack.length > 0) {
|
|
96
|
+
const item = stack.pop();
|
|
97
|
+
if (item === undefined)
|
|
98
|
+
break;
|
|
99
|
+
if (item.expanded) {
|
|
100
|
+
order.push(item.id);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (visited.has(item.id))
|
|
104
|
+
continue;
|
|
105
|
+
visited.add(item.id);
|
|
106
|
+
stack.push({ id: item.id, expanded: true });
|
|
107
|
+
for (const id of graph.get(item.id) ?? [])
|
|
108
|
+
stack.push({ id, expanded: false });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function finishOrder(graph) {
|
|
112
|
+
const visited = new Set();
|
|
113
|
+
const order = [];
|
|
114
|
+
for (const start of graph.keys())
|
|
115
|
+
finishFrom(graph, { start, visited, order });
|
|
116
|
+
return order;
|
|
117
|
+
}
|
|
118
|
+
function observedGraph(known, links) {
|
|
119
|
+
const graph = new Map([...known].map((id) => [id, []]));
|
|
120
|
+
const reverse = new Map([...known].map((id) => [id, []]));
|
|
121
|
+
for (const link of links) {
|
|
122
|
+
if (link.childUuid === null ||
|
|
123
|
+
link.parentUuid === null ||
|
|
124
|
+
!known.has(link.parentUuid))
|
|
125
|
+
continue;
|
|
126
|
+
graph.get(link.childUuid)?.push(link.parentUuid);
|
|
127
|
+
reverse.get(link.parentUuid)?.push(link.childUuid);
|
|
128
|
+
}
|
|
129
|
+
return { graph, reverse };
|
|
130
|
+
}
|
|
131
|
+
function componentFrom(graph, state) {
|
|
132
|
+
const component = [];
|
|
133
|
+
const stack = [state.start];
|
|
134
|
+
while (stack.length > 0) {
|
|
135
|
+
const id = stack.pop();
|
|
136
|
+
if (id === undefined || state.visited.has(id))
|
|
137
|
+
continue;
|
|
138
|
+
state.visited.add(id);
|
|
139
|
+
component.push(id);
|
|
140
|
+
for (const parent of graph.get(id) ?? [])
|
|
141
|
+
stack.push(parent);
|
|
142
|
+
}
|
|
143
|
+
return component;
|
|
144
|
+
}
|
|
145
|
+
function cycleGroups(known, links) {
|
|
146
|
+
const { graph, reverse } = observedGraph(known, links);
|
|
147
|
+
const rank = new Map([...known].map((id, index) => [id, index]));
|
|
148
|
+
const visited = new Set();
|
|
149
|
+
const cycles = [];
|
|
150
|
+
for (const start of finishOrder(graph).reverse()) {
|
|
151
|
+
if (visited.has(start))
|
|
152
|
+
continue;
|
|
153
|
+
const component = componentFrom(reverse, { start, visited });
|
|
154
|
+
if (component.length > 1 || graph.get(start)?.includes(start))
|
|
155
|
+
cycles.push(component.sort((left, right) => Number(rank.get(left)) - Number(rank.get(right))));
|
|
156
|
+
}
|
|
157
|
+
return cycles.sort((left, right) => Number(rank.get(String(left[0]))) - Number(rank.get(String(right[0]))));
|
|
158
|
+
}
|
|
159
|
+
function sessionStructure(sessionId, occurrences) {
|
|
160
|
+
const groups = groupNodes(occurrences);
|
|
161
|
+
const known = new Set(occurrences.flatMap((item) => (item.uuid === null ? [] : [item.uuid])));
|
|
162
|
+
const links = groups.flatMap((group) => parentLinks(group, known));
|
|
163
|
+
return {
|
|
164
|
+
sessionId,
|
|
165
|
+
nodes: groups.map((group) => ({
|
|
166
|
+
uuid: group[0]?.uuid ?? null,
|
|
167
|
+
occurrences: group.map(reference),
|
|
168
|
+
})),
|
|
169
|
+
links,
|
|
170
|
+
forkPoints: forkPoints(links),
|
|
171
|
+
unknownParents: occurrences
|
|
172
|
+
.filter((item) => !Object.hasOwn(item, "parentUuid"))
|
|
173
|
+
.map(reference),
|
|
174
|
+
missingParentTargets: [
|
|
175
|
+
...new Set(links.flatMap((link) => link.parentUuid !== null && !known.has(link.parentUuid)
|
|
176
|
+
? [link.parentUuid]
|
|
177
|
+
: [])),
|
|
178
|
+
],
|
|
179
|
+
cycles: cycleGroups(known, links),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export function structureImport(records) {
|
|
183
|
+
const identities = new Map();
|
|
184
|
+
const occurrences = records.map((record, index) => occurrence(record, { recordIndex: index, identities }));
|
|
185
|
+
const grouped = new Map();
|
|
186
|
+
for (const item of occurrences) {
|
|
187
|
+
if (item.sessionId === null || item.sessionId === "")
|
|
188
|
+
continue;
|
|
189
|
+
const group = grouped.get(item.sessionId) ?? [];
|
|
190
|
+
grouped.set(item.sessionId, group);
|
|
191
|
+
group.push(item);
|
|
192
|
+
}
|
|
193
|
+
const sessions = [...grouped].map(([id, group]) => sessionStructure(id, group));
|
|
194
|
+
const conflictGroups = [...identities]
|
|
195
|
+
.filter(([, variants]) => variants.size > 1)
|
|
196
|
+
.map(([uuid, variants]) => ({ uuid, variants: [...variants.values()] }));
|
|
197
|
+
return {
|
|
198
|
+
historyCompleteness: "unknown",
|
|
199
|
+
counts: {
|
|
200
|
+
records: records.length,
|
|
201
|
+
knownSessions: sessions.length,
|
|
202
|
+
forks: sessions.reduce((sum, session) => sum + session.forkPoints.length, 0),
|
|
203
|
+
copies: occurrences.filter((item) => item.copyOf !== undefined).length,
|
|
204
|
+
conflictGroups: conflictGroups.length,
|
|
205
|
+
unknownIdentities: occurrences.filter((item) => item.uuid === null)
|
|
206
|
+
.length,
|
|
207
|
+
missingParentTargets: sessions.reduce((sum, session) => sum + session.missingParentTargets.length, 0),
|
|
208
|
+
},
|
|
209
|
+
occurrences,
|
|
210
|
+
conflictGroups,
|
|
211
|
+
sessions,
|
|
212
|
+
unknownSessions: occurrences
|
|
213
|
+
.filter((item) => item.sessionId === null || item.sessionId === "")
|
|
214
|
+
.map(reference),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -5,11 +5,19 @@ export type Invocation = {
|
|
|
5
5
|
readonly kind: "version";
|
|
6
6
|
} | {
|
|
7
7
|
readonly kind: "validate-help";
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: "chats-redact-help";
|
|
10
|
+
} | {
|
|
11
|
+
readonly kind: "chats-redact";
|
|
12
|
+
readonly importId: string;
|
|
8
13
|
} | {
|
|
9
14
|
readonly kind: "chats-import-help";
|
|
10
15
|
} | {
|
|
11
16
|
readonly kind: "chats-import";
|
|
12
17
|
readonly paths: readonly string[];
|
|
18
|
+
readonly roots?: readonly string[];
|
|
19
|
+
readonly excludeProjects?: readonly string[];
|
|
20
|
+
readonly previousImportId?: string;
|
|
13
21
|
} | {
|
|
14
22
|
readonly kind: "validate";
|
|
15
23
|
readonly manifest: string;
|
|
@@ -50,6 +50,8 @@ function textValues(argumentsList) {
|
|
|
50
50
|
export function parseInvocation(given) {
|
|
51
51
|
if (given[0] === "chats" && given[1] === "import")
|
|
52
52
|
return chatsImport(given.slice(2));
|
|
53
|
+
if (given[0] === "chats" && given[1] === "redact")
|
|
54
|
+
return chatsRedact(given.slice(2));
|
|
53
55
|
const argumentsList = textValues(given);
|
|
54
56
|
const json = given.includes("--json");
|
|
55
57
|
const parsed = parsedArguments(argumentsList);
|
|
@@ -70,6 +72,17 @@ export function parseInvocation(given) {
|
|
|
70
72
|
return { kind: "usage-error", code: outcome, json };
|
|
71
73
|
return outcome;
|
|
72
74
|
}
|
|
75
|
+
function chatsRedact(argumentsList) {
|
|
76
|
+
if (argumentsList.length === 1 &&
|
|
77
|
+
["--help", "-h"].includes(String(argumentsList[0])))
|
|
78
|
+
return { kind: "chats-redact-help" };
|
|
79
|
+
const importId = argumentsList[0];
|
|
80
|
+
if (argumentsList.length !== 1 ||
|
|
81
|
+
importId === undefined ||
|
|
82
|
+
!/^[a-f\d]{8}-[a-f\d]{4}-4[a-f\d]{3}-[89ab][a-f\d]{3}-[a-f\d]{12}$/.test(importId))
|
|
83
|
+
return { kind: "usage-error", code: "invalid-import-id", json: false };
|
|
84
|
+
return { kind: "chats-redact", importId };
|
|
85
|
+
}
|
|
73
86
|
function chatsImport(argumentsList) {
|
|
74
87
|
const error = (code) => ({
|
|
75
88
|
kind: "usage-error",
|
|
@@ -79,21 +92,89 @@ function chatsImport(argumentsList) {
|
|
|
79
92
|
if (argumentsList.length === 1 &&
|
|
80
93
|
["--help", "-h"].includes(String(argumentsList[0])))
|
|
81
94
|
return { kind: "chats-import-help" };
|
|
82
|
-
const
|
|
83
|
-
|
|
95
|
+
const state = {
|
|
96
|
+
paths: [],
|
|
97
|
+
roots: [],
|
|
98
|
+
excludeProjects: [],
|
|
99
|
+
literal: false,
|
|
100
|
+
option: undefined,
|
|
101
|
+
previousImportId: undefined,
|
|
102
|
+
};
|
|
84
103
|
for (const argument of argumentsList) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
104
|
+
const failure = importArgument(state, argument);
|
|
105
|
+
if (failure !== undefined)
|
|
106
|
+
return error(failure);
|
|
107
|
+
}
|
|
108
|
+
if (state.option !== undefined)
|
|
109
|
+
return error("missing-value");
|
|
110
|
+
if (state.excludeProjects.length > 0 && state.roots.length === 0)
|
|
111
|
+
return error("roots-required");
|
|
112
|
+
if (state.paths.length + state.roots.length === 0)
|
|
113
|
+
return error("missing-argument");
|
|
114
|
+
return importInvocation(state);
|
|
115
|
+
}
|
|
116
|
+
function importInvocation(state) {
|
|
117
|
+
const previous = state.previousImportId === undefined
|
|
118
|
+
? {}
|
|
119
|
+
: { previousImportId: state.previousImportId };
|
|
120
|
+
return state.roots.length === 0
|
|
121
|
+
? { kind: "chats-import", paths: state.paths, ...previous }
|
|
122
|
+
: {
|
|
123
|
+
kind: "chats-import",
|
|
124
|
+
paths: state.paths,
|
|
125
|
+
...previous,
|
|
126
|
+
roots: state.roots,
|
|
127
|
+
excludeProjects: state.excludeProjects,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function importArgument(state, argument) {
|
|
131
|
+
if (state.option !== undefined)
|
|
132
|
+
return importValue(state, argument);
|
|
133
|
+
if (argument === "")
|
|
134
|
+
return "missing-argument";
|
|
135
|
+
if (state.literal) {
|
|
136
|
+
state.paths.push(argument);
|
|
137
|
+
return;
|
|
93
138
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
139
|
+
if (argument === "--previous-import" && state.previousImportId !== undefined)
|
|
140
|
+
return "repeated-previous-import";
|
|
141
|
+
if (["--root", "--exclude-project", "--previous-import"].includes(argument)) {
|
|
142
|
+
state.option = argument;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (argument === "--") {
|
|
146
|
+
state.literal = true;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (argument.startsWith("-"))
|
|
150
|
+
return "unexpected-option";
|
|
151
|
+
state.paths.push(argument);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
function importValue(state, argument) {
|
|
155
|
+
if (argument === "" || argument.startsWith("-"))
|
|
156
|
+
return "missing-value";
|
|
157
|
+
const option = state.option;
|
|
158
|
+
state.option = undefined;
|
|
159
|
+
if (option === "--root") {
|
|
160
|
+
state.roots.push(argument);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (option === "--exclude-project" && !validProjectName(argument))
|
|
164
|
+
return "invalid-exclusion";
|
|
165
|
+
if (option === "--exclude-project") {
|
|
166
|
+
state.excludeProjects.push(argument);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (!/^[a-f\d]{8}-[a-f\d]{4}-4[a-f\d]{3}-[89ab][a-f\d]{3}-[a-f\d]{12}$/.test(argument))
|
|
170
|
+
return "invalid-import-id";
|
|
171
|
+
state.previousImportId = argument;
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
function validProjectName(name) {
|
|
175
|
+
return (name !== "." &&
|
|
176
|
+
name !== ".." &&
|
|
177
|
+
!["/", "\\", "\0", "*", "?", "[", "]"].some((character) => name.includes(character)));
|
|
97
178
|
}
|
|
98
179
|
function parsedArguments(argumentsList) {
|
|
99
180
|
try {
|
package/dist/src/cli/client.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface Interruption {
|
|
|
4
4
|
readonly reply: Reply;
|
|
5
5
|
}
|
|
6
6
|
export declare function send(runtimeDirectory: string, request: ServiceRequest): Promise<Reply>;
|
|
7
|
+
export declare function listRuns(runtimeDirectory: string): Promise<Reply>;
|
|
7
8
|
export declare function mutate(runtimeDirectory: string, mutation: {
|
|
8
9
|
readonly request: ServiceRequest;
|
|
9
10
|
readonly signal: Promise<void>;
|
package/dist/src/cli/client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import net from "node:net";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { socketAddress } from "../broker/socket-address.js";
|
|
4
|
-
import { parseReplyLine, requestLine, } from "../protocol.js";
|
|
4
|
+
import { parseReplyLine, record, requestLine, } from "../protocol.js";
|
|
5
5
|
const mutations = ["run", "decision", "run-command"];
|
|
6
6
|
function unavailable(runtimeDirectory) {
|
|
7
7
|
return {
|
|
@@ -89,6 +89,43 @@ const unobserved = () => undefined;
|
|
|
89
89
|
export function send(runtimeDirectory, request) {
|
|
90
90
|
return exchange({ runtimeDirectory, request, onEvent: unobserved });
|
|
91
91
|
}
|
|
92
|
+
function listedRuns(result) {
|
|
93
|
+
const runs = result["runs"];
|
|
94
|
+
return Array.isArray(runs)
|
|
95
|
+
? runs.map(record).filter((run) => run !== undefined)
|
|
96
|
+
: [];
|
|
97
|
+
}
|
|
98
|
+
export async function listRuns(runtimeDirectory) {
|
|
99
|
+
const first = await send(runtimeDirectory, {
|
|
100
|
+
operation: "status",
|
|
101
|
+
parameters: { limit: 500 },
|
|
102
|
+
});
|
|
103
|
+
if (first.kind === "error")
|
|
104
|
+
return first;
|
|
105
|
+
const runs = listedRuns(first.result);
|
|
106
|
+
let next = first.result["next"];
|
|
107
|
+
let after = "";
|
|
108
|
+
while (typeof next === "string") {
|
|
109
|
+
if (next <= after)
|
|
110
|
+
return {
|
|
111
|
+
kind: "error",
|
|
112
|
+
error: {
|
|
113
|
+
code: "invalid-run-page",
|
|
114
|
+
detail: "The run-list cursor did not advance.",
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
after = next;
|
|
118
|
+
const page = await send(runtimeDirectory, {
|
|
119
|
+
operation: "status",
|
|
120
|
+
parameters: { limit: 500, after },
|
|
121
|
+
});
|
|
122
|
+
if (page.kind === "error")
|
|
123
|
+
return page;
|
|
124
|
+
runs.push(...listedRuns(page.result));
|
|
125
|
+
next = page.result["next"];
|
|
126
|
+
}
|
|
127
|
+
return { kind: "result", result: { ...first.result, runs, next: null } };
|
|
128
|
+
}
|
|
92
129
|
export function mutate(runtimeDirectory, mutation) {
|
|
93
130
|
return exchange({
|
|
94
131
|
runtimeDirectory,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WatchPrSettings } from "./watch-pr-settings.ts";
|
|
1
2
|
import type { RuntimeInvocation } from "./runtime-arguments.ts";
|
|
2
3
|
export interface Terminal {
|
|
3
4
|
readonly out: (text: string) => void;
|
|
@@ -5,6 +6,8 @@ export interface Terminal {
|
|
|
5
6
|
}
|
|
6
7
|
export interface CommandEnvironment {
|
|
7
8
|
readonly runtimeDirectory: string | undefined;
|
|
9
|
+
readonly watchActions?: string;
|
|
10
|
+
readonly watchPr?: WatchPrSettings;
|
|
8
11
|
readonly client?: string;
|
|
9
12
|
readonly workingDirectory: string;
|
|
10
13
|
readonly terminal: Terminal;
|