# @hasna/instructions

`@hasna/instructions` stores, versions, renders, and applies configuration and
instruction sources for developers and platform teams operating AI coding
agents. Install the CLI with `bun add --global @hasna/instructions`; the package
also provides MCP, HTTP API, and SDK surfaces.

[![npm](https://img.shields.io/npm/v/@hasna/instructions)](https://www.npmjs.com/package/@hasna/instructions)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)

## Install

```bash
bun add --global @hasna/instructions
```

## Quick start

```bash
instructions --help

# Hosted-by-default: resolves the approved key and uses
# https://api.hasna.com/instructions/v1.
instructions status
instructions list

# Explicit on-box SQLite mode; there is never an implicit fallback.
HASNA_INSTRUCTIONS_LOCAL=1 instructions init
HASNA_INSTRUCTIONS_LOCAL=1 instructions list
```

## Migrating from `@hasna/configs`

This package was formerly `@hasna/configs`. The `configs` CLI alias and
`configs-mcp` alias remain available for existing integrations; new usage can
use the `instructions` names.

Historical on-box rows can be migrated without overwriting the current store:

```bash
export HASNA_INSTRUCTIONS_LOCAL=1
instructions migrate-legacy --confirm-local --json          # no-write plan
instructions migrate-legacy --confirm-local --apply --json  # backup + transaction
```

The source defaults to `~/.hasna/configs/configs.db`; the destination defaults
to the current `instructions.db`. A non-empty destination is refused unless
`--merge-preserve-destination` is explicit, and that merge never overwrites an
existing row.

## CLI Usage

```bash
instructions --help
instructions list                                      # compact, paged summary
instructions list --verbose                            # expanded metadata, still paged
instructions list --json                               # legacy complete full-record array
instructions list --json --detail compact --limit 20   # bounded identity envelope
instructions list --json --detail compact --fields id,slug,name,version
instructions list --json --detail full --limit 20      # explicit bounded content read
instructions list --json --detail compact --all        # explicit complete metadata read
instructions show <slug>             # full metadata + content
instructions inspect <slug>          # alias for show
instructions profile resolve
instructions profile apply --auto
instructions package-manager-scan --fail-on-findings .
instructions package-manager-scan --home --fail-on-findings .
```

Skills are managed through the Skills CLI. Profile application and bootstrap do
not inspect, install or repair native Inbox skill payloads, and do not send
Conversations heartbeats. Their legacy `--from` and `--delivery-verified` flags
remain accepted for compatibility but perform no verification.

`instructions managed-skills status` and `instructions managed-skills apply`
are deprecated, read-only migration reports. They return a failure when legacy
Inbox paths remain and preserve those paths, including symlinks. Use the Skills
CLI to review `skills migrate native`, then sync and load the selected skills.
An empty legacy inventory does not verify hosted skill selection or runtime
delivery. The exported inspection/reconciliation helpers behave the same way;
an explicit legacy `assetPath` no longer enables native payload writes.

Collection commands are compact by default to keep agent terminals and context
small. Human output is capped at 20 rows unless you pass `--limit`; use
`--cursor` to continue from the next page. Detail is explicit:

- `--verbose` expands list rows with descriptions, tags, and paths.
- Legacy `--json` with no modern detail flags preserves the complete full-record array for automation. Explicit `--limit`/`--cursor` now bound that legacy array.
- `--detail compact` returns a content-free `{ configs, _meta }` identity envelope through the already-deployed `/v1/configs?view=identity` projection. Add `--fields` (including immutable `id`), `--all`, or `--pretty` as needed.
- `--detail full` is the explicit content-bearing envelope; it is bounded unless `--all` is supplied.
- `show`/`inspect` and `snapshot show` print full config or snapshot content.

`instructions report --json` emits the stable `schema_version: 1` report
envelope. Its top-level fields are `configs`, `profiles`, `drift`, `secrets`,
`by_agent`, and `by_category`. The nested count fields are numeric, and
`secrets.policy` is `redacted_on_ingest`. Run `instructions report` without
`--json` for the human-readable report.

### Conditional config updates

Read the current config version before preparing an edit, then pass that
`expected_version` to `updateConfig` (store/public SDK), `update_config` (MCP), or:

```bash
instructions add ./rule.md --kind reference --name example-rule --update --expected-version 7
```

Guarded clients use `POST /v1/configs/:id/conditional-update`, which requires
`expected_version`. The generated SDK also exposes `conditionalUpdateConfig` for
this operation. An older server returns 404 without mutation; clients never fall
back to an unconditional update. A version mismatch returns HTTP 409 with code
`CONFIG_VERSION_CONFLICT`, changing neither the config nor its snapshots. Refresh
and review the newer version before trying again. Calls without a precondition
retain their existing behavior. New servers also accept `expected_version` on
PATCH/PUT; use the dedicated conditional route when server capabilities are unknown.

## Package-Manager Secret Guard

`instructions package-manager-scan` blocks package-manager credential ingress without
printing credential values. It scans repo `.npmrc` files, home `.npmrc`, Bun
config, lockfiles, and shell profiles, then reports only the path, line, rule,
surface, and tracked status.

Use it in CI or pre-commit hooks:

```bash
instructions package-manager-scan --fail-on-findings .
```

Use `--home` for local operator scans:

```bash
instructions package-manager-scan --home --fail-on-findings .
```

Safe `.npmrc` auth uses npm's environment reference, not a rendered literal:

```ini
@hasna:registry=https://registry.npmjs.org/
# Add the npm auth-token entry with an environment reference, not a literal value.
```

Bun release-age quarantine must stay enabled. Keep
`minimumReleaseAgeExcludes` narrowed to exact package names published by the
public Hasna, OpenAI, or Anthropic scopes, or the verified official unscoped
`openai` package. An additional publisher requires an exact Instructions
authority source ID, version, content hash, and integrity-bound policy file;
wildcards, placeholders, lookalike scopes, and unreviewed third-party package
names fail the guard.

## MCP Server

```bash
instructions-mcp        # or the aliased binary: configs-mcp
```

The MCP server identity is intentionally kept as `configs` so the existing
fleet MCP configuration keeps working; the fleet rename is a separate controlled
step. Agent-facing MCP tools follow the same gradual disclosure model.
`list_configs` and `list_profiles` return paged compact envelopes by default and
accept `limit`, `cursor`, and `verbose`. `apply_config` and `apply_profile` omit
`previous_content` and `new_content` unless `verbose: true` is passed. Use
`get_config` when full config content is needed.

To register a reviewed server in Claude or Sumi, use the version-bound
[Native MCP plan/apply commands](docs/native-mcp.md). Registration preserves
unrelated settings and requires separate native adoption verification.

## HTTP mode (MCP)

```bash
instructions-mcp --http          # http://127.0.0.1:8807/mcp
MCP_HTTP=1 instructions-mcp
```

Health: `GET http://127.0.0.1:8807/health`. The production
`instructions-serve` process deliberately does **not** mount MCP; local MCP and
the authenticated `/v1` service remain separate authorities.

## HTTP API server (`instructions-serve`)

```bash
instructions-serve
```

Surfaces:

- `GET /health`, `GET /ready`, `GET /version` → `{ status, version, backend }`
- `GET /openapi.json`, `GET /v1/openapi.json` → the OpenAPI 3.1 document the SDK
  is generated from.
- `/v1/*` — versioned cloud API (configs, profiles, snapshots, stats).
- No `/api/*` — the former local REST surface is not mounted (the removed
  bundled dashboard was its only consumer).

### Server data backend (PostgreSQL, fail closed)

`instructions-serve` requires `HASNA_INSTRUCTIONS_DATABASE_URL` (or its documented
alias) for a usable `/v1` service. Without a DSN, `/health` remains a liveness
probe but reports `backend: "unconfigured"`; `/ready` returns 503 and `/v1`
returns a stable unavailable error. It never exposes local SQLite over the
service boundary.

When `HASNA_INSTRUCTIONS_DATABASE_URL` is set the `/v1` API reads/writes the
shared Postgres **directly** (no local sync/cache in the service) and every
`/v1` request is authenticated with a `@hasna/contracts` API key
(`x-api-key` or `Authorization: Bearer`). Reads need `instructions:read`, writes
need `instructions:write` (an `instructions:*` key satisfies both).

```bash
# apply the schema (idempotent; never clobbers existing tables)
instructions-serve migrate

# mint a key with the SAME signing secret the server verifies with
contracts issue-key --app instructions --scopes 'instructions:*'
```

Env: `HASNA_INSTRUCTIONS_DATABASE_URL` (DSN) and
`HASNA_INSTRUCTIONS_API_SIGNING_KEY` (HMAC signing secret; `HASNA_API_SIGNING_KEY`
and `API_KEY_SIGNING_SECRET` are also accepted). Client apps never carry a DSN —
they resolve `HASNA_INSTRUCTIONS_API_KEY` (or the Keychain / credentials-file
tiers) through the one `@hasna/contracts` client resolver, and the authority
defaults to the fleet gateway `https://api.hasna.com/instructions`.

Collection responses are producer-bounded with `limit`/`cursor` envelopes.
Current clients follow every page; `view=identity` returns an allowlisted
metadata-only projection for configs, profiles, and machines, without loading
instruction content or private profile fields. Retryable create and binding
writes may send `Idempotency-Key`; PostgreSQL stores the authenticated
principal, operation, canonical request digest, and first committed response in
the same transaction as the domain mutation. A same-body retry replays that
response, while key reuse with different bytes returns
`409 IDEMPOTENCY_KEY_REUSED`.

## SDK

The importable module ships INSIDE `@hasna/instructions` at the `./sdk` export
(one package per app — there is no split `@hasna/instructions-sdk`). It is a
self-contained bundle (node builtins only; the `@hasna/contracts` resolver is
inlined at build time). The versioned `InstructionsV1Client` is generated from
the serve OpenAPI document (`bun run generate:sdk` → `src/sdk/v1.generated.ts`).

```typescript
import { createInstructionsV1ClientFromEnv } from "@hasna/instructions/sdk";

const client = createInstructionsV1ClientFromEnv(); // Keychain -> disk -> HASNA_INSTRUCTIONS_API_KEY
const { configs = [] } = await client.listConfigs({ category: "rules" });
```

`createInstructionsV1ClientFromEnv` resolves the credential through the same
`@hasna/contracts` chain as the CLI and the MCP server — fresh on every
request, so a rotated key heals a long-lived client without rebuilding it.
`resolveInstructionsSdkTransport(options)` reports WHICH tier supplied the
credential (never the value). An explicit `baseUrl` requires an explicit
`apiKey` (hasna/apps#1794): the SDK never attaches the machine's fleet key to
an authority the caller chose itself. No credential anywhere throws; there is
no local mode and no unauthenticated client on this surface.

## Client transports

Every CLI command, MCP tool, and SDK method resolves its credential and service
authority through the ONE client resolver in `@hasna/contracts`
(`hasna/apps#1720`) — the package owns no second chain. The tiers, fresh per
call:

| Tier | Credential | Authority |
|---|---|---|
| 1 — argument | `--api-key` / `--profile` | explicit `baseUrl` (SDK only) |
| 2 — env pointer | `HASNA_INSTRUCTIONS_API_KEY_OVERRIDE`, `HASNA_PROFILE`, `HASNA_INSTRUCTIONS_API_KEY_REF` | — |
| 3 — macOS Keychain | `hasna.credentials.instructions.api-key` (account `HASNA_STATION` → `hostname -s` → `$USER`) | `hasna.credentials.instructions.api-url` |
| 4 — disk | `~/.hasna/instructions/config/credentials` (owner-only 0400/0600; `HASNA_HOME` / `HASNA_CONFIG_HOME` move the root) | same file's `HASNA_INSTRUCTIONS_API_URL` |
| 5 — env | `HASNA_INSTRUCTIONS_API_KEY` | `HASNA_INSTRUCTIONS_API_URL` |

With a credential resolved and nothing configuring the authority, the default is
the fleet gateway `https://api.hasna.com/instructions`. The unprefixed
`INSTRUCTIONS_API_URL` / `INSTRUCTIONS_API_KEY` names are accepted for one
release as silent aliases one rung below the canonical `HASNA_INSTRUCTIONS_*`
names.

The two transports behind `ConfigStore`:

- **api** — HTTP `/v1` through the shared authenticated transport
  (`CloudConfigStore`). Runs fail loud with no credential: non-zero exit naming
  the tiers consulted, no SQLite fallback, no local-fallback event.
- **local** — on-box SQLite (`LocalConfigStore`), **opt-in only**:
  `HASNA_INSTRUCTIONS_LOCAL=1`, honoured only when the environment configures
  no authority and no credential. Every local run says `local mode` once on
  stderr. The SQLite half is loaded through a single dynamic import and is
  bundled as a separate chunk, so `dist/cli/index.js` and `dist/mcp/index.js`
  ship with zero `bun:sqlite` references and a hosted run never loads it.

Retired locations (`~/.hasna/fleet-env`, `~/.hasna/cloud`, `~/.config/hasna`,
`$XDG_CONFIG_HOME`, a `~/.instructions/config.json` key store) are inputs
nowhere, and no `*_MODE` / `*_STORAGE_MODE` switch selects anything — the
transport is decided by what resolves, never by a mode word.

Clients never hold a database DSN. The raw Postgres connection is a server-only
concern (`instructions-serve`), selected by `HASNA_INSTRUCTIONS_DATABASE_URL`.



## Domain export and import

`instructions export` writes a restorable Instructions domain archive v2. The
archive contains config content and every retained config snapshot, profiles,
ordered profile membership and config-binding metadata, profile asset bindings,
and registered machines. Relationships are recorded by stable config/profile
slugs and machine hostnames, so import can map them safely onto destination IDs.

```bash
instructions export --output ./instructions-domain.tar.gz
instructions import ./instructions-domain.tar.gz # exact recovery into an empty destination
```

V2 is deliberately a recovery format, not a merge format. Import validates the
complete archive and requires a destination with zero configs, profiles, and
machines before the first mutation. `--overwrite` is rejected for v2 before
destination inspection or mutation. Any validation, mutation, or readback
failure throws and makes the CLI exit nonzero; discard that attempted
destination and retry from a newly empty database.

The manifest contains deterministic per-collection counts and SHA-256 logical
hashes for pre/post deployment comparison without placing instruction content
in logs. Exact deployment integrity includes config `created_at`, `updated_at`,
and `synced_at`; snapshot `created_at`; profile `created_at` and `updated_at`;
and machine `created_at` plus the exact `last_applied_at` value. After recovery,
import reads the complete domain back through `ConfigStore` and verifies every
field that interface can reproduce: config data and versions, retained snapshot
contents, profiles, ordered bindings, asset mappings, machines, and whether a
machine was ever applied.

`ConfigStore` cannot assign archived config/profile/snapshot/machine creation or
update timestamps, nor an exact machine `last_applied_at`; generated destination
IDs are also intentionally remapped by stable slugs and hostnames. These values
remain protected in the archive's exact integrity hashes but are not recreated
by recovery. ConfigStore also has no cross-entity transaction, so an operational
failure may leave a partial destination; the nonzero result is terminal and that
destination must not be reused. Legacy v1 config-only archives remain importable,
including their historical skip/overwrite conflict behavior.

API keys and idempotency receipts are intentionally excluded: they are security
and transport state that must be provisioned independently. Feedback is also
excluded because it is product telemetry, not part of the Instructions
configuration domain.

## Native S3 backup storage

S3 is an **adjunct immutable backup plane**, never a database selector. SQLite
remains the explicit local authority and PostgreSQL remains the hosted `/v1`
authority. Setting S3 variables alone never opens SQLite, never selects HTTP,
and never changes CRUD routing.

```bash
export HASNA_INSTRUCTIONS_S3_BUCKET=your-private-bucket
export HASNA_INSTRUCTIONS_AWS_REGION=us-east-1
# Optional: HASNA_INSTRUCTIONS_S3_PREFIX (default: instructions/)
# Optional local/S3-compatible endpoint and explicit static credentials.

instructions storage status --json
instructions export --output ./instructions-backup.tar.gz
instructions storage backup push ./instructions-backup.tar.gz   --id 2026-09-15-pre-deploy --dry-run --json
instructions storage backup push ./instructions-backup.tar.gz   --id 2026-09-15-pre-deploy --json
instructions storage backup verify 2026-09-15-pre-deploy --json
instructions storage backup pull 2026-09-15-pre-deploy   --output ./restored-instructions.tar.gz --json
```

Each backup uses traversal-safe deterministic keys, an atomically created
payload, and an atomically created manifest containing SHA-256, byte size,
content type, and creation time. Native S3 creation uses `If-None-Match: *`;
concurrent different-byte writers cannot replace the winner, identical replay
is idempotent, and an injected store without conditional-create support fails
closed. Pulls verify the payload before an owner-only local file is written.
Production backup buckets must enable versioning, encryption, public-access
blocking, and S3 Object Lock with a default retention period. AWS runtime
credentials may come from Bun's standard AWS chain (including an ECS task role);
explicit static credentials are optional and must be a complete pair. Status
never prints credential values or the bucket name.

The production deployment runs the immutable predeploy backup push and verify
as direct Bun commands in the exact candidate image. The GitHub runner alone
parses the push receipt, then supplies the recorded payload and manifest version
IDs to verification; it also checks the receipt digest and size, COMPLIANCE
Object Lock retention for both versions, and an exact versioned-payload
readback. Every backup gate completes before any ECR push, database migration,
or ECS update.

The public importable surface is available at `@hasna/instructions/storage`.

## Data Directory

Local data (the SQLite store and backups) lives under the configs store home,
resolved by the in-package resolver. The legacy `~/.hasna/instructions/` default
(with the `HASNA_CONFIGS_HOME` exact-app override) stays the effective store
home until the store has actually been migrated to the XDG config home
(`~/.config/hasna/configs` on Linux; `~/Library/Application
Support/Hasna/configs` on macOS) or the operator sets the config-kind override
`HASNA_CONFIG_HOME` — an existing local store never becomes invisible on
upgrade.

## Session Instruction Rendering

`instructions session plan` and `instructions session apply` render
OpenIdentities and instruction sources into provider-native files for Claude,
Codex, Cursor, OpenCode, Sumi, Codewith, Qwen, 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.

Sumi renders one flattened `AGENTS.md`. Pass its actual config directory from
`sumi debug paths config` as `--target-home`, or use `--project-root` for
repository instructions. Global plans return `SUMI_CONFIG_DIR`; project plans
do not change the global config environment. No OpenCode config or fragment
imports are written. See [session rendering](docs/session-rendering.md) for
capability limits and examples. New project renders can use `--sumi-project`
to preserve another provider's common manifest and keep Sumi ownership in
`.hasna/session-render/sumi/manifest.json`; refresh them with `--tool sumi`.
Existing shared-manifest Sumi projects retain their original flags and refresh
path. See [Sumi project ownership](docs/session-rendering.md#sumi-project-ownership).

A compiled Sumi project profile can optionally add a managed Claude `CLAUDE.md`
that imports the same `AGENTS.md`, using `--claude-project-import 2.1.278`.
Both providers must explicitly select the same immutable sources and prose.
The companion shares the project's manifest, snapshots and refresh transaction;
see [shared project instructions](docs/session-rendering.md#shared-project-instructions-for-sumi-and-claude).

Claude can also own a project render directly with `--claude-project`. Its
root `CLAUDE.md` contains the selected instructions inline for descendant
working directories. Retained provenance fragments and the manifest use the
project-owned `.hasna/session-render/claude/` namespace, so a project `.claude` directory
used as the physical global Claude home is never traversed. OpenCode's generic project manifest and
an unrelated retained `AGENTS.md` remain independently owned; see
[Claude project instructions](docs/session-rendering.md#claude-project-instructions).
First Claude and OpenCode project renders produce exact created-file rollback
snapshots. The Codex project-hook installer can also pin one Projects root/ID,
persist a reviewed document-byte budget, return partial-effect receipts, and
restore only its receipt-owned native fields through guarded CAS operations.

Qwen Code session rendering writes `QWEN.md` instructional context with
`QWEN_HOME` pointing at the rendered profile home. Known config sync also
tracks Qwen Code `QWEN.md` and `settings.json` files at `~/.qwen/...` and
project `QWEN.md` / `.qwen/settings.json`, so native hook settings can be
managed without claiming session-rendered context is hard enforcement.

```bash
instructions session plan \
  --tool codewith \
  --profile account999 \
  --identity-export ./instructions.json \
  --source project:repo-rules=./CODEWITH.md \
  --json

instructions session apply \
  --tool codex \
  --profile account999 \
  --identity-export ./instructions.json
```

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.

Grok Build and Devin CLI can render global instructions into an explicitly
selected native home. Pass Grok's `GROK_HOME` directory or Devin's resolved user
config directory as `--target-home`; the renderer writes a flattened `AGENTS.md`
and its ownership manifest there. Devin's target is the `devin` config directory,
not its parent `XDG_CONFIG_HOME` or its credential-data directory.

```bash
instructions session plan --tool grok --profile work \
  --target-home "$GROK_HOME" --identity-export ./instructions.json --json
instructions session apply --tool devin --profile work \
  --target-home /absolute/isolated/config/devin --identity-export ./instructions.json
```

An explicit `--project-root` keeps the project renderer: Grok writes repository
`AGENTS.md`; Devin writes `.devin/rules/*.md`. Without either explicit target,
these providers remain blocked. Compiled native profiles select the
`native-profile` capability (Grok 1.0.13+, Devin 3000.10.21+); conditional bindings
require an explicit supported fallback because global `AGENTS.md` is always on.
The existing conflict, drift, symlink and snapshot checks also apply to native homes.
See [Devin global rules](https://docs.devin.ai/cli/extensibility/rules) and
[Grok settings](https://docs.x.ai/build/settings) for native home configuration.

### Session renderer ownership

Anything the session renderer writes is off limits to `instructions apply` and
`instructions sync --to-disk`; those rows are reported as `[owned]` and skipped
instead of overwriting rendered output. Ownership is decided from the renderer's
own definitions rather than a hand-maintained path list:

- **Managed directories** — every `managedDir` in `SESSION_TOOL_ADAPTERS`, matched
  on whole path segments under any target home (`~/.claude/.hasna/instructions/…`,
  `~/.hasna/accounts/profiles/claude/account003/.hasna/instructions/…`, …), plus
  the renderer's `.hasna/session-render-manifest.json` and
  `.hasna/session-render-snapshots/`.
- **Manifest claims** — any file listed in an ancestor target home's
  `.hasna/session-render-manifest.json`. This covers provider-native output such
  as `CLAUDE.md`, `AGENTS.md`, and `opencode.json` exactly where the renderer
  actually rendered.

`~/.cursor/rules` is deliberately *not* statically reserved: the config fan-out
writes `cursor-mdc` transform outputs there too, so files in that directory are
owned only when a manifest claims them.

Renumbering a source (inserting a new lower-order source shifts `01-` → `02-`
and so on) orphans older `target_path` values that still point inside a managed
directory. The guard is what neutralises those orphans, so stale rows are inert
rather than destructive.

Applying a config into renderer-owned space requires the explicit opt-in flag:

```bash
instructions apply <id> --allow-renderer-owned
```

It is separate from `--force` on purpose — "overwrite even if unchanged" must
never silently become "overwrite renderer-owned instruction files".

### Managed project context

`instructions project-context plan|apply` is the sole writer for the strict
`hasna.projects.project_context_bundle.v1`, `v2`, and `v3` contracts emitted by Projects. v3
uses Projects' explicit `authority.transport` (`http` or `local`) instead of the
legacy `mode`/`storage` pair. The renderer preserves the producer's schema and
canonical hash in the cache and session provenance; mixed authority shapes are
rejected. The registry project path and station context are source facts, not
assertions about the local workspace selected by `--workspace-root`. It
accepts bounded structured JSON from a regular file or stdin and never invokes
Projects, Todos, Conversations, or Mementos while rendering:

```bash
projects context-bundle <project-id> --json > ./project-context.json
instructions project-context plan \
  --runtime codewith \
  --workspace-root /absolute/workspace \
  --bundle ./project-context.json \
  --json
instructions project-context apply \
  --runtime codewith \
  --workspace-root /absolute/workspace \
  --bundle ./project-context.json \
  --json
```

The renderer writes one canonical `.hasna/instructions/project-context.md`
fragment, then a managed import in `CLAUDE.md`, a managed inline block in
`AGENTS.md`, and a managed inline block in `.codewith/CODEWITH.md` by default.
Codewith uses an import only when its existing
`HASNA_CONFIGS_CODEWITH_NATIVE_IMPORTS=1` capability gate is active or
`--codewith-native-imports` explicitly selects that supported runtime mode.
Bytes outside the managed marker pair are preserved. Codewith's
`.codewith/CODEWITH.override.md` takes precedence and causes the stable
`PROJECT_CONTEXT_SHADOWED` failure instead of an ignored write.
Existing Instructions session manifests are updated additively, and later
`instructions session plan|apply` runs recompose the validated durable project
context so routine Claude, Codewith, or Codex rerenders cannot discard it.

Input is limited to 8 KiB, output to 4 KiB and six allowlisted argv commands.
The writer rejects unknown fields, hash/revision inconsistencies, credentials,
URLs, symlinks, malformed or conflicting markers, and older revisions. Applies
use a per-workspace lock, compare-and-swap hashes, same-directory fsynced temp
files and renames, and a metadata-only manifest written last. Existing-file
updates require an atomic exchange primitive (Linux `renameat2` or macOS
`renameatx_np`); unsupported platforms, including Windows, fail closed before
replacement rather than approximating an exchange. A same-project,
compatible last-known-good cache can be selected explicitly with
`--allow-stale-cache --expected-project-id <id>`; its bounded age/status is
visible in the rendered context.

Projects v2 bundles may carry the strict optional
`hasna.projects.finance_project_metadata.v1` object. Instructions validates
that object and preserves every accepted finance field through the bundle
cache and session-manifest provenance path; malformed finance metadata and
finance attached to a legacy v1 bundle fail closed.

Compatibility remains additive: project-context manifests keep
`hasna.configs.session-render/v1`, `Managed by @hasna/configs`, and
`ownedBy: open-configs`, while recording `canonicalOwner: instructions`. The
legacy `@hasna/configs` 0.2.45 `configs` executable remains available as a bin
alias; no separate Configs repository or flag-day manifest v2 is introduced.

`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, 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.

They also seed `dangerous-operation-guard-standard`, the managed guard source
for risky shell commands, edits, git operations, package installs, and
secret-adjacent access. The guard excludes Gemini CLI, requires Codewith/Codex
`PreToolUse` to hard-deny or inject context rather than ask for approval, uses
`PermissionRequest` for Codewith/Codex approvals, records that Qwen `QWEN.md`
is policy context only, and records native hook or wrapper/plugin fallback
expectations for Claude, Qwen, OpenCode, Cursor, and Antigravity.

The Codewith-only `codewith-shared-todos-storage-standard` requires every
operational repository, work or research project, coordinator, worker, and loop
to use the managed shared Todos route. On-box SQLite or file Todos storage is limited
to disposable repository tests. Projects linkage and sanitized server-route
verification are required before mutation; unavailable routing fails closed,
and prior on-box-only evidence must be explicitly reconciled before use.

## Guarded profile unbinding

Guarded unbinding uses the transactional HTTP API. It removes one profile binding,
retaining the source, version history, unrelated members, raw binding metadata and
all original sort orders. Explicit local SQLite supports the legacy API but refuses
this new capability rather than falling back to an unconditional removal.

Preserve and review a complete original before executing a removal. Use new private
files in an existing owner-only directory:

```bash
instructions profile unbinding-state <profile> <source-id> --output <private-original.json>
instructions profile unbind --state-file <private-original.json> --operation-key <stable-remove-key> --receipt-file <private-remove-result.json>
```

The CLI reads back the original and persists the exact request before sending.
The source version/content digest and complete source/profile/membership digest
must still match inside the same transaction. The state includes selectors,
variables, every config/asset binding and order; it is complete or refused
(2,048 config bindings, 1,024 asset bindings, 1 MiB canonical payload).
The API returns the preserved before/after state, original binding and a `replayed`
flag. After a timeout or lost response, preserve the original key/request and read
its principal-bound receipt; do not issue a fresh-key retry:

```bash
instructions profile removal-receipt <profile-id> <source-id> --operation-key <stable-remove-key> --output <private-reconciled-receipt.json>
```

A missing visible receipt leaves an earlier in-flight outcome unverified. An exact
replay of the same canonical request/key returns the original committed receipt;
a different request under that key is refused. To recover, preserve the current complete state
with `unbinding-state`, then use `profile restore-binding --state-file <private-current.json>
--operation-key <stable-restore-key> --removal-key <stable-remove-key>
--receipt-file <private-restore-result.json>`. Recovery restores only the binding
and exact order recorded by that principal's removal receipt, only while the
source and entire post-removal state remain unchanged. It never rewrites the
profile or overwrites later work automatically. An ambiguous restore is reconciled
without resubmitting it: `profile restore-receipt <profile-id> <source-id>
--operation-key <stable-restore-key> --output <private-reconciled-restore.json>`.

HTTP operations are `GET .../configs/{configId}/unbinding-state`,
`POST .../configs/{configId}/conditional-remove`, its read-only receipt `GET`
with `operation_key`, and `POST .../configs/{configId}/conditional-restore` with
its own read-only receipt `GET`, under
`/v1/profiles/{id}`. Writes require immutable profile/source IDs and all
`expected_version`, `expected_hash`, `expected_state_sha256`, `operation_key`
fields; restore also requires `removal_key`. An optional `Idempotency-Key` header
must match `operation_key`. The MCP tools and generated SDK expose these separate
operations. Older servers refuse them; clients never substitute legacy DELETE.
Legacy `profile remove` remains unconditional and accepts no guard flags; the
current legacy HTTP DELETE refuses request bodies, query fields and conditional headers.
These mechanisms do not grant authority to remove a production binding or replace
the applicable preservation, review and migration gates. Receipt/state files
include source content and profile variables: keep them private.

## Machine-aware Profiles

`instructions init` seeds two empty platform profiles:

- `linux-arm64` for Linux arm64
- `macos-arm64` for macOS arm64

The public presets contain no fleet hostnames or workspace paths. They resolve
portable tool variables such as `{{BUN_BIN_DIR}}`, `{{BUN_PATH}}`, and
`{{PATH_PREFIX}}`. Instruction membership is never bulk-copied from the config
registry; bind reviewed provider/project/role sources explicitly before compiling
a prompt profile.

They also include the project channel variable used by agent-managed project
workflows:

- `{{PROJECT_CHANNEL_PREFIX}}` -> `""` (no prefix; the channel is the normalized project slug)

Existing profiles can be migrated in place without deleting or recreating them:

```bash
instructions profile update linux-arm64 \
  --var PROJECT_CHANNEL_PREFIX= \
  --unset-var LEGACY_VARIABLE
```

`instructions init` and `bun run seed` seed the
`agent-managed-project-dashboard-standard` reference. It documents the canonical
per-workspace store layout (`~/.hasna/projects/workspaces/<workspace_id>/` — the
only project-layout convention), `projects dashboard *` commands, provider panel
commands, normalized project-slug channel naming, durable todos/goal workflow,
and the rule that dashboards must show ids/statuses/evidence refs instead of raw
private documents or secrets.

## License

Apache-2.0 -- see [LICENSE](LICENSE)
