@noir-ai/adapters 1.7.2 → 1.7.3

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.js CHANGED
@@ -142,7 +142,12 @@ var opencodeAdapter = {
142
142
  id: "opencode",
143
143
  emitMcpConfig(_ctx, opts, integration) {
144
144
  const mcp = {
145
- noir: opts.transport === "stdio" ? { type: "local", command: ["noir", "mcp", "serve", "--stdio"] } : { type: "remote", url: opts.url ?? "http://127.0.0.1:0/mcp" }
145
+ noir: opts.transport === "stdio" ? (
146
+ // Thread opts.command like buildMcpServersJson does (mcp.ts:36) — the
147
+ // absolute native shim when resolveNoirCommand() detects a native
148
+ // install, so GUI MCP clients (no shell profile PATH) can spawn it.
149
+ { type: "local", command: [opts.command ?? "noir", "mcp", "serve", "--stdio"] }
150
+ ) : { type: "remote", url: opts.url ?? "http://127.0.0.1:0/mcp" }
146
151
  };
147
152
  if (integration) {
148
153
  mcp[integration.serverName] = integration.transport === "http" ? {
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 Slice X adapter contract — only `external-mcp`\n * integrations surface a `hostMcp` block by the time it reaches here).\n *\n * Refactored out of `claude.ts` in S10-Adapters so every `{mcpServers}`-shape\n * host emits byte-identical JSON. Claude's `emitMcpConfig` now delegates here\n * (the claude.test.ts + create/scaffold.test.ts parity gates must hold).\n *\n * Stdio entry: `{ command, args }`.\n * HTTP entry : `{ type: 'http', url, [env] }` — env nested under `env:`, never\n * spread at the entry top level (would corrupt the server-field shape).\n */\nexport function buildMcpServersJson(\n opts: McpConfigOptions,\n integration?: IntegrationMcpEmission,\n): string {\n // Always present: the Noir MCP server — `noir mcp serve --stdio` or the\n // streamable-http endpoint. The placeholder URL (`:0`) is a best-effort hint\n // the user edits; same behavior as the original claude implementation.\n // 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 // pre-S10 inline implementation — the claude.test.ts + create/scaffold.test.ts\n // byte-equality parity gates must hold (Slice X integration merge preserved).\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n return `${CONTEXT_BLOCK_BEGIN}\\n@import \".noir/NOIR.md\"\\n${CONTEXT_BLOCK_END}\\n`;\n },\n emitRules(_ctx: EmitContext): string {\n return `${RULES_BLOCK.begin}\\n@import \".noir/rules/RULES.md\"\\n${RULES_BLOCK.end}\\n`;\n },\n skillsDir(ctx: EmitContext): string {\n return join(ctx.root, '.claude', 'skills');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `cursor` host adapter — Cursor. Cursor reads the universal `AGENTS.md`\n * for context (same content as every other host); that file's\n * `@.noir/rules/RULES.md` import IS the Noir working-rules surface for cursor\n * (NO separate `.cursor/rules/noir-contract.mdc` host-rules pointer — that\n * file was REMOVED: it was `noir-`-prefixed and the cursor flat-skill prune\n * in `emitSkillsToDir` deleted it on every `noir init/create/sync --host\n * cursor`). Skills compile to FLAT `.mdc` in `.cursor/rules/` via\n * `compileSkill(_, 'cursor')` (one file per skill, no per-name subdir). MCP\n * config lands at `.cursor/mcp.json`.\n *\n * `emitContext` returns the universal AGENTS.md content (the single native\n * context surface for cursor). There is NO `emitRules` here — cursor's rules\n * are delivered via AGENTS.md's `@.noir/rules/RULES.md` import, identical to\n * agents-md/opencode.\n */\nexport const cursorAdapter: HostAdapter = {\n id: 'cursor',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // Cursor reads AGENTS.md — same universal content as every other host.\n return emitAgentsMd(ctx);\n },\n skillsDir(ctx: EmitContext): string {\n // Skills compile to `.mdc` here (via `compileSkill(_, 'cursor')` in skills).\n return join(ctx.root, '.cursor', 'rules');\n },\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.cursor', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `gemini` host adapter — Gemini CLI. Emits `GEMINI.md` (Gemini's native\n * context file) carrying BOTH the context block AND the rules block — Gemini\n * has no separate rules file. Uses Gemini's `@file` import syntax: bare\n * `@.noir/NOIR.md` (no `@import` keyword, no quotes — distinct from Claude's\n * `@import \".noir/...\"` form). The cli ALSO emits a root `AGENTS.md` via the\n * shared `emitAgentsMd` helper (Gemini reads AGENTS.md too).\n *\n * Managed-block form (CONTEXT_BLOCK + RULES_BLOCK markers) is used so user\n * content outside the markers survives `noir sync` rewrites — consistent with\n * the claude adapter's contract.\n *\n * MCP config lands at `.gemini/mcp.json` (workspace-level — the portable\n * choice; `~/.gemini/settings.json` is the global alternative, documented).\n */\nexport const geminiAdapter: HostAdapter = {\n id: 'gemini',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n // Gemini's `@file` import: bare `@.noir/...` (no `@import`, no quotes).\n // Rules folded into the same GEMINI.md (per spec — Gemini has no separate\n // rules file). Both blocks are marker-wrapped so user content survives sync.\n return (\n `${CONTEXT_BLOCK_BEGIN}\\n@.noir/NOIR.md\\n${CONTEXT_BLOCK_END}\\n` +\n `${RULES_BLOCK.begin}\\n@.noir/rules/RULES.md\\n${RULES_BLOCK.end}\\n`\n );\n },\n // No `emitRules` — rules folded into emitContext (GEMINI.md carries both).\n // No `skillsDir` — no skill concept; GEMINI.md + AGENTS.md are the surface.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.gemini', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `opencode` host adapter — OpenCode. Reads the universal `AGENTS.md`\n * (context + rules unified — no separate rules emission, no skill concept).\n *\n * The DIFFERENCE from the `{mcpServers}` family lives in MCP config: OpenCode's\n * root `opencode.json` carries an `mcp` block whose entries are `type`-tagged —\n * `{ type: 'local', command: [...] }` for stdio (note: `command` is an ARRAY,\n * not the claude `{command, args}` split) and `{ type: 'remote', url }` for\n * remote (HTTP/SSE). Server env vars land under the `environment` key (NOT\n * `env`). The `$schema` key pins the opencode config schema. See\n * https://opencode.ai/docs/mcp-servers/ + https://opencode.ai/config.json.\n *\n * Verified verbatim from the opencode docs (\"Add remote MCP servers by setting\n * `type` to `\\\"remote\\\"`\"); the prior `type:'http'` / `env:` shape was wrong.\n */\nexport const opencodeAdapter: HostAdapter = {\n id: 'opencode',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // OpenCode's `mcp` block — entries carry an explicit `type` tag (verified\n // against https://opencode.ai/docs/mcp-servers/):\n // - stdio → { type: 'local', command: [...] } (command is an ARRAY)\n // - remote → { type: 'remote', url: ... } (HTTP/SSE — NOT 'http')\n // Server env vars land under `environment:` (NOT `env:` — opencode's own\n // spelling). Noir server always present; optional integration merges\n // alongside under its `serverName`. (Does NOT use buildMcpServersJson —\n // different shape; that helper's `env:` is correct for `.mcp.json` /\n // `.cursor/mcp.json` / `.gemini/mcp.json`.)\n const mcp: Record<string, unknown> = {\n noir:\n opts.transport === 'stdio'\n ? { type: 'local', command: ['noir', 'mcp', 'serve', '--stdio'] }\n : { type: 'remote', url: opts.url ?? 'http://127.0.0.1:0/mcp' },\n };\n if (integration) {\n mcp[integration.serverName] =\n integration.transport === 'http'\n ? {\n type: 'remote',\n url: integration.url ?? '',\n ...(integration.env ? { environment: integration.env } : {}),\n }\n : {\n type: 'local',\n command: [integration.command, ...(integration.args ?? [])],\n ...(integration.env ? { environment: integration.env } : {}),\n };\n }\n return JSON.stringify({ $schema: 'https://opencode.ai/config.json', mcp }, null, 2);\n },\n emitContext(ctx: EmitContext): string {\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN AGENTS.md (the @-import covers RULES.md).\n // No `skillsDir` — no skill concept.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, 'opencode.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","// Host handoff helpers. The single source for the \"open your host CLI\"\n// directive text shared by the home banner (`noir` bare) and the handoff\n// artifact (`noir handoff`). TEXT ONLY — Noir NEVER launches the host (doctrine:\n// the host-launch directive is pasteable text, never a spawn). A host that wants\n// richer handoff wording implements `HostAdapter.emitHandoff`; the CLI falls\n// back to {@link hostLaunchDirective} when that hook is absent.\n\nimport type { EmitContext, HandoffPayload, HostId } from './types.js';\n\n/**\n * The readonly host list mirrored from {@link HostId} so this module has NO\n * import edge back to `index.ts` (which re-exports THIS module) — avoids a\n * module-eval cycle. `registry.test.ts` pins `SUPPORTED_HOSTS` to the same five\n * literals, so a new host added to `HostId` surfaces here via that test.\n */\nconst HOSTS: readonly HostId[] = ['claude', 'agents-md', 'gemini', 'cursor', 'opencode'];\n\n/**\n * One-line host-direction directive (refactored from the home-banner line).\n * Tells the user to open their configured host CLI to do the actual development\n * (Noir is the orchestration/context/memory brain; the host is the execution\n * engine — bring-your-own-agent). Host-agnostic via the {@link HOSTS}\n * registry; lists the alternatives so a multi-host user knows their options.\n *\n * This is the SINGLE source — the home banner AND the handoff artifact both\n * call it so the wording never drifts. A host-specific override lives on\n * {@link HostAdapter.emitHandoff}, not here.\n */\nexport function hostLaunchDirective(host: HostId): string {\n const others = HOSTS.filter((h) => h !== host);\n return `→ host: ${host}. Open \\`${host}\\` to start development — Noir set the rules, skills, and memory; ${host} runs the code. (other hosts: ${others.join(', ')})`;\n}\n\n/**\n * The default handoff directive block (multi-line) used when a host adapter\n * does NOT implement {@link HostAdapter.emitHandoff}. Composes the generic\n * {@link hostLaunchDirective} line plus a reminder that the MCP wire is already\n * configured (`.mcp.json` / host equivalent), so the host can call Noir's tools\n * the moment it starts. The CLI passes this as the \"Open host\" portion of the\n * handoff artifact.\n */\nexport function defaultHandoffBlock(_ctx: EmitContext, payload: HandoffPayload): string {\n const directive = hostLaunchDirective(payload.host);\n return `${directive}\\nThe Noir MCP server is already wired via the host's MCP config — call \\`noir.*\\` tools once \\`${payload.host}\\` is open.`;\n}\n","import { agentsMdAdapter } from './agents-md-adapter.js';\nimport { claudeAdapter } from './claude.js';\nimport { cursorAdapter } from './cursor.js';\nimport { geminiAdapter } from './gemini.js';\nimport { opencodeAdapter } from './opencode.js';\nimport type { HostAdapter, HostId } from './types.js';\n\nexport { AGENTS_MD_FILENAME, emitAgentsMd } from './agents-md.js';\nexport { agentsMdAdapter } from './agents-md-adapter.js';\nexport { claudeAdapter } from './claude.js';\nexport { cursorAdapter } from './cursor.js';\nexport { geminiAdapter } from './gemini.js';\nexport { defaultHandoffBlock, hostLaunchDirective } from './handoff.js';\nexport { buildMcpServersJson } from './mcp.js';\nexport { opencodeAdapter } from './opencode.js';\nexport type {\n EmitContext,\n HandoffPayload,\n HostAdapter,\n HostId,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/** The readonly list of supported hosts — derived from the `HostId` union so it\n * stays in lockstep with the type. Consumers (cli `--host` flag, doctor\n * reporting) use this for \"is this host valid?\" + iteration. Order is the\n * declaration order in `HostId` (claude first — the default).\n *\n * `Object.freeze` enforces the `readonly` type at runtime — a stray\n * `SUPPORTED_HOSTS.push('qwen')` from a JS caller fails loudly instead of\n * silently corrupting the registry list (TS already prevents it in typed code). */\nexport const SUPPORTED_HOSTS: readonly HostId[] = Object.freeze([\n 'claude',\n 'agents-md',\n 'gemini',\n 'cursor',\n 'opencode',\n]);\n\n/**\n * Resolve a host id to its `HostAdapter`. The registry is a `Record<HostId,\n * HostAdapter>` so the type system enforces completeness — adding a host to\n * `HostId` requires wiring it here (TS errors otherwise). The CLI uses this\n * indirection instead of importing adapters directly so adding a host needs NO\n * CLI edits beyond the `--host` flag's enum.\n *\n * S10-Adapters: all five hosts are now wired —\n * - `claude` — CLAUDE.md + `.claude/skills/` + `.mcp.json` (regression anchor).\n * - `agents-md` — universal AGENTS.md (the 32-platform baseline).\n * - `gemini` — GEMINI.md + AGENTS.md + `.gemini/mcp.json`.\n * - `cursor` — AGENTS.md + `.cursor/rules/*.mdc` + `.cursor/mcp.json`.\n * - `opencode` — AGENTS.md + `opencode.json` (different MCP shape).\n *\n * Unknown/non-`HostId` strings are impossible to pass at compile time (the\n * signature accepts only `HostId`); the runtime fallback is defensive — a\n * JS caller ignoring types still gets a clear error, not a silent `undefined`.\n */\nexport function resolveAdapter(host: HostId): HostAdapter {\n switch (host) {\n case 'claude':\n return claudeAdapter;\n case 'agents-md':\n return agentsMdAdapter;\n case 'gemini':\n return geminiAdapter;\n case 'cursor':\n return cursorAdapter;\n case 'opencode':\n return opencodeAdapter;\n default: {\n // Exhaustiveness guard — if `HostId` gains a member and this switch is\n // not updated, TS narrows `host` to `never` here. At runtime (untyped JS\n // callers) we still surface a clear message.\n const _exhaustive: never = host;\n throw new Error(`Unsupported host: ${String(_exhaustive)} (not in registry)`);\n }\n }\n}\n"],"mappings":"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","names":["join","join","join","join","join","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","join","join","join"]}
1
+ {"version":3,"sources":["../src/agents-md-adapter.ts","../src/agents-md.ts","../src/mcp.ts","../src/claude.ts","../src/cursor.ts","../src/gemini.ts","../src/opencode.ts","../src/handoff.ts","../src/index.ts"],"sourcesContent":["import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `agents-md` host adapter — the 32-platform universal AGENTS.md standard.\n * The SMALLEST surface: a root `AGENTS.md` (context + rules unified — the\n * `@.noir/` imports already cover both NOIR.md and RULES.md), no skill concept\n * (the context IS the surface), and a workspace `.mcp.json` (the Claude shape —\n * broadly compatible; many AGENTS.md readers also read it).\n *\n * This adapter is the fallback / universal baseline — any host that \"just reads\n * AGENTS.md\" (incl. qwen/agy and other deferred hosts) behaves identically to\n * this. Per-host specialization lives in the other adapters' native files.\n */\nexport const agentsMdAdapter: HostAdapter = {\n id: 'agents-md',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // The universal AGENTS.md content — byte-identical across every adapter\n // that composes `emitAgentsMd` (agents-md / gemini / cursor / opencode).\n return emitAgentsMd(ctx);\n },\n // No `emitRules` — rules live IN the AGENTS.md content already (the\n // `@.noir/rules/RULES.md` import covers them).\n // No `skillsDir` — no skill concept for the universal host.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { basename } from 'node:path';\nimport type { EmitContext } from './types.js';\n\n/** The canonical filename for the universal AGENTS.md emitter. Every host\n * writes the SAME content (single source of truth) — adapters may place it at\n * different paths via `HostAdapter#agentsMdPath`, but the filename is constant. */\nexport const AGENTS_MD_FILENAME = 'AGENTS.md';\n\n/**\n * The universal AGENTS.md emitter — a SHARED HELPER composed by every adapter\n * (NOT a HostAdapter itself). AGENTS.md is now the cross-tool standard (32+\n * platforms read it natively — Claude Code, OpenAI Codex, Cursor, Gemini CLI,\n * JetBrains Junie, …), so the SAME file content works for every host: it\n * `@`-imports the canonical `.noir/` sources. Per-host specialization lives in\n * each adapter's OWN native files (CLAUDE.md, GEMINI.md, .cursor/rules/*.mdc,\n * opencode.json); AGENTS.md is the always-emitted universal baseline.\n *\n * Content (byte-identical across hosts):\n *\n * # <projectName> — Noir-managed agent context\n *\n * > Generated by `noir`. Canonical source lives in `.noir/`. Edit there; re-run `noir sync`.\n *\n * > Noir manages this project's context under `.noir/`. Edit `.noir/NOIR.md`\n * > (project brief) + `.noir/rules/RULES.md` (working contract); re-run\n * > `noir sync` to re-emit host files.\n *\n * @.noir/NOIR.md\n * @.noir/rules/RULES.md\n *\n * The inline 3-line fallback sits BEFORE the `@`-imports so AGENTS.md\n * readers that do NOT resolve `@`-imports (e.g. plain markdown viewers, some\n * GA dashboards) still get a one-glance summary of where to edit. The\n * `@`-imports remain canonical for the hosts that DO resolve them (Cursor,\n * Codex, Junie, …).\n *\n * `<projectName>` is derived from `ctx.root`'s basename — a best-effort label\n * for human readability. If the basename is empty/undeterminable, falls back to\n * the literal `project` so the heading is never malformed.\n */\nexport function emitAgentsMd(ctx: EmitContext): string {\n const projectName = deriveProjectName(ctx.root);\n return (\n `# ${projectName} — Noir-managed agent context\\n\\n` +\n `> Generated by \\`noir\\`. Canonical source lives in \\`.noir/\\`. Edit there; re-run \\`noir sync\\`.\\n\\n` +\n `> Noir manages this project's context under \\`.noir/\\`. Edit \\`.noir/NOIR.md\\` ` +\n `(project brief) + \\`.noir/rules/RULES.md\\` (working contract); re-run \\`noir sync\\` ` +\n `to re-emit host files.\\n\\n` +\n `@.noir/NOIR.md\\n` +\n `@.noir/rules/RULES.md\\n`\n );\n}\n\n/** Derive a human-readable project label from the root path's basename.\n * Trims trailing slashes (POSIX) so `/p/demo/` → `demo`; falls back to the\n * literal `project` when the basename is empty (e.g. root was `/` or `''`). */\nfunction deriveProjectName(root: string): string {\n // `basename` already handles trailing slashes on POSIX; guard the empty case.\n const name = basename(root.replace(/\\/+$/, '')) || basename(root);\n return name && name.length > 0 ? name : 'project';\n}\n","import type { IntegrationMcpEmission, McpConfigOptions } from './types.js';\n\n/**\n * Build the host MCP config JSON string — the `{mcpServers: {...}}` shape shared\n * by claude (`.mcp.json`), agents-md (`.mcp.json`), gemini (`.gemini/mcp.json`),\n * and cursor (`.cursor/mcp.json`). OpenCode uses a DIFFERENT shape (an `mcp`\n * block with `type`-tagged entries) and does NOT use this helper — see\n * `opencode.ts`.\n *\n * The Noir server entry is always present; an optional integration entry merges\n * alongside it (per the Slice X adapter contract — only `external-mcp`\n * integrations surface a `hostMcp` block by the time it reaches here).\n *\n * Refactored out of `claude.ts` in S10-Adapters so every `{mcpServers}`-shape\n * host emits byte-identical JSON. Claude's `emitMcpConfig` now delegates here\n * (the claude.test.ts + create/scaffold.test.ts parity gates must hold).\n *\n * Stdio entry: `{ command, args }`.\n * HTTP entry : `{ type: 'http', url, [env] }` — env nested under `env:`, never\n * spread at the entry top level (would corrupt the server-field shape).\n */\nexport function buildMcpServersJson(\n opts: McpConfigOptions,\n integration?: IntegrationMcpEmission,\n): string {\n // Always present: the Noir MCP server — `noir mcp serve --stdio` or the\n // streamable-http endpoint. The placeholder URL (`:0`) is a best-effort hint\n // the user edits; same behavior as the original claude implementation.\n // 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 // pre-S10 inline implementation — the claude.test.ts + create/scaffold.test.ts\n // byte-equality parity gates must hold (Slice X integration merge preserved).\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n return `${CONTEXT_BLOCK_BEGIN}\\n@import \".noir/NOIR.md\"\\n${CONTEXT_BLOCK_END}\\n`;\n },\n emitRules(_ctx: EmitContext): string {\n return `${RULES_BLOCK.begin}\\n@import \".noir/rules/RULES.md\"\\n${RULES_BLOCK.end}\\n`;\n },\n skillsDir(ctx: EmitContext): string {\n return join(ctx.root, '.claude', 'skills');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `cursor` host adapter — Cursor. Cursor reads the universal `AGENTS.md`\n * for context (same content as every other host); that file's\n * `@.noir/rules/RULES.md` import IS the Noir working-rules surface for cursor\n * (NO separate `.cursor/rules/noir-contract.mdc` host-rules pointer — that\n * file was REMOVED: it was `noir-`-prefixed and the cursor flat-skill prune\n * in `emitSkillsToDir` deleted it on every `noir init/create/sync --host\n * cursor`). Skills compile to FLAT `.mdc` in `.cursor/rules/` via\n * `compileSkill(_, 'cursor')` (one file per skill, no per-name subdir). MCP\n * config lands at `.cursor/mcp.json`.\n *\n * `emitContext` returns the universal AGENTS.md content (the single native\n * context surface for cursor). There is NO `emitRules` here — cursor's rules\n * are delivered via AGENTS.md's `@.noir/rules/RULES.md` import, identical to\n * agents-md/opencode.\n */\nexport const cursorAdapter: HostAdapter = {\n id: 'cursor',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(ctx: EmitContext): string {\n // Cursor reads AGENTS.md — same universal content as every other host.\n return emitAgentsMd(ctx);\n },\n skillsDir(ctx: EmitContext): string {\n // Skills compile to `.mdc` here (via `compileSkill(_, 'cursor')` in skills).\n return join(ctx.root, '.cursor', 'rules');\n },\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.cursor', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { CONTEXT_BLOCK_BEGIN, CONTEXT_BLOCK_END, RULES_BLOCK } from '@noir-ai/core';\nimport { buildMcpServersJson } from './mcp.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `gemini` host adapter — Gemini CLI. Emits `GEMINI.md` (Gemini's native\n * context file) carrying BOTH the context block AND the rules block — Gemini\n * has no separate rules file. Uses Gemini's `@file` import syntax: bare\n * `@.noir/NOIR.md` (no `@import` keyword, no quotes — distinct from Claude's\n * `@import \".noir/...\"` form). The cli ALSO emits a root `AGENTS.md` via the\n * shared `emitAgentsMd` helper (Gemini reads AGENTS.md too).\n *\n * Managed-block form (CONTEXT_BLOCK + RULES_BLOCK markers) is used so user\n * content outside the markers survives `noir sync` rewrites — consistent with\n * the claude adapter's contract.\n *\n * MCP config lands at `.gemini/mcp.json` (workspace-level — the portable\n * choice; `~/.gemini/settings.json` is the global alternative, documented).\n */\nexport const geminiAdapter: HostAdapter = {\n id: 'gemini',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n return buildMcpServersJson(opts, integration);\n },\n emitContext(_ctx: EmitContext): string {\n // Gemini's `@file` import: bare `@.noir/...` (no `@import`, no quotes).\n // Rules folded into the same GEMINI.md (per spec — Gemini has no separate\n // rules file). Both blocks are marker-wrapped so user content survives sync.\n return (\n `${CONTEXT_BLOCK_BEGIN}\\n@.noir/NOIR.md\\n${CONTEXT_BLOCK_END}\\n` +\n `${RULES_BLOCK.begin}\\n@.noir/rules/RULES.md\\n${RULES_BLOCK.end}\\n`\n );\n },\n // No `emitRules` — rules folded into emitContext (GEMINI.md carries both).\n // No `skillsDir` — no skill concept; GEMINI.md + AGENTS.md are the surface.\n mcpConfigPath(ctx: EmitContext): string {\n return join(ctx.root, '.gemini', 'mcp.json');\n },\n agentsMdPath(ctx: EmitContext): string {\n return join(ctx.root, 'AGENTS.md');\n },\n};\n","import { join } from 'node:path';\nimport { emitAgentsMd } from './agents-md.js';\nimport type {\n EmitContext,\n HostAdapter,\n IntegrationMcpEmission,\n McpConfigOptions,\n} from './types.js';\n\n/**\n * The `opencode` host adapter — OpenCode. Reads the universal `AGENTS.md`\n * (context + rules unified — no separate rules emission, no skill concept).\n *\n * The DIFFERENCE from the `{mcpServers}` family lives in MCP config: OpenCode's\n * root `opencode.json` carries an `mcp` block whose entries are `type`-tagged —\n * `{ type: 'local', command: [...] }` for stdio (note: `command` is an ARRAY,\n * not the claude `{command, args}` split) and `{ type: 'remote', url }` for\n * remote (HTTP/SSE). Server env vars land under the `environment` key (NOT\n * `env`). The `$schema` key pins the opencode config schema. See\n * https://opencode.ai/docs/mcp-servers/ + https://opencode.ai/config.json.\n *\n * Verified verbatim from the opencode docs (\"Add remote MCP servers by setting\n * `type` to `\\\"remote\\\"`\"); the prior `type:'http'` / `env:` shape was wrong.\n */\nexport const opencodeAdapter: HostAdapter = {\n id: 'opencode',\n emitMcpConfig(_ctx, opts: McpConfigOptions, integration?: IntegrationMcpEmission): string {\n // OpenCode's `mcp` block — entries carry an explicit `type` tag (verified\n // against https://opencode.ai/docs/mcp-servers/):\n // - stdio → { type: 'local', command: [...] } (command is an ARRAY)\n // - remote → { type: 'remote', url: ... } (HTTP/SSE — NOT 'http')\n // Server env vars land under `environment:` (NOT `env:` — opencode's own\n // spelling). Noir server always present; optional integration merges\n // alongside under its `serverName`. (Does NOT use buildMcpServersJson —\n // different shape; that helper's `env:` is correct for `.mcp.json` /\n // `.cursor/mcp.json` / `.gemini/mcp.json`.)\n const mcp: Record<string, unknown> = {\n noir:\n opts.transport === 'stdio'\n ? // 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 * S10-Adapters: all five hosts are now wired —\n * - `claude` — CLAUDE.md + `.claude/skills/` + `.mcp.json` (regression anchor).\n * - `agents-md` — universal AGENTS.md (the 32-platform baseline).\n * - `gemini` — GEMINI.md + AGENTS.md + `.gemini/mcp.json`.\n * - `cursor` — AGENTS.md + `.cursor/rules/*.mdc` + `.cursor/mcp.json`.\n * - `opencode` — AGENTS.md + `opencode.json` (different MCP shape).\n *\n * Unknown/non-`HostId` strings are impossible to pass at compile time (the\n * signature accepts only `HostId`); the runtime fallback is defensive — a\n * JS caller ignoring types still gets a clear error, not a silent `undefined`.\n */\nexport function resolveAdapter(host: HostId): HostAdapter {\n switch (host) {\n case 'claude':\n return claudeAdapter;\n case 'agents-md':\n return agentsMdAdapter;\n case 'gemini':\n return geminiAdapter;\n case 'cursor':\n return cursorAdapter;\n case 'opencode':\n return opencodeAdapter;\n default: {\n // Exhaustiveness guard — if `HostId` gains a member and this switch is\n // not updated, TS narrows `host` to `never` here. At runtime (untyped JS\n // callers) we still surface a clear message.\n const _exhaustive: never = host;\n throw new Error(`Unsupported host: ${String(_exhaustive)} (not in registry)`);\n }\n }\n}\n"],"mappings":"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","names":["join","join","join","join","join","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","CONTEXT_BLOCK_BEGIN","CONTEXT_BLOCK_END","RULES_BLOCK","join","join","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noir-ai/adapters",
3
- "version": "1.7.2",
3
+ "version": "1.7.3",
4
4
  "description": "Noir adapters — the HostAdapter abstraction and the Claude Code adapter (the v1 host).",
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.7.2"
46
+ "@noir-ai/core": "1.7.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^26.1.1"