@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +27 -4
- package/README.md +9 -6
- package/package.json +1 -1
- package/plugins/agent-os/.claude-plugin/plugin.json +1 -1
- package/src/claude-setup.mjs +81 -0
- package/src/cli.mjs +22 -0
- package/src/selftest.mjs +41 -0
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.
|
|
8
|
+
## [0.5.3] — 2026-09-15
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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.**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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.
|
|
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.
|
|
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);
|