Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ keeps the token value in the runtime environment or secret manager.
| Category | What's stored |
|----------|--------------|
| `agent` | settings.json, keybindings.json, config.toml |
| `rules` | CLAUDE.md, AGENTS.md, GEMINI.md, rules/*.md |
| `rules` | CLAUDE.md, AGENTS.md, AICOPILOT.md, .agents/rules/*.md, rules/*.md |
| `mcp` | ~/.claude.json (MCP server entries) |
| `shell` | .zshrc |
| `git` | .gitconfig |
Expand Down
49 changes: 30 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,20 @@ and `API_KEY_SIGNING_SECRET` are also accepted). Client apps use
`InstructionsV1Client` is generated from the serve OpenAPI document
(`bun run generate:sdk`).

## Storage Sync (local CLI)
## Storage Modes

The local CLI supports optional remote storage sync through a package-local
Postgres connection:
Every CLI command, MCP tool, and SDK method routes through a single `ConfigStore`
abstraction with two transports:

```bash
export HASNA_INSTRUCTIONS_DATABASE_URL=postgres://...
instructions storage status
instructions storage push
instructions storage pull
instructions storage sync
```
- **local** — on-box SQLite (`LocalConfigStore`), fully first-class. Used when no
API env vars are set.
- **api** (self_hosted / cloud) — HTTP `/v1` + bearer key (`CloudConfigStore`).
Activated by setting **both** `HASNA_INSTRUCTIONS_API_URL` and
`HASNA_INSTRUCTIONS_API_KEY`. Identical client code; only the URL/key differ,
and the self_hosted/cloud distinction is enforced server-side by tenancy.

The MCP server also exposes `storage_status`, `storage_push`, `storage_pull`, and
`storage_sync`.
Clients never hold a database DSN. The raw Postgres connection is a server-only
concern (`instructions-serve`).

## Data Directory

Expand All @@ -158,7 +157,12 @@ Local data is stored in `~/.hasna/configs/` (unchanged, for fleet continuity).

`instructions session plan` and `instructions session apply` render
OpenIdentities and instruction sources into provider-native files for Claude,
Codex, Cursor, OpenCode, and Codewith.
Codex, Cursor, OpenCode, Codewith, aicopilot, and Google Antigravity.
The old Google agent target is removed; Antigravity is the only Google coding
agent render target. Antigravity workspace rules are rendered to
`.agents/rules/*.md`; its current global rules and MCP files use Google's
legacy-named `~/.gemini/GEMINI.md` and `~/.gemini/config/mcp_config.json`
paths but remain owned by the `antigravity` target.

```bash
instructions session plan \
Expand All @@ -174,12 +178,19 @@ instructions session apply \
--identity-export ./instructions.json
```

Accepted source layers are `global`, `provider`/`tool`, `account`,
`identity`/`agent`, `project`, and `local`. Empty renders fail closed unless
`--allow-empty-sources` is passed. Apply writes generated manifests with file
hashes, checks previous manifests for drift, refuses unmanaged file conflicts
unless `--force` is passed, removes stale managed mirrors only when safe, and
writes local snapshots before mutating managed files.
Accepted source layers are `global`, `provider`/`tool`, `account`, `machine`,
`division`, `workspace`, `project`/`repo`, `path`, `identity`/`agent`,
`session`, and `local`. Empty renders fail closed unless `--allow-empty-sources`
is passed. Apply writes generated manifests with file hashes, checks previous
manifests for drift, refuses unmanaged file conflicts unless `--force` is
passed, removes stale managed mirrors only when safe, and writes local snapshots
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.

## Machine-aware Profiles

Expand Down
105 changes: 105 additions & 0 deletions docs/managed-prompt-hierarchy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Managed Prompt Hierarchy

## Goal

`open-instructions` is the source of truth for Hasna coding-agent prompts. It
stores canonical instruction fragments, renders them into each agent's native
files, records provenance, and detects drift.

The system must support every active coding-agent surface while keeping one
canonical source per rule. Google Antigravity is the only active Google coding
agent target.

## Layers

Render sources are composed in this order:

| Layer | Purpose |
| --- | --- |
| `global` | Non-overridable Hasna rules and default agent behavior. |
| `tool` | Tool/provider-specific behavior such as Codewith, Claude, Codex, Cursor, OpenCode, Antigravity, or aicopilot. |
| `account` | Account or auth-profile overlays such as `live-codewith`. |
| `machine` | Host-specific rules for machines such as `spark01`, Apple laptops, or fleet nodes. |
| `division` | Area-level rules for repo families such as `opensource`, `hasnastudio`, `hasnatools`, or `infra`. |
| `workspace` | Rules for ephemeral or project workspaces such as `~/.hasna/projects/workspaces/wks_*`. |
| `repo` | Repository purpose, commands, ownership, deployment mode, boundaries, and validation. |
| `path` | Subfolder/module rules for large repos. |
| `agent` | Persona or named-agent behavior. |
| `session` | Temporary task/session-specific overlays. |
| `local` | Highest-precedence local override for explicit one-off use. |

`provider` remains a CLI alias for `tool`, and `identity` remains a CLI alias
for `agent`. Legacy identity exports that call a source `project-overlay`
continue to map to `repo`; `machine-overlay` maps to `machine`; and
`session-overlay` maps to `session`.

## Active Targets

| Tool | Managed output |
| --- | --- |
| `codewith` | `CODEWITH.md`, with optional native imports into `.hasna/instructions`. |
| `claude` | `CLAUDE.md`, with native imports into `.hasna/instructions`. |
| `codex` | Flattened `AGENTS.md`. |
| `cursor` | Project-owned `.cursor/rules/*.mdc`. |
| `opencode` | `AGENTS.md`, `opencode.json`, and `.hasna/instructions` fragments. |
| `aicopilot` | `AICOPILOT.md`, with optional `aicopilot.json` instructions in a later pass. |
| `antigravity` | Project-owned `.agents/rules/*.md`, workspace MCP at `.agents/mcp_config.json`, and Google's current legacy-named global Antigravity files at `~/.gemini/GEMINI.md` and `~/.gemini/config/mcp_config.json`. |

The retired Google coding-agent target has no active render path. Do not create
new `agent=gemini` records, do not render project `GEMINI.md` for that retired
target, and do not add new global rules that target it. Existing legacy project
files should be treated only as migration inputs into Antigravity or `AGENTS.md`.

Antigravity compatibility note: Google's current Antigravity documentation still
uses legacy-named `~/.gemini/...` paths for Antigravity global rules and global
MCP configuration. `open-instructions` should treat those paths as Antigravity
outputs only; there must be no active `gemini` agent target.

## Required Global Rules

The managed global prompt must include these rules:

1. Agents that support session naming must rename the session early to match
the task. If the task materially pivots, rename it again.
2. Repo mutation must happen in a task-scoped worktree. First inspect the
canonical worktree root `$HOME/.hasna/repos/worktrees`; prefer Hasna
repo/project worktree mechanisms when available; otherwise use
`git worktree`. Never mutate shared checkouts.
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
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`,
`mementos`, `knowledge`, `projects`, `repos`, `accounts`, `instructions`,
`machines`, `secrets`, and `access`.
7. 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;
`git-prs`, `git-commits`, and `git-releases` for repository landing events;
`hq` for broad coordination; `agent-policy` for agent operating-rule
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
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,
reports, logs, or PR text. Reference vault item names and grants only.

## Implementation Notes

The implementation is incremental:

1. Extend the session render layer model and aliases.
2. Add active Antigravity support and remove the retired Google agent from active config sync.
3. Keep Codewith behavior compatible with existing flattened renders and the
`HASNA_CONFIGS_CODEWITH_NATIVE_IMPORTS` gate.
4. Seed the managed `global-agent-rules-standard` reference so canonical global
prompt content includes the required operating rules.
5. Add tests that prove layer ordering, Antigravity output paths, the
Antigravity 12,000-character rule-file limit, active agent target coverage,
and the seeded global rules content.
14 changes: 5 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@hasna/instructions",
"version": "0.3.2",
"description": "AI coding agent instruction & configuration manager store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
"version": "0.4.5",
"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",
"types": "dist/index.d.ts",
Expand All @@ -15,10 +15,6 @@
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./storage": {
"types": "./dist/storage.d.ts",
"import": "./dist/storage.js"
}
},
"files": [
Expand All @@ -29,8 +25,8 @@
],
"scripts": {
"clean": "rm -rf dist",
"build": "bun run clean && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external pg --external @modelcontextprotocol/sdk && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external pg --external @modelcontextprotocol/sdk && bun build src/server/index.ts --outdir dist/server --target bun --external pg && bun build src/index.ts src/storage.ts --outdir dist --target bun --external pg && tsc --emitDeclarationOnly --outDir dist",
"build:server": "bun run clean && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external pg --external @modelcontextprotocol/sdk && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external pg --external @modelcontextprotocol/sdk && bun build src/server/index.ts --outdir dist/server --target bun --external pg && bun build src/index.ts src/storage.ts --outdir dist --target bun --external pg",
"build": "bun run clean && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external pg --external @modelcontextprotocol/sdk && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external pg --external @modelcontextprotocol/sdk && bun build src/server/index.ts --outdir dist/server --target bun --external pg && bun build src/index.ts --outdir dist --target bun --external pg && tsc --emitDeclarationOnly --outDir dist",
"build:server": "bun run clean && bun build src/cli/index.tsx --outdir dist/cli --target bun --external ink --external react --external chalk --external pg --external @modelcontextprotocol/sdk && bun build src/mcp/index.ts --outdir dist/mcp --target bun --external pg --external @modelcontextprotocol/sdk && bun build src/server/index.ts --outdir dist/server --target bun --external pg && bun build src/index.ts --outdir dist --target bun --external pg",
"build:dashboard": "cd dashboard && bun run build",
"migrate": "bun run src/server/index.ts migrate",
"generate:sdk": "bun run scripts/generate-sdk.ts",
Expand All @@ -52,7 +48,7 @@
"agent-config",
"claude",
"codex",
"gemini",
"antigravity",
"mcp",
"ai",
"coding-agent",
Expand Down
19 changes: 12 additions & 7 deletions scripts/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@
import { getDatabase } from "../src/db/database";
import { getConfigStats } from "../src/db/configs";
import { syncKnown } from "../src/lib/sync";
import { createConfig, getConfig } from "../src/db/configs";
import { LocalConfigStore } from "../src/data/config-store";
import { ensurePlatformProfiles } from "../src/lib/platform-profiles";
import { ensureProjectDashboardStandardConfig } from "../src/lib/project-dashboard-standard";
import { ensureGlobalAgentRulesStandardConfig } from "../src/lib/global-agent-rules-standard";

const db = getDatabase();
const store = new LocalConfigStore(db);

console.log("\n@hasna/configs — seeding initial configurations\n");

// Sync all known configs (CLAUDE.md, rules/*.md, settings.json, codex, gemini, zshrc, gitconfig, npmrc, etc.)
const result = await syncKnown({ db });
// Sync all known configs (CLAUDE.md, rules/*.md, settings.json, codex, antigravity, zshrc, gitconfig, npmrc, etc.)
const result = await syncKnown({ store });
console.log(`Synced known configs: +${result.added} updated:${result.updated} unchanged:${result.unchanged} skipped:${result.skipped.length}`);
if (result.skipped.length > 0) {
console.log(" skipped (not found):", result.skipped.join(", "));
Expand Down Expand Up @@ -74,18 +76,21 @@ Format: export KEY_NAME="value"
console.log("\nReference docs:");
for (const ref of refs) {
try {
getConfig(ref.slug, db);
await store.getConfig(ref.slug);
console.log(` = ${ref.slug}`);
} catch {
const c = createConfig({ name: ref.name, category: ref.category, agent: "global", format: "markdown", content: ref.content, kind: "reference", description: ref.description }, db);
const c = await store.createConfig({ name: ref.name, category: ref.category, agent: "global", format: "markdown", content: ref.content, kind: "reference", description: ref.description });
console.log(` + ${c.slug} (reference)`);
}
}

const projectDashboardStandard = ensureProjectDashboardStandardConfig(db);
const globalAgentRulesStandard = await ensureGlobalAgentRulesStandardConfig(store);
console.log(` = ${globalAgentRulesStandard.slug}`);

const projectDashboardStandard = await ensureProjectDashboardStandardConfig(store);
console.log(` = ${projectDashboardStandard.slug}`);

const machineProfiles = ensurePlatformProfiles(db);
const machineProfiles = await ensurePlatformProfiles(store);
console.log(`\nMachine-aware profiles: ${machineProfiles.map((profile) => profile.slug).join(", ")}`);

const stats = getConfigStats(db);
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export interface Config {
slug: string;
kind: "file" | "reference";
category: "agent" | "rules" | "mcp" | "shell" | "secrets_schema" | "workspace" | "git" | "tools";
agent: "claude" | "codex" | "gemini" | "opencode" | "cursor" | "codewith" | "aicopilot" | "zsh" | "git" | "npm" | "global";
agent: "claude" | "codex" | "antigravity" | "opencode" | "cursor" | "codewith" | "aicopilot" | "zsh" | "git" | "npm" | "global";
target_path: string | null;
outputs: ConfigOutput[];
format: "text" | "json" | "toml" | "yaml" | "markdown" | "ini";
Expand Down
Loading
Loading