Skip to content

feat: add fullsend CLI with install command (PoC for Story 1) - #132

Closed
ralphbean wants to merge 36 commits into
mainfrom
agent-124-fullsend-cli-poc
Closed

feat: add fullsend CLI with install command (PoC for Story 1)#132
ralphbean wants to merge 36 commits into
mainfrom
agent-124-fullsend-cli-poc

Conversation

@ralphbean

Copy link
Copy Markdown
Member

Summary

Implements a Go CLI tool (fullsend) that demonstrates the installation and bootstrap workflow described in #124. This is a PoC using a fake GitHub client — the Client interface is designed for easy replacement with a real implementation.

What's included

  • fullsend install <org> command with --repo, --agents, and --dry-run flags
  • GitHub App configuration with minimum required permissions (issues r/w, PRs r/w, checks read, contents write)
  • .fullsend config repo creation with safe defaults (auto_merge: false, all repos disabled)
  • Enrollment PR generation for enabled repositories with stub workflow files
  • Reusable GitHub Actions workflow generation for agent dispatch
  • CODEOWNERS file generation protecting config from agent modification
  • Beautiful CLI output using charmbracelet/lipgloss (styled progress, summary box, PR links)

Testing & CI

  • 46 unit tests across 5 packages (92-100% coverage)
  • golangci-lint configuration with revive, errcheck, govet, staticcheck, and more
  • Makefile targets: go-build, go-test, go-lint, go-fmt, go-vet, go-tidy
  • Note: The .github/workflows/lint.yml update (adding a Go CI job) requires the workflow scope and should be applied separately by a maintainer. The updated file is included in the PR description below.

Architecture

Built with Cobra (CLI framework) and charmbracelet/lipgloss (styled output). Uses standard Go project layout:

cmd/fullsend/         # CLI entry point
internal/
  cli/                # Cobra command definitions
  config/             # Config types and YAML generation
  github/             # GitHub client interface + test fake
  install/            # Installation workflow orchestration
  ui/                 # Styled terminal output

Demo output

  ⚡ fullsend  autonomous agentic development for GitHub

  → Installing fullsend to my-org

  ✓ Found 8 repositories
  ✓ GitHub App configured
  ✓ Configuration generated (1/8 repos enabled)
  ✓ Created .fullsend repository with config and workflows
  ✓ PR created for cool-project

  ╭───────────────────────────────────────────╮
  │  Installation complete                    │
  │    ✓ GitHub App: fullsend-my-org          │
  │    ✓ Config repo: my-org/.fullsend        │
  │    ✓ Repos discovered: 8                  │
  │    ✓ Enrollment PRs: 1                    │
  │    ✓ Auto-merge: disabled (safe default)  │
  ╰───────────────────────────────────────────╯

CI workflow update needed

The .github/workflows/lint.yml needs a Go CI job added. Here's the updated workflow that should be committed by a maintainer with workflow scope:

  go:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6.0.2

      - uses: actions/setup-go@v5
        with:
          go-version-file: go.mod

      - name: Build
        run: make go-build

      - name: Test
        run: make go-test

      - name: Vet
        run: make go-vet

      - name: Lint
        uses: golangci/golangci-lint-action@v8
        with:
          version: latest

