BOHICA-LABS / BOHICA-LABS/vsdd-factory
feat(preflight): factory should validate branching strategy, default branch, branch protection, and GitHub repo settings on session entry
- Dominant language
- Rust
- Stars
- 2
- Forks
- 1
- Avg merge
- 6h 43m
- Merged PRs (30d)
- 29
Description
## Summary
`/vsdd-factory:setup-env` and `/vsdd-factory:factory-health` are the two skills an operator is told to run at session start. **Neither validates the repository's branching strategy, default branch, branch protection rules, or any GitHub-side repo configuration.** The factory will happily run any pipeline phase against a repo that has:
- No default branch set (or `main` when `develop` is the actual integration branch)
- No branch protection on the integration branch
- A branching strategy that conflicts with the factory's hardcoded `develop` assumption
- Settings/policies (squash-merge only, signed-commit enforcement, CODEOWNERS, Dependabot, etc.) that contradict pipeline assumptions
The closest the factory gets is:
- `scaffold-claude-md` **detects** branch strategy when generating a CLAUDE.md — purely textual, no enforcement, opt-in.
- `repo-initialization` **creates** a new repo with `develop` as default + branch protection — only when greenfield.
- `brownfield-sequence` S2 mentions "Verify default branch is set / Check/add branch protection if missing" — textual; the grep for an actual implementation step turns up nothing concrete.
For an existing repo (brownfield, or just "session start"), nothing checks any of this.
## What's missing — concrete checks needed
### Branching-strategy preflight (per-session)
```bash
# 1. Default branch
default_branch=$(git symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|origin/||')
test -n "$default_branch" || warn "origin/HEAD unset — default branch not configured"
# 2. Detect strategy
has_develop=$(git ls-remote --heads origin develop)
has_release=$(git ls-remote --heads origin 'release/*')
has_main=$(git ls-remote --heads origin main)
if [ -n "$has_develop" ] && [ -n "$has_main" ]; then
strategy=gitflow
elif [ -n "$has_main" ] && [ -z "$has_develop" ]; then
strategy="github-flow (trunk-based)"
fi
# 3. Confirm with the operator on first run; cache in .factory/repo-config.yaml
# Subsequent sessions: re-detect and warn on drift
# 4. Guard against bad starting branch
current=$(git rev-parse --abbrev-ref HEAD)
case "$strategy:$current" in
gitflow:main|gitflow:master)
fail "gitflow in use but you are on $current. Switch to develop before running pipeline operations." ;;
esac
```
### Branch protection preflight
```bash
gh api "repos/$OWNER/$REPO/branches/$default_branch/protection" 2>&1 || \
warn "no branch protection on $default_branch — required reviews, status checks, signed commits all unenforced"
```
Should at minimum surface:
- Are required reviews configured?
- Are status checks required?
- Is `enforce_admins` on?
- Are signed commits required?
- Is `delete_branch_on_merge` set?
- Are linear history / signed merges configured?
### GitHub repo settings sweep
```bash
gh api "repos/$OWNER/$REPO" --jq '{
visibility, default_branch, allow_squash_merge, allow_merge_commit,
allow_rebase_merge, delete_branch_on_merge, has_issues, has_projects,
license: .license.spdx_id, archived, has_security_policy
}'
```
Report missing/unset things that the factory pipeline assumes:
- `delete_branch_on_merge=true` (the pr-manager skill calls `gh pr merge --delete-branch`; if the repo doesn't honor it, branches accumulate)
- License set (compliance — affects whether the holdout-evaluator can ship)
- Signed-commit enforcement matching the operator's local `commit.gpgsign`
### CODEOWNERS / templates / community files
```bash
test -f .github/CODEOWNERS || warn "no CODEOWNERS — pr-manager review dispatch may be brittle"
test -f .github/PULL_REQUEST_TEMPLATE.md || info "no PR template — pr-manager will create one"
test -f SECURITY.md || info "no SECURITY.md — security-reviewer may surface as a finding"
ls .github/workflows/*.yml 2>/dev/null | wc -l | xargs -I{} info "CI workflows: {} files"
```
### Dependency posture preflight
For each detected language, run the language-native CVE/audit tool:
| Language | Tool |
|----------|------|
| Go | `govulncheck ./...` |
| Rust | `cargo audit` |
| Python | `pip-audit` |
| Node | `npm audit --audit-level=high` |
Surface high-severity hits at session start so the operator decides whether to address before kicking off a pipeline that's about to add more.
### Cache the result
Write what was discovered to `.factory/repo-config.yaml` so subsequent sessions don't re-prompt the operator:
```yaml
default_branch: develop
branching_strategy: gitflow
integration_branch: develop
release_branch_pattern: 'release/*'
hotfix_branch_pattern: 'hotfix/*'
feature_branch_pattern: 'feature/*'
discovered: 2026-06-23T19:30:00Z
discovered_by:
```
The brownfield-ingest skill should populate this; setup-env / factory-health should verify it stays consistent across sessions.
## Why this matters
1. **Silent misconfiguration risk.** A team that uses GitHub-flow (trunk-based, `main` only) gets bizarre behavior when the factory creates a `develop` branch they don't want, then PRs to `develop` instead of `main`. The factory doesn't ask; it just assumes.
2. **Branch protection blind spot.** The factory's pipeline assumes signed commits / required checks. If the repo has no protection on `develop` (or the operator is somehow allowed to bypass it), the pipeline silently produces unverified work.
3. **"Don't push to main" is only enforced by the user's CLAUDE.md.** If a team adopts vsdd-factory and the user's CLAUDE.md isn't propagated, nothing stops a `gh pr merge` against `main` directly.
4. **Repo settings drift.** A repo can have settings flipped by an admin between sessions; the factory should detect drift and re-prompt.
5. **No discovery for brownfield.** When ingesting an existing repo, the factory should LEARN the branching strategy from the repo (merge patterns, branch presence, GitHub default), not assume.
## Acceptance criteria
- [ ] A new `/vsdd-factory:repo-preflight` skill (or expansion of `factory-health`) runs at session entry and checks:
- default branch is set on origin
- branching strategy detected (gitflow / github-flow / gitlab-flow / unknown)
- branch protection rules present on the integration branch
- signed-commit enforcement matches operator config
- GitHub repo settings: `delete_branch_on_merge`, `allow_squash_merge`, license, CODEOWNERS, SECURITY.md
- Dependency posture: language-native CVE check (govulncheck / cargo audit / pip-audit / npm audit)
- [ ] On first run, results are cached to `.factory/repo-config.yaml` (committed to factory-artifacts).
- [ ] On subsequent runs, drift from cached state is surfaced as a warning.
- [ ] When gitflow is detected, attempting to start a pipeline phase from `main`/`master` is BLOCKED with a clear error.
- [ ] When trunk-based is detected, the pipeline's `develop` assumption is swapped for the actual integration branch (configurable).
- [ ] `worktree-manage`, `code-delivery`, `pr-manager` all read the integration-branch name from `.factory/repo-config.yaml` instead of hardcoding `develop`.
## Found during
`/vsdd-factory:setup-env` then `/vsdd-factory:factory-health` on `switchboard-blue` (Go project, gitflow, 2026-06-23, vsdd-factory@1.0.0-rc.21). Neither skill validates branching strategy, default branch, or branch protection. The project's gitflow setup is enforced only by the user's CLAUDE.md (which mandates "don't push to main / develop directly, always PR") and the project CLAUDE.md. If those weren't in place, the factory wouldn't catch a wrong-branch operation.
Specific issue earlier in this session: the `/factory-health` orphan-branch recipe runs `git checkout -` which, in a gitflow repo, could happily land on `main` if that's where the session started — no skill would have caught it as a violation of the gitflow contract.
## Related
- (this session, filed) #204 — `git checkout -` strands session on factory-artifacts; the recipe also has no branch-name awareness
- skills/scaffold-claude-md/SKILL.md — has the detection logic (default branch, develop existence, branch naming patterns) but only writes to a doc, doesn't enforce
- skills/repo-initialization/SKILL.md — has the enforcement logic but only on greenfield (new repo creation)
- agents/orchestrator/brownfield-sequence.md S2 — textually mentions "Verify default branch is set" with no implementation
Contributor guide
Assessment
This issue has not been assessed yet.