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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ What the verbs cover:
| **status** | Per-subsystem ✓/⚠/✗ (versions, the kit's own version, **ruvnet-brain** (present + release drift, or "not installed"), natives (agentdb copies **and** ruflo's own memory runtime — the one `npx ruflo memory` loads — load-tested for a native better-sqlite3, not just the agentdb dirs), **memory-pin** (warns when `CLAUDE_FLOW_DB_PATH` points off the live DB), security, learning, aqe/RVF, **agentdb** (CLI present + coherent with ruflo's bundled version, or a store-skew warning), MCP, **hosts** (claude/codex — version + install method + **auth mode** (subscription $0 vs metered api-key), with the **primary** host marked and a *fail* when the primary host is absent), **providers** (host wiring + aqe fallback chain, or "drifted"/claude-only default), **routing** (per-activity Claude/Codex host+model policy, when dual-host — with drift vs the on-disk `agentOverrides`), daemons, guidance-file blocks (`~/.claude/CLAUDE.md`, project `AGENTS.md`, and `~/.codex/AGENTS.md` on codex machines), statusline), each drift row naming what `sync` would do about it — plus a **health-history** line that flags regressions since the last sync (learning shrank, native slots dropped, drift/security backslid). |
| **sync** | The one convergence verb: upgrades first when a new release exists, then re-heals everything an upgrade wipes, then re-checks and reports. Included in that heal: it **installs any enabled frontier host** (claude/codex) that's entirely absent — never touching an external (mise/brew/native) install — and **re-applies provider wiring** (the `ENABLE_*` host env, the aqe fallback chain, and ruflo API providers) whenever it has drifted — and, on a dual-host project, **seeds/heals the per-activity routing policy** (materializing it into agentic-qe's `agentOverrides`, e.g. after an aqe upgrade first makes it eligible). It also **installs/repins the standalone `agentdb` CLI** to ruflo's bundled version (keeping the shared cognitive store coherent) and appends a **health-history snapshot** so `status` can flag regressions across syncs. It also **re-runs the RuvNet Brain installer** to pull the latest release when the on-disk KB has drifted (or installs it if absent, when enabled). It also **self-updates the kit**: when a newer `@pacphi/agentic-kit` exists it installs it as the *last* step (the new code applies from the next `ak` run, never mid-sync). Prerelease installs (`4.0.0-alpha.*`) track the `next` npm dist-tag as well as `latest`, so alphas see their successors; stable installs only ever follow `latest`. `--no-upgrade` skips the self-update along with the package upgrades. |
| **dashboard** | Opens a read-only local web dashboard (`127.0.0.1:7431`, localhost-only, never detaches) with seven tabs: **Overview · Hosts & Routing · Providers · Runtime · Intelligence · Usage · Live**. The first five render `ak status` health and routing; Usage indexes local Claude/Codex transcripts on demand. Live groups work by project, then provider-branded root sessions with nested agent/worker threads, and pairs an interactive agent/tool execution canvas with a rich, server-masked transcript stream. Active sessions can be followed live or reviewed with synchronized play/pause/seek; completed sessions remain available for bounded playback. Live contains no chat or control plane. Ruflo, agentic-qe, and dual-run stores are not auto-discovered; register each trusted structured JSONL file with repeatable `--live-source 'surface=path'` (`surface` is `ruflo`, `aqe`, or `dual-run`). The page is self-contained and offline-first (no internet fetches; local files and loopback subprocesses/endpoints only). See [Live Sessions](docs/LIVE-SESSIONS.md) for coverage, syntax, and privacy limits. **Auto-opens your browser** (`--no-open` for headless/SSH); `--port N` changes the port; tabs deep-link (`#live`) and persist. Stop with Ctrl-C. (Also available as `ak x dashboard`.) |
| **admin** | Opens the **maintainer admin** (`127.0.0.1:7432`, localhost-only, foreground) — the project-telemetry sibling of `dashboard`, with the same dark/light visual theme and persisted theme preference: unique repo visitors and cloners (GitHub traffic API, needs a push-access token via `GITHUB_TOKEN`/`GH_TOKEN`/`gh auth token` — panels degrade honestly without one), npm download momentum (last 7d vs prior 7d, sparklines), release pulls, a **"since you last looked"** delta strip over a local baseline, open issues/PRs from others (oldest first), and external humans ranked by recency (bots excluded). Access is gated by a **per-session token** carried in the URL fragment and sent header-only; the page makes **zero external fetches** (the server proxies GitHub/npm; your credential never reaches the page or the payload — ADR-0007). Where `dashboard` is offline-first, `admin` does deliberate GitHub/npm egress — that contract split is why they're siblings, not tabs. `--port N`, `--no-open`; Ctrl-C stops. (Also available as `ak x admin`.) |
| **admin** | Opens the **maintainer admin** (`127.0.0.1:7432`, localhost-only, foreground) — the project-telemetry sibling of `dashboard`, with the same dark/light visual theme and persisted theme preference: unique repo visitors and cloners (GitHub traffic API, needs a push-access token via `GITHUB_TOKEN`/`GH_TOKEN`/`gh auth token` — panels degrade honestly without one), contributors and watchers, npm download momentum (last 7d vs prior 7d, sparklines — shown as trend only, never an absolute reach number, since mirrors/CI inflate the raw count), latest CI run status and open Dependabot alerts, a **"since you last looked"** delta strip over a local baseline, open issues/PRs from others (oldest first), and external humans ranked by recency (bots excluded). Access is gated by a **per-session token** carried in the URL fragment and sent header-only; the page makes **zero external fetches** (the server proxies GitHub/npm; your credential never reaches the page or the payload — ADR-0007, ADR-0013). Where `dashboard` is offline-first, `admin` does deliberate GitHub/npm egress — that contract split is why they're siblings, not tabs. `--port N`, `--no-open`; Ctrl-C stops. (Also available as `ak x admin`.) |
| **dual** | Runs a **Claude + Codex collaboration swarm** using your per-activity routing policy: `ak dual run <template> "<task>"` materializes a dual-run config (each pipeline step assigned to the host + model your policy chose) and drives it via `claude-flow-codex`. Templates: `feature`, `security`, `refactor`, `packaging`, `release`. `--dry-run` prints the plan + config without running; `--route 'activity:host[:model]'` overrides one step for that run; `--escalate` retries once up the cross-vendor ladder on failure. Requires dual-host enabled (`ak setup --codex`, or `ak x provider pick --host claude,codex`). **Pre-flight refusal:** before spawning a worker, `dual run` refuses to start when ruflo's memory runtime lacks a native better-sqlite3 binding **and** the shared DB has an active native WAL (`-wal`/`-shm` sidecars) — the native WAL writer and the WASM `ruflo memory store` cannot share that DB without corrupting it; the fix is `ak sync` (builds the native binding), then retry. |
| **uninstall** | Removes the kit's footprint (and any legacy shell-kit install); project data is never touched; `--purge` also offers to remove the global packages. |

