feat(flows): publish the generated Software Garden as a pinned artifact (1/2 for #174)
Part 1 of 2 for #174, where option (a) was decided: the /flows generator is the single source of Software Garden.
What
web/lib/software-garden-artifact.tsdefines the published draft andsoftwareGardenSource(), which isfactorySource(draft, 'cloud').web/public/flows/software-garden/v1.flow.tsis the generator output, 64,355 bytes,sha256:b89a36e2cf9054036a7fc53fba20011a42ef0275ffbaaa907b080738ab46ad27.web/public/flows/software-garden/manifest.jsonrecords{version, sha256, bytes, path, url, target, draft, versions[]}.web/scripts/publish-software-garden.mtscuts the next immutable version:cd web && npx -y tsx@4 scripts/publish-software-garden.mts.public/is now excluded fromtsc. The artifact imports@relayflows/surface, which this app does not install.
Which variant, and why
The draft is {sources:['github'], sourceSettings:{}, agents:['claude'], otherAgent:'', task:'', workflow:'traditional', step:3} with target cloud.
- Agents
['claude']. Cloud's prebuilt wizard (loadPrebuiltFlowHandoffin cloudflow-catalog-client.ts) deploys the catalog source verbatim and passesagents: entry.inputs.defaults.agents. That is["claude"], andallowedAgentsis["claude"]. Agent selection changes the bytes (claude,codexgivesdb1b9ed2…), so the artifact has to be the Claude-only variant. - Sources
['github']with empty settings. This is the entry'sdefaultTrigger. Repository and label filters do not change the cloud source (verified: identical hash with{repository, labels}set), because Cloud's listener applies them. - Workflow Traditional. This is the workflow the entry describes: implement, run checks outside the agent, adversarial review, then a PR for a human decision.
- Task empty. The catalog wizard hands off
task: "".
Tests (red, then green)
web/lib/test/software-garden-artifact.test.ts:
- The artifact equals
softwareGardenSource()byte for byte. - The manifest pins the version's sha256, size, path and url, and its draft equals the module's.
- Published versions are immutable: every
versions[]entry's file hashes to its recorded sha. - /flows handoff identity.
cloudConnectionsHref()for a GitHub + Claude Code + Traditional draft, with and without repo/label filters, carriespayload.sourceequal to the artifact bytes. - The draft equals the catalog entry's default agents and trigger.
Checked locally:
- Before the artifact existed: 4 failed.
- After publishing: 5 passed.
- After changing generator output: tests 1 and 4 fail.
- After overwriting v1 in place: tests 2 and 3 fail.
- After running the publish script (writes v2): 5 pass.
- Full suite: 45 files / 522 tests plus the router's 133 pass, and
tsc --noEmitis clean.
Why two PRs
The catalog's source is kind: "github", and Cloud fetches it browser-side from raw.githubusercontent.com at a full commit SHA. The pinned commit must therefore be on main before the catalog references it. PR 2/2 points flows.0 at AgentWorkforce/agentrelay.com@<this PR's merge commit>/web/public/flows/software-garden/v1.flow.ts with this sha256. It needs no Cloud change, since its contract already accepts any public GitHub owner/repo.
🤖 Generated with Claude Code
[!NOTE]
Low Risk
Adds static assets, a publish script, and tests without changing runtime deploy paths in this PR; catalog wiring is deferred to a follow-up.
Overview
Introduces a versioned, sha256-pinned Software Garden flow artifact so /flows onboarding, Cloud prebuilt wizards, and the recommended-flow catalog can all deploy the same generator bytes from public/flows/software-garden/.
Adds software-garden-artifact.ts with the canonical FactoryDraft (GitHub, Claude-only, Traditional, empty task) and softwareGardenSource() via factorySource(draft, 'cloud'), plus v1 (manifest.json + v1.flow.ts) and publish-software-garden.mts to cut the next immutable version when generator output changes. Tests assert byte identity with the generator, manifest pinning, per-version immutability, parity with cloudConnectionsHref handoff (including repo/label filters), and alignment with the software-factory catalog entry.
TypeScript now includes **/*.mts and excludes public/ so the published flow (which imports @relayflows/surface) does not break tsc.
Reviewed by Cursor Bugbot for commit 2044744cd0bbf5f3bcdbbf82f9ca6646d313f1f4. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Publishes the /flows generator's Software Garden output as a pinned, immutable artifact so every surface that deploys it shares the same bytes, and makes the publish script retry-safe.
- Adds the generator module, the published v1 file and manifest, a test suite, and the publish script;
public/and.mtsare now covered or excluded from TypeScript appropriately. - The manifest pins version, sha256, size, path, URL, target, and the draft that produced it;
softwareGardenSource()isfactorySource(draft, 'cloud')for the catalog's default selection (GitHub, Claude Code only, Traditional). - Five tests pin the artifact's hash, bytes, path, URL, and the catalog draft, verify it's byte-identical to what
/flowsonboarding hands to Cloud, and fail on a generator change until republished as a new version. - Publishing refuses to proceed while a recorded version is missing or edited, reuses an interrupted publish's file only when it matches, rewrites the manifest when bytes are unchanged, allows a digest to recur as a new version, and stages every write beside its target so an interrupted publish leaves no truncated file.
Agent Relay sessions
claudesessioneae07e25· opened viagh pr create· last active 2026-10-08