merge: default culture and rig spec audit

This commit is contained in:
v-openrig-build committed 2026-08-02 08:53:24 +00:00
commit 5f3b5bd497
13 files changed
+257 -16

No files matched your search

+1
View File
@@ -355,4 +355,5 @@ When creating a new agent's startup experience:
- [ ] Include a system check in the startup context
- [ ] Verify the agent spec validates: `rig agent validate agent.yaml`
- [ ] Verify the rig spec validates: `rig spec validate rig.yaml`
- [ ] Run the advisory authoring check: `rig spec audit rig.yaml`
- [ ] Test by launching the rig and checking that the agent received the expected content
+69
View File
@@ -5,6 +5,7 @@ import { DaemonClient } from "../client.js";
import { getDaemonStatus, getDaemonUrl } from "../daemon-lifecycle.js";
import { realDeps } from "./daemon.js";
import type { StatusDeps } from "./status.js";
import { parse as parseYaml } from "yaml";
export interface RigDeps extends StatusDeps {
readFile: (path: string) => string;
@@ -18,6 +19,74 @@ export function rigCommand(depsOverride?: RigDeps): Command {
readFile: (p) => fs.readFileSync(p, "utf-8"),
};
cmd
.command("audit <path>")
.description("Advisory audit of rig-spec culture and startup context")
.option("--json", "JSON output")
.action((filePath: string, opts: { json?: boolean }) => {
const deps = getDeps();
let source: string;
try {
source = deps.readFile(filePath);
} catch {
console.error(`Cannot read file: ${filePath}`);
process.exitCode = 1;
return;
}
let parsed: unknown;
try {
parsed = parseYaml(source);
} catch (error) {
console.error(`Cannot audit ${filePath}: invalid YAML (${(error as Error).message})`);
process.exitCode = 1;
return;
}
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
console.error(`Cannot audit ${filePath}: rig spec must be a YAML object`);
process.exitCode = 1;
return;
}
const spec = parsed as Record<string, unknown>;
const findings: Array<{ kind: string; message: string; typicalFix: string }> = [];
if (typeof spec["culture_file"] !== "string" || spec["culture_file"].trim() === "") {
findings.push({
kind: "missing_culture",
message: "No culture_file is declared.",
typicalFix: "Author CULTURE.md and add culture_file: CULTURE.md. See the openrig-architect skill's authoring workflow.",
});
}
const startup = spec["startup"];
const startupFiles = startup && typeof startup === "object" && !Array.isArray(startup)
? (startup as Record<string, unknown>)["files"]
: undefined;
if (!Array.isArray(startupFiles) || startupFiles.length === 0) {
findings.push({
kind: "missing_startup_context",
message: "No startup.files context is declared.",
typicalFix: "Add startup.files for the environment context agents need at boot. See the openrig-architect skill's authoring workflow.",
});
}
const result = { clean: findings.length === 0, findingCount: findings.length, findings };
if (opts.json) {
console.log(JSON.stringify(result));
return;
}
if (result.clean) {
console.log(`Spec audit clean: ${filePath}`);
return;
}
console.log(`Spec audit: ${findings.length} advisory findings for ${filePath}`);
for (const finding of findings) {
console.log(` - ${finding.message}\n Typical fix: ${finding.typicalFix}`);
}
console.log("Advisory only: these findings do not block validation or launch.");
});
// rig spec validate <path>
cmd
.command("validate <path>")
+55
View File
@@ -187,4 +187,59 @@ describe("rig spec", () => {
expect(output).toContain("not ready");
expect(exitCode).toBe(1);
});
it("rig spec audit reports missing culture and startup context without blocking", async () => {
const clientFactory = vi.fn(() => new DaemonClient(`http://127.0.0.1:${port}`));
const deps: RigDeps = {
lifecycleDeps: mockLifecycleDeps(),
clientFactory,
readFile: vi.fn(() => "version: '0.2'\nname: sparse-rig\npods: []\nedges: []\n"),
};
const program = new Command();
program.exitOverride();
const specCommand = rigCommand(deps);
specCommand.exitOverride();
program.addCommand(specCommand);
const { logs, exitCode } = await captureLogs(() =>
program.parseAsync(["node", "rig", "spec", "audit", "rig.yaml"]),
);
const output = logs.join("\n");
expect(output).toContain("2 advisory findings");
expect(output).toContain("culture_file");
expect(output).toContain("startup.files");
expect(output).toContain("openrig-architect");
expect(exitCode).toBeUndefined();
expect(clientFactory).not.toHaveBeenCalled();
});
it("rig spec audit reports a complete spec clean as JSON", async () => {
const deps = rigDeps([
"version: '0.2'",
"name: complete-rig",
"culture_file: CULTURE.md",
"startup:",
" files:",
" - path: startup/context.md",
" delivery_hint: guidance_merge",
"pods: []",
"edges: []",
"",
].join("\n"));
const program = new Command();
program.exitOverride();
const specCommand = rigCommand(deps);
specCommand.exitOverride();
program.addCommand(specCommand);
const { logs, exitCode } = await captureLogs(() =>
program.parseAsync(["node", "rig", "spec", "audit", "rig.yaml", "--json"]),
);
expect(JSON.parse(logs.join("\n"))).toEqual({
clean: true,
findingCount: 0,
findings: [],
});
expect(exitCode).toBeUndefined();
});
});
@@ -0,0 +1,19 @@
# OpenRig default culture
This is the universal operating floor for every OpenRig team. A rig's own culture may add role- or domain-specific guidance on top of it.
## Pragmatic truth-seeking
Be plain and forthcoming. Say when something is wrong, show the evidence that matters, and change your mind quickly when the evidence changes. Do not agree performatively, and do not manufacture objections to look rigorous.
## Principles over rules
Use judgment from the goal in front of you. Keep hard rules for genuinely high-stakes boundaries: never destroy an authenticated owner environment, never push or publish without authorization, and never leak secrets.
## Ship good, working product
Bias toward action and deliver what the user asked for. Guardrails, reviews, and proofs serve the product; they are not the product. A real blocker should be named precisely while unblocked work continues.
## Match rigor to stakes
Use root-cause discipline, verify the real user path, and protect owner state. Be thorough where failure has real consequences and decisive on low-risk details. When process starts taking more attention than the product, simplify and get back to shipping.
@@ -218,9 +218,10 @@ For rigs that reuse builtin agents, the agents directory is often unnecessary
4. **Write role guidance** for each custom agent — who they are, what they do
5. **Write startup context** for agents that need environment grounding
6. **Validate:** `rig spec validate rig.yaml` and `rig agent validate agents/*/agent.yaml`
7. **Confirm the runtime cwd** — do not assume agents should work from the directory where the rig spec is stored. The spec root controls file resolution; the runtime cwd controls trust, project guidance, permissions, and repo context.
8. **Launch:** `rig up rig.yaml --cwd /path/to/project`
9. **Verify:** `rig ps --nodes` — all agents ready? Check `rig capture` on each agent.
7. **Audit authoring completeness:** `rig spec audit rig.yaml` — advisory findings identify missing culture or startup context without blocking launch.
8. **Confirm the runtime cwd** — do not assume agents should work from the directory where the rig spec is stored. The spec root controls file resolution; the runtime cwd controls trust, project guidance, permissions, and repo context.
9. **Launch:** `rig up rig.yaml --cwd /path/to/project`
10. **Verify:** `rig ps --nodes` — all agents ready? Check `rig capture` on each agent.
### Validation Is Non-Negotiable
@@ -228,6 +229,7 @@ Always validate before launching:
```bash
rig spec validate rig.yaml
rig spec audit rig.yaml
rig agent validate agents/my-agent/agent.yaml
```
@@ -218,9 +218,10 @@ For rigs that reuse builtin agents, the agents directory is often unnecessary
4. **Write role guidance** for each custom agent — who they are, what they do
5. **Write startup context** for agents that need environment grounding
6. **Validate:** `rig spec validate rig.yaml` and `rig agent validate agents/*/agent.yaml`
7. **Confirm the runtime cwd** — do not assume agents should work from the directory where the rig spec is stored. The spec root controls file resolution; the runtime cwd controls trust, project guidance, permissions, and repo context.
8. **Launch:** `rig up rig.yaml --cwd /path/to/project`
9. **Verify:** `rig ps --nodes` — all agents ready? Check `rig capture` on each agent.
7. **Audit authoring completeness:** `rig spec audit rig.yaml` — advisory findings identify missing culture or startup context without blocking launch.
8. **Confirm the runtime cwd** — do not assume agents should work from the directory where the rig spec is stored. The spec root controls file resolution; the runtime cwd controls trust, project guidance, permissions, and repo context.
9. **Launch:** `rig up rig.yaml --cwd /path/to/project`
10. **Verify:** `rig ps --nodes` — all agents ready? Check `rig capture` on each agent.
### Validation Is Non-Negotiable
@@ -228,6 +229,7 @@ Always validate before launching:
```bash
rig spec validate rig.yaml
rig spec audit rig.yaml
rig agent validate agents/my-agent/agent.yaml
```
@@ -294,6 +294,18 @@ import type { RuntimeAdapter, NodeBinding, ResolvedStartupFile } from "./runtime
import { resolveConcreteHint } from "./runtime-adapter.js";
import type { TmuxAdapter } from "../adapters/tmux.js";
function defaultCultureStartupFile(): ResolvedStartupFile {
const assetsRoot = nodePath.resolve(import.meta.dirname, "../../assets");
return {
path: "CULTURE-default.md",
absolutePath: nodePath.join(assetsRoot, "guidance/CULTURE-default.md"),
ownerRoot: assetsRoot,
deliveryHint: "guidance_merge",
required: true,
appliesOn: ["fresh_start", "restore"],
};
}
interface PodInstantiatorDeps {
db: Database.Database;
rigRepo: RigRepository;
@@ -1804,7 +1816,8 @@ export class PodRigInstantiator {
const files: ResolvedStartupFile[] = [];
// Skip layers 1-2 (agent base, profile) — terminal nodes have no agent spec
// Layer 3: Rig culture file
// Layer 3: OpenRig culture floor, then the rig-specific overlay.
files.push(defaultCultureStartupFile());
if (rigSpec.cultureFile) {
files.push({
path: rigSpec.cultureFile,
@@ -1860,7 +1873,8 @@ export class PodRigInstantiator {
files.push({ ...f, absolutePath: nodePath.resolve(agentSourcePath, f.path), ownerRoot: agentSourcePath });
}
}
// 3. Rig culture file
// 3. OpenRig culture floor, then the rig-specific overlay.
files.push(defaultCultureStartupFile());
if (rigSpec.cultureFile) {
files.push({
path: rigSpec.cultureFile,
@@ -60,6 +60,8 @@ function mockClaudeFs(seed: Record<string, string>): ClaudeAdapterFsOps & { _sto
}
const RIG_ROOT = "/project/rigs/test-rig";
const DEFAULT_CULTURE_PATH = path.resolve(import.meta.dirname, "../assets/guidance/CULTURE-default.md");
const DEFAULT_CULTURE = fs.readFileSync(DEFAULT_CULTURE_PATH, "utf8");
const CLAUDE_STARTER = `draft: false
starter_id: claude-fixture-starter
@@ -101,7 +103,10 @@ describe("Agent Starter v1 vertical — real Claude adapter delivery (M2 R2)", (
// registry-entry content (matching the path the resolver will hand
// back) so the adapter's `readFile(file.absolutePath)` succeeds and
// the merge_guidance branch can run.
const claudeFs = mockClaudeFs({ [registryEntryPath]: CLAUDE_STARTER });
const claudeFs = mockClaudeFs({
[registryEntryPath]: CLAUDE_STARTER,
[DEFAULT_CULTURE_PATH]: DEFAULT_CULTURE,
});
const claudeAdapter = new ClaudeCodeAdapter({ tmux, fsOps: claudeFs });
// Pass-through Codex/terminal adapters keep the instantiator's
@@ -172,6 +177,8 @@ describe("Agent Starter v1 vertical — real Claude adapter delivery (M2 R2)", (
// We pick `starter_id: claude-fixture-starter` because it's a stable,
// unambiguous marker the resolver passed through.
expect(claudeMd).toContain("starter_id: claude-fixture-starter");
expect(claudeMd).toContain("BEGIN OpenRig MANAGED BLOCK: CULTURE-default.md");
expect(claudeMd).toContain("Ship good, working product");
db.close();
} finally {
@@ -51,6 +51,8 @@ function mockCodexFs(seed: Record<string, string>): CodexAdapterFsOps & { _store
}
const RIG_ROOT = "/project/rigs/test-rig";
const DEFAULT_CULTURE_PATH = path.resolve(import.meta.dirname, "../assets/guidance/CULTURE-default.md");
const DEFAULT_CULTURE = fs.readFileSync(DEFAULT_CULTURE_PATH, "utf8");
const CODEX_STARTER = `draft: false
starter_id: codex-fixture-starter
@@ -88,7 +90,10 @@ describe("Agent Starter v1 vertical — real Codex adapter delivery (M2 R2)", ()
const nodeLauncher = new NodeLauncher({ db, rigRepo, sessionRegistry, eventBus, tmuxAdapter: tmux });
const startupOrch = new StartupOrchestrator({ db, sessionRegistry, eventBus, tmuxAdapter: tmux });
const codexFs = mockCodexFs({ [registryEntryPath]: CODEX_STARTER });
const codexFs = mockCodexFs({
[registryEntryPath]: CODEX_STARTER,
[DEFAULT_CULTURE_PATH]: DEFAULT_CULTURE,
});
const codexAdapter = new CodexRuntimeAdapter({
tmux,
fsOps: codexFs,
@@ -156,6 +161,8 @@ describe("Agent Starter v1 vertical — real Codex adapter delivery (M2 R2)", ()
expect(agentsMd).toContain("BEGIN OpenRig MANAGED BLOCK: codex-fixture-starter.yaml");
expect(agentsMd).toContain("END OpenRig MANAGED BLOCK: codex-fixture-starter.yaml");
expect(agentsMd).toContain("starter_id: codex-fixture-starter");
expect(agentsMd).toContain("BEGIN OpenRig MANAGED BLOCK: CULTURE-default.md");
expect(agentsMd).toContain("Ship good, working product");
db.close();
} finally {
@@ -125,6 +125,19 @@ describe("openrig-core plugin — skills (HG-2.1 skill content per agentskills.i
);
expect(actual.sort()).toEqual([...EXPECTED_SKILLS].sort());
});
it("routes rig authors through the advisory spec audit in the skill and startup guide", () => {
const architectSkill = fs.readFileSync(
nodePath.join(PLUGIN_ROOT, "skills", "openrig-architect", "SKILL.md"),
"utf-8",
);
const startupGuide = fs.readFileSync(
nodePath.resolve(import.meta.dirname, "../../../docs/reference/agent-startup-guide.md"),
"utf-8",
);
expect(architectSkill).toContain("rig spec audit rig.yaml");
expect(startupGuide).toContain("rig spec audit rig.yaml");
});
});
describe("openrig-core plugin — hooks (HG-2.6 + HG-2.7)", () => {
@@ -260,6 +260,56 @@ profiles:
db.close();
});
it("always includes the default culture when the rig has no culture_file", async () => {
const { db, inst } = setup();
const result = await inst.instantiate(RigSpecCodec.serialize(makeRigSpec()), RIG_ROOT);
expect(result.ok).toBe(true);
const ctxRows = db.prepare("SELECT resolved_files_json FROM node_startup_context").all() as Array<{ resolved_files_json: string }>;
const allFiles = ctxRows.flatMap((row) => JSON.parse(row.resolved_files_json) as Array<{
path: string;
deliveryHint: string;
required: boolean;
}>);
expect(allFiles).toContainEqual(expect.objectContaining({
path: "CULTURE-default.md",
deliveryHint: "guidance_merge",
required: true,
}));
db.close();
});
it("orders the default culture before the rig culture overlay", async () => {
const { db, inst } = setup();
const spec = makeRigSpec({ cultureFile: "CULTURE.md" });
const result = await inst.instantiate(RigSpecCodec.serialize(spec), RIG_ROOT);
expect(result.ok).toBe(true);
const ctxRows = db.prepare("SELECT resolved_files_json FROM node_startup_context").all() as Array<{ resolved_files_json: string }>;
const paths = ctxRows.flatMap((row) =>
(JSON.parse(row.resolved_files_json) as Array<{ path: string }>).map((file) => file.path),
);
expect(paths).toContain("CULTURE-default.md");
expect(paths).toContain("CULTURE.md");
expect(paths.indexOf("CULTURE-default.md")).toBeLessThan(paths.indexOf("CULTURE.md"));
db.close();
});
it("CULTURE-default.md ships the lightweight operating-model floor", () => {
const { existsSync, readFileSync } = require("node:fs");
const { resolve } = require("node:path");
const assetPath = resolve(import.meta.dirname, "../src/domain/../../assets/guidance/CULTURE-default.md");
expect(existsSync(assetPath)).toBe(true);
const content = readFileSync(assetPath, "utf8");
expect(content).toContain("Pragmatic truth-seeking");
expect(content).toContain("Principles over rules");
expect(content).toContain("Ship good, working product");
expect(content).toContain("Match rigor to stakes");
expect(content).not.toContain("full stop on new production");
});
it("openrig-start.md asset exists on disk", () => {
const { existsSync, readFileSync } = require("node:fs");
const { resolve } = require("node:path");
+3 -3
View File
@@ -3,7 +3,7 @@
"edges": {
"canonical": {
"core/mission-slice-sop/SKILL.md": "d195ef466ddd09944ad4ddb073fa1c5e5f809c8167ac72d1a3b3277314321930",
"core/openrig-architect/SKILL.md": "fe038f4ec31e47ac1d5f40fd6d940f6ec291e0f57eab04ce9b2f4ae127289545",
"core/openrig-architect/SKILL.md": "12c3ddb129cd82b304b24eb7330970c6008ce00e42badfe2961967424f2ca45e",
"core/openrig-cmux/SKILL.md": "f54d9b0a0cf42fc9617ab26bb1a45d8b28b982091dbb4332d68b2ebda7d09208",
"core/openrig-herdr/SKILL.md": "9301cdff9734dfaffe400c11733168a82154814023e6e021550fe28b392ef9a8",
"core/openrig-installer/SKILL.md": "362bf919ff91dc083550e4fa2583ec349187d3ed9e0ec5443d49b704cb900e80",
@@ -61,7 +61,7 @@
"claude-compaction-restore/templates/post-compact-restore-instruction.md": "10a5a7e5e052f3aa000a4209f386dc4eb90a3c2bba449ca685f124714f59e47a",
"forming-an-openrig-mental-model/SKILL.md": "ebadef30569ea07fd27e4cac87104a1ce02fe7a17838e4591c35a9a186e2e7f9",
"mission-slice-sop/SKILL.md": "d195ef466ddd09944ad4ddb073fa1c5e5f809c8167ac72d1a3b3277314321930",
"openrig-architect/SKILL.md": "fe038f4ec31e47ac1d5f40fd6d940f6ec291e0f57eab04ce9b2f4ae127289545",
"openrig-architect/SKILL.md": "12c3ddb129cd82b304b24eb7330970c6008ce00e42badfe2961967424f2ca45e",
"openrig-cmux/SKILL.md": "f54d9b0a0cf42fc9617ab26bb1a45d8b28b982091dbb4332d68b2ebda7d09208",
"openrig-herdr/SKILL.md": "9301cdff9734dfaffe400c11733168a82154814023e6e021550fe28b392ef9a8",
"openrig-operator/SKILL.md": "5fa5223914b7242455dec8c47f4c88b2c408257b5be60cd8bfb5f340a819d948",
@@ -71,7 +71,7 @@
},
"spec": {
"core/mission-slice-sop/SKILL.md": "d195ef466ddd09944ad4ddb073fa1c5e5f809c8167ac72d1a3b3277314321930",
"core/openrig-architect/SKILL.md": "fe038f4ec31e47ac1d5f40fd6d940f6ec291e0f57eab04ce9b2f4ae127289545",
"core/openrig-architect/SKILL.md": "12c3ddb129cd82b304b24eb7330970c6008ce00e42badfe2961967424f2ca45e",
"core/openrig-cmux/SKILL.md": "f54d9b0a0cf42fc9617ab26bb1a45d8b28b982091dbb4332d68b2ebda7d09208",
"core/openrig-herdr/SKILL.md": "9301cdff9734dfaffe400c11733168a82154814023e6e021550fe28b392ef9a8",
"core/openrig-installer/SKILL.md": "362bf919ff91dc083550e4fa2583ec349187d3ed9e0ec5443d49b704cb900e80",
@@ -218,9 +218,10 @@ For rigs that reuse builtin agents, the agents directory is often unnecessary
4. **Write role guidance** for each custom agent — who they are, what they do
5. **Write startup context** for agents that need environment grounding
6. **Validate:** `rig spec validate rig.yaml` and `rig agent validate agents/*/agent.yaml`
7. **Confirm the runtime cwd** — do not assume agents should work from the directory where the rig spec is stored. The spec root controls file resolution; the runtime cwd controls trust, project guidance, permissions, and repo context.
8. **Launch:** `rig up rig.yaml --cwd /path/to/project`
9. **Verify:** `rig ps --nodes` — all agents ready? Check `rig capture` on each agent.
7. **Audit authoring completeness:** `rig spec audit rig.yaml` — advisory findings identify missing culture or startup context without blocking launch.
8. **Confirm the runtime cwd** — do not assume agents should work from the directory where the rig spec is stored. The spec root controls file resolution; the runtime cwd controls trust, project guidance, permissions, and repo context.
9. **Launch:** `rig up rig.yaml --cwd /path/to/project`
10. **Verify:** `rig ps --nodes` — all agents ready? Check `rig capture` on each agent.
### Validation Is Non-Negotiable
@@ -228,6 +229,7 @@ Always validate before launching:
```bash
rig spec validate rig.yaml
rig spec audit rig.yaml
rig agent validate agents/my-agent/agent.yaml
```