Expand Down
129 changes: 129 additions & 0 deletions docs/adr/0013-admin-build-security-signals-and-honest-reach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# ADR-0013 — Admin: build/security signals, an honest Reach panel, and a pagination fix

- **Status:** Accepted
- **Date:** 2026-07-28
- **Deciders:** agentic-kit maintainers

## Context

ADR-0007 shipped `ak x admin` as a loopback, deliberate-egress maintainer console answering "how is
this project actually doing" from GitHub + npm. A follow-up audit (live queries against the real
`pacphi/agentic-kit` repo, with the same token the admin server itself resolves) found the page
answered that question less completely than it could:

1. **Two of the four Reach hero tiles were permanently dead for this project.** "bundle downloads"
and "newest release pulls" read from GitHub release **assets**. agentic-kit ships exclusively via
npm — verified live, 0 of 29 releases carry any asset — so both tiles rendered `—` forever. That
is correct behavior per the model's "unknown is not zero" rule (ADR-0007 §4.1), not a bug, but it
is dead screen real estate for this project's actual distribution channel.
2. **`admin-collect.mjs` capped the releases fetch at `per_page=20`** while the "bundle downloads"
tile's own copy claimed "lifetime, **all releases**." The repo already has 29 releases; the oldest
9 were silently dropped from both the releases list and `totalAssetDownloads`. Harmless today only
because no release carries assets — the moment one does, the label becomes false with no
truncation indicator.
3. **No CI/build health or security-alert signal reached the page at all**, despite `ci`/`release`
GitHub Actions workflows running on every push and Dependabot maintaining the dependency tree —
both directly answer "how is this project doing" and both were one more `Promise.all` entry away.
4. **Two already-collected fields were invisible in the UI**: `repo.watchers` (GitHub subscriber
count) was computed in the collector and never rendered; `npm.lastWeek`/`npm.lastMonth` were
computed and only ever consumed as a trend-arrow input in Momentum, never shown as an absolute
number anywhere.
5. On point 4's npm figures specifically: `admin-view.mjs` already carried a **deliberate** decision
to keep npm's absolute download count out of the Reach panel, because `npm downloads are dominated
by mirrors` (CI runners, cache warmers, and registry mirrors re-pull as often as a real install,
and `api.npmjs.org` cannot distinguish them). That rationale was correct but stated in one
half-sentence buried in a footnote — worth stating as its own first-class, explicit entry so a
maintainer reading the Gaps tab understands *why* npm never appears as a reach number, not just
that it doesn't.

## Decision

### 1. Fix the releases pagination bug

`admin-collect.mjs` now fetches `/releases?per_page=100` (GitHub's per-request ceiling), matching the
same single-page-best-effort cap already used for issues/stargazers/forks in this file. This is a
correctness fix, not a new pagination system — the file's existing house pattern (one page, capped at
100, no further pages followed) is extended consistently rather than reinvented. The 100-item ceiling
is now called out in a code comment at the fan-out site so a future reader does not mistake it for
full pagination.

### 2. Replace the two dead Reach tiles with real, GitHub-native people signals

"bundle downloads" and "newest release pulls" are replaced with:

- **contributors** — `GET /repos/{slug}/contributors`, count only. A genuinely different circle from
"people who filed issues/PRs" (buildPeople's `people.contributors`): this counts who has code in
the tree via GitHub's own merge history.
- **watching** — `repo.watchers` (already collected, previously unrendered). A standing-interest
signal distinct from a one-time visitor.

Both stay inside GitHub's own numbers, so neither reopens the mirror-inflation problem npm carries.
The npm absolute-download question raised by point 4 was decided explicitly: **npm downloads still do
not appear in Reach.** The existing exclusion was correct; only the messaging around it was thin (see
Decision 4).

### 3. A new "Project health" section: latest CI run + open Dependabot alerts

A new subsection in the Overview panel, fed by two more `Promise.all` entries:

- `GET /repos/{slug}/actions/runs?per_page=5` → latest run's `{name, status, conclusion, at, url}`
plus a failure count over the fetched page. No runs found renders `—`, never a fabricated
"passing" (the same unknown-is-not-zero discipline as everywhere else in this file).
- `GET /repos/{slug}/dependabot/alerts?state=open&per_page=100` → open alert count. This endpoint
needs a token with `security_events` scope (classic PAT) or equivalent fine-grained access; an
unconfigured or under-scoped token 403s, which `ghJson` already folds to `null` — the tile
degrades to "unknown" with an explanatory `why`, never a false "0 alerts."

Both are additive `Promise.all` entries in the existing single fan-out (ADR-0007 §3) — no new
request pattern, no new auth flow, no new caching layer.

### 4. Make the npm mirror-inflation rationale a first-class, explicit statement

The Gaps tab (ADR-0007's "honesty section") gains a `design`-tagged entry stating plainly that npm
download counts are shown as **trend only, never as a reach number**, and why: mirrors/CI/cache
warmers are indistinguishable from real installs in npm's own data, so an absolute total would
overstate reach. The Reach panel's footer note and the Momentum tile's note are both tightened to
carry the same point concisely rather than as an aside.

## Consequences

- `admin-collect.mjs`'s single `Promise.all` grows from 9 to 12 entries: `contributors`,
`actions/runs`, `dependabot/alerts` join the existing set. All three follow the file's existing
discipline — internally try/caught, degrade to `null`/empty on any failure, never reject the batch,
never appear in the payload if the credential is absent from the request itself (contributors and
CI runs work unauthenticated on a public repo; Dependabot alerts do not and degrade honestly).
- The payload contract gains `contributorsCount` (number|null), `ci: {latest, recentFailures,
recentTotal}`, and `security: {dependabotAlerts}`. `defaultCollect`'s fail-soft path returns honest
empty values for all three so a thrown collector error still yields a renderable payload.
- The Reach panel's four tiles are now: unique repo visitors (hero), contributors, watching, opted-in
installs (still an honest gap) — all either real headcounts or an explicit "not built" admission,
none permanently dead for this project's npm-only distribution model.
- `tests/admin.test.cjs` gained fixtures and assertions for the three new endpoints, including the
same never-fabricate-a-zero discipline tested elsewhere (Dependabot 403 → `null`, no workflow runs
→ `ci.latest: null`, a failing contributors fetch nulls only `contributorsCount`) and an explicit
assertion that the releases fetch requests `per_page=100`.
- `admin-model.mjs` is unchanged — every new field renders through the existing `metric()` helper, so
the pure-model import-nothing constraint (ADR-0007 §5) is untouched.

## Alternatives considered

- **Show npm absolute downloads as a Reach tile instead of GitHub-native signals.** Rejected: it
would reintroduce exactly the mirror-inflation overstatement the original design deliberately
avoided (Context, point 5). GitHub-native signals (contributors, watchers) answer the same "kill
the dead tiles" goal without that risk.
- **Full pagination (follow `Link` headers) for releases/issues/stargazers/forks/contributors.**
Rejected for now: none of these currently exceed the 100-item cap for this repo, and full
pagination is a larger, inconsistent change against the file's established one-page-best-effort
house pattern. The 100-item ceiling is now documented in code rather than silently assumed; revisit
if/when any list genuinely exceeds it.
- **Cache GitHub/npm responses between polls (ETag/`If-None-Match`) to cut request volume on
auto-refresh.** Out of scope for this ADR — freshness-over-caching is ADR-0007's explicit contract
(`Cache-Control: no-store` on every `/api/*` response), and today's request volume is nowhere near
GitHub's rate ceiling. Worth its own ADR if auto-refresh usage patterns change that calculus.

## References

- ADR-0007 (maintainer admin: loopback telemetry with deliberate egress) — this ADR extends its
collector/payload/Reach-panel decisions rather than superseding them.
- `src/lib/admin-collect.mjs`, `src/lib/admin-view.mjs`, `src/lib/admin-server.mjs`,
`src/lib/admin-styles.mjs`, `tests/admin.test.cjs`.
7 changes: 6 additions & 1 deletion docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Consequences**, and cites the grounded source it rests on where relevant.
| [0010](0010-provider-mediated-quota-reads.md) | Provider-mediated quota reads (the only honest denominators) | Accepted |
| [0011](0011-local-model-provenance-zero-cost-and-transcript-fidelity.md) | Local models: provenance out-of-band, $0 per model, stated transcript fidelity | Proposed |
| [0012](0012-live-sessions-observability.md) | Live sessions as local, evidence-graded observability | Accepted |
| [0013](0013-admin-build-security-signals-and-honest-reach.md) | Admin: build/security signals, an honest Reach panel, and a pagination fix | Accepted |

Theme: ADRs **0001–0006** define **dual-host LLM routing and leadership** — how `ak` lets ruflo route
each development activity (architecture, implementation, testing, review, …) to the right host (Claude
Expand Down Expand Up @@ -49,4 +50,8 @@ keep content out of broad topology snapshots/replay while preserving masked loca
keeping chat/control absent. Claude/Codex collection is
implemented; ruflo, agentic-qe, and dual-run require explicit, repeatable `--live-source`
registration. Independent plugin/skill/MCP discovery and a measured frame-time budget remain
documented limitations rather than implied capabilities.
documented limitations rather than implied capabilities. **0013** extends 0007's admin collector with
CI-run and Dependabot-alert signals, fixes a releases-pagination cap that silently dropped older
releases, replaces two GitHub-release-asset Reach tiles that were permanently dead for this npm-only
project with real GitHub-native people signals (contributors, watchers), and states the existing
npm-mirror-inflation exclusion explicitly instead of as a footnote.
13 changes: 7 additions & 6 deletions src/commands/x/admin.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ export const options = {
export const help = `ak admin — maintainer-only local telemetry admin (localhost only) [alias: ak x admin]

Serves a self-contained web panel showing how the project is actually doing —
unique repo visitors, release-asset pulls, npm range, GitHub traffic, and the
humans who filed issues, opened PRs, or forked. Unlike \`ak dashboard\` (which is
offline-first and never leaves your machine), admin makes DELIBERATE network
egress: the server fetches GitHub + npm on your behalf and reads a GitHub
credential (GITHUB_TOKEN → GH_TOKEN → \`gh auth token\`, best-effort) at runtime.
That credential is never persisted and never reaches the page.
unique repo visitors, contributors, npm range, GitHub traffic, latest CI run,
open Dependabot alerts, and the humans who filed issues, opened PRs, or
forked. Unlike \`ak dashboard\` (which is offline-first and never leaves your
machine), admin makes DELIBERATE network egress: the server fetches GitHub +
npm on your behalf and reads a GitHub credential (GITHUB_TOKEN → GH_TOKEN →
\`gh auth token\`, best-effort) at runtime. That credential is never persisted
and never reaches the page.

Bound to 127.0.0.1. A fresh session token is minted at startup and carried into
the browser in the launch URL's # fragment (never a query param, never logged);
Expand Down
Loading
Loading