Story 2 considerations (#125)

The CLI structure is designed with Story 2 in mind — the fullsend entrypoint subcommand and related sub-commands (provider, sandbox, harness, runtime) from the issue comments are natural additions to the Cobra command tree. The Client interface and config package are ready for reuse.

Related

Implement a Go CLI tool using Cobra that demonstrates the installation
and bootstrap workflow described in issue #124. The CLI uses
charmbracelet/lipgloss for styled terminal output.

What's included:
- `fullsend install <org>` command with --repo, --agents, --dry-run flags
- GitHub App configuration with minimum required permissions
- .fullsend config repo creation with safe defaults (auto_merge: false)
- Enrollment PR generation for enabled repositories
- Reusable GitHub Actions workflow generation
- CODEOWNERS file generation
- Comprehensive unit tests (46 tests, 92-100% coverage)
- golangci-lint configuration and CI integration
- Go build/test/lint/vet/fmt targets in Makefile

The install command uses a fake GitHub client for the PoC to demonstrate
the full workflow without requiring real API credentials. The Client
interface is designed for easy replacement with a real implementation.

Note: The .github/workflows/lint.yml update (adding Go CI job) requires
the workflow scope and should be applied separately by a maintainer.

Resolves #124

Assisted-by: OpenCode claude-opus-4-6@default
The end-of-file-fixer pre-commit hook requires exactly one trailing
newline. The previous version had two.

Assisted-by: OpenCode claude-opus-4-6@default
No code uses Go 1.26-specific features. All dependencies require
Go 1.18 or lower, so lowering the minimum version is safe.

Assisted-by: OpenCode claude-opus-4-6@default
Replace the simulated install flow with a real GitHub API client that
creates repos, files, branches, and PRs via the REST API. Address all
findings from code review.

Changes:
- Add LiveClient implementing Client interface with real HTTP calls
- Authenticate via GITHUB_TOKEN environment variable (removed --token flag)
- Add security hardening: io.LimitReader (10MB cap), pagination limit
  (100 pages), URL path escaping on all user inputs, dedicated
  http.Client with 30s timeout, GitHub Actions env vars instead of
  inline interpolation to prevent script injection
- Use discovered DefaultBranch for PR base (not hardcoded 'main')
- Default .fullsend repo to private
- Validate org name format and --repo values against discovered repos
- Fix DefaultAgents slice aliasing (now returns fresh copy)
- Sort validation error messages for determinism
- Validate per-repo agent lists in config
- Unexport ui style variables (internal use only)
- Remove all PoC/demo language from code and help text
- Be honest about GitHub App creation (manual step, not automated)
- Add comprehensive tests: httptest-based client tests, org name
  validation, default branch handling, config validation, repo warnings

Note: .github/workflows/lint.yml update (adding Go CI job) requires
the workflow scope and should be applied separately by a maintainer.

Assisted-by: OpenCode claude-opus-4-6@default
Add token resolution chain: GH_TOKEN > GITHUB_TOKEN > gh auth token.
If the user has already authenticated with the gh CLI, fullsend picks
up their stored credentials automatically — no env var needed.

Assisted-by: OpenCode claude-opus-4-6@default
…e/github

Extract forge-neutral interface (Client, Repository, ChangeProposal) into
internal/forge/ and move the GitHub-specific implementation to
internal/forge/github/. This is a pure structural refactor with no behavior
changes, preparing the codebase for multi-forge support (GitLab, Forgejo).

Key renames:
- PullRequest -> ChangeProposal (with URL instead of HTMLURL)
- CreatePullRequest -> CreateChangeProposal
- Result.PRs -> Result.Proposals
- FakeClient moved to forge package with neutral test URLs

Assisted-by: OpenCode claude-opus-4-6@default
Add interactive GitHub App setup using the manifest flow API:

1. fullsend starts a local HTTP server on a random port
2. Opens the user's browser to a form page that auto-submits the
   app manifest to GitHub with the correct permissions
3. GitHub shows the app creation form; user clicks 'Create GitHub App'
4. GitHub redirects back to the local server with a temporary code
5. fullsend exchanges the code for app credentials (ID, PEM key,
   client secret, webhook secret) via POST /app-manifests/{code}/conversions
6. Prompts user to install the app on their org, opens the install page
7. Verifies the installation exists via the org installations API

The flow is fully interactive with Enter-to-proceed prompts at each
step. The user never has to manually configure permissions or copy
URLs.

New package: internal/appsetup/ with Prompter and BrowserOpener
interfaces for testability. Includes --skip-app-setup flag for
re-running install when the app already exists.

Assisted-by: OpenCode claude-opus-4-6@default
GitHub requires hook_attributes.url in the manifest even when webhooks
aren't actively used. Set active: false to disable delivery.

Assisted-by: OpenCode claude-opus-4-6@default
- Clarify prompt text: tell user what's about to happen before they
  press Enter, not after
- Fix installation URL: use /apps/{slug}/installations/new without
  the broken target_id=0 query parameter
- Print confirmation after browser opens for both creation and
  installation steps
- Add guidance text: 'Name the app and click Create GitHub App'

Assisted-by: OpenCode claude-opus-4-6@default
The user can rename the GitHub App during creation. The manifest code
exchange API returns the actual name and slug they chose. Thread these
through the entire install flow:

- Add AppIdentity (name + slug) to OrgConfig, written to config.yaml
- Add AppName/AppSlug to install.Options so CLI can pass them through
- configureApp uses the real name instead of always defaulting to
  'fullsend-<org>'
- generateConfig writes the app identity into the org config
- Falls back to 'fullsend-<org>' when --skip-app-setup is used

Assisted-by: OpenCode claude-opus-4-6@default
fmt.Scanln on a [1]byte returns immediately on empty input with an
'unexpected newline' error. In some terminal environments, stdin may
have buffered newlines that cause the prompt to not actually block.

Switch to bufio.NewReader(os.Stdin).ReadString('\n') which reliably
waits for a complete line before returning.

Assisted-by: OpenCode claude-opus-4-6@default
Before walking the user through app creation, check if a fullsend app
is already installed on the organization. If found, ask whether to
reuse it (Y/n prompt). Similarly for installation: if the app is
already installed with access to all repos, skip the installation step
entirely. If installed with selected repos, offer to update.

Changes:
- Add Confirm(prompt) method to Prompter interface for Y/n prompts
- Add findExistingApp: scans org installations for apps with 'fullsend'
  prefix
- Replace verifyInstallation with getInstallation returning full
  installationInfo (includes repository_selection: all vs selected)
- Split Run into resolveApp + ensureInstalled for clearer flow
- Add StdinPrompter.Confirm with default-yes behavior (Enter = yes)

Assisted-by: OpenCode claude-opus-4-6@default
…tection

Two fixes:

1. Check if .fullsend repo already exists before attempting to create
   it. The discoverRepos step already iterates all org repos, so it
   now returns a ConfigRepoExists flag. If true, the creation step is
   skipped with a log message.

2. Replace fake PEM key string in appsetup_test.go with a non-PEM
   placeholder. The detect-private-key pre-commit hook was flagging
   the test's '-----BEGIN RSA PRIVATE KEY-----' string, causing CI
   to fail on every push.

Assisted-by: OpenCode claude-opus-4-6@default
Add `fullsend uninstall <org>` which reverses the install process:

1. Prompts the user to type the org name to confirm (skip with --yolo)
2. Reads .fullsend/config.yaml to find the app slug (source of truth)
3. Deletes the .fullsend configuration repository
4. Removes the GitHub App installation from the organization
5. Opens the browser to the app settings page for the user to delete
   the app registration (cannot be done via PAT, requires browser)

If config.yaml can't be read (e.g., repo already deleted), falls back
to scanning org installations for apps with a 'fullsend' prefix.

Each step is resilient — if one fails, the others still proceed,
with clear messages about what needs manual cleanup.

New interface methods on forge.Client:
- GetFileContent: reads a file from a repository
- DeleteRepo: deletes a repository

Assisted-by: OpenCode claude-opus-4-6@default
Three fixes for the uninstall command:

1. App settings URL was wrong — used /settings/apps/{slug} (user-scoped,
   404 for org apps). Fixed to /organizations/{org}/settings/apps/{slug}/advanced
   which is the correct org-scoped URL with the delete button visible.

2. Repo deletion now checks token scopes first. Makes a HEAD request to
   /user and reads the X-OAuth-Scopes header. If delete_repo scope is
   missing, skips the API call entirely and walks the user through
   deleting the repo in their browser (opens the repo settings page).
   If the scope exists, tries the API first and falls back to browser
   on failure.

3. App uninstallation now always uses the browser flow. The API endpoint
   (DELETE /app/installations/{id}) requires JWT auth from the app's
   private key, which we don't have. Opens the org installation settings
   page directly with the installation ID so the user sees the right
   Uninstall button.

All three destructive steps (repo delete, app uninstall, app registration
delete) follow the same Enter-prompt-then-open-browser pattern used by
the install command.

Assisted-by: OpenCode claude-opus-4-6@default
If the .fullsend/config.yaml can't be read (repo doesn't exist or
config is missing), abort immediately with a clear error message
and links to the org's installations and apps settings pages for
manual cleanup.

