@noir-ai/adapters 1.15.0 → 1.16.0-beta.1

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/dist/index.d.ts CHANGED
@@ -17,6 +17,12 @@
17
17
  type HostId = 'claude' | 'agents-md' | 'gemini' | 'cursor' | 'opencode';
18
18
  interface EmitContext {
19
19
  root: string;
20
+ /** The project's `rules.enabled` switch, when the caller knows it. False says
21
+ * this project has no `.noir/rules/RULES.md` — the scaffold withholds the
22
+ * seed — so no emitted host file may `@`-import it or describe it. Undefined
23
+ * (every caller that predates the switch, and every project with no `rules:`
24
+ * block) means enabled, exactly as before. */
25
+ rulesEnabled?: boolean;
20
26
  }
21
27
  interface McpConfigOptions {
22
28
  transport: 'stdio' | 'streamable-http';
@@ -27,6 +33,14 @@ interface McpConfigOptions {
27
33
  * Cursor) that don't read shell profiles can still spawn the server — see
28
34
  * `resolveNoirCommand()` in @noir-ai/core. */
29
35
  command?: string;
36
+ /** The workspace this repo has joined, when it has joined one. A member repo
37
+ * reaches the shared workspace daemon through Noir's stdio bridge, so the
38
+ * server entry names the workspace on the command line rather than carrying
39
+ * an address: `noir mcp serve --stdio --workspace <name>`. The bridge is what
40
+ * resolves the daemon, proves it owns the record, and reads the token — none
41
+ * of which can live in a committed config file. Absent ⇒ a plain stdio entry
42
+ * that talks to a daemon scoped to this repo alone. */
43
+ workspace?: string;
30
44
  }
31
45
  /**
32
46
  * Provider-neutral host MCP server entry an integration can ask the adapter to
@@ -145,6 +159,13 @@ declare const AGENTS_MD_FILENAME = "AGENTS.md";
145
159
  * `@`-imports remain canonical for the hosts that DO resolve them (Cursor,
146
160
  * Codex, Junie, …).
147
161
  *
162
+ * A project that switched the working rules off (`ctx.rulesEnabled: false`)
163
+ * has no `.noir/rules/RULES.md` — the scaffold withholds the seed — so both
164
+ * the import line AND the sentence that names it are dropped. An `@`-import
165
+ * of a file that is not there is a dangling reference the host resolves to
166
+ * nothing, and prose pointing the reader at it is worse. The brief import
167
+ * stays either way: the switch gates the rules, not the context.
168
+ *
148
169
  * `<projectName>` is derived from `ctx.root`'s basename — a best-effort label
149
170
  * for human readability. If the basename is empty/undeterminable, falls back to
150
171
  * the literal `project` so the heading is never malformed.
@@ -223,6 +244,39 @@ declare function hostLaunchDirective(host: HostId): string;
223
244
  */
224
245
  declare function defaultHandoffBlock(_ctx: EmitContext, payload: HandoffPayload): string;
225
246
 
247
+ /**
248
+ * The keys that say HOW a host reaches the Noir server — `command`/`args` for
249
+ * stdio, `type`/`url` for the http endpoint. Rewriting the transport replaces
250
+ * exactly these, so an entry never ends up describing two transports at once,
251
+ * and everything else the entry carries (`env`, `headers`, `headersHelper`, …)
252
+ * is the user's and is kept. Exported once here (beside `noirStdioArgs`) and
253
+ * shared by the cli's workspace rewrite and the create scaffold migration,
254
+ * which both strip these keys — the set must never be re-declared there.
255
+ */
256
+ declare const TRANSPORT_KEYS: ReadonlySet<string>;
257
+ /**
258
+ * Where a host keeps its MCP config file (absolute), with the `.mcp.json`
259
+ * default a host that does not declare a path falls back to. Every adapter's
260
+ * `mcpConfigPath` reads `root` and nothing else from its context, so a caller
261
+ * that holds no adapter object asks by host id and this is where the fallback
262
+ * lives — one expression, not one per caller.
263
+ */
264
+ declare function mcpConfigPathForAdapter(adapter: HostAdapter, root: string): string;
265
+ /** {@link mcpConfigPathForAdapter} for a caller that has the host id rather than
266
+ * the adapter object — it resolves the adapter first. The cli's workspace
267
+ * rewrite, its `doctor` expectation check, and the create scaffold migration
268
+ * all come through here; the create manifest, which already holds the adapter,
269
+ * calls the adapter form directly. Either way the fallback is spelled once. */
270
+ declare function mcpConfigPathFor(host: HostId, root: string): string;
271
+ /**
272
+ * The arguments that start Noir's MCP server over stdio. A repo that has joined
273
+ * a workspace names it here, so the host spawns the bridge, which resolves the
274
+ * workspace daemon and reads its token itself — the entry never has to carry an
275
+ * address or a secret. Shared by every host adapter (OpenCode carries the same
276
+ * argv in a single `command` array) and by the scaffold upgrade that rewrites a
277
+ * stale entry, so the invocation is spelled in exactly one place.
278
+ */
279
+ declare function noirStdioArgs(workspace?: string): string[];
226
280
  /**
227
281
  * Build the host MCP config JSON string — the `{mcpServers: {...}}` shape shared
228
282
  * by claude (`.mcp.json`), agents-md (`.mcp.json`), gemini (`.gemini/mcp.json`),
@@ -290,4 +344,4 @@ declare const SUPPORTED_HOSTS: readonly HostId[];
290
344
  */
