diff --git a/.github/workflows/installer-smoke-test.yml b/.github/workflows/installer-smoke-test.yml index 514b629..7d5b31b 100644 --- a/.github/workflows/installer-smoke-test.yml +++ b/.github/workflows/installer-smoke-test.yml @@ -39,7 +39,7 @@ jobs: sleep 1 done exit 1 - - name: Install CLI and dashboard + - name: Install CLI env: ARCHDEV_RELEASE_BASE_URL: http://127.0.0.1:8123 ARCHDEV_VERSION: 0.31.0 @@ -47,10 +47,11 @@ jobs: SHELL: /bin/${{ matrix.shell_name }} ARCHDEV_INSTALL_DIR: ${{ runner.temp }}/home/.local/bin run: | - mkdir -p "$HOME" + mkdir -p "$HOME" "$ARCHDEV_INSTALL_DIR" + touch "$ARCHDEV_INSTALL_DIR/archdev-dashboard" ./install.sh test "$("$HOME/.local/bin/archdev" --version)" = 0.31.0 - test -x "$HOME/.local/bin/archdev-dashboard" + test ! -e "$HOME/.local/bin/archdev-dashboard" test -f "$HOME/${{ matrix.completion_file }}" - name: Stop fixture server if: always() @@ -70,14 +71,15 @@ jobs: $server = Start-Process python -ArgumentList "-m", "http.server", "8123", "--bind", "127.0.0.1" -WorkingDirectory "$env:RUNNER_TEMP\release" -PassThru $server.Id | Set-Content "$env:RUNNER_TEMP\server.pid" Start-Sleep -Seconds 2 - - name: Install CLI and dashboard + - name: Install CLI shell: pwsh run: | + New-Item -ItemType Directory -Path "$env:RUNNER_TEMP\bin" -Force | Out-Null + New-Item -ItemType File -Path "$env:RUNNER_TEMP\bin\archdev-dashboard.exe" -Force | Out-Null ./install.ps1 -Version 0.31.0 -BaseUrl http://127.0.0.1:8123 -InstallDir "$env:RUNNER_TEMP\bin" -SkipPathUpdate $cliVersion = & "$env:RUNNER_TEMP\bin\archdev.exe" --version if ($cliVersion.Trim() -ne "0.31.0") { throw "archdev.exe version mismatch" } - $sidecarVersion = & "$env:RUNNER_TEMP\bin\archdev-dashboard.exe" --version - if ($sidecarVersion.Trim() -ne "0.31.0") { throw "archdev-dashboard.exe is not runnable" } + if (Test-Path "$env:RUNNER_TEMP\bin\archdev-dashboard.exe") { throw "retired archdev-dashboard.exe was installed" } - name: Stop fixture server if: always() shell: pwsh diff --git a/README.md b/README.md index 78b5149..795b15c 100644 --- a/README.md +++ b/README.md @@ -33,12 +33,40 @@ curl -fsSL https://raw.githubusercontent.com/ArchAstro/archdev/main/install.sh | irm https://raw.githubusercontent.com/ArchAstro/archdev/main/install.ps1 | iex ``` -The release archive installs both `archdev` and its `archdev-dashboard` -sidecar. The Unix installer also configures Bash, Zsh, or Fish completions for -the active shell. +The release archive installs `archdev`. The Unix installer also configures +Bash, Zsh, or Fish completions for the active shell. + +## Install one domain as an agent skill + +ArchDev's domain skills are independently installable. Each skill bootstraps a +compatible ArchDev CLI on first use and configures only the capability it owns. + +| Skill | Use it for | ArchDev login | Local runner | +| --- | --- | --- | --- | +| `tasks` | Plans, dependencies, claims, and fenced completion | Required for shared Tasks | Not installed | +| `jobs` | Exact-commit pipelines, logs, retries, and repository automation | Not required | Installed only by `jobs setup` | + +Install a skill for your machine: + +```bash +npx skills add ArchAstro/archdev --skill tasks --global --yes +npx skills add ArchAstro/archdev --skill jobs --global --yes +``` + +Or omit `--global` to install into the current repository: + +```bash +npx skills add ArchAstro/archdev --skill tasks --yes +npx skills add ArchAstro/archdev --skill jobs --yes +``` + +The Tasks skill never runs umbrella setup or installs the Jobs runner. The Jobs +skill does not log in or configure a model provider; it runs `jobs setup` only +when durable local automation is requested. ## Repository scope -This repository owns public distribution: installers, release metadata, and -downloadable binaries. ArchDev's source and release build stay in firstlanding. -Report installation and packaging problems with a GitHub issue here. +This repository owns public distribution: installers, skills, release metadata, +and downloadable binaries. ArchDev's source and release build stay in +firstlanding. Report installation and packaging problems with a GitHub issue +here. diff --git a/install.ps1 b/install.ps1 index 8fbe576..58040b9 100644 --- a/install.ps1 +++ b/install.ps1 @@ -55,11 +55,10 @@ try { $ActualHash = (Get-FileHash $ArchivePath -Algorithm SHA256).Hash if ($ActualHash.ToLowerInvariant() -ne $ExpectedHash.ToLowerInvariant()) { throw "Checksum mismatch for $AssetName" } Expand-Archive -Path $ArchivePath -DestinationPath $ExtractDir -Force - foreach ($Name in @("archdev.exe", "archdev-dashboard.exe")) { - $Source = Join-Path $ExtractDir $Name - if (-not (Test-Path $Source)) { throw "Archive is missing $Name" } - Copy-Item $Source (Join-Path $InstallDir $Name) -Force - } + $Source = Join-Path $ExtractDir "archdev.exe" + if (-not (Test-Path $Source)) { throw "Archive is missing archdev.exe" } + Copy-Item $Source (Join-Path $InstallDir "archdev.exe") -Force + Remove-Item (Join-Path $InstallDir "archdev-dashboard.exe") -Force -ErrorAction SilentlyContinue if (-not $SkipPathUpdate) { $CurrentUserPath = [Environment]::GetEnvironmentVariable("Path", "User") $Entries = if ($CurrentUserPath) { $CurrentUserPath -split ';' } else { @() } @@ -69,7 +68,7 @@ try { } } if (-not $SkipVerify) { & (Join-Path $InstallDir "archdev.exe") --version } - Write-Host "Installed archdev and archdev-dashboard to $InstallDir" + Write-Host "Installed archdev to $InstallDir" } finally { Remove-Item $TempRoot -Recurse -Force -ErrorAction SilentlyContinue } diff --git a/install.sh b/install.sh index 4759312..68a9d34 100755 --- a/install.sh +++ b/install.sh @@ -5,7 +5,6 @@ set -euo pipefail OWNER="ArchAstro" REPO="archdev" BINARY_NAME="archdev" -SIDECAR_NAME="archdev-dashboard" INSTALL_DIR="${ARCHDEV_INSTALL_DIR:-}" REQUESTED_VERSION="${ARCHDEV_VERSION:-latest}" RELEASE_BASE_URL="${ARCHDEV_RELEASE_BASE_URL:-}" @@ -112,7 +111,6 @@ asset_url=${ASSET_URL} checksum_url=${CHECKSUM_URL} install_dir=${INSTALL_DIR} binary_path=${INSTALL_DIR}/${BINARY_NAME} -sidecar_path=${INSTALL_DIR}/${SIDECAR_NAME} EOF exit 0 fi @@ -146,10 +144,12 @@ fi mkdir -p "$EXTRACT_DIR" tar -xzf "$ASSET_PATH" -C "$EXTRACT_DIR" -for executable in "$BINARY_NAME" "$SIDECAR_NAME"; do - [[ -f "$EXTRACT_DIR/$executable" ]] || { printf 'Archive is missing %s\n' "$executable" >&2; exit 1; } - install -m 0755 "$EXTRACT_DIR/$executable" "$INSTALL_DIR/$executable" -done +[[ -f "$EXTRACT_DIR/$BINARY_NAME" ]] || { + printf 'Archive is missing %s\n' "$BINARY_NAME" >&2 + exit 1 +} +install -m 0755 "$EXTRACT_DIR/$BINARY_NAME" "$INSTALL_DIR/$BINARY_NAME" +rm -f "$INSTALL_DIR/archdev-dashboard" append_once() { local file="$1" line="$2" @@ -198,4 +198,4 @@ fi if [[ "$SKIP_VERIFY" != true ]]; then "$INSTALL_DIR/$BINARY_NAME" --version fi -printf 'Installed %s and %s to %s\n' "$BINARY_NAME" "$SIDECAR_NAME" "$INSTALL_DIR" +printf 'Installed %s to %s\n' "$BINARY_NAME" "$INSTALL_DIR" diff --git a/scripts/create-unix-fixtures.sh b/scripts/create-unix-fixtures.sh index aad9779..3a64a7c 100755 --- a/scripts/create-unix-fixtures.sh +++ b/scripts/create-unix-fixtures.sh @@ -22,12 +22,8 @@ if [ "\${1:-}" = "--version" ]; then printf '%s\n' '$VERSION'; exit 0; fi if [ "\${1:-}" = "completion" ]; then printf '# completion for %s\n' "\${2:-unknown}"; exit 0; fi exit 0 EOF - cat >"$fixture/archdev-dashboard" <<'EOF' -#!/usr/bin/env sh -exit 0 -EOF - chmod +x "$fixture/archdev" "$fixture/archdev-dashboard" - tar -C "$fixture" -czf "$OUTPUT_DIR/archdev-$target.tar.gz" archdev archdev-dashboard + chmod +x "$fixture/archdev" + tar -C "$fixture" -czf "$OUTPUT_DIR/archdev-$target.tar.gz" archdev rm -rf "$fixture" done ( diff --git a/scripts/create-windows-fixtures.ps1 b/scripts/create-windows-fixtures.ps1 index 19cd422..0d794b1 100644 --- a/scripts/create-windows-fixtures.ps1 +++ b/scripts/create-windows-fixtures.ps1 @@ -26,8 +26,7 @@ foreach ($Arch in @("arm64", "x64")) { $Fixture = Join-Path ([IO.Path]::GetTempPath()) ("archdev-fixture-" + [Guid]::NewGuid().ToString("N")) New-Item -ItemType Directory -Path $Fixture | Out-Null Copy-Item $FixtureBinary (Join-Path $Fixture "archdev.exe") - Copy-Item (Join-Path $Fixture "archdev.exe") (Join-Path $Fixture "archdev-dashboard.exe") - Compress-Archive -Path (Join-Path $Fixture "archdev.exe"), (Join-Path $Fixture "archdev-dashboard.exe") -DestinationPath (Join-Path $OutputDir "archdev-windows-$Arch.zip") + Compress-Archive -Path (Join-Path $Fixture "archdev.exe") -DestinationPath (Join-Path $OutputDir "archdev-windows-$Arch.zip") Remove-Item $Fixture -Recurse -Force } Remove-Item $FixtureBinaryRoot -Recurse -Force diff --git a/skills/jobs/SKILL.md b/skills/jobs/SKILL.md new file mode 100644 index 0000000..f11e11e --- /dev/null +++ b/skills/jobs/SKILL.md @@ -0,0 +1,141 @@ +--- +name: jobs +description: Use when someone asks to run durable repository automation, execute a pipeline against an exact commit, inspect or retry a job, read persisted logs, manage the local runner, submit or sync a repository, automate review delivery, or recover conflicts through ArchDev Jobs. +--- + +# Jobs + +ArchDev Jobs runs repository automation in isolated worktrees against exact +commits. Jobs is local: do not authenticate an ArchDev user or configure a model +provider unless a pipeline step independently requires one. + +## Start + +Resolve the absolute path of this loaded `SKILL.md` from the host's skill +inventory. Do not derive it from the current repository. Bootstrap ArchDev from +that directory. + +Unix: + +```sh +skill_file="/absolute/path/to/loaded/jobs/SKILL.md" +skill_dir="$(cd -P "$(dirname "$skill_file")" && pwd)" +archdev="$(bash "$skill_dir/scripts/bootstrap.sh")" +``` + +Windows PowerShell: + +```powershell +$skillFile = 'C:\absolute\path\to\loaded\jobs\SKILL.md' +$skillDir = Split-Path -Parent (Resolve-Path -LiteralPath $skillFile) +$archdev = & powershell -NoProfile -File "$skillDir\scripts\bootstrap.ps1" +``` + +If bootstrap fails, report its error and point to the official ArchDev +installer. Run all repository commands from the intended Git checkout. + +Inspect configuration and runner state before changing either: + +```sh +"$archdev" check +"$archdev" --json jobs runner status +"$archdev" --json jobs list +``` + +A stopped or absent runner is not an authentication failure. These inspection +commands may return nonzero before first setup; when durable work was requested, +continue with `jobs setup`. Do not run `archdev auth login`, `archdev agents +setup`, provider login, or umbrella setup for Jobs. + +## Choose foreground or durable execution + +Use `--here` only when the user asked to run a pipeline immediately in the +current checkout. This can mutate the checkout and does not create durable job +history: + +```sh +"$archdev" jobs run "" --here +``` + +For work that must survive the terminal, first enable local automation. This +installs or starts the per-user runner and registers the current repository; it +does not configure model access: + +```sh +"$archdev" jobs setup +``` + +Commit the intended input before submission. A durable named run captures the +current committed `HEAD`, ignores later working-tree edits, and disables +automatic publication unless the pipeline explicitly publishes: + +```sh +"$archdev" --json jobs run "" +``` + +Keep the returned job ID. + +## Observe and control jobs + +```sh +"$archdev" --json jobs show "" +"$archdev" jobs logs "" +"$archdev" --json jobs cancel "" +"$archdev" --json jobs retry "" +``` + +Retry only failed non-PR jobs. Pull-request retries belong to the PR watcher. +Cancellation can be asynchronous; re-read the job until it reaches a terminal +state rather than reporting success from the request alone. + +Ordinary cleanup targets terminal jobs: + +```sh +"$archdev" --json jobs clean --status succeeded failed cancelled +``` + +`jobs clean --all` stops the runner and can permanently delete active jobs and +owned artifacts. Run it only after explicit user approval of the exact cleanup. + +## Runner lifecycle + +Use the runner namespace instead of the deprecated `daemon` commands: + +```sh +"$archdev" --json jobs runner doctor +"$archdev" jobs runner start +"$archdev" jobs runner stop +"$archdev" jobs runner uninstall +``` + +`runner uninstall` removes the native service but preserves repositories, job +history, reports, and project configuration. Run stop or uninstall only when the +user requested that local service change. + +## Repository delivery + +`jobs setup` normally enables the current repository. Use the explicit +repository lifecycle when diagnosing or scripting it: + +```sh +"$archdev" jobs repo enable +"$archdev" jobs repo submit +"$archdev" jobs repo sync +"$archdev" jobs repo disable +``` + +`repo submit` sends committed `HEAD` to the private runner and can rebase the +submitted branch according to repository policy. Check `git status`, the +current branch, and the intended commit first. `repo sync` updates a clean +source checkout only when the recorded identity fences match. + +For event-shaped diagnostics, submit only a payload whose schema the selected +binding expects: + +```sh +"$archdev" --json jobs trigger one_off --payload '{"prompt":"Run the focused verification"}' +``` + +Never invent successful logs, publication, or settlement. Inspect the terminal +job, attempts, and persisted output. Do not expose private worktree paths, +credentials, or customer data in prompts, payloads, or reports. diff --git a/skills/jobs/scripts/bootstrap.ps1 b/skills/jobs/scripts/bootstrap.ps1 new file mode 100644 index 0000000..40667c8 --- /dev/null +++ b/skills/jobs/scripts/bootstrap.ps1 @@ -0,0 +1,73 @@ +$ErrorActionPreference = "Stop" + +function Resolve-ArchDevPath([string]$Candidate) { + return (Resolve-Path -LiteralPath $Candidate).Path +} + +function Install-ArchDev { + $installDir = if ($env:ARCHDEV_INSTALL_DIR) { + $env:ARCHDEV_INSTALL_DIR + } else { + Join-Path $env:LOCALAPPDATA "ArchDev\bin" + } + $requestedVersion = if ($env:ARCHDEV_VERSION) { $env:ARCHDEV_VERSION } else { "latest" } + $baseUrl = if ($env:ARCHDEV_RELEASE_BASE_URL) { + $env:ARCHDEV_RELEASE_BASE_URL.TrimEnd('/') + } elseif ($requestedVersion -eq "latest") { + "https://github.com/ArchAstro/archdev/releases/latest/download" + } else { + $tag = if ($requestedVersion.StartsWith("v")) { $requestedVersion } else { "v$requestedVersion" } + "https://github.com/ArchAstro/archdev/releases/download/$tag" + } + $architecture = switch ($env:PROCESSOR_ARCHITECTURE.ToLowerInvariant()) { + "amd64" { "x64" } + "arm64" { "arm64" } + default { throw "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE" } + } + $asset = "archdev-windows-$architecture.zip" + $tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("archdev-skill-" + [Guid]::NewGuid().ToString("N")) + $archive = Join-Path $tempRoot $asset + $checksums = Join-Path $tempRoot "SHA256SUMS" + $extract = Join-Path $tempRoot "extract" + try { + New-Item -ItemType Directory -Path $extract -Force | Out-Null + Invoke-WebRequest -Uri "$baseUrl/$asset" -OutFile $archive + Invoke-WebRequest -Uri "$baseUrl/SHA256SUMS" -OutFile $checksums + $expectedLine = Select-String -Path $checksums -Pattern ([Regex]::Escape($asset) + '$') | Select-Object -First 1 + if (-not $expectedLine) { throw "Checksum missing for $asset" } + $expected = ($expectedLine.Line -split '\s+')[0] + $actual = (Get-FileHash $archive -Algorithm SHA256).Hash + if ($actual.ToLowerInvariant() -ne $expected.ToLowerInvariant()) { + throw "Checksum mismatch for $asset" + } + Expand-Archive -Path $archive -DestinationPath $extract -Force + $source = Join-Path $extract "archdev.exe" + if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { + throw "Archive is missing archdev.exe" + } + New-Item -ItemType Directory -Path $installDir -Force | Out-Null + Copy-Item $source (Join-Path $installDir "archdev.exe") -Force + Remove-Item (Join-Path $installDir "archdev-dashboard.exe") -Force -ErrorAction SilentlyContinue + } finally { + Remove-Item $tempRoot -Recurse -Force -ErrorAction SilentlyContinue + } + return (Resolve-ArchDevPath (Join-Path $installDir "archdev.exe")) +} + +$existing = Get-Command archdev -ErrorAction SilentlyContinue +$archdev = if ($existing) { Resolve-ArchDevPath $existing.Source } else { Install-ArchDev } + +& $archdev jobs setup --help *> $null +if ($LASTEXITCODE -ne 0) { + [Console]::Error.WriteLine("Updating ArchDev because this version lacks the Jobs domain.") + $archdev = Install-ArchDev +} + +if (-not (Test-Path -LiteralPath $archdev -PathType Leaf)) { + throw "ArchDev installation did not create an executable at $archdev" +} +& $archdev --version *> $null +if ($LASTEXITCODE -ne 0) { throw "ArchDev version verification failed" } +& $archdev jobs setup --help *> $null +if ($LASTEXITCODE -ne 0) { throw "Installed ArchDev does not provide the Jobs domain" } +Write-Output $archdev diff --git a/skills/jobs/scripts/bootstrap.sh b/skills/jobs/scripts/bootstrap.sh new file mode 100755 index 0000000..17f6133 --- /dev/null +++ b/skills/jobs/scripts/bootstrap.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +set -euo pipefail + +install_dir="${ARCHDEV_INSTALL_DIR:-$HOME/.local/bin}" +requested_version="${ARCHDEV_VERSION:-latest}" +release_base_url="${ARCHDEV_RELEASE_BASE_URL:-}" + +absolute_path() { + local candidate="$1" + local directory + directory="$(cd -P "$(dirname "$candidate")" && pwd)" + printf '%s/%s\n' "$directory" "$(basename "$candidate")" +} + +install_archdev() ( + set -euo pipefail + local platform architecture asset base temp_dir archive checksums expected actual + for command_name in curl tar mktemp install; do + command -v "$command_name" >/dev/null 2>&1 || { + printf 'Missing required command: %s\n' "$command_name" >&2 + exit 1 + } + done + case "$(uname -s)" in + Darwin) platform="darwin" ;; + Linux) platform="linux" ;; + *) printf 'Unsupported operating system: %s\n' "$(uname -s)" >&2; exit 1 ;; + esac + case "$(uname -m)" in + x86_64|amd64) architecture="x64" ;; + arm64|aarch64) architecture="arm64" ;; + *) printf 'Unsupported architecture: %s\n' "$(uname -m)" >&2; exit 1 ;; + esac + if [[ "$platform" == linux && "$architecture" == x64 ]] && + command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then + architecture="x64-musl" + fi + asset="archdev-${platform}-${architecture}.tar.gz" + if [[ -n "$release_base_url" ]]; then + base="${release_base_url%/}" + elif [[ "$requested_version" == latest ]]; then + base="https://github.com/ArchAstro/archdev/releases/latest/download" + else + [[ "$requested_version" == v* ]] || requested_version="v$requested_version" + base="https://github.com/ArchAstro/archdev/releases/download/$requested_version" + fi + temp_dir="$(mktemp -d)" + trap 'rm -rf "$temp_dir"' EXIT + archive="$temp_dir/$asset" + checksums="$temp_dir/SHA256SUMS" + curl --fail --silent --show-error --location "$base/$asset" --output "$archive" + curl --fail --silent --show-error --location "$base/SHA256SUMS" --output "$checksums" + expected="$(awk -v asset="$asset" '$2 == asset { print $1 }' "$checksums")" + [[ -n "$expected" ]] || { printf 'Checksum missing for %s\n' "$asset" >&2; exit 1; } + if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$archive" | awk '{print $1}')" + else + command -v shasum >/dev/null 2>&1 || { printf 'SHA-256 tool is required\n' >&2; exit 1; } + actual="$(shasum -a 256 "$archive" | awk '{print $1}')" + fi + [[ "$actual" == "$expected" ]] || { printf 'Checksum mismatch for %s\n' "$asset" >&2; exit 1; } + mkdir -p "$temp_dir/extract" "$install_dir" + tar -xzf "$archive" -C "$temp_dir/extract" + [[ -f "$temp_dir/extract/archdev" ]] || { printf 'Archive is missing archdev\n' >&2; exit 1; } + install -m 0755 "$temp_dir/extract/archdev" "$install_dir/archdev" + rm -f "$install_dir/archdev-dashboard" +) + +candidate="$(command -v archdev 2>/dev/null || true)" +if [[ -n "$candidate" ]]; then + executable="$(absolute_path "$candidate")" +else + install_archdev + executable="$(absolute_path "$install_dir/archdev")" +fi + +if ! "$executable" jobs setup --help >/dev/null 2>&1; then + printf 'Updating ArchDev because this version lacks the Jobs domain.\n' >&2 + install_archdev + executable="$(absolute_path "$install_dir/archdev")" +fi + +[[ -x "$executable" ]] || { + printf 'ArchDev installation did not create an executable at %s\n' "$executable" >&2 + exit 1 +} +"$executable" --version >&2 +"$executable" jobs setup --help >/dev/null 2>&1 || { + printf 'Installed ArchDev does not provide the Jobs domain.\n' >&2 + exit 1 +} +printf '%s\n' "$executable" diff --git a/skills/tasks/SKILL.md b/skills/tasks/SKILL.md new file mode 100644 index 0000000..71ed860 --- /dev/null +++ b/skills/tasks/SKILL.md @@ -0,0 +1,126 @@ +--- +name: tasks +description: Use when someone asks to plan work, create or import a dependency graph, find ready or blocked work, claim and fence a task, update task context, manage blockers or links, or complete shared work through ArchDev Tasks. +--- + +# Tasks + +ArchDev Tasks is the shared work ledger. It uses the ArchDev user session, but +it does not need `archdev setup`, a Jobs runner, a model provider, or repository +configuration. + +## Start + +Resolve the absolute path of this loaded `SKILL.md` from the host's skill +inventory. Do not derive it from the current repository. Bootstrap ArchDev from +that directory. + +Unix: + +```sh +skill_file="/absolute/path/to/loaded/tasks/SKILL.md" +skill_dir="$(cd -P "$(dirname "$skill_file")" && pwd)" +archdev="$(bash "$skill_dir/scripts/bootstrap.sh")" +``` + +Windows PowerShell: + +```powershell +$skillFile = 'C:\absolute\path\to\loaded\tasks\SKILL.md' +$skillDir = Split-Path -Parent (Resolve-Path -LiteralPath $skillFile) +$archdev = & powershell -NoProfile -File "$skillDir\scripts\bootstrap.ps1" +``` + +If bootstrap fails, report its error and point to the official ArchDev +installer. Run `"$archdev" tasks guide` before changing shared work; it is the +CLI's current claim, lease, dependency, and completion contract. + +Commands that only print help, the guide, or the graph schema work signed out. +Before reading or changing server-backed Tasks, run `"$archdev" auth status`. +On a nonzero result, run `"$archdev" auth login`, let the user finish browser +sign-in, then retry. Do not run umbrella setup or install Jobs for Tasks. + +## Inspect and plan + +Search before creating duplicate work. JSON output is preferred when another +agent will consume the result. + +```sh +"$archdev" --json tasks search "retry handling" +"$archdev" --json tasks ready --explain +"$archdev" --json tasks blocked +"$archdev" --json tasks show "" +``` + +`tasks ready` is a snapshot, not an ownership guarantee. Only `tasks claim` is +the atomic gate. + +For one task: + +```sh +"$archdev" --json tasks create "Add retry handling" --priority 1 --tag reliability +``` + +For a dependency-aware plan, render the schema, author a JSON graph, inspect it, +and then import it. Do not translate an existing plan into a series of +independent creates that can leave half a graph behind. + +```sh +"$archdev" tasks graph schema > task-graph.schema.json +"$archdev" --json tasks graph import --file plan.json +"$archdev" --json tasks deps add "" --blocked-by "" +"$archdev" --json tasks deps list "" +"$archdev" --json tasks deps remove "" --blocked-by "" +"$archdev" --json tasks deps cycles +``` + +## Claim and fence execution + +Claim immediately before starting implementation: + +```sh +"$archdev" --json tasks claim "" --session-name "short purpose" +``` + +Keep the returned `lease.id` and `session_id`. Use both values on every fenced +update and completion. Never borrow identifiers from another process or claim. + +```sh +"$archdev" --json tasks update "" --description "Current verified state" --lease-id "" --session-id "" +"$archdev" --json tasks comment "" "Concrete progress, blocker, or verification result" +"$archdev" --json tasks links add "" "" +"$archdev" --json tasks links remove "" "" +"$archdev" --json tasks close "" --lease-id "" --session-id "" +``` + +If work stops without completion, release the exact lease instead of leaving +ownership stale: + +```sh +"$archdev" --json tasks release "" --lease-id "" --session-id "" +``` + +Reopen completed work only when the user asks to resume that shared task: + +```sh +"$archdev" --json tasks reopen "" +``` + +A stale-lease or ownership refusal is authoritative. Re-read the task and ask +the user before taking over someone else's work. + +## Room-owned work + +Room work is still a Task. Use the canonical Tasks namespace and an explicit +Room ID: + +```sh +"$archdev" --json tasks list --room "" +"$archdev" --json tasks create "Watch deploy health" --room "" --kind daily +"$archdev" --json tasks close "" --room "" +``` + +Do not use the deprecated `rooms jobs` spelling. Do not create, claim, update, +close, release, link, comment on, or import Tasks unless the user requested the +corresponding shared-work mutation. Never put secrets or customer data in a +Task, comment, or graph. diff --git a/skills/tasks/scripts/bootstrap.ps1 b/skills/tasks/scripts/bootstrap.ps1 new file mode 100644 index 0000000..acd2fc6 --- /dev/null +++ b/skills/tasks/scripts/bootstrap.ps1 @@ -0,0 +1,73 @@ +$ErrorActionPreference = "Stop" + +function Resolve-ArchDevPath([string]$Candidate) { + return (Resolve-Path -LiteralPath $Candidate).Path +} + +function Install-ArchDev { + $installDir = if ($env:ARCHDEV_INSTALL_DIR) { + $env:ARCHDEV_INSTALL_DIR + } else { + Join-Path $env:LOCALAPPDATA "ArchDev\bin" + } + $requestedVersion = if ($env:ARCHDEV_VERSION) { $env:ARCHDEV_VERSION } else { "latest" } + $baseUrl = if ($env:ARCHDEV_RELEASE_BASE_URL) { + $env:ARCHDEV_RELEASE_BASE_URL.TrimEnd('/') + } elseif ($requestedVersion -eq "latest") { + "https://github.com/ArchAstro/archdev/releases/latest/download" + } else { + $tag = if ($requestedVersion.StartsWith("v")) { $requestedVersion } else { "v$requestedVersion" } + "https://github.com/ArchAstro/archdev/releases/download/$tag" + } + $architecture = switch ($env:PROCESSOR_ARCHITECTURE.ToLowerInvariant()) { + "amd64" { "x64" } + "arm64" { "arm64" } + default { throw "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE" } + } + $asset = "archdev-windows-$architecture.zip" + $tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("archdev-skill-" + [Guid]::NewGuid().ToString("N")) + $archive = Join-Path $tempRoot $asset + $checksums = Join-Path $tempRoot "SHA256SUMS" + $extract = Join-Path $tempRoot "extract" + try { + New-Item -ItemType Directory -Path $extract -Force | Out-Null + Invoke-WebRequest -Uri "$baseUrl/$asset" -OutFile $archive + Invoke-WebRequest -Uri "$baseUrl/SHA256SUMS" -OutFile $checksums + $expectedLine = Select-String -Path $checksums -Pattern ([Regex]::Escape($asset) + '$') | Select-Object -First 1 + if (-not $expectedLine) { throw "Checksum missing for $asset" } + $expected = ($expectedLine.Line -split '\s+')[0] + $actual = (Get-FileHash $archive -Algorithm SHA256).Hash + if ($actual.ToLowerInvariant() -ne $expected.ToLowerInvariant()) { + throw "Checksum mismatch for $asset" + } + Expand-Archive -Path $archive -DestinationPath $extract -Force + $source = Join-Path $extract "archdev.exe" + if (-not (Test-Path -LiteralPath $source -PathType Leaf)) { + throw "Archive is missing archdev.exe" + } + New-Item -ItemType Directory -Path $installDir -Force | Out-Null + Copy-Item $source (Join-Path $installDir "archdev.exe") -Force + Remove-Item (Join-Path $installDir "archdev-dashboard.exe") -Force -ErrorAction SilentlyContinue + } finally { + Remove-Item $tempRoot -Recurse -Force -ErrorAction SilentlyContinue + } + return (Resolve-ArchDevPath (Join-Path $installDir "archdev.exe")) +} + +$existing = Get-Command archdev -ErrorAction SilentlyContinue +$archdev = if ($existing) { Resolve-ArchDevPath $existing.Source } else { Install-ArchDev } + +& $archdev tasks guide --help *> $null +if ($LASTEXITCODE -ne 0) { + [Console]::Error.WriteLine("Updating ArchDev because this version lacks the Tasks domain.") + $archdev = Install-ArchDev +} + +if (-not (Test-Path -LiteralPath $archdev -PathType Leaf)) { + throw "ArchDev installation did not create an executable at $archdev" +} +& $archdev --version *> $null +if ($LASTEXITCODE -ne 0) { throw "ArchDev version verification failed" } +& $archdev tasks guide --help *> $null +if ($LASTEXITCODE -ne 0) { throw "Installed ArchDev does not provide the Tasks domain" } +Write-Output $archdev diff --git a/skills/tasks/scripts/bootstrap.sh b/skills/tasks/scripts/bootstrap.sh new file mode 100755 index 0000000..8f2c048 --- /dev/null +++ b/skills/tasks/scripts/bootstrap.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash + +set -euo pipefail + +install_dir="${ARCHDEV_INSTALL_DIR:-$HOME/.local/bin}" +requested_version="${ARCHDEV_VERSION:-latest}" +release_base_url="${ARCHDEV_RELEASE_BASE_URL:-}" + +absolute_path() { + local candidate="$1" + local directory + directory="$(cd -P "$(dirname "$candidate")" && pwd)" + printf '%s/%s\n' "$directory" "$(basename "$candidate")" +} + +install_archdev() ( + set -euo pipefail + local platform architecture asset base temp_dir archive checksums expected actual + for command_name in curl tar mktemp install; do + command -v "$command_name" >/dev/null 2>&1 || { + printf 'Missing required command: %s\n' "$command_name" >&2 + exit 1 + } + done + case "$(uname -s)" in + Darwin) platform="darwin" ;; + Linux) platform="linux" ;; + *) printf 'Unsupported operating system: %s\n' "$(uname -s)" >&2; exit 1 ;; + esac + case "$(uname -m)" in + x86_64|amd64) architecture="x64" ;; + arm64|aarch64) architecture="arm64" ;; + *) printf 'Unsupported architecture: %s\n' "$(uname -m)" >&2; exit 1 ;; + esac + if [[ "$platform" == linux && "$architecture" == x64 ]] && + command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then + architecture="x64-musl" + fi + asset="archdev-${platform}-${architecture}.tar.gz" + if [[ -n "$release_base_url" ]]; then + base="${release_base_url%/}" + elif [[ "$requested_version" == latest ]]; then + base="https://github.com/ArchAstro/archdev/releases/latest/download" + else + [[ "$requested_version" == v* ]] || requested_version="v$requested_version" + base="https://github.com/ArchAstro/archdev/releases/download/$requested_version" + fi + temp_dir="$(mktemp -d)" + trap 'rm -rf "$temp_dir"' EXIT + archive="$temp_dir/$asset" + checksums="$temp_dir/SHA256SUMS" + curl --fail --silent --show-error --location "$base/$asset" --output "$archive" + curl --fail --silent --show-error --location "$base/SHA256SUMS" --output "$checksums" + expected="$(awk -v asset="$asset" '$2 == asset { print $1 }' "$checksums")" + [[ -n "$expected" ]] || { printf 'Checksum missing for %s\n' "$asset" >&2; exit 1; } + if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$archive" | awk '{print $1}')" + else + command -v shasum >/dev/null 2>&1 || { printf 'SHA-256 tool is required\n' >&2; exit 1; } + actual="$(shasum -a 256 "$archive" | awk '{print $1}')" + fi + [[ "$actual" == "$expected" ]] || { printf 'Checksum mismatch for %s\n' "$asset" >&2; exit 1; } + mkdir -p "$temp_dir/extract" "$install_dir" + tar -xzf "$archive" -C "$temp_dir/extract" + [[ -f "$temp_dir/extract/archdev" ]] || { printf 'Archive is missing archdev\n' >&2; exit 1; } + install -m 0755 "$temp_dir/extract/archdev" "$install_dir/archdev" + rm -f "$install_dir/archdev-dashboard" +) + +candidate="$(command -v archdev 2>/dev/null || true)" +if [[ -n "$candidate" ]]; then + executable="$(absolute_path "$candidate")" +else + install_archdev + executable="$(absolute_path "$install_dir/archdev")" +fi + +if ! "$executable" tasks guide --help >/dev/null 2>&1; then + printf 'Updating ArchDev because this version lacks the Tasks domain.\n' >&2 + install_archdev + executable="$(absolute_path "$install_dir/archdev")" +fi + +[[ -x "$executable" ]] || { + printf 'ArchDev installation did not create an executable at %s\n' "$executable" >&2 + exit 1 +} +"$executable" --version >&2 +"$executable" tasks guide --help >/dev/null 2>&1 || { + printf 'Installed ArchDev does not provide the Tasks domain.\n' >&2 + exit 1 +} +printf '%s\n' "$executable" diff --git a/tests/jobs-skill.sh b/tests/jobs-skill.sh new file mode 100755 index 0000000..f235056 --- /dev/null +++ b/tests/jobs-skill.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +root="$(mktemp -d)" +trap 'rm -rf "$root"' EXIT + +mkdir -p "$root/home" "$root/global-project" "$root/project" +git -C "$root/global-project" init -q +git -C "$root/project" init -q +git -C "$root/project" config user.name 'Jobs Skill Test' +git -C "$root/project" config user.email 'jobs-skill@test.invalid' +printf 'tracked input\n' >"$root/project/input.txt" +git -C "$root/project" add input.txt +git -C "$root/project" commit -qm 'Seed exact input' + +# Package installation: npx skills must retain the complete self-contained +# Jobs package in both supported scopes. +HOME="$root/home" npx --yes skills add "$repo" \ + --global --skill jobs --agent codex --yes --copy >/dev/null +test -x "$root/home/.agents/skills/jobs/scripts/bootstrap.sh" +( + cd "$root/project" + HOME="$root/home" npx --yes skills add "$repo" \ + --skill jobs --agent codex --yes --copy >/dev/null +) +test -x "$root/project/.agents/skills/jobs/scripts/bootstrap.sh" +grep -F 'releases/latest/download' \ + "$root/project/.agents/skills/jobs/scripts/bootstrap.sh" >/dev/null +grep -F 'releases/latest/download' \ + "$root/project/.agents/skills/jobs/scripts/bootstrap.ps1" >/dev/null +grep -F 'jobs setup --help' \ + "$root/project/.agents/skills/jobs/scripts/bootstrap.ps1" >/dev/null +if grep -ERq 'raw\.githubusercontent\.com/.*/(main|refs/heads)' \ + "$root/project/.agents/skills/jobs/scripts"; then + printf 'Jobs bootstrap must not execute a mutable branch installer.\n' >&2 + exit 1 +fi + +# Cold bootstrap: the skill downloads a release archive, verifies its published +# checksum, and returns the absolute CLI path even when it is not on PATH. +mkdir -p "$root/release/fixture" +cat >"$root/release/fixture/archdev" <<'ARCHDEV' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$*" >>"${ARCHDEV_TEST_LOG:?}" +case "$*" in + "--version") printf '0.33.2\n' ;; + "jobs setup --help") ;; + "check") printf 'Configuration is valid.\n' ;; + "--json jobs runner status") printf '{"state":"stopped"}\n' ;; + "--json jobs list") printf '[]\n' ;; + "jobs run verification --here") printf 'foreground verification passed\n' ;; + "jobs setup") + mkdir -p "$HOME/.archdev" + touch "$HOME/.archdev/runner-installed" + printf '{"project_id":"project_test","runner":"running"}\n' + ;; + "--json jobs run verification") printf '{"id":"job_test","state":"queued","commit_oid":"commit_test"}\n' ;; + "--json jobs show job_test") printf '{"id":"job_test","state":"succeeded","commit_oid":"commit_test"}\n' ;; + "jobs logs job_test") printf 'focused verification passed\n' ;; + "--json jobs cancel job_test") printf '{"id":"job_test","state":"cancelled"}\n' ;; + "--json jobs retry job_test") printf '{"id":"job_test","state":"queued"}\n' ;; + "--json jobs clean --status succeeded failed cancelled") printf '{"matched":1,"deleted":["job_test"],"failed":[]}\n' ;; + "--json jobs runner doctor") printf '{"healthy":true}\n' ;; + "jobs runner start") printf 'Runner started.\n' ;; + "jobs runner stop") printf 'Runner stopped.\n' ;; + "jobs runner uninstall") printf 'Runner uninstalled; data preserved.\n' ;; + "jobs repo enable") printf 'Repository enabled.\n' ;; + "jobs repo submit") printf 'Submitted commit_test.\n' ;; + "jobs repo sync") printf 'Synchronized commit_test.\n' ;; + "jobs repo disable") printf 'Repository disabled.\n' ;; + "--json jobs trigger one_off --payload "*) printf '{"id":"job_trigger","state":"queued"}\n' ;; + *) printf 'unexpected fake ArchDev arguments: %s\n' "$*" >&2; exit 2 ;; +esac +ARCHDEV +chmod 0755 "$root/release/fixture/archdev" +case "$(uname -s)-$(uname -m)" in + Darwin-arm64) target="darwin-arm64" ;; + Darwin-x86_64) target="darwin-x64" ;; + Linux-aarch64) target="linux-arm64" ;; + Linux-x86_64) target="linux-x64" ;; + *) printf 'Unsupported test host: %s %s\n' "$(uname -s)" "$(uname -m)" >&2; exit 1 ;; +esac +if [[ "$target" == linux-x64 ]] && command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then + target="linux-x64-musl" +fi +asset="archdev-$target.tar.gz" +tar -C "$root/release/fixture" -czf "$root/release/$asset" archdev +( + cd "$root/release" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$asset" >SHA256SUMS + else + shasum -a 256 "$asset" >SHA256SUMS + fi +) + +log="$root/archdev.log" +mkdir -p "$root/bin" +touch "$root/bin/archdev-dashboard" +binary="$( + cd "$root/project" + HOME="$root/home" \ + PATH="/usr/bin:/bin" \ + ARCHDEV_INSTALL_DIR="$root/bin" \ + ARCHDEV_RELEASE_BASE_URL="file://$root/release" \ + ARCHDEV_TEST_LOG="$log" \ + bash .agents/skills/jobs/scripts/bootstrap.sh +)" +expected_binary="$(cd -P "$root/bin" && pwd)/archdev" +test "$binary" = "$expected_binary" +test -x "$binary" +test ! -e "$root/bin/archdev-dashboard" +cp -R "$root/release" "$root/tampered-release" +printf '%064d %s\n' 0 "$asset" >"$root/tampered-release/SHA256SUMS" +if HOME="$root/tampered-home" \ + PATH="/usr/bin:/bin" \ + ARCHDEV_INSTALL_DIR="$root/tampered-bin" \ + ARCHDEV_RELEASE_BASE_URL="file://$root/tampered-release" \ + bash "$root/project/.agents/skills/jobs/scripts/bootstrap.sh" >/dev/null 2>&1; then + printf 'Jobs bootstrap accepted a release with the wrong checksum.\n' >&2 + exit 1 +fi +test ! -e "$root/tampered-bin/archdev" + +run_archdev() { + ( + cd "$root/project" + HOME="$root/home" ARCHDEV_TEST_LOG="$log" "$binary" "$@" + ) +} + +# First-use story: inspect before mutation, choose the foreground path once, +# then enable durable execution and observe the exact submitted job. +run_archdev check >/dev/null +run_archdev --json jobs runner status >/dev/null +run_archdev --json jobs list >/dev/null +run_archdev jobs run verification --here >/dev/null +run_archdev jobs setup >/dev/null +test -e "$root/home/.archdev/runner-installed" +created="$(run_archdev --json jobs run verification)" +printf '%s' "$created" | grep -F '"id":"job_test"' >/dev/null +printf '%s' "$created" | grep -F '"commit_oid":"commit_test"' >/dev/null +shown="$(run_archdev --json jobs show job_test)" +printf '%s' "$shown" | grep -F '"state":"succeeded"' >/dev/null +run_archdev jobs logs job_test >/dev/null +run_archdev --json jobs cancel job_test >/dev/null +run_archdev --json jobs retry job_test >/dev/null +run_archdev --json jobs clean --status succeeded failed cancelled >/dev/null +run_archdev --json jobs runner doctor >/dev/null +run_archdev jobs runner stop >/dev/null +run_archdev jobs runner start >/dev/null +run_archdev jobs repo enable >/dev/null +run_archdev jobs repo submit >/dev/null +run_archdev jobs repo sync >/dev/null +run_archdev --json jobs trigger one_off --payload '{"prompt":"Run the focused verification"}' >/dev/null +run_archdev jobs repo disable >/dev/null +run_archdev jobs runner uninstall >/dev/null + +# Jobs setup must remain local and must not invent a user or provider login. +if grep -Eq 'auth login|agents setup|provider login|^setup$' "$log"; then + printf 'Jobs skill crossed into authentication or another setup domain.\n' >&2 + exit 1 +fi +test ! -e "$root/home/.archdev-test-authenticated" +test ! -e "$root/home/.archdev/credentials" + +printf 'Jobs installs in both scopes and completes foreground and durable automation without authentication.\n' diff --git a/tests/tasks-skill.sh b/tests/tasks-skill.sh new file mode 100755 index 0000000..1142ad5 --- /dev/null +++ b/tests/tasks-skill.sh @@ -0,0 +1,153 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo="$(cd -P "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +root="$(mktemp -d)" +trap 'rm -rf "$root"' EXIT + +mkdir -p "$root/home" "$root/global-project" "$root/project" +git -C "$root/global-project" init -q +git -C "$root/project" init -q + +# Package installation: npx skills must retain the complete self-contained +# Tasks package in both supported scopes. +HOME="$root/home" npx --yes skills add "$repo" \ + --global --skill tasks --agent codex --yes --copy >/dev/null +test -x "$root/home/.agents/skills/tasks/scripts/bootstrap.sh" +( + cd "$root/project" + HOME="$root/home" npx --yes skills add "$repo" \ + --skill tasks --agent codex --yes --copy >/dev/null +) +test -x "$root/project/.agents/skills/tasks/scripts/bootstrap.sh" +grep -F 'releases/latest/download' \ + "$root/project/.agents/skills/tasks/scripts/bootstrap.sh" >/dev/null +grep -F 'releases/latest/download' \ + "$root/project/.agents/skills/tasks/scripts/bootstrap.ps1" >/dev/null +grep -F 'tasks guide --help' \ + "$root/project/.agents/skills/tasks/scripts/bootstrap.ps1" >/dev/null +if grep -ERq 'raw\.githubusercontent\.com/.*/(main|refs/heads)' \ + "$root/project/.agents/skills/tasks/scripts"; then + printf 'Tasks bootstrap must not execute a mutable branch installer.\n' >&2 + exit 1 +fi + +# Cold bootstrap: the skill downloads a release archive, verifies its published +# checksum, and returns the absolute CLI path even when it is not on PATH. +mkdir -p "$root/release/fixture" +cat >"$root/release/fixture/archdev" <<'ARCHDEV' +#!/usr/bin/env bash +set -euo pipefail +printf '%s\n' "$*" >>"${ARCHDEV_TEST_LOG:?}" +case "$*" in + "--version") printf '0.33.2\n' ;; + "tasks guide --help") ;; + "tasks guide") printf 'Claim atomically and retain the lease and session IDs.\n' ;; + "auth status") test -f "$HOME/.archdev-test-authenticated" ;; + "auth login") touch "$HOME/.archdev-test-authenticated"; printf 'Signed in\n' ;; + "--json tasks claim "*) printf '{"lease":{"id":"lease_test"},"session_id":"session_test","task":{"id":"tsk_ready","status":"in_progress"}}\n' ;; + "--json tasks "*) printf '{"ok":true}\n' ;; + "tasks graph schema") printf '{"type":"object"}\n' ;; + *) printf 'unexpected fake ArchDev arguments: %s\n' "$*" >&2; exit 2 ;; +esac +ARCHDEV +chmod 0755 "$root/release/fixture/archdev" +case "$(uname -s)-$(uname -m)" in + Darwin-arm64) target="darwin-arm64" ;; + Darwin-x86_64) target="darwin-x64" ;; + Linux-aarch64) target="linux-arm64" ;; + Linux-x86_64) target="linux-x64" ;; + *) printf 'Unsupported test host: %s %s\n' "$(uname -s)" "$(uname -m)" >&2; exit 1 ;; +esac +if [[ "$target" == linux-x64 ]] && command -v ldd >/dev/null 2>&1 && ldd --version 2>&1 | grep -qi musl; then + target="linux-x64-musl" +fi +asset="archdev-$target.tar.gz" +tar -C "$root/release/fixture" -czf "$root/release/$asset" archdev +( + cd "$root/release" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum "$asset" >SHA256SUMS + else + shasum -a 256 "$asset" >SHA256SUMS + fi +) + +log="$root/archdev.log" +mkdir -p "$root/bin" +touch "$root/bin/archdev-dashboard" +binary="$( + cd "$root/project" + HOME="$root/home" \ + PATH="/usr/bin:/bin" \ + ARCHDEV_INSTALL_DIR="$root/bin" \ + ARCHDEV_RELEASE_BASE_URL="file://$root/release" \ + ARCHDEV_TEST_LOG="$log" \ + bash .agents/skills/tasks/scripts/bootstrap.sh +)" +expected_binary="$(cd -P "$root/bin" && pwd)/archdev" +test "$binary" = "$expected_binary" +test -x "$binary" +test ! -e "$root/bin/archdev-dashboard" +cp -R "$root/release" "$root/tampered-release" +printf '%064d %s\n' 0 "$asset" >"$root/tampered-release/SHA256SUMS" +if HOME="$root/tampered-home" \ + PATH="/usr/bin:/bin" \ + ARCHDEV_INSTALL_DIR="$root/tampered-bin" \ + ARCHDEV_RELEASE_BASE_URL="file://$root/tampered-release" \ + bash "$root/project/.agents/skills/tasks/scripts/bootstrap.sh" >/dev/null 2>&1; then + printf 'Tasks bootstrap accepted a release with the wrong checksum.\n' >&2 + exit 1 +fi +test ! -e "$root/tampered-bin/archdev" + +run_archdev() { + HOME="$root/home" ARCHDEV_TEST_LOG="$log" "$binary" "$@" +} + +# First-use story: start signed out, authenticate once, inspect shared work, +# author dependencies, claim atomically, and preserve the returned fence. +run_archdev tasks guide >/dev/null +if run_archdev auth status >/dev/null 2>&1; then + printf 'Expected the cold Tasks user to start signed out.\n' >&2 + exit 1 +fi +run_archdev auth login >/dev/null +run_archdev auth status +run_archdev --json tasks search 'retry handling' >/dev/null +run_archdev --json tasks ready --explain >/dev/null +run_archdev --json tasks blocked >/dev/null +run_archdev --json tasks show tsk_ready >/dev/null +run_archdev --json tasks create 'Add retry handling' --priority 1 --tag reliability >/dev/null +run_archdev tasks graph schema >"$root/task-graph.schema.json" +printf '{"tasks":[]}\n' >"$root/project/plan.json" +run_archdev --json tasks graph import --file "$root/project/plan.json" >/dev/null +run_archdev --json tasks deps add tsk_ready --blocked-by tsk_prerequisite >/dev/null +run_archdev --json tasks deps list tsk_ready >/dev/null +run_archdev --json tasks deps remove tsk_ready --blocked-by tsk_prerequisite >/dev/null +run_archdev --json tasks deps cycles >/dev/null +claim="$(run_archdev --json tasks claim tsk_ready --session-name 'retry implementation')" +printf '%s' "$claim" | grep -F '"id":"lease_test"' >/dev/null +printf '%s' "$claim" | grep -F '"session_id":"session_test"' >/dev/null +run_archdev --json tasks update tsk_ready --description 'Current verified state' --lease-id lease_test --session-id session_test >/dev/null +run_archdev --json tasks comment tsk_ready 'Focused verification passed' >/dev/null +run_archdev --json tasks links add tsk_ready 'owner/repo#42' >/dev/null +run_archdev --json tasks links remove tsk_ready 'owner/repo#42' >/dev/null +run_archdev --json tasks close tsk_ready --lease-id lease_test --session-id session_test >/dev/null +run_archdev --json tasks claim tsk_abandoned --session-name 'abandoned follow-up' >/dev/null +run_archdev --json tasks release tsk_abandoned --lease-id lease_test --session-id session_test >/dev/null +run_archdev --json tasks reopen tsk_completed >/dev/null +run_archdev --json tasks list --room tem_room >/dev/null +run_archdev --json tasks create 'Watch deploy health' --room tem_room --kind daily >/dev/null +run_archdev --json tasks close tsk_room --room tem_room >/dev/null + +# Tasks stays independent of local automation and repository setup. +test ! -e "$root/project/archdev.json" +test ! -e "$root/home/.archdev/daemon.sqlite" +if grep -Eq '(^| )jobs( |$)|provider|agents setup| setup$' "$log"; then + printf 'Tasks skill crossed into another setup domain.\n' >&2 + exit 1 +fi + +printf 'Tasks installs in both scopes and completes an authenticated fenced work lifecycle without Jobs.\n'