From b8d1a36bdb3e1c768321f4386e3a06a167fb2bbe Mon Sep 17 00:00:00 2001 From: Andrei Hasna Date: Fri, 10 Jul 2026 10:57:16 +0300 Subject: [PATCH] feat(prompts): render no-hardcoding agent rule --- README.md | 5 +-- docs/managed-prompt-hierarchy.md | 16 +++++++--- package.json | 2 +- src/lib/global-agent-rules-standard.test.ts | 28 +++++++++++++++++ src/lib/global-agent-rules-standard.ts | 11 ++++--- src/lib/session-render.test.ts | 34 +++++++++++++++++++++ 6 files changed, 84 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 3d84a8c..4f6d16f 100644 --- a/README.md +++ b/README.md @@ -189,8 +189,9 @@ before mutating managed files. `instructions init` and `bun run seed` also seed `global-agent-rules-standard`, the managed global/system prompt source for session renaming, task-scoped worktrees, PR-first landing, protected-branch -push safety, autonomous repair, Hasna CLI source-of-truth usage, conversation -surface routing, and unbudgeted Codewith goals unless a user asks for budgets. +push safety, no brittle hardcoding when source-of-truth or reusable abstractions +exist, autonomous repair, Hasna CLI source-of-truth usage, conversation surface +routing, and unbudgeted Codewith goals unless a user asks for budgets. ## Machine-aware Profiles diff --git a/docs/managed-prompt-hierarchy.md b/docs/managed-prompt-hierarchy.md index 28f274d..ab988f4 100644 --- a/docs/managed-prompt-hierarchy.md +++ b/docs/managed-prompt-hierarchy.md @@ -68,14 +68,20 @@ The managed global prompt must include these rules: 3. PR-first landing: normal changes go through branch/worktree plus PR. 4. Never push directly to `main`, the default branch, or any protected branch unless the user explicitly instructs that exact repo and operation. -5. Agents act autonomously: diagnose, repair, validate, and iterate on the +5. Agents must not hardcode brittle values, paths, provider names, config, + business logic, environment-specific IDs, or one-off mappings when a + source-of-truth, schema/config-driven, package-owned, reusable, or cleaner + abstraction exists. This is especially strict in medium and large + applications; explicit constants, fixtures, tests, and temporary + compatibility shims are allowed only when scoped, named, and justified. +6. Agents act autonomously: diagnose, repair, validate, and iterate on the owning CLIs, packages, and workflows before asking the user. Ask only when blocked by destructive decisions, secret-bearing decisions, user-only authority, or external state the agent cannot safely obtain. -6. Use Hasna CLIs/packages as source of truth: `todos`, `conversations`, +7. Use Hasna CLIs/packages as source of truth: `todos`, `conversations`, `mementos`, `knowledge`, `projects`, `repos`, `accounts`, `instructions`, `machines`, `secrets`, and `access`. -7. Use the fleet conversation surfaces correctly: +8. Use the fleet conversation surfaces correctly: `announcements` for `[FREEZE]`, `[UNFREEZE]`, `[BREAKING]`, `[CUTOVER]`, `[POLICY]`, and `[RELEASE]`; `incidents` for outages, crash loops, data risk, or security exposure; `git-publishing` before and after package publishes; @@ -84,10 +90,10 @@ The managed global prompt must include these rules: discussion; project/product channels for normal work; and `conversations blockers` for blocker discovery. Do not invent or refer to a literal blockers channel. -8. Never set Codewith goal/token budgets or goal-plan budgets unless the user +9. Never set Codewith goal/token budgets or goal-plan budgets unless the user explicitly asks for a budget. Durable goals and goal plans should be unbudgeted by default. -9. Never expose secrets in prompts, tasks, memories, conversations, manifests, +10. Never expose secrets in prompts, tasks, memories, conversations, manifests, reports, logs, or PR text. Reference vault item names and grants only. ## Implementation Notes diff --git a/package.json b/package.json index 1da065e..3524f34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hasna/instructions", - "version": "0.4.5", + "version": "0.4.6", "description": "AI coding agent instruction & configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.", "type": "module", "main": "dist/index.js", diff --git a/src/lib/global-agent-rules-standard.test.ts b/src/lib/global-agent-rules-standard.test.ts index 0de4d6d..8a97a60 100644 --- a/src/lib/global-agent-rules-standard.test.ts +++ b/src/lib/global-agent-rules-standard.test.ts @@ -9,6 +9,7 @@ import { planSessionRender, sourceFromConfig } from "./session-render"; import { GLOBAL_AGENT_RULES_STANDARD_CONTENT, GLOBAL_AGENT_RULES_STANDARD_SLUG, + NO_BRITTLE_HARDCODING_RULE, ensureGlobalAgentRulesStandardConfig, } from "./global-agent-rules-standard"; @@ -40,6 +41,9 @@ describe("global agent rules standard", () => { expect(content).toContain("Never mutate shared checkouts"); expect(content).toContain("PR-first landing"); expect(content).toMatch(/Never push directly to `main`, the default branch, or any protected branch/); + expect(content).toContain(NO_BRITTLE_HARDCODING_RULE); + expect(content).toContain("medium and large applications"); + expect(content).toContain("temporary compatibility shims are allowed only when scoped, named, and justified"); expect(content).toContain("Act autonomously"); expect(content).toContain("owning\n CLIs, packages, and workflows"); expect(content).toContain("destructive decisions, secret-bearing decisions, user-only authority"); @@ -92,9 +96,33 @@ describe("global agent rules standard", () => { expect(plan.files[0]?.content).toContain("Global Coding Agent Rules Standard"); expect(plan.files[0]?.content).toContain("Never mutate shared checkouts"); expect(plan.files[0]?.content).toContain("conversations blockers"); + expect(plan.files[0]?.content).toContain(NO_BRITTLE_HARDCODING_RULE); expect(plan.manifest.sources[0]?.layer).toBe("global"); }); + test("renders the no-hardcoding rule into Codewith and Antigravity plans", async () => { + const config = await ensureGlobalAgentRulesStandardConfig(new LocalConfigStore(db)); + const source = sourceFromConfig(config); + + const codewith = planSessionRender({ + tool: "codewith", + profile: "account999", + targetHome: "/tmp/codewith-account999", + sources: [source], + }); + expect(codewith.files[0]?.relativePath).toBe("CODEWITH.md"); + expect(codewith.files[0]?.content).toContain(NO_BRITTLE_HARDCODING_RULE); + + const antigravity = planSessionRender({ + tool: "antigravity", + profile: "account999", + projectRoot: "/tmp/repo", + sources: [source], + }); + expect(antigravity.files[0]?.relativePath).toBe(".agents/rules/01-global-agent-rules-standard.md"); + expect(antigravity.files[0]?.content).toContain(NO_BRITTLE_HARDCODING_RULE); + }); + test("platform profiles link the global rules standard when present", async () => { const standard = await ensureGlobalAgentRulesStandardConfig(new LocalConfigStore(db)); const profiles = await ensurePlatformProfiles(new LocalConfigStore(db)); diff --git a/src/lib/global-agent-rules-standard.ts b/src/lib/global-agent-rules-standard.ts index 976a07c..325c7c0 100644 --- a/src/lib/global-agent-rules-standard.ts +++ b/src/lib/global-agent-rules-standard.ts @@ -3,6 +3,8 @@ import { resolveConfigStore, type ConfigStore } from "../data/config-store.js"; export const GLOBAL_AGENT_RULES_STANDARD_SLUG = "global-agent-rules-standard"; +export const NO_BRITTLE_HARDCODING_RULE = "Do not hardcode brittle values, paths, provider names, config, business logic, environment-specific IDs, or one-off mappings when a source-of-truth, schema/config-driven, package-owned, reusable, or cleaner abstraction exists. This is especially strict in medium and large applications. Explicit constants, fixtures, tests, and temporary compatibility shims are allowed only when scoped, named, and justified."; + export const GLOBAL_AGENT_RULES_STANDARD_CONTENT = `# Global Coding Agent Rules Standard This standard is managed global/system prompt source content for Hasna coding @@ -27,24 +29,25 @@ policy source supersedes them. request before landing. 5. Never push directly to \`main\`, the default branch, or any protected branch unless the user explicitly instructs that exact repo and operation. +6. ${NO_BRITTLE_HARDCODING_RULE} ## Autonomy and Source-of-Truth Tools -6. Act autonomously. Diagnose, repair, validate, and iterate on the owning +7. Act autonomously. Diagnose, repair, validate, and iterate on the owning CLIs, packages, and workflows before asking the user. Ask only when blocked by destructive decisions, secret-bearing decisions, user-only authority, or external state the agent cannot safely obtain. -7. Use Hasna CLIs/packages as the source of truth: \`todos\`, \`conversations\`, +8. Use Hasna CLIs/packages as the source of truth: \`todos\`, \`conversations\`, \`mementos\`, \`knowledge\`, \`projects\`, \`repos\`, \`accounts\`, \`instructions\`, \`machines\`, \`secrets\`, and \`access\`. -8. Secrets safety is mandatory. Never expose secrets in prompts, tasks, +9. Secrets safety is mandatory. Never expose secrets in prompts, tasks, memories, conversations, manifests, reports, logs, PR text, or any other agent-visible output. Reference vault item names, secret identifiers, and access grants only; never print credential values. ## Conversation Surfaces -9. Use default conversation surfaces correctly: \`announcements\` for policy, +10. Use default conversation surfaces correctly: \`announcements\` for policy, freeze, breaking, cutover, and release notices; \`incidents\` for outages, crash loops, data risk, or security exposure; \`git-publishing\` before and after package publishes; \`git-prs\`, \`git-commits\`, and \`git-releases\` diff --git a/src/lib/session-render.test.ts b/src/lib/session-render.test.ts index 456fe19..7375ccd 100644 --- a/src/lib/session-render.test.ts +++ b/src/lib/session-render.test.ts @@ -13,6 +13,7 @@ import { sourcesFromIdentityExport, type SessionInstructionSource, } from "./session-render"; +import { GLOBAL_AGENT_RULES_STANDARD_CONTENT, NO_BRITTLE_HARDCODING_RULE } from "./global-agent-rules-standard"; const globalIdentity: SessionInstructionSource = { id: "global-codewith", @@ -30,6 +31,14 @@ const agentIdentity: SessionInstructionSource = { content: "Prefer repository-local evidence and focused tests.", }; +const globalRulesStandard: SessionInstructionSource = { + id: "global-agent-rules-standard", + label: "Global Agent Rules Standard", + layer: "global", + order: 0, + content: GLOBAL_AGENT_RULES_STANDARD_CONTENT, +}; + function hash(content: string): string { return createHash("sha256").update(content).digest("hex"); } @@ -197,6 +206,19 @@ describe("session render planner", () => { expect(plan.files[0]?.content).toContain("Global Codewith Identity"); }); + test("renders the no-hardcoding global rule into Antigravity project rules", () => { + const projectRoot = join(tmpRoot, "repo"); + const plan = planSessionRender({ + tool: "antigravity", + profile: "account999", + projectRoot, + sources: [globalRulesStandard], + }); + + expect(plan.files[0]?.relativePath).toBe(".agents/rules/01-global-agent-rules-standard.md"); + expect(plan.files[0]?.content).toContain(NO_BRITTLE_HARDCODING_RULE); + }); + test("blocks Antigravity planning until a repository root is explicit", () => { const plan = planSessionRender({ tool: "antigravity", @@ -321,6 +343,18 @@ describe("session render planner", () => { ]); }); + test("renders the no-hardcoding global rule into flattened Codewith instructions", () => { + const plan = planSessionRender({ + tool: "codewith", + profile: "account999", + targetHome: "/tmp/codewith-account999", + sources: [globalRulesStandard], + }); + + expect(plan.files[0]?.relativePath).toBe("CODEWITH.md"); + expect(plan.files[0]?.content).toContain(NO_BRITTLE_HARDCODING_RULE); + }); + test("plans Codewith native imports only when the runtime gate is enabled", () => { process.env[CODEWITH_NATIVE_IMPORTS_ENV] = "1"; const plan = planSessionRender({