This moves the config read before the confirmation prompt so the
user isn't asked to confirm something that can't proceed. The
confirmation prompt now also shows the specific app name that will
be deleted (from the config), not just the org name.

Assisted-by: OpenCode claude-opus-4-6@default
Refactor from a single GitHub App to one app per agent role, each with
least-privilege permissions:

  triage: issues read/write — labels, assigns, triages issues
  coder:  contents write, PRs write, checks read — pushes code, creates PRs
  review: PRs write, contents read, checks read — reviews code, no push

Config changes:
- Replace singular `app: {name, slug}` with `agents:` list of
  `{role, name, slug}` entries in config.yaml
- Rename `defaults.agents` to `defaults.roles` and
  `repos[].agents` to `repos[].roles` for clarity
- Default roles: triage, coder, review (was triage, implementation, review)
- Add AgentSlugs() and ValidRoles() helpers
- NewOrgConfig now takes []AgentEntry instead of single app name/slug

Install changes:
- CLI loops through each role, calling appsetup.Run(ctx, org, role)
  for each to create and install a separate GitHub App
- --agents flag selects which roles to set up (default: all three)
- Each agent's PEM key gets its own secret name:
  FULLSEND_TRIAGE_APP_PRIVATE_KEY, FULLSEND_CODER_APP_PRIVATE_KEY, etc.

