Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .claude/agent-memory/archgate-developer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Skipping steps 2 or 3 is a workflow violation. The user should NEVER have to inv
- **Inquirer prompts leave cursor at wrong column on Windows — always reset with `cursorTo`** — After an `inquirer.prompt()` call finishes (especially checkbox with long wrapped answer lines), the cursor is left at the end of the rendered answer text. On Windows terminals, `\n` moves down but does NOT reset to column 0, so all subsequent output starts at the wrong horizontal offset. Fix: call `cursorTo(process.stdout, 0)` from `node:readline` after each prompt returns, guarded by `if (process.stdout.isTTY)`. Applied in `src/helpers/editor-detect.ts` for `promptEditorSelection()` and `promptSingleEditorSelection()`. The `upgrade.ts` command already uses the same `clearLine`/`cursorTo` pattern for download progress cleanup.
- **`inquirer` must be lazy-loaded via dynamic `import()` — never statically imported at module level** — `inquirer` costs ~200ms to parse. Static `import inquirer from "inquirer"` at module level forces every CLI invocation to pay this cost — even `--help`, `--version`, and non-interactive commands that never prompt. Always use `const { default: inquirer } = await import("inquirer")` at the call site, inside the action handler or function that actually needs it. Applied in `src/commands/adr/create.ts`, `src/commands/init.ts`, `src/helpers/editor-detect.ts`, `src/helpers/login-flow.ts`. Same principle applies to any heavy dependency used only in specific code paths (e.g., Sentry and PostHog SDKs are already lazy-loaded in `sentry.ts` and `telemetry.ts`).
- **Telemetry/Sentry init should be started eagerly but awaited lazily** — In `src/cli.ts`, `initSentry()` + `initTelemetry()` are started before command registration (so they run concurrently with setup) but only awaited in the `preAction` hook (right before the first telemetry event). This defers ~150ms of SDK parsing + git subprocess cost for `--help`/`--version` which never trigger `preAction`. The `preAction` hook is `async` to support this `await`.
- **Smoke test install-script steps must find a release with uploaded assets, not just the latest tag** — On release-commit pushes to main, the Validate and Release workflows trigger concurrently. The Release workflow creates the tag/release before `release-binaries.yml` uploads platform binaries. Smoke tests that naively use `gh release view --json tagName` get the just-created release and 404 on the binary download. Fix: iterate `gh release list --limit 5` and check each release's assets for the expected artifact (`archgate-win32-x64.zip` / `archgate-linux-x64.tar.gz`) before selecting the version. Applied in `smoke-test-windows.yml` and `smoke-test-linux.yml`.
- **GitHub CodeQL "default setup" can silently skip PRs — use an explicit workflow** — The repository-level "default setup" for CodeQL does not guarantee analysis on every PR. PR #279 (Renovate deps update) had zero CodeQL analyses, dropping the Scorecard SAST score from 10 to 9. Fix: add an explicit `.github/workflows/codeql.yml` that runs on `push: [main]`, `pull_request: [main]`, and a weekly schedule. After merging, disable the "default setup" in repository Settings > Code security > Code scanning to avoid duplicate analyses. The explicit workflow gives Scorecard a detectable `github/codeql-action` reference and guarantees coverage.
- **`GITHUB_TOKEN`-authored pushes do NOT trigger downstream workflows — release.yml MUST use the GH App token** — When an Actions workflow pushes commits or opens PRs using `${{ github.token }}` / `secrets.GITHUB_TOKEN`, GitHub intentionally suppresses the resulting `push` / `pull_request` events to prevent recursion. Symptom on release PRs: the head SHA has no `pull_request`-event check runs, so `Validate Code` / `Lint, Test & Check` / `DCO Sign-off Check` are missing from the PR rollup and branch protection treats the PR as missing required checks. PR [#131](https://github.com/archgate/cli/pull/131) papered over this by manually `gh workflow run` + posting commit statuses, but `workflow_dispatch` runs land on `head_branch: release` with `pull_requests: []` — they are not associated with the PR ref, so `Lint, Test & Check` stayed orphaned and the bug recurred on PR [#251](https://github.com/archgate/cli/pull/251). Root-cause fix: in `release.yml` the `pull-request` job MUST generate a GitHub App installation token via `actions/create-github-app-token` (using `secrets.GH_APP_APP_ID` / `secrets.GH_APP_PRIVATE_KEY`) and pass it to BOTH `actions/checkout` and `simple-release-action`. App-token-authored pushes DO trigger `pull_request` events naturally. Apply the same pattern to any future workflow that pushes to a branch whose downstream CI must run.

## Validation Pipeline
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CodeQL

on:
push:
branches:
- main
pull_request:
branches:
- main
schedule:
# Weekly Monday 06:30 UTC (offset from Scorecard at 06:15)
- cron: "30 6 * * 1"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
ARCHGATE_TELEMETRY: "0"

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
security-events: write
contents: read

strategy:
fail-fast: false
matrix:
language:
- javascript-typescript
- actions

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6

- name: Initialize CodeQL
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: "/language:${{ matrix.language }}"
2 changes: 1 addition & 1 deletion .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ jobs:
publish_results: true

- name: Upload SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v4.35.3
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
sarif_file: results.sarif
22 changes: 20 additions & 2 deletions .github/workflows/smoke-test-linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,29 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
if [ -z "$ARCHGATE_VERSION" ]; then
ARCHGATE_VERSION="$(gh release view --json tagName --jq .tagName 2>/dev/null || true)"
if [ -z "$ARCHGATE_VERSION" ]; then
# Find the newest release that already has the Linux asset uploaded.
# On release-commit pushes the Validate and Release workflows start
# concurrently, so the latest tag may exist before its binaries are
# uploaded by release-binaries.yml — hitting a 404.
asset="archgate-linux-x64.tar.gz"
tags="$(gh release list --limit 5 --json tagName --jq '.[].tagName' 2>/dev/null || true)"
if [ -z "$tags" ]; then
echo "::warning::No releases found, skipping install.sh smoke test"
exit 0
fi
found=""
for tag in $tags; do
assets="$(gh release view "$tag" --json assets --jq '.assets[].name' 2>/dev/null || true)"
if echo "$assets" | grep -qF "$asset"; then
ARCHGATE_VERSION="$tag"
found=1
break
fi
done
if [ -z "$found" ]; then
echo "::warning::No release with $asset found, skipping install.sh smoke test"
exit 0
fi
export ARCHGATE_VERSION
fi
echo "Testing install.sh with version $ARCHGATE_VERSION"
Expand Down
25 changes: 22 additions & 3 deletions .github/workflows/smoke-test-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,31 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: |
if (-not $env:ARCHGATE_VERSION) {
$release = gh release view --json tagName --jq .tagName 2>$null
if ($LASTEXITCODE -ne 0 -or -not $release) {
# Find the newest release that already has the Windows asset uploaded.
# On release-commit pushes the Validate and Release workflows start
# concurrently, so the latest tag may exist before its binaries are
# uploaded by release-binaries.yml — hitting a 404.
$asset = "archgate-win32-x64.zip"
$tags = gh release list --limit 5 --json tagName --jq '.[].tagName' 2>$null
if ($LASTEXITCODE -ne 0 -or -not $tags) {
Write-Host "::warning::No releases found, skipping install.ps1 smoke test"
exit 0
}
$env:ARCHGATE_VERSION = $release
$found = $false
foreach ($tag in $tags -split "`n") {
$tag = $tag.Trim()
if (-not $tag) { continue }
$assets = gh release view $tag --json assets --jq '.assets[].name' 2>$null
if ($assets -match [regex]::Escape($asset)) {
$env:ARCHGATE_VERSION = $tag
$found = $true
break
}
}
if (-not $found) {
Write-Host "::warning::No release with $asset found, skipping install.ps1 smoke test"
exit 0
}
}
Write-Host "Testing install.ps1 with version $env:ARCHGATE_VERSION"

Expand Down
Loading