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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: ci

on:
push:
branches: [main, npm-kit]
branches: [main, develop, npm-kit]
pull_request:
workflow_dispatch:

Expand Down Expand Up @@ -60,7 +60,7 @@ jobs:
# The smoke test proves local CLI behavior, not npm reachability.
# Seed a fresh empty drift cache so an offline Windows runner does
# not pay four sequential 20s `npm view` timeouts inside status.
node -e 'const fs=require("node:fs"),p=require("node:path"),base=process.platform==="win32"?process.env.APPDATA:process.env.XDG_CONFIG_HOME,dir=p.join(base,"agentic-kit"),last=Date.now();fs.mkdirSync(dir,{recursive:true});fs.writeFileSync(p.join(dir,"kit.json"),JSON.stringify({versionCheck:{last,seen:{},self:{last,best:null}}}))'
node -e 'const fs=require("node:fs"),p=require("node:path"),base=process.platform==="win32"?process.env.APPDATA:process.env.XDG_CONFIG_HOME,dir=p.join(base,"agentic-kit"),last=Date.now();fs.mkdirSync(dir,{recursive:true});fs.writeFileSync(p.join(dir,"kit.json"),JSON.stringify({versionCheck:{last,seen:{},self:{last,best:null,lastTags:["latest","next"]}}}))'
node bin/agentic-kit.mjs --version
node bin/agentic-kit.mjs --help --all > /dev/null
# status must emit valid JSON and exit deterministically even on a
Expand Down Expand Up @@ -89,6 +89,8 @@ jobs:
- name: Install devDependencies
run: pnpm install --frozen-lockfile

- name: Tracked JavaScript comment and test-title guard
run: pnpm run test:quality
- name: Typecheck (tsc --checkJs)
run: pnpm run typecheck
- name: Lint (eslint)
Expand Down
54 changes: 53 additions & 1 deletion .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,12 @@ jobs:
# same teardown abort on store-touching commands (`memory search` → correct
# output, rc 134), so an `--only memory-routes` step added here would need the same
# guard. Remove continue-on-error once that issue closes.
- name: Require Node 22.15+ for the macOS resolution hook
if: matrix.os == 'macos-latest'
run: node -e "const [major, minor] = process.versions.node.split('.').map(Number); if (major < 22 || (major === 22 && minor < 15)) { console.error('trace-ort requires Node 22.15+'); process.exit(1); }"

- name: Deep proof against the live packages (learning)
if: matrix.os != 'macos-latest'
continue-on-error: true
env:
HOME: ${{ runner.temp }}/kit-home
Expand All @@ -97,6 +102,52 @@ jobs:
APPDATA: ${{ runner.temp }}/kit-home/AppData/Roaming
run: node bin/agentic-kit.mjs status --refresh=live --only learning

- name: Deep proof against the live packages (learning, traced macOS)
if: matrix.os == 'macos-latest'
continue-on-error: true
shell: bash
env:
HOME: ${{ runner.temp }}/kit-home
USERPROFILE: ${{ runner.temp }}/kit-home
XDG_CONFIG_HOME: ${{ runner.temp }}/kit-home/.config
APPDATA: ${{ runner.temp }}/kit-home/AppData/Roaming
NODE_OPTIONS: --import=${{ github.workspace }}/scripts/trace-ort.mjs
TRACE_ORT_LOG: ${{ runner.temp }}/trace-ort.jsonl
run: |
set +e
node bin/agentic-kit.mjs status --refresh=live --only learning
learning_rc=$?
LEARNING_RC="$learning_rc" NODE_OPTIONS='' node --input-type=module -e '
import fs from "node:fs";
import crypto from "node:crypto";
const source = fs.readFileSync("scripts/trace-ort.mjs");
fs.writeFileSync(process.env.RUNNER_TEMP + "/trace-ort-receipt.json", JSON.stringify({
sourceSha: process.env.GITHUB_SHA,
hookSha256: crypto.createHash("sha256").update(source).digest("hex"),
node: process.version, platform: process.platform, arch: process.arch,
learningExitCode: Number(process.env.LEARNING_RC),
tracePresent: fs.existsSync(process.env.TRACE_ORT_LOG),
}) + "\n");
'
exit "$learning_rc"