Appsetup changes:
- Run() accepts a role parameter and creates role-specific app
- AgentAppConfig(org, role) replaces DefaultAppConfig(org)
- findExistingApp() uses exact slug match per role instead of prefix
- UI messages include the role name throughout

Uninstall changes:
- Reads agents list from config.yaml instead of singular app slug
- Loops over each agent for uninstall and delete steps
- Summary lists each agent separately

Assisted-by: OpenCode claude-opus-4-6@default
The coder agent subscribes to issues and issue_comment events (to know
which issue to implement) but was missing the issues permission.
GitHub's manifest validation requires that event subscriptions are
backed by matching permissions.

Added issues: read to the coder's permissions. It needs to read issues
to understand what to implement, but doesn't need write access (the
triage agent handles issue updates).

Assisted-by: OpenCode claude-opus-4-6@default
After creating a repo with auto_init: true, GitHub may not have
finished initializing the default branch when we immediately try to
create files via the Contents API. This causes 'config.yaml' creation
to fail.

Add retry with linear backoff (1s, 2s, 3s, 4s) for the first file
created in a new repo. Subsequent files don't need retries because
if the first succeeds, the branch is confirmed ready.

Also includes the actual API error in the failure message so users
can see what went wrong.

Assisted-by: OpenCode claude-opus-4-6@default
Split repo creation from file writing so that config.yaml, the
reusable workflow, and CODEOWNERS are written regardless of whether
the repo was just created or already existed.

If a file already exists (GitHub returns 422 'already exists'), it
is skipped with a log message. This makes the install command
idempotent — running it twice produces the same result without
errors.

The retry-with-backoff for new repo initialization is preserved
but only applied when the repo was freshly created.

Assisted-by: OpenCode claude-opus-4-6@default
The GitHub Contents API creates a new commit per file. When creating
multiple files sequentially, the branch HEAD moves after each commit.
Subsequent CreateFile calls can hit 404/409 because the branch ref
is still being updated.

Add retry with backoff (2s, 4s) for all file creates, not just the
first one. The first file on a new repo still gets 5 attempts to
handle branch initialization delay. Files that already exist (422)
are detected and skipped without retrying.

Assisted-by: OpenCode claude-opus-4-6@default
When the .fullsend repo exists but files were already created (from a
previous run), the Contents API returns 422 'sha wasn't supplied'
because updating an existing file requires the current SHA.

