cloving 1.1.0 → 1.2.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 +121 -21
- package/assets/watch-fonts/fira-code-OFL.md +93 -0
- package/assets/watch-fonts/fira-code-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/fira-code-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-OFL.md +93 -0
- package/assets/watch-fonts/heebo-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-700-normal.woff2 +0 -0
- package/assets/watch-fonts/heebo-latin-800-normal.woff2 +0 -0
- package/assets/watch-fonts/manifest.json +112 -0
- package/assets/watch-fonts/space-grotesk-OFL.md +93 -0
- package/assets/watch-fonts/space-grotesk-latin-400-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-500-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-600-normal.woff2 +0 -0
- package/assets/watch-fonts/space-grotesk-latin-700-normal.woff2 +0 -0
- package/dist/src/authority/commands.d.ts +1 -0
- package/dist/src/authority/commands.js +25 -4
- package/dist/src/authority/dispatch-decision.d.ts +1 -0
- package/dist/src/authority/dispatch-decision.js +7 -4
- package/dist/src/authority/events.js +2 -2
- package/dist/src/authority/handoff-chain.d.ts +24 -0
- package/dist/src/authority/handoff-chain.js +46 -0
- package/dist/src/authority/reconciliation.d.ts +1 -0
- package/dist/src/authority/reconciliation.js +18 -11
- package/dist/src/authority/transitions.d.ts +1 -1
- package/dist/src/authority/transitions.js +3 -0
- package/dist/src/broker/actor/authority-queries.d.ts +4 -0
- package/dist/src/broker/actor/authority-queries.js +51 -2
- package/dist/src/broker/actor/delivery-intents.d.ts +1 -0
- package/dist/src/broker/actor/effect-delivery.js +9 -0
- package/dist/src/broker/authority-admission.js +13 -5
- package/dist/src/chats/analysis-batches.d.ts +18 -0
- package/dist/src/chats/analysis-batches.js +15 -1
- package/dist/src/chats/analysis-checkpoint.d.ts +29 -0
- package/dist/src/chats/analysis-checkpoint.js +31 -0
- package/dist/src/chats/analysis-input.d.ts +7 -1
- package/dist/src/chats/analysis-input.js +20 -6
- package/dist/src/chats/analysis-merge.d.ts +16 -0
- package/dist/src/chats/analysis-merge.js +134 -0
- package/dist/src/chats/analysis-prompt.d.ts +8 -0
- package/dist/src/chats/analysis-prompt.js +27 -1
- package/dist/src/chats/analysis-proposal.d.ts +2 -0
- package/dist/src/chats/analysis-proposal.js +9 -0
- package/dist/src/chats/analysis-shape.d.ts +1 -0
- package/dist/src/chats/analysis-shape.js +11 -4
- package/dist/src/chats/reference-mapping.d.ts +16 -0
- package/dist/src/chats/reference-mapping.js +153 -0
- package/dist/src/chats/workflow-generation-projection.d.ts +8 -0
- package/dist/src/chats/workflow-generation-projection.js +75 -0
- package/dist/src/chats/workflow-generation-source.d.ts +25 -0
- package/dist/src/chats/workflow-generation-source.js +124 -0
- package/dist/src/chats/workflow-generation-text.d.ts +23 -0
- package/dist/src/chats/workflow-generation-text.js +74 -0
- package/dist/src/chats/workflow-generation.d.ts +69 -0
- package/dist/src/chats/workflow-generation.js +142 -0
- package/dist/src/cli/render.d.ts +1 -0
- package/dist/src/cli/render.js +60 -13
- package/dist/src/cli/usage.js +5 -2
- package/dist/src/cli/watch-board-view.d.ts +12 -1
- package/dist/src/cli/watch-board-view.js +57 -11
- package/dist/src/cli/watch-board.d.ts +7 -1
- package/dist/src/cli/watch-board.js +42 -10
- package/dist/src/cli/watch-client.d.ts +1 -1
- package/dist/src/cli/watch-client.js +7 -3
- package/dist/src/cli/watch-model.d.ts +10 -4
- package/dist/src/cli/watch-model.js +35 -58
- package/dist/src/cli/watch-page.js +21 -4
- package/dist/src/cli/watch-phases.js +3 -3
- package/dist/src/cli/watch-pr-command.d.ts +5 -2
- package/dist/src/cli/watch-pr-command.js +41 -4
- package/dist/src/cli/watch-pr-identity.d.ts +14 -2
- package/dist/src/cli/watch-pr-identity.js +25 -0
- package/dist/src/cli/watch-pr-observer.d.ts +1 -0
- package/dist/src/cli/watch-pr-observer.js +48 -6
- package/dist/src/cli/watch-pull-request.d.ts +10 -0
- package/dist/src/cli/watch-pull-request.js +130 -0
- package/dist/src/cli/watch-reads.d.ts +7 -1
- package/dist/src/cli/watch-reads.js +42 -20
- package/dist/src/cli/watch-render.d.ts +14 -1
- package/dist/src/cli/watch-render.js +2 -1
- package/dist/src/cli/watch-server.js +33 -2
- package/dist/src/cli/watch-turn.js +1 -1
- package/dist/src/cli/watch-view.d.ts +7 -0
- package/dist/src/cli/watch-view.js +51 -3
- package/dist/src/daemon/analysis-worker.d.ts +0 -2
- package/dist/src/daemon/analysis-worker.js +71 -19
- package/dist/src/daemon/chats-batch-binding.d.ts +23 -0
- package/dist/src/daemon/chats-batch-binding.js +99 -0
- package/dist/src/daemon/chats-capture.d.ts +18 -0
- package/dist/src/daemon/chats-capture.js +194 -0
- package/dist/src/daemon/chats-checkpoints.d.ts +41 -0
- package/dist/src/daemon/chats-checkpoints.js +186 -0
- package/dist/src/daemon/chats-files.d.ts +29 -2
- package/dist/src/daemon/chats-files.js +142 -3
- package/dist/src/daemon/chats-follow-up.d.ts +38 -0
- package/dist/src/daemon/chats-follow-up.js +180 -0
- package/dist/src/daemon/chats-question.d.ts +15 -0
- package/dist/src/daemon/chats-question.js +93 -0
- package/dist/src/daemon/chats-worker.d.ts +1 -0
- package/dist/src/daemon/chats-worker.js +52 -51
- package/dist/src/daemon/claude-turn.d.ts +2 -1
- package/dist/src/daemon/claude-turn.js +1 -1
- package/dist/src/daemon/claude-worker.d.ts +0 -2
- package/dist/src/daemon/claude-worker.js +6 -11
- package/dist/src/daemon/daemon.d.ts +5 -1
- package/dist/src/daemon/daemon.js +38 -10
- package/dist/src/daemon/delivery-router.js +6 -0
- package/dist/src/daemon/github-calls.d.ts +4 -0
- package/dist/src/daemon/github-calls.js +16 -7
- package/dist/src/daemon/github-ci.d.ts +6 -0
- package/dist/src/daemon/github-ci.js +170 -0
- package/dist/src/daemon/github-merge-call.d.ts +14 -0
- package/dist/src/daemon/github-merge-call.js +73 -0
- package/dist/src/daemon/github-merge.d.ts +3 -5
- package/dist/src/daemon/github-merge.js +52 -95
- package/dist/src/daemon/github-methods.d.ts +28 -0
- package/dist/src/daemon/github-methods.js +80 -16
- package/dist/src/daemon/github-operation.d.ts +2 -0
- package/dist/src/daemon/github-retry.d.ts +6 -0
- package/dist/src/daemon/github-retry.js +49 -0
- package/dist/src/daemon/github-ticket-status.js +27 -1
- package/dist/src/daemon/github-worker.js +2 -1
- package/dist/src/daemon/handoff-worker.d.ts +10 -0
- package/dist/src/daemon/handoff-worker.js +232 -0
- package/dist/src/daemon/issue-queue.d.ts +1 -0
- package/dist/src/daemon/issue-queue.js +94 -42
- package/dist/src/daemon/notify-journal.d.ts +18 -0
- package/dist/src/daemon/notify-journal.js +108 -0
- package/dist/src/daemon/notify-message.js +2 -1
- package/dist/src/daemon/notify.js +176 -37
- package/dist/src/daemon/retrying-turn.d.ts +3 -0
- package/dist/src/daemon/retrying-turn.js +55 -9
- package/dist/src/daemon/ticket-dependency-waits.d.ts +18 -0
- package/dist/src/daemon/ticket-dependency-waits.js +60 -0
- package/dist/src/daemon/ticket-registration.d.ts +8 -4
- package/dist/src/daemon/ticket-registration.js +76 -22
- package/dist/src/daemon/ticket-source.d.ts +4 -1
- package/dist/src/daemon/ticket-source.js +8 -2
- package/dist/src/decision-visibility.d.ts +2 -0
- package/dist/src/decision-visibility.js +10 -0
- package/dist/src/execution-pin.d.ts +4 -0
- package/dist/src/execution-pin.js +23 -2
- package/dist/src/host/start-delivery.d.ts +2 -1
- package/dist/src/host/start-delivery.js +1 -1
- package/dist/src/host/step-bridge-host.d.ts +1 -0
- package/dist/src/host/step-bridge-host.js +16 -0
- package/dist/src/host/world-reconciliation.d.ts +1 -0
- package/dist/src/host/world-reconciliation.js +3 -0
- package/dist/src/host/world-runs.js +5 -0
- package/dist/src/issue-identity.d.ts +6 -0
- package/dist/src/issue-identity.js +23 -0
- package/dist/src/service/queue-runs.d.ts +3 -0
- package/dist/src/service/queue-runs.js +39 -0
- package/dist/src/service/registry.d.ts +8 -2
- package/dist/src/service/registry.js +20 -8
- package/dist/src/service/replies.d.ts +2 -0
- package/dist/src/service/run-handlers.js +44 -12
- package/dist/src/service/service.d.ts +3 -0
- package/dist/src/service/service.js +3 -1
- package/dist/src/service/submission-handler.d.ts +1 -0
- package/dist/src/service/submission-handler.js +92 -13
- package/dist/src/service/worker-totals.js +25 -1
- package/dist/src/step-bridge.d.ts +9 -0
- package/dist/src/steps/handoff.d.ts +2 -0
- package/dist/src/steps/handoff.js +13 -0
- package/dist/src/ticket-dependencies.d.ts +1 -0
- package/dist/src/ticket-dependencies.js +15 -0
- package/dist/src/ticket-status.js +7 -4
- package/dist/src/token-prices.d.ts +2 -0
- package/dist/src/token-prices.js +41 -9
- package/dist/src/worker-usage.d.ts +9 -0
- package/dist/src/worker-usage.js +1 -1
- package/dist/src/workflow-admission.d.ts +1 -0
- package/dist/src/workflow-candidate.js +1 -0
- package/dist/src/workflow-contracts.d.ts +2 -0
- package/dist/src/workflow-contracts.js +8 -3
- package/dist/src/workflow-declaration.d.ts +11 -0
- package/dist/src/workflow-declaration.js +71 -15
- package/dist/src/workflow-discovery.d.ts +18 -0
- package/dist/src/workflow-discovery.js +33 -0
- package/dist/src/workflow-refusals.d.ts +6 -0
- package/dist/src/workflow-refusals.js +39 -0
- package/dist/src/workflows.d.ts +2 -1
- package/dist/src/workflows.js +1 -0
- package/dist/tools/admit-workflow.d.ts +13 -0
- package/dist/tools/admit-workflow.js +94 -0
- package/dist/tools/self-host-project.d.ts +17 -0
- package/dist/tools/self-host-project.js +177 -0
- package/dist/tools/self-host.js +27 -81
- package/dist/tools/workflow-bundle-dependencies.d.ts +5 -0
- package/dist/tools/workflow-bundle-dependencies.js +93 -0
- package/dist/tools/workflow-bundles.d.ts +20 -0
- package/dist/tools/workflow-bundles.js +173 -0
- package/dist/tools/workflow-candidate-counts.d.ts +8 -0
- package/dist/tools/workflow-candidate-counts.js +251 -0
- package/dist/tools/workflow-candidate-program.d.ts +11 -0
- package/dist/tools/workflow-candidate-program.js +78 -0
- package/dist/tools/workflow-candidate-source.d.ts +6 -0
- package/dist/tools/workflow-candidate-source.js +107 -0
- package/dist/tools/workflow-candidate-syntax.d.ts +15 -0
- package/dist/tools/workflow-candidate-syntax.js +219 -0
- package/dist/tools/workflow-folder-capture.d.ts +17 -0
- package/dist/tools/workflow-folder-capture.js +216 -0
- package/dist/tools/workflow-folders.d.ts +33 -0
- package/dist/tools/workflow-folders.js +150 -0
- package/dist/tools/workflow-source-effects.d.ts +10 -0
- package/dist/tools/workflow-source-effects.js +213 -0
- package/dist/workflows/analyze-chats.d.ts +6 -1
- package/dist/workflows/analyze-chats.declaration.json +14 -1
- package/dist/workflows/analyze-chats.js +188 -11
- package/dist/workflows/develop-prd.js +71 -3
- package/dist/workflows/develop-ticket.js +5 -0
- package/dist/workflows/ticket-prompts.js +12 -2
- package/package.json +21 -3
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
export type WorkflowCaptureCode = "unsafe-workflow-path" | "workflow-read-failed" | "workflow-source-changed" | "unsupported-source" | "invalid-declaration";
|
|
3
|
+
export interface WorkflowFolder {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly components: readonly {
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly stats: BigIntStats;
|
|
8
|
+
}[];
|
|
9
|
+
}
|
|
10
|
+
export declare function inspectWorkflowFolder(root: string, parts: readonly string[]): Promise<WorkflowFolder>;
|
|
11
|
+
export declare function captureWorkflowCandidate(folder: WorkflowFolder, name: string): Promise<{
|
|
12
|
+
source: string | undefined;
|
|
13
|
+
declaration: string | undefined;
|
|
14
|
+
codes: WorkflowCaptureCode[];
|
|
15
|
+
}>;
|
|
16
|
+
export declare function workflowCaptureFailure(error: unknown): WorkflowCaptureCode;
|
|
17
|
+
export declare function workflowFolderEntries(folder: WorkflowFolder): Promise<string[]>;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat, open, readdir, realpath, } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join, relative, sep } from "node:path";
|
|
4
|
+
function contained(root, path) {
|
|
5
|
+
const suffix = relative(root, path);
|
|
6
|
+
return (!isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${sep}`));
|
|
7
|
+
}
|
|
8
|
+
function safeComponent(name) {
|
|
9
|
+
return (name.length > 0 &&
|
|
10
|
+
name !== "." &&
|
|
11
|
+
name !== ".." &&
|
|
12
|
+
!/[/\\:]/.test(name) &&
|
|
13
|
+
!name.startsWith("~"));
|
|
14
|
+
}
|
|
15
|
+
export async function inspectWorkflowFolder(root, parts) {
|
|
16
|
+
const canonical = await realpath(root);
|
|
17
|
+
if (parts.some((part) => !safeComponent(part)))
|
|
18
|
+
throw new Error("unsafe-workflow-path");
|
|
19
|
+
const components = [];
|
|
20
|
+
let path = canonical;
|
|
21
|
+
for (const part of ["", ...parts]) {
|
|
22
|
+
path = join(path, part);
|
|
23
|
+
const stats = await lstat(path, { bigint: true }).catch(async (error) => {
|
|
24
|
+
await verifyFolder({ path, components });
|
|
25
|
+
throw error;
|
|
26
|
+
});
|
|
27
|
+
if (stats.isSymbolicLink())
|
|
28
|
+
throw new Error("unsafe-workflow-path");
|
|
29
|
+
if (!stats.isDirectory())
|
|
30
|
+
throw new Error("workflow-read-failed");
|
|
31
|
+
components.push({ path, stats });
|
|
32
|
+
}
|
|
33
|
+
const resolved = await realpath(path).catch(changedPathError);
|
|
34
|
+
const folder = { path, components };
|
|
35
|
+
await verifyFolder(folder);
|
|
36
|
+
if (!contained(canonical, resolved))
|
|
37
|
+
throw new Error("unsafe-workflow-path");
|
|
38
|
+
return folder;
|
|
39
|
+
}
|
|
40
|
+
function sameIdentity(left, right) {
|
|
41
|
+
return (left.dev === right.dev && left.ino === right.ino && left.mode === right.mode);
|
|
42
|
+
}
|
|
43
|
+
function sameSnapshot(left, right) {
|
|
44
|
+
return (sameIdentity(left, right) &&
|
|
45
|
+
left.size === right.size &&
|
|
46
|
+
left.mtimeNs === right.mtimeNs &&
|
|
47
|
+
left.ctimeNs === right.ctimeNs);
|
|
48
|
+
}
|
|
49
|
+
async function verifyFolder(folder) {
|
|
50
|
+
for (const component of folder.components) {
|
|
51
|
+
const compare = component.path === folder.path ? sameSnapshot : sameIdentity;
|
|
52
|
+
if (!compare(component.stats, await capturedStats(component.path)))
|
|
53
|
+
throw new Error("workflow-source-changed");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function capturedStats(path) {
|
|
57
|
+
return lstat(path, { bigint: true }).catch(changedPathError);
|
|
58
|
+
}
|
|
59
|
+
function changedPathError(error) {
|
|
60
|
+
if (error instanceof Error &&
|
|
61
|
+
"code" in error &&
|
|
62
|
+
(error.code === "ENOENT" ||
|
|
63
|
+
error.code === "ENOTDIR" ||
|
|
64
|
+
error.code === "ELOOP"))
|
|
65
|
+
throw new Error("workflow-source-changed");
|
|
66
|
+
throw error;
|
|
67
|
+
}
|
|
68
|
+
async function openSource(folder, name) {
|
|
69
|
+
const flags = constants.O_RDONLY | constants.O_NONBLOCK;
|
|
70
|
+
if (process.platform === "darwin")
|
|
71
|
+
return open(join(folder.path, name), flags | 0x20000000);
|
|
72
|
+
if (process.platform !== "linux")
|
|
73
|
+
throw new Error("workflow-read-failed");
|
|
74
|
+
const handles = [];
|
|
75
|
+
try {
|
|
76
|
+
let path = folder.components[0]?.path;
|
|
77
|
+
if (!path)
|
|
78
|
+
throw new Error("workflow-read-failed");
|
|
79
|
+
for (const component of folder.components) {
|
|
80
|
+
const handle = await open(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
81
|
+
handles.push(handle);
|
|
82
|
+
if (!sameIdentity(component.stats, await handle.stat({ bigint: true })))
|
|
83
|
+
throw new Error("workflow-source-changed");
|
|
84
|
+
const next = folder.components[handles.length];
|
|
85
|
+
path = `/proc/self/fd/${String(handle.fd)}/${next ? relative(component.path, next.path) : name}`;
|
|
86
|
+
}
|
|
87
|
+
return await open(path, flags | constants.O_NOFOLLOW);
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
for (const handle of handles.reverse())
|
|
91
|
+
await handle.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function inspectFile(folder, name) {
|
|
95
|
+
const path = join(folder.path, name);
|
|
96
|
+
const stats = await lstat(path, { bigint: true });
|
|
97
|
+
if (!stats.isFile())
|
|
98
|
+
throw new Error("unsafe-workflow-path");
|
|
99
|
+
const resolved = await realpath(path).catch(changedPathError);
|
|
100
|
+
if (!sameSnapshot(stats, await capturedStats(path)))
|
|
101
|
+
throw new Error("workflow-source-changed");
|
|
102
|
+
if (!contained(folder.path, resolved))
|
|
103
|
+
throw new Error("unsafe-workflow-path");
|
|
104
|
+
return { path, name, stats };
|
|
105
|
+
}
|
|
106
|
+
async function captureFile(folder, file) {
|
|
107
|
+
const { stats } = file;
|
|
108
|
+
const handle = await openSource(folder, file.name).catch(changedPathError);
|
|
109
|
+
try {
|
|
110
|
+
if (!sameSnapshot(stats, await handle.stat({ bigint: true })))
|
|
111
|
+
throw new Error("workflow-source-changed");
|
|
112
|
+
const bytes = await handle.readFile();
|
|
113
|
+
if (!sameSnapshot(stats, await handle.stat({ bigint: true })) ||
|
|
114
|
+
BigInt(bytes.length) !== stats.size)
|
|
115
|
+
throw new Error("workflow-source-changed");
|
|
116
|
+
return { ...file, bytes };
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
await handle.close();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function captureFiles(folder, names) {
|
|
123
|
+
const inspected = [];
|
|
124
|
+
const captured = [];
|
|
125
|
+
const codes = [];
|
|
126
|
+
for (const name of names) {
|
|
127
|
+
try {
|
|
128
|
+
inspected.push(await inspectFile(folder, name));
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
codes.push(workflowCaptureFailure(error));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const file of inspected) {
|
|
135
|
+
try {
|
|
136
|
+
captured.push(await captureFile(folder, file));
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
codes.push(workflowCaptureFailure(error));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const file of captured) {
|
|
143
|
+
if (!sameSnapshot(file.stats, await capturedStats(file.path)))
|
|
144
|
+
throw new Error("workflow-source-changed");
|
|
145
|
+
}
|
|
146
|
+
return { captured, codes };
|
|
147
|
+
}
|
|
148
|
+
function decodedFile(input, codes) {
|
|
149
|
+
if (input.bytes === undefined)
|
|
150
|
+
return undefined;
|
|
151
|
+
const text = input.bytes.toString("utf8");
|
|
152
|
+
if (input.bytes.equals(Buffer.from(text)))
|
|
153
|
+
return text;
|
|
154
|
+
codes.push(input.code);
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
export async function captureWorkflowCandidate(folder, name) {
|
|
158
|
+
if (!safeComponent(name) || !name.endsWith(".ts") || name.endsWith(".d.ts"))
|
|
159
|
+
throw new Error("unsafe-workflow-path");
|
|
160
|
+
await verifyFolder(folder);
|
|
161
|
+
const declarationName = name.slice(0, -3) + ".declaration.json";
|
|
162
|
+
const { captured, codes } = await captureFiles(folder, [
|
|
163
|
+
name,
|
|
164
|
+
declarationName,
|
|
165
|
+
]);
|
|
166
|
+
await verifyFolder(folder);
|
|
167
|
+
const source = decodedFile({
|
|
168
|
+
bytes: captured.find((file) => file.name === name)?.bytes,
|
|
169
|
+
code: "unsupported-source",
|
|
170
|
+
}, codes);
|
|
171
|
+
const declaration = decodedFile({
|
|
172
|
+
bytes: captured.find((file) => file.name === declarationName)?.bytes,
|
|
173
|
+
code: "invalid-declaration",
|
|
174
|
+
}, codes);
|
|
175
|
+
return {
|
|
176
|
+
source,
|
|
177
|
+
declaration,
|
|
178
|
+
codes: [
|
|
179
|
+
...new Set(codes.includes("unsafe-workflow-path")
|
|
180
|
+
? codes.filter((code) => code !== "workflow-read-failed")
|
|
181
|
+
: codes),
|
|
182
|
+
].sort(),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
export function workflowCaptureFailure(error) {
|
|
186
|
+
const code = error instanceof Error && "code" in error ? error.code : undefined;
|
|
187
|
+
if (code === "ELOOP")
|
|
188
|
+
return "unsafe-workflow-path";
|
|
189
|
+
const message = error instanceof Error ? error.message : "";
|
|
190
|
+
if (message === "unsafe-workflow-path" ||
|
|
191
|
+
message === "workflow-source-changed" ||
|
|
192
|
+
message === "unsupported-source" ||
|
|
193
|
+
message === "invalid-declaration")
|
|
194
|
+
return message;
|
|
195
|
+
return "workflow-read-failed";
|
|
196
|
+
}
|
|
197
|
+
async function descriptorEntries(folder) {
|
|
198
|
+
const handle = await openSource(folder, ".").catch(changedPathError);
|
|
199
|
+
try {
|
|
200
|
+
const expected = folder.components.at(-1)?.stats;
|
|
201
|
+
if (!expected ||
|
|
202
|
+
!sameIdentity(expected, await handle.stat({ bigint: true })))
|
|
203
|
+
throw new Error("workflow-source-changed");
|
|
204
|
+
return await readdir(`/proc/self/fd/${String(handle.fd)}`);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
await handle.close();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
export async function workflowFolderEntries(folder) {
|
|
211
|
+
const entries = process.platform === "darwin"
|
|
212
|
+
? await readdir(`/.nofollow${folder.path}`).catch(changedPathError)
|
|
213
|
+
: await descriptorEntries(folder);
|
|
214
|
+
await verifyFolder(folder);
|
|
215
|
+
return entries;
|
|
216
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WorkflowCandidateCode } from "../src/workflow-candidate.ts";
|
|
2
|
+
import { type WorkflowConflictCode, type WorkflowReservation } from "../src/workflow-discovery.ts";
|
|
3
|
+
import type { CandidatePackageOptions } from "./workflow-candidate-program.ts";
|
|
4
|
+
import { type WorkflowCaptureCode } from "./workflow-folder-capture.ts";
|
|
5
|
+
export interface WorkflowFolderInput {
|
|
6
|
+
readonly repository: string;
|
|
7
|
+
readonly home: string;
|
|
8
|
+
readonly xdgConfigHome?: string | undefined;
|
|
9
|
+
readonly bundled: readonly WorkflowReservation[];
|
|
10
|
+
}
|
|
11
|
+
interface WorkflowLabel {
|
|
12
|
+
readonly scope: "project" | "user";
|
|
13
|
+
readonly label: string;
|
|
14
|
+
}
|
|
15
|
+
interface WorkflowSnapshot extends WorkflowLabel {
|
|
16
|
+
readonly baseName: string;
|
|
17
|
+
readonly source: string;
|
|
18
|
+
readonly declaration: string;
|
|
19
|
+
}
|
|
20
|
+
export interface WorkflowFolderCandidate extends WorkflowSnapshot {
|
|
21
|
+
readonly repository: string;
|
|
22
|
+
readonly artifactDigest: string;
|
|
23
|
+
}
|
|
24
|
+
export interface WorkflowFolderRefusal extends WorkflowLabel {
|
|
25
|
+
readonly codes: readonly (WorkflowCandidateCode | WorkflowCaptureCode | WorkflowConflictCode | "compile-failed")[];
|
|
26
|
+
}
|
|
27
|
+
export interface WorkflowFolderResult {
|
|
28
|
+
readonly repository: string;
|
|
29
|
+
readonly candidates: readonly WorkflowFolderCandidate[];
|
|
30
|
+
readonly refusals: readonly WorkflowFolderRefusal[];
|
|
31
|
+
}
|
|
32
|
+
export declare function loadWorkflowFolders(input: WorkflowFolderInput, installed?: CandidatePackageOptions): Promise<WorkflowFolderResult>;
|
|
33
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, join } from "node:path";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
import { record } from "../src/protocol.js";
|
|
6
|
+
import { workflowConflicts, } from "../src/workflow-discovery.js";
|
|
7
|
+
import { admitWorkflowCandidate } from "./admit-workflow.js";
|
|
8
|
+
import { captureWorkflowCandidate, inspectWorkflowFolder, workflowCaptureFailure, workflowFolderEntries, } from "./workflow-folder-capture.js";
|
|
9
|
+
function fileLabel(name) {
|
|
10
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.ts$/.test(name)
|
|
11
|
+
? name
|
|
12
|
+
: "file-" + createHash("sha256").update(name).digest("hex").slice(0, 16);
|
|
13
|
+
}
|
|
14
|
+
function parsedDeclaration(text) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(text);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function identity(entry) {
|
|
23
|
+
const snapshot = entry.kind === "captured" ? entry.snapshot : entry;
|
|
24
|
+
const source = ts.createSourceFile("candidate.ts", snapshot.source ?? "", ts.ScriptTarget.Latest, true);
|
|
25
|
+
const workflowId = record(record(parsedDeclaration(snapshot.declaration ?? ""))?.["workflow"])?.["workflowId"];
|
|
26
|
+
return {
|
|
27
|
+
baseName: snapshot.baseName,
|
|
28
|
+
workflowId: typeof workflowId === "string" ? workflowId : undefined,
|
|
29
|
+
functionNames: source.statements
|
|
30
|
+
.filter(ts.isFunctionDeclaration)
|
|
31
|
+
.filter((node) => (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Export) !== 0)
|
|
32
|
+
.flatMap((node) => (node.name ? [node.name.text] : [])),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function compareLabels(left, right) {
|
|
36
|
+
const a = `${left.scope}\0${left.label}`;
|
|
37
|
+
const b = `${right.scope}\0${right.label}`;
|
|
38
|
+
return a < b ? -1 : Number(a > b);
|
|
39
|
+
}
|
|
40
|
+
async function scanFolder(location, refusals) {
|
|
41
|
+
let folder;
|
|
42
|
+
let entries;
|
|
43
|
+
try {
|
|
44
|
+
folder = await inspectWorkflowFolder(location.root, location.parts);
|
|
45
|
+
entries = await workflowFolderEntries(folder);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (!(error instanceof Error && "code" in error && error.code === "ENOENT"))
|
|
49
|
+
refusals.push({
|
|
50
|
+
scope: location.scope,
|
|
51
|
+
label: "workflows/",
|
|
52
|
+
codes: [workflowCaptureFailure(error)],
|
|
53
|
+
});
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
const snapshots = [];
|
|
57
|
+
for (const name of entries
|
|
58
|
+
.filter((name) => name.endsWith(".ts") && !name.endsWith(".d.ts"))
|
|
59
|
+
.sort()) {
|
|
60
|
+
snapshots.push(await captureEntry(folder, {
|
|
61
|
+
scope: location.scope,
|
|
62
|
+
label: fileLabel(name),
|
|
63
|
+
name,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
return snapshots;
|
|
67
|
+
}
|
|
68
|
+
async function captureEntry(folder, file) {
|
|
69
|
+
const label = {
|
|
70
|
+
scope: file.scope,
|
|
71
|
+
label: file.label,
|
|
72
|
+
baseName: file.name.slice(0, -3),
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const capture = await captureWorkflowCandidate(folder, file.name);
|
|
76
|
+
if (capture.source === undefined ||
|
|
77
|
+
capture.declaration === undefined ||
|
|
78
|
+
capture.codes.length > 0)
|
|
79
|
+
return { kind: "refused", ...label, ...capture };
|
|
80
|
+
return {
|
|
81
|
+
kind: "captured",
|
|
82
|
+
snapshot: {
|
|
83
|
+
...label,
|
|
84
|
+
source: capture.source,
|
|
85
|
+
declaration: capture.declaration,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return {
|
|
91
|
+
kind: "refused",
|
|
92
|
+
...label,
|
|
93
|
+
codes: [workflowCaptureFailure(error)],
|
|
94
|
+
source: undefined,
|
|
95
|
+
declaration: undefined,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
async function scanFolders(input, refusals) {
|
|
100
|
+
const repository = await realpath(input.repository);
|
|
101
|
+
const configuration = input.xdgConfigHome && isAbsolute(input.xdgConfigHome)
|
|
102
|
+
? input.xdgConfigHome
|
|
103
|
+
: join(input.home, ".config");
|
|
104
|
+
const snapshots = [
|
|
105
|
+
...(await scanFolder({ scope: "project", root: repository, parts: [".cloving", "workflows"] }, refusals)),
|
|
106
|
+
...(await scanFolder({ scope: "user", root: configuration, parts: ["cloving", "workflows"] }, refusals)),
|
|
107
|
+
];
|
|
108
|
+
return { repository, snapshots };
|
|
109
|
+
}
|
|
110
|
+
export async function loadWorkflowFolders(input, installed = {}) {
|
|
111
|
+
const candidates = [];
|
|
112
|
+
const refusals = [];
|
|
113
|
+
const { repository, snapshots } = await scanFolders(input, refusals);
|
|
114
|
+
const conflicts = workflowConflicts(snapshots.map(identity), input.bundled);
|
|
115
|
+
for (const [index, entry] of snapshots.entries()) {
|
|
116
|
+
const label = entry.kind === "captured" ? entry.snapshot : entry;
|
|
117
|
+
const codes = [
|
|
118
|
+
...new Set([
|
|
119
|
+
...(entry.kind === "refused" ? entry.codes : []),
|
|
120
|
+
...(conflicts[index] ?? []),
|
|
121
|
+
]),
|
|
122
|
+
].sort();
|
|
123
|
+
if (entry.kind === "refused" || codes.length > 0) {
|
|
124
|
+
refusals.push({ scope: label.scope, label: label.label, codes });
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const { snapshot } = entry;
|
|
128
|
+
const result = await admitWorkflowCandidate({
|
|
129
|
+
source: snapshot.source,
|
|
130
|
+
declaration: parsedDeclaration(snapshot.declaration),
|
|
131
|
+
}, installed);
|
|
132
|
+
if (result.kind === "refused")
|
|
133
|
+
refusals.push({
|
|
134
|
+
scope: snapshot.scope,
|
|
135
|
+
label: snapshot.label,
|
|
136
|
+
codes: result.codes,
|
|
137
|
+
});
|
|
138
|
+
else
|
|
139
|
+
candidates.push({
|
|
140
|
+
...snapshot,
|
|
141
|
+
repository,
|
|
142
|
+
artifactDigest: result.artifactDigest,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
repository,
|
|
147
|
+
candidates: candidates.sort(compareLabels),
|
|
148
|
+
refusals: refusals.sort(compareLabels),
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
export interface ReferenceContext {
|
|
3
|
+
readonly parent: ts.Node | undefined;
|
|
4
|
+
readonly checker: ts.TypeChecker;
|
|
5
|
+
readonly profile?: "candidate";
|
|
6
|
+
}
|
|
7
|
+
export declare function referenceSymbol(node: ts.Identifier, context: ReferenceContext): ts.Symbol | undefined;
|
|
8
|
+
export declare function typeOnlyEffectNode(node: ts.Node): boolean;
|
|
9
|
+
export declare function restrictedEffect(node: ts.Node, context: ReferenceContext): boolean;
|
|
10
|
+
export declare function sourceEffectNodes(source: ts.SourceFile, context: Omit<ReferenceContext, "parent">): readonly ts.Node[];
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
const ambientNames = new Set([
|
|
3
|
+
"globalThis",
|
|
4
|
+
"global",
|
|
5
|
+
"window",
|
|
6
|
+
"self",
|
|
7
|
+
"process",
|
|
8
|
+
"Date",
|
|
9
|
+
"performance",
|
|
10
|
+
"fetch",
|
|
11
|
+
"WebSocket",
|
|
12
|
+
"Worker",
|
|
13
|
+
"crypto",
|
|
14
|
+
"setTimeout",
|
|
15
|
+
"setInterval",
|
|
16
|
+
"setImmediate",
|
|
17
|
+
"queueMicrotask",
|
|
18
|
+
"eval",
|
|
19
|
+
"Function",
|
|
20
|
+
"require",
|
|
21
|
+
"console",
|
|
22
|
+
"navigator",
|
|
23
|
+
"Atomics",
|
|
24
|
+
"SharedArrayBuffer",
|
|
25
|
+
"WeakRef",
|
|
26
|
+
"FinalizationRegistry",
|
|
27
|
+
"Intl",
|
|
28
|
+
]);
|
|
29
|
+
const candidateNames = new Set([
|
|
30
|
+
"Buffer",
|
|
31
|
+
"document",
|
|
32
|
+
"screen",
|
|
33
|
+
"history",
|
|
34
|
+
"location",
|
|
35
|
+
"localStorage",
|
|
36
|
+
"sessionStorage",
|
|
37
|
+
"indexedDB",
|
|
38
|
+
"caches",
|
|
39
|
+
"Deno",
|
|
40
|
+
"Bun",
|
|
41
|
+
"__dirname",
|
|
42
|
+
"__filename",
|
|
43
|
+
"File",
|
|
44
|
+
"FileReader",
|
|
45
|
+
"readFile",
|
|
46
|
+
"writeFile",
|
|
47
|
+
"XMLHttpRequest",
|
|
48
|
+
"EventSource",
|
|
49
|
+
"WebTransport",
|
|
50
|
+
"BroadcastChannel",
|
|
51
|
+
"MessageChannel",
|
|
52
|
+
"MessagePort",
|
|
53
|
+
"postMessage",
|
|
54
|
+
"clearTimeout",
|
|
55
|
+
"clearInterval",
|
|
56
|
+
"clearImmediate",
|
|
57
|
+
"requestAnimationFrame",
|
|
58
|
+
"requestIdleCallback",
|
|
59
|
+
"cancelAnimationFrame",
|
|
60
|
+
"cancelIdleCallback",
|
|
61
|
+
"AbortSignal",
|
|
62
|
+
"Reflect",
|
|
63
|
+
"Proxy",
|
|
64
|
+
"constructor",
|
|
65
|
+
"WebAssembly",
|
|
66
|
+
]);
|
|
67
|
+
const reflectiveProperties = new Set(["constructor", "__proto__", "prototype"]);
|
|
68
|
+
const candidateProperties = new Set([
|
|
69
|
+
"getPrototypeOf",
|
|
70
|
+
"setPrototypeOf",
|
|
71
|
+
"getOwnPropertyDescriptor",
|
|
72
|
+
"getOwnPropertyDescriptors",
|
|
73
|
+
]);
|
|
74
|
+
const pureMathMembers = new Set([
|
|
75
|
+
"E",
|
|
76
|
+
"LN10",
|
|
77
|
+
"LN2",
|
|
78
|
+
"LOG10E",
|
|
79
|
+
"LOG2E",
|
|
80
|
+
"PI",
|
|
81
|
+
"SQRT1_2",
|
|
82
|
+
"SQRT2",
|
|
83
|
+
"abs",
|
|
84
|
+
"acos",
|
|
85
|
+
"acosh",
|
|
86
|
+
"asin",
|
|
87
|
+
"asinh",
|
|
88
|
+
"atan",
|
|
89
|
+
"atan2",
|
|
90
|
+
"atanh",
|
|
91
|
+
"cbrt",
|
|
92
|
+
"ceil",
|
|
93
|
+
"clz32",
|
|
94
|
+
"cos",
|
|
95
|
+
"cosh",
|
|
96
|
+
"exp",
|
|
97
|
+
"expm1",
|
|
98
|
+
"floor",
|
|
99
|
+
"fround",
|
|
100
|
+
"hypot",
|
|
101
|
+
"imul",
|
|
102
|
+
"log",
|
|
103
|
+
"log10",
|
|
104
|
+
"log1p",
|
|
105
|
+
"log2",
|
|
106
|
+
"max",
|
|
107
|
+
"min",
|
|
108
|
+
"pow",
|
|
109
|
+
"round",
|
|
110
|
+
"sign",
|
|
111
|
+
"sin",
|
|
112
|
+
"sinh",
|
|
113
|
+
"sqrt",
|
|
114
|
+
"tan",
|
|
115
|
+
"tanh",
|
|
116
|
+
"trunc",
|
|
117
|
+
]);
|
|
118
|
+
function localBinding(symbol) {
|
|
119
|
+
return (symbol.declarations ?? []).some((declaration) => !declaration.getSourceFile().isDeclarationFile &&
|
|
120
|
+
(ts.getCombinedModifierFlags(declaration) & ts.ModifierFlags.Ambient) ===
|
|
121
|
+
0);
|
|
122
|
+
}
|
|
123
|
+
export function referenceSymbol(node, context) {
|
|
124
|
+
const { parent, checker } = context;
|
|
125
|
+
if (parent &&
|
|
126
|
+
(ts.isPropertyAccessExpression(parent) ||
|
|
127
|
+
ts.isPropertyAssignment(parent)) &&
|
|
128
|
+
parent.name === node)
|
|
129
|
+
return undefined;
|
|
130
|
+
if (parent && ts.isShorthandPropertyAssignment(parent))
|
|
131
|
+
return checker.getShorthandAssignmentValueSymbol(parent);
|
|
132
|
+
return checker.getSymbolAtLocation(node);
|
|
133
|
+
}
|
|
134
|
+
function referenceIdentifier(node, parent) {
|
|
135
|
+
if (!ts.isIdentifier(node))
|
|
136
|
+
return false;
|
|
137
|
+
if (!parent)
|
|
138
|
+
return true;
|
|
139
|
+
if ((ts.isPropertyAccessExpression(parent) ||
|
|
140
|
+
ts.isPropertyAssignment(parent)) &&
|
|
141
|
+
parent.name === node)
|
|
142
|
+
return false;
|
|
143
|
+
return !ts.isImportSpecifier(parent);
|
|
144
|
+
}
|
|
145
|
+
function restrictedAmbientName(name, context) {
|
|
146
|
+
return (ambientNames.has(name) ||
|
|
147
|
+
name === "Math" ||
|
|
148
|
+
(context.profile === "candidate" && candidateNames.has(name)));
|
|
149
|
+
}
|
|
150
|
+
function pureMathAccess(node, parent) {
|
|
151
|
+
return (!!parent &&
|
|
152
|
+
ts.isPropertyAccessExpression(parent) &&
|
|
153
|
+
parent.expression === node &&
|
|
154
|
+
pureMathMembers.has(parent.name.text));
|
|
155
|
+
}
|
|
156
|
+
function ambientReference(node, context) {
|
|
157
|
+
const { parent } = context;
|
|
158
|
+
if (!referenceIdentifier(node, parent))
|
|
159
|
+
return false;
|
|
160
|
+
if (!restrictedAmbientName(node.text, context))
|
|
161
|
+
return false;
|
|
162
|
+
const candidate = context.profile === "candidate";
|
|
163
|
+
const symbol = referenceSymbol(node, context);
|
|
164
|
+
if ((!symbol && !candidate) || (symbol && localBinding(symbol)))
|
|
165
|
+
return false;
|
|
166
|
+
return node.text !== "Math" || !pureMathAccess(node, parent);
|
|
167
|
+
}
|
|
168
|
+
function reflectiveAccess(node, context) {
|
|
169
|
+
const candidate = context.profile === "candidate";
|
|
170
|
+
const restricted = (name) => reflectiveProperties.has(name) ||
|
|
171
|
+
(candidate && candidateProperties.has(name));
|
|
172
|
+
if (ts.isPropertyAccessExpression(node))
|
|
173
|
+
return restricted(node.name.text);
|
|
174
|
+
if (ts.isElementAccessExpression(node) &&
|
|
175
|
+
ts.isStringLiteralLike(node.argumentExpression))
|
|
176
|
+
return restricted(node.argumentExpression.text);
|
|
177
|
+
if (!candidate || !ts.isBindingElement(node))
|
|
178
|
+
return false;
|
|
179
|
+
const name = node.propertyName ?? node.name;
|
|
180
|
+
return ((ts.isIdentifier(name) || ts.isStringLiteralLike(name)) &&
|
|
181
|
+
restricted(name.text));
|
|
182
|
+
}
|
|
183
|
+
function dynamicCapability(node, checker) {
|
|
184
|
+
if (!ts.isElementAccessExpression(node))
|
|
185
|
+
return false;
|
|
186
|
+
const target = checker.getTypeAtLocation(node.expression);
|
|
187
|
+
return (target.getCallSignatures().length > 0 ||
|
|
188
|
+
target.getConstructSignatures().length > 0);
|
|
189
|
+
}
|
|
190
|
+
export function typeOnlyEffectNode(node) {
|
|
191
|
+
return ((ts.isTypeNode(node) && !ts.isExpressionWithTypeArguments(node)) ||
|
|
192
|
+
ts.isInterfaceDeclaration(node) ||
|
|
193
|
+
ts.isTypeAliasDeclaration(node));
|
|
194
|
+
}
|
|
195
|
+
export function restrictedEffect(node, context) {
|
|
196
|
+
return (ambientReference(node, context) ||
|
|
197
|
+
reflectiveAccess(node, context) ||
|
|
198
|
+
dynamicCapability(node, context.checker));
|
|
199
|
+
}
|
|
200
|
+
export function sourceEffectNodes(source, context) {
|
|
201
|
+
const findings = [];
|
|
202
|
+
function visit(node, parent) {
|
|
203
|
+
if (typeOnlyEffectNode(node))
|
|
204
|
+
return;
|
|
205
|
+
if (restrictedEffect(node, { ...context, parent }))
|
|
206
|
+
findings.push(node);
|
|
207
|
+
ts.forEachChild(node, (child) => {
|
|
208
|
+
visit(child, node);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
visit(source);
|
|
212
|
+
return findings;
|
|
213
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { CycleOutcome } from "cloving/workflows";
|
|
2
|
+
interface Decision {
|
|
3
|
+
readonly gateId: string;
|
|
4
|
+
readonly decision: string;
|
|
5
|
+
readonly answer: string | null;
|
|
6
|
+
}
|
|
2
7
|
interface AnalysisInput {
|
|
3
8
|
readonly cycleId: string;
|
|
4
9
|
readonly repository: string;
|
|
@@ -6,5 +11,5 @@ interface AnalysisInput {
|
|
|
6
11
|
readonly importId: string;
|
|
7
12
|
};
|
|
8
13
|
}
|
|
9
|
-
export declare function analyzeChats(input: AnalysisInput): Promise<CycleOutcome>;
|
|
14
|
+
export declare function analyzeChats(input: AnalysisInput, decision?: Decision): Promise<CycleOutcome>;
|
|
10
15
|
export {};
|
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"workflowId": "analyze-chats",
|
|
3
|
-
"effectLimit":
|
|
3
|
+
"effectLimit": 11,
|
|
4
4
|
"capabilities": ["chats", "claude"],
|
|
5
|
+
"stages": [
|
|
6
|
+
{
|
|
7
|
+
"name": "read-input",
|
|
8
|
+
"label": "Read input",
|
|
9
|
+
"operations": ["read-analysis"]
|
|
10
|
+
},
|
|
11
|
+
{ "name": "analyze", "label": "Analyze", "operations": ["turn-analysis"] },
|
|
12
|
+
{
|
|
13
|
+
"name": "save-result",
|
|
14
|
+
"label": "Save result",
|
|
15
|
+
"operations": ["save-analysis"]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
5
18
|
"input": {
|
|
6
19
|
"maximumBytes": 4096,
|
|
7
20
|
"fields": { "importId": { "type": "string" } }
|