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 AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Before ending a session that changed team status, decisions, or work another per
1. Update `HANDOFF.md` (overwrite; keep it current, not a diary)
2. Update `STATUS.md` if in-flight work changed
3. Append to `DECISIONS.md` only for a real decision
4. Commit and push this repo
4. Follow `skills/zero-state-context/SKILL.md` Safe shared-context workflow: reconcile freshness, review named staged files for secrets, commit locally, and push only with explicit current authorization. Verify the exact remote commit afterward.

## Do not

Expand Down
2 changes: 1 addition & 1 deletion CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ If history matters, append a dated note to `DECISIONS.md` or the product repo in

## Pull before you write

Someone else may have pushed. `git pull --ff-only` in `~/agent-context` before editing.
Someone else may have pushed. Follow `skills/zero-state-context/SKILL.md` Safe shared-context workflow: clean-tree checks, confirmed upstream, freshness before and after drafting, reviewed named staging, authorized publication and exact remote read-back. Stop on divergence.

## Size

Expand Down
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,27 @@ gh repo clone Zero-State-LLC/agent-context ~/agent-context
~/agent-context/install.sh
```

Then `git pull` in `~/agent-context` at the start of a working day. After a session that changed team status, update the files here and `git push`.
Follow the [safe shared-context workflow](skills/zero-state-context/SKILL.md) before reading or drafting. Publication is separately authorized, reviewed, and verified; installation does not grant push authority.

`install.sh` only **symlinks** the shared rule and skill into your local Grok / Claude / Cursor dirs (`alwaysApply` on the Cursor `.mdc`). It does not copy personal memory.
`install.sh` only **symlinks** the shared rule and skill into your local Hermes / Grok / Claude / Cursor dirs (`alwaysApply` on the Cursor `.mdc`). It does not copy personal memory.

Cursor Cloud VMs do not inherit `~/.cursor`. Clone this repo (or `gh repo clone Zero-State-LLC/agent-context ~/agent-context`) and read `HANDOFF.md` at session start.

## Hermes only

With Bash available, run `bash install.sh --hermes-only --dry-run` from the
reviewed checkout, inspect the destination, then `bash install.sh --hermes-only`.
The skill is linked at `${HERMES_HOME:-$HOME/.hermes}/skills/zero-state-context`.
Keep the clone at its installed path; restart Hermes and load
`skill_view(name="zero-state-context")`, checking its returned skill_dir.
Existing unrelated destinations are refused, not overwritten. To migrate,
review the old link/package and move it outside discovery before reinstalling.
`--dry-run` works for the multi-host default too and writes nothing.

## What stays private

- `~/.grok/memory/`, Claude/Cursor/OpenClaw session logs
- Preferences, unfinished rabbit holes
- Tokens, keys, `.env`, anything that could authenticate

Skill source selection, migration and safe checks: [SKILL_PROVENANCE.md](SKILL_PROVENANCE.md).
30 changes: 30 additions & 0 deletions SKILL_PROVENANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Skill source provenance and verification

Before installation record `git remote get-url origin`, `git rev-parse HEAD`,
skill path/name/version, and resolved host/profile target. Repository names or
identical triggers do not prove two packs are interchangeable. The operator must
choose the source/version; this document makes no new canonical ownership choice.
Preserve existing domain authority distinctions (especially SUAS specification
versus runtime conformance). Do not install both same-named variants into one
profile without a deliberate selection. Avoid globally installing whole project
packs when repository-local discovery is enough.

Before migration compare source contents and dependencies, preserve the old
package/link and mutable state outside discovery, and record old/new commit and
target. Verify the selected new package before retiring the old discovery entry.
Deprecation requires owner approval and an explicit replacement source/commit;
do not silently delete another profile's copy or infer retirement from age.

## Safe local checks

Run from this checkout; Python 3.11+, pytest for pytest commands, and
PyYAML for SUAS metadata tests (`python3 -m pip install pytest PyYAML` in a
disposable venv). No live enrollment, deploy, or publication is needed.

```bash
python3 -m pytest tests/test_skill_install.py -q
```

Shared synchronization remains a human/agent procedure, not an executable guard.
Installer tests do not certify concurrent editing or remote publication. Review
the baseline SHA, outgoing content and current authorization on each handoff.
48 changes: 44 additions & 4 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,70 @@ set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
RULE_SRC="$ROOT/rules/zero-state-context.md"
SKILL_SRC="$ROOT/skills/zero-state-context"
DRY_RUN=0
HERMES_ONLY=0
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
--hermes-only) HERMES_ONLY=1 ;;
--help) printf '%s\n' 'Usage: install.sh [--hermes-only] [--dry-run]'; exit 0 ;;
*) printf 'Unknown argument: %s\n' "$arg" >&2; exit 2 ;;
esac
done

reject_symlink_ancestors() {
local parent
parent="$(dirname "$1")"
# Do not canonicalize first: that would hide profile/skills redirects.
while :; do
if [[ -L "$parent" ]]; then
printf 'Refusing symlink ancestor: %s\n' "$parent" >&2
exit 1
fi
[[ "$parent" != / && "$parent" != . ]] || break
parent="$(dirname "$parent")"
done
}

link_file() {
local src="$1" dest="$2"
reject_symlink_ancestors "$dest"
if [[ -e "$dest" || -L "$dest" ]]; then
if [[ -L "$dest" && "$(readlink "$dest")" == "$src" ]]; then
printf 'already linked %s\n' "$dest"
return
fi
printf 'Refusing to replace existing destination: %s\n' "$dest" >&2
exit 1
fi
if [[ "$DRY_RUN" == 1 ]]; then
printf '[dry-run] link %s -> %s\n' "$dest" "$src"
return
fi
mkdir -p "$(dirname "$dest")"
ln -sfn "$src" "$dest"
reject_symlink_ancestors "$dest"
ln -s "$src" "$dest"
echo "linked $dest"
}

# Hermes is profile-scoped; keep the source checkout at this path.
link_file "$SKILL_SRC" "${HERMES_HOME:-$HOME/.hermes}/skills/zero-state-context"
[[ "$HERMES_ONLY" == 0 ]] || exit 0

# Grok
link_file "$RULE_SRC" "${GROK_HOME:-$HOME/.grok}/rules/zero-state-context.md"
link_file "$SKILL_SRC" "${GROK_HOME:-$HOME/.grok}/skills/zero-state-context"

# Claude Code (create dirs even if unused yet)
mkdir -p "$HOME/.claude/rules" "$HOME/.claude/skills"
link_file "$RULE_SRC" "$HOME/.claude/rules/zero-state-context.md"
link_file "$SKILL_SRC" "$HOME/.claude/skills/zero-state-context"

# Cursor local IDE — user-level alwaysApply rule (.mdc) + skill
mkdir -p "$HOME/.cursor/rules" "$HOME/.cursor/skills"
link_file "$RULE_SRC" "$HOME/.cursor/rules/zero-state-context.mdc"
link_file "$SKILL_SRC" "$HOME/.cursor/skills/zero-state-context"

echo
echo "Clone path: $ROOT"
echo "Pull this repo at the start of a working day. Push after a real handoff."
echo "Follow the skill safe-sync workflow; publishing requires current user authorization."
echo "Personal memory stays local — do not copy ~/.grok/memory into git."
echo "Cursor Cloud does not see ~/.cursor; it must clone this repo or read HANDOFF via gh."
18 changes: 6 additions & 12 deletions rules/zero-state-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,9 @@ Team knowledge for Zero State LLC lives in the git repo cloned at `~/agent-conte