- name: Check macOS trace artifact
if: always() && matrix.os == 'macos-latest'
shell: bash
run: |
test -s "$RUNNER_TEMP/trace-ort.jsonl" || { echo '::error::trace-ort artifact absent or empty'; exit 1; }
test -s "$RUNNER_TEMP/trace-ort-receipt.json" || { echo '::error::trace-ort receipt absent or empty'; exit 1; }

- name: Upload macOS learning resolution trace
if: always() && matrix.os == 'macos-latest'
uses: actions/upload-artifact@v7
with:
name: macos-learning-ort-trace
if-no-files-found: error
path: |
${{ runner.temp }}/trace-ort.jsonl
${{ runner.temp }}/trace-ort-receipt.json

clean-mac-setup:
name: clean macOS setup (packed artifact)
runs-on: macos-latest
Expand Down Expand Up @@ -134,14 +185,15 @@ jobs:
ollama serve > "$RUNNER_TEMP/ollama.log" 2>&1 &
AK_OLLAMA_PID=$!
trap 'kill "$AK_OLLAMA_PID" 2>/dev/null || true' EXIT
for attempt in {1..30}; do
for ((ak_readiness_attempt=0; ak_readiness_attempt<30; ak_readiness_attempt++)); do
curl --fail --silent http://127.0.0.1:11434/api/version >/dev/null && break
sleep 1
done
curl --fail --silent http://127.0.0.1:11434/api/version
git -C "$AK_PROJECT" init
(cd "$AK_PROJECT" && ak setup --yes --no-ruvnet-brain --aqe-embedding-mode local) | tee "$RUNNER_TEMP/setup.log"
(cd "$AK_PROJECT" && ak x aqe-embedding verify --json) | tee "$RUNNER_TEMP/embedding-proof.json"
# shellcheck disable=SC2016 # JavaScript template literals are evaluated by Node.
node --input-type=module -e '
import fs from "node:fs";
import path from "node:path";
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/upstream-watch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ jobs:
set -e
{
echo "## Upstream watch preview (exit $code)"
jq -r '"since \(.since) (\(.sinceSource)), new records \(.records | length), could not check \(.fetchErrors | length), blind \(.blind), notice \(.notice.post), would fire \(.wouldFire | length)"' watch.json
jq -r 'if .blind then "blind \(.blind): \(.error // "unknown error"), could not check \(.fetchErrors | length), dispatch errors \(.dispatchErrors | length), deferred \(.deferred | length)" else "since \(.since) (\(.sinceSource)), new records \(.records | length), could not check \(.fetchErrors | length), dispatch errors \(.dispatchErrors | length), blind \(.blind), notice \(.notice.post), would fire \(.wouldFire | length), deferred \(.deferred | length)" end' watch.json
jq -r '(.wouldFire // [])[] | "- would fire \(.id) \(.version) \(.branch)"' watch.json
jq -r '(.deferred // [])[] | "- deferred \(.id) \(.version) \(.branch)"' watch.json
jq -r '(.fired // [])[] | "- observed session before ledger failure: \(.id) \(.fields.session)"' watch.json
echo; echo '```text'; cat errors.txt; echo '```'
} >> "$GITHUB_STEP_SUMMARY"
exit "$code"
Expand Down Expand Up @@ -102,9 +104,10 @@ jobs:
set -e
{
echo "## Upstream watch (exit $code)"
jq -r '"since \(.since) (\(.sinceSource)), new records \(.records | length), could not check \(.fetchErrors | length), dispatch errors \(.dispatchErrors | length), blind \(.blind), commit \(.commit // "none"), would fire \(.wouldFire | length), deferred \(.deferred | length)"' watch.json
jq -r 'if .blind then "blind \(.blind): \(.error // "unknown error"), could not check \(.fetchErrors | length), dispatch errors \(.dispatchErrors | length), deferred \(.deferred | length)" else "since \(.since) (\(.sinceSource)), new records \(.records | length), could not check \(.fetchErrors | length), dispatch errors \(.dispatchErrors | length), blind \(.blind), commit \(.commit // "none"), would fire \(.wouldFire | length), deferred \(.deferred | length)" end' watch.json
jq -r '(.wouldFire // [])[] | "- would fire \(.id) \(.version) \(.branch)"' watch.json
jq -r '(.deferred // [])[] | "- deferred \(.id) \(.version) \(.branch)"' watch.json
jq -r '(.fired // [])[] | "- observed session before ledger failure: \(.id) \(.fields.session)"' watch.json
echo; echo '```text'; cat errors.txt; echo '```'
jq -r '.notice.body' watch.json
} >> "$GITHUB_STEP_SUMMARY"
Expand All @@ -121,7 +124,8 @@ jobs:
if: env.RECORD == 'true'
run: |
[ "$(jq -r '.notice.post' watch.json)" = true ] || { echo 'Nothing needs the maintainer.'; exit 0; }
commit=$(jq -r '.commit' watch.json)
commit=$(jq -r '.commit // empty' watch.json)
[ -n "$commit" ] || { echo 'Notice requested without a ledger commit.' >&2; exit 1; }
jq -r '.notice.body' watch.json > notice.md
test -s notice.md
gh api "repos/$GITHUB_REPOSITORY/commits/$commit/comments" -F body=@notice.md --jq .html_url
Expand Down
20 changes: 17 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ compression, or neural-routing targets are not measured agentic-kit guarantees.
pnpm test

