@sayansr26/agent-os 0.5.2 → 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 +86 -4
- package/README.md +30 -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 +12 -0
- package/src/cli.mjs +76 -11
- package/src/plugin.mjs +74 -33
- package/src/selftest.mjs +103 -3
package/src/selftest.mjs
CHANGED
|
@@ -8,6 +8,12 @@ import { spawnSync } from "node:child_process";
|
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
10
|
const PKG = join(fileURLToPath(new URL(".", import.meta.url)), "..");
|
|
11
|
+
// Every child inherits this, so nothing here can ever touch the real ~/.claude.
|
|
12
|
+
const HOME = mkdtempSync(join(tmpdir(), "agent-os-home-"));
|
|
13
|
+
process.env.CLAUDE_CONFIG_DIR = HOME;
|
|
14
|
+
const PLUGIN = join(PKG, "plugins/agent-os");
|
|
15
|
+
const node = (script, args, opts = {}) =>
|
|
16
|
+
spawnSync(process.execPath, [join(PLUGIN, script), ...args], { encoding: "utf8", ...opts });
|
|
11
17
|
const cli = (args, cwd) => spawnSync(process.execPath, [join(PKG, "bin/agent-os.mjs"), ...args, "--root", cwd], { encoding: "utf8" });
|
|
12
18
|
let fail = 0;
|
|
13
19
|
const ok = (c, m) => { console.log(` ${c ? "ok " : "FAIL"} ${m}`); if (!c) fail++; };
|
|
@@ -85,13 +91,14 @@ try {
|
|
|
85
91
|
ok(existsSync(join(r2, ".agent-os/rules/theming.md")), "existing .claude/rules/ are adopted");
|
|
86
92
|
ok(!existsSync(join(r2, ".agent-os/rules/example.md")), "no example.md when real rules were adopted");
|
|
87
93
|
ok(!existsSync(join(r2, ".claude/rules/example.md")), "no example.md compiled into the project");
|
|
88
|
-
ok(!i.stdout.includes("
|
|
94
|
+
ok(!i.stdout.includes("--no-plugin skips this"), "--no-plugin skips the plugin install");
|
|
89
95
|
|
|
90
96
|
// init offers the plugin when not told otherwise; --dry-run proves the
|
|
91
97
|
// commands without running them against the machine's real config.
|
|
92
98
|
const r4 = mkdtempSync(join(tmpdir(), "agent-os-plug-"));
|
|
93
99
|
const pi = cli(["init", "--dry-run"], r4);
|
|
94
|
-
ok(pi.stdout.includes("
|
|
100
|
+
ok(pi.stdout.includes("--no-plugin skips this"), "init sets up the Claude Code plugin by default");
|
|
101
|
+
ok(pi.stdout.includes("SETUP FRESH"), "init on an empty project reports a fresh setup");
|
|
95
102
|
ok(/marketplace add sayansr26\/agent-os|plugin marketplace add|not on PATH/.test(pi.stdout),
|
|
96
103
|
"init names the marketplace step or says why it could not run it");
|
|
97
104
|
rmSync(r4, { recursive: true, force: true });
|
|
@@ -114,13 +121,106 @@ try {
|
|
|
114
121
|
rmSync(r3, { recursive: true, force: true });
|
|
115
122
|
}
|
|
116
123
|
|
|
124
|
+
console.log("\n Claude Code project setup");
|
|
125
|
+
{
|
|
126
|
+
const r5 = mkdtempSync(join(tmpdir(), "agent-os-cc-"));
|
|
127
|
+
mkdirSync(join(r5, ".claude"), { recursive: true });
|
|
128
|
+
// An existing settings.json with unrelated keys must survive untouched.
|
|
129
|
+
writeFileSync(join(r5, ".claude/settings.json"),
|
|
130
|
+
JSON.stringify({ permissions: { deny: ["Bash(git push *)"] }, env: { FOO: "bar" } }, null, 2));
|
|
131
|
+
writeFileSync(join(r5, "CLAUDE.md"),
|
|
132
|
+
"# CLAUDE.md\n\nStack notes.\n\n## Operator preferences\n\n- Never run git unasked.\n\n## Verification\n\nRun make test.\n");
|
|
133
|
+
|
|
134
|
+
cli(["init", "--no-plugin"], r5);
|
|
135
|
+
const st = JSON.parse(readFileSync(join(r5, ".claude/settings.json"), "utf8"));
|
|
136
|
+
ok(st.env.CLAUDE_CODE_ENABLE_TODO_TOOLS === "1", "todo tools enabled in project settings");
|
|
137
|
+
ok(st.env.FOO === "bar", "existing env keys preserved");
|
|
138
|
+
ok(st.permissions?.deny?.[0] === "Bash(git push *)", "existing permissions preserved");
|
|
139
|
+
ok(st.permissions.deny.length === 42, "git write protection merged into project settings, no duplicate push rule");
|
|
140
|
+
ok(!existsSync(join(HOME, "settings.json")), "without --global (and no terminal) ~/.claude is left alone");
|
|
141
|
+
ok(!existsSync(join(r5, ".claude/settings.json.agent-os.bak")), "no backup litter inside the project");
|
|
142
|
+
|
|
143
|
+
const again = cli(["init", "--no-plugin", "--global"], r5).stdout;
|
|
144
|
+
ok(again.includes("SETUP REPAIR") && again.includes("MISSING ~/.claude/settings.json git write protection"),
|
|
145
|
+
"init on a set-up project reports a repair and names what is missing");
|
|
146
|
+
ok(again.includes("kept .agent-os/"), "repair keeps the existing source instead of re-scaffolding");
|
|
147
|
+
const us = JSON.parse(readFileSync(join(HOME, "settings.json"), "utf8"));
|
|
148
|
+
ok(us.permissions.deny.includes("Bash(git commit *)") && us.env.CLAUDE_CODE_ENABLE_TODO_TOOLS === "1",
|
|
149
|
+
"--global writes git protection and task tools to ~/.claude/settings.json");
|
|
150
|
+
ok(readFileSync(join(HOME, "CLAUDE.md"), "utf8").includes("TaskCreate"), "--global adds the task rule to ~/.claude/CLAUDE.md");
|
|
151
|
+
cli(["init", "--no-plugin", "--global"], r5);
|
|
152
|
+
ok(JSON.parse(readFileSync(join(HOME, "settings.json"), "utf8")).permissions.deny.length === 42, "second --global run adds nothing");
|
|
153
|
+
|
|
154
|
+
const cm = readFileSync(join(r5, "CLAUDE.md"), "utf8");
|
|
155
|
+
ok(cm.includes("TaskCreate / TaskUpdate"), "task-tracking rule added to CLAUDE.md");
|
|
156
|
+
ok(cm.indexOf("TaskCreate") > cm.indexOf("## Operator preferences") &&
|
|
157
|
+
cm.indexOf("TaskCreate") < cm.indexOf("## Verification"),
|
|
158
|
+
"rule lands inside Operator preferences, not at the end");
|
|
159
|
+
ok(cm.includes("Never run git unasked."), "existing CLAUDE.md content preserved");
|
|
160
|
+
|
|
161
|
+
// Idempotent: a second init must not duplicate either.
|
|
162
|
+
cli(["init", "--no-plugin"], r5);
|
|
163
|
+
const cm2 = readFileSync(join(r5, "CLAUDE.md"), "utf8");
|
|
164
|
+
ok(cm2.split("TaskCreate").length - 1 === 1, "second init does not duplicate the rule");
|
|
165
|
+
|
|
166
|
+
// A user who set the flag their own way keeps their value.
|
|
167
|
+
const r6 = mkdtempSync(join(tmpdir(), "agent-os-cc2-"));
|
|
168
|
+
mkdirSync(join(r6, ".claude"), { recursive: true });
|
|
169
|
+
writeFileSync(join(r6, ".claude/settings.json"),
|
|
170
|
+
JSON.stringify({ env: { CLAUDE_CODE_ENABLE_TODO_TOOLS: "true" } }, null, 2));
|
|
171
|
+
cli(["init", "--no-plugin"], r6);
|
|
172
|
+
ok(JSON.parse(readFileSync(join(r6, ".claude/settings.json"), "utf8"))
|
|
173
|
+
.env.CLAUDE_CODE_ENABLE_TODO_TOOLS === "true", "an existing truthy value is left as the user set it");
|
|
174
|
+
|
|
175
|
+
rmSync(r5, { recursive: true, force: true });
|
|
176
|
+
rmSync(r6, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log("\n plugin hooks");
|
|
180
|
+
{
|
|
181
|
+
const r7 = mkdtempSync(join(tmpdir(), "agent-os-hooks-"));
|
|
182
|
+
const mem = join(r7, ".claude/agent-memory/agent-os-feature-cartographer");
|
|
183
|
+
mkdirSync(mem, { recursive: true });
|
|
184
|
+
for (const f of ["billing", "orders", "search"]) mkdirSync(join(r7, "src/features", f), { recursive: true });
|
|
185
|
+
writeFileSync(join(mem, "_architecture.md"), "# Architecture\n");
|
|
186
|
+
writeFileSync(join(mem, "billing.md"), "---\nmapped: 2026-01-01\nentry: src/features/billing/index.ts\n---\n# billing\n");
|
|
187
|
+
writeFileSync(join(mem, "MEMORY.md"), "- billing — src/features/billing — mapped 2026-01-01\n");
|
|
188
|
+
mkdirSync(join(r7, ".agent-os/rules"), { recursive: true });
|
|
189
|
+
writeFileSync(join(r7, ".agent-os/rules/api.md"), '---\npaths:\n - "src/**"\n---\n\nSource body.\n');
|
|
190
|
+
mkdirSync(join(r7, ".claude/rules"), { recursive: true });
|
|
191
|
+
writeFileSync(join(r7, ".claude/rules/api.md"), "---\n---\n\n<!-- agent-os: generated from .agent-os/ — edit the source -->\nEdited in place.\n");
|
|
192
|
+
const env = { ...process.env, CLAUDE_PROJECT_DIR: r7 };
|
|
193
|
+
|
|
194
|
+
const start = node("hooks/session-resume.mjs", [], { cwd: r7, env }).stdout;
|
|
195
|
+
ok(start.includes("agent-os is active") && start.includes("agent-os:feature-cartographer"), "session start announces the agents");
|
|
196
|
+
ok(start.includes("Mapped: 1 of 3 features") && start.includes("billing"), "session start says what is mapped");
|
|
197
|
+
|
|
198
|
+
const run = `selftest-${process.pid}-${Date.now()}`;
|
|
199
|
+
const edit = (file, session = run) => node("hooks/pre-edit.mjs", [], {
|
|
200
|
+
env, input: JSON.stringify({ session_id: session, cwd: r7, tool_input: { file_path: join(r7, file) } }),
|
|
201
|
+
}).stdout;
|
|
202
|
+
const denied = edit(".claude/rules/api.md");
|
|
203
|
+
ok(denied.includes('"deny"') && denied.includes(".agent-os/rules/api.md"), "edit to a generated rule is blocked, source named");
|
|
204
|
+
ok(edit("src/features/orders/a.ts").includes("no cartographer map"), "first edit to an unmapped feature reminds");
|
|
205
|
+
ok(edit("src/features/orders/b.ts") === "", "reminder shows once per feature per session");
|
|
206
|
+
ok(edit("src/features/billing/a.ts") === "", "mapped feature stays silent");
|
|
207
|
+
ok(edit("src/lib/x.ts") === "", "non-feature path stays silent");
|
|
208
|
+
|
|
209
|
+
mkdirSync(join(mem, ".claude/agent-memory"), { recursive: true });
|
|
210
|
+
const audit = node("skills/init/scripts/audit.mjs", [r7]).stdout;
|
|
211
|
+
ok(/api\.md was edited in place/.test(audit), "audit flags a generated rule that drifted from its source");
|
|
212
|
+
ok(audit.includes("folder inside agent memory"), "audit flags a nested folder in agent memory");
|
|
213
|
+
ok(audit.includes("1 of 3 (33%)"), "audit reports map coverage, not just that a folder exists");
|
|
214
|
+
rmSync(r7, { recursive: true, force: true });
|
|
215
|
+
}
|
|
216
|
+
|
|
117
217
|
console.log("\n merge, not overwrite");
|
|
118
218
|
writeFileSync(join(root, "opencode.json"), JSON.stringify({ model: "anthropic/x", instructions: ["KEEP.md"] }, null, 2));
|
|
119
219
|
cli(["sync"], root);
|
|
120
220
|
const oc = JSON.parse(read("opencode.json"));
|
|
121
221
|
ok(oc.model === "anthropic/x", "existing keys preserved");
|
|
122
222
|
ok(oc.instructions.includes("KEEP.md"), "existing instructions preserved");
|
|
123
|
-
} finally { rmSync(root, { recursive: true, force: true }); }
|
|
223
|
+
} finally { rmSync(root, { recursive: true, force: true }); rmSync(HOME, { recursive: true, force: true }); }
|
|
124
224
|
|
|
125
225
|
console.log(`\n${fail ? `FAILED (${fail})` : "PASSED"}\n`);
|
|
126
226
|
process.exit(fail ? 1 : 0);
|