@sayansr26/agent-os 0.5.2 → 0.5.3

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.0"
9
+ "version": "0.4.1"
10
10
  },
11
11
  "plugins": [
12
12
  {
package/CHANGELOG.md CHANGED
@@ -5,11 +5,29 @@ 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.5.1] — 2026-09-15
8
+ ## [0.5.3] — 2026-09-15
9
9
 
10
- Found by running 0.5.0 against a real repository that already had six
11
- path-scoped rules and a hand-written `AGENTS.md`. It overwrote the
12
- `AGENTS.md`. 0.5.0 was never published.
10
+ ### Added
11
+ - **`init` finishes the Claude Code project setup.** Two things that are
12
+ neither rules nor plugin, both written at project scope only:
13
+ `.claude/settings.json` gets `env.CLAUDE_CODE_ENABLE_TODO_TOOLS = "1"`,
14
+ merged into the existing keys rather than replacing the file; and `CLAUDE.md`
15
+ gets the task-tracking rule, inserted under `## Operator preferences` when
16
+ the project keeps that heading. Both are idempotent, an existing truthy value
17
+ for the flag is left as the user set it, and `CLAUDE.md` is never created — a
18
+ project's always-loaded context should not be invented by a CLI. Nothing
19
+ writes to `~/.claude/`; a project setup tool has no business editing the
20
+ machine's global config.
21
+ - **The validator checks release hygiene.** `package.json`'s version must have
22
+ a matching `## [x.y.z]` changelog section, and it must be the newest one.
23
+ 0.5.2 shipped with no entry of its own; a deterministic check belongs in a
24
+ script rather than in a habit.
25
+
26
+ ### Changed
27
+ - Plugin 0.4.1: the audit no longer misreports a generated rules directory as a
28
+ legacy store to fold into `CLAUDE.md` and delete.
29
+
30
+ ## [0.5.2] — 2026-09-15
13
31
 
14
32
  ### Added
15
33
  - **`init` installs the Claude Code plugin.** When Claude Code is detected it
@@ -19,6 +37,11 @@ path-scoped rules and a hand-written `AGENTS.md`. It overwrote the
19
37
  commands instead. `--no-plugin` skips it, and the rules are written either
20
38
  way — the compiler never depends on the plugin step succeeding.
21
39
 
40
+ ## [0.5.1] — 2026-09-15
41
+
42
+ Found by running 0.5.0 against a real repository that already had six
43
+ path-scoped rules and a hand-written `AGENTS.md`. It overwrote the `AGENTS.md`.
44
+
22
45
  ### Fixed
23
46
  - **`init` scaffolded over projects that already had rules.** It wrote a
24
47
  placeholder `AGENTS.md` and an `example.md` rule regardless of what was
package/README.md CHANGED
@@ -76,12 +76,15 @@ Every command below is `npx @sayansr26/agent-os <command>`. Install it once —
76
76
 
77
77
  `--root <dir>` to target another directory, `--dry-run` to preview.
78
78
 
79
- **`init` sets up Claude Code completely.** When it detects Claude Code it also
80
- installs the plugin — the agents, skills, per-agent memory and session hook —
81
- with `claude plugin marketplace add` and `claude plugin install --yes` at
82
- project scope, so it travels with the repo. `--no-plugin` skips it. Every other
83
- detected tool gets its rules in its own schema; the plugin layer is Claude Code
84
- only because no other tool has anywhere to put it.
79
+ **`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
86
+ schema; the plugin layer is Claude Code only because no other tool has anywhere
87
+ to put it.
85
88
 
86
89
  **On an existing project, `init` adopts rather than scaffolds.** It takes your
87
90
  current `AGENTS.md` and the first rules directory it recognises as the source,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sayansr26/agent-os",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
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.0",
4
+ "version": "0.4.1",
5
5
  "author": {
6
6
  "name": "Sayan Choudhury",
7
7
  "email": "sayan.choudhury.in@gmail.com"
@@ -0,0 +1,81 @@
1
+ /**
2
+ * The two Claude Code settings that are not rules and not a plugin.
3
+ *
4
+ * Both are written at PROJECT scope only — `.claude/settings.json` and the
5
+ * project's `CLAUDE.md`. Nothing here touches `~/.claude/`: a project setup
6
+ * tool that edits the machine's global config is overreach, and the point is
7
+ * that the setup travels with the repository.
8
+ */
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { write } from "./source.mjs";
12
+
13
+ export const TODO_ENV = "CLAUDE_CODE_ENABLE_TODO_TOOLS";
14
+
15
+ export const TASK_RULE = `- **Always track work with the task tool (TaskCreate / TaskUpdate).** Any request
16
+ with more than one step gets a task list before work starts: one task per
17
+ deliverable, marked \`in_progress\` when started and \`completed\` only when
18
+ verified. Keep it current as scope changes, including work delegated to
19
+ subagents, so I can see what is done, running and left at any moment.`;
20
+
21
+ /**
22
+ * Turn on the todo tools in project settings, preserving every other key.
23
+ * Returns "added" | "present" | "conflict" | "invalid".
24
+ */
25
+ export function ensureTodoEnv(root, { dry = false } = {}) {
26
+ const rel = ".claude/settings.json";
27
+ const p = join(root, rel);
28
+ let settings = {};
29
+ if (existsSync(p)) {
30
+ try { settings = JSON.parse(readFileSync(p, "utf8")); }
31
+ catch { return { status: "invalid", rel }; }
32
+ }
33
+ const env = settings.env || {};
34
+ const current = env[TODO_ENV];
35
+
36
+ // "true" and "1" are both truthy to Claude Code; an existing truthy value is
37
+ // the user's choice and is left alone rather than normalised.
38
+ if (current !== undefined) {
39
+ const on = current === "1" || current === "true" || current === true;
40
+ return { status: on ? "present" : "conflict", rel, current };
41
+ }
42
+
43
+ settings.env = { ...env, [TODO_ENV]: "1" };
44
+ if (!dry) write(root, rel, JSON.stringify(settings, null, 2) + "\n");
45
+ return { status: "added", rel };
46
+ }
47
+
48
+ /**
49
+ * Put the task-tracking rule in CLAUDE.md if it is not already there.
50
+ * Never creates CLAUDE.md — that file is the project's own always-loaded
51
+ * context, and inventing one from a CLI is how a project ends up with a stub
52
+ * nobody owns. Returns "added" | "present" | "no-file".
53
+ */
54
+ export function ensureTaskRule(root, { dry = false } = {}) {
55
+ const rel = "CLAUDE.md";
56
+ const p = join(root, rel);
57
+ if (!existsSync(p)) return { status: "no-file", rel };
58
+
59
+ const text = readFileSync(p, "utf8");
60
+ if (/TaskCreate/.test(text)) return { status: "present", rel };
61
+
62
+ // Prefer the operator-preferences section if the project keeps one, so the
63
+ // rule lands with the other instructions about how to work rather than
64
+ // orphaned at the bottom of the file.
65
+ const heading = text.match(/^##\s+Operator preferences\s*$/m);
66
+ let next;
67
+ if (heading) {
68
+ const start = heading.index + heading[0].length;
69
+ const rest = text.slice(start);
70
+ const nextHeading = rest.search(/^##\s+/m);
71
+ const end = nextHeading === -1 ? text.length : start + nextHeading;
72
+ const before = text.slice(0, end).replace(/\s*$/, "");
73
+ next = `${before}\n\n${TASK_RULE}\n\n${text.slice(end)}`;
74
+ } else {
75
+ next = `${text.replace(/\s*$/, "")}\n\n## Working agreement\n\n${TASK_RULE}\n`;
76
+ }
77
+ next = next.replace(/\n{4,}/g, "\n\n\n");
78
+
79
+ if (!dry) write(root, rel, next);
80
+ return { status: "added", rel, section: heading ? "Operator preferences" : "Working agreement" };
81
+ }
package/src/cli.mjs CHANGED
@@ -6,6 +6,7 @@ import { detect, summarise, TOOLS } from "./detect.mjs";
6
6
  import { load, write, readIfExists, matches, BANNER, DIR } from "./source.mjs";
7
7
  import { adopt } from "./adopt.mjs";
8
8
  import { installPlugin, MARKETPLACE, MARKETPLACE_NAME, PLUGIN } from "./plugin.mjs";
9
+ import { ensureTodoEnv, ensureTaskRule, TODO_ENV } from "./claude-setup.mjs";
9
10
  import { compile, TARGETS } from "./targets.mjs";
10
11
 
11
12
  const bold = (s) => `\x1b[1m${s}\x1b[0m`;
@@ -210,6 +211,27 @@ Pick one:
210
211
  // The rules are done and safe at this point. The Claude Code plugin is the
211
212
  // other half — agents, skills, per-agent memory, the session hook — and it
212
213
  // installs from the same repo. Only on `init`, never on `sync`.
214
+ if (cmd === "init" && targets.includes("claude-code")) {
215
+ console.log(`\n${bold("Claude Code setup")}\n`);
216
+
217
+ const e = ensureTodoEnv(root, { dry });
218
+ const eMsg = {
219
+ added: ` ${e.rel} ${dim(`env.${TODO_ENV} = "1"`)}`,
220
+ present: ` ${e.rel} ${dim(`env.${TODO_ENV} already set`)}`,
221
+ conflict: ` ${e.rel} ${dim(`env.${TODO_ENV} is "${e.current}" — left as you set it`)}`,
222
+ invalid: ` ${e.rel} ${dim("is not valid JSON — left alone, fix it and re-run")}`,
223
+ }[e.status];
224
+ console.log(eMsg);
225
+
226
+ const t = ensureTaskRule(root, { dry });
227
+ const tMsg = {
228
+ added: ` ${t.rel} ${dim(`task-tracking rule added under ${t.section}`)}`,
229
+ present: ` ${t.rel} ${dim("task-tracking rule already there")}`,
230
+ "no-file": ` ${t.rel} ${dim("absent — run /agent-os:init inside Claude Code to build it")}`,
231
+ }[t.status];
232
+ console.log(tMsg);
233
+ }
234
+
213
235
  if (cmd === "init" && targets.includes("claude-code") && !argv.includes("--no-plugin")) {
214
236
  console.log(`\n${bold("Claude Code plugin")} ${dim(`${PLUGIN}@${MARKETPLACE_NAME}`)}\n`);
215
237
  const r = installPlugin({ dry });
package/src/selftest.mjs CHANGED
@@ -114,6 +114,47 @@ try {
114
114
  rmSync(r3, { recursive: true, force: true });
115
115
  }
116
116
 
117
+ console.log("\n Claude Code project setup");
118
+ {
119
+ const r5 = mkdtempSync(join(tmpdir(), "agent-os-cc-"));
120
+ mkdirSync(join(r5, ".claude"), { recursive: true });
121
+ // An existing settings.json with unrelated keys must survive untouched.
122
+ writeFileSync(join(r5, ".claude/settings.json"),
123
+ JSON.stringify({ permissions: { deny: ["Bash(git push *)"] }, env: { FOO: "bar" } }, null, 2));
124
+ writeFileSync(join(r5, "CLAUDE.md"),
125
+ "# CLAUDE.md\n\nStack notes.\n\n## Operator preferences\n\n- Never run git unasked.\n\n## Verification\n\nRun make test.\n");
126
+
127
+ cli(["init", "--no-plugin"], r5);
128
+ const st = JSON.parse(readFileSync(join(r5, ".claude/settings.json"), "utf8"));
129
+ ok(st.env.CLAUDE_CODE_ENABLE_TODO_TOOLS === "1", "todo tools enabled in project settings");
130
+ ok(st.env.FOO === "bar", "existing env keys preserved");
131
+ ok(st.permissions?.deny?.[0] === "Bash(git push *)", "existing permissions preserved");
132
+
133
+ const cm = readFileSync(join(r5, "CLAUDE.md"), "utf8");
134
+ ok(cm.includes("TaskCreate / TaskUpdate"), "task-tracking rule added to CLAUDE.md");
135
+ ok(cm.indexOf("TaskCreate") > cm.indexOf("## Operator preferences") &&
136
+ cm.indexOf("TaskCreate") < cm.indexOf("## Verification"),
137
+ "rule lands inside Operator preferences, not at the end");
138
+ ok(cm.includes("Never run git unasked."), "existing CLAUDE.md content preserved");
139
+
140
+ // Idempotent: a second init must not duplicate either.
141
+ cli(["init", "--no-plugin"], r5);
142
+ const cm2 = readFileSync(join(r5, "CLAUDE.md"), "utf8");
143
+ ok(cm2.split("TaskCreate").length - 1 === 1, "second init does not duplicate the rule");
144
+
145
+ // A user who set the flag their own way keeps their value.
146
+ const r6 = mkdtempSync(join(tmpdir(), "agent-os-cc2-"));
147
+ mkdirSync(join(r6, ".claude"), { recursive: true });
148
+ writeFileSync(join(r6, ".claude/settings.json"),
149
+ JSON.stringify({ env: { CLAUDE_CODE_ENABLE_TODO_TOOLS: "true" } }, null, 2));
150
+ cli(["init", "--no-plugin"], r6);
151
+ ok(JSON.parse(readFileSync(join(r6, ".claude/settings.json"), "utf8"))
152
+ .env.CLAUDE_CODE_ENABLE_TODO_TOOLS === "true", "an existing truthy value is left as the user set it");
153
+
154
+ rmSync(r5, { recursive: true, force: true });
155
+ rmSync(r6, { recursive: true, force: true });
156
+ }
157
+
117
158
  console.log("\n merge, not overwrite");
118
159
  writeFileSync(join(root, "opencode.json"), JSON.stringify({ model: "anthropic/x", instructions: ["KEEP.md"] }, null, 2));
119
160
  cli(["sync"], root);