@alexkroman1/aai-cli 10.0.0 → 11.0.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/dist/{_bundler-B31MqYaD.mjs → _bundler-C31rlcYz.mjs} +1 -1
- package/dist/{_dev-server-Bo5dSBzQ.mjs → _dev-server-DWvkNoIS.mjs} +3 -3
- package/dist/{_vite-env-BNveawd1.mjs → _vite-env-D9Qj6KPX.mjs} +4 -2
- package/dist/_workflow-determinism-f9J-5Zlj.mjs +206 -0
- package/dist/_workflow-determinism.d.ts +118 -0
- package/dist/{build-DKC2e9o_.mjs → build-BLlLOrng.mjs} +4 -2
- package/dist/cli.mjs +372 -353
- package/dist/{client-bundler-CWnG42cU.mjs → client-bundler-j5c46x0G.mjs} +3 -2
- package/dist/client-bundler.mjs +1 -1
- package/dist/{deploy-CchW5pmw.mjs → deploy-BzZ-r4iM.mjs} +6 -4
- package/dist/{dev-BfVl1uBA.mjs → dev-BDykFvcc.mjs} +1 -1
- package/dist/{eval-DwNwdqmS.mjs → eval-0qEOdm0l.mjs} +1 -1
- package/dist/{init-BRfoc2EK.mjs → init-BtJI306C.mjs} +1 -1
- package/dist/{login-BBuM1sxH.mjs → login-DeUETobb.mjs} +4 -1
- package/dist/scaffold/CLAUDE.md +190 -11
- package/dist/scaffold/package.json +4 -4
- package/dist/{studio-wVWNLREn.mjs → studio-BxAS_FQQ.mjs} +4 -1
- package/dist/templates/call-audit/agent.test.ts +105 -26
- package/dist/templates/call-audit/workflows/audit.ts +9 -26
- package/dist/templates/dispatch-center/client.tsx +36 -2
- package/dist/templates/infocom-adventure/client.tsx +37 -9
- package/dist/templates/link-digest/agent.eval.test.ts +1 -1
- package/dist/templates/link-digest/agent.test.ts +164 -8
- package/dist/templates/link-digest/workflows/digest.ts +63 -20
- package/dist/templates/podcast-digest/agent.eval.test.ts +7 -4
- package/dist/templates/podcast-digest/agent.test.ts +265 -24
- package/dist/templates/podcast-digest/workflows/digest.ts +2 -2
- package/dist/templates/podcast-digest/workflows/feeds.ts +78 -73
- package/dist/templates/recap-workflow/agent.test.ts +206 -20
- package/dist/templates/recap-workflow/workflows/recap.ts +17 -14
- package/dist/templates/redline/agent.test.ts +137 -11
- package/dist/templates/research-workflow/agent.eval.test.ts +1 -1
- package/dist/templates/research-workflow/agent.test.ts +131 -9
- package/dist/templates/research-workflow/workflows/research.ts +1 -1
- package/dist/templates/retail/client.tsx +30 -2
- package/dist/templates/spoken-summary/agent.eval.test.ts +4 -1
- package/dist/templates/spoken-summary/agent.test.ts +117 -8
- package/dist/templates/spoken-summary/workflows/summarize.ts +1 -1
- package/dist/templates/transcription-workflow/agent.test.ts +127 -13
- package/dist/templates/transcription-workflow/workflows/batch.ts +6 -6
- package/dist/templates/transcription-workflow/workflows/stream.ts +4 -3
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +4 -23
- package/dist/{test-CvwgeVSQ.mjs → test-CiLab-AA.mjs} +2 -1
- package/dist/worker-bundler.mjs +1 -1
- package/package.json +25 -24
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { f as validateAgentExport } from "./_utils-B8QmtFhK.mjs";
|
|
3
|
-
import { t as buildClient } from "./client-bundler-
|
|
3
|
+
import { t as buildClient } from "./client-bundler-j5c46x0G.mjs";
|
|
4
4
|
import { buildWorker } from "./worker-bundler.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { i as outputSilenced, r as notify } from "./_ui-u7T4YooX.mjs";
|
|
3
3
|
import { i as errorCode, o as errorMessage$1 } from "./_utils-B8QmtFhK.mjs";
|
|
4
|
-
import { n as fallbackHtmlPlugin } from "./client-bundler-
|
|
5
|
-
import { t as DEDUPED_PEERS } from "./_vite-env-
|
|
4
|
+
import { n as fallbackHtmlPlugin } from "./client-bundler-j5c46x0G.mjs";
|
|
5
|
+
import { t as DEDUPED_PEERS } from "./_vite-env-D9Qj6KPX.mjs";
|
|
6
6
|
import { buildWorker } from "./worker-bundler.mjs";
|
|
7
7
|
import { typecheckProject } from "./typecheck.mjs";
|
|
8
8
|
import { n as ensureApiKey } from "./_config-CmJOFsAP.mjs";
|
|
9
|
-
import { n as createWorkerEvaluator } from "./_bundler-
|
|
9
|
+
import { n as createWorkerEvaluator } from "./_bundler-C31rlcYz.mjs";
|
|
10
10
|
import { t as resolveServerEnv } from "./_server-common-6e0QI4mq.mjs";
|
|
11
11
|
import { existsSync, statSync } from "node:fs";
|
|
12
12
|
import path from "node:path";
|
|
@@ -69,8 +69,10 @@ async function withPreservedNodeEnv(fn, env = process.env) {
|
|
|
69
69
|
return await fn();
|
|
70
70
|
} finally {
|
|
71
71
|
activeBuilds--;
|
|
72
|
-
if (activeBuilds === 0)
|
|
73
|
-
|
|
72
|
+
if (activeBuilds === 0) {
|
|
73
|
+
if (savedNodeEnv === void 0) delete env.NODE_ENV;
|
|
74
|
+
else env.NODE_ENV = savedNodeEnv;
|
|
75
|
+
}
|
|
74
76
|
}
|
|
75
77
|
}
|
|
76
78
|
//#endregion
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFile, readdir } from "node:fs/promises";
|
|
4
|
+
//#region _workflow-determinism.ts
|
|
5
|
+
/**
|
|
6
|
+
* A COMPUTED step identity in the project's own `workflows/`.
|
|
7
|
+
*
|
|
8
|
+
* `guard-invariants` rule 32 is this same check over the repo's own shipped
|
|
9
|
+
* template bodies; this is it pointed at a USER's project, at `aai build` and
|
|
10
|
+
* `aai deploy`. The two exist together because the gate holds the examples and
|
|
11
|
+
* held nothing written from them.
|
|
12
|
+
*
|
|
13
|
+
* It is also the half of rule 30 ("no clock, random number, uuid or network
|
|
14
|
+
* read at body level") that ports at all — and the half the type system
|
|
15
|
+
* provably leaves open.
|
|
16
|
+
*
|
|
17
|
+
* ## What it catches, and why the type does not
|
|
18
|
+
*
|
|
19
|
+
* `ctx.step`, `ctx.sleep` and `ctx.waitFor` all constrain their identity with
|
|
20
|
+
* `Literal<Name>` (`string extends Name ? never : Name`), which rejects a name
|
|
21
|
+
* that has widened to `string`. **A template literal passes it**: the type of
|
|
22
|
+
* `` `charge-${coin}` `` is a template-literal type, not `string`, so this
|
|
23
|
+
* compiles — and it is checked rather than claimed, `check:doc-examples`
|
|
24
|
+
* compiling every fence in this repo's shipped docs:
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* import type { WorkflowCtx } from "@alexkroman1/aai";
|
|
28
|
+
*
|
|
29
|
+
* declare const charge: () => Promise<string>;
|
|
30
|
+
*
|
|
31
|
+
* export async function body(ctx: WorkflowCtx, coin: string): Promise<void> {
|
|
32
|
+
* // No error. The banned shape, against the real `WorkflowCtx`.
|
|
33
|
+
* await ctx.step(`charge-${coin}`, charge);
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*
|
|
37
|
+
* And it is exactly
|
|
38
|
+
* the shape of the engine's own measured defect: a body-level `Math.random()`
|
|
39
|
+
* feeding a step name executed the side effect twice in **7 of 10 runs, with
|
|
40
|
+
* all 10 reporting `completed`** (`aai-runtime/workflow-replay-divergence.ts`).
|
|
41
|
+
* Substitute "charge the customer" for the side effect.
|
|
42
|
+
*
|
|
43
|
+
* A computed identity has no legitimate use in this engine, which is what makes
|
|
44
|
+
* the check cheap AND precise. Identity is `(name, occurrence)`: a fan-out
|
|
45
|
+
* reuses ONE name and the per-name occurrence counter distinguishes the calls,
|
|
46
|
+
* so every step in all fourteen shipped templates — the seven-way transcription
|
|
47
|
+
* fan-out included — is a plain string literal. The scan finds zero occurrences
|
|
48
|
+
* across them.
|
|
49
|
+
*
|
|
50
|
+
* ## Why rule 30's OTHER half is deliberately not ported
|
|
51
|
+
*
|
|
52
|
+
* That half scans for the reads themselves, anywhere in a `workflows/` file,
|
|
53
|
+
* and it pays for the breadth with seven baselined occurrences in this repo.
|
|
54
|
+
* Measured before writing this: a faithful port reports **all seven and nothing
|
|
55
|
+
* else** — and all seven are correct code. Each is a read inside a step-called
|
|
56
|
+
* helper (`timed`, `pollTranscript`, `file`), which `link-digest`'s own comment
|
|
57
|
+
* explains: "the `ctx.step` callback boundary is not decidable from a line …
|
|
58
|
+
* Anything at BODY level is the bug, not an exception."
|
|
59
|
+
*
|
|
60
|
+
* A user's project has no baseline to carry, so that port would be a 100%
|
|
61
|
+
* false-positive rate on the only corpus anyone can measure — and a checker
|
|
62
|
+
* that is always wrong is one an author learns to scroll past, which costs the
|
|
63
|
+
* precise finding below as well as itself. Deciding that boundary needs a real
|
|
64
|
+
* parse; the repo does that with `oxc-parser`
|
|
65
|
+
* (`scripts/_test-assertions-parse.mjs`, whose doc argues it against the ~140
|
|
66
|
+
* lines of hand-written lexer it replaced), and a native parser cannot join a
|
|
67
|
+
* published CLI's runtime dependencies — a new one fails the artifact-size
|
|
68
|
+
* budget on its own, regardless of bytes. So the reads half stays in the repo
|
|
69
|
+
* gate, where the baseline mechanism it needs already exists.
|
|
70
|
+
*
|
|
71
|
+
* ## It WARNS
|
|
72
|
+
*
|
|
73
|
+
* `assertTypechecks` throws and `aai build` stops. This does not, because one
|
|
74
|
+
* shape is legitimate: a name interpolating a CONSTANT (`` `${PREFIX}-fetch` ``
|
|
75
|
+
* over a `const` string) is the same on every walk. It is rare enough not to
|
|
76
|
+
* shape the message and real enough not to fail a build over.
|
|
77
|
+
*
|
|
78
|
+
* Same posture and the same call site as `agentConfigWarnings`, whose comment
|
|
79
|
+
* in `build.ts` states it: "Legal, and worth saying".
|
|
80
|
+
*
|
|
81
|
+
* @module
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* The three `ctx` methods whose first argument is a journal identity.
|
|
85
|
+
*
|
|
86
|
+
* All three key a journal row by it — `name#occurrence` for a step,
|
|
87
|
+
* `sleep!<label>#<n>` and `hook!<token>#<n>` for the waits — so a computed one
|
|
88
|
+
* mints a row no earlier walk reached in exactly the same way. Kept as a list
|
|
89
|
+
* so the message can name the method that was called.
|
|
90
|
+
*/
|
|
91
|
+
const IDENTITY_METHODS = [
|
|
92
|
+
"step",
|
|
93
|
+
"sleep",
|
|
94
|
+
"waitFor"
|
|
95
|
+
];
|
|
96
|
+
/**
|
|
97
|
+
* A `ctx.<method>(` immediately followed by a BACKTICK.
|
|
98
|
+
*
|
|
99
|
+
* The whole check, and its narrowness is the point. A quoted literal is
|
|
100
|
+
* accepted; a bare identifier is already the type system's job (`Literal<Name>`
|
|
101
|
+
* refuses one that widened to `string`, and one that did not is a `const`
|
|
102
|
+
* literal and deterministic); a template literal is what neither of them sees.
|
|
103
|
+
*
|
|
104
|
+
* The receiver is not pinned to `ctx` — a body may destructure or rename it —
|
|
105
|
+
* and a `.step(` on anything else taking a template-literal first argument is
|
|
106
|
+
* not a shape worth excluding.
|
|
107
|
+
*/
|
|
108
|
+
const identityPattern = () => new RegExp(`\\.(${IDENTITY_METHODS.join("|")})\\(\``, "g");
|
|
109
|
+
/** Whether a template literal actually INTERPOLATES, or is merely quoted oddly. */
|
|
110
|
+
const INTERPOLATES = /\$\{/;
|
|
111
|
+
/**
|
|
112
|
+
* Whether this line is only a comment.
|
|
113
|
+
*
|
|
114
|
+
* A method NAMED IN PROSE is prose: this module's own doc shows the banned
|
|
115
|
+
* shape, and rule 30 carries the same skip for the same reason. Block-comment
|
|
116
|
+
* continuations (`*`) count, which is what covers a paragraph rather than only
|
|
117
|
+
* its first line.
|
|
118
|
+
*/
|
|
119
|
+
function isCommentOnly(line) {
|
|
120
|
+
const trimmed = line.trimStart();
|
|
121
|
+
return trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*");
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Every computed identity in one source file.
|
|
125
|
+
*
|
|
126
|
+
* The interpolation is looked for in the REST of the line rather than inside a
|
|
127
|
+
* balanced template: a template-literal name long enough to wrap is not a shape
|
|
128
|
+
* this needs to resolve, and reporting one whose `${` sits on the next line is
|
|
129
|
+
* the safer direction for a warning.
|
|
130
|
+
*/
|
|
131
|
+
function findComputedIdentities(source, file) {
|
|
132
|
+
const findings = [];
|
|
133
|
+
for (const [n, line] of source.split("\n").entries()) {
|
|
134
|
+
if (isCommentOnly(line)) continue;
|
|
135
|
+
for (const match of line.matchAll(identityPattern())) {
|
|
136
|
+
const method = match[1];
|
|
137
|
+
if (method === void 0) continue;
|
|
138
|
+
if (!INTERPOLATES.test(line.slice((match.index ?? 0) + match[0].length))) continue;
|
|
139
|
+
findings.push({
|
|
140
|
+
file,
|
|
141
|
+
line: n + 1,
|
|
142
|
+
method,
|
|
143
|
+
text: line.trim()
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return findings;
|
|
148
|
+
}
|
|
149
|
+
/** Every workflow body under `cwd`, or nothing when the project declares none. */
|
|
150
|
+
async function workflowFiles(cwd) {
|
|
151
|
+
const dir = path.join(cwd, "workflows");
|
|
152
|
+
let entries;
|
|
153
|
+
try {
|
|
154
|
+
entries = await readdir(dir);
|
|
155
|
+
} catch {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
return entries.filter((name) => name.endsWith(".ts")).filter((name) => !(name.endsWith(".test.ts") || name.endsWith(".test-d.ts"))).map((name) => path.join("workflows", name));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Scan the project's workflow bodies.
|
|
162
|
+
*
|
|
163
|
+
* FLAT, matching both the shape every template uses and rule 30's own pathspec
|
|
164
|
+
* (`templates/*` + `/workflows/*.ts`). A nested file is missed, which is the
|
|
165
|
+
* same gap the rule has; widening both together is the change to make, rather
|
|
166
|
+
* than widening one and having the two disagree about their own corpus.
|
|
167
|
+
*
|
|
168
|
+
* A file that will not READ is skipped rather than failing the build: this is a
|
|
169
|
+
* warning pass beside a typecheck that has already run on the same tree.
|
|
170
|
+
*/
|
|
171
|
+
async function scanWorkflowDeterminism(cwd) {
|
|
172
|
+
const findings = [];
|
|
173
|
+
for (const rel of await workflowFiles(cwd)) {
|
|
174
|
+
let source;
|
|
175
|
+
try {
|
|
176
|
+
source = await readFile(path.join(cwd, rel), "utf-8");
|
|
177
|
+
} catch {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
findings.push(...findComputedIdentities(source, rel));
|
|
181
|
+
}
|
|
182
|
+
return findings;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* The remedy, once, however many findings there are.
|
|
186
|
+
*
|
|
187
|
+
* Repeating it per finding is how a warning becomes a wall an author scrolls
|
|
188
|
+
* past, and the fix is the same for all three methods. It names the OCCURRENCE
|
|
189
|
+
* counter, because "use a literal" alone reads as a restriction on fan-outs —
|
|
190
|
+
* the one case an author will think they need this for, and the case the
|
|
191
|
+
* counter already handles.
|
|
192
|
+
*/
|
|
193
|
+
const REMEDY = "A workflow identity is a journal KEY, and a body is replayed — so a computed one mints a key no earlier walk reached, and the engine either re-executes the step or refuses the run. Measured on a one-line body: 7 of 10 runs ran the side effect twice, all 10 reporting completed. Use a plain string literal. A fan-out does not need a unique name: identity is (name, occurrence), so N calls under one name are N distinct rows — ctx.step(\"transcribeSegment\", …) inside the loop is right.";
|
|
194
|
+
/**
|
|
195
|
+
* One warning line per finding, plus the remedy — or nothing at all.
|
|
196
|
+
*
|
|
197
|
+
* Shaped as strings rather than printed here for the reason every reporter in
|
|
198
|
+
* this package takes an injected one: `build` and `deploy` notify, and a spec
|
|
199
|
+
* reads what an author would have seen without capturing stdout.
|
|
200
|
+
*/
|
|
201
|
+
function determinismWarnings(findings) {
|
|
202
|
+
if (findings.length === 0) return [];
|
|
203
|
+
return [...findings.map((found) => `${found.file}:${found.line} computes a ctx.${found.method} identity: ${found.text}`), REMEDY];
|
|
204
|
+
}
|
|
205
|
+
//#endregion
|
|
206
|
+
export { scanWorkflowDeterminism as n, determinismWarnings as t };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A COMPUTED step identity in the project's own `workflows/`.
|
|
3
|
+
*
|
|
4
|
+
* `guard-invariants` rule 32 is this same check over the repo's own shipped
|
|
5
|
+
* template bodies; this is it pointed at a USER's project, at `aai build` and
|
|
6
|
+
* `aai deploy`. The two exist together because the gate holds the examples and
|
|
7
|
+
* held nothing written from them.
|
|
8
|
+
*
|
|
9
|
+
* It is also the half of rule 30 ("no clock, random number, uuid or network
|
|
10
|
+
* read at body level") that ports at all — and the half the type system
|
|
11
|
+
* provably leaves open.
|
|
12
|
+
*
|
|
13
|
+
* ## What it catches, and why the type does not
|
|
14
|
+
*
|
|
15
|
+
* `ctx.step`, `ctx.sleep` and `ctx.waitFor` all constrain their identity with
|
|
16
|
+
* `Literal<Name>` (`string extends Name ? never : Name`), which rejects a name
|
|
17
|
+
* that has widened to `string`. **A template literal passes it**: the type of
|
|
18
|
+
* `` `charge-${coin}` `` is a template-literal type, not `string`, so this
|
|
19
|
+
* compiles — and it is checked rather than claimed, `check:doc-examples`
|
|
20
|
+
* compiling every fence in this repo's shipped docs:
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* import type { WorkflowCtx } from "@alexkroman1/aai";
|
|
24
|
+
*
|
|
25
|
+
* declare const charge: () => Promise<string>;
|
|
26
|
+
*
|
|
27
|
+
* export async function body(ctx: WorkflowCtx, coin: string): Promise<void> {
|
|
28
|
+
* // No error. The banned shape, against the real `WorkflowCtx`.
|
|
29
|
+
* await ctx.step(`charge-${coin}`, charge);
|
|
30
|
+
* }
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* And it is exactly
|
|
34
|
+
* the shape of the engine's own measured defect: a body-level `Math.random()`
|
|
35
|
+
* feeding a step name executed the side effect twice in **7 of 10 runs, with
|
|
36
|
+
* all 10 reporting `completed`** (`aai-runtime/workflow-replay-divergence.ts`).
|
|
37
|
+
* Substitute "charge the customer" for the side effect.
|
|
38
|
+
*
|
|
39
|
+
* A computed identity has no legitimate use in this engine, which is what makes
|
|
40
|
+
* the check cheap AND precise. Identity is `(name, occurrence)`: a fan-out
|
|
41
|
+
* reuses ONE name and the per-name occurrence counter distinguishes the calls,
|
|
42
|
+
* so every step in all fourteen shipped templates — the seven-way transcription
|
|
43
|
+
* fan-out included — is a plain string literal. The scan finds zero occurrences
|
|
44
|
+
* across them.
|
|
45
|
+
*
|
|
46
|
+
* ## Why rule 30's OTHER half is deliberately not ported
|
|
47
|
+
*
|
|
48
|
+
* That half scans for the reads themselves, anywhere in a `workflows/` file,
|
|
49
|
+
* and it pays for the breadth with seven baselined occurrences in this repo.
|
|
50
|
+
* Measured before writing this: a faithful port reports **all seven and nothing
|
|
51
|
+
* else** — and all seven are correct code. Each is a read inside a step-called
|
|
52
|
+
* helper (`timed`, `pollTranscript`, `file`), which `link-digest`'s own comment
|
|
53
|
+
* explains: "the `ctx.step` callback boundary is not decidable from a line …
|
|
54
|
+
* Anything at BODY level is the bug, not an exception."
|
|
55
|
+
*
|
|
56
|
+
* A user's project has no baseline to carry, so that port would be a 100%
|
|
57
|
+
* false-positive rate on the only corpus anyone can measure — and a checker
|
|
58
|
+
* that is always wrong is one an author learns to scroll past, which costs the
|
|
59
|
+
* precise finding below as well as itself. Deciding that boundary needs a real
|
|
60
|
+
* parse; the repo does that with `oxc-parser`
|
|
61
|
+
* (`scripts/_test-assertions-parse.mjs`, whose doc argues it against the ~140
|
|
62
|
+
* lines of hand-written lexer it replaced), and a native parser cannot join a
|
|
63
|
+
* published CLI's runtime dependencies — a new one fails the artifact-size
|
|
64
|
+
* budget on its own, regardless of bytes. So the reads half stays in the repo
|
|
65
|
+
* gate, where the baseline mechanism it needs already exists.
|
|
66
|
+
*
|
|
67
|
+
* ## It WARNS
|
|
68
|
+
*
|
|
69
|
+
* `assertTypechecks` throws and `aai build` stops. This does not, because one
|
|
70
|
+
* shape is legitimate: a name interpolating a CONSTANT (`` `${PREFIX}-fetch` ``
|
|
71
|
+
* over a `const` string) is the same on every walk. It is rare enough not to
|
|
72
|
+
* shape the message and real enough not to fail a build over.
|
|
73
|
+
*
|
|
74
|
+
* Same posture and the same call site as `agentConfigWarnings`, whose comment
|
|
75
|
+
* in `build.ts` states it: "Legal, and worth saying".
|
|
76
|
+
*
|
|
77
|
+
* @module
|
|
78
|
+
*/
|
|
79
|
+
/** One computed identity the scan found. */
|
|
80
|
+
export type DeterminismFinding = {
|
|
81
|
+
/** Relative to the project root, so a message is copy-pasteable. */
|
|
82
|
+
file: string;
|
|
83
|
+
/** 1-indexed, so `file:line` opens in an editor. */
|
|
84
|
+
line: number;
|
|
85
|
+
/** `step`, `sleep` or `waitFor` — which identity this is. */
|
|
86
|
+
method: string;
|
|
87
|
+
/** The source line, trimmed — what makes the warning actionable unopened. */
|
|
88
|
+
text: string;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Every computed identity in one source file.
|
|
92
|
+
*
|
|
93
|
+
* The interpolation is looked for in the REST of the line rather than inside a
|
|
94
|
+
* balanced template: a template-literal name long enough to wrap is not a shape
|
|
95
|
+
* this needs to resolve, and reporting one whose `${` sits on the next line is
|
|
96
|
+
* the safer direction for a warning.
|
|
97
|
+
*/
|
|
98
|
+
export declare function findComputedIdentities(source: string, file: string): DeterminismFinding[];
|
|
99
|
+
/**
|
|
100
|
+
* Scan the project's workflow bodies.
|
|
101
|
+
*
|
|
102
|
+
* FLAT, matching both the shape every template uses and rule 30's own pathspec
|
|
103
|
+
* (`templates/*` + `/workflows/*.ts`). A nested file is missed, which is the
|
|
104
|
+
* same gap the rule has; widening both together is the change to make, rather
|
|
105
|
+
* than widening one and having the two disagree about their own corpus.
|
|
106
|
+
*
|
|
107
|
+
* A file that will not READ is skipped rather than failing the build: this is a
|
|
108
|
+
* warning pass beside a typecheck that has already run on the same tree.
|
|
109
|
+
*/
|
|
110
|
+
export declare function scanWorkflowDeterminism(cwd: string): Promise<DeterminismFinding[]>;
|
|
111
|
+
/**
|
|
112
|
+
* One warning line per finding, plus the remedy — or nothing at all.
|
|
113
|
+
*
|
|
114
|
+
* Shaped as strings rather than printed here for the reason every reporter in
|
|
115
|
+
* this package takes an injected one: `build` and `deploy` notify, and a spec
|
|
116
|
+
* reads what an author would have seen without capturing stdout.
|
|
117
|
+
*/
|
|
118
|
+
export declare function determinismWarnings(findings: readonly DeterminismFinding[]): string[];
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { a as ok, t as CliError } from "./_output-CKkmWs7i.mjs";
|
|
3
3
|
import { n as log, r as notify } from "./_ui-u7T4YooX.mjs";
|
|
4
|
-
import { r as evalWorkerBundle, t as buildAgentBundle } from "./_bundler-
|
|
4
|
+
import { r as evalWorkerBundle, t as buildAgentBundle } from "./_bundler-C31rlcYz.mjs";
|
|
5
5
|
import { assertTypechecks } from "./_typecheck-gate-BXvUNnfs.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { n as scanWorkflowDeterminism, t as determinismWarnings } from "./_workflow-determinism-f9J-5Zlj.mjs";
|
|
7
|
+
import { classifyVitestError, runVitest } from "./test-CiLab-AA.mjs";
|
|
7
8
|
import path from "node:path";
|
|
8
9
|
import fs from "node:fs/promises";
|
|
9
10
|
import { agentConfigWarnings } from "@alexkroman1/aai/manifest";
|
|
@@ -50,6 +51,7 @@ async function executeBuild(opts) {
|
|
|
50
51
|
const bundle = await buildAgentBundle(cwd, { minify: true });
|
|
51
52
|
const agentDef = await evalWorkerBundle(bundle.worker);
|
|
52
53
|
for (const warning of agentConfigWarnings(agentDef)) notify("warn", warning);
|
|
54
|
+
for (const warning of determinismWarnings(await scanWorkflowDeterminism(cwd))) notify("warn", warning);
|
|
53
55
|
const worker = path.join(cwd, WORKER_ARTIFACT_REL);
|
|
54
56
|
await fs.mkdir(path.dirname(worker), { recursive: true });
|
|
55
57
|
await fs.writeFile(worker, bundle.worker, "utf-8");
|