This rule applies in Cursor, Claude Code, Grok, Codex, and OpenClaw. Personal memory and chat logs are not the sync channel.

At session start, when the task involves org status, a partner handoff, PLAY/Noema, or work another Zero State agent may have touched:

1. `git -C ~/agent-context pull --ff-only` if the clone exists
2. Read `~/agent-context/HANDOFF.md`
3. Read `~/agent-context/STATUS.md`
4. Read `DECISIONS.md` only if a prior choice affects the task

If `~/agent-context` is missing: `gh repo clone Zero-State-LLC/agent-context ~/agent-context && ~/agent-context/install.sh`

Before ending a session that changed team status, overwrite `HANDOFF.md`, update `STATUS.md`, append `DECISIONS.md` only for a real decision, then commit and push `~/agent-context`.

Personal memory, session logs, and secrets do not go in that repo. Product code maps stay in each product repo's `graft/` and `AGENTS.md`.
Use the [shared-context skill](../skills/zero-state-context/SKILL.md)'s
Safe shared-context workflow: confirm repo/remote/branch, require a clean tree,
fetch and fast-forward before reading/drafting, reconcile concurrent handoffs,
review precisely staged files for secrets, and publish only with current user
authorization. Verify the exact remote commit after an authorized push.
Do not copy personal memory, raw transcripts, or secrets into team context.
59 changes: 43 additions & 16 deletions skills/zero-state-context/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,46 @@ Team durable knowledge is the git repo at `~/agent-context` (or `$ZERO_STATE_CON

Any session that continues org work, partner work, or a prior agent session.

Read, in order:

1. `HANDOFF.md`
2. `STATUS.md`
3. `DECISIONS.md` only if a prior choice affects this task

## When to write

If this session changed what the other person or another model must know:

1. Overwrite `HANDOFF.md` with the current baton
2. Update `STATUS.md` if in-flight work changed
3. Append a dated entry to `DECISIONS.md` only for a real decision
4. `git pull --ff-only && git add … && git commit && git push` in that repo

Do not write secrets, personal preferences, or raw transcripts there.
## Safe shared-context workflow

Resolve `${ZERO_STATE_CONTEXT:-$HOME/agent-context}` to the intended repository.
Confirm `git remote -v`, the named branch (`git branch --show-current`), its
upstream (`git rev-parse --abbrev-ref '@{upstream}'`), and the user's allowed
publication scope. Stop if detached, no upstream, wrong remote, or any dirty or
staged changes (`git status --porcelain`); never stash or discard teammates' work.

Before reading or drafting, fetch the confirmed remote and fast-forward only:
`git fetch <confirmed-remote>` then `git merge --ff-only '@{upstream}'`.
Stop on divergence or fetch failure; offline context must be labeled stale.
Require `git rev-parse HEAD` to equal `git rev-parse '@{upstream}'` after
fast-forwarding; stop on unreviewed local-ahead commits so they cannot hitchhike.
Coordinate one local editor per clone; if another editor changes the draft,
repeat content review and obtain fresh authorization.
Record `git rev-parse HEAD` as the baseline. Read HANDOFF.md, STATUS.md, and
DECISIONS.md only as relevant. Reconcile the partner's baton; do not blindly
replace content just because a textual merge would succeed.

Draft only changes needed for the present handoff. HANDOFF.md stays concise,
STATUS.md tracks in-flight work, and DECISIONS.md is append-only for real decisions.
Before committing, fetch again and compare `git rev-parse '@{upstream}'` with the
recorded baseline. If it moved, stop: preserve the draft separately, reconcile
against the new handoff with the operator, and repeat review. No force push.

Stage only the actual named context files, for example
`git add -- HANDOFF.md STATUS.md` (include DECISIONS.md only when edited).
Review `git diff --cached --check`, `git diff --cached --name-only`, and the full
`git diff --cached` for accidental deletion, secrets, tokens, personal data and
raw transcripts. Do not commit until the reviewed file list and content match
scope. If secrets are found, stop and arrange rotation; never publish them.
Commit locally. Publication requires explicit current user authorization for the
confirmed remote and branch; without it report the local commit and do not push.

When authorized, push the explicit destination:
`git push <confirmed-remote> HEAD:refs/heads/<confirmed-branch>`.
On rejection, stop and reconcile; do not force. Read back that exact remote ref
with `git ls-remote <confirmed-remote> refs/heads/<confirmed-branch>` and compare
the returned SHA to `git rev-parse HEAD`. Only an exact match verifies publication;
a newer/different SHA needs reconciliation, not a success claim.

These are operator-resolved placeholders, not literal commands to paste before
confirming repository/remote/branch. No network publication is part of installation.
59 changes: 59 additions & 0 deletions tests/test_skill_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import os
from pathlib import Path
import subprocess
import sys
ROOT = Path(__file__).resolve().parents[1]

import pytest

@pytest.mark.parametrize("component", ["skills", "profile", "leaf"])
@pytest.mark.parametrize("dry_run", [False, True])
def test_symlink_redirect_is_refused(tmp_path, component, dry_run):
profile = tmp_path / "home/profile"
other = tmp_path / "home/other"
other.mkdir(parents=True)
(other / "sentinel").write_text("keep")
if component == "profile":
profile.symlink_to(other, target_is_directory=True)
else:
profile.mkdir()
if component == "skills":
(profile / "skills").symlink_to(other, target_is_directory=True)
else:
(profile / "skills").mkdir()
(profile / "skills" / 'zero-state-context').symlink_to(other, target_is_directory=True)
env = dict(os.environ, HOME=str(tmp_path / "home"), HERMES_HOME=str(profile))
command = ["bash", str(ROOT / "install.sh"), "--hermes-only"]
result = subprocess.run(command + (["--dry-run"] if dry_run else []),
cwd=tmp_path, env=env, capture_output=True, text=True)
assert result.returncode != 0, result.stdout
assert "symlink" in result.stderr.lower(), result.stderr
assert (other / "sentinel").read_text() == "keep"
assert sorted(p.name for p in other.iterdir()) == ["sentinel"]


def test_profile_install(tmp_path):
env = dict(os.environ, HOME=str(tmp_path / "home"), HERMES_HOME=str(tmp_path / "profile"))
command = ['bash', str(ROOT / 'install.sh'), '--hermes-only']
result = subprocess.run(command + ["--dry-run"], cwd=tmp_path, env=env, capture_output=True)
assert result.returncode == 0, result.stderr
assert not (tmp_path / "profile").exists()
assert not (tmp_path / "home").exists()
result = subprocess.run(command, cwd=tmp_path, env=env, capture_output=True)
assert result.returncode == 0, result.stderr
dest = tmp_path / "profile/skills/zero-state-context"
assert (dest / "SKILL.md").read_text().startswith("---\n")
for source in (ROOT / "skills/zero-state-context").rglob("*"):
if source.is_file():
assert (dest / source.relative_to(ROOT / "skills/zero-state-context")).read_bytes() == source.read_bytes()
assert not (tmp_path / "home").exists()

def test_existing_directory_is_preserved(tmp_path):
dest = tmp_path / "profile/skills/zero-state-context"
dest.mkdir(parents=True)
(dest / "sentinel").write_text("keep")
result = subprocess.run(['bash', str(ROOT / 'install.sh'), '--hermes-only'],
env=dict(os.environ, HOME=str(tmp_path / "home"), HERMES_HOME=str(tmp_path / "profile")),
capture_output=True)
assert result.returncode != 0
assert (dest / "sentinel").read_text() == "keep"
Loading