291
345
  declare function resolveAdapter(host: HostId): HostAdapter;
292
346
 
293
- 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 };
347
+ export { AGENTS_MD_FILENAME, type EmitContext, type HandoffPayload, type HostAdapter, type HostId, type IntegrationMcpEmission, type McpConfigOptions, SUPPORTED_HOSTS, TRANSPORT_KEYS, agentsMdAdapter, buildMcpServersJson, claudeAdapter, cursorAdapter, defaultHandoffBlock, emitAgentsMd, geminiAdapter, hostLaunchDirective, mcpConfigPathFor, mcpConfigPathForAdapter, noirStdioArgs, opencodeAdapter, resolveAdapter };
package/dist/index.js CHANGED
@@ -1,20 +1,26 @@
1
1
  // src/agents-md-adapter.ts
2
- import { join } from "path";
2
+ import { join as join2 } from "path";
3
3
 
4
4
  // src/agents-md.ts
5
5
  import { basename } from "path";
6
6
  var AGENTS_MD_FILENAME = "AGENTS.md";
7
7
  function emitAgentsMd(ctx) {
8
8
  const projectName = deriveProjectName(ctx.root);
9
- return `# ${projectName} \u2014 Noir-managed agent context
9
+ const rules = ctx.rulesEnabled !== false;
10
+ const managedProse = rules ? `> Noir manages this project's context under \`.noir/\`. Edit \`.noir/NOIR.md\` (project brief) + \`.noir/rules/RULES.md\` (working contract); re-run \`noir sync\` to re-emit host files.
10
11
 
11
- > Generated by \`noir\`. Canonical source lives in \`.noir/\`. Edit there; re-run \`noir sync\`.
12
+ ` : `> Noir manages this project's context under \`.noir/\`. Edit \`.noir/NOIR.md\` (project brief); re-run \`noir sync\` to re-emit host files.
12
13
 
13
- > Noir manages this project's context under \`.noir/\`. Edit \`.noir/NOIR.md\` (project brief) + \`.noir/rules/RULES.md\` (working contract); re-run \`noir sync\` to re-emit host files.
14
-
15
- @.noir/NOIR.md
14
+ `;
15
+ const imports = rules ? `@.noir/NOIR.md
16
16
  @.noir/rules/RULES.md
17
+ ` : `@.noir/NOIR.md
17
18
  `;
19
+ return `# ${projectName} \u2014 Noir-managed agent context
20
+
21
+ > Generated by \`noir\`. Canonical source lives in \`.noir/\`. Edit there; re-run \`noir sync\`.
22
+
23
+ ` + managedProse + imports;
18
24
  }