Add CreateOrUpdateFile to the forge.Client interface. The GitHub
implementation first GETs the file to retrieve its SHA (if it exists),
then PUTs with the SHA included for updates. If the file doesn't
exist (GET returns 404), it creates without a SHA.

This makes install fully idempotent — re-running updates config.yaml
with the latest agent entries and settings rather than failing.

Assisted-by: OpenCode claude-opus-4-6@default
The GitHub Contents API creates a new commit per file. Sequential
writes can hit transient 404s as the branch ref updates between
commits. This is especially common for nested paths like
.github/workflows/agent.yaml.

Split file writes into required (config.yaml) and supplementary
(workflow, CODEOWNERS). Config.yaml failures are still fatal.
Workflow and CODEOWNERS failures are logged as warnings with
guidance to add them manually, allowing install to complete
successfully.

Extracted writeFileWithRetry helper for cleaner retry logic.

Assisted-by: OpenCode claude-opus-4-6@default
The GitHub Contents API returns 404 (not 403) when writing to
.github/workflows/ without the 'workflow' token scope. The gh CLI
does not request this scope by default, so users hit an opaque 404.

Changes:
- Make .github/workflows/agent.yaml a required file (was best-effort)
- Detect 404 on workflow file and show clear error with fix command:
  'gh auth refresh -s workflow'
- Keep CODEOWNERS as optional (best-effort)

Assisted-by: OpenCode claude-opus-4-6@default
Add GetAuthenticatedUser to forge.Client interface. The install flow
calls GET /user to retrieve the logged-in user's login and uses it as
the CODEOWNERS owner instead of the generic @org/admin team.

Falls back to @org/admin if the user lookup fails.

Assisted-by: OpenCode claude-opus-4-6@default
If any repo in the org is private, create .fullsend as private. If all
repos are public, create .fullsend as public. The user is informed of
the choice and the reason in the CLI output.

Previously .fullsend was always created as private.

Assisted-by: OpenCode claude-opus-4-6@default
Automate the 'Next steps' that previously required manual secret
creation. After writing config files, the install command now stores
each agent's PEM private key as a GitHub Actions secret on the
.fullsend repo.

Implementation:
- Add CreateRepoSecret to forge.Client interface
- GitHub implementation fetches the repo's public key, encrypts the
  value with libsodium sealed box (x/crypto/nacl/box.SealAnonymous),
  and PUTs to the Actions secrets API
- Secret names follow FULLSEND_{ROLE}_APP_PRIVATE_KEY convention
- AgentCredentials struct carries the PEM alongside the config entry
- Summary box now lists stored secrets instead of manual instructions

Assisted-by: OpenCode claude-opus-4-6@default
When reusing existing apps (user chose 'yes' to reuse), the PEM key
is not available — it's only returned during app creation via the
manifest code exchange. The install command was printing 'Secret
stored' in the summary even when no secrets were actually stored.

Fix:
- storeAgentSecrets returns the count of secrets actually stored
- Summary only shows 'Secrets stored: N' when N > 0
- When PEM keys are empty (reuse flow), prints a clear explanation:
  'Agent apps were reused — PEM keys are only available at creation
  time. Secrets were not updated.'

Assisted-by: OpenCode claude-opus-4-6@default
When an existing fullsend app is found during install, check whether
its PEM private key is stored as a repo secret in .fullsend. If the
secret doesn't exist, the app is useless — the PEM is only available
at creation time and can't be retrieved later.

When the secret is missing:
- Explain that the PEM key is lost
- Walk the user through deleting the app in the browser
- Then create a fresh app (which produces a new PEM that gets stored)

When the secret exists:
- Offer to reuse the app as before

Implementation:
- Add RepoSecretExists to forge.Client interface (GET /actions/secrets/{name})
- Add SecretExistsFunc + WithSecretCheck option to appsetup.Setup
- CLI wires it up: checks FULLSEND_{ROLE}_APP_PRIVATE_KEY existence

Assisted-by: OpenCode claude-opus-4-6@default
Add a fourth agent role 'fullsend' that handles maintenance and
bootstrapping operations — managing the .fullsend config repo,
workflows, and repo settings.

