@noir-ai/adapters 1.3.0-beta.6 → 1.4.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/README.md CHANGED
@@ -1,19 +1,19 @@
1
1
  # @noir-ai/adapters
2
2
 
3
- The host abstraction (`HostAdapter`) that decouples Noir from any specific agentic CLI. v1 ships the Claude Code adapter (`.mcp.json` wiring, `CLAUDE.md` `@import`, and `.claude/skills/` emission). Additional hosts arrive in later slices via a `resolveAdapter(host)` registry.
3
+ The host abstraction (`HostAdapter`) that decouples Noir from any specific agentic CLI. Noir ships five adapters through a `resolveAdapter(host)` registry: `claude`, `agents-md`, `gemini`, `cursor`, and `opencode`. They emit each host's native context and MCP artifacts; Claude and Cursor also have skill-emission surfaces.
4
4
 
5
5
  Part of the **[Noir](https://github.com/agaaaptr/noir#readme)** toolkit — the discipline, context, and memory layer for any agentic CLI.
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- npm install @noir-ai/adapters
10
+ npm install @noir-ai/adapters@beta
11
11
  ```
12
12
 
13
13
  > Most users install the CLI instead, which wires the host adapter via `noir init`:
14
14
  >
15
15
  > ```bash
16
- > npm install -g @noir-ai/cli
16
+ > npm install -g @noir-ai/cli@beta
17
17
  > ```
18
18
 
19
19
  ## License
package/dist/index.d.ts CHANGED
@@ -39,6 +39,27 @@ interface IntegrationMcpEmission {
39
39
  url?: string;
40
40
  env?: Record<string, string>;
41
41
  }
42
+ /**
43
+ * The host-facing subset of a Noir handoff artifact. Defined HERE so the
44
+ * optional {@link HostAdapter.emitHandoff} hook has a typed payload without the
45
+ * adapters package taking a CLI dependency — the CLI conforms its richer
46
+ * snapshot to this shape before handing it to the adapter. Every field is
47
+ * nullable so a daemon-down / no-active-task handoff still type-checks.
48
+ */
49
+ interface HandoffPayload {
50
+ project: {
51
+ id: string;
52
+ name: string;
53
+ };
54
+ host: HostId;
55
+ /** Active workflow task, if any. Null when no task is active or daemon-down. */
56
+ task: {
57
+ taskId: string;
58
+ phase: string;
59
+ nextGate: string | null;
60
+ nextSkill: string | null;
61
+ } | null;
62
+ }
42
63
  interface HostAdapter {
43
64
  /** The host identifier — must match a `HostId` registry key. Tightened from
44
65
  * `string` to `HostId` in S10 so the registry is type-safe end-to-end. */
@@ -76,6 +97,14 @@ interface HostAdapter {
76
97
  * it elsewhere. The shared `emitAgentsMd(ctx)` helper produces the CONTENT
77
98
  * (byte-identical across hosts); the cli writes it to this path. */
78
99
  agentsMdPath?(ctx: EmitContext): string;
100
+ /** Host-handoff seam — the host-specific directive block for a Noir handoff
101
+ * artifact (the "Open \`<host>\` …" portion). OPTIONAL so existing / third-party
102
+ * adapters continue to type-check without implementing it; the CLI falls back
103
+ * to {@link hostLaunchDirective} (the generic single-line directive) when this
104
+ * is absent. A host that wants richer handoff wording (e.g. naming its native
105
+ * context file or skill dir) implements this. TEXT ONLY — never launches the
106
+ * host (doctrine: Noir never spawns the host; the directive is pasteable text). */
107
+ emitHandoff?(ctx: EmitContext, payload: HandoffPayload): string;
79
108
  }
80
109
 
81
110
  /** The canonical filename for the universal AGENTS.md emitter. Every host
@@ -104,7 +133,7 @@ declare const AGENTS_MD_FILENAME = "AGENTS.md";
104
133
  * @.noir/NOIR.md
105
134
  * @.noir/rules/RULES.md
106
135
  *
107
- * The inline 3-line fallback (I2) sits BEFORE the `@`-imports so AGENTS.md
136
+ * The inline 3-line fallback sits BEFORE the `@`-imports so AGENTS.md
108
137
  * readers that do NOT resolve `@`-imports (e.g. plain markdown viewers, some
109
138
  * GA dashboards) still get a one-glance summary of where to edit. The
110
139
  * `@`-imports remain canonical for the hosts that DO resolve them (Cursor,
@@ -136,7 +165,7 @@ declare const claudeAdapter: HostAdapter;
136
165
  * for context (same content as every other host); that file's
137
166
  * `@.noir/rules/RULES.md` import IS the Noir working-rules surface for cursor
138
167
  * (NO separate `.cursor/rules/noir-contract.mdc` host-rules pointer — that
139
- * file was REMOVED: it was `noir-`-prefixed and the C3 cursor flat-skill prune
168
+ * file was REMOVED: it was `noir-`-prefixed and the cursor flat-skill prune
140
169
  * in `emitSkillsToDir` deleted it on every `noir init/create/sync --host
141
170
  * cursor`). Skills compile to FLAT `.mdc` in `.cursor/rules/` via
142
171
  * `compileSkill(_, 'cursor')` (one file per skill, no per-name subdir). MCP
@@ -166,6 +195,28 @@ declare const cursorAdapter: HostAdapter;
166
195
  */
167
196
  declare const geminiAdapter: HostAdapter;
168
197
 
198
+ /**
199
+ * One-line host-direction directive (refactored from the home-banner line).
200
+ * Tells the user to open their configured host CLI to do the actual development
201
+ * (Noir is the orchestration/context/memory brain; the host is the execution
202
+ * engine — bring-your-own-agent). Host-agnostic via the {@link HOSTS}
203
+ * registry; lists the alternatives so a multi-host user knows their options.
204
+ *
205
+ * This is the SINGLE source — the home banner AND the handoff artifact both
206
+ * call it so the wording never drifts. A host-specific override lives on
207
+ * {@link HostAdapter.emitHandoff}, not here.
208
+ */
209
+ declare function hostLaunchDirective(host: HostId): string;
210
+ /**
211
+ * The default handoff directive block (multi-line) used when a host adapter
212
+ * does NOT implement {@link HostAdapter.emitHandoff}. Composes the generic
213
+ * {@link hostLaunchDirective} line plus a reminder that the MCP wire is already
214
+ * configured (`.mcp.json` / host equivalent), so the host can call Noir's tools
215
+ * the moment it starts. The CLI passes this as the "Open host" portion of the
216
+ * handoff artifact.
217
+ */
218
+ declare function defaultHandoffBlock(_ctx: EmitContext, payload: HandoffPayload): string;
219
+
169
220
  /**
170
221
  * Build the host MCP config JSON string — the `{mcpServers: {...}}` shape shared
171
222
  * by claude (`.mcp.json`), agents-md (`.mcp.json`), gemini (`.gemini/mcp.json`),
@@ -233,4 +284,4 @@ declare const SUPPORTED_HOSTS: readonly HostId[];
233
284
  */
234
285
  declare function resolveAdapter(host: HostId): HostAdapter;
235
286
 
236
- export { AGENTS_MD_FILENAME, type EmitContext, type HostAdapter, type HostId, type IntegrationMcpEmission, type McpConfigOptions, SUPPORTED_HOSTS, agentsMdAdapter, buildMcpServersJson, claudeAdapter, cursorAdapter, emitAgentsMd, geminiAdapter, opencodeAdapter, resolveAdapter };
287
+ export { AGENTS_MD_FILENAME, type EmitContext, type HandoffPayload, type HostAdapter, type HostId, type IntegrationMcpEmission, type McpConfigOptions, SUPPORTED_HOSTS, agentsMdAdapter, buildMcpServersJson, claudeAdapter, cursorAdapter, defaultHandoffBlock, emitAgentsMd, geminiAdapter, hostLaunchDirective, opencodeAdapter, resolveAdapter };
package/dist/index.js CHANGED
@@ -170,6 +170,18 @@ var opencodeAdapter = {
170
170
  }
171
171
  };
172
172
 
173
+ // src/handoff.ts
174
+ var HOSTS = ["claude", "agents-md", "gemini", "cursor", "opencode"];
175
+ function hostLaunchDirective(host) {
176
+ const others = HOSTS.filter((h) => h !== host);
177
+ return `\u2192 host: ${host}. Open \`${host}\` to start development \u2014 Noir set the rules, skills, and memory; ${host} runs the code. (other hosts: ${others.join(", ")})`;
178
+ }
179
+ function defaultHandoffBlock(_ctx, payload) {
180
+ const directive = hostLaunchDirective(payload.host);
181
+ return `${directive}
182
+ The Noir MCP server is already wired via the host's MCP config \u2014 call \`noir.*\` tools once \`${payload.host}\` is open.`;
183
+ }
184
+
173
185
  // src/index.ts