19
25
  function deriveProjectName(root) {
20
26
  const name = basename(root.replace(/\/+$/, "")) || basename(root);
@@ -22,8 +28,19 @@ function deriveProjectName(root) {
22
28
  }
23
29
 
24
30
  // src/mcp.ts
31
+ import { join } from "path";
32
+ var TRANSPORT_KEYS = /* @__PURE__ */ new Set(["command", "args", "type", "url"]);
33
+ function mcpConfigPathForAdapter(adapter, root) {
34
+ return adapter.mcpConfigPath?.({ root }) ?? join(root, ".mcp.json");
35
+ }
36
+ function mcpConfigPathFor(host, root) {
37
+ return mcpConfigPathForAdapter(resolveAdapter(host), root);
38
+ }
39
+ function noirStdioArgs(workspace) {
40
+ return workspace === void 0 ? ["mcp", "serve", "--stdio"] : ["mcp", "serve", "--stdio", "--workspace", workspace];
41
+ }
25
42
  function buildMcpServersJson(opts, integration) {
26
- const noirServer = opts.transport === "stdio" ? { command: opts.command ?? "noir", args: ["mcp", "serve", "--stdio"] } : { type: "http", url: opts.url ?? "http://127.0.0.1:0/mcp" };
43
+ const noirServer = opts.transport === "stdio" ? { command: opts.command ?? "noir", args: noirStdioArgs(opts.workspace) } : { type: "http", url: opts.url ?? "http://127.0.0.1:0/mcp" };
27
44
  const mcpServers = { noir: noirServer };
28
45
  if (integration) {
29
46
  const entry = integration.transport === "http" ? {
@@ -56,15 +73,15 @@ var agentsMdAdapter = {
56
73
  // `@.noir/rules/RULES.md` import covers them).
57
74
  // No `skillsDir` — no skill concept for the universal host.
58
75
  mcpConfigPath(ctx) {
59
- return join(ctx.root, ".mcp.json");
76
+ return join2(ctx.root, ".mcp.json");
60
77
  },
61
78
  agentsMdPath(ctx) {
62
- return join(ctx.root, "AGENTS.md");
79
+ return join2(ctx.root, "AGENTS.md");
63
80
  }
64
81
  };
65
82
 
66
83
  // src/claude.ts
67
- import { join as join2 } from "path";
84
+ import { join as join3 } from "path";
68
85
  import { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from "@noir-ai/core";
69
86
  var claudeAdapter = {
70
87
  id: "claude",
@@ -84,12 +101,12 @@ ${RULES_BLOCK.end}
84
101
  `;
85
102
  },
86
103
  skillsDir(ctx) {
87
- return join2(ctx.root, ".claude", "skills");
104
+ return join3(ctx.root, ".claude", "skills");
88
105
  }
89
106
  };
90
107
 
91
108
  // src/cursor.ts
92
- import { join as join3 } from "path";
109
+ import { join as join4 } from "path";
93
110
  var cursorAdapter = {
94
111
  id: "cursor",
95
112
  emitMcpConfig(_ctx, opts, integration) {
@@ -99,18 +116,18 @@ var cursorAdapter = {
99
116
  return emitAgentsMd(ctx);
100
117
  },
101
118
  skillsDir(ctx) {
102
- return join3(ctx.root, ".cursor", "rules");
119
+ return join4(ctx.root, ".cursor", "rules");
103
120
  },
104
121
  mcpConfigPath(ctx) {
105
- return join3(ctx.root, ".cursor", "mcp.json");
122
+ return join4(ctx.root, ".cursor", "mcp.json");
106
123
  },
107
124
  agentsMdPath(ctx) {
108
- return join3(ctx.root, "AGENTS.md");
125
+ return join4(ctx.root, "AGENTS.md");
109
126
  }
110
127
  };
111
128
 
112
129
  // src/gemini.ts
113
- import { join as join4 } from "path";
130
+ import { join as join5 } from "path";
114
131
  import { CONTEXT_BLOCK_BEGIN as CONTEXT_BLOCK_BEGIN2, CONTEXT_BLOCK_END as CONTEXT_BLOCK_END2, RULES_BLOCK as RULES_BLOCK2 } from "@noir-ai/core";
115
132
  var geminiAdapter = {
116
133
  id: "gemini",
@@ -129,15 +146,15 @@ ${RULES_BLOCK2.end}
129
146
  // No `emitRules` — rules folded into emitContext (GEMINI.md carries both).
130
147
  // No `skillsDir` — no skill concept; GEMINI.md + AGENTS.md are the surface.
131
148
  mcpConfigPath(ctx) {
132
- return join4(ctx.root, ".gemini", "mcp.json");
149
+ return join5(ctx.root, ".gemini", "mcp.json");
133
150
  },
134
151
  agentsMdPath(ctx) {
135
- return join4(ctx.root, "AGENTS.md");
152
+ return join5(ctx.root, "AGENTS.md");
136
153
  }
137
154
  };
138
155
 
139
156
  // src/opencode.ts
140
- import { join as join5 } from "path";
157
+ import { join as join6 } from "path";
141
158
  var opencodeAdapter = {
142
159
  id: "opencode",
143
160
  emitMcpConfig(_ctx, opts, integration) {
@@ -146,7 +163,9 @@ var opencodeAdapter = {
146
163
  // Thread opts.command like buildMcpServersJson does (mcp.ts:36) — the
147
164
  // absolute native shim when resolveNoirCommand() detects a native
148
165
  // install, so GUI MCP clients (no shell profile PATH) can spawn it.
149
- { type: "local", command: [opts.command ?? "noir", "mcp", "serve", "--stdio"] }
166
+ // The argv itself comes from the shared helper, so a joined repo's
167
+ // `--workspace` flag is spelled identically on every host.
168
+ { type: "local", command: [opts.command ?? "noir", ...noirStdioArgs(opts.workspace)] }
150
169
  ) : { type: "remote", url: opts.url ?? "http://127.0.0.1:0/mcp" }
151
170
  };
152
171
  if (integration) {
@@ -168,10 +187,10 @@ var opencodeAdapter = {
168
187
  // No `emitRules` — rules live IN AGENTS.md (the @-import covers RULES.md).
169
188
  // No `skillsDir` — no skill concept.
170
189
  mcpConfigPath(ctx) {
171
- return join5(ctx.root, "opencode.json");
190
+ return join6(ctx.root, "opencode.json");
172
191
  },
173
192
  agentsMdPath(ctx) {
174
- return join5(ctx.root, "AGENTS.md");
193
+ return join6(ctx.root, "AGENTS.md");
175
194
  }
176
195
  };
177
196
 
@@ -216,6 +235,7 @@ function resolveAdapter(host) {
216
235
  export {
217
236
  AGENTS_MD_FILENAME,
218
237
  SUPPORTED_HOSTS,
238
+ TRANSPORT_KEYS,
219
239
  agentsMdAdapter,
220
240
  buildMcpServersJson,
221
241
  claudeAdapter,
@@ -224,6 +244,9 @@ export {
224
244
  emitAgentsMd,
225
245
  geminiAdapter,
226
246
  hostLaunchDirective,
247
+ mcpConfigPathFor,
248
+ mcpConfigPathForAdapter,
249
+ noirStdioArgs,
227
250
  opencodeAdapter,
228
251
  resolveAdapter
229
252
  };
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/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 adapter contract — only `external-mcp`\n * integrations surface a `hostMcp` block by the time it reaches here).\n *\n * Refactored out of `claude.ts` 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 // For stdio, `command` defaults to 'noir' but the engine passes the absolute\n // native-shim path when a native install is detected — GUI MCP clients (VS\n // Code, Cursor) launch from the Dock/Finder and don't read shell profiles, so\n // a bare 'noir' fails with `spawn noir ENOENT` even when ~/.noir/bin is on\n // the user's shell PATH. See resolveNoirCommand() in @noir-ai/core.\n const noirServer =\n opts.transport === 'stdio'\n ? { command: opts.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 // previous inline implementation — the claude.test.ts + create/scaffold.test.ts\n // byte-equality parity gates must hold (integration entry 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 ? // Thread opts.command like buildMcpServersJson does (mcp.ts:36) — the\n // absolute native shim when resolveNoirCommand() detects a native\n // install, so GUI MCP clients (no shell profile PATH) can spawn it.\n { type: 'local', command: [opts.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 * All five hosts are 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"]}
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 * A project that switched the working rules off (`ctx.rulesEnabled: false`)\n * has no `.noir/rules/RULES.md` — the scaffold withholds the seed — so both\n * the import line AND the sentence that names it are dropped. An `@`-import\n * of a file that is not there is a dangling reference the host resolves to\n * nothing, and prose pointing the reader at it is worse. The brief import\n * stays either way: the switch gates the rules, not the context.\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 const rules = ctx.rulesEnabled !== false;\n const managedProse = rules\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 manages this project's context under \\`.noir/\\`. Edit \\`.noir/NOIR.md\\` ` +\n `(project brief); re-run \\`noir sync\\` to re-emit host files.\\n\\n`;\n const imports = rules ? `@.noir/NOIR.md\\n@.noir/rules/RULES.md\\n` : `@.noir/NOIR.md\\n`;\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 managedProse +\n imports\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 { join } from 'node:path';\nimport { resolveAdapter } from './index.js';\nimport type { HostAdapter, HostId, IntegrationMcpEmission, McpConfigOptions } from './types.js';\n\n/**\n * The keys that say HOW a host reaches the Noir server — `command`/`args` for\n * stdio, `type`/`url` for the http endpoint. Rewriting the transport replaces\n * exactly these, so an entry never ends up describing two transports at once,\n * and everything else the entry carries (`env`, `headers`, `headersHelper`, …)\n * is the user's and is kept. Exported once here (beside `noirStdioArgs`) and\n * shared by the cli's workspace rewrite and the create scaffold migration,\n * which both strip these keys — the set must never be re-declared there.\n */\nexport const TRANSPORT_KEYS: ReadonlySet<string> = new Set(['command', 'args', 'type', 'url']);\n\n/**\n * Where a host keeps its MCP config file (absolute), with the `.mcp.json`\n * default a host that does not declare a path falls back to. Every adapter's\n * `mcpConfigPath` reads `root` and nothing else from its context, so a caller\n * that holds no adapter object asks by host id and this is where the fallback\n * lives — one expression, not one per caller.\n */\nexport function mcpConfigPathForAdapter(adapter: HostAdapter, root: string): string {\n return adapter.mcpConfigPath?.({ root }) ?? join(root, '.mcp.json');\n}\n\n/** {@link mcpConfigPathForAdapter} for a caller that has the host id rather than\n * the adapter object — it resolves the adapter first. The cli's workspace\n * rewrite, its `doctor` expectation check, and the create scaffold migration\n * all come through here; the create manifest, which already holds the adapter,\n * calls the adapter form directly. Either way the fallback is spelled once. */\nexport function mcpConfigPathFor(host: HostId, root: string): string {\n return mcpConfigPathForAdapter(resolveAdapter(host), root);\n}\n\n/**\n * The arguments that start Noir's MCP server over stdio. A repo that has joined\n * a workspace names it here, so the host spawns the bridge, which resolves the\n * workspace daemon and reads its token itself — the entry never has to carry an\n * address or a secret. Shared by every host adapter (OpenCode carries the same\n * argv in a single `command` array) and by the scaffold upgrade that rewrites a\n * stale entry, so the invocation is spelled in exactly one place.\n */\nexport function noirStdioArgs(workspace?: string): string[] {\n return workspace === undefined\n ? ['mcp', 'serve', '--stdio']\n : ['mcp', 'serve', '--stdio', '--workspace', workspace];\n}\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 adapter contract — only `external-mcp`\n * integrations surface a `hostMcp` block by the time it reaches here).\n *\n * Refactored out of `claude.ts` 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 // For stdio, `command` defaults to 'noir' but the engine passes the absolute\n // native-shim path when a native install is detected — GUI MCP clients (VS\n // Code, Cursor) launch from the Dock/Finder and don't read shell profiles, so\n // a bare 'noir' fails with `spawn noir ENOENT` even when ~/.noir/bin is on\n // the user's shell PATH. See resolveNoirCommand() in @noir-ai/core.\n const noirServer =\n opts.transport === 'stdio'\n ? { command: opts.command ?? 'noir', args: noirStdioArgs(opts.workspace) }\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 // previous inline implementation — the claude.test.ts + create/scaffold.test.ts\n // byte-equality parity gates must hold (integration entry 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 { noirStdioArgs } from './mcp.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 ? // Thread opts.command like buildMcpServersJson does (mcp.ts:36) — the\n // absolute native shim when resolveNoirCommand() detects a native\n // install, so GUI MCP clients (no shell profile PATH) can spawn it.\n // The argv itself comes from the shared helper, so a joined repo's\n // `--workspace` flag is spelled identically on every host.\n { type: 'local', command: [opts.command ?? 'noir', ...noirStdioArgs(opts.workspace)] }\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 {\n buildMcpServersJson,\n mcpConfigPathFor,\n mcpConfigPathForAdapter,\n noirStdioArgs,\n TRANSPORT_KEYS,\n} 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 * All five hosts are 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","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.15.0",
3
+ "version": "1.16.0-beta.1",
4
4
  "description": "Noir adapters — the HostAdapter abstraction and 5 host adapters (claude, agents-md, gemini, cursor, opencode).",
5
5
  "license": "MIT",
6
6
  "author": "agaaaptr",
@@ -43,7 +43,7 @@
43
43
  "README.md"
44
44
  ],
45
45
  "dependencies": {
46
- "@noir-ai/core": "1.15.0"
46
+ "@noir-ai/core": "1.16.0-beta.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^22.17.0"