Skip to content

Commit 1c0e82a

Browse files
authored
perf(ci): parallelize repo audits, guard env-dependent tests, and fix the docs generator (#6358)
* perf(ci): parallelize the repo audits and guard env-dependent tests The 21 independent audits ran as 21 sequential CI steps, each a single-threaded read-only walk of the tree. scripts/run-audits.ts runs them concurrently: 28s serial -> 5.0s wall locally at 13-way. It buffers each audit's output and replays only failures, so a green run stays quiet and a red one still names the audit and shows why. Audits needing a git base ref (block registry, migration safety) or that write files (drizzle generate) stay as their own steps. Also fixes 5 tests that fail for every macOS dev and are invisible in CI. They shell out to python3 using `match` statements and 3.12 f-string nesting, which need >= 3.10; stock macOS ships 3.9.6, so `bun run test` produced raw Python SyntaxErrors with no guard and nothing tying them to a missing tool. One also needs ripgrep, which CI installs and a Mac usually does not. @sim/testing/environment detects both and the tests skip with a reason via vitest's ctx.skip(). Under CI it throws instead: these suites deliberately run the real helper rather than a mock -- the cloud-review path/read-size bounds and the placeholder compiler's generated Python are only observable that way -- so a missing tool in CI means a security boundary silently stopped being covered, which is worse than a red build. Drops the Codecov upload. The workflow already documented it as a dead path: nothing generates apps/sim/coverage, vitest runs without --coverage, and fail_ci_if_error hides it, so it reported green having uploaded nothing. * fix(ci): raise the python floor to 3.12 and stop the bridge audit serializing the batch Two review findings, both real. MIN_PYTHON was 3.10, chosen for the `match` statements the compiler suite generates. But two of the three guarded tests also use PEP 701 f-strings -- reusing the outer quote, and embedding `#` -- which are 3.12. Verified on a real 3.11 interpreter: the match-guard test passes, the other two fail with `f-string: unmatched '('` and `f-string expression part cannot include '#'`, which is exactly the raw SyntaxError the guard exists to prevent. A 3.10 floor let them through and failed anyway. The audit parallelization did not speed CI up -- it slowed it down. Serially the 21 audits took ~31s; concurrently the batch took 39.2s wall, because check:desktop-bridge went from 1s to 39.2s and became the entire wall clock while the other 20 finished in 9s. It is the only audit that shells out through `bunx`, which re-resolves the package against the shared install cache -- a network-backed sticky-disk mount on CI. Cheap when it runs alone, serialized behind the others when they run together. Spawning the resolved compiler entry point directly removes that layer. Verified the audit still fails on a breaking bridge change rather than passing faster by doing less. * fix(docs): unbreak the MDX build and read trigger config from the registry The docs build has been failing on staging since the Smartlead merge: ./apps/docs/content/docs/en/integrations/smartlead.mdx Expected a closing tag for `<original>` before the end of `paragraph` Tool descriptions are emitted as prose, and that path escaped only braces -- every table-cell path already escaped angle brackets. MDX reads `<` as the start of a JSX tag, so a description like 'The copy is named "<original> - copy"' fails the build outright. escapeMdxProse handles the MDX-hostile characters and leaves pipes, parens and brackets alone, which are legal in prose and whose escaping would mangle markdown links. Trigger configuration now comes from the evaluated registry instead of regex over source. Static parsing silently dropped every field whose builder assembled its array imperatively or took a description as a parameter -- all ten Jira triggers lost `webhookSecret` and `jqlFilter` that way, and Monday lost its config too, so regenerating the docs was destructive. Reading real objects also deletes 232 lines of parsing. Note `required` may be a condition object rather than `true`; only an unconditional `true` renders as Required, matching the previous behavior. Tool headings now show the tool's name ("A2A Send Message") rather than its id (`a2a_send_message`), unformatted, across 241 generated pages. Names come from tools/generated/tool-metadata.ts, which CI keeps in sync. These headings feed each page's table of contents. a2a.mdx is hand-written, so its headings were updated directly. Also consolidates five hand-inlined copies of the escape chain into the escapeMdxCell that already existed, and drops 44 comments that restated the line below them. Generator: 4306 -> 4069 lines. Every refactor step was verified against a golden manifest of all 289 generated files -- proven deterministic across runs and proven to catch a one-character change -- so the only output differences are the intended ones. KNOWN GAP: extractTriggerOutputs still parses source and has the same blind spot; it already drops one Jira output section on main. Regenerating is now safe for trigger config but still lossy for trigger outputs. * refactor(ci): derive the audit list and stop shelling out through bunx Review pass over the audit runner and the tool guards. The audit list was hand-maintained alongside package.json with nothing linking them, and it had already drifted: check:cron-parity exists, passes, and ran in no CI step at all. The list is now derived from the check:* scripts with an explicit exclusion map, so a new audit is opted out deliberately rather than forgotten. That picks up cron-parity — 22 audits now, not 21. check-realtime-prune-graph.ts still shelled out through `bunx turbo`, the same pattern that took the bridge audit from 1s to 39s once the audits ran concurrently. Both now go through scripts/local-bin.ts, which resolves node_modules/.bin — the same path check:native-typecheck asserts is the native TypeScript 7 compiler, so the one guarded path is the one that runs. Audits are spawned as their script rather than `bun run <name>`, which started a bun process only to read package.json and start a second one. Tool detection is memoized per process; it was re-spawning python3 on each of the 5 call sites, in every vitest worker. The CI throw is deliberately NOT memoized — memoizing it would turn every call after the first into a silent skip, which is the failure mode the guard exists to prevent. Verified it still throws for all three guarded tests, not just the first. Also: dropped the environment module from the @sim/testing barrel so node:child_process stays out of unrelated consumers' module graphs, restored the per-audit reporting the 21 separate steps used to give (collapsible groups, error annotations, and a timing table they never had), and trimmed comments that restated their code or duplicated the runner's own docs. * fix(devin): give the 11 Devin tools real display names Every Devin tool had its id as its `name` (`list_session_messages`), so the generated docs rendered `### list_session_messages` where every other integration renders a human name. It was the only integration doing this -- 11 of 4427 tools. Names take the service prefix, matching the majority convention (3200 of 4416 names start with their service). Also points the ship skill at check:audits instead of hand-listing the audits. That copy had drifted five behind package.json: cron-parity, import-specifiers, sql-date-binding, trigger-block-cycle and native-typecheck were all missing, so shipping never ran them. It was the third copy of that list; there is now one. * fix(docs): read trigger outputs from the registry too Closes the gap left by the config fix: extractTriggerOutputs still parsed source, so triggers whose outputs come from a builder call lost their tables. jira_webhook had no output section at all. The registry was not a drop-in, which is why the naive swap deleted 10,298 lines earlier. The two sides encode nesting differently. A TriggerOutput marks a group by OMITTING type and holding children as sibling keys: issue: { id: { type: 'number' }, title: { type: 'string' } } while the renderer walks the JSON-Schema-ish shape the parser used to synthesize: issue: { type: 'object', properties: { id: …, title: … } } formatOutputStructure only descends into .properties, so handing it the raw registry value collapsed every nested group to one untyped row and dropped its children. normalizeTriggerOutputs converts between the two, preserving leaves that already declare properties/items and merging the 13 hybrid nodes that carry both a type and inline children. Measured across all 368 triggers before changing anything: 155 identical, 213 divergent, and the divergence was purely the nesting encoding — no node has a non-string type, and a group never carries its own string description, so leaf-vs-group classification is unambiguous. That is what makes a nested property literally named 'description' (42 of them) survive. Deletes the static path: extractTriggerOutputs, resolveTriggerBuilderFunction, resolveTriggerOutputsConstant, readTriggerSiblingModules, getWebhookProviderConstants, plus resolveConstStringValue and matchQuotedProperty which the config fix had already stranded. 20 output sections recovered (linear 79->93, tiktok 6->11, jira 44->45) and 1698 rows. Verified independently: zero sections lost across all 289 generated files, no file lost rows, output deterministic across regeneration. The 96 deletions are all corrections, not losses. 70 are confluence fields the parser flattened out of `comment: { ...buildContentEntityFields(), parent: {…} }` and rendered as top-level trigger outputs; they reappear nested under their parent in the same hunk. 8 are greenhouse key ordering, 6 are intercom descriptions the parser had dropped, 1 is a vercel row moving position. Generator: 4069 -> 3903 lines. * chore(test): silence vite 8 deprecation warnings in the sim vitest config @vitejs/plugin-react v4 targets pre-rolldown Vite: it sets `esbuild.jsx` and `optimizeDeps.rollupOptions`, both deprecated under Vite 8's oxc pipeline, and self-reports that plugin-react-oxc should be used instead. v6 is that plugin merged back under the original name — it requires Vite ^8, drops Babel entirely, and emits none of those options. Vite 8 also resolves tsconfig paths natively, so vite-tsconfig-paths is replaced by `resolve.tsconfigPaths`. Full apps/sim suite unchanged: 1483 passed / 2 skipped files, 20415 passed / 30 skipped tests. * refactor(docs): drop 33 more comments that restated their code Second pass over the generator, e.g. `// Copy icons from sim app to docs app` above `copyIconsFile()`. Kept the multi-line runs (those carry reasoning), the ones with concrete examples, and the one marking a deliberate empty catch. Verified byte-identical output across all 289 generated files. Generator: 3903 -> 3870 lines, 4306 at the start of this branch. * refactor(ci): read package.json once in the audit runner auditScripts() re-read the manifest the module body had already loaded. * fix(pdl): name the tools directory after the tool ids People Data Labs declared `pdl_*` tool ids under `tools/peopledatalabs/`. Every other integration names the directory after its id prefix -- 259 of 260 before this, and PDL was the only exception. The docs generator locates a tool's definition by deriving the directory from the id prefix, so it looked in `tools/pdl/`, found nothing, and returned null for all 11 tools. peopledatalabs.mdx rendered eleven bare `###` headings with no description, no Input table and no Output table. Renaming the directory rather than the ids: tool ids are persisted in saved workflows, so renaming those would break existing users. The directory is internal -- 15 files' imports. Fixed at the source rather than teaching the generator a fallback. A special case would have left the invariant broken and the next integration free to break it again; now 260 of 260 hold, and the generator needs no exception. peopledatalabs.mdx: 11 empty headings -> 456 lines. Repo-wide: zero pages with an empty action body.
1 parent 8694f55 commit 1c0e82a

290 files changed

Lines changed: 6332 additions & 5066 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/ship/SKILL.md

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,10 @@ When the user runs `/ship`:
6565
echo "❌ block registry audit failed — do not ship"
6666
exit 1
6767
}
68-
rm -f /tmp/ship-audit-results
69-
for s in check:boundaries check:api-validation:strict check:desktop-bridge check:desktop-ipc \
70-
check:utils check:zustand-v5 \
71-
check:react-query check:client-boundary check:bare-icons check:icon-paths \
72-
check:realtime-prune check:tool-registry-boundary check:tool-request-boundary \
73-
tool-metadata:check \
74-
integration-catalog:check skills:check agent-stream-docs:check; do
75-
( bun run "$s" >"/tmp/ship-audit-${s//:/-}.log" 2>&1; echo "$? $s" >>/tmp/ship-audit-results ) &
76-
done
77-
wait
78-
# any non-zero line is a failing audit — read its /tmp/ship-audit-<name>.log and fix before shipping.
79-
# `exit 1` on failure preserves the original sequential checks' semantics (their non-zero exit is
80-
# what an agent gates on); never use `grep … && echo ❌ || echo ✅` here — it always exits 0.
81-
if grep -vE '^0 ' /tmp/ship-audit-results; then echo "❌ audit(s) failed — do not ship"; exit 1; fi
82-
echo "✅ all audits passed"
68+
# Runs every audit CI runs, concurrently, and replays the output of any that fail.
69+
# Do not hand-list the audits here: the list is derived in scripts/run-audits.ts, and the
70+
# copy that used to live in this file had already drifted five audits behind package.json.
71+
bun run check:audits || { echo "❌ audit(s) failed — do not ship"; exit 1; }
8372
```
8473
If Phase A regenerated a file, its matching `:check` in Phase B now passes trivially — that parity is the point. Do not ship with any generator or audit failing; fix the cause (never silence it) and re-run. `check:migrations` and `type-check` are covered by steps 5 and CI respectively and are not repeated here.
8574
7. **Stage and commit** the changes with the generated message — including any files Phase A regenerated in step 6

.claude/commands/ship.md

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,10 @@ When the user runs `/ship`:
6464
echo "❌ block registry audit failed — do not ship"
6565
exit 1
6666
}
67-
rm -f /tmp/ship-audit-results
68-
for s in check:boundaries check:api-validation:strict check:desktop-bridge check:desktop-ipc \
69-
check:utils check:zustand-v5 \
70-
check:react-query check:client-boundary check:bare-icons check:icon-paths \
71-
check:realtime-prune check:tool-registry-boundary check:tool-request-boundary \
72-
tool-metadata:check \
73-
integration-catalog:check skills:check agent-stream-docs:check; do
74-
( bun run "$s" >"/tmp/ship-audit-${s//:/-}.log" 2>&1; echo "$? $s" >>/tmp/ship-audit-results ) &
75-
done
76-
wait
77-
# any non-zero line is a failing audit — read its /tmp/ship-audit-<name>.log and fix before shipping.
78-
# `exit 1` on failure preserves the original sequential checks' semantics (their non-zero exit is
79-
# what an agent gates on); never use `grep … && echo ❌ || echo ✅` here — it always exits 0.
80-
if grep -vE '^0 ' /tmp/ship-audit-results; then echo "❌ audit(s) failed — do not ship"; exit 1; fi
81-
echo "✅ all audits passed"
67+
# Runs every audit CI runs, concurrently, and replays the output of any that fail.
68+
# Do not hand-list the audits here: the list is derived in scripts/run-audits.ts, and the
69+
# copy that used to live in this file had already drifted five audits behind package.json.
70+
bun run check:audits || { echo "❌ audit(s) failed — do not ship"; exit 1; }
8271
```
8372
If Phase A regenerated a file, its matching `:check` in Phase B now passes trivially — that parity is the point. Do not ship with any generator or audit failing; fix the cause (never silence it) and re-run. `check:migrations` and `type-check` are covered by steps 5 and CI respectively and are not repeated here.
8473
7. **Stage and commit** the changes with the generated message — including any files Phase A regenerated in step 6

.cursor/commands/ship.md

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,10 @@ When the user runs `/ship`:
5959
echo "❌ block registry audit failed — do not ship"
6060
exit 1
6161
}
62-
rm -f /tmp/ship-audit-results
63-
for s in check:boundaries check:api-validation:strict check:desktop-bridge check:desktop-ipc \
64-
check:utils check:zustand-v5 \
65-
check:react-query check:client-boundary check:bare-icons check:icon-paths \
66-
check:realtime-prune check:tool-registry-boundary check:tool-request-boundary \
67-
tool-metadata:check \
68-
integration-catalog:check skills:check agent-stream-docs:check; do
69-
( bun run "$s" >"/tmp/ship-audit-${s//:/-}.log" 2>&1; echo "$? $s" >>/tmp/ship-audit-results ) &
70-
done
71-
wait
72-
# any non-zero line is a failing audit — read its /tmp/ship-audit-<name>.log and fix before shipping.
73-
# `exit 1` on failure preserves the original sequential checks' semantics (their non-zero exit is
74-
# what an agent gates on); never use `grep … && echo ❌ || echo ✅` here — it always exits 0.
75-
if grep -vE '^0 ' /tmp/ship-audit-results; then echo "❌ audit(s) failed — do not ship"; exit 1; fi
76-
echo "✅ all audits passed"
62+
# Runs every audit CI runs, concurrently, and replays the output of any that fail.
63+
# Do not hand-list the audits here: the list is derived in scripts/run-audits.ts, and the
64+
# copy that used to live in this file had already drifted five audits behind package.json.
65+
bun run check:audits || { echo "❌ audit(s) failed — do not ship"; exit 1; }
7766
```
7867
If Phase A regenerated a file, its matching `:check` in Phase B now passes trivially — that parity is the point. Do not ship with any generator or audit failing; fix the cause (never silence it) and re-run. `check:migrations` and `type-check` are covered by steps 5 and CI respectively and are not repeated here.
7968
7. **Stage and commit** the changes with the generated message — including any files Phase A regenerated in step 6

.github/workflows/test-build.yml

Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -117,70 +117,11 @@ jobs:
117117
- name: Lint code
118118
run: bun run lint:check
119119

120-
- name: Enforce monorepo boundaries
121-
run: bun run check:boundaries
122-
123-
- name: API contract boundary audit
124-
run: bun run check:api-validation:strict
125-
126-
- name: Desktop bridge contract audit
127-
run: bun run check:desktop-bridge
128-
129-
# Complements the bridge audit above, which compares against a snapshot
130-
# this same PR is allowed to regenerate. This one derives every fact from
131-
# the source both sides execute, so it has no such blind spot.
132-
- name: Desktop IPC contract audit
133-
run: bun run check:desktop-ipc
134-
135-
- name: Shared utils enforcement audit
136-
run: bun run check:utils
137-
138-
- name: Zustand v5 selector audit
139-
run: bun run check:zustand-v5
140-
141-
- name: React Query pattern audit
142-
run: bun run check:react-query
143-
144-
- name: Client boundary import audit
145-
run: bun run check:client-boundary
146-
147-
- name: Bare-icon theme-safety audit
148-
run: bun run check:bare-icons
149-
150-
- name: Icon SVG path validity audit
151-
run: bun run check:icon-paths
152-
153-
- name: Verify realtime prune graph
154-
run: bun run check:realtime-prune
155-
156-
- name: Tool registry client-boundary audit
157-
run: bun run check:tool-registry-boundary
158-
159-
- name: Tool request transport boundary audit
160-
run: bun run check:tool-request-boundary
161-
162-
- name: Trigger/block initialization cycle audit
163-
run: bun run check:trigger-block-cycle
164-
165-
# This job builds with webpack; devs run Turbopack. A specifier only webpack
166-
# resolves passes here and breaks every dev server.
167-
- name: Import specifier hygiene audit
168-
run: bun run check:import-specifiers
169-
170-
- name: SQL Date binding audit
171-
run: bun run check:sql-date-binding
172-
173-
- name: Verify generated tool metadata is in sync
174-
run: bun run tool-metadata:check
175-
176-
- name: Verify integration deployment metadata is in sync
177-
run: bun run integration-catalog:check
178-
179-
- name: Verify skill projections are in sync
180-
run: bun run skills:check
181-
182-
- name: Verify agent stream capability docs are in sync
183-
run: bun run agent-stream-docs:check
120+
# Every zero-argument `check:*` script, run concurrently. The list is derived in
121+
# scripts/run-audits.ts, which also writes the per-audit timing table to the job
122+
# summary and annotates failures. Audits needing a base ref stay separate below.
123+
- name: Repo audits
124+
run: bun run check:audits
184125

185126
- name: Migration safety (zero-downtime) audit
186127
run: |
@@ -192,12 +133,6 @@ jobs:
192133
fi
193134
bun run check:migrations "$BASE_REF"
194135
195-
# Runs before the type-check itself: if a bare `tsc` has fallen back to the
196-
# JavaScript TypeScript 6 compiler that `@typescript/typescript6` drags in
197-
# transitively, the type-check below still passes — it just takes ~10x longer.
198-
- name: Native type-check audit
199-
run: bun run check:native-typecheck
200-
201136
# Every workspace, not just realtime. packages/emcn, packages/utils,
202137
# apps/desktop and apps/docs had no type check in CI at all; apps/sim's
203138
# source was covered only as a side effect of `next build` in the separate
@@ -236,21 +171,6 @@ jobs:
236171
fi
237172
echo "✅ Schema and migrations are in sync"
238173
239-
# DEAD PATH: nothing generates `apps/sim/coverage`. The test step runs
240-
# `vitest run` without `--coverage`, and vitest.config.ts declares no
241-
# coverage provider, so this uploads nothing and still reports success in
242-
# ~1s (`fail_ci_if_error: false` hides it). `@vitest/coverage-v8` IS
243-
# installed, so wiring it up is possible — but coverage instrumentation
244-
# costs test time and nothing gates on the result today. Left in place
245-
# pending a decision to either enable coverage or drop this step; do not
246-
# read its green tick as "coverage was published".
247-
- name: Upload coverage to Codecov
248-
uses: codecov/codecov-action@0fb7174895f61a3b6b78fc075e0cd60383518dac # v5
249-
with:
250-
directory: ./apps/sim/coverage
251-
fail_ci_if_error: false
252-
verbose: true
253-
254174
# Next.js production build, in parallel with lint + tests. Sticky disks are
255175
# cloned from the last committed snapshot per job and committed last-writer-
256176
# wins, so concurrent mounts are safe. The bun/node_modules disks are shared

apps/docs/content/docs/en/integrations/a2a.mdx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Use the A2A (Agent-to-Agent) protocol to call external AI agents over the latest
2828

2929
## Tools
3030

31-
### `a2a_send_message`
31+
### A2A Send Message
3232

3333
Send a message to an external A2A agent and return its response.
3434

@@ -54,7 +54,7 @@ Send a message to an external A2A agent and return its response.
5454
| `state` | string | Task lifecycle state: `submitted`, `working`, `input-required`, `auth-required`, `completed`, `failed`, `canceled`, or `rejected` |
5555
| `artifacts` | array | Structured task output artifacts |
5656

57-
### `a2a_get_task`
57+
### A2A Get Task
5858

5959
Retrieve the current state and result of an A2A task.
6060

@@ -77,7 +77,7 @@ Retrieve the current state and result of an A2A task.
7777
| `state` | string | Task lifecycle state |
7878
| `artifacts` | array | Structured task output artifacts |
7979

80-
### `a2a_cancel_task`
80+
### A2A Cancel Task
8181

8282
Request cancellation of an in-progress A2A task.
8383

@@ -97,7 +97,7 @@ Request cancellation of an in-progress A2A task.
9797
| `state` | string | Task lifecycle state after cancellation |
9898
| `canceled` | boolean | Whether the task reached the canceled state |
9999

100-
### `a2a_get_agent_card`
100+
### A2A Get Agent Card
101101

102102
Fetch the Agent Card (discovery document) for an external A2A agent.
103103

0 commit comments

Comments
 (0)