# One focused suite
node --test tests/kit/dispatch-surface.test.mjs
node scripts/run-tests.mjs focus tests/kit/dispatch-surface.test.mjs

# Browser verification
pnpm run test:ui
Expand All @@ -320,6 +320,8 @@ pnpm run lint:md
pnpm run build
```

A plain `node --test` run lacks the wrapper's real-state tripwire and temp-root checks.

`pnpm test` and `pnpm run test:ui` run through `scripts/run-tests.mjs`, which fingerprints
`~/.config/agentic-kit`, `~/.local/state/agentic-kit` (or `%APPDATA%`/`%LOCALAPPDATA%` on
Windows), `~/.claude/CLAUDE.md`, `~/.claude/settings.json`, `~/.claude.json`,
Expand All @@ -332,8 +334,20 @@ them, and `sandboxHome()` and `redirectToolState()` do the same for in-process c
Code's own `~/.claude.json`) are listed as "concurrent writers" and do not fail a local run; CI
(or `AK_TRIPWIRE_STRICT=1`) fails on them too. Every command also runs with
`TMPDIR`/`TEMP`/`TMP` pointed at a fresh `ak-suite-*` folder: anything left in it afterwards fails the run and is
listed, and the runner refuses to start when that folder sits inside a git repository (point
`TMPDIR` elsewhere). The runner also drops `FORCE_COLOR` (Claude Code shells set it), because
listed (excluding its private atomic `.ak-suite-owner.json`, child-hold directory and Node compile cache). The runner
refuses home/filesystem-root temp bases before allocation and refuses roots inside a git
repository (point `TMPDIR` elsewhere). A completed run removes only its own validated direct,
canonical, nonsymlink, current-owner root. Tests with known child lifetime uncertainty acquire
`acquireRunRootHold()` before launching those children and release only after proving their exits.
An unresolved or unreadable hold retains the own root; it is not a general descendant-exit proof.
The runner then lists sibling suite roots: missing, invalid,
foreign or uncertain owner metadata means keep. Sibling handling is list-only on macOS, Linux
and Windows because no installed probe proves all descendants have exited; even a dead owner
is insufficient. Interrupted runs remove and collect nothing. Sibling listing/collection errors
do not change the suite's exit code. Own-root inspection failure retains the root; inspection,
removal or safety-refusal failure returns hygiene exit 4 unless a command or tripwire failure
already takes precedence. Removal errors may leave a partially removed own root. The runner also
drops `FORCE_COLOR` (Claude Code shells set it), because
tests read plain text from pipes. Tests make temporary folders with `tempDir()` from
`tests/kit/helpers/temp-dir.mjs`, and spawned children get their environment from `spawnEnv()` in
`tests/kit/helpers/home-sandbox.mjs`. UI tests launch Chrome with `launchChrome()` from
Expand Down
Loading
Loading