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
73 changes: 73 additions & 0 deletions .github/workflows/nightly.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ jobs:
with:
node-version: 22

- name: Isolate npm globals from the runner image
shell: bash
run: |
echo "npm_config_prefix=$RUNNER_TEMP/npm-prefix" >> "$GITHUB_ENV"
echo "$RUNNER_TEMP/npm-prefix/bin" >> "$GITHUB_PATH"
mkdir -p "$RUNNER_TEMP/npm-prefix"

# Deliberately npm, not pnpm: this simulates the kit's TARGET environment —
# ruflo/agentic-qe installed via `npm i -g`, whose trees the kit heals with
# npm (lib/heal.mjs). pnpm-managed globals are a separate follow-up.
Expand All @@ -32,6 +39,9 @@ jobs:
- name: Kit heals a fresh install (sync --no-upgrade)
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
run: |
mkdir -p "$HOME"
node bin/agentic-kit.mjs sync --no-upgrade || true
Expand All @@ -47,10 +57,73 @@ jobs:
- name: Deep proofs against the live packages
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
run: |
node bin/agentic-kit.mjs x verify security
node bin/agentic-kit.mjs x verify learning

clean-mac-setup:
name: clean macOS setup (packed artifact)
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- name: Install the packed kit into a disposable prefix
shell: bash
run: |
echo "npm_config_prefix=$RUNNER_TEMP/npm-prefix" >> "$GITHUB_ENV"
echo "$RUNNER_TEMP/npm-prefix/bin" >> "$GITHUB_PATH"
mkdir -p "$RUNNER_TEMP/npm-prefix" "$RUNNER_TEMP/package"
npm pack --pack-destination "$RUNNER_TEMP/package"
npm install -g "$RUNNER_TEMP"/package/pacphi-agentic-kit-*.tgz
- name: Run real setup in a disposable HOME and project
shell: bash
env:
HOME: ${{ runner.temp }}/clean-home
USERPROFILE: ${{ runner.temp }}/clean-home
XDG_CONFIG_HOME: ${{ runner.temp }}/clean-home/.config
XDG_STATE_HOME: ${{ runner.temp }}/clean-home/.local/state
APPDATA: ${{ runner.temp }}/clean-home/AppData/Roaming
npm_config_cache: ${{ runner.temp }}/npm-cache
RUVNET_BRAIN_KB: ${{ runner.temp }}/brain-kb
AK_PROJECT: ${{ runner.temp }}/clean-project
run: |
mkdir -p "$HOME" "$AK_PROJECT"
git -C "$AK_PROJECT" init
(cd "$AK_PROJECT" && ak setup --yes --no-ruvnet-brain) | tee "$RUNNER_TEMP/setup.log"
node --input-type=module -e '
import fs from "node:fs";
import path from "node:path";
const log = fs.readFileSync(process.env.RUNNER_TEMP + "/setup.log", "utf8");
const settings = JSON.parse(fs.readFileSync(path.join(process.env.AK_PROJECT, ".claude", "settings.json"), "utf8"));
const expected = ["Bash(npx @claude-flow*)","Bash(npx claude-flow*)","Bash(node .claude/*)","mcp__claude-flow__*","Bash(npx agentic-qe:*)","Bash(npx @anthropics/agentic-qe:*)","mcp__agentic-qe__*"];
for (const rule of expected) if (!log.includes(rule)) throw new Error(`permission was not disclosed: ${rule}`);
const actual = new Set(settings.permissions?.allow ?? []);
for (const rule of expected) if (!actual.has(rule)) throw new Error(`permission missing after setup: ${rule}`);
if (log.indexOf(expected[0]) > log.indexOf("ruflo init --full")) throw new Error("permission disclosure happened after project mutation");
'
- name: Stop disposable daemons
if: always()
shell: bash
env:
HOME: ${{ runner.temp }}/clean-home
USERPROFILE: ${{ runner.temp }}/clean-home
XDG_CONFIG_HOME: ${{ runner.temp }}/clean-home/.config
APPDATA: ${{ runner.temp }}/clean-home/AppData/Roaming
run: ruflo daemon stop --all || true
- name: Upload disposable-run setup evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: clean-mac-setup-evidence
path: |
${{ runner.temp }}/setup.log
${{ runner.temp }}/clean-project/.claude/settings.json

