@sayansr26/agent-os 0.5.3 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +59 -0
- package/README.md +27 -7
- package/package.json +1 -1
- package/plugins/agent-os/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-os/agents/architect.md +5 -0
- package/plugins/agent-os/agents/builder.md +7 -2
- package/plugins/agent-os/agents/documenter.md +6 -1
- package/plugins/agent-os/agents/feature-cartographer.md +18 -1
- package/plugins/agent-os/agents/orchestrator.md +5 -0
- package/plugins/agent-os/agents/reviewer.md +5 -0
- package/plugins/agent-os/agents/tester.md +5 -0
- package/plugins/agent-os/hooks/hooks.json +14 -1
- package/plugins/agent-os/hooks/pre-edit.mjs +83 -0
- package/plugins/agent-os/hooks/session-resume.mjs +75 -18
- package/plugins/agent-os/lib/features.mjs +154 -0
- package/plugins/agent-os/skills/init/SKILL.md +72 -32
- package/plugins/agent-os/skills/init/references/changing-a-feature.md +7 -0
- package/plugins/agent-os/skills/init/references/establishing.md +28 -0
- package/plugins/agent-os/skills/init/references/git-permissions.md +5 -2
- package/plugins/agent-os/skills/init/scripts/audit.mjs +73 -2
- package/plugins/agent-os/skills/init/scripts/settings.mjs +186 -0
- package/plugins/agent-os/skills/init/scripts/state.mjs +133 -0
- package/src/claude-setup.mjs +9 -78
- package/src/cli.mjs +71 -28
- package/src/plugin.mjs +74 -33
- package/src/selftest.mjs +62 -3
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,65 @@ All notable changes to this project are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
6
|
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.6.0] — 2026-09-25
|
|
9
|
+
|
|
10
|
+
Plugin 0.5.0. Found by running 0.4.1 in a real project: the plugin passed its
|
|
11
|
+
own audit while Claude ignored it — a whole feature was built without the
|
|
12
|
+
cartographer, and the audit still said `MAINTAIN`.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Claude uses agent-os without being told to.** The SessionStart hook now
|
|
16
|
+
prints, after the git snapshot, which agent to use for what, which features
|
|
17
|
+
the cartographer has mapped, the plan-mode rule and where rules are edited.
|
|
18
|
+
It also fires on `compact`, so the contract survives a long session. The
|
|
19
|
+
"snapshot, not instructions" footer now covers only the snapshot; it used to
|
|
20
|
+
read as an instruction to ignore everything the hook said.
|
|
21
|
+
- **PreToolUse hook on Edit/MultiEdit/Write.** Blocks edits to files generated
|
|
22
|
+
from `.agent-os/` — the next sync reverted them silently — and names the
|
|
23
|
+
source to edit. The first edit to an existing feature with no map adds a
|
|
24
|
+
one-time note to ask the cartographer first. Advisory, never blocks; off with
|
|
25
|
+
`claude.cartographerReminder: false`.
|
|
26
|
+
- **Settings are written, not handed over.** `scripts/settings.mjs` (and
|
|
27
|
+
`agent-os settings`) merges the git write-protection deny rules and
|
|
28
|
+
`CLAUDE_CODE_ENABLE_TODO_TOOLS` into project and user settings, and the
|
|
29
|
+
task-tracking rule into each `CLAUDE.md`. `/agent-os:init` previews, asks
|
|
30
|
+
once, applies. `agent-os init` writes project scope and asks before
|
|
31
|
+
`~/.claude` (`--global` / `--no-global`). Changed `~/.claude` files are backed
|
|
32
|
+
up to `*.agent-os.bak`. This reverses 0.5.3's "nothing writes to
|
|
33
|
+
`~/.claude/`": in practice that meant the protection was never applied.
|
|
34
|
+
- **`init` knows a fresh setup from a repair.** `scripts/state.mjs` checks the
|
|
35
|
+
source, CLAUDE.md, git protection and task tools at both scopes, the
|
|
36
|
+
task-tracking rule, the agent triggers and the plugin (installed, enabled,
|
|
37
|
+
version) and says `FRESH`, `REPAIR` or `HEALTHY`. `agent-os init` prints it
|
|
38
|
+
first, keeps an existing `.agent-os/` instead of re-scaffolding, and ends
|
|
39
|
+
with before → after and anything still to do. The audit prints the same
|
|
40
|
+
block, and `/agent-os:init` fixes only the `MISSING` items on a repair.
|
|
41
|
+
- **`agent-os init` updates the plugin.** It used to install only; a project
|
|
42
|
+
installed once stayed on that version. Now it refreshes the marketplace and
|
|
43
|
+
runs `claude plugin update` (or `enable`, or `install`) for this project's
|
|
44
|
+
install, reports `from → to`, and runs every `claude` command in the project
|
|
45
|
+
directory so `--root` installs into the right project.
|
|
46
|
+
- `claude.features` in `.agent-os/config.json` names the feature directories
|
|
47
|
+
(`["src/features/*"]` style). Defaults to the first of `src/features`,
|
|
48
|
+
`src/modules`, `app/features`, `features`, `modules`.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
- **The audit's "mapped" check was a folder-exists test.** It now requires the
|
|
52
|
+
architecture map and reports feature coverage (mapped of total) and maps
|
|
53
|
+
older than their feature's last commit.
|
|
54
|
+
- The audit now flags generated `.claude/rules/*` that drifted from
|
|
55
|
+
`.agent-os/rules/`, folders nested inside agent memory, missing git
|
|
56
|
+
protection or task tools, a CLAUDE.md with no agent triggers, and a
|
|
57
|
+
project-level cartographer reminder hook the plugin now duplicates.
|
|
58
|
+
- **Nested `.claude/agent-memory/` inside agent memory.** Every agent is told
|
|
59
|
+
to write flat, by absolute path, inside its own memory directory.
|
|
60
|
+
- **Plan mode skipped the cartographer.** The cartographer answers read-only in
|
|
61
|
+
plan mode and files its map when called again after; the change workflow
|
|
62
|
+
and session contract say so.
|
|
63
|
+
- Builder, cartographer and documenter name `.agent-os/rules/` as the rule
|
|
64
|
+
source when the project has one. `establishing.md` adds an "Agents in this
|
|
65
|
+
project" section to CLAUDE.md so each agent has a trigger.
|
|
66
|
+
|
|
8
67
|
## [0.5.3] — 2026-09-15
|
|
9
68
|
|
|
10
69
|
### Added
|
package/README.md
CHANGED
|
@@ -73,16 +73,25 @@ Every command below is `npx @sayansr26/agent-os <command>`. Install it once —
|
|
|
73
73
|
| `detect` | Show which tools this project is set up for |
|
|
74
74
|
| `audit` | Inspect the context layer and report findings |
|
|
75
75
|
| `memory` | Inspect and health-check every memory store |
|
|
76
|
+
| `settings` | Git write protection + task tools for Claude Code (`--scope project\|user\|both`, `--apply`) |
|
|
76
77
|
|
|
77
78
|
`--root <dir>` to target another directory, `--dry-run` to preview.
|
|
78
79
|
|
|
80
|
+
**`init` is safe to re-run.** It first reports the setup as `FRESH`,
|
|
81
|
+
`REPAIR` (with each missing item) or `HEALTHY`, fixes only what is missing, and
|
|
82
|
+
brings the Claude Code plugin to the latest version — installing it, enabling
|
|
83
|
+
it, or refreshing the marketplace and running `claude plugin update`.
|
|
84
|
+
`/agent-os:init` inside Claude Code reads the same state.
|
|
85
|
+
|
|
79
86
|
**`init` sets up Claude Code completely.** Beyond the rules it installs the
|
|
80
|
-
plugin — agents, skills, per-agent memory,
|
|
81
|
-
`claude plugin marketplace add` and `claude plugin install --yes`,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
plugin — agents, skills, per-agent memory, hooks — with
|
|
88
|
+
`claude plugin marketplace add` and `claude plugin install --yes`, and writes
|
|
89
|
+
the settings: git write-protection deny rules and the task tools in
|
|
90
|
+
`.claude/settings.json`, plus the task-tracking rule in `CLAUDE.md`. It then
|
|
91
|
+
asks once whether to apply the same to `~/.claude/` so every project on the
|
|
92
|
+
machine is protected (`--global` / `--no-global` answer in advance). Every
|
|
93
|
+
write merges into what is there; changed `~/.claude` files are backed up to
|
|
94
|
+
`*.agent-os.bak`. `--no-plugin` skips the plugin step. Every other detected tool gets its rules in its own
|
|
86
95
|
schema; the plugin layer is Claude Code only because no other tool has anywhere
|
|
87
96
|
to put it.
|
|
88
97
|
|
|
@@ -111,7 +120,7 @@ Being straight about this matters more than the feature list.
|
|
|
111
120
|
|
|
112
121
|
## The Claude Code plugin
|
|
113
122
|
|
|
114
|
-
The deeper context-engineering work — the architecture map, seven coordinated agents, per-agent memory, the
|
|
123
|
+
The deeper context-engineering work — the architecture map, seven coordinated agents, per-agent memory, and the hooks that make Claude use them — ships as a Claude Code plugin in this repo:
|
|
115
124
|
|
|
116
125
|
```
|
|
117
126
|
/plugin marketplace add sayansr26/agent-os
|
|
@@ -120,6 +129,17 @@ The deeper context-engineering work — the architecture map, seven coordinated
|
|
|
120
129
|
|
|
121
130
|
That's where the memory layer lives, because Claude Code is currently the only tool with somewhere to put it. The CLI is the cross-tool layer beneath it.
|
|
122
131
|
|
|
132
|
+
Two hooks make the agents part of every session rather than something you have to ask for:
|
|
133
|
+
|
|
134
|
+
- **SessionStart** (startup, resume, clear, compact) — prints where you left off, then which agent to use for what, which features the cartographer has mapped, and the plan-mode rule.
|
|
135
|
+
- **PreToolUse on Edit/Write** — blocks edits to files generated from `.agent-os/` (the next sync would revert them) and names the source to edit; the first edit to an unmapped feature adds a one-time note to ask the cartographer first.
|
|
136
|
+
|
|
137
|
+
Feature directories default to the first of `src/features`, `src/modules`, `app/features`, `features`, `modules`. Override in `.agent-os/config.json`:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{ "targets": ["claude-code"], "claude": { "features": ["src/features/*", "apps/*"], "cartographerReminder": true } }
|
|
141
|
+
```
|
|
142
|
+
|
|
123
143
|
---
|
|
124
144
|
|
|
125
145
|
## Design rules
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sayansr26/agent-os",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "One source of truth for AI coding agent config. Write your rules once; compile them to Claude Code, Cursor, Cline, Windsurf, Antigravity, Gemini CLI, OpenCode and Kilo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-os",
|
|
3
3
|
"description": "A context-engineered agent system for Claude Code. Ships a durable per-project memory layer, a cartographer that maps your codebase once and remembers it, and a coordinated agent set that reads your project's rules rather than hardcoding them.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sayan Choudhury",
|
|
7
7
|
"email": "sayan.choudhury.in@gmail.com"
|
|
@@ -15,6 +15,11 @@ not create a second file under a different name.
|
|
|
15
15
|
|
|
16
16
|
When you write:
|
|
17
17
|
|
|
18
|
+
- **Write only directly inside your memory directory** — flat, no
|
|
19
|
+
subfolders. It already is `<project>/.claude/agent-memory/<your name>/`, so a
|
|
20
|
+
path like `.claude/agent-memory/...` written relative to it nests a second
|
|
21
|
+
copy of the tree inside your memory. Use the absolute path your memory
|
|
22
|
+
directory was given, plus the file name.
|
|
18
23
|
- **One topic file per subject**, named kebab-case: `<subject-slug>.md`. Never
|
|
19
24
|
the `snake_case` variant, never a synonym for a file that already exists.
|
|
20
25
|
`defect-patterns.md` and `defect_patterns.md` are the same subject and must not
|
|
@@ -14,6 +14,11 @@ not create a second file under a different name.
|
|
|
14
14
|
|
|
15
15
|
When you write:
|
|
16
16
|
|
|
17
|
+
- **Write only directly inside your memory directory** — flat, no
|
|
18
|
+
subfolders. It already is `<project>/.claude/agent-memory/<your name>/`, so a
|
|
19
|
+
path like `.claude/agent-memory/...` written relative to it nests a second
|
|
20
|
+
copy of the tree inside your memory. Use the absolute path your memory
|
|
21
|
+
directory was given, plus the file name.
|
|
17
22
|
- **One topic file per subject**, named kebab-case: `<subject-slug>.md`. Never
|
|
18
23
|
the `snake_case` variant, never a synonym for a file that already exists.
|
|
19
24
|
`defect-patterns.md` and `defect_patterns.md` are the same subject and must not
|
|
@@ -86,5 +91,5 @@ in `CLAUDE.md` or a rule file, anything derivable by reading the code, and
|
|
|
86
91
|
anything about this specific task.
|
|
87
92
|
|
|
88
93
|
If you learned something that belongs in the project's own rules rather than your
|
|
89
|
-
private memory, say so in your reply and name the rule file —
|
|
90
|
-
|
|
94
|
+
private memory, say so in your reply and name the rule file — `.agent-os/rules/<name>.md` when the project has `.agent-os/` (the `.claude/rules/` copies are generated and the next sync reverts edits to them), otherwise `.claude/rules/<name>.md`. Do not write
|
|
95
|
+
rules yourself; the main conversation owns those.
|
|
@@ -14,6 +14,11 @@ not create a second file under a different name.
|
|
|
14
14
|
|
|
15
15
|
When you write:
|
|
16
16
|
|
|
17
|
+
- **Write only directly inside your memory directory** — flat, no
|
|
18
|
+
subfolders. It already is `<project>/.claude/agent-memory/<your name>/`, so a
|
|
19
|
+
path like `.claude/agent-memory/...` written relative to it nests a second
|
|
20
|
+
copy of the tree inside your memory. Use the absolute path your memory
|
|
21
|
+
directory was given, plus the file name.
|
|
17
22
|
- **One topic file per subject**, named kebab-case: `<subject-slug>.md`. Never
|
|
18
23
|
the `snake_case` variant, never a synonym for a file that already exists.
|
|
19
24
|
`defect-patterns.md` and `defect_patterns.md` are the same subject and must not
|
|
@@ -48,7 +53,7 @@ fresh one. Before writing anything, ask where it belongs, and put it only there:
|
|
|
48
53
|
| Kind of fact | Where |
|
|
49
54
|
|---|---|
|
|
50
55
|
| True in every session, every file | project `CLAUDE.md` |
|
|
51
|
-
| Durable but scoped to some files | `.
|
|
56
|
+
| Durable but scoped to some files | `.agent-os/rules/<topic>.md` with `paths:`, then `npx @sayansr26/agent-os sync` — or `.claude/rules/<topic>.md` in a project with no `.agent-os/`. Never edit a file carrying the agent-os generated banner. |
|
|
52
57
|
| Derivable by reading the code | **nowhere** — do not write it down |
|
|
53
58
|
| What changed, when, and why | the changelog / implementation log |
|
|
54
59
|
| A decision and its reasoning | an ADR, if the project keeps them |
|
|
@@ -15,6 +15,11 @@ not create a second file under a different name.
|
|
|
15
15
|
|
|
16
16
|
When you write:
|
|
17
17
|
|
|
18
|
+
- **Write only directly inside your memory directory** — flat, no
|
|
19
|
+
subfolders. It already is `<project>/.claude/agent-memory/<your name>/`, so a
|
|
20
|
+
path like `.claude/agent-memory/...` written relative to it nests a second
|
|
21
|
+
copy of the tree inside your memory. Use the absolute path your memory
|
|
22
|
+
directory was given, plus the file name.
|
|
18
23
|
- **One topic file per subject**, named kebab-case: `<subject-slug>.md`. Never
|
|
19
24
|
the `snake_case` variant, never a synonym for a file that already exists.
|
|
20
25
|
`defect-patterns.md` and `defect_patterns.md` are the same subject and must not
|
|
@@ -128,11 +133,23 @@ repo's life.
|
|
|
128
133
|
at startup, so a bloated index silently loses your oldest maps.
|
|
129
134
|
- If you discovered something durable that is true beyond this one feature — a
|
|
130
135
|
convention, an invariant, a registration site the docs get wrong — say so in
|
|
131
|
-
your reply and name the `.claude/rules/`
|
|
136
|
+
your reply and name the rule file it belongs in — `.agent-os/rules/<name>.md` when the project has `.agent-os/` (the `.claude/rules/` copies are generated and the next sync reverts edits to them), otherwise `.claude/rules/<name>.md`. Do not write
|
|
132
137
|
project rules yourself; the main conversation owns those.
|
|
133
138
|
- If a map you relied on turned out to be stale, correct the topic file and
|
|
134
139
|
update its `mapped:` date in the same turn.
|
|
135
140
|
|
|
141
|
+
## Plan mode and read-only calls
|
|
142
|
+
|
|
143
|
+
If the caller says it is in plan mode, or asks for a read-only answer, answer
|
|
144
|
+
the question exactly as usual — memory first, explore what is missing — but
|
|
145
|
+
**write nothing**. End the reply with one line: `Map not filed (read-only) —
|
|
146
|
+
call me again after plan mode to file it.` The caller does that as the first
|
|
147
|
+
step once plan mode ends; your second call re-uses what you just explored.
|
|
148
|
+
|
|
149
|
+
Plan mode's "use only Explore agents" phase is not a reason to skip you: an
|
|
150
|
+
Explore agent finds files, you return the map and remember it. If you are told
|
|
151
|
+
to skip the map because of plan mode, the caller has misread the workflow.
|
|
152
|
+
|
|
136
153
|
## What not to do
|
|
137
154
|
|
|
138
155
|
- Never Write or Edit anything outside your own memory directory.
|
|
@@ -14,6 +14,11 @@ not create a second file under a different name.
|
|
|
14
14
|
|
|
15
15
|
When you write:
|
|
16
16
|
|
|
17
|
+
- **Write only directly inside your memory directory** — flat, no
|
|
18
|
+
subfolders. It already is `<project>/.claude/agent-memory/<your name>/`, so a
|
|
19
|
+
path like `.claude/agent-memory/...` written relative to it nests a second
|
|
20
|
+
copy of the tree inside your memory. Use the absolute path your memory
|
|
21
|
+
directory was given, plus the file name.
|
|
17
22
|
- **One topic file per subject**, named kebab-case: `<subject-slug>.md`. Never
|
|
18
23
|
the `snake_case` variant, never a synonym for a file that already exists.
|
|
19
24
|
`defect-patterns.md` and `defect_patterns.md` are the same subject and must not
|
|
@@ -15,6 +15,11 @@ not create a second file under a different name.
|
|
|
15
15
|
|
|
16
16
|
When you write:
|
|
17
17
|
|
|
18
|
+
- **Write only directly inside your memory directory** — flat, no
|
|
19
|
+
subfolders. It already is `<project>/.claude/agent-memory/<your name>/`, so a
|
|
20
|
+
path like `.claude/agent-memory/...` written relative to it nests a second
|
|
21
|
+
copy of the tree inside your memory. Use the absolute path your memory
|
|
22
|
+
directory was given, plus the file name.
|
|
18
23
|
- **One topic file per subject**, named kebab-case: `<subject-slug>.md`. Never
|
|
19
24
|
the `snake_case` variant, never a synonym for a file that already exists.
|
|
20
25
|
`defect-patterns.md` and `defect_patterns.md` are the same subject and must not
|
|
@@ -14,6 +14,11 @@ not create a second file under a different name.
|
|
|
14
14
|
|
|
15
15
|
When you write:
|
|
16
16
|
|
|
17
|
+
- **Write only directly inside your memory directory** — flat, no
|
|
18
|
+
subfolders. It already is `<project>/.claude/agent-memory/<your name>/`, so a
|
|
19
|
+
path like `.claude/agent-memory/...` written relative to it nests a second
|
|
20
|
+
copy of the tree inside your memory. Use the absolute path your memory
|
|
21
|
+
directory was given, plus the file name.
|
|
17
22
|
- **One topic file per subject**, named kebab-case: `<subject-slug>.md`. Never
|
|
18
23
|
the `snake_case` variant, never a synonym for a file that already exists.
|
|
19
24
|
`defect-patterns.md` and `defect_patterns.md` are the same subject and must not
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"hooks": {
|
|
3
3
|
"SessionStart": [
|
|
4
4
|
{
|
|
5
|
-
"matcher": "startup|resume|clear",
|
|
5
|
+
"matcher": "startup|resume|clear|compact",
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
@@ -12,6 +12,19 @@
|
|
|
12
12
|
}
|
|
13
13
|
]
|
|
14
14
|
}
|
|
15
|
+
],
|
|
16
|
+
"PreToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "Edit|MultiEdit|Write",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-edit.mjs\"",
|
|
23
|
+
"timeout": 10,
|
|
24
|
+
"statusMessage": "Checking agent-os maps and rules"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
15
28
|
]
|
|
16
29
|
}
|
|
17
30
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PreToolUse hook on Edit|MultiEdit|Write. Two jobs, both at the moment the
|
|
4
|
+
* edit happens, because a CLAUDE.md sentence read an hour ago loses to
|
|
5
|
+
* whatever else is in context (plan mode's "Explore only" phase did exactly
|
|
6
|
+
* that):
|
|
7
|
+
*
|
|
8
|
+
* 1. Generated-file guard (BLOCKS). A file carrying the agent-os banner was
|
|
9
|
+
* compiled from `.agent-os/`; the next `sync` reverts any edit to it.
|
|
10
|
+
* Deny, and name the source to edit instead.
|
|
11
|
+
* 2. Cartographer reminder (advisory, never blocks). The first edit to an
|
|
12
|
+
* existing feature with no cartographer map adds a note telling Claude to
|
|
13
|
+
* ask agent-os:feature-cartographer first. Once per feature per session.
|
|
14
|
+
*
|
|
15
|
+
* Stays silent for new (untracked) features, paths outside a feature
|
|
16
|
+
* directory, and anything it cannot work out. Turn the reminder off with
|
|
17
|
+
* `{ "claude": { "cartographerReminder": false } }` in `.agent-os/config.json`.
|
|
18
|
+
*/
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { basename, isAbsolute, join, relative } from "node:path";
|
|
22
|
+
import { claudeConfig, featureOf, findMap, mapFiles, tracked } from "../lib/features.mjs";
|
|
23
|
+
|
|
24
|
+
// Must stay a substring of BANNER in src/source.mjs.
|
|
25
|
+
const BANNER = "agent-os: generated from .agent-os/";
|
|
26
|
+
|
|
27
|
+
const quiet = () => process.exit(0);
|
|
28
|
+
const emit = (hookSpecificOutput) => {
|
|
29
|
+
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", ...hookSpecificOutput } }));
|
|
30
|
+
process.exit(0);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
let payload;
|
|
34
|
+
try { payload = JSON.parse(readFileSync(0, "utf8") || "{}"); } catch { quiet(); }
|
|
35
|
+
|
|
36
|
+
const root = process.env.CLAUDE_PROJECT_DIR || payload?.cwd || process.cwd();
|
|
37
|
+
const raw = String(payload?.tool_input?.file_path ?? "");
|
|
38
|
+
if (!raw) quiet();
|
|
39
|
+
const abs = isAbsolute(raw) ? raw : join(root, raw);
|
|
40
|
+
const rel = relative(root, abs).replace(/\\/g, "/");
|
|
41
|
+
if (rel.startsWith("..")) quiet();
|
|
42
|
+
|
|
43
|
+
// ---- 1. generated-file guard
|
|
44
|
+
try {
|
|
45
|
+
if (existsSync(join(root, ".agent-os")) && existsSync(abs) && readFileSync(abs, "utf8").includes(BANNER)) {
|
|
46
|
+
let source = ".agent-os/";
|
|
47
|
+
const rule = rel.match(/^\.(?:claude|clinerules|cursor|windsurf|kilocode|gemini|agents?)\/(?:rules\/)?(.+?)\.(?:md|mdc)$/);
|
|
48
|
+
if (rel === "AGENTS.md") source = ".agent-os/AGENTS.md";
|
|
49
|
+
else if (rule && existsSync(join(root, ".agent-os/rules", `${basename(rule[1])}.md`)))
|
|
50
|
+
source = `.agent-os/rules/${basename(rule[1])}.md`;
|
|
51
|
+
emit({
|
|
52
|
+
permissionDecision: "deny",
|
|
53
|
+
permissionDecisionReason:
|
|
54
|
+
`${rel} is generated by agent-os and the next sync would revert this edit. ` +
|
|
55
|
+
`Make the change in ${source} instead, then run \`npx @sayansr26/agent-os sync\`.`,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} catch { /* fall through to the reminder */ }
|
|
59
|
+
|
|
60
|
+
// ---- 2. cartographer reminder
|
|
61
|
+
try {
|
|
62
|
+
if (claudeConfig(root).cartographerReminder === false) quiet();
|
|
63
|
+
const feature = featureOf(root, rel);
|
|
64
|
+
if (!feature) quiet();
|
|
65
|
+
if (findMap(feature, mapFiles(root))) quiet();
|
|
66
|
+
if (!tracked(root, feature.dir)) quiet();
|
|
67
|
+
|
|
68
|
+
const dir = join(tmpdir(), "agent-os-reminders");
|
|
69
|
+
mkdirSync(dir, { recursive: true });
|
|
70
|
+
const session = String(payload?.session_id ?? "nosession").replace(/[^\w-]/g, "");
|
|
71
|
+
const marker = join(dir, `${session}--${feature.dir.replace(/[^\w-]/g, "_")}`);
|
|
72
|
+
if (existsSync(marker)) quiet();
|
|
73
|
+
writeFileSync(marker, "");
|
|
74
|
+
|
|
75
|
+
emit({
|
|
76
|
+
additionalContext:
|
|
77
|
+
`agent-os (advisory): ${feature.dir}/ is an existing feature with no cartographer map. ` +
|
|
78
|
+
`Before changing it further, ask agent-os:feature-cartographer how "${feature.name}" is built — ` +
|
|
79
|
+
`it answers and files the map for every later session. This applies in plan mode too ` +
|
|
80
|
+
`(ask it read-only; it writes the map after plan mode ends). Shown once per feature per session.`,
|
|
81
|
+
});
|
|
82
|
+
} catch { quiet(); }
|
|
83
|
+
quiet();
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* SessionStart hook — "where you left off".
|
|
3
|
+
* SessionStart hook — "where you left off", then "how this project works".
|
|
4
4
|
*
|
|
5
|
-
* Prints
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Prints two blocks to stdout, which Claude Code injects as context before the
|
|
6
|
+
* first turn (and again after /clear and after a compaction):
|
|
7
|
+
*
|
|
8
|
+
* 1. A read-only snapshot: branch, recent commits, uncommitted files, the
|
|
9
|
+
* handoff note and the active task. Context, not a request.
|
|
10
|
+
* 2. The agent-os contract: which agent to use for what, which features the
|
|
11
|
+
* cartographer has mapped, and where rules are edited. This one IS an
|
|
12
|
+
* instruction — without it Claude only uses agent-os when told to, because
|
|
13
|
+
* a CLAUDE.md sentence loses to whatever else is in context.
|
|
8
14
|
*
|
|
9
15
|
* Design constraints:
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
* - Fails silent: any error exits 0 with no output, so a broken hook can
|
|
13
|
-
* never block a session.
|
|
16
|
+
* - The snapshot is hard-capped (MAX_LINES); the contract is fixed-size.
|
|
17
|
+
* - Fails silent: any error exits 0, so a broken hook never blocks a session.
|
|
14
18
|
* - Read-only. Runs git for reading only; never writes, never mutates.
|
|
15
|
-
*
|
|
16
|
-
* Install: ~/.claude/hooks/session-resume.mjs, registered in
|
|
17
|
-
* ~/.claude/settings.json under SessionStart with matcher "startup|resume|clear".
|
|
18
19
|
*/
|
|
19
20
|
|
|
20
21
|
import { execSync } from "node:child_process";
|
|
21
22
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
22
23
|
import { join } from "node:path";
|
|
24
|
+
import { coverage } from "../lib/features.mjs";
|
|
23
25
|
|
|
24
26
|
const MAX_LINES = 40;
|
|
25
|
-
const cwd = process.cwd();
|
|
27
|
+
const cwd = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
26
28
|
const out = [];
|
|
27
29
|
|
|
28
30
|
const sh = (cmd) => {
|
|
@@ -50,9 +52,9 @@ const head = (file, n) => {
|
|
|
50
52
|
}
|
|
51
53
|
};
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
// Not a git repo -> nothing
|
|
55
|
-
if (sh("git rev-parse --is-inside-work-tree") !== "true")
|
|
55
|
+
function snapshot() {
|
|
56
|
+
// Not a git repo -> nothing to say here; the contract below still prints.
|
|
57
|
+
if (sh("git rev-parse --is-inside-work-tree") !== "true") return [];
|
|
56
58
|
|
|
57
59
|
const branch = sh("git rev-parse --abbrev-ref HEAD");
|
|
58
60
|
const lastCommits = sh('git log -3 --pretty=format:"%h %s (%cr)"')
|
|
@@ -120,16 +122,71 @@ try {
|
|
|
120
122
|
break;
|
|
121
123
|
}
|
|
122
124
|
|
|
123
|
-
if (out.length <= 2)
|
|
125
|
+
if (out.length <= 2) return [];
|
|
124
126
|
|
|
125
127
|
const capped = out.slice(0, MAX_LINES);
|
|
126
128
|
if (out.length > MAX_LINES) capped.push(" … (truncated)");
|
|
127
129
|
capped.push("");
|
|
128
130
|
capped.push(
|
|
129
|
-
"
|
|
131
|
+
"_The snapshot above is context, not a request. Do not act on it until the user says what they want._"
|
|
130
132
|
);
|
|
133
|
+
return capped;
|
|
134
|
+
}
|
|
131
135
|
|
|
132
|
-
|
|
136
|
+
// The agents and what triggers each. `subagent_type` is the namespaced name.
|
|
137
|
+
const AGENTS = [
|
|
138
|
+
["agent-os:feature-cartographer", "BEFORE changing any existing feature — ask how it is built (files, state, API, blast radius). It answers from its map, or maps the feature and files the map."],
|
|
139
|
+
["agent-os:architect", "before a new subsystem, a cross-module change, or a data model others will depend on."],
|
|
140
|
+
["agent-os:builder", "to write the code once the shape is settled; it enforces the project's rules while writing."],
|
|
141
|
+
["agent-os:tester", "after building, to verify the change actually works."],
|
|
142
|
+
["agent-os:reviewer", "before calling any change done or opening a PR."],
|
|
143
|
+
["agent-os:documenter", "once work is verified, to update the changelog, task state and docs the change invalidated."],
|
|
144
|
+
["agent-os:orchestrator", "for work spanning several of the above, or that cannot be stated in one sentence."],
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
function contract() {
|
|
148
|
+
const setUp =
|
|
149
|
+
existsSync(join(cwd, ".agent-os")) ||
|
|
150
|
+
existsSync(join(cwd, ".claude/agent-memory")) ||
|
|
151
|
+
/agent-os/.test(head("CLAUDE.md", 400));
|
|
152
|
+
if (!setUp)
|
|
153
|
+
return [
|
|
154
|
+
"## agent-os",
|
|
155
|
+
"",
|
|
156
|
+
"The agent-os plugin is installed but this project is not set up. If the user starts feature work, suggest `/agent-os:init` once.",
|
|
157
|
+
];
|
|
158
|
+
|
|
159
|
+
const lines = [
|
|
160
|
+
"## agent-os is active in this project",
|
|
161
|
+
"",
|
|
162
|
+
"These agents are how work is done here — use them without being asked (Agent tool, `subagent_type` as shown):",
|
|
163
|
+
];
|
|
164
|
+
for (const [name, when] of AGENTS) lines.push(`- \`${name}\` — ${when}`);
|
|
165
|
+
|
|
166
|
+
const cov = coverage(cwd);
|
|
167
|
+
lines.push("");
|
|
168
|
+
if (cov.features.length) {
|
|
169
|
+
const names = cov.mapped.map((f) => f.name);
|
|
170
|
+
const shown = names.slice(0, 12).join(", ") + (names.length > 12 ? `, … ${names.length - 12} more` : "");
|
|
171
|
+
lines.push(`Mapped: ${cov.mapped.length} of ${cov.features.length} features under \`${cov.parents.join("`, `")}\`${names.length ? ` — ${shown}` : ""}.`);
|
|
172
|
+
lines.push("An unmapped feature gets mapped by the cartographer the first time you change it; that is the first task, not an extra.");
|
|
173
|
+
} else if (cov.dir) {
|
|
174
|
+
lines.push(`Cartographer maps are indexed in \`${cov.dir.slice(cwd.length + 1)}/MEMORY.md\`.`);
|
|
175
|
+
}
|
|
176
|
+
if (!cov.architecture) lines.push("No architecture map yet — `/agent-os:map` builds it.");
|
|
177
|
+
|
|
178
|
+
lines.push("");
|
|
179
|
+
lines.push("Plan mode: its \"Explore agents only\" phase does not replace the cartographer. Ask the cartographer read-only during planning (it will not write), and let it file its map once plan mode ends.");
|
|
180
|
+
if (existsSync(join(cwd, ".agent-os/rules")))
|
|
181
|
+
lines.push("Rules: `.claude/rules/*` and other tool copies are generated. Edit `.agent-os/rules/`, then run `npx @sayansr26/agent-os sync`.");
|
|
182
|
+
return lines;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const blocks = [];
|
|
187
|
+
try { const s = snapshot(); if (s.length) blocks.push(s.join("\n")); } catch {}
|
|
188
|
+
try { blocks.push(contract().join("\n")); } catch {}
|
|
189
|
+
if (blocks.length) process.stdout.write(blocks.join("\n\n") + "\n");
|
|
133
190
|
} catch {
|
|
134
191
|
// Never let a resume hook break a session.
|
|
135
192
|
}
|