174
186
  var SUPPORTED_HOSTS = Object.freeze([
175
187
  "claude",
@@ -203,8 +215,10 @@ export {
203
215
  buildMcpServersJson,
204
216
  claudeAdapter,
205
217
  cursorAdapter,
218
+ defaultHandoffBlock,
206
219
  emitAgentsMd,
207
220
  geminiAdapter,
221
+ hostLaunchDirective,
208
222
  opencodeAdapter,
209
223
  resolveAdapter
210
224
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/agents-md-adapter.ts","../src/agents-md.ts","../src/mcp.ts","../src/claude.ts","../src/cursor.ts","../src/gemini.ts","../src/opencode.ts","../src/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `agents-md` host adapter — the 32-platform universal AGENTS.md standard.\n * The SMALLEST surface: a root `AGENTS.md` (context + rules unified — the\n * `@.noir/` imports already cover both NOIR.md and RULES.md), no skill concept\n * (the context IS the surface), and a workspace `.mcp.json` (the Claude shape —\n * broadly compatible; many AGENTS.md readers also read it).\n *\n * This adapter is the fallback / universal baseline — any host that \"just reads\n * AGENTS.md\" (incl. qwen/agy and other deferred hosts) behaves identically to\n * this. Per-host specialization lives in the other adapters' native files.\n */\nexport const agentsMdAdapter: HostAdapter = {\n id: 'agents-md',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // The universal AGENTS.md content — byte-identical across every adapter\n // that composes `emitAgentsMd` (agents-md / gemini / cursor / opencode).\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN the AGENTS.md content already (the\n // `@.noir/rules/RULES.md` import covers them).\n // No `skillsDir` — no skill concept for the universal host.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { basename } from 'node:path';\nimport type { EmitContext } from './types.js';\n\n/** The canonical filename for the universal AGENTS.md emitter. Every host\n * writes the SAME content (single source of truth) — adapters may place it at\n * different paths via `HostAdapter#agentsMdPath`, but the filename is constant. */\nexport const AGENTS_MD_FILENAME = 'AGENTS.md';\n\n/**\n * The universal AGENTS.md emitter — a SHARED HELPER composed by every adapter\n * (NOT a HostAdapter itself). AGENTS.md is now the cross-tool standard (32+\n * platforms read it natively — Claude Code, OpenAI Codex, Cursor, Gemini CLI,\n * JetBrains Junie, …), so the SAME file content works for every host: it\n * `@`-imports the canonical `.noir/` sources. Per-host specialization lives in\n * each adapter's OWN native files (CLAUDE.md, GEMINI.md, .cursor/rules/*.mdc,\n * opencode.json); AGENTS.md is the always-emitted universal baseline.\n *\n * Content (byte-identical across hosts):\n *\n * # <projectName> — Noir-managed agent context\n *\n * > Generated by `noir`. Canonical source lives in `.noir/`. Edit there; re-run `noir sync`.\n *\n * > Noir manages this project's context under `.noir/`. Edit `.noir/NOIR.md`\n * > (project brief) + `.noir/rules/RULES.md` (working contract); re-run\n * > `noir sync` to re-emit host files.\n *\n * @.noir/NOIR.md\n * @.noir/rules/RULES.md\n *\n * The inline 3-line fallback (I2) sits BEFORE the `@`-imports so AGENTS.md\n * readers that do NOT resolve `@`-imports (e.g. plain markdown viewers, some\n * GA dashboards) still get a one-glance summary of where to edit. The\n * `@`-imports remain canonical for the hosts that DO resolve them (Cursor,\n * Codex, Junie, …).\n *\n * `<projectName>` is derived from `ctx.root`'s basename — a best-effort label\n * for human readability. If the basename is empty/undeterminable, falls back to\n * the literal `project` so the heading is never malformed.\n */\nexport function emitAgentsMd(ctx: EmitContext): string {\n const projectName = deriveProjectName(ctx.root);\n return (\n `# ${projectName} — Noir-managed agent context\\n\\n` +\n `> Generated by \\`noir\\`. Canonical source lives in \\`.noir/\\`. Edit there; re-run \\`noir sync\\`.\\n\\n` +\n `> Noir manages this project's context under \\`.noir/\\`. Edit \\`.noir/NOIR.md\\` ` +\n `(project brief) + \\`.noir/rules/RULES.md\\` (working contract); re-run \\`noir sync\\` ` +\n `to re-emit host files.\\n\\n` +\n `@.noir/NOIR.md\\n` +\n `@.noir/rules/RULES.md\\n`\n );\n}\n\n/** Derive a human-readable project label from the root path's basename.\n * Trims trailing slashes (POSIX) so `/p/demo/` → `demo`; falls back to the\n * literal `project` when the basename is empty (e.g. root was `/` or `''`). */\nfunction deriveProjectName(root: string): string {\n // `basename` already handles trailing slashes on POSIX; guard the empty case.\n const name = basename(root.replace(/\\/+$/, '')) || basename(root);\n return name && name.length > 0 ? name : 'project';\n}\n","import type { IntegrationMcpEmission, McpConfigOptions } from './types.js';\n\n/**\n * Build the host MCP config JSON string — the `{mcpServers: {...}}` shape shared\n * by claude (`.mcp.json`), agents-md (`.mcp.json`), gemini (`.gemini/mcp.json`),\n * and cursor (`.cursor/mcp.json`). OpenCode uses a DIFFERENT shape (an `mcp`\n * block with `type`-tagged entries) and does NOT use this helper — see\n * `opencode.ts`.\n *\n * The Noir server entry is always present; an optional integration entry merges\n * alongside it (per the Slice X adapter contract — only `external-mcp`\n * integrations surface a `hostMcp` block by the time it reaches here).\n *\n * Refactored out of `claude.ts` in S10-Adapters so every `{mcpServers}`-shape\n * host emits byte-identical JSON. Claude's `emitMcpConfig` now delegates here\n * (the claude.test.ts + create/scaffold.test.ts parity gates must hold).\n *\n * Stdio entry: `{ command, args }`.\n * HTTP entry : `{ type: 'http', url, [env] }` — env nested under `env:`, never\n * spread at the entry top level (would corrupt the server-field shape).\n */\nexport function buildMcpServersJson(\n opts: McpConfigOptions,\n integration?: IntegrationMcpEmission,\n): string {\n // Always present: the Noir MCP server — `noir mcp serve --stdio` or the\n // streamable-http endpoint. The placeholder URL (`:0`) is a best-effort hint\n // the user edits; same behavior as the original claude implementation.\n const noirServer =\n opts.transport === 'stdio'\n ? { command: 'noir', args: ['mcp', 'serve', '--stdio'] }\n : { type: 'http', url: opts.url ?? 'http://127.0.0.1:0/mcp' };\n\n const mcpServers: Record<string, unknown> = { noir: noirServer };\n if (integration) {\n const entry =\n integration.transport === 'http'\n ? {\n type: 'http',\n url: integration.url ?? '',\n // Nest env under `env:` (NOT spread at the entry top level) so the\n // emitted shape matches the stdio branch + Claude's spec. Top-level\n // spread would leak env keys as server fields.\n ...(integration.env ? { env: integration.env } : {}),\n }\n : {\n command: integration.command,\n ...(integration.args ? { args: integration.args } : {}),\n ...(integration.env ? { env: integration.env } : {}),\n };\n mcpServers[integration.serverName] = entry;\n }\n return JSON.stringify({ mcpServers }, null, 2);\n}\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\nexport const claudeAdapter: HostAdapter = {\n id: 'claude',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // Delegates to the shared `{mcpServers}` builder. Behavior-identical to the\n // pre-S10 inline implementation — the claude.test.ts + create/scaffold.test.ts\n // byte-equality parity gates must hold (Slice X integration merge preserved).\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n return `${CONTEXT_BLOCK_BEGIN}\\n@import \".noir/NOIR.md\"\\n${CONTEXT_BLOCK_END}\\n`;\n },\n emitRules(_ctx: EmitContext): string {\n return `${RULES_BLOCK.begin}\\n@import \".noir/rules/RULES.md\"\\n${RULES_BLOCK.end}\\n`;\n },\n skillsDir(ctx: EmitContext): string {\n return join(ctx.root, '.claude', 'skills');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `cursor` host adapter — Cursor. Cursor reads the universal `AGENTS.md`\n * for context (same content as every other host); that file's\n * `@.noir/rules/RULES.md` import IS the Noir working-rules surface for cursor\n * (NO separate `.cursor/rules/noir-contract.mdc` host-rules pointer — that\n * file was REMOVED: it was `noir-`-prefixed and the C3 cursor flat-skill prune\n * in `emitSkillsToDir` deleted it on every `noir init/create/sync --host\n * cursor`). Skills compile to FLAT `.mdc` in `.cursor/rules/` via\n * `compileSkill(_, 'cursor')` (one file per skill, no per-name subdir). MCP\n * config lands at `.cursor/mcp.json`.\n *\n * `emitContext` returns the universal AGENTS.md content (the single native\n * context surface for cursor). There is NO `emitRules` here — cursor's rules\n * are delivered via AGENTS.md's `@.noir/rules/RULES.md` import, identical to\n * agents-md/opencode.\n */\nexport const cursorAdapter: HostAdapter = {\n id: 'cursor',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // Cursor reads AGENTS.md — same universal content as every other host.\n return emitAgentsMd(ctx);\n },\n skillsDir(ctx: EmitContext): string {\n // Skills compile to `.mdc` here (via `compileSkill(_, 'cursor')` in skills).\n return join(ctx.root, '.cursor', 'rules');\n },\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.cursor', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `gemini` host adapter — Gemini CLI. Emits `GEMINI.md` (Gemini's native\n * context file) carrying BOTH the context block AND the rules block — Gemini\n * has no separate rules file. Uses Gemini's `@file` import syntax: bare\n * `@.noir/NOIR.md` (no `@import` keyword, no quotes — distinct from Claude's\n * `@import \".noir/...\"` form). The cli ALSO emits a root `AGENTS.md` via the\n * shared `emitAgentsMd` helper (Gemini reads AGENTS.md too).\n *\n * Managed-block form (CONTEXT_BLOCK + RULES_BLOCK markers) is used so user\n * content outside the markers survives `noir sync` rewrites — consistent with\n * the claude adapter's contract.\n *\n * MCP config lands at `.gemini/mcp.json` (workspace-level — the portable\n * choice; `~/.gemini/settings.json` is the global alternative, documented).\n */\nexport const geminiAdapter: HostAdapter = {\n id: 'gemini',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n // Gemini's `@file` import: bare `@.noir/...` (no `@import`, no quotes).\n // Rules folded into the same GEMINI.md (per spec — Gemini has no separate\n // rules file). Both blocks are marker-wrapped so user content survives sync.\n return (\n `${CONTEXT_BLOCK_BEGIN}\\n@.noir/NOIR.md\\n${CONTEXT_BLOCK_END}\\n` +\n `${RULES_BLOCK.begin}\\n@.noir/rules/RULES.md\\n${RULES_BLOCK.end}\\n`\n );\n },\n // No `emitRules` — rules folded into emitContext (GEMINI.md carries both).\n // No `skillsDir` — no skill concept; GEMINI.md + AGENTS.md are the surface.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.gemini', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `opencode` host adapter — OpenCode. Reads the universal `AGENTS.md`\n * (context + rules unified — no separate rules emission, no skill concept).\n *\n * The DIFFERENCE from the `{mcpServers}` family lives in MCP config: OpenCode's\n * root `opencode.json` carries an `mcp` block whose entries are `type`-tagged —\n * `{ type: 'local', command: [...] }` for stdio (note: `command` is an ARRAY,\n * not the claude `{command, args}` split) and `{ type: 'remote', url }` for\n * remote (HTTP/SSE). Server env vars land under the `environment` key (NOT\n * `env`). The `$schema` key pins the opencode config schema. See\n * https://opencode.ai/docs/mcp-servers/ + https://opencode.ai/config.json.\n *\n * Verified verbatim from the opencode docs (\"Add remote MCP servers by setting\n * `type` to `\\\"remote\\\"`\"); the prior `type:'http'` / `env:` shape was wrong.\n */\nexport const opencodeAdapter: HostAdapter = {\n id: 'opencode',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // OpenCode's `mcp` block — entries carry an explicit `type` tag (verified\n // against https://opencode.ai/docs/mcp-servers/):\n // - stdio → { type: 'local', command: [...] } (command is an ARRAY)\n // - remote → { type: 'remote', url: ... } (HTTP/SSE — NOT 'http')\n // Server env vars land under `environment:` (NOT `env:` — opencode's own\n // spelling). Noir server always present; optional integration merges\n // alongside under its `serverName`. (Does NOT use buildMcpServersJson —\n // different shape; that helper's `env:` is correct for `.mcp.json` /\n // `.cursor/mcp.json` / `.gemini/mcp.json`.)\n const mcp: Record<string, unknown> = {\n noir:\n opts.transport === 'stdio'\n ? { type: 'local', command: ['noir', 'mcp', 'serve', '--stdio'] }\n : { type: 'remote', url: opts.url ?? 'http://127.0.0.1:0/mcp' },\n };\n if (integration) {\n mcp[integration.serverName] =\n integration.transport === 'http'\n ? {\n type: 'remote',\n url: integration.url ?? '',\n ...(integration.env ? { environment: integration.env } : {}),\n }\n : {\n type: 'local',\n command: [integration.command, ...(integration.args ?? [])],\n ...(integration.env ? { environment: integration.env } : {}),\n };\n }\n return JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp }, null, 2);\n },\n emitContext(ctx: EmitContext): string {\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN AGENTS.md (the @-import covers RULES.md).\n // No `skillsDir` — no skill concept.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, 'opencode.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { agentsMdAdapter } from './agents-md-adapter.js';\nimport { claudeAdapter } from './claude.js';\nimport { cursorAdapter } from './cursor.js';\nimport { geminiAdapter } from './gemini.js';\nimport { opencodeAdapter } from './opencode.js';\nimport type { HostAdapter, HostId } from './types.js';\n\nexport { AGENTS_MD_FILENAME, emitAgentsMd } from './agents-md.js';\nexport { agentsMdAdapter } from './agents-md-adapter.js';\nexport { claudeAdapter } from './claude.js';\nexport { cursorAdapter } from './cursor.js';\nexport { geminiAdapter } from './gemini.js';\nexport { buildMcpServersJson } from './mcp.js';\nexport { opencodeAdapter } from './opencode.js';\nexport type {\n EmitContext,\n HostAdapter,\n HostId,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/** The readonly list of supported hosts — derived from the `HostId` union so it\n * stays in lockstep with the type. Consumers (cli `--host` flag, doctor\n * reporting) use this for \"is this host valid?\" + iteration. Order is the\n * declaration order in `HostId` (claude first — the default).\n *\n * `Object.freeze` enforces the `readonly` type at runtime — a stray\n * `SUPPORTED_HOSTS.push('qwen')` from a JS caller fails loudly instead of\n * silently corrupting the registry list (TS already prevents it in typed code). */\nexport const SUPPORTED_HOSTS: readonly HostId[] = Object.freeze([\n 'claude',\n 'agents-md',\n 'gemini',\n 'cursor',\n 'opencode',\n]);\n\n/**\n * Resolve a host id to its `HostAdapter`. The registry is a `Record<HostId,\n * HostAdapter>` so the type system enforces completeness — adding a host to\n * `HostId` requires wiring it here (TS errors otherwise). The CLI uses this\n * indirection instead of importing adapters directly so adding a host needs NO\n * CLI edits beyond the `--host` flag's enum.\n *\n * S10-Adapters: all five hosts are now wired —\n * - `claude` — CLAUDE.md + `.claude/skills/` + `.mcp.json` (regression anchor).\n * - `agents-md` — universal AGENTS.md (the 32-platform baseline).\n * - `gemini` — GEMINI.md + AGENTS.md + `.gemini/mcp.json`.\n * - `cursor` — AGENTS.md + `.cursor/rules/*.mdc` + `.cursor/mcp.json`.\n * - `opencode` — AGENTS.md + `opencode.json` (different MCP shape).\n *\n * Unknown/non-`HostId` strings are impossible to pass at compile time (the\n * signature accepts only `HostId`); the runtime fallback is defensive — a\n * JS caller ignoring types still gets a clear error, not a silent `undefined`.\n */\nexport function resolveAdapter(host: HostId): HostAdapter {\n switch (host) {\n case 'claude':\n return claudeAdapter;\n case 'agents-md':\n return agentsMdAdapter;\n case 'gemini':\n return geminiAdapter;\n case 'cursor':\n return cursorAdapter;\n case 'opencode':\n return opencodeAdapter;\n default: {\n // Exhaustiveness guard — if `HostId` gains a member and this switch is\n // not updated, TS narrows `host` to `never` here. At runtime (untyped JS\n // callers) we still surface a clear message.\n const _exhaustive: never = host;\n throw new Error(`Unsupported host: ${String(_exhaustive)} (not in registry)`);\n }\n }\n}\n"],"mappings":";AAAA,SAAS,YAAY;;;ACArB,SAAS,gBAAgB;AAMlB,IAAM,qBAAqB;AAkC3B,SAAS,aAAa,KAA0B;AACrD,QAAM,cAAc,kBAAkB,IAAI,IAAI;AAC9C,SACE,KAAK,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQpB;AAKA,SAAS,kBAAkB,MAAsB;AAE/C,QAAM,OAAO,SAAS,KAAK,QAAQ,QAAQ,EAAE,CAAC,KAAK,SAAS,IAAI;AAChE,SAAO,QAAQ,KAAK,SAAS,IAAI,OAAO;AAC1C;;;ACvCO,SAAS,oBACd,MACA,aACQ;AAIR,QAAM,aACJ,KAAK,cAAc,UACf,EAAE,SAAS,QAAQ,MAAM,CAAC,OAAO,SAAS,SAAS,EAAE,IACrD,EAAE,MAAM,QAAQ,KAAK,KAAK,OAAO,yBAAyB;AAEhE,QAAM,aAAsC,EAAE,MAAM,WAAW;AAC/D,MAAI,aAAa;AACf,UAAM,QACJ,YAAY,cAAc,SACtB;AAAA,MACE,MAAM;AAAA,MACN,KAAK,YAAY,OAAO;AAAA;AAAA;AAAA;AAAA,MAIxB,GAAI,YAAY,MAAM,EAAE,KAAK,YAAY,IAAI,IAAI,CAAC;AAAA,IACpD,IACA;AAAA,MACE,SAAS,YAAY;AAAA,MACrB,GAAI,YAAY,OAAO,EAAE,MAAM,YAAY,KAAK,IAAI,CAAC;AAAA,MACrD,GAAI,YAAY,MAAM,EAAE,KAAK,YAAY,IAAI,IAAI,CAAC;AAAA,IACpD;AACN,eAAW,YAAY,UAAU,IAAI;AAAA,EACvC;AACA,SAAO,KAAK,UAAU,EAAE,WAAW,GAAG,MAAM,CAAC;AAC/C;;;AFhCO,IAAM,kBAA+B;AAAA,EAC1C,IAAI;AAAA,EACJ,cAAc,MAAM,MAAwB,aAA8C;AACxF,WAAO,oBAAoB,MAAM,WAAW;AAAA,EAC9C;AAAA,EACA,YAAY,KAA0B;AAGpC,WAAO,aAAa,GAAG;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAIA,cAAc,KAA0B;AACtC,WAAO,KAAK,IAAI,MAAM,WAAW;AAAA,EACnC;AAAA,EACA,aAAa,KAA0B;AACrC,WAAO,KAAK,IAAI,MAAM,WAAW;AAAA,EACnC;AACF;;;AGxCA,SAAS,QAAAA,aAAY;AACrB,SAAS,qBAAqB,mBAAmB,mBAAmB;AAS7D,IAAM,gBAA6B;AAAA,EACxC,IAAI;AAAA,EACJ,cAAc,MAAM,MAAwB,aAA8C;AAIxF,WAAO,oBAAoB,MAAM,WAAW;AAAA,EAC9C;AAAA,EACA,YAAY,MAA2B;AACrC,WAAO,GAAG,mBAAmB;AAAA;AAAA,EAA8B,iBAAiB;AAAA;AAAA,EAC9E;AAAA,EACA,UAAU,MAA2B;AACnC,WAAO,GAAG,YAAY,KAAK;AAAA;AAAA,EAAqC,YAAY,GAAG;AAAA;AAAA,EACjF;AAAA,EACA,UAAU,KAA0B;AAClC,WAAOC,MAAK,IAAI,MAAM,WAAW,QAAQ;AAAA,EAC3C;AACF;;;AC3BA,SAAS,QAAAC,aAAY;AA0Bd,IAAM,gBAA6B;AAAA,EACxC,IAAI;AAAA,EACJ,cAAc,MAAM,MAAwB,aAA8C;AACxF,WAAO,oBAAoB,MAAM,WAAW;AAAA,EAC9C;AAAA,EACA,YAAY,KAA0B;AAEpC,WAAO,aAAa,GAAG;AAAA,EACzB;AAAA,EACA,UAAU,KAA0B;AAElC,WAAOC,MAAK,IAAI,MAAM,WAAW,OAAO;AAAA,EAC1C;AAAA,EACA,cAAc,KAA0B;AACtC,WAAOA,MAAK,IAAI,MAAM,WAAW,UAAU;AAAA,EAC7C;AAAA,EACA,aAAa,KAA0B;AACrC,WAAOA,MAAK,IAAI,MAAM,WAAW;AAAA,EACnC;AACF;;;AC7CA,SAAS,QAAAC,aAAY;AACrB,SAAS,uBAAAC,sBAAqB,qBAAAC,oBAAmB,eAAAC,oBAAmB;AAwB7D,IAAM,gBAA6B;AAAA,EACxC,IAAI;AAAA,EACJ,cAAc,MAAM,MAAwB,aAA8C;AACxF,WAAO,oBAAoB,MAAM,WAAW;AAAA,EAC9C;AAAA,EACA,YAAY,MAA2B;AAIrC,WACE,GAAGC,oBAAmB;AAAA;AAAA,EAAqBC,kBAAiB;AAAA,EACzDC,aAAY,KAAK;AAAA;AAAA,EAA4BA,aAAY,GAAG;AAAA;AAAA,EAEnE;AAAA;AAAA;AAAA,EAGA,cAAc,KAA0B;AACtC,WAAOC,MAAK,IAAI,MAAM,WAAW,UAAU;AAAA,EAC7C;AAAA,EACA,aAAa,KAA0B;AACrC,WAAOA,MAAK,IAAI,MAAM,WAAW;AAAA,EACnC;AACF;;;AC/CA,SAAS,QAAAC,aAAY;AAwBd,IAAM,kBAA+B;AAAA,EAC1C,IAAI;AAAA,EACJ,cAAc,MAAM,MAAwB,aAA8C;AAUxF,UAAM,MAA+B;AAAA,MACnC,MACE,KAAK,cAAc,UACf,EAAE,MAAM,SAAS,SAAS,CAAC,QAAQ,OAAO,SAAS,SAAS,EAAE,IAC9D,EAAE,MAAM,UAAU,KAAK,KAAK,OAAO,yBAAyB;AAAA,IACpE;AACA,QAAI,aAAa;AACf,UAAI,YAAY,UAAU,IACxB,YAAY,cAAc,SACtB;AAAA,QACE,MAAM;AAAA,QACN,KAAK,YAAY,OAAO;AAAA,QACxB,GAAI,YAAY,MAAM,EAAE,aAAa,YAAY,IAAI,IAAI,CAAC;AAAA,MAC5D,IACA;AAAA,QACE,MAAM;AAAA,QACN,SAAS,CAAC,YAAY,SAAS,GAAI,YAAY,QAAQ,CAAC,CAAE;AAAA,QAC1D,GAAI,YAAY,MAAM,EAAE,aAAa,YAAY,IAAI,IAAI,CAAC;AAAA,MAC5D;AAAA,IACR;AACA,WAAO,KAAK,UAAU,EAAE,SAAS,mCAAmC,IAAI,GAAG,MAAM,CAAC;AAAA,EACpF;AAAA,EACA,YAAY,KAA0B;AACpC,WAAO,aAAa,GAAG;AAAA,EACzB;AAAA;AAAA;AAAA,EAGA,cAAc,KAA0B;AACtC,WAAOC,MAAK,IAAI,MAAM,eAAe;AAAA,EACvC;AAAA,EACA,aAAa,KAA0B;AACrC,WAAOA,MAAK,IAAI,MAAM,WAAW;AAAA,EACnC;AACF;;;ACvCO,IAAM,kBAAqC,OAAO,OAAO;AAAA,EAC9D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAoBM,SAAS,eAAe,MAA2B;AACxD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,SAAS;AAIP,YAAM,cAAqB;AAC3B,YAAM,IAAI,MAAM,qBAAqB,OAAO,WAAW,CAAC,oBAAoB;AAAA,IAC9E;AAAA,EACF;AACF;","names":["join","join","join","join","join","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","join","join","join"]}
1
+ {"version":3,"sources":["../src/agents-md-adapter.ts","../src/agents-md.ts","../src/mcp.ts","../src/claude.ts","../src/cursor.ts","../src/gemini.ts","../src/opencode.ts","../src/handoff.ts","../src/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `agents-md` host adapter — the 32-platform universal AGENTS.md standard.\n * The SMALLEST surface: a root `AGENTS.md` (context + rules unified — the\n * `@.noir/` imports already cover both NOIR.md and RULES.md), no skill concept\n * (the context IS the surface), and a workspace `.mcp.json` (the Claude shape —\n * broadly compatible; many AGENTS.md readers also read it).\n *\n * This adapter is the fallback / universal baseline — any host that \"just reads\n * AGENTS.md\" (incl. qwen/agy and other deferred hosts) behaves identically to\n * this. Per-host specialization lives in the other adapters' native files.\n */\nexport const agentsMdAdapter: HostAdapter = {\n id: 'agents-md',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // The universal AGENTS.md content — byte-identical across every adapter\n // that composes `emitAgentsMd` (agents-md / gemini / cursor / opencode).\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN the AGENTS.md content already (the\n // `@.noir/rules/RULES.md` import covers them).\n // No `skillsDir` — no skill concept for the universal host.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { basename } from 'node:path';\nimport type { EmitContext } from './types.js';\n\n/** The canonical filename for the universal AGENTS.md emitter. Every host\n * writes the SAME content (single source of truth) — adapters may place it at\n * different paths via `HostAdapter#agentsMdPath`, but the filename is constant. */\nexport const AGENTS_MD_FILENAME = 'AGENTS.md';\n\n/**\n * The universal AGENTS.md emitter — a SHARED HELPER composed by every adapter\n * (NOT a HostAdapter itself). AGENTS.md is now the cross-tool standard (32+\n * platforms read it natively — Claude Code, OpenAI Codex, Cursor, Gemini CLI,\n * JetBrains Junie, …), so the SAME file content works for every host: it\n * `@`-imports the canonical `.noir/` sources. Per-host specialization lives in\n * each adapter's OWN native files (CLAUDE.md, GEMINI.md, .cursor/rules/*.mdc,\n * opencode.json); AGENTS.md is the always-emitted universal baseline.\n *\n * Content (byte-identical across hosts):\n *\n * # <projectName> — Noir-managed agent context\n *\n * > Generated by `noir`. Canonical source lives in `.noir/`. Edit there; re-run `noir sync`.\n *\n * > Noir manages this project's context under `.noir/`. Edit `.noir/NOIR.md`\n * > (project brief) + `.noir/rules/RULES.md` (working contract); re-run\n * > `noir sync` to re-emit host files.\n *\n * @.noir/NOIR.md\n * @.noir/rules/RULES.md\n *\n * The inline 3-line fallback sits BEFORE the `@`-imports so AGENTS.md\n * readers that do NOT resolve `@`-imports (e.g. plain markdown viewers, some\n * GA dashboards) still get a one-glance summary of where to edit. The\n * `@`-imports remain canonical for the hosts that DO resolve them (Cursor,\n * Codex, Junie, …).\n *\n * `<projectName>` is derived from `ctx.root`'s basename — a best-effort label\n * for human readability. If the basename is empty/undeterminable, falls back to\n * the literal `project` so the heading is never malformed.\n */\nexport function emitAgentsMd(ctx: EmitContext): string {\n const projectName = deriveProjectName(ctx.root);\n return (\n `# ${projectName} — Noir-managed agent context\\n\\n` +\n `> Generated by \\`noir\\`. Canonical source lives in \\`.noir/\\`. Edit there; re-run \\`noir sync\\`.\\n\\n` +\n `> Noir manages this project's context under \\`.noir/\\`. Edit \\`.noir/NOIR.md\\` ` +\n `(project brief) + \\`.noir/rules/RULES.md\\` (working contract); re-run \\`noir sync\\` ` +\n `to re-emit host files.\\n\\n` +\n `@.noir/NOIR.md\\n` +\n `@.noir/rules/RULES.md\\n`\n );\n}\n\n/** Derive a human-readable project label from the root path's basename.\n * Trims trailing slashes (POSIX) so `/p/demo/` → `demo`; falls back to the\n * literal `project` when the basename is empty (e.g. root was `/` or `''`). */\nfunction deriveProjectName(root: string): string {\n // `basename` already handles trailing slashes on POSIX; guard the empty case.\n const name = basename(root.replace(/\\/+$/, '')) || basename(root);\n return name && name.length > 0 ? name : 'project';\n}\n","import type { IntegrationMcpEmission, McpConfigOptions } from './types.js';\n\n/**\n * Build the host MCP config JSON string — the `{mcpServers: {...}}` shape shared\n * by claude (`.mcp.json`), agents-md (`.mcp.json`), gemini (`.gemini/mcp.json`),\n * and cursor (`.cursor/mcp.json`). OpenCode uses a DIFFERENT shape (an `mcp`\n * block with `type`-tagged entries) and does NOT use this helper — see\n * `opencode.ts`.\n *\n * The Noir server entry is always present; an optional integration entry merges\n * alongside it (per the Slice X adapter contract — only `external-mcp`\n * integrations surface a `hostMcp` block by the time it reaches here).\n *\n * Refactored out of `claude.ts` in S10-Adapters so every `{mcpServers}`-shape\n * host emits byte-identical JSON. Claude's `emitMcpConfig` now delegates here\n * (the claude.test.ts + create/scaffold.test.ts parity gates must hold).\n *\n * Stdio entry: `{ command, args }`.\n * HTTP entry : `{ type: 'http', url, [env] }` — env nested under `env:`, never\n * spread at the entry top level (would corrupt the server-field shape).\n */\nexport function buildMcpServersJson(\n opts: McpConfigOptions,\n integration?: IntegrationMcpEmission,\n): string {\n // Always present: the Noir MCP server — `noir mcp serve --stdio` or the\n // streamable-http endpoint. The placeholder URL (`:0`) is a best-effort hint\n // the user edits; same behavior as the original claude implementation.\n const noirServer =\n opts.transport === 'stdio'\n ? { command: 'noir', args: ['mcp', 'serve', '--stdio'] }\n : { type: 'http', url: opts.url ?? 'http://127.0.0.1:0/mcp' };\n\n const mcpServers: Record<string, unknown> = { noir: noirServer };\n if (integration) {\n const entry =\n integration.transport === 'http'\n ? {\n type: 'http',\n url: integration.url ?? '',\n // Nest env under `env:` (NOT spread at the entry top level) so the\n // emitted shape matches the stdio branch + Claude's spec. Top-level\n // spread would leak env keys as server fields.\n ...(integration.env ? { env: integration.env } : {}),\n }\n : {\n command: integration.command,\n ...(integration.args ? { args: integration.args } : {}),\n ...(integration.env ? { env: integration.env } : {}),\n };\n mcpServers[integration.serverName] = entry;\n }\n return JSON.stringify({ mcpServers }, null, 2);\n}\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\nexport const claudeAdapter: HostAdapter = {\n id: 'claude',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // Delegates to the shared `{mcpServers}` builder. Behavior-identical to the\n // pre-S10 inline implementation — the claude.test.ts + create/scaffold.test.ts\n // byte-equality parity gates must hold (Slice X integration merge preserved).\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n return `${CONTEXT_BLOCK_BEGIN}\\n@import \".noir/NOIR.md\"\\n${CONTEXT_BLOCK_END}\\n`;\n },\n emitRules(_ctx: EmitContext): string {\n return `${RULES_BLOCK.begin}\\n@import \".noir/rules/RULES.md\"\\n${RULES_BLOCK.end}\\n`;\n },\n skillsDir(ctx: EmitContext): string {\n return join(ctx.root, '.claude', 'skills');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `cursor` host adapter — Cursor. Cursor reads the universal `AGENTS.md`\n * for context (same content as every other host); that file's\n * `@.noir/rules/RULES.md` import IS the Noir working-rules surface for cursor\n * (NO separate `.cursor/rules/noir-contract.mdc` host-rules pointer — that\n * file was REMOVED: it was `noir-`-prefixed and the cursor flat-skill prune\n * in `emitSkillsToDir` deleted it on every `noir init/create/sync --host\n * cursor`). Skills compile to FLAT `.mdc` in `.cursor/rules/` via\n * `compileSkill(_, 'cursor')` (one file per skill, no per-name subdir). MCP\n * config lands at `.cursor/mcp.json`.\n *\n * `emitContext` returns the universal AGENTS.md content (the single native\n * context surface for cursor). There is NO `emitRules` here — cursor's rules\n * are delivered via AGENTS.md's `@.noir/rules/RULES.md` import, identical to\n * agents-md/opencode.\n */\nexport const cursorAdapter: HostAdapter = {\n id: 'cursor',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // Cursor reads AGENTS.md — same universal content as every other host.\n return emitAgentsMd(ctx);\n },\n skillsDir(ctx: EmitContext): string {\n // Skills compile to `.mdc` here (via `compileSkill(_, 'cursor')` in skills).\n return join(ctx.root, '.cursor', 'rules');\n },\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.cursor', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `gemini` host adapter — Gemini CLI. Emits `GEMINI.md` (Gemini's native\n * context file) carrying BOTH the context block AND the rules block — Gemini\n * has no separate rules file. Uses Gemini's `@file` import syntax: bare\n * `@.noir/NOIR.md` (no `@import` keyword, no quotes — distinct from Claude's\n * `@import \".noir/...\"` form). The cli ALSO emits a root `AGENTS.md` via the\n * shared `emitAgentsMd` helper (Gemini reads AGENTS.md too).\n *\n * Managed-block form (CONTEXT_BLOCK + RULES_BLOCK markers) is used so user\n * content outside the markers survives `noir sync` rewrites — consistent with\n * the claude adapter's contract.\n *\n * MCP config lands at `.gemini/mcp.json` (workspace-level — the portable\n * choice; `~/.gemini/settings.json` is the global alternative, documented).\n */\nexport const geminiAdapter: HostAdapter = {\n id: 'gemini',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n // Gemini's `@file` import: bare `@.noir/...` (no `@import`, no quotes).\n // Rules folded into the same GEMINI.md (per spec — Gemini has no separate\n // rules file). Both blocks are marker-wrapped so user content survives sync.\n return (\n `${CONTEXT_BLOCK_BEGIN}\\n@.noir/NOIR.md\\n${CONTEXT_BLOCK_END}\\n` +\n `${RULES_BLOCK.begin}\\n@.noir/rules/RULES.md\\n${RULES_BLOCK.end}\\n`\n );\n },\n // No `emitRules` — rules folded into emitContext (GEMINI.md carries both).\n // No `skillsDir` — no skill concept; GEMINI.md + AGENTS.md are the surface.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.gemini', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `opencode` host adapter — OpenCode. Reads the universal `AGENTS.md`\n * (context + rules unified — no separate rules emission, no skill concept).\n *\n * The DIFFERENCE from the `{mcpServers}` family lives in MCP config: OpenCode's\n * root `opencode.json` carries an `mcp` block whose entries are `type`-tagged —\n * `{ type: 'local', command: [...] }` for stdio (note: `command` is an ARRAY,\n * not the claude `{command, args}` split) and `{ type: 'remote', url }` for\n * remote (HTTP/SSE). Server env vars land under the `environment` key (NOT\n * `env`). The `$schema` key pins the opencode config schema. See\n * https://opencode.ai/docs/mcp-servers/ + https://opencode.ai/config.json.\n *\n * Verified verbatim from the opencode docs (\"Add remote MCP servers by setting\n * `type` to `\\\"remote\\\"`\"); the prior `type:'http'` / `env:` shape was wrong.\n */\nexport const opencodeAdapter: HostAdapter = {\n id: 'opencode',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // OpenCode's `mcp` block — entries carry an explicit `type` tag (verified\n // against https://opencode.ai/docs/mcp-servers/):\n // - stdio → { type: 'local', command: [...] } (command is an ARRAY)\n // - remote → { type: 'remote', url: ... } (HTTP/SSE — NOT 'http')\n // Server env vars land under `environment:` (NOT `env:` — opencode's own\n // spelling). Noir server always present; optional integration merges\n // alongside under its `serverName`. (Does NOT use buildMcpServersJson —\n // different shape; that helper's `env:` is correct for `.mcp.json` /\n // `.cursor/mcp.json` / `.gemini/mcp.json`.)\n const mcp: Record<string, unknown> = {\n noir:\n opts.transport === 'stdio'\n ? { type: 'local', command: ['noir', 'mcp', 'serve', '--stdio'] }\n : { type: 'remote', url: opts.url ?? 'http://127.0.0.1:0/mcp' },\n };\n if (integration) {\n mcp[integration.serverName] =\n integration.transport === 'http'\n ? {\n type: 'remote',\n url: integration.url ?? '',\n ...(integration.env ? { environment: integration.env } : {}),\n }\n : {\n type: 'local',\n command: [integration.command, ...(integration.args ?? [])],\n ...(integration.env ? { environment: integration.env } : {}),\n };\n }\n return JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp }, null, 2);\n },\n emitContext(ctx: EmitContext): string {\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN AGENTS.md (the @-import covers RULES.md).\n // No `skillsDir` — no skill concept.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, 'opencode.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","// Host handoff helpers. The single source for the \"open your host CLI\"\n// directive text shared by the home banner (`noir` bare) and the handoff\n// artifact (`noir handoff`). TEXT ONLY — Noir NEVER launches the host (doctrine:\n// the host-launch directive is pasteable text, never a spawn). A host that wants\n// richer handoff wording implements `HostAdapter.emitHandoff`; the CLI falls\n// back to {@link hostLaunchDirective} when that hook is absent.\n\nimport type { EmitContext, HandoffPayload, HostId } from './types.js';\n\n/**\n * The readonly host list mirrored from {@link HostId} so this module has NO\n * import edge back to `index.ts` (which re-exports THIS module) — avoids a\n * module-eval cycle. `registry.test.ts` pins `SUPPORTED_HOSTS` to the same five\n * literals, so a new host added to `HostId` surfaces here via that test.\n */\nconst HOSTS: readonly HostId[] = ['claude', 'agents-md', 'gemini', 'cursor', 'opencode'];\n\n/**\n * One-line host-direction directive (refactored from the home-banner line).\n * Tells the user to open their configured host CLI to do the actual development\n * (Noir is the orchestration/context/memory brain; the host is the execution\n * engine — bring-your-own-agent). Host-agnostic via the {@link HOSTS}\n * registry; lists the alternatives so a multi-host user knows their options.\n *\n * This is the SINGLE source — the home banner AND the handoff artifact both\n * call it so the wording never drifts. A host-specific override lives on\n * {@link HostAdapter.emitHandoff}, not here.\n */\nexport function hostLaunchDirective(host: HostId): string {\n const others = HOSTS.filter((h) => h !== host);\n return `→ host: ${host}. Open \\`${host}\\` to start development — Noir set the rules, skills, and memory; ${host} runs the code. (other hosts: ${others.join(', ')})`;\n}\n\n/**\n * The default handoff directive block (multi-line) used when a host adapter\n * does NOT implement {@link HostAdapter.emitHandoff}. Composes the generic\n * {@link hostLaunchDirective} line plus a reminder that the MCP wire is already\n * configured (`.mcp.json` / host equivalent), so the host can call Noir's tools\n * the moment it starts. The CLI passes this as the \"Open host\" portion of the\n * handoff artifact.\n */\nexport function defaultHandoffBlock(_ctx: EmitContext, payload: HandoffPayload): string {\n const directive = hostLaunchDirective(payload.host);\n return `${directive}\\nThe Noir MCP server is already wired via the host's MCP config — call \\`noir.*\\` tools once \\`${payload.host}\\` is open.`;\n}\n","import { agentsMdAdapter } from './agents-md-adapter.js';\nimport { claudeAdapter } from './claude.js';\nimport { cursorAdapter } from './cursor.js';\nimport { geminiAdapter } from './gemini.js';\nimport { opencodeAdapter } from './opencode.js';\nimport type { HostAdapter, HostId } from './types.js';\n\nexport { AGENTS_MD_FILENAME, emitAgentsMd } from './agents-md.js';\nexport { agentsMdAdapter } from './agents-md-adapter.js';\nexport { claudeAdapter } from './claude.js';\nexport { cursorAdapter } from './cursor.js';\nexport { geminiAdapter } from './gemini.js';\nexport { defaultHandoffBlock, hostLaunchDirective } from './handoff.js';\nexport { buildMcpServersJson } from './mcp.js';\nexport { opencodeAdapter } from './opencode.js';\nexport type {\n EmitContext,\n HandoffPayload,\n HostAdapter,\n HostId,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/** The readonly list of supported hosts — derived from the `HostId` union so it\n * stays in lockstep with the type. Consumers (cli `--host` flag, doctor\n * reporting) use this for \"is this host valid?\" + iteration. Order is the\n * declaration order in `HostId` (claude first — the default).\n *\n * `Object.freeze` enforces the `readonly` type at runtime — a stray\n * `SUPPORTED_HOSTS.push('qwen')` from a JS caller fails loudly instead of\n * silently corrupting the registry list (TS already prevents it in typed code). */\nexport const SUPPORTED_HOSTS: readonly HostId[] = Object.freeze([\n 'claude',\n 'agents-md',\n 'gemini',\n 'cursor',\n 'opencode',\n]);\n\n/**\n * Resolve a host id to its `HostAdapter`. The registry is a `Record<HostId,\n * HostAdapter>` so the type system enforces completeness — adding a host to\n * `HostId` requires wiring it here (TS errors otherwise). The CLI uses this\n * indirection instead of importing adapters directly so adding a host needs NO\n * CLI edits beyond the `--host` flag's enum.\n *\n * S10-Adapters: all five hosts are now wired —\n * - `claude` — CLAUDE.md + `.claude/skills/` + `.mcp.json` (regression anchor).\n * - `agents-md` — universal AGENTS.md (the 32-platform baseline).\n * - `gemini` — GEMINI.md + AGENTS.md + `.gemini/mcp.json`.\n * - `cursor` — AGENTS.md + `.cursor/rules/*.mdc` + `.cursor/mcp.json`.\n * - `opencode` — AGENTS.md + `opencode.json` (different MCP shape).\n *\n * Unknown/non-`HostId` strings are impossible to pass at compile time (the\n * signature accepts only `HostId`); the runtime fallback is defensive — a\n * JS caller ignoring types still gets a clear error, not a silent `undefined`.\n */\nexport function resolveAdapter(host: HostId): HostAdapter {\n switch (host) {\n case 'claude':\n return claudeAdapter;\n case 'agents-md':\n return agentsMdAdapter;\n case 'gemini':\n return geminiAdapter;\n case 'cursor':\n return cursorAdapter;\n case 'opencode':\n return opencodeAdapter;\n default: {\n // Exhaustiveness guard — if `HostId` gains a member and this switch is\n // not updated, TS narrows `host` to `never` here. At runtime (untyped JS\n // callers) we still surface a clear message.\n const _exhaustive: never = host;\n throw new Error(`Unsupported host: ${String(_exhaustive)} (not in registry)`);\n }\n }\n}\n"],"mappings":"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","names":["join","join","join","join","join","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","join","join","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noir-ai/adapters",
3
- "version": "1.3.0-beta.6",
3
+ "version": "1.4.0",
4
4
  "description": "Noir adapters — the HostAdapter abstraction and the Claude Code adapter (the v1 host).",
5
5
  "license": "MIT",
6
6
  "author": "agaaaptr",
@@ -23,7 +23,7 @@
23
23
  "agent"
24
24
  ],
25
25
  "engines": {
26
- "node": ">=20"
26
+ "node": ">=22"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public",
@@ -43,7 +43,7 @@
43
43
  "README.md"
44
44
  ],
45
45
  "dependencies": {
46
- "@noir-ai/core": "1.3.0-beta.6"
46
+ "@noir-ai/core": "1.4.0"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^26.1.1"