@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.
Files changed (45) hide show
  1. package/dist/{_bundler-B31MqYaD.mjs → _bundler-C31rlcYz.mjs} +1 -1
  2. package/dist/{_dev-server-Bo5dSBzQ.mjs → _dev-server-DWvkNoIS.mjs} +3 -3
  3. package/dist/{_vite-env-BNveawd1.mjs → _vite-env-D9Qj6KPX.mjs} +4 -2
  4. package/dist/_workflow-determinism-f9J-5Zlj.mjs +206 -0
  5. package/dist/_workflow-determinism.d.ts +118 -0
  6. package/dist/{build-DKC2e9o_.mjs → build-BLlLOrng.mjs} +4 -2
  7. package/dist/cli.mjs +372 -353
  8. package/dist/{client-bundler-CWnG42cU.mjs → client-bundler-j5c46x0G.mjs} +3 -2
  9. package/dist/client-bundler.mjs +1 -1
  10. package/dist/{deploy-CchW5pmw.mjs → deploy-BzZ-r4iM.mjs} +6 -4
  11. package/dist/{dev-BfVl1uBA.mjs → dev-BDykFvcc.mjs} +1 -1
  12. package/dist/{eval-DwNwdqmS.mjs → eval-0qEOdm0l.mjs} +1 -1
  13. package/dist/{init-BRfoc2EK.mjs → init-BtJI306C.mjs} +1 -1
  14. package/dist/{login-BBuM1sxH.mjs → login-DeUETobb.mjs} +4 -1
  15. package/dist/scaffold/CLAUDE.md +190 -11
  16. package/dist/scaffold/package.json +4 -4
  17. package/dist/{studio-wVWNLREn.mjs → studio-BxAS_FQQ.mjs} +4 -1
  18. package/dist/templates/call-audit/agent.test.ts +105 -26
  19. package/dist/templates/call-audit/workflows/audit.ts +9 -26
  20. package/dist/templates/dispatch-center/client.tsx +36 -2
  21. package/dist/templates/infocom-adventure/client.tsx +37 -9
  22. package/dist/templates/link-digest/agent.eval.test.ts +1 -1
  23. package/dist/templates/link-digest/agent.test.ts +164 -8
  24. package/dist/templates/link-digest/workflows/digest.ts +63 -20
  25. package/dist/templates/podcast-digest/agent.eval.test.ts +7 -4
  26. package/dist/templates/podcast-digest/agent.test.ts +265 -24
  27. package/dist/templates/podcast-digest/workflows/digest.ts +2 -2
  28. package/dist/templates/podcast-digest/workflows/feeds.ts +78 -73
  29. package/dist/templates/recap-workflow/agent.test.ts +206 -20
  30. package/dist/templates/recap-workflow/workflows/recap.ts +17 -14
  31. package/dist/templates/redline/agent.test.ts +137 -11
  32. package/dist/templates/research-workflow/agent.eval.test.ts +1 -1
  33. package/dist/templates/research-workflow/agent.test.ts +131 -9
  34. package/dist/templates/research-workflow/workflows/research.ts +1 -1
  35. package/dist/templates/retail/client.tsx +30 -2
  36. package/dist/templates/spoken-summary/agent.eval.test.ts +4 -1
  37. package/dist/templates/spoken-summary/agent.test.ts +117 -8
  38. package/dist/templates/spoken-summary/workflows/summarize.ts +1 -1
  39. package/dist/templates/transcription-workflow/agent.test.ts +127 -13
  40. package/dist/templates/transcription-workflow/workflows/batch.ts +6 -6
  41. package/dist/templates/transcription-workflow/workflows/stream.ts +4 -3
  42. package/dist/templates/transcription-workflow/workflows/transcribe.ts +4 -23
  43. package/dist/{test-CvwgeVSQ.mjs → test-CiLab-AA.mjs} +2 -1
  44. package/dist/worker-bundler.mjs +1 -1
  45. 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-CWnG42cU.mjs";
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-CWnG42cU.mjs";
5
- import { t as DEDUPED_PEERS } from "./_vite-env-BNveawd1.mjs";
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-B31MqYaD.mjs";
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) if (savedNodeEnv === void 0) delete env.NODE_ENV;
73
- else env.NODE_ENV = savedNodeEnv;
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-B31MqYaD.mjs";
4
+ import { r as evalWorkerBundle, t as buildAgentBundle } from "./_bundler-C31rlcYz.mjs";
5
5
  import { assertTypechecks } from "./_typecheck-gate-BXvUNnfs.mjs";
6
- import { classifyVitestError, runVitest } from "./test-CvwgeVSQ.mjs";
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");