From ab41bf161d860522a1cdb54e3219804975ba0d23 Mon Sep 17 00:00:00 2001 From: adam Date: Tue, 6 Oct 2026 14:51:37 -0700 Subject: [PATCH 1/7] feat(v2): ak onboard writes the config a repository needs in one call A repository joins ak by hand-writing .agent/config.env, or by the legacy onboard-repo skill: nine gated steps and ~2,000 helper lines read into the root's context. `ak onboard` replaces both with one command that writes only what the repository already says and prints at most 20 lines. - slug and base branch from git; the linked project board and its Status options from one GraphQL read; setup and test commands from marker files, the lockfile choosing the package manager and a Makefile test target winning. A directory with its own toolchain becomes an AGENT_CMD_ suite with AGENT_RUNDIR_, which `ak verify` already runs per area. - existing keys are kept, so a re-run changes nothing; an untracked .agent/ stays out of git through info/exclude, like .ak/. - two linked boards print `board=choose` with the command that picks one; no board, no toolchain and a failed board read are notes, not refusals. - the `onboard` skill is five steps: run it, settle `board=choose` or `commands=none` with one more call, run the `next=` command, report. Closes #997 Co-Authored-By: Claude Fable 5.1 --- tests/v2/test-onboard.sh | 107 +++++++++++++++++++++++++ v2/bin/ak | 3 +- v2/lib/onboard.sh | 158 +++++++++++++++++++++++++++++++++++++ v2/skills/onboard/SKILL.md | 21 +++++ 4 files changed, 288 insertions(+), 1 deletion(-) create mode 100644 tests/v2/test-onboard.sh create mode 100644 v2/lib/onboard.sh create mode 100644 v2/skills/onboard/SKILL.md diff --git a/tests/v2/test-onboard.sh b/tests/v2/test-onboard.sh new file mode 100644 index 00000000..25e4aed4 --- /dev/null +++ b/tests/v2/test-onboard.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# ak onboard: writes the config keys ak reads from what the repository already says, and keeps what is there. +TEST_NAME=v2-onboard +# shellcheck source=lib.sh +source "$(dirname -- "${BASH_SOURCE[0]}")/lib.sh" + +# linked_route BOARDS: answer the "boards linked to this repository" query with the JSON array BOARDS. +linked_route() { + route 'api graphql*projectsV2(first*' "$(jq -c '{data: {repository: {projectsV2: {nodes: .}}}}' <<<"$1")" +} +FULL='[{"name":"Backlog"},{"name":"Ready"},{"name":"In progress"},{"name":"In review"},{"name":"Done"}]' +board() { jq -nc --argjson n "$1" --arg t "$2" --argjson o "${3:-$FULL}" '{number: $n, title: $t, closed: false, owner: {login: "acme"}, field: {options: $o}}'; } + +repo=$(fixture_repo) +cd "$repo" || exit 1 + +# --- a Node repository linked to one board --- +printf '{"name":"widget","scripts":{"test":"jest"}}\n' >package.json +printf '{}\n' >package-lock.json +git add package.json package-lock.json && git commit -q -m node +linked_route "[$(board 4 'Widget board')]" +out=$("$AK" onboard 2>&1); rc=$? +assert_eq 0 "$rc" 'onboard exits 0' +assert_contains "$out" 'wrote=.agent/config.env keys=5 kept=1' 'onboard counts the keys it added and the ones it kept' +assert_contains "$out" $'\nAGENT_BASE_BRANCH=main\n' 'the base branch comes from origin/HEAD' +assert_contains "$out" $'\nAGENT_PROJECT_OWNER=acme\nAGENT_PROJECT_NUMBER=4\n' 'the one linked board is adopted' +assert_contains "$out" $'\nAGENT_CMD_SETUP=npm ci\nAGENT_CMD_TEST=npm test\n' 'the lockfile picks the package manager' +assert_contains "$out" 'board=4 "Widget board" status=Backlog,Ready,In progress,In review,Done' 'the board line names the Status options' +assert_contains "$out" $'\nnext=ak setup && ak verify --full' 'the next line proves the declared commands' +assert_not_contains "$out" 'AGENT_REPO_SLUG' 'a key already in the file is kept, not reprinted' +assert_eq 1 "$(grep -c '^AGENT_REPO_SLUG=' .agent/config.env)" 'the existing slug line survives once' +assert_eq 'npm test' "$(sed -n 's/^AGENT_CMD_TEST=//p' .agent/config.env)" 'the file carries the test command' +assert_eq 0 "$(grep -c '^fake gh' <<<"$out")" 'gh calls all had routes' +assert_eq 1 "$(grep -c 'api graphql' "$FAKE_GH_LOG")" 'the board costs one GraphQL read' +assert_rc 0 'onboard prints at most 20 lines' -- test "$(wc -l <<<"$out")" -le 20 + +before=$(cat .agent/config.env) +out=$("$AK" onboard 2>&1); rc=$? +assert_eq 0 "$rc" 'a second onboard exits 0' +assert_contains "$out" 'wrote=.agent/config.env keys=0 kept=6' 'a second onboard adds nothing' +assert_eq "$before" "$(cat .agent/config.env)" 'a second onboard leaves the file byte-identical' + +# --- a bad flag --- +out=$("$AK" onboard --bogus 2>&1); rc=$? +assert_eq 2 "$rc" 'an unknown flag exits 2' + +# --- a repository linked to two boards, then a chosen one --- +rm .agent/config.env +: >"$FAKE_GH_ROUTES" +linked_route "[$(board 4 'Widget board'),$(board 9 'Platform')]" +out=$("$AK" onboard 2>&1); rc=$? +assert_eq 0 "$rc" 'two linked boards still write the rest of the config' +assert_contains "$out" $'board=choose\n 4 Widget board\n 9 Platform\nfix: ak onboard --project N --owner acme' 'two boards are listed with the command that picks one' +assert_not_contains "$out" 'AGENT_PROJECT_NUMBER' 'no board is guessed' +route 'api graphql*projectV2(number*' "$(jq -c '{data: {repositoryOwner: {projectV2: .}}}' <<<"$(board 9 Platform '[{"name":"Todo"},{"name":"Ready"},{"name":"In progress"},{"name":"Done"}]')")" +out=$("$AK" onboard --project 9 --owner acme 2>&1); rc=$? +assert_eq 0 "$rc" 'a named board is adopted' +assert_contains "$out" $'\nAGENT_PROJECT_OWNER=acme\nAGENT_PROJECT_NUMBER=9\n' 'the named board is written' +assert_contains "$out" 'board=9 "Platform" status=Todo,Ready,In progress,Done' 'the chosen board line names its options' +assert_contains "$out" $'\nmissing-status=In review (ak board no-ops a move there)' 'a missing canonical column is named, not fatal' + +# --- no linked board, no toolchain --- +rm .agent/config.env package.json package-lock.json +git rm -q package.json package-lock.json && git commit -q -m bare +: >"$FAKE_GH_ROUTES" +linked_route '[]' +out=$("$AK" onboard 2>&1); rc=$? +assert_eq 0 "$rc" 'nothing discovered still exits 0' +assert_contains "$out" 'board=none; ak plan reads AGENT_READY_LABEL=