@sayansr26/agent-os 0.5.1 → 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 +51 -12
- package/README.md +10 -0
- 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 +43 -1
- package/src/plugin.mjs +74 -0
- package/src/selftest.mjs +52 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,57 @@ 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.3] — 2026-09-15
|
|
9
|
+
|
|
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
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- **`init` installs the Claude Code plugin.** When Claude Code is detected it
|
|
34
|
+
runs `claude plugin marketplace add` and `claude plugin install --yes` at
|
|
35
|
+
project scope, so the plugin travels with the repository rather than living
|
|
36
|
+
on one machine. If the `claude` CLI is not on PATH it prints the two slash
|
|
37
|
+
commands instead. `--no-plugin` skips it, and the rules are written either
|
|
38
|
+
way — the compiler never depends on the plugin step succeeding.
|
|
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
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- **`init` scaffolded over projects that already had rules.** It wrote a
|
|
47
|
+
placeholder `AGENTS.md` and an `example.md` rule regardless of what was
|
|
48
|
+
there, and the next `sync` compiled the placeholder on top of the project's
|
|
49
|
+
real `AGENTS.md`. `init` now adopts what it finds — the existing `AGENTS.md`
|
|
50
|
+
and the first rules directory it recognises, `.cursor/rules/*.mdc` converted
|
|
51
|
+
back to `paths:` — and seeds `example.md` only when there was nothing to
|
|
52
|
+
adopt. A tool for stopping rule drift must not cause it.
|
|
53
|
+
- **`sync` now refuses to overwrite a file it did not generate.** Generated
|
|
54
|
+
files carry a banner; anything else at a generated path is the user's own
|
|
55
|
+
work. `sync` names those files, leaves them alone and exits non-zero, with
|
|
56
|
+
`--force` as the explicit opt-out. `AGENTS.md` carries the banner too — it
|
|
57
|
+
previously did not, which is why nothing could tell it apart.
|
|
58
|
+
|
|
8
59
|
## [0.5.0] — 2026-09-15
|
|
9
60
|
|
|
10
61
|
`agent-os` becomes a cross-tool CLI. The Claude Code plugin is now one target
|
|
@@ -40,18 +91,6 @@ among several rather than the whole product.
|
|
|
40
91
|
- Documentation is generic throughout; examples are invented.
|
|
41
92
|
|
|
42
93
|
### Fixed
|
|
43
|
-
- **`init` scaffolded over projects that already had rules.** It wrote a
|
|
44
|
-
placeholder `AGENTS.md` and an `example.md` rule regardless of what was
|
|
45
|
-
there, and the next `sync` compiled the placeholder on top of the project's
|
|
46
|
-
real `AGENTS.md`. `init` now adopts what it finds — the existing `AGENTS.md`
|
|
47
|
-
and the first rules directory it recognises, `.cursor/rules/*.mdc` converted
|
|
48
|
-
back to `paths:` — and seeds `example.md` only when there was nothing to
|
|
49
|
-
adopt. A tool for stopping rule drift must not cause it.
|
|
50
|
-
- **`sync` now refuses to overwrite a file it did not generate.** Generated
|
|
51
|
-
files carry a banner; anything else at a generated path is the user's own
|
|
52
|
-
work. `sync` names those files, leaves them alone and exits non-zero, with
|
|
53
|
-
`--force` as the explicit opt-out. `AGENTS.md` carries the banner too — it
|
|
54
|
-
previously did not, which is why nothing could tell it apart.
|
|
55
94
|
- `check` reported every skill file as drifted immediately after a `sync`. Skill
|
|
56
95
|
files are read as buffers so a skill can ship a binary asset, rule files are
|
|
57
96
|
generated as strings, and the two were compared with `!==`. Comparison is now
|
package/README.md
CHANGED
|
@@ -76,6 +76,16 @@ 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.** 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.
|
|
88
|
+
|
|
79
89
|
**On an existing project, `init` adopts rather than scaffolds.** It takes your
|
|
80
90
|
current `AGENTS.md` and the first rules directory it recognises as the source,
|
|
81
91
|
so the first `sync` regenerates what you already had. **`sync` never overwrites
|
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
|
@@ -5,6 +5,8 @@ import { join } from "node:path";
|
|
|
5
5
|
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
|
+
import { installPlugin, MARKETPLACE, MARKETPLACE_NAME, PLUGIN } from "./plugin.mjs";
|
|
9
|
+
import { ensureTodoEnv, ensureTaskRule, TODO_ENV } from "./claude-setup.mjs";
|
|
8
10
|
import { compile, TARGETS } from "./targets.mjs";
|
|
9
11
|
|
|
10
12
|
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
@@ -27,6 +29,7 @@ Options
|
|
|
27
29
|
--root <dir> project directory (default: cwd)
|
|
28
30
|
--dry-run print what would change, write nothing
|
|
29
31
|
--force overwrite files agent-os did not generate (it refuses by default)
|
|
32
|
+
--no-plugin skip installing the Claude Code plugin during init
|
|
30
33
|
`;
|
|
31
34
|
|
|
32
35
|
function scaffold(root, found) {
|
|
@@ -203,7 +206,46 @@ Pick one:
|
|
|
203
206
|
return;
|
|
204
207
|
}
|
|
205
208
|
|
|
206
|
-
console.log(`\n${dim("Generated files carry a banner. Edit .agent-os/ and re-run sync; never edit them directly.")}
|
|
209
|
+
console.log(`\n${dim("Generated files carry a banner. Edit .agent-os/ and re-run sync; never edit them directly.")}`);
|
|
210
|
+
|
|
211
|
+
// The rules are done and safe at this point. The Claude Code plugin is the
|
|
212
|
+
// other half — agents, skills, per-agent memory, the session hook — and it
|
|
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
|
+
|
|
235
|
+
if (cmd === "init" && targets.includes("claude-code") && !argv.includes("--no-plugin")) {
|
|
236
|
+
console.log(`\n${bold("Claude Code plugin")} ${dim(`${PLUGIN}@${MARKETPLACE_NAME}`)}\n`);
|
|
237
|
+
const r = installPlugin({ dry });
|
|
238
|
+
for (const line of r.done) console.log(` ${dim(line)}`);
|
|
239
|
+
if (r.ok && r.reason === "installed") {
|
|
240
|
+
console.log(`\n installed at project scope ${dim("— .claude/settings.json, so it travels with the repo")}`);
|
|
241
|
+
console.log(` ${dim("restart Claude Code, or /reload-plugins, then run /agent-os:init")}`);
|
|
242
|
+
} else if (!r.ok) {
|
|
243
|
+
console.log("");
|
|
244
|
+
for (const line of r.hint) console.log(` ${line}`);
|
|
245
|
+
}
|
|
246
|
+
console.log(`\n ${dim("--no-plugin skips this")}`);
|
|
247
|
+
}
|
|
248
|
+
console.log("");
|
|
207
249
|
return;
|
|
208
250
|
}
|
|
209
251
|
|
package/src/plugin.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install the Claude Code half of agent-os during `init`.
|
|
3
|
+
*
|
|
4
|
+
* The CLI compiles rules for every tool. Claude Code additionally gets the
|
|
5
|
+
* agents, skills, per-agent memory and session hook, which ship as a plugin —
|
|
6
|
+
* so `init` should install it rather than printing two slash commands and
|
|
7
|
+
* hoping. `claude plugin ...` with `--yes` is the documented automation path.
|
|
8
|
+
*
|
|
9
|
+
* Everything here is best effort and reversible: if the `claude` CLI is not on
|
|
10
|
+
* PATH, or a command fails, `init` reports it and carries on. The rules are the
|
|
11
|
+
* part that must not depend on this.
|
|
12
|
+
*/
|
|
13
|
+
import { spawnSync } from "node:child_process";
|
|
14
|
+
|
|
15
|
+
export const MARKETPLACE = "sayansr26/agent-os";
|
|
16
|
+
export const MARKETPLACE_NAME = "sayan-plugins";
|
|
17
|
+
export const PLUGIN = "agent-os";
|
|
18
|
+
|
|
19
|
+
const run = (args) =>
|
|
20
|
+
spawnSync("claude", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
21
|
+
|
|
22
|
+
/** Is the Claude Code CLI usable from here? */
|
|
23
|
+
export function claudeAvailable() {
|
|
24
|
+
const r = spawnSync("claude", ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
25
|
+
return !r.error && r.status === 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Add the marketplace and install the plugin at project scope, so the setup
|
|
30
|
+
* travels with the repository instead of living on one machine.
|
|
31
|
+
*/
|
|
32
|
+
export function installPlugin({ dry = false, scope = "project" } = {}) {
|
|
33
|
+
const steps = [
|
|
34
|
+
["marketplace", ["plugin", "marketplace", "add", MARKETPLACE, "--scope", scope]],
|
|
35
|
+
["plugin", ["plugin", "install", `${PLUGIN}@${MARKETPLACE_NAME}`, "--scope", scope, "--yes"]],
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
if (!claudeAvailable())
|
|
39
|
+
return {
|
|
40
|
+
ok: false,
|
|
41
|
+
reason: "no-cli",
|
|
42
|
+
done: [],
|
|
43
|
+
hint: [
|
|
44
|
+
"The `claude` CLI is not on PATH, so the plugin was not installed.",
|
|
45
|
+
"Inside Claude Code, run:",
|
|
46
|
+
` /plugin marketplace add ${MARKETPLACE}`,
|
|
47
|
+
` /plugin install ${PLUGIN}@${MARKETPLACE_NAME}`,
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
if (dry)
|
|
52
|
+
return { ok: true, reason: "dry-run", done: steps.map(([, a]) => `claude ${a.join(" ")}`), hint: [] };
|
|
53
|
+
|
|
54
|
+
const done = [];
|
|
55
|
+
for (const [what, args] of steps) {
|
|
56
|
+
const r = run(args);
|
|
57
|
+
if (r.status !== 0) {
|
|
58
|
+
const err = (r.stderr || r.stdout || "").trim().split("\n").slice(-3).join("\n");
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
reason: what,
|
|
62
|
+
done,
|
|
63
|
+
hint: [
|
|
64
|
+
`\`claude ${args.join(" ")}\` failed:`,
|
|
65
|
+
...err.split("\n").map((l) => ` ${l}`),
|
|
66
|
+
"The rules above were still written. Install the plugin by hand inside",
|
|
67
|
+
`Claude Code: /plugin install ${PLUGIN}@${MARKETPLACE_NAME}`,
|
|
68
|
+
],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
done.push(`claude ${args.join(" ")}`);
|
|
72
|
+
}
|
|
73
|
+
return { ok: true, reason: "installed", done, hint: [] };
|
|
74
|
+
}
|
package/src/selftest.mjs
CHANGED
|
@@ -77,7 +77,7 @@ try {
|
|
|
77
77
|
writeFileSync(join(r2, ".claude/rules/theming.md"),
|
|
78
78
|
'---\ndescription: Theming\npaths:\n - "src/**/*.tsx"\n---\n\nUse the token set.\n');
|
|
79
79
|
|
|
80
|
-
const i = cli(["init"], r2);
|
|
80
|
+
const i = cli(["init", "--no-plugin"], r2);
|
|
81
81
|
ok(i.status === 0, "init exits 0 on a project that already has rules");
|
|
82
82
|
ok(readFileSync(join(r2, "AGENTS.md"), "utf8") === realAgents ||
|
|
83
83
|
readFileSync(join(r2, ".agent-os/AGENTS.md"), "utf8") === realAgents,
|
|
@@ -85,6 +85,16 @@ try {
|
|
|
85
85
|
ok(existsSync(join(r2, ".agent-os/rules/theming.md")), "existing .claude/rules/ are adopted");
|
|
86
86
|
ok(!existsSync(join(r2, ".agent-os/rules/example.md")), "no example.md when real rules were adopted");
|
|
87
87
|
ok(!existsSync(join(r2, ".claude/rules/example.md")), "no example.md compiled into the project");
|
|
88
|
+
ok(!i.stdout.includes("Claude Code plugin"), "--no-plugin skips the plugin install");
|
|
89
|
+
|
|
90
|
+
// init offers the plugin when not told otherwise; --dry-run proves the
|
|
91
|
+
// commands without running them against the machine's real config.
|
|
92
|
+
const r4 = mkdtempSync(join(tmpdir(), "agent-os-plug-"));
|
|
93
|
+
const pi = cli(["init", "--dry-run"], r4);
|
|
94
|
+
ok(pi.stdout.includes("Claude Code plugin"), "init sets up the Claude Code plugin by default");
|
|
95
|
+
ok(/marketplace add sayansr26\/agent-os|plugin marketplace add|not on PATH/.test(pi.stdout),
|
|
96
|
+
"init names the marketplace step or says why it could not run it");
|
|
97
|
+
rmSync(r4, { recursive: true, force: true });
|
|
88
98
|
|
|
89
99
|
// A hand-written file at a generated path must survive a sync.
|
|
90
100
|
const r3 = mkdtempSync(join(tmpdir(), "agent-os-guard-"));
|
|
@@ -104,6 +114,47 @@ try {
|
|
|
104
114
|
rmSync(r3, { recursive: true, force: true });
|
|
105
115
|
}
|
|
106
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
|
+
|
|
107
158
|
console.log("\n merge, not overwrite");
|
|
108
159
|
writeFileSync(join(root, "opencode.json"), JSON.stringify({ model: "anthropic/x", instructions: ["KEEP.md"] }, null, 2));
|
|
109
160
|
cli(["sync"], root);
|