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
1,628 changes: 1,628 additions & 0 deletions .claude/hooks/session-journal.py

Large diffs are not rendered by default.

73 changes: 73 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{
"hooks": {
"SessionStart": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.claude/hooks/session-journal.py\" hook SessionStart || true",
"timeout": 15
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.claude/hooks/session-journal.py\" hook Stop || true",
"timeout": 30
}
]
}
],
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.claude/hooks/session-journal.py\" hook UserPromptSubmit || true",
"timeout": 10
}
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write|MultiEdit|NotebookEdit|Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.claude/hooks/session-journal.py\" hook PreToolUse || true",
"timeout": 10
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write|MultiEdit|NotebookEdit|Bash",
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.claude/hooks/session-journal.py\" hook PostToolUse || true",
"timeout": 10
}
]
}
],
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "python3 \"${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.claude/hooks/session-journal.py\" hook SessionEnd || true",
"timeout": 5
}
]
}
]
}
}
32 changes: 23 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,46 @@ permissions:
jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: bats-core/bats-action@3.0.1
# Actions are pinned by commit SHA. This repository already SHA-pins the
# gitleaks tarball below; a mutable tag on an action that can read the
# workflow token is the weaker link of the two.
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- uses: bats-core/bats-action@42fcc8700f773c075a16a90eb11674c0318ad507 # 3.0.1
- name: Install shellcheck on macOS
if: runner.os == 'macOS'
run: brew install shellcheck
- name: Validate shell syntax
run: |
bash -n hooks/session-journal-pointer.sh
bash -n hooks/session-journal-nudge.sh
bash -n extras/spine-promotion/journal-spine-promotion-nudge.sh
bash -n install.sh
bash -n doctor.sh
- name: Run shellcheck
run: shellcheck hooks/*.sh extras/spine-promotion/*.sh install.sh
run: shellcheck install.sh doctor.sh
- name: Run the hook scenario tests
# The runner's python3; the script itself is also checked to parse
# under the oldest supported interpreter's grammar (3.8).
run: |
python3 --version
python3 -c "import ast; ast.parse(open('hooks/session-journal.py').read(), feature_version=(3, 8))"
python3 -m unittest discover -s tests -p 'test_*.py'
- name: Run bats suites
run: bats tests
- name: Check tracked journals with the runtime secrets guard
run: printf '%s' '{"stop_hook_active":false}' | JOURNAL_FRESH_SECS=999999999 hooks/session-journal-nudge.sh
- name: Self-check this repository's own installation
run: ./doctor.sh .
- name: Check every tracked journal for credential shapes
# The hook checks each journal write as it happens; CI re-checks every
# tracked journal, because older journals are history a reader still
# trusts. Line numbers and rule names only, never values.
run: python3 hooks/session-journal.py scan sessions/2*.md

secrets:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0
- name: Install gitleaks 8.24.3
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
sessions/*.local.md
.DS_Store
__pycache__/
8 changes: 8 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Synthetic credential fixtures in 1.1.0 test history (tests/secrets-guard.bats, tests/doctor.bats).
# They exist to prove the secrets check fires; none is a real credential.
a9a5e9b5571efc042c3bb608be2e4c72516420ac:tests/secrets-guard.bats:generic-api-key:123
a9a5e9b5571efc042c3bb608be2e4c72516420ac:tests/secrets-guard.bats:generic-api-key:124
a9a5e9b5571efc042c3bb608be2e4c72516420ac:tests/secrets-guard.bats:generic-api-key:125
a9a5e9b5571efc042c3bb608be2e4c72516420ac:tests/secrets-guard.bats:generic-api-key:128
a9a5e9b5571efc042c3bb608be2e4c72516420ac:tests/secrets-guard.bats:generic-api-key:131
a9e4e2a3a55261d6b4d234eb80f12ebcceacabc7:tests/doctor.bats:generic-api-key:65
141 changes: 105 additions & 36 deletions ADOPT.md
Original file line number Diff line number Diff line change
@@ -1,61 +1,130 @@
# Agent Adoption Contract

You are installing the Session Journal discipline into a target repository. Adapt it to the repository you actually find. Preserve existing configuration and prove the result before finishing.
You are installing the Session Journal discipline into a target repository. Adapt it to the repository you actually
find, preserve existing configuration, and prove the result before finishing.

**Read `skills/session-journal/SKILL.md` first and follow it.** The skill holds the judgment calls:

- where journals live;
- how to adapt the setup to this repository's agents and orchestration;
- lead and worker roles;
- migrating old journals;
- what may never go in a journal.

This contract adds the order of work, the question budget, and the proof you must report.

Ask at most three questions, and only when discovery cannot answer them:

1. Which journal directory should be used, if both root `sessions/` and `docs/sessions/` are equally plausible?
1. Which journal directory should be used, if root `sessions/` and `docs/sessions/` are equally plausible?
2. Should journals be committed (the default) or ignored locally?
3. Should the optional spine-promotion nudge be installed?
3. Should the optional spine-promotion nudge be turned on (`SPINE_PROMOTION_THRESHOLD`)?

## Phase A — survey without writing

Inspect and record for your final report:

1. Agent runtimes: `.claude/settings.json`, `.codex/config.toml`, `.codex/hooks.json`, and instruction-only runtimes such as Cursor or Copilot. Treat user-level configuration as a clue, never as permission to modify global files.
2. Instruction files: CLAUDE.md, AGENTS.md, both, or neither. Inventory existing hooks and plan a merge; never replace a hooks array.
3. Documentation topology: plans, specifications, ADRs, runbooks, or an issue tracker. Use the strongest active artifact as the default THIN pointer. With no docs culture, default to DETAILED and `none`.
4. Journal location: default to root `sessions/`; prefer `docs/sessions/` when the repository consistently keeps process docs under `docs/` or tightly controls published root files. A monorepo normally gets one workspace-root journal directory because a journal follows the human session, not a package.
5. Timezone: run `date +%Z` in the target repository and use that machine-local zone label. Do not use `date -u`, `UTC` by convention, or a zone inherited from this upstream repository.
6. Repository vocabulary: add branch lines for multi-repository work and name relevant issue, gate, ADR, or test identifiers in the local evidence guidance.
7. Ignore rules: journals are intended to be committed. Warn if an existing rule ignores the chosen directory, then ask Question 2 only if intent remains unclear.

Before writing, ask Question 3 unless the user's request already says whether to install the optional spine-promotion module. Do not infer opt-in or opt-out from repository shape.
Inspect the following and record what you find for your final report:

1. **Agent runtimes.** Check `.claude/settings.json`, `.codex/hooks.json`, `.codex/config.toml`, and
instruction-only runtimes. Treat user-level configuration as a clue, never as permission to modify global
files.
2. **Instruction files.** Check for CLAUDE.md, AGENTS.md, both, or neither. Inventory existing hooks and plan a
merge; never replace a hooks array.
3. **Documentation.** Look for plans, specifications, ADRs, runbooks, or an issue tracker. Use the strongest active
artifact as the default THIN pointer. If the repository has no documentation habit, default to DETAILED and
`none`.
4. **Journal location.** Follow skill §1.
- A monorepo gets one journal directory at its root.
- A repository whose developers use linked worktrees still gets one directory: the hooks resolve it in the main
checkout.
5. **Existing journals or notes.** If the repository already has v1 journals or session notes elsewhere, plan the
migration in skill §6.
6. **Multi-agent setup.** If an orchestrator dispatches worker agents, find how a worker can be recognised: an
environment variable the orchestrator can set, or the shape of the dispatch prompt. Skill §2 and §4 turn that
into `JOURNAL_ROLE=worker` or `JOURNAL_WORKER_PROMPT`.
7. **Timezone.** Run `date +%Z` in the target repository and use that local label.
8. **Ignore rules.** Journals are meant to be committed. Warn if a rule ignores the chosen directory or `.claude/`.
Ask Question 2 only if the intent stays unclear.
9. **Repository root.** The installer refuses a subdirectory of a git repository. Target the root.
10. **Python.** Confirm `python3 --version` reports 3.8 or later wherever agents run.

Before writing anything, ask Question 3, unless the user's request already says whether to install the optional
spine-promotion nudge.

## Phase B — install and adapt

1. Prefer the tested installer instead of rebuilding its merge logic. Clone or download this repository to a temporary location, then run `./install.sh <target>` with the discovered `--sessions-dir`, `--codex`, and/or `--no-claude` flags. If the installer cannot run, perform the equivalent steps below manually and explain why in the final report.
2. The installer puts one real copy of both scripts in `.claude/hooks/`, merges `SessionStart` and `Stop` without replacing existing groups, and uses runtime-relative commands so subdirectory starts and relocated clones work. Project-local Codex hooks require a trusted project and hook review through `/hooks`.
3. Adapt the generated sessions README with the strongest THIN default, the `date +%Z` result, and repository-specific evidence vocabulary. Keep the generated directory accurate.
4. Adapt the sentinel-delimited blocks in CLAUDE.md and AGENTS.md. Keep the twin-sync sentence only when both instruction files exist. Instruction-only runtimes need the manual end-of-turn check.
5. Set `JOURNAL_FRESH_SECS` in the generated hook commands only when the repository needs a non-default freshness window. The core installer does not manage this optional override.
6. If Question 3 opted into spine promotion, merge `extras/spine-promotion/journal-spine-promotion-nudge.sh` as an additional Stop handler and test it. This optional handler is manually managed and must also be removed manually; the core installer intentionally owns only its pointer and freshness/secrets hooks.

Never delete or rewrite an existing user hook. Never commit unless the user asked or repository conventions already authorize it. Never put a real or realistic credential in a demonstration journal. If a settings file contains comments or JSON5, do not normalize or overwrite it: show the matching snippet and ask the user to merge it manually.
1. Clone or download this repository to a temporary location. Then run `./install.sh <target>` with the discovered
`--sessions-dir`, `--plan-pointer`, `--codex`, and/or `--no-claude` flags. Do not rebuild its merge logic by hand.
If the installer cannot run, perform the equivalent steps manually from `hooks/*.snippet.json`, and explain why
in the report.
2. Adapt `<sessions>/README.md` (the THIN default, the `date +%Z` result, the evidence vocabulary) and, if needed,
`TEMPLATE.md`. Adapt the sentinel blocks in CLAUDE.md and AGENTS.md. Keep the twin-sync sentence only when both
files exist. A re-run of the installer preserves all of these.
3. Add any environment prefixes from skill §2 to the generated hook commands. A re-run preserves them.
4. If Question 3 opted into spine promotion, add `SPINE_PROMOTION_THRESHOLD=<n>` (and, if the plan directory is
not `docs/plans/`, `SPINE_PROMOTION_TARGET_HINT=<dir>`) to the front of the generated Stop command. A re-run of
the installer preserves it.

Never delete or rewrite an existing user hook. Never commit unless the user asked or repository conventions already
authorize it. Never put a real or realistic credential in a demonstration journal. If a settings file contains
comments or JSON5, do not normalize it: show the matching snippet and ask the user to merge it.

## Phase C — verify before finishing

Run and report this matrix:
Run `./doctor.sh <target>` and include its output. It must exit 0, apart from "the installation is committed",
which Phase D resolves.

Then exercise the hook directly. Pipe JSON into `python3 .claude/hooks/session-journal.py hook <Event>` from the
target root, using a throwaway `SESSION_JOURNAL_STATE_DIR`. For the cases that need landed work, also write a
two-line Claude Code transcript, and pass its path as `transcript_path`.

| Check | Expected proof |
| --- | --- |
| Static scripts | `bash -n` succeeds; both scripts are executable |
| Settings | `jq .` succeeds for each modified JSON file; pre-existing hooks remain |
| Empty pointer | Direct SessionStart run returns valid JSON with a create instruction |
| Populated pointer | A synthetic journal yields its relative path, Status, and complete next step |
| Fresh Stop | Fresh journal plus `{"stop_hook_active":false}` exits 0 |
| Stale Stop | Old THIN and DETAILED fixtures exit 2 with mode-correct guidance |
| Loop protection | `{"stop_hook_active":true}` skips freshness and exits 0 |
| Secrets guard | Construct a synthetic credential assignment at runtime; Stop exits 2 without echoing the value |
| Empty start | With no journal, SessionStart returns context that names `TEMPLATE.md` |
| Candidates | A synthetic ACTIVE journal is listed with its next step; a DONE one is not |
| Binding | A PostToolUse `Write` to a journal binds it: `whoami <sid>` shows it |
| IN USE | A PreToolUse `Write` from a second session id into that journal is denied |
| Read-only turn | Stop with no edits in the transcript prints `{}` |
| Landed turn | Stop after an edit and a commit, with no journal write, returns `decision: block` once; a second Stop in the same turn does not |
| Credential | A PreToolUse `Write` whose content holds a credential assignment built at runtime is denied, and the reason does not contain the value |
| Redaction | The same write with `<redacted>` is allowed |

Delete the fixtures and the throwaway state. Then write the first real journal, about this adoption session, with
no secrets.

For Codex, trust the project, review the hooks with `/hooks`, and run one short task with the installed Codex
version. For Claude Code, run a short headless task if available. If a runtime cannot be exercised, say so. Never
claim that an unobserved hook fired.

## Phase D — hand it to the team

1. Commit `.claude/` (and `.codex/` and `.agents/` when Codex is wired), the instruction blocks, and the journal
directory together. `.claude/state/` ignores itself.
2. Confirm that no ignore rule excludes them. The doctor checks this.
3. Tell the user what a teammate must do on first clone (normally nothing). Tell them that `./doctor.sh` is how
anyone checks the setup later.
4. Mention `python3 .claude/hooks/session-journal.py skip` and `SESSION_JOURNAL_SKIP=1`. Mention
`JOURNAL_ROLE=worker` for dispatched agents.

Delete verification fixtures. Then write the first real journal about this adoption session, with no secrets, and run the pointer once more so the install demonstrates itself.
## Phase E — document reversal

For Codex, trust the project, review the changed hooks with `/hooks`, and verify behavior with the installed Codex version. For Claude Code, run a small headless task if available. If a runtime cannot be exercised, say so and retain the manual fallback wording; never claim an unobserved hook fired.
`./install.sh <repo> --uninstall`, with the runtime flags you installed with, removes:

## Phase D — document reversal
- the owned hook groups (v1 and v2), the script, and the skills;
- local state;
- the instruction blocks.

Uninstalling removes the two owned hook groups, the two installed scripts, and the sentinel-delimited instruction blocks. Keep the journal directory by default because it is project history. The deterministic installer performs this with `./install.sh <repo> --uninstall` using the same runtime flags used for installation.
It keeps the journal directory, its README, and TEMPLATE.md, because they are project history. Upgrading is the
same command as installing, run from a fresh clone.

## Final report

Report the survey decisions, changed files, exact verification matrix, any runtime limitation, first-journal path, and uninstall command. Mention any question that used the three-question budget.
Report:

- the survey decisions;
- the changed files;
- the `./doctor.sh` output and the behavioural matrix;
- any runtime limitation;
- the role configuration;
- the first journal's path;
- whether the install was committed;
- the uninstall command;
- any question that used the three-question budget.
Loading
Loading