links-external:
name: links (external)
runs-on: ubuntu-latest
Expand Down
5 changes: 4 additions & 1 deletion bin/agentic-kit.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,10 @@ async function main() {
// `ak usage status` promises a pure offline cache read. The explicit
// `refresh` subcommand owns its one named network request; neither form may
// silently add unrelated npm probes through the generic drift nudge.
if (!values.json && !values['dry-run'] && !['sync', 'usage'].includes(cmd)) {
// setup and host own complete mutation/reporting flows. Running the generic
// nudge after a declined trust preflight could write version-cache state and
// violate their "before any changes" boundary.
if (!values.json && !values['dry-run'] && !['sync', 'usage', 'setup', 'host'].includes(cmd)) {
try {
const { driftReport } = await import('../src/lib/versions.mjs');
for (const r of await driftReport()) {
Expand Down
9 changes: 7 additions & 2 deletions docs/HOST-SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,13 @@ Ruflo, AQE, or Brain parity merely because it can call their MCP tools.
OpenCode supervision is a safety boundary around resources, not a sandbox around
the repository. An `opencode.json` that pre-approves a tool is inside the user's
workspace trust decision; no permission-request event exists for agentic-kit to
deny. Claude and Codex likewise inherit repository-owned hooks, instructions, and
permissions.
deny. Claude and Codex likewise inherit repository-owned hooks, instructions,
and permissions. `ak setup` and new enablements through `ak host pick` disclose
the applicable host-neutral trust manifest
before machine, user, or project mutation: Claude auto-approvals, OpenCode
wildcard approvals and managed extensions, and Codex registrations while its
sandbox/approval policy remains unchanged. See
[Setup trust manifest](SETUP.md#setup-trust-manifest).

Official extension references: [Claude hooks](https://code.claude.com/docs/en/hooks),
[Claude MCP](https://code.claude.com/docs/en/mcp),
Expand Down
20 changes: 19 additions & 1 deletion docs/INSTALLATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,10 @@ against Node and npm. They are not the supported machine-management contract.

The RuvNet Brain is user-level and shared by every project for that user. Ruflo and
AQE project memory remains repository-local. Installing the package globally does
not copy project memory into the npm prefix.
not copy project memory into the npm prefix. Dashboard runtime discovery is
user-scoped rather than project-scoped, so supported host controllers running as
the same UID in other repositories may appear and are grouped by their observed
workspace when that identity can be established.

## Multi-user and CI guidance

Expand All @@ -234,6 +237,11 @@ not copy project memory into the npm prefix.
- Prefer one user-writable npm prefix per OS account.
- Each user runs `ak setup` under their own account so guidance, credentials, MCP
registration, Brain data, and `kit.json` do not land under another user's home.
- Runtime process discovery is scoped to the numeric UID running `ak dashboard`.
Separate OS accounts are outside the normal survey; people sharing one login
also share one UID and are therefore inside the same discovery boundary. This
is least-privilege selection, not an OS sandbox. Never run the dashboard with
`sudo`: it would survey root-owned sessions instead of the invoking user's.
- Do not assume one user's global install is available to another user.
- Coordinate `ak sync` when several live sessions share the same prefix, because
package replacement and daemon stops are prefix/machine-wide for those sessions.
Expand All @@ -246,6 +254,16 @@ not copy project memory into the npm prefix.
- Cache npm and the Brain only when the cache's size and trust model are acceptable.
- Avoid `ak sync` self-update in a lockfile-controlled job; use `--no-upgrade`.
- Never persist provider credentials in the repository or image layer.
- HOME, XDG, and npm-prefix isolation protects files but does not isolate the
process table. Prefer a private PID namespace. A container using the host PID
namespace can observe same-numeric-UID processes that the container permits it
to inspect.

The current-UID rule is independent of how `ak` was acquired: local dependency,
global prefix, `npm exec`, tarball, Git checkout, and direct Node execution all
use the UID of the process running the dashboard. A service sees only the
service account's sessions. Windows does not currently provide runtime process
discovery; retained transcript/history sources remain available there.

### Repository onboarding

Expand Down
11 changes: 9 additions & 2 deletions docs/MANAGED-TOOLS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Managed tools — the consistency contract

Every tool ak manages follows one contract for how it is installed, updated,
version-detected, and displayed. This doc states the contract's four
version-detected, and displayed. This doc states the contract's five
invariants, maps every managed tool onto them, and gives the checklist for
adding a new tool without breaking them.

Expand All @@ -14,7 +14,7 @@ design is Proposed in [ADR-0016](adr/0016-capability-driven-integration-adapters
Each invariant traces to a live failure it prevents — the appendix records
them.

## The four invariants
## The five invariants

1. **Disk-first installed versions.** The "installed" side of every drift
check is read from what is actually on disk — never from a cached claim or
Expand Down Expand Up @@ -46,6 +46,13 @@ them.
(`foldBrainDrift()` / the `selfDrift` fold in
`src/lib/dashboard-server.mjs`).

5. **Exit status outranks artifact presence.** Every managed operation reports
both an outcome (`ok`, `degraded`, `failed`, or `skipped`) and whether a
usable artifact remains. A failed repair can therefore say that an older
install is still usable, but it cannot render green or advance a release
stamp. A fallback is `degraded`, never an implied native repair. Version
stamps advance only after the installer exits successfully.

## The tools

| Tool | Install / update spec | Update owner | Installed version read from | Drift compared against | status / statusline / dashboard |
Expand Down
14 changes: 12 additions & 2 deletions docs/OBSERVABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,18 @@ Agentic-QE cannot currently configure OpenCode-routed court models. The dashboar
seat from an OpenCode process heartbeat.

The default dashboard automatically discovers Claude and Codex transcript files, observes
supported controller processes, and reads the Codex state ledger. It does **not** search arbitrary ruflo,
agentic-qe, plugin, or skill stores. Register a structured source
supported controller processes, and reads the Codex state ledger. On macOS and Linux,
process discovery is selected by the real numeric UID running the dashboard. It first reads
only PID/parent/start/command columns, then requests full argv only for Node or known
host-controller candidates from that selection. Separate OS accounts are outside the
intended survey; people sharing one login, a service running under that account, and a
container sharing the host PID namespace remain inside the same numeric-UID boundary.
Do not run the dashboard with `sudo`. Windows runtime process discovery is unsupported,
and missing/restricted `ps`, `lsof`, or `/proc` degrades runtime presence without removing
retained transcript/history evidence. The argv lookup is a second process-table query; a PID
could theoretically be reused between selection and lookup, so current-UID selection is a
least-privilege reduction rather than a hard isolation boundary. It does **not** search arbitrary
ruflo, agentic-qe, plugin, or skill stores. Register a structured source
explicitly with repeatable `--live-source 'surface=path'`, where `surface` is
exactly `ruflo` or `aqe`.

Expand Down
63 changes: 62 additions & 1 deletion docs/SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,60 @@ Project setup also reapplies enabled host/provider wiring and seeds the default
per-activity routing policy in dual-host mode. With Codex enabled, it repairs
both directions of the Claude/Codex–Ruflo bridge.

## Setup trust manifest

Before making any machine, user, or project change, setup derives one manifest
from the enabled host adapters and prints every applicable approval, registration,
and host-integration change. Interactive setup asks for one confirmation after
this preflight. `--yes` suppresses the question but still prints the manifest, so
automation retains an auditable record; `--dry-run` is the non-mutating way to
inspect the effective manifest for the current flags. A non-interactive setup
with applicable trust changes must pass `--yes`; otherwise it prints the
manifest and exits before mutation.

The host registry requires every present or future host adapter to declare an
approval posture and its setup-time trust changes. A future host cannot pass
registry validation without that declaration, and setup consumes it without a
host-specific disclosure branch. Enabling a host later with `ak host pick`
uses the same registry-derived preflight for changes that command applies;
already-enabled hosts are not prompted again.

### Claude Code project auto-approvals

Project setup discloses the exact Claude Code rules that Ruflo and agentic-qe may
ensure or retain in `.claude/settings.json`:

| Owner | Auto-approved rule | Effect |
| --- | --- | --- |
| Ruflo | `Bash(npx @claude-flow*)` | Run scoped `@claude-flow` npx commands |
| Ruflo | `Bash(npx claude-flow*)` | Run scoped `claude-flow` npx commands |
| Ruflo | `Bash(node .claude/*)` | Run repository-local `.claude` Node helpers |
| Ruflo | `mcp__claude-flow__*` | Call the project Ruflo MCP tool family |
| agentic-qe | `Bash(npx agentic-qe:*)` | Run scoped agentic-qe npx commands |
| agentic-qe | `Bash(npx @anthropics/agentic-qe:*)` | Run scoped `@anthropics/agentic-qe` npx commands |
| agentic-qe | `mcp__agentic-qe__*` | Call the project agentic-qe MCP tool family |

The last three rules are omitted with `--no-aqe`. Rules that existed before
setup remain user-owned. After each upstream initializer, agentic-kit compares
the resulting allow-list with the manifest: any newly added, undisclosed rule
is removed and setup fails instead of silently expanding project trust.

### Codex and OpenCode

Codex does not need an agentic-kit auto-approve list. The manifest instead names
the project Claude-to-Codex MCP bridge, the user-scope Codex-to-Ruflo MCP
registration, and the AQE Codex integration that project setup will create.
Agentic-kit does not alter Codex's sandbox or approval policy.

OpenCode's user-scope manifest names all four wildcard tool approvals, the
Ruflo and optional Brain MCP registrations, the lifecycle plugin, and the
managed agent/skill/guidance projection. These are workspace-trust grants, not
an agentic-kit sandbox.

The separately offered Claude user-scope Ruflo MCP registration retains its own
dedicated prompt because it is optional rather than a deterministic setup
change. `--yes` accepts that prompt as documented.

## Existing projects

For an existing project:
Expand Down Expand Up @@ -113,9 +167,16 @@ For an empty or newly created directory:
as disabled in agentic-kit's machine configuration.
- `--codex`, `--opencode`, and `--primary-host` enable and wire the selected
hosts before the project phase.
- `--yes` accepts prompts; it does not change the project mutation contract.
- `--yes` accepts prompts; it does not hide the setup trust manifest or change
the project mutation contract.
- `--dry-run` prints a high-level plan and changes nothing.

The clean-machine regression runs from the packed release artifact on GitHub's
`macos-latest` runner with disposable `HOME`, XDG directories, npm global
prefix/cache, Brain KB, and project directory. Local regression coverage uses
the same isolation model in a child process. Neither test runs setup against
the developer's existing home or global npm prefix.

See [Upgrading](UPGRADING.md) for the `setup` versus `sync` lifecycle and
[Troubleshooting](TROUBLESHOOTING.md) for setup and health-check failures. See
[Host support](HOST-SUPPORT.md) before enabling Claude, Codex, or OpenCode.
16 changes: 8 additions & 8 deletions docs/TRANSCRIPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ rewritten; rule 3 of the module header, `usage-index.mjs:22-29`):
| Claude Code | `~/.claude/projects/<encoded-project-dir>/<sessionId>.jsonl` | `listClaude` (`usage-index.mjs:684`) — exactly one level of project directories |
| Codex CLI | `~/.codex/sessions/<yyyy>/<mm>/<dd>/rollout-<ts>-<uuid>.jsonl` | `listCodex` (`usage-index.mjs:705`) — the `yyyy/mm/dd` tree walk |

Roots come from `defaultRoots()` (`usage-index.mjs:676`) and are injectable
Roots come from `defaultRoots()` (`usage-index.mjs:697-701`) and are injectable
for tests. A malformed line is skipped, never fatal (`jsonLines`,
`usage-index.mjs:315` — one corrupt line must not cost a whole file).
`usage-index.mjs:328-334` — one corrupt line must not cost a whole file).

Host evidence is not inference-provider proof. A Claude transcript may describe Anthropic-,
OpenRouter-, or Ollama-served inference. ADR-0016 defines separate
Expand Down Expand Up @@ -77,7 +77,7 @@ Codex rollout lines carry `type` + `payload`. The parser (`parseCodex`,
|---|---|
| `session_meta` | Authoritative session id, `cwd`, and `thread_source` (`usage-index.mjs:539-544`) — `"subagent"` marks a thread_spawn replay whose tokens are excluded from aggregation (`usage-index.mjs:609`; `USAGE-SCORECARD-METRICS.md` Appendix A, Bug B) |
| `turn_context` | The model id in effect from this point on (`usage-index.mjs:545`) |
| `event_msg` → `token_count` | A **cumulative** usage snapshot; only the last one is kept (`usage-index.mjs:552`) |
| `event_msg` → `token_count` | A **cumulative** usage snapshot; only the last one is kept (`usage-index.mjs:609-611`) |
| `event_msg` → `user_message` | A real human prompt — Codex does not route tool output through this event (`usage-index.mjs:584-592`) |
| `event_msg` → `agent_message` | A model response (`usage-index.mjs:594-605`) |

Expand Down Expand Up @@ -182,8 +182,8 @@ transcript content leaves the module, and every step is a gate:

1. **Id grammar before any filesystem access** — `VALID_ID`
(`/^[A-Za-z0-9._-]{1,128}$/`, `usage-index.mjs:83`) rejects traversal
shapes with `ERR_INVALID_SESSION_ID` (`usage-index.mjs:1260`).
2. **Locate by id** across both roots (`locate`, `usage-index.mjs:1267`),
shapes with `ERR_INVALID_SESSION_ID` (`usage-index.mjs:1303-1307`).
2. **Locate by id** across both roots (`locate`, `usage-index.mjs:1313`),
consulting the scan cache when present but never requiring it —
`readSession` works with no prior `buildIndex`.
3. **Realpath containment** (`usage-index.mjs:1335-1349`) — the resolved file
Expand All @@ -198,8 +198,8 @@ transcript content leaves the module, and every step is a gate:
### 4.2 Parse and price

The file is parsed with `withTurns: true` by the provider's parser
(`usage-index.mjs:1298-1303`), and `meta` is assembled
(`usage-index.mjs:1305-1330`) with the same fields the Sessions view rows
(`usage-index.mjs:1404-1411`), and `meta` is assembled
(`usage-index.mjs:1414-1442`) with the same fields the Sessions view rows
carry — `prompts`, `responses`, `exceptions`, `sidechain`, `threadSource`,
`models`, `tools`, `skill`/`plugin`, worktree — plus a `cost` priced from the
same per-model usage rows `aggregate()` uses (the header used to render a
Expand All @@ -211,7 +211,7 @@ Every turn body is passed through `maskSecrets` (`usage-index.mjs:196` — the
23 secret shapes) **server-side, before
serialization**, then length-capped at `MAX_TURN_CHARS` (40,000,
`usage-index.mjs:77`) with the marker appended
(`usage-index.mjs:1404-1414`). Two invariants:
(`usage-index.mjs:1451-1461`). Two invariants:

- **Presence is the signal.** `truncated`/`originalChars` are emitted only
when the slice fired, so a complete turn cannot be misread as abridged.
Expand Down
Loading
Loading