Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
edc5824
feat(VST-258): surface vstack drift at session start — check --json/-…
bmethod Aug 15, 2026
cc4b376
fix(VST-258): Address PR review - available is a suggestion not drift…
bmethod Aug 16, 2026
8f6321c
fix(VST-258): Address PR review - one guarded cache fetch, per-kind p…
bmethod Aug 16, 2026
8c301a2
fix(VST-258): Address PR review - session start never fetches; backgr…
bmethod Aug 16, 2026
7267df1
fix(VST-258): Address PR review - Codex marker/native scoping, git en…
bmethod Aug 16, 2026
66a5100
fix(VST-258): Address PR review - event-derived Codex nativeness, ful…
bmethod Aug 16, 2026
e4b199c
fix(VST-258): Address PR review - POSIX-quote remediation command arg…
bmethod Aug 16, 2026
6d4b330
fix(VST-258): Address PR review - codex hook registration presence, i…
bmethod Aug 16, 2026
89491ba
fix(VST-258): Address PR review - never fail open on drift, dash-safe…
bmethod Aug 16, 2026
4355f4c
refactor(VST-258): Address PR review - split the size-ratchet offende…
bmethod Aug 16, 2026
2708d5c
fix(VST-258): Address PR review - guarded kill escalation, parsed Cod…
bmethod Aug 16, 2026
bda0d6b
fix(VST-258): Address PR review - scope-flagged remediation commands,…
bmethod Aug 16, 2026
5416689
fix(VST-258): Address PR review - Pi package registration is presence…
bmethod Aug 16, 2026
71543dd
fix(VST-258): Address PR review - one source parser behind the clone …
bmethod Aug 16, 2026
f899cc4
fix(VST-258): Address PR review - non-unix guard compiled and tested,…
bmethod Aug 16, 2026
2613962
merge(VST-258): reconcile the drift contract with VST-256's hardened …
bmethod Aug 16, 2026
68aaedd
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 16, 2026
4ee55d8
docs(VST-258): record the drift contract in the changelog
bmethod Aug 16, 2026
0df1911
fix(VST-258): Address PR review - executable-position hook matching, …
bmethod Aug 17, 2026
1fa777f
fix(VST-258): Address PR review - install-vs-refresh cache guard, who…
bmethod Aug 17, 2026
792fed6
fix(VST-258): Address PR review - unparseable registration files are …
bmethod Aug 17, 2026
ec65fd0
fix(VST-258): Address PR review - one declared schema decides readabl…
bmethod Aug 17, 2026
1a6274a
fix(VST-258): Address PR review - toml_edit writes Codex config, one …
bmethod Aug 17, 2026
55303c8
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
98a67bb
fix(VST-258): Address PR review - a switched-off harness is its own r…
bmethod Aug 17, 2026
4483e3e
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
842a966
fix(VST-258): Address PR review - structure is parsed, never matched …
bmethod Aug 17, 2026
0dfae8c
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
80152a6
fix(VST-258): Address PR review - jq's own failure hands off, oversiz…
bmethod Aug 17, 2026
a427e0d
fix(VST-258): Address PR review - an agent's declared skills are pars…
bmethod Aug 17, 2026
025f6e2
merge(VST-258): reconcile the drift contract with VST-283's hook exec…
bmethod Aug 17, 2026
51c62f0
merge(VST-258): reconcile the drift contract with the hook-enforcemen…
bmethod Aug 17, 2026
47e9d42
fix(VST-258): Address PR review - one POSIX-quoting helper for every …
bmethod Aug 17, 2026
6e59aae
merge(VST-258): reconcile the printed-command and Pi-carrier rules wi…
bmethod Aug 17, 2026
520b0aa
fix(VST-258): Address PR review - reword the OpenCode removal doc so …
bmethod Aug 17, 2026
9bbf683
docs(VST-258): README and CHANGELOG carry the printed-command helper,…
bmethod Aug 17, 2026
3184ccd
fix(VST-258): Address PR review - read-only cache readers probe the f…
bmethod Aug 17, 2026
29068e5
fix(VST-258): Address PR review - named sources fetch now, catalog ro…
bmethod Aug 17, 2026
adaaaa6
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
15f93da
fix(VST-258): Address PR review - one switch-aware hook state, every …
bmethod Aug 17, 2026
d86c634
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
97f5005
fix(VST-258): Address PR review - parse before substituting, serializ…
bmethod Aug 17, 2026
c724628
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
d173dac
fix(VST-258): Address PR review - split the cursor rule into its own …
bmethod Aug 17, 2026
3dc3695
fix(VST-258): Address PR review - read each shared config with its ha…
bmethod Aug 17, 2026
93206c7
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
c80a07f
fix(VST-258): Address PR review - opencode hook code splits at its ow…
bmethod Aug 17, 2026
17044be
fix(VST-258): Address PR review - local source paths keep their punct…
bmethod Aug 17, 2026
ee63956
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
c03799d
Merge remote-tracking branch 'origin/main' into vst-258
bmethod Aug 17, 2026
6c111cf
fix(VST-258): Address PR review - report the prerequisite fault first…
bmethod Aug 17, 2026
fcd6239
fix(VST-258): Address PR review - a held cache lock stays alive for t…
bmethod Aug 17, 2026
20ec6f6
fix(VST-258): Address PR review - type every config field the code reads
bmethod Aug 17, 2026
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
25 changes: 21 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,28 @@ Session handoff files live ONLY in `docs/handoff/` — exactly one untracked fil
cli/src/
├── main.rs CLI entry; routes to commands/
├── commands/ add, remove, list, check, update, update_pi, verify, refresh, init
├── pi_extension.rs Pi extension discovery, install/remove, settings.json merge
├── commands/add/source.rs Source resolution for `add` — local dir, cached remote, or the checkout; SourceFetch states whether a source is fetched now or served from a fresh cache
├── commands/check/ render.rs — the drift report's text and its credential/control/length scrubbing
├── pi_extension.rs Pi extension discovery, install/remove
├── pi_extension/settings.rs Pi settings.json `packages` array — registration write, removal, and matching
├── config.rs Lock file (JSON), project root detection, staleness/mtime helpers
├── config/remote_cache.rs Remote source cache fetch stamps and recorded refresh outcomes
├── config/remote_cache/fetch.rs The CacheLease a reader holds, bounded git fetch/reset, refresh drivers
├── config/remote_cache/fetch/guard.rs The two fetch-guard implementations (flock, and the portable lock file)
├── config/remote_cache/fetch/guard/liveness.rs A portable lock's owner record, its held-for-life heartbeat, and stale-holder takeover
├── refresh_sources.rs Source resolution; RemoteSource (display/URL/cache key), hardened git commands, cache-entry ownership
├── refresh_sources/records.rs A lock's sources as records — resolution, refusals, and the lease each cached root is read under
├── scope.rs Scope enum (project | global | all); uniform `--scope`/`-g` parsing
├── catalog.rs Source catalog discovery from default dirs or vstack.toml `[catalog]`
├── catalog/roots.rs Configured-path expansion and the entry-type rule a root must satisfy to be readable
├── mapping.rs Source vstack.toml — MappingConfig (catalog, agent-skills, role-skills, hook-events)
├── project_config.rs Project vstack.toml — ProjectConfig, ensure/write/update
├── resolve.rs Shared helpers — skill-pair resolution, hook source attribution/matching, read_existing_extras, is_vstack_source
├── json_config.rs Harness JSON configs vstack shares (claude settings.json, codex hooks.json, opencode.json/.jsonc, Pi settings.json) — the syntax and declared schema every reader and writer validates against
├── json_config/ deviation.rs — walking a document against its schema and naming the value that deviates, since that message is the user's whole remedy
├── installer.rs Symlink/copy logic, install/remove orchestration
├── installer/hooks.rs Hook install/remove orchestration and shared Claude/Codex/Cursor helpers
├── installer/hooks/ Hook submodules — OpenCode cleanup/install and focused hook tests
├── installer/hooks.rs Hook install/remove orchestration and shared Claude helpers
├── installer/hooks/ contract.rs (event × harness matrix), enforcement.rs (per-install resolution of it), command.rs (reading a registered command the way a shell would), per-harness submodules — codex.rs (native registration, config.toml feature, prose fallback), cursor.rs (safety rule + its alwaysApply switch), opencode.rs — and focused hook tests
├── harness/ (canonical → per-harness translation)
│ ├── claude.rs → .claude/agents/*.md (disallowedTools, effort/background/isolation/memory, skills, hooks frontmatter)
│ ├── cursor.rs → .cursor/rules/*.mdc (description + alwaysApply + skills)
Expand Down Expand Up @@ -53,7 +65,11 @@ Seam map and sequenced split plan for `project_config.rs` and `installer.rs`: [d
- **Report ownership is identity-based.** Installed lock entries stamp the source GitHub `owner/repo` as `source_repo` when resolvable. `vstack report` uses skill/agent frontmatter first, then `source_repo` or a live source Git origin; a vstack-shaped local directory alone is never proof of upstream ownership.
- **Upstream reports carry a routing label.** vstack-targeted issues get one flat label — `cli`, `skills`, `harness` (team VST subsystems) or `ci-infra`, `docs`, `chore` (existing workspace labels, reused) — derived from the asset selector or set explicitly with `--area`. The GitHub→Linear sync carries it across, where a Linear triage rule files the synced issue into the matching project. Labels are bare words with no prefix or parent group, matching memsira/hyprtrade; `agent:*` is the workspace's only grouped-and-prefixed set. Project-local reports never get one: a `gh` call naming a label the target repo lacks fails outright. See [docs/issue-label-taxonomy.md](docs/issue-label-taxonomy.md).
- **One hook execution contract.** `cli/src/installer/hooks/contract.rs` holds the event × harness matrix — enforcement mechanism, advisory, or unsupported — and every install path, CLI label, and the published table derive from it. Advisory artifacts carry `advisory — this harness cannot execute hooks`. Registered commands anchor on something that holds from any working directory in git and non-git projects: `$CLAUDE_PROJECT_DIR` for project-scope Claude, the install-time absolute path everywhere else.
- **Pi extensions are npm-shaped.** vstack copies them to `<scope>/packages/<name>`, runs a prod-only, lockfile-free `npm install` there when the package declares dependencies, and registers the path in Pi's `settings.json` `packages` array.
- **An enforcement level is only as good as the artifact behind it.** `cli/src/installer/hooks/enforcement.rs` downgrades a contract cell to `unsupported` unless everything the harness needs to invoke the hook is present — script AND registration, in the hook's own event and matcher, with Codex's `[features] hooks` on and Pi's carrier package deployed and registered. One reader per harness answers that question for `list`, `check` and `verify` alike, and it is the same predicate the installer replaces entries with and `remove` deletes them by, so no two of them can disagree about which registration is ours.
- **vstack never overwrites a value it could not understand.** Every harness config vstack shares — claude `settings.json`, codex `hooks.json` and `config.toml`, OpenCode's `opencode.json`/`opencode.jsonc`, Pi `settings.json` — is read through one declared schema in `cli/src/json_config.rs`. A deviation anywhere on the path vstack reads makes the whole document UNREADABLE: presence reports it unverifiable naming the file and the value, and every writer refuses rather than replacing what it could not parse. Add a key to the schema, not a probe at the call site that noticed — and add EVERY field the code goes on to interpret, with its real type. A read field left as `Any` is a field whose malformed value the reader silently rereads as its own fallback: a `"matcher"` that is not a string read as "no matcher" answered `Registered` for a hook the harness cannot deserialize and would never run. `Any` is a claim that vstack only ever preserves that value and draws no conclusion from it, so each one carries the per-field reason it is correct.
- **A presence check never returns on the first fault it finds.** Two faults co-occur constantly — a missing artifact and the config the reinstall for it would have to write — and a report that stops at the missing artifact prescribes a command the other fault refuses. Every per-harness reader in `cli/src/commands/verify/hooks.rs` asks all of its questions and collects the answers into three lists; the classification is `Unverifiable` → `Missing` → `Disabled`, which is the order the remedies have to happen in, and every note rides along so a repair is never followed by a second surprise. The one thing a fault may suppress is a fault the SAME command would fix anyway (a claude registration beside a missing script; codex's `hooks.json` and `[features] hooks` beside a missing script) — never one it would refuse. Same rule outside hooks: `install_gap` reads Pi settings whatever the package directory says and names an unusable `.agents` ahead of the project skill it blocks, and `cli/src/config/remote_cache/problems.rs` puts an unwritable cache ahead of the fetch failure its frozen stamp still records.
- **A config's syntax is the harness's parser, never its file extension.** `Syntax` sits beside the schema in `cli/src/json_config.rs`: OpenCode hands every spelling of its config to one JSONC parser, so comments and trailing commas are content in `opencode.json` as much as in `opencode.jsonc`, while claude, codex and Pi parse strictly and a comment there really is a broken file. Reading with the wrong parser breaks both ways — too strict calls a working install unverifiable and blocks `add`/`remove` on it, too loose accepts a file the harness silently drops and then rewrites it. A JSONC config is edited through `jsonc-parser`'s CST, the way codex's `config.toml` is edited through `toml_edit`, so an install or removal changes only its own value and every comment, blank line and key order survives.
- **Pi extensions are npm-shaped.** vstack copies them to `<scope>/packages/<name>`, runs a prod-only, lockfile-free `npm install` there when the package declares dependencies, and registers the path in Pi's `settings.json` `packages` array. Both artifacts define presence: `check`/`verify` call a package installed only when the copy exists AND a `packages` entry resolves to it, since Pi loads only what that array points at. Entries are compared as paths, so any spelling resolving to the same directory counts.
- **Skill/hook attribution is config-driven.** Source `vstack.toml` `[agent-skills]` is authoritative — explicit entries skip prefix matching; `[role-skills]` adds skills to all agents of a role. Project `vstack.toml` gets `[agent-skills]` populated at install; users edit and refresh. Markdown harnesses get `skills:` frontmatter; Codex agents get a "Required Skills" instruction section.
- **Reconciliation is automatic.** After every `vstack add`, all installed agents are regenerated with the current full set of installed skills and hooks.
- **Project root walks up from CWD.** `config::project_root()` finds `.vstack-lock.json` or a harness dir (`.claude/`, `.cursor/`, `.codex/`, `.opencode/`, `.pi/`, `.agents/`) by walking parents. `$HOME` with only user-level harness dirs and no lock file is rejected, so project-scope writes never route into user state.
Expand Down Expand Up @@ -198,6 +214,7 @@ Each canonical agent declares its own `effort:` (`low` | `medium` | `high` | `xh
- **`vstack add` scope is destructive — read the printed summary.** Every non-interactive run prints `Scope: PROJECT (...)` vs `GLOBAL (...)`, method, and every item written. Confirm both before claiming success.
- **Never `--global` without an item filter.** CLI refuses `--global -y` unless `--all` or an item filter is set. Item filters are exclusive, except `--agent` auto-includes dependent skills from `[agent-skills]` + `[role-skills]` (opt out with `--no-auto-skills`); auto-included skills appear in the scope summary.
- **Scope flag is uniform.** `list`, `check`, `refresh`, `remove` accept `--scope project|global|all`; `-g` = `--scope global`. Default: `all` for read-only, `project` for `remove`. Bare `vstack refresh` reinstalls items at every scope they're locked at.
- **`vstack check` is the drift contract.** Exit `0` clean, `1` drift, `2` the check failed; available-but-not-installed items are suggestions, never drift, while a source cache that has been failing to refresh for more than two TTL windows (or cannot be written at all) is. A source whose cache another vstack process is fetching while the check runs is reported unchecked — its entries are classified as nothing at all, and it is neither drift nor counted clean; readers probe that lock and never wait on it. A source's malformed asset is drift only for the kinds that scope installs from it, and an install whose evidence is itself unreadable is reported as that, never as a phantom — reinstalling repairs nothing there. An install that is complete and SWITCHED OFF (Claude `disableAllHooks`, Codex `[features] hooks`, a Cursor rule whose `alwaysApply` is not `true`) is its own report naming the setting and the file — also drift, also not a phantom. OpenCode has no such switch; Pi's live in vstack's own extension-manager UI and are deliberately never reported. Drift is computed from disk alone — a cache older than the six-hour TTL is refreshed by a detached `vstack cache-refresh` nobody waits on, and its outcome is reported at the next session. The only network call left is the human report's CLI-version lookup, which `--quiet` (the hook path) and `--offline` skip. A background refresh that cannot even be started is reported on its own line — informational, never drift. `--quiet` prints nothing when clean, `--json` goes to stdout, `--offline` skips the CLI version lookup and the background refresh while still reporting recorded cache failures. The `session-drift-check` hook and the Pi `pi-hooks` `sessionDriftCheck` setting are thin adapters over `check --quiet` — change the report in `cli/src/commands/check/render.rs`, not in the adapters.
- **Verify after refresh.** `vstack refresh -v` prints per-item `old→new` hash; `vstack verify [-g] [name…]` confirms source matches lock and byte-matches Pi package installs. Use both before claiming a change is live.
- **Docs and instruction payloads ship with the code change.** Any change to a hook, skill, agent, or Pi extension updates — in the same commit — affected READMEs, AGENTS.md, `vstack.toml`, settings examples, `package.json`, and agent instruction payloads. A behavior change without its docs update is incomplete.
- **Every Pi extension keeps a `CHANGELOG.md` in its package folder** (`pi-extensions/<name>/CHANGELOG.md`), led by a `## Consumer-impacting changes` section. This is the authoritative channel for critical developer information to consumers and repos that vendor an extension: record behavior deltas, new/renamed/removed exports, settings and config changes, and protocol/audit-shape changes under the version that ships them, in the same commit as the change (with the `package.json` bump). Internal-only changes may be omitted; a consumer-impacting change without its changelog entry is incomplete. `package-policy.test.mjs` enforces the file's presence and shape.
Expand Down
Loading
Loading