Skip to content

feat(cmd): add approve subcommand for workflow gates - #98

Merged
5uck1ess merged 3 commits into
mainfrom
feat/approve-cli
Apr 18, 2026
Merged

feat(cmd): add approve subcommand for workflow gates#98
5uck1ess merged 3 commits into
mainfrom
feat/approve-cli

Conversation

@5uck1ess

Copy link
Copy Markdown
Owner

Summary

  • Adds devkit approve <name> CLI — writes .devkit/gates/<name>.approved with a timestamp and git-config approver
  • Idempotent: second call reads the existing marker instead of overwriting
  • Path-traversal safe: names must match [a-zA-Z0-9][a-zA-Z0-9_-]{0,63}

Why

Enables a polling-gate pattern in workflows without any engine changes:

```yaml

  • id: plan-gate
    command: |
    while [ ! -f .devkit/gates/plan.approved ]; do
    echo "Waiting for approval — run: devkit approve plan"
    sleep 5
    done
    cat .devkit/gates/plan.approved
    expect: success
    ```

The step blocks until the marker exists. Ctrl+C kills the shell cleanly via `exec.CommandContext`. No new YAML field, no new step type, no session-state changes — this is pure convenience over what command+expect already supported.

Test plan

  • `go test ./...` (407 passed)
  • `go vet ./...` clean
  • `devkit approve --help` renders
  • Manual: run `devkit approve plan` in a repo, verify `.devkit/gates/plan.approved` contents
  • Manual: re-run `devkit approve plan`, verify idempotent (no rewrite)
  • Manual: `devkit approve ../escape` rejected

Adds `devkit approve <name>` which writes `.devkit/gates/<name>.approved`
with a timestamp and git-config approver identity. Idempotent on repeat
invocation, rejects path-traversal and otherwise-unsafe names.

Unblocks a polling-gate pattern for workflows where a shell step waits
on a marker file before the pipeline continues — e.g. a plan-review gate
in a long-running feature workflow.
Atomic marker publish (the NO-SHIP blocker) plus cheap cleanups.

- Publish marker via temp-file + rename so a failed approve can no
  longer leave a visible marker that unblocks a polling gate
- Single git subprocess via --get-regexp instead of two
- 2s timeout on git config so a locked/misconfigured repo can't hang
- Route output through cmd.OutOrStdout() for testability and redirection
- Include regex anchors in the invalid-name error message
- Test mtime preservation on idempotent re-approve; assert null-byte
  rejection happens at the regex layer; cover parseGitUserRegexp
- Use os.DevNull instead of hardcoded /dev/null in test fixture
- Cover approverIdentity fallback chain: $USER and "unknown" paths had
  no coverage because newTestRepo always seeded a valid git identity.
  Add isolateGitConfig helper + two tests so a regression that reorders
  or drops the fallback branches fails loudly.
- Rename wantErr -> wantMatch in the name-validation table. The old
  field was readable backwards ("wantErr: false" meant "expect match")
  and a reviewer misread the assertion as a no-op. The logic was
  correct; the naming was the bug.
- Backdate mtime between the two idempotent approves so the "file was
  not rewritten" check works on filesystems with 1-second mtime
  resolution (ext3, older HFS+, some CI overlays). Previously flaky
  off APFS/ext4 — the two approves finish in microseconds.
- Log git failures in approverIdentity to stderr before falling back.
  Silent swallow left users with approver=unknown and no signal that
  git was consulted and failed (timeout, corrupt config, locked index).
@5uck1ess
5uck1ess merged commit ce2f332 into main Apr 18, 2026
6 checks passed
@5uck1ess
5uck1ess deleted the feat/approve-cli branch April 18, 2026 16:05
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.

1 participant