feat(onboard): permanent client-onboarding package — templates + onboard command (#127) - #131
Merged
Merged
Conversation
Adds failing tests for:
- parseArgs onboard <org/repo> --vm <name> parsing + error cases
- runOnboard scaffold shape (all TEMPLATE_FILES, CLAUDE.md sync markers,
staging.env.example, PR URL in report)
- runOnboard app registration and trigger coverage verification
- runOnboard idempotency (re-run → updated, no duplicate PR or app record)
- renderTemplate {{RUNNER_TAG}} substitution
- TEMPLATE_FILES exported constant
All tests fail with "Cannot find module '../src/commands/onboard.ts'" —
the module does not exist yet (this is the RED commit).
Refs NikolayS#127
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the permanent, replicable client-onboarding package:
templates/client-repo/ (versioned in samohost):
- .github/workflows/ci.yml — generic client CI using a postgres:18 service
container; parameterised runner tag ({{RUNNER_TAG}}); NO field-record-1 NikolayS#117
AppArmor/host-PG workarounds
- .samohost.toml — standard client manifest template with all
canonical fields (name, repo, branch, appDir, buildCmd, migrateCmd,
healthUrl, serviceUnit, kind, dbBackend, mainHost)
- CLAUDE.md — carries SAMO-STACK:START/END + SAMO-DEV-PRINCIPLES
sync-block markers so client-project agents load principles + stack docs
- staging.env.example — standard env var shape documentation
src/commands/onboard.ts:
- runOnboard() — scaffolds templates/client-repo/ into the target repo as
a branch + PR (samo-agent identity via GITHUB_TOKEN), then calls
runAppRegister() to register the app in state, verifies trigger coverage
(app appears in appStore = trigger picks it up). Fully idempotent: re-run
updates files on existing branch, surfaces existing PR URL, upserts app
record without duplication.
- renderTemplate() — {{RUNNER_TAG}}/{{APP_NAME}}/{{REPO}}/{{APP_DB_NAME}}
substitution with sensible defaults
- TEMPLATE_FILES constant — canonical list of scaffolded paths
- defaultOnboardDeps() — live GitHub API deps (requires GITHUB_TOKEN)
src/cli.ts:
- parseArgs onboard <org/repo> --vm <name> [--runner-tag <tag>]
[--toml-path <path>]
- Dispatch case wired into main()
All 1374 tests pass, typecheck clean.
Deferred (not built now, per brief):
- Mechanising per-repo self-hosted runner registration (docs/runner-admin-handoff.md PAT flow)
- field-record cutover onto the canonical path
Closes NikolayS#127 (the package; rollout checklist in the issue remains open)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pins the expected behaviour for all 5 blockers from the samorev REQUEST_CHANGES: 1. EXIT-CODE HONESTY — test/onboard-exit-code.test.ts: imports `computeOnboardExitCode` (does not exist yet) and asserts it returns 1 for every partial-failure shape (appRegistered=false, triggerCovered=false, status="error") and 0 only on full success. 2. REAL CI.YML TEST — replaces the Array.isArray placeholder at test line 400 with a real readFileSync of the template and checks for absence of NikolayS#117, AppArmor, and host-PG markers. 3. CI.YML COMMENT — asserts the template comment does NOT say "repository variable" (it is a scaffold-time substitution, not a repo variable). 4. CI.YML DUPLICATE RUNS-ON — asserts the rendered default (RUNNER_TAG= "self-hosted") does not produce [self-hosted, "self-hosted"]. 5. RE-ONBOARD CLOBBER GUARD — asserts scaffoldFile is never called for .samohost.toml when the repo already has one. 6. findPr ERROR SURFACING — asserts defaultOnboardDeps().findPr throws on non-200 HTTP instead of silently returning null. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ikolayS#131) Root-cause fixes (not symptom guards): 1. EXIT-CODE HONESTY — cli.ts: Extract `computeOnboardExitCode(report)` and wire it into the onboard case. Returns 1 when `status==="error"` OR `appRegistered===false` OR `triggerCovered===false`. Previously the inline code only checked `status==="error"`, silently returning 0 when the PR was created but the app was not registered. 2. REAL CI.YML TEST (test-only) — test/onboard.test.ts: the placeholder `Array.isArray(TEMPLATE_FILES)` check at line 400 is replaced with a real `readFileSync` of templates/client-repo/.github/workflows/ci.yml asserting absence of `NikolayS#117`, `AppArmor`, and `host-PG` markers. 3. CI.YML TEMPLATE — templates/client-repo/.github/workflows/ci.yml: (a) Corrected the runs-on comment: RUNNER_TAG is a scaffold-time {{RUNNER_TAG}} substitution (performed by `samohost onboard`), NOT a GitHub repository variable. (b) Changed runs-on from `[self-hosted, "{{RUNNER_TAG}}"]` to `"{{RUNNER_TAG}}"`. The old form rendered `[self-hosted, "self-hosted"]` (duplicate label) when the default "self-hosted" was used. 4. RE-ONBOARD CLOBBER GUARD — src/commands/onboard.ts: Track whether `.samohost.toml` was fetched from the target repo (vs. generated from the template). If it was, skip `scaffoldFile` for `.samohost.toml` in the scaffold loop. The real manifest is still used for app registration; only the overwrite is suppressed. 5. findPr ERROR SURFACING — defaultOnboardDeps().findPr now throws `Error("GitHub API error ${status} ...")` on non-200 HTTP instead of silently returning null. A null return incorrectly routed to createPr, risking duplicate PRs on transient API failures. All 1382 tests pass; `bunx tsc --noEmit` clean. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
samo-agent
force-pushed
the
feat/client-onboard-package
branch
from
July 7, 2026 19:23
605e690 to
00888e6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #127 (the package itself; rollout checklist in the issue remains open).
Adds the permanent, replicable client-onboarding package so a new client repo inherits prod + previews + CI + stack knowledge with one command — no per-client dance.
What ships
templates/client-repo/— versioned in samohost; scaffolded into every new client repo:.github/workflows/ci.ymlTanya301/field-record-1but with all#117AppArmor / host-PG workarounds stripped. Uses apostgres:18service container. Runner tag parameterised as{{RUNNER_TAG}}(repo variable)..samohost.tomldocs/examples/.samohost.toml.example.CLAUDE.mdSAMO-STACK:START/END+SAMO-DEV-PRINCIPLES:START/ENDsync-block markers so client-project Claude agents load the stack docs and dev principles automatically.staging.env.examplesrc/commands/onboard.ts—samohost onboard <org/repo> --vm <name>:.samohost.tomlfrom the target repo (GitHub API) or reads a local--toml-path.templates/client-repo/into the target repo as asamohost-onboardingbranch.GITHUB_TOKEN).runAppRegister()to register the app in samohost state — equivalent toapp register --from-toml.appStore.list(), sosamohost-trigger.timerwill auto-deploy on every push tomain.src/cli.ts— wiresparseArgs onboard …and dispatches torunOnboard().TDD — RED → GREEN
09c534c: 20 failing tests (Cannot find module '../src/commands/onboard.ts'). Covers parseArgs, scaffold shape, app registration, trigger coverage, idempotency, template rendering.605e690: implementation; all 1374 tests pass, typecheck clean.Root cause / design
The manual onboarding checklist (adopt VM → register app → scaffold CI → open PR) had no code representation. Each new client required a bespoke sequence and produced subtly different CI configurations. This PR collapses that into a single idempotent command. All I/O is injected via
OnboardDepsso the flow is fully unit-tested offline.Deferred (not built — per brief)
docs/runner-admin-handoff.md).ci.ymltemplate (depends on feat(secret-scan): gitleaks CI job + PreToolUse secret-deny hook #117 being closed first).Test evidence
🤖 Generated with Claude Code