Permissions (broader than other agents by design):
  - contents: write (manage config files and workflows)
  - issues: read (receive dispatch commands)
  - pull_requests: write (create config change PRs)
  - checks: read (monitor workflow status)
  - administration: write (manage repo settings)
  - members: read (discover org members)

Events: issues, push, workflow_dispatch

The fullsend agent is created first during install, before triage,
coder, and review. Default roles are now: fullsend, triage, coder,
review.

Assisted-by: OpenCode claude-opus-4-6@default
Replace direct enrollment PR creation with a cascading onboarding
model driven by a GitHub Actions workflow in the .fullsend repo.

How it works:
1. Install writes repo-onboard.yaml to .fullsend/.github/workflows/
2. This workflow triggers on push to main (when config.yaml changes)
   or on workflow_dispatch
3. It reads config.yaml, iterates enabled repos, checks if each has
   .github/workflows/fullsend.yaml, and creates PRs to add it
4. The install command watches the onboarding workflow run and reports
   the enrollment PRs back to the user

New components:
- generateOnboardingWorkflow(): produces the repo-onboard.yaml workflow
  that uses the fullsend maintenance app token to create branches and
  PRs across org repos
- watchOnboarding(): polls the Actions API for the workflow run,
  waits for completion, then scans enabled repos for fullsend PRs
- `fullsend repo onboard <repository>`: manual single-repo onboarding
  command for when you want to onboard one repo without waiting for
  the workflow

Forge interface additions:
- GetLatestWorkflowRun: fetch most recent run of a workflow file
- GetWorkflowRun: fetch a specific run by ID
- ListRepoPullRequests: list open PRs in a repo

The old createEnrollmentPRs/enrollRepo methods are removed — the
onboarding workflow handles this now.

Assisted-by: OpenCode claude-opus-4-6@default
When config.yaml already exists in .fullsend, the install command now:

1. Fetches the existing config.yaml from the repo
2. Compares it to the new version that would be written
3. If unchanged, skips with a log message
4. If changed, displays a colorized unified diff:
   - Green (+) for added lines
   - Red (-) for removed lines
   - Gray for context lines
5. Asks the user: 'Overwrite config.yaml with the new version? [Y/n]'
6. If yes: overwrites the file
7. If no: writes the new version as config.yaml.new so the user can
   review and rename manually

This fixes the issue where a second install run would skip config.yaml
entirely (because the file already existed) without writing the new
agent entries from the current session.

Assisted-by: OpenCode claude-opus-4-6@default
The findExistingApp function always looked for slug pattern
'fullsend-{org}-{role}', but the fullsend maintenance agent's app
is named 'fullsend-{org}' (no '-fullsend' suffix), as defined in
AgentAppConfig.

Extract expectedAppSlug() helper that matches the naming convention
from AgentAppConfig — returns 'fullsend-{org}' for the fullsend role
and 'fullsend-{org}-{role}' for all others.

Assisted-by: OpenCode claude-opus-4-6@default
App detection during re-runs was using a hardcoded naming convention
(expectedAppSlug) to guess what slug to look for in the org's
installations. This breaks when the user renames an app during
creation, since the actual slug wouldn't match the convention.

Fix: before the app setup loop, read the existing config.yaml from
the .fullsend repo (if it exists) and extract the slug for each role.
Pass these known slugs to appsetup via WithKnownSlugs(). The
findExistingApp method now checks the config slug first, falling back
to the naming convention only if no config entry exists for that role.

This makes detection reliable regardless of whether apps were renamed.

Assisted-by: OpenCode claude-opus-4-6@default
The repo onboarding workflow uses actions/create-github-app-token@v1
which requires the app ID as input. The app ID is not sensitive (it's
a public integer) so it's stored as a repo Actions variable, not a
secret.

Changes:
- Add CreateOrUpdateRepoVariable to forge.Client interface
- GitHub implementation tries PATCH first (update), falls back to
  POST (create) on 404
- storeAgentSecrets now also stores FULLSEND_{ROLE}_APP_ID as a
  variable alongside the PEM secret
