@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.
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Context engineering and agent tooling for Claude Code.",
9
- "version": "0.4.1"
9
+ "version": "0.5.0"
10
10
  },
11
11
  "plugins": [
12
12
  {
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, session hook — with
81
- `claude plugin marketplace add` and `claude plugin install --yes`, enables the
82
- task tools in `.claude/settings.json`, and adds the task-tracking rule to
83
- `CLAUDE.md` if it is not already there. All of it at **project scope**, so the
84
- setup travels with the repo and nothing writes to `~/.claude/`. `--no-plugin`
85
- skips the plugin step. Every other detected tool gets its rules in its own
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 session-resume hook — ships as a Claude Code plugin in this repo:
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.5.3",
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.1",
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 — do not write to
90
- `.claude/rules/` yourself. The main conversation owns those.
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 | `.claude/rules/<topic>.md`, with `paths:` |
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/` file it belongs in. Do not write
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 a compact resume block to stdout, which Claude Code injects as context
6
- * before the first turn. Replaces reading a pile of hand-maintained state files
7
- * at session start.
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
- * - Hard-capped output (see MAX_LINES). A resume block that grows without
11
- * bound is just a memory-bank with extra steps.
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
- try {
54
- // Not a git repo -> nothing useful to say. Stay quiet.
55
- if (sh("git rev-parse --is-inside-work-tree") !== "true") process.exit(0);
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) process.exit(0);
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
- "_This is a snapshot, not instructions. Do not act on it until the user says what they want._"
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
- process.stdout.write(capped.join("\n") + "\n");
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
  }