@crewhaus/tool-flow 0.7.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 +178 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +461 -0
- package/dist/lib/assign.d.ts +122 -0
- package/dist/lib/assign.js +235 -0
- package/dist/lib/branch.d.ts +68 -0
- package/dist/lib/branch.js +76 -0
- package/dist/lib/canonical.d.ts +13 -0
- package/dist/lib/canonical.js +20 -0
- package/dist/lib/consensus.d.ts +58 -0
- package/dist/lib/consensus.js +99 -0
- package/dist/lib/deadline.d.ts +49 -0
- package/dist/lib/deadline.js +68 -0
- package/dist/lib/decision.d.ts +38 -0
- package/dist/lib/decision.js +107 -0
- package/dist/lib/errors.d.ts +77 -0
- package/dist/lib/errors.js +436 -0
- package/dist/lib/score.d.ts +50 -0
- package/dist/lib/score.js +56 -0
- package/dist/lib/sequence.d.ts +109 -0
- package/dist/lib/sequence.js +244 -0
- package/dist/lib/stall.d.ts +41 -0
- package/dist/lib/stall.js +124 -0
- package/package.json +46 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan a declared sequence of steps. Nothing here runs one.
|
|
3
|
+
*
|
|
4
|
+
* A sequence is the other half of {@link ./branch}: a branch answers "which
|
|
5
|
+
* way", a sequence answers "what next, and what is it still waiting on". The
|
|
6
|
+
* answer is a plan — the steps whose prerequisites are met, whose gate time
|
|
7
|
+
* has passed and whose conditions hold, each carrying the parameters the
|
|
8
|
+
* caller should execute it with.
|
|
9
|
+
*
|
|
10
|
+
* It stops there on purpose. Invoking a tool needs the executor, which no
|
|
11
|
+
* tool package has, and a "runner" that quietly returned the steps it would
|
|
12
|
+
* have run is the shape of lie a caller only discovers in production. So
|
|
13
|
+
* this returns the plan and the caller — or the harness, which does have the
|
|
14
|
+
* executor — acts on it.
|
|
15
|
+
*
|
|
16
|
+
* Progress is an argument, not a memory. `completed` and `failed` come in
|
|
17
|
+
* the way {@link ./stall} takes its history, so the same inputs give the
|
|
18
|
+
* same plan in a test, in a replay and in production.
|
|
19
|
+
*
|
|
20
|
+
* Three distinctions carry the weight here, and each one is a different
|
|
21
|
+
* answer a caller has to be able to tell apart:
|
|
22
|
+
*
|
|
23
|
+
* - **waiting** — a prerequisite has not run yet, or the step is not due
|
|
24
|
+
* yet. It can still happen.
|
|
25
|
+
* - **unreachable** — a prerequisite failed or was skipped, so this step can
|
|
26
|
+
* never happen. Reporting that as "waiting" is how a flow hangs forever on
|
|
27
|
+
* a step that died three turns ago.
|
|
28
|
+
* - **skipped** — the step's own conditions did not hold. A settled no.
|
|
29
|
+
*/
|
|
30
|
+
import { runChecks } from "@crewhaus/tool-schema";
|
|
31
|
+
/**
|
|
32
|
+
* Where a flow stands.
|
|
33
|
+
*
|
|
34
|
+
* `finished` and `halted` are kept apart on purpose. Both mean there is
|
|
35
|
+
* nothing left to run, and a caller that reads one boolean for both reports
|
|
36
|
+
* a cadence that died on its second step as a cadence that completed.
|
|
37
|
+
*/
|
|
38
|
+
export const PLAN_STATES = ["ready", "blocked", "finished", "halted"];
|
|
39
|
+
/**
|
|
40
|
+
* Work out what to run next.
|
|
41
|
+
*
|
|
42
|
+
* Structural problems throw rather than being reported, the way a duplicate
|
|
43
|
+
* row id does elsewhere in this package: a cycle, a `needs` naming a step
|
|
44
|
+
* that does not exist, or progress naming a step that does not exist are all
|
|
45
|
+
* bugs in the spec or in the caller's bookkeeping, not answers about this
|
|
46
|
+
* particular record. In particular an unknown id in `completed` — a typo, or
|
|
47
|
+
* a leftover from an older revision of the flow — would silently re-run a
|
|
48
|
+
* step the caller believes is finished.
|
|
49
|
+
*/
|
|
50
|
+
export function planSequence(value, spec, state) {
|
|
51
|
+
const steps = spec.steps;
|
|
52
|
+
if (steps.length === 0)
|
|
53
|
+
throw new Error("the sequence has no steps");
|
|
54
|
+
const byId = new Map();
|
|
55
|
+
for (const step of steps) {
|
|
56
|
+
if (step.when && step.when.length === 0) {
|
|
57
|
+
throw new Error(`step "${step.id}" has an empty condition list — omit "when" for a step that is always due`);
|
|
58
|
+
}
|
|
59
|
+
if (byId.has(step.id))
|
|
60
|
+
throw new Error(`two steps share the id "${step.id}"`);
|
|
61
|
+
byId.set(step.id, step);
|
|
62
|
+
}
|
|
63
|
+
for (const step of steps) {
|
|
64
|
+
for (const need of step.needs ?? []) {
|
|
65
|
+
if (need === step.id)
|
|
66
|
+
throw new Error(`step "${step.id}" needs itself`);
|
|
67
|
+
if (!byId.has(need)) {
|
|
68
|
+
throw new Error(`step "${step.id}" needs "${need}", which is not a step in this sequence`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const completed = new Set(state.completed ?? []);
|
|
73
|
+
const failed = new Set(state.failed ?? []);
|
|
74
|
+
for (const [label, ids] of [
|
|
75
|
+
["completed", completed],
|
|
76
|
+
["failed", failed],
|
|
77
|
+
]) {
|
|
78
|
+
for (const id of ids) {
|
|
79
|
+
if (!byId.has(id)) {
|
|
80
|
+
throw new Error(`${label} names "${id}", which is not a step in this sequence`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
for (const id of completed) {
|
|
85
|
+
if (failed.has(id))
|
|
86
|
+
throw new Error(`step "${id}" is listed as both completed and failed`);
|
|
87
|
+
}
|
|
88
|
+
// Kahn's algorithm, which both orders the walk and finds the cycle. Every
|
|
89
|
+
// step's prerequisites are decided before the step itself, so a chain of
|
|
90
|
+
// unreachability propagates in one pass instead of needing a fixpoint.
|
|
91
|
+
const order = topologicalOrder(steps, byId);
|
|
92
|
+
const states = new Map();
|
|
93
|
+
const reasons = new Map();
|
|
94
|
+
for (const step of order) {
|
|
95
|
+
if (failed.has(step.id)) {
|
|
96
|
+
states.set(step.id, "failed");
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (completed.has(step.id)) {
|
|
100
|
+
states.set(step.id, "done");
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const dead = (step.needs ?? []).find((need) => {
|
|
104
|
+
const s = states.get(need);
|
|
105
|
+
return s === "failed" || s === "skipped" || s === "unreachable";
|
|
106
|
+
});
|
|
107
|
+
if (dead !== undefined) {
|
|
108
|
+
states.set(step.id, "unreachable");
|
|
109
|
+
reasons.set(step.id, {
|
|
110
|
+
id: step.id,
|
|
111
|
+
reason: `needs "${dead}", which is ${states.get(dead)}`,
|
|
112
|
+
});
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const pending = (step.needs ?? []).find((need) => states.get(need) !== "done");
|
|
116
|
+
if (pending !== undefined) {
|
|
117
|
+
states.set(step.id, "waiting");
|
|
118
|
+
reasons.set(step.id, { id: step.id, reason: `needs "${pending}", which has not run` });
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
// The `after` gate is read before the step's own conditions, and the
|
|
122
|
+
// conditions are read only once the prerequisites are done. Both orders
|
|
123
|
+
// are deliberate: a gate usually reads something an earlier step
|
|
124
|
+
// produced, so evaluating it out of turn would report a settled `skipped`
|
|
125
|
+
// about a context that does not exist yet.
|
|
126
|
+
if (step.afterMs !== undefined && step.afterMs > state.nowMs) {
|
|
127
|
+
states.set(step.id, "waiting");
|
|
128
|
+
reasons.set(step.id, {
|
|
129
|
+
id: step.id,
|
|
130
|
+
reason: `not due until ${new Date(step.afterMs).toISOString()}`,
|
|
131
|
+
dueInMs: step.afterMs - state.nowMs,
|
|
132
|
+
});
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// The shared evaluator reports a check it could not evaluate — a regex
|
|
136
|
+
// that will not compile, a malformed path — as a check that did not hold,
|
|
137
|
+
// with the reason as its text and no flag to tell the two apart. That is
|
|
138
|
+
// `Branch` and `RuleScore`'s behaviour too, and inventing a different one
|
|
139
|
+
// here would mean two answers to the same question. It bites harder in a
|
|
140
|
+
// sequence, because the skip cascades into `unreachable` dependents, so
|
|
141
|
+
// the reason travels with every one of them.
|
|
142
|
+
if (step.when && step.when.length > 0) {
|
|
143
|
+
const report = runChecks(value, step.when);
|
|
144
|
+
const holds = (step.match ?? "all") === "all" ? report.ok : report.passed > 0;
|
|
145
|
+
if (!holds) {
|
|
146
|
+
states.set(step.id, "skipped");
|
|
147
|
+
reasons.set(step.id, {
|
|
148
|
+
id: step.id,
|
|
149
|
+
reason: report.failures[0]?.reason ?? "no condition held",
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
states.set(step.id, "ready");
|
|
155
|
+
}
|
|
156
|
+
// Emitted in declared order, not in the topological order the walk used:
|
|
157
|
+
// the caller wrote the sequence down in the order they think about it, and
|
|
158
|
+
// a plan that came back reshuffled would read as a reordering.
|
|
159
|
+
const ready = [];
|
|
160
|
+
const waiting = [];
|
|
161
|
+
const unreachable = [];
|
|
162
|
+
const skipped = [];
|
|
163
|
+
const doneIds = [];
|
|
164
|
+
const failedIds = [];
|
|
165
|
+
for (const [index, step] of steps.entries()) {
|
|
166
|
+
const held = reasons.get(step.id) ?? { id: step.id, reason: "" };
|
|
167
|
+
switch (states.get(step.id)) {
|
|
168
|
+
case "ready":
|
|
169
|
+
ready.push(step.params === undefined
|
|
170
|
+
? { id: step.id, index }
|
|
171
|
+
: { id: step.id, index, params: step.params });
|
|
172
|
+
break;
|
|
173
|
+
case "waiting":
|
|
174
|
+
waiting.push(held);
|
|
175
|
+
break;
|
|
176
|
+
case "unreachable":
|
|
177
|
+
unreachable.push(held);
|
|
178
|
+
break;
|
|
179
|
+
case "skipped":
|
|
180
|
+
skipped.push(held);
|
|
181
|
+
break;
|
|
182
|
+
case "done":
|
|
183
|
+
doneIds.push(step.id);
|
|
184
|
+
break;
|
|
185
|
+
case "failed":
|
|
186
|
+
failedIds.push(step.id);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const planState = ready.length > 0
|
|
191
|
+
? "ready"
|
|
192
|
+
: waiting.length > 0
|
|
193
|
+
? "blocked"
|
|
194
|
+
: failedIds.length > 0 || unreachable.length > 0
|
|
195
|
+
? "halted"
|
|
196
|
+
: "finished";
|
|
197
|
+
return {
|
|
198
|
+
state: planState,
|
|
199
|
+
ready,
|
|
200
|
+
next: ready[0] ?? null,
|
|
201
|
+
waiting,
|
|
202
|
+
unreachable,
|
|
203
|
+
skipped,
|
|
204
|
+
completed: doneIds,
|
|
205
|
+
failed: failedIds,
|
|
206
|
+
version: spec.version ?? null,
|
|
207
|
+
total: steps.length,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
/** Steps ordered so that every step follows the steps it needs. */
|
|
211
|
+
function topologicalOrder(steps, byId) {
|
|
212
|
+
const remaining = new Map();
|
|
213
|
+
const dependents = new Map();
|
|
214
|
+
for (const step of steps) {
|
|
215
|
+
const needs = new Set(step.needs ?? []);
|
|
216
|
+
remaining.set(step.id, needs.size);
|
|
217
|
+
for (const need of needs) {
|
|
218
|
+
const list = dependents.get(need);
|
|
219
|
+
if (list)
|
|
220
|
+
list.push(step.id);
|
|
221
|
+
else
|
|
222
|
+
dependents.set(need, [step.id]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Seeded in declared order so the walk is stable, though only the
|
|
226
|
+
// dependency order it guarantees is relied on downstream.
|
|
227
|
+
const queue = steps.filter((s) => remaining.get(s.id) === 0).map((s) => s.id);
|
|
228
|
+
const ordered = [];
|
|
229
|
+
for (let head = 0; head < queue.length; head++) {
|
|
230
|
+
const id = queue[head];
|
|
231
|
+
ordered.push(byId.get(id));
|
|
232
|
+
for (const dependent of dependents.get(id) ?? []) {
|
|
233
|
+
const left = remaining.get(dependent) - 1;
|
|
234
|
+
remaining.set(dependent, left);
|
|
235
|
+
if (left === 0)
|
|
236
|
+
queue.push(dependent);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (ordered.length !== steps.length) {
|
|
240
|
+
const stuck = steps.filter((s) => !ordered.includes(s)).map((s) => `"${s.id}"`);
|
|
241
|
+
throw new Error(`steps ${stuck.join(", ")} depend on each other in a cycle`);
|
|
242
|
+
}
|
|
243
|
+
return ordered;
|
|
244
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether a loop is still making progress.
|
|
3
|
+
*
|
|
4
|
+
* A fix-test-fail loop that has stopped moving looks, from inside one turn,
|
|
5
|
+
* exactly like a loop that is about to succeed. The difference is only
|
|
6
|
+
* visible across turns, in whether the signals change — the failing-test
|
|
7
|
+
* fingerprint, the workspace diff hash, the last error, the tool call being
|
|
8
|
+
* made. This takes that history and answers the question.
|
|
9
|
+
*
|
|
10
|
+
* The history is an input. Nothing here persists anything, so the same
|
|
11
|
+
* history always gives the same verdict.
|
|
12
|
+
*/
|
|
13
|
+
export declare const STALL_REASONS: readonly ["progressing", "unchanged", "oscillating", "insufficient"];
|
|
14
|
+
export type StallReason = (typeof STALL_REASONS)[number];
|
|
15
|
+
/** One turn's observation: named signals, each an opaque fingerprint. */
|
|
16
|
+
export type Snapshot = Readonly<Record<string, string>>;
|
|
17
|
+
export type StallOptions = {
|
|
18
|
+
/** Identical snapshots needed in a row before calling it stalled. */
|
|
19
|
+
readonly window?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Longest repeating cycle to look for. A loop that alternates between two
|
|
22
|
+
* states is not "unchanged", but it is just as stuck.
|
|
23
|
+
*/
|
|
24
|
+
readonly maxCycle?: number;
|
|
25
|
+
/** Only these signals are considered, when given. */
|
|
26
|
+
readonly signals?: ReadonlyArray<string>;
|
|
27
|
+
};
|
|
28
|
+
export type StallReport = {
|
|
29
|
+
readonly stalled: boolean;
|
|
30
|
+
readonly reason: StallReason;
|
|
31
|
+
/** Consecutive identical snapshots at the end of the history. */
|
|
32
|
+
readonly repeats: number;
|
|
33
|
+
/** Length of the detected cycle; 1 means "unchanged". Null when progressing. */
|
|
34
|
+
readonly cycleLength: number | null;
|
|
35
|
+
/** Signals that changed between the last two snapshots. */
|
|
36
|
+
readonly changed: ReadonlyArray<string>;
|
|
37
|
+
/** Signals that have not changed anywhere in the history. */
|
|
38
|
+
readonly frozen: ReadonlyArray<string>;
|
|
39
|
+
readonly observed: number;
|
|
40
|
+
};
|
|
41
|
+
export declare function detectStall(history: ReadonlyArray<Snapshot>, options?: StallOptions): StallReport;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decide whether a loop is still making progress.
|
|
3
|
+
*
|
|
4
|
+
* A fix-test-fail loop that has stopped moving looks, from inside one turn,
|
|
5
|
+
* exactly like a loop that is about to succeed. The difference is only
|
|
6
|
+
* visible across turns, in whether the signals change — the failing-test
|
|
7
|
+
* fingerprint, the workspace diff hash, the last error, the tool call being
|
|
8
|
+
* made. This takes that history and answers the question.
|
|
9
|
+
*
|
|
10
|
+
* The history is an input. Nothing here persists anything, so the same
|
|
11
|
+
* history always gives the same verdict.
|
|
12
|
+
*/
|
|
13
|
+
export const STALL_REASONS = ["progressing", "unchanged", "oscillating", "insufficient"];
|
|
14
|
+
const DEFAULT_WINDOW = 3;
|
|
15
|
+
const DEFAULT_MAX_CYCLE = 3;
|
|
16
|
+
/**
|
|
17
|
+
* A snapshot reduced to a comparable string.
|
|
18
|
+
*
|
|
19
|
+
* Keys are sorted so that two snapshots with the same content compare equal
|
|
20
|
+
* whatever order they were built in, and the pairs are JSON rather than
|
|
21
|
+
* joined with a separator: a fingerprint is opaque caller-supplied text, and
|
|
22
|
+
* any separator could occur inside one, making two different snapshots
|
|
23
|
+
* collide.
|
|
24
|
+
*/
|
|
25
|
+
function fingerprint(snapshot, only) {
|
|
26
|
+
const keys = (only ?? Object.keys(snapshot)).slice().sort();
|
|
27
|
+
return JSON.stringify(keys.map((k) => [k, snapshot[k] ?? ""]));
|
|
28
|
+
}
|
|
29
|
+
export function detectStall(history, options = {}) {
|
|
30
|
+
const window = options.window ?? DEFAULT_WINDOW;
|
|
31
|
+
const maxCycle = options.maxCycle ?? DEFAULT_MAX_CYCLE;
|
|
32
|
+
if (window < 2)
|
|
33
|
+
throw new Error("window must be at least 2 — one snapshot cannot show a trend");
|
|
34
|
+
if (maxCycle < 1)
|
|
35
|
+
throw new Error("maxCycle must be at least 1");
|
|
36
|
+
const prints = history.map((s) => fingerprint(s, options.signals));
|
|
37
|
+
const observed = prints.length;
|
|
38
|
+
const changed = [];
|
|
39
|
+
if (observed >= 2) {
|
|
40
|
+
const a = history[observed - 2];
|
|
41
|
+
const b = history[observed - 1];
|
|
42
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
43
|
+
for (const key of [...keys].sort()) {
|
|
44
|
+
if (options.signals && !options.signals.includes(key))
|
|
45
|
+
continue;
|
|
46
|
+
if (a[key] !== b[key])
|
|
47
|
+
changed.push(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const frozen = [];
|
|
51
|
+
if (observed >= 2) {
|
|
52
|
+
const keys = new Set(history.flatMap((s) => Object.keys(s)));
|
|
53
|
+
for (const key of [...keys].sort()) {
|
|
54
|
+
if (options.signals && !options.signals.includes(key))
|
|
55
|
+
continue;
|
|
56
|
+
const first = history[0]?.[key];
|
|
57
|
+
if (history.every((s) => s[key] === first))
|
|
58
|
+
frozen.push(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Not enough history to say anything. Reporting "progressing" here would be
|
|
62
|
+
// a claim the data does not support, so it gets its own reason.
|
|
63
|
+
if (observed < window) {
|
|
64
|
+
return {
|
|
65
|
+
stalled: false,
|
|
66
|
+
reason: "insufficient",
|
|
67
|
+
repeats: 0,
|
|
68
|
+
cycleLength: null,
|
|
69
|
+
changed,
|
|
70
|
+
frozen,
|
|
71
|
+
observed,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
let repeats = 1;
|
|
75
|
+
for (let i = observed - 1; i > 0; i--) {
|
|
76
|
+
if (prints[i] === prints[i - 1])
|
|
77
|
+
repeats += 1;
|
|
78
|
+
else
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (repeats >= window) {
|
|
82
|
+
return {
|
|
83
|
+
stalled: true,
|
|
84
|
+
reason: "unchanged",
|
|
85
|
+
repeats,
|
|
86
|
+
cycleLength: 1,
|
|
87
|
+
changed,
|
|
88
|
+
frozen,
|
|
89
|
+
observed,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Oscillation: the tail repeats a cycle of length k, at least twice, and
|
|
93
|
+
// the cycle itself is not constant (that is "unchanged", already handled).
|
|
94
|
+
for (let k = 2; k <= maxCycle; k++) {
|
|
95
|
+
const needed = k * 2;
|
|
96
|
+
if (observed < needed || needed < window)
|
|
97
|
+
continue;
|
|
98
|
+
const tail = prints.slice(observed - needed);
|
|
99
|
+
const first = tail.slice(0, k);
|
|
100
|
+
const second = tail.slice(k);
|
|
101
|
+
const cycles = first.every((p, i) => p === second[i]);
|
|
102
|
+
const varied = new Set(first).size > 1;
|
|
103
|
+
if (cycles && varied) {
|
|
104
|
+
return {
|
|
105
|
+
stalled: true,
|
|
106
|
+
reason: "oscillating",
|
|
107
|
+
repeats,
|
|
108
|
+
cycleLength: k,
|
|
109
|
+
changed,
|
|
110
|
+
frozen,
|
|
111
|
+
observed,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
stalled: false,
|
|
117
|
+
reason: "progressing",
|
|
118
|
+
repeats,
|
|
119
|
+
cycleLength: null,
|
|
120
|
+
changed,
|
|
121
|
+
frozen,
|
|
122
|
+
observed,
|
|
123
|
+
};
|
|
124
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crewhaus/tool-flow",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Deterministic control-flow tools: branching, decision tables, error classification, deadlines, consensus, stall detection, rule scoring, record routing and sequence planning",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "bun test src"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@crewhaus/errors": "0.7.0",
|
|
19
|
+
"@crewhaus/tool-builder": "0.7.0",
|
|
20
|
+
"@crewhaus/tool-catalog": "0.7.0",
|
|
21
|
+
"@crewhaus/tool-schema": "0.7.0",
|
|
22
|
+
"zod": "^3.23.8"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@crewhaus/tool-executor": "workspace:*"
|
|
26
|
+
},
|
|
27
|
+
"license": "Apache-2.0",
|
|
28
|
+
"author": {
|
|
29
|
+
"name": "Max Meier",
|
|
30
|
+
"email": "max@crewhaus.ai",
|
|
31
|
+
"url": "https://crewhaus.ai"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/crewhaus/factory.git",
|
|
36
|
+
"directory": "packages/tool-flow"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://github.com/crewhaus/factory/tree/main/packages/tool-flow#readme",
|
|
39
|
+
"bugs": {
|
|
40
|
+
"url": "https://github.com/crewhaus/factory/issues"
|
|
41
|
+
},
|
|
42
|
+
"publishConfig": {
|
|
43
|
+
"access": "public"
|
|
44
|
+
},
|
|
45
|
+
"files": ["dist", "README.md", "LICENSE", "NOTICE"]
|
|
46
|
+
}
|