- Add AppID field to AgentCredentials, passed from appsetup
- Fix workflow template: FULLSEND_APP_ID → FULLSEND_FULLSEND_APP_ID
  to match the naming convention (role is 'fullsend')

Assisted-by: OpenCode claude-opus-4-6@default
Two fixes for the workflow watcher:

1. Skip watching entirely if no config files were actually written
   (nothing changed, so no workflow will trigger)

2. Only match workflow runs created AFTER our file writes started.
   Records a timestamp before writing config files and compares it
   to each run's created_at. This prevents matching stale runs from
   previous installs.

The watcher now has two phases:
- Phase 1 (15s timeout): Wait for a new run to appear. If none
  starts, warn the user with troubleshooting tips and a manual
  trigger URL.
- Phase 2 (120s timeout): Poll the run until it completes, then
  collect enrollment PRs.

Assisted-by: OpenCode claude-opus-4-6@default
ralphbean added a commit that referenced this pull request Apr 2, 2026
Documents non-obvious GitHub API behaviors discovered during the original
implementation:
- auto_init is async; file writes after repo creation need retry
- Contents API requires existing file SHA for updates (422 otherwise)
- Sequential file writes cause transient 404s as branch refs update
- Writing to .github/workflows/ returns 404 (not 403) without workflow scope
- App PEM private keys are one-shot; only available at creation time
- Event subscriptions must have matching permissions or manifest is rejected
- App installation URL must not include target_id parameter
- Org-scoped app settings need /advanced suffix in URL
- App uninstall API requires JWT auth, not PAT (browser fallback needed)
- Users can rename apps during creation; match by stored slug first
- Token scopes (delete_repo, workflow) are often missing from default gh auth

Assisted-by: OpenCode claude-opus-4-6@default
@ralphbean

Copy link
Copy Markdown
Member Author

Closing this in favor of #142

@ralphbean ralphbean closed this Apr 2, 2026
ralphbean added a commit that referenced this pull request Apr 3, 2026
Documents non-obvious GitHub API behaviors discovered during the original
implementation:
- auto_init is async; file writes after repo creation need retry
- Contents API requires existing file SHA for updates (422 otherwise)
- Sequential file writes cause transient 404s as branch refs update
- Writing to .github/workflows/ returns 404 (not 403) without workflow scope
- App PEM private keys are one-shot; only available at creation time
- Event subscriptions must have matching permissions or manifest is rejected
- App installation URL must not include target_id parameter
- Org-scoped app settings need /advanced suffix in URL
- App uninstall API requires JWT auth, not PAT (browser fallback needed)
- Users can rename apps during creation; match by stored slug first
- Token scopes (delete_repo, workflow) are often missing from default gh auth

Assisted-by: OpenCode claude-opus-4-6@default
waynesun09 added a commit that referenced this pull request Apr 12, 2026
Seven specialized agents for working on the fullsend project:

- fullsend-architect (opus): architectural coherence guardian; knows all
  ADRs, five execution layers, story dependencies, repo-as-coordinator invariant
- go-developer (sonnet): CLI specialist; forge abstraction, layered config,
  multi-role GitHub App model, known gaps in PR #132
- doc-architect (sonnet): problem doc and ADR writer; design-exploration
  conventions, org-agnostic authoring rules
- stage-prompt-designer (opus): designs/reviews stage agent prompts;
  triage/implement/review/fix constraints, injection surface rules,
  known failure modes from live operation (Issues #4, #5, #010a)
- security-reviewer (opus): applies fullsend threat model; prompt injection,
  ADR 0017 credential isolation, sandbox integrity, workflow file protection
- workflow-engineer (sonnet): GitHub Actions and dispatch layer; label state
  machine, slash commands, concurrency groups, fixes for Issues #1 #003b
  #4 #5 #7 #9 #010a
- e2e-integrator (opus): full flow tracing; integration gap analysis, demo
  readiness checklist, sprint prioritization across stories

Also adds .claude/AGENTS.md with usage guide and team composition patterns.
@ben-alkov
ben-alkov deleted the agent-124-fullsend-cli-poc branch April 23, 2026 16:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Story 1: Installation & Bootstrap

1 participant