@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 +55 -1
- package/dist/index.js +45 -22
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
76
|
+
return join2(ctx.root, ".mcp.json");
|
|
60
77
|
},
|
|
61
78
|
agentsMdPath(ctx) {
|
|
62
|
-
return
|
|
79
|
+
return join2(ctx.root, "AGENTS.md");
|
|
63
80
|
}
|
|
64
81
|
};
|
|
65
82
|
|
|
66
83
|
// src/claude.ts
|
|
67
|
-
import { join as
|
|
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
|
|
104
|
+
return join3(ctx.root, ".claude", "skills");
|
|
88
105
|
}
|
|
89
106
|
};
|
|
90
107
|
|
|
91
108
|
// src/cursor.ts
|
|
92
|
-
import { join as
|
|
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
|
|
119
|
+
return join4(ctx.root, ".cursor", "rules");
|
|
103
120
|
},
|
|
104
121
|
mcpConfigPath(ctx) {
|
|
105
|
-
return
|
|
122
|
+
return join4(ctx.root, ".cursor", "mcp.json");
|
|
106
123
|
},
|
|
107
124
|
agentsMdPath(ctx) {
|
|
108
|
-
return
|
|
125
|
+
return join4(ctx.root, "AGENTS.md");
|
|
109
126
|
}
|
|
110
127
|
};
|
|
111
128
|
|
|
112
129
|
// src/gemini.ts
|
|
113
|
-
import { join as
|
|
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
|
|
149
|
+
return join5(ctx.root, ".gemini", "mcp.json");
|
|
133
150
|
},
|
|
134
151
|
agentsMdPath(ctx) {
|
|
135
|
-
return
|
|
152
|
+
return join5(ctx.root, "AGENTS.md");
|
|
136
153
|
}
|
|
137
154
|
};
|
|
138
155
|
|
|
139
156
|
// src/opencode.ts
|
|
140
|
-
import { join as
|
|
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
|
-
|
|
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
|
|
190
|
+
return join6(ctx.root, "opencode.json");
|
|
172
191
|
},
|
|
173
192
|
agentsMdPath(ctx) {
|
|
174
|
-
return
|
|
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.
|
|
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.
|
|
46
|
+
"@noir-ai/core": "1.16.0-beta.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^22.17.0"
|