@hecer/yoke 1.14.0 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +13 -9
  3. package/dist/cli.js +16 -2
  4. package/dist/code-intelligence/adapters/graft.js +11 -0
  5. package/dist/code-intelligence/adapters/graphify.js +8 -0
  6. package/dist/code-intelligence/adapters/index.js +5 -0
  7. package/dist/code-intelligence/adapters/mcp.js +37 -0
  8. package/dist/code-intelligence/adapters/serena.js +11 -0
  9. package/dist/code-intelligence/adapters/types.js +1 -0
  10. package/dist/code-intelligence/contracts.js +137 -0
  11. package/dist/code-intelligence/coordinator.js +370 -0
  12. package/dist/code-intelligence/edit-plans.js +53 -0
  13. package/dist/code-intelligence/evidence.js +77 -0
  14. package/dist/code-intelligence/index.js +5 -0
  15. package/dist/code-intelligence/internal-types.js +1 -0
  16. package/dist/code-intelligence/mcp-client.js +139 -0
  17. package/dist/code-intelligence/mcp-server.js +93 -0
  18. package/dist/code-intelligence/snapshots.js +117 -0
  19. package/dist/code-intelligence/transactions.js +60 -0
  20. package/dist/retrofit/command.js +3 -1
  21. package/dist/retrofit/config.js +10 -0
  22. package/dist/retrofit/gitignore.js +1 -0
  23. package/dist/retrofit/plan.js +3 -3
  24. package/dist/retrofit/planners/claude.js +3 -3
  25. package/dist/retrofit/planners/codex.js +5 -5
  26. package/dist/retrofit/planners/gemini.js +2 -2
  27. package/dist/retrofit/planners/kilo.js +2 -2
  28. package/dist/retrofit/planners/opencode.js +2 -2
  29. package/dist/retrofit/planners/pi.js +1 -1
  30. package/dist/retrofit/planners/qwen.js +2 -2
  31. package/dist/retrofit/tools.js +9 -7
  32. package/dist/setup/command.js +3 -1
  33. package/docs/CODE-INTELLIGENCE.md +36 -0
  34. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.15.0 — 2026-09-09
4
+
5
+ ### Added
6
+ - Add opt-in federated Code Intelligence that composes Graft, Graphify and Serena behind one Yoke-controlled MCP facade with six stable tools for context, symbols, traces, impact and edit workflows.
7
+ - Add pinned backend adapters, explicit coverage/provenance/freshness evidence, content-addressed workspace snapshots and bounded local policy checks.
8
+ - Add isolated edit previews and guarded apply transactions with approval, snapshot freshness, exclusive locking and idempotency checks; Yoke's existing review, verify and commit gates remain authoritative.
9
+
10
+ ### Changed
11
+ - Add `off`, `shadow` and `active` Code Intelligence modes to setup and retrofit. `off` preserves the legacy `codeGraph` path unchanged; `shadow` is read-only; `active` enables preview and approved edits.
12
+ - Keep backend runtimes external and configurable instead of vendoring or silently installing them. Pin the validated integration targets to Graft `0.17.0`, Graphify `0.9.56` and Serena `1.7.1-dev`.
13
+ - Add the [Code Intelligence guide](docs/CODE-INTELLIGENCE.md), including setup, backend requirements, safety boundaries, limitations and the Pi integration note.
14
+
15
+ ### Migration and validation limits
16
+ - No migration is required. Existing projects remain on the legacy path until `yoke setup` or `yoke retrofit` is run with `--code-intelligence=shadow` or `--code-intelligence=active`.
17
+ - Validated with the Code Intelligence contract/coordinator/snapshot/MCP tests, TypeScript lint/build, documentation metadata, package dry run and the facade MCP handshake. The full suite retains one pre-existing provider-process timing failure; it is reproduced independently and is not caused by this release.
18
+ - This release does not claim that every language or backend is available in every environment. Backend failures are surfaced as partial coverage or an explicit unavailable capability, never silently treated as complete evidence.
19
+
3
20
  ## 1.14.0 — 2026-09-09
4
21
 
5
22
  ### Added
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  <div align="center">
2
2
 
3
- <h1><img src="https://raw.githubusercontent.com/HECer/yoke/v1.14.0/docs/assets/yoke-logo.png" alt="Yoke" width="100" height="63"></h1>
3
+ <h1><img src="https://raw.githubusercontent.com/HECer/yoke/v1.15.0/docs/assets/yoke-logo.png" alt="Yoke" width="100" height="63"></h1>
4
4
 
5
- <!-- yoke:version:start -->1.14.0<!-- yoke:version:end -->
6
- <!-- yoke:tests:start -->1258<!-- yoke:tests:end -->
5
+ <!-- yoke:version:start -->1.15.0<!-- yoke:version:end -->
6
+ <!-- yoke:tests:start -->1268<!-- yoke:tests:end -->
7
7
  <!-- yoke:skills:start -->34<!-- yoke:skills:end -->
8
8
  <!-- yoke:agents:start -->Claude | Codex | Gemini | Qwen | OpenCode | Kilo | Pi<!-- yoke:agents:end -->
9
9
 
@@ -17,7 +17,7 @@
17
17
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](#-license)
18
18
  ![Node](https://img.shields.io/badge/node-%E2%89%A520-339933?logo=node.js&logoColor=white)
19
19
  ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white)
20
- ![Tests](https://img.shields.io/badge/tests-1258%20defined-blue.svg)
20
+ ![Tests](https://img.shields.io/badge/tests-1268%20defined-blue.svg)
21
21
  ![Agents](https://img.shields.io/badge/agents-Claude%20%7C%20Codex%20%7C%20Gemini%20%7C%20Qwen%20%7C%20OpenCode%20%7C%20Kilo%20%7C%20Pi-8A2BE2)
22
22
  ![Built with TDD](https://img.shields.io/badge/built%20with-TDD%20%2B%20review-ff69b4.svg)
23
23
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  > **TL;DR** — `yoke setup .` asks six questions and installs the native harness for your agent. `yoke new my-app --idea="..."` bootstraps a project and drafts its story backlog. `yoke loop run my-app --isolate --review` then implements it behind hard gates: **clean tree → acceptance criteria → your real tests green → an independent model approves → commit**. Add `--parallel=N` for dependency-aware workers, or declare a reference and add `--quality` for a bounded critic/repair gauntlet. If any blocking gate is red, nothing is committed. Proof lives in `.yoke/proof/<story>/`.
29
29
 
30
- **New in 1.13.0:** first-class [OpenCode, Kilo and Pi integrations](docs/HARNESSES.md), including native headless invocation, provider/model/variant routing, retrofit artifacts, role configuration and provider telemetry. [Qwen Code hardening and explicit DeepSeek/Kimi API model profiles](docs/QWEN-MODEL-SUPPORT.md) remain available. Since 1.10.0, Yoke also includes [dashboard search, filters and period comparisons](docs/DASHBOARD-EVOLUTION.md), [batch task assessments with separate planning models](docs/CAPABILITY-ROUTING.md), and [Windows sandbox preflight and process supervision](docs/WINDOWS-RUNNER-VALIDATION.md). Existing routing settings remain authoritative. See the [changelog](CHANGELOG.md) and the [harness integration guide](docs/HARNESSES.md) for limitations and setup.
30
+ **New in 1.15.0:** federated [Code Intelligence](docs/CODE-INTELLIGENCE.md) composes Graft, Graphify and Serena behind one Yoke-controlled MCP surface, with structural and semantic evidence, content-addressed snapshots, partial-coverage reporting and isolated edit previews. It is opt-in: use `off` for the unchanged legacy path, `shadow` for read-only canaries, or `active` for previews and approved edits. The [1.14.0 dashboard overhaul](docs/DASHBOARD-OVERHAUL.md) and first-class [OpenCode, Kilo and Pi integrations](docs/HARNESSES.md) remain available. See the [changelog](CHANGELOG.md) and [Code Intelligence guide](docs/CODE-INTELLIGENCE.md) for setup and limitations.
31
31
 
32
32
  OpenCode, Kilo and Pi are real CLI integrations, not bundled runtimes or credentials. OpenCode/Kilo use their JSON headless modes and local MCP configuration; Pi uses JSONL and explicit tool allowlists, but has no native MCP, sub-agent or plan layer. Read the [integration guide](docs/HARNESSES.md) before selecting a permission profile.
33
33
 
@@ -208,10 +208,11 @@ Yoke's CLI is deterministic and chainable by design: an agent (or a shell `&&`)
208
208
  | `yoke projects add\|list\|remove` | Register a project, list registrations or remove a reference by ID | `0` · `2` invalid/unavailable |
209
209
  | `yoke check [dir] [--json] [--requirement=] [--protect [--refresh]]` | Execute acceptance checks or explicitly pin their infrastructure | `0` passed/pinned · `1` failed · `2` unverified/unavailable |
210
210
  | `yoke goal set\|run\|resume\|pause\|status\|handoff\|budget [dir]` | Durable objectives, provider handoff, protected checks and checkpoint budgets | run/resume: `0` complete · `1` unfinished · `2` unavailable |
211
- | `yoke setup [dir] [--yes] [--host=] [--agent=] [--runner=] [--code-graph=] [--decision-policy=] [--loop\|--no-loop] [--routing\|--no-routing] [--model-provider=deepseek,kimi]` | Shared setup for all seven harnesses; optional DeepSeek/Kimi API profiles run through Qwen | `0` · `1` invalid setup |
211
+ | `yoke setup [dir] [--yes] [--host=] [--agent=] [--runner=] [--code-graph=] [--code-intelligence=off\|shadow\|active] [--decision-policy=] [--loop\|--no-loop] [--routing\|--no-routing] [--model-provider=deepseek,kimi]` | Shared setup for all seven harnesses; optional federated code intelligence and DeepSeek/Kimi API profiles | `0` · `1` invalid setup |
212
+ | `yoke code-intelligence-server [--workspace=] [--mode=off\|shadow\|active]` | Serve the single Yoke-controlled MCP facade for federated code intelligence | `0` · `1` invalid/unavailable |
212
213
  | `yoke validate [canonDir]` | Validate the canon (schema, frontmatter, templates) | `0` valid · `1` errors |
213
214
  | `yoke new <dir> [--idea=] [--agent=] [--runner=] [--loop]` | Greenfield bootstrap: git init → scaffold → retrofit → context → PRD (drafted from `--idea`) → committed | `0` · `1` usage / non-empty dir / draft failed (scaffold survives) · `2` draft agent unavailable |
214
- | `yoke retrofit [dir] [--agent=claude,codex,gemini,qwen,opencode,kilo,pi\|all] [--code-graph=graphify\|serena] [--loop]` | Install/update the harness for the selected agents, non-destructively | `0` |
215
+ | `yoke retrofit [dir] [--agent=claude,codex,gemini,qwen,opencode,kilo,pi\|all] [--code-graph=graphify\|serena] [--code-intelligence=off\|shadow\|active] [--loop]` | Install/update the harness for the selected agents, non-destructively | `0` |
215
216
  | `yoke prd draft [dir] --idea= [--runner=] [--force]` | Idea → 5–12 stories with testable acceptance criteria | `0` · `1` invalid/guarded · `2` agent unavailable |
216
217
  | `yoke prd check [dir]` | PRD lint gate (schema, dependencies, cycles, duplicate ids, acceptance) | `0` valid · `1` violations |
217
218
  | `yoke change add\|status [dir] [--idea=]` | Queue a change at any time; the loop turns it into append-only stories at the next safe boundary | `0` · `1` invalid inbox/request |
@@ -852,7 +853,7 @@ Yoke's guardrails are **mechanical, not advisory** — the loop blocks on a dirt
852
853
 
853
854
  ## 🧠 Choose your code-graph
854
855
 
855
- `yoke retrofit --code-graph=graphify|serena` (default `graphify`, remembered per project). The `yoke-retrofit` skill asks and recommends based on the project.
856
+ `yoke retrofit --code-graph=graphify|serena` (default `graphify`, remembered per project) selects the legacy single graph. For complete code intelligence, enable the federated facade with `yoke retrofit --code-intelligence=active` (or `shadow` for a read-only canary). See the [Code Intelligence guide](docs/CODE-INTELLIGENCE.md).
856
857
 
857
858
  | | **graphify** | **Serena** |
858
859
  |---|---|---|
@@ -862,6 +863,8 @@ Yoke's guardrails are **mechanical, not advisory** — the loop blocks on a dirt
862
863
  | Best for | rapid exploration / migration / onboarding | systematic refactoring in typed codebases |
863
864
  | Caveat | heuristic edges; static index can go stale | one language server per language |
864
865
 
866
+ The federated mode composes both structural and semantic evidence and adds Graphify's architecture/document graph. It exposes one Yoke-controlled MCP surface, content-addressed snapshots, partial-coverage reporting and isolated edit previews; the legacy `codeGraph` setting remains valid and unchanged when code intelligence is `off`.
867
+
865
868
  ## 🪙 Token efficiency
866
869
 
867
870
  Yoke attacks tokens on two complementary surfaces:
@@ -903,6 +906,7 @@ canon/ # the source of truth — harness-agnostic
903
906
  AGENTS.md skills/ policy/ loop/ tools/ manifest.yaml
904
907
  src/
905
908
  canon/ # manifest schema + validator (yoke validate)
909
+ code-intelligence/ # federated MCP facade, adapters, snapshots and guarded edits
906
910
  change/ # append-only change inbox · planning · independent coverage review
907
911
  retrofit/ # detect · plan · apply · planners (all seven harnesses) · tools
908
912
  loop/ # prd · gates · runner · verify · git/worktree · loop · run-command · lock · cleanup
@@ -925,7 +929,7 @@ release provenance.
925
929
  ## 🧪 Development
926
930
 
927
931
  ```bash
928
- npm test # vitest (1258 tests)
932
+ npm test # vitest (1268 tests)
929
933
  npm run build # tsc, no emit errors
930
934
  npm run yoke -- validate canon
931
935
  ```
package/dist/cli.js CHANGED
@@ -22,6 +22,7 @@ import { runUpgrade } from './update/upgrade.js';
22
22
  import { AGENT_LIST, isSupportedAgent, SUPPORTED_AGENTS } from './agents/catalog.js';
23
23
  import { printAudit, runAudit } from './audit/command.js';
24
24
  import { runSetup } from './setup/command.js';
25
+ import { runCodeIntelligenceServer } from './code-intelligence/mcp-server.js';
25
26
  import { pendingChanges, queueChange } from './change/inbox.js';
26
27
  import { answerPendingDecision, answeredDecisionResumeIsValid, clearDecisionResume, decisionProcessingExists, decisionResumeMatchesCurrent, finalizeCommittedDecisionResume, formatPendingDecision, readDecisionResume, readPendingDecision, writeDecisionResume, } from './loop/decision.js';
27
28
  export { runRetrofit } from './retrofit/command.js';
@@ -104,6 +105,8 @@ export function parseQualityFlags(args) {
104
105
  export function main(argv) {
105
106
  const [cmd, ...rest] = argv;
106
107
  switch (cmd) {
108
+ case 'code-intelligence-server':
109
+ return runCodeIntelligenceServer(rest).then(() => 0).catch(error => { console.error(`Code intelligence server: ${error.message}`); return 2; });
107
110
  case 'setup': {
108
111
  const targetDir = rest.find(a => !a.startsWith('-')) ?? '.';
109
112
  const valid = [...SUPPORTED_AGENTS];
@@ -130,6 +133,11 @@ export function main(argv) {
130
133
  console.error(`Invalid --code-graph value: ${graphArg}`);
131
134
  return 1;
132
135
  }
136
+ const intelligenceArg = rest.find(a => a.startsWith('--code-intelligence='))?.slice('--code-intelligence='.length);
137
+ if (intelligenceArg && !['off', 'shadow', 'active'].includes(intelligenceArg)) {
138
+ console.error(`Invalid --code-intelligence value: ${intelligenceArg}`);
139
+ return 1;
140
+ }
133
141
  const policyArg = rest.find(a => a.startsWith('--decision-policy='))?.slice('--decision-policy='.length);
134
142
  if (policyArg && policyArg !== 'auto' && policyArg !== 'critical') {
135
143
  console.error(`Invalid --decision-policy value: ${policyArg}`);
@@ -152,6 +160,7 @@ export function main(argv) {
152
160
  modelProviders: modelProviders,
153
161
  host: hostArg, agents, runner: runnerArg,
154
162
  codeGraph: graphArg,
163
+ codeIntelligence: intelligenceArg,
155
164
  loop, routing, decisionPolicy: policyArg,
156
165
  routingStrategy: routingStrategy,
157
166
  routingPreset: rest.includes('--routing-preset'),
@@ -291,7 +300,12 @@ export function main(argv) {
291
300
  console.error(`Invalid --code-graph value: ${cgArg} (expected graphify|serena)`);
292
301
  return 1;
293
302
  }
294
- return runRetrofit(targetDir, { loop, agents, codeGraph });
303
+ const ciArg = rest.find(a => a.startsWith('--code-intelligence='))?.slice('--code-intelligence='.length);
304
+ if (ciArg && !['off', 'shadow', 'active'].includes(ciArg)) {
305
+ console.error(`Invalid --code-intelligence value: ${ciArg} (expected off|shadow|active)`);
306
+ return 1;
307
+ }
308
+ return runRetrofit(targetDir, { loop, agents, codeGraph, codeIntelligence: ciArg });
295
309
  }
296
310
  case 'change': {
297
311
  const sub = rest[0];
@@ -657,7 +671,7 @@ export function main(argv) {
657
671
  return runUpgrade();
658
672
  default:
659
673
  console.log('Project workflows: yoke check [dir] [--json|--protect] | goal set|run|resume|pause|status|handoff|budget [dir] | projects add|list|remove | dashboard [dir] [--port=N]');
660
- console.log(`usage: yoke <setup [dir] | new <dir> [--idea="..."] | validate [canonDir] | retrofit [targetDir] [--agent=${AGENT_LIST}|all] [--code-graph=graphify|serena] [--loop] | change <add|status> [dir] | prd <draft|check|assess> [dir] | loop <on|off|status|decision|answer|resume|run|cleanup> | context <init|status> | review [dir] [--reviewer=<${AGENT_LIST}>] [--base=<ref>] [--focus="..."] | design-scan [dir] [--max=N] [--report] | flow-smoke [dir] [--url=<baseUrl>] [--label=<name>] | upgrade>`);
674
+ console.log(`usage: yoke <setup [dir] | new <dir> [--idea="..."] | validate [canonDir] | retrofit [targetDir] [--agent=${AGENT_LIST}|all] [--code-graph=graphify|serena] [--code-intelligence=off|shadow|active] [--loop] | change <add|status> [dir] | code-intelligence-server --workspace=<dir> --mode=<mode> | prd <draft|check|assess> [dir] | loop <on|off|status|decision|answer|resume|run|cleanup> | context <init|status> | review [dir] | design-scan [dir] | flow-smoke [dir] | upgrade>`);
661
675
  return cmd ? 1 : 0;
662
676
  }
663
677
  }
@@ -0,0 +1,11 @@
1
+ import { McpBackendAdapter } from './mcp.js';
2
+ export const GRAFT_VERSION = '0.17.0';
3
+ export function createGraftAdapter(cwd, override) {
4
+ return new McpBackendAdapter({
5
+ name: 'graft', version: GRAFT_VERSION, cwd, command: override?.command ?? 'graft', args: override?.args ?? ['mcp'], framing: 'line', semantic: false, documents: false,
6
+ aliases: { context: 'graft_find_code', trace: 'graft_trace_calls', file: 'graft_file_api', freshness: 'graft_check_freshness', repo_map: 'graft_repo_map' },
7
+ });
8
+ }
9
+ export function graftCall(kind, args) {
10
+ return { tool: kind, arguments: args };
11
+ }
@@ -0,0 +1,8 @@
1
+ import { McpBackendAdapter } from './mcp.js';
2
+ export const GRAPHIFY_VERSION = '0.9.56';
3
+ export function createGraphifyAdapter(cwd, override) {
4
+ return new McpBackendAdapter({
5
+ name: 'graphify', version: GRAPHIFY_VERSION, cwd, command: override?.command ?? 'python', args: override?.args ?? ['-m', 'graphify.serve'], framing: 'content-length', semantic: false, documents: true,
6
+ aliases: { context: 'query_graph', symbol: 'get_node', neighbors: 'get_neighbors', trace: 'shortest_path', impact: 'get_pr_impact' },
7
+ });
8
+ }
@@ -0,0 +1,5 @@
1
+ export * from './types.js';
2
+ export * from './mcp.js';
3
+ export * from './graft.js';
4
+ export * from './graphify.js';
5
+ export * from './serena.js';
@@ -0,0 +1,37 @@
1
+ import { McpStdioClient } from '../mcp-client.js';
2
+ function valueFromResult(value) {
3
+ if (!value || typeof value !== 'object')
4
+ return value;
5
+ if (value.structuredContent !== undefined)
6
+ return value.structuredContent;
7
+ const text = Array.isArray(value.content) ? value.content.find((item) => item?.type === 'text')?.text : undefined;
8
+ if (typeof text !== 'string')
9
+ return value;
10
+ try {
11
+ return JSON.parse(text);
12
+ }
13
+ catch {
14
+ return { text };
15
+ }
16
+ }
17
+ export class McpBackendAdapter {
18
+ name;
19
+ version;
20
+ semantic;
21
+ documents;
22
+ client;
23
+ aliases;
24
+ constructor(options) {
25
+ this.name = options.name;
26
+ this.version = options.version;
27
+ this.semantic = options.semantic;
28
+ this.documents = options.documents;
29
+ this.aliases = options.aliases;
30
+ this.client = new McpStdioClient(options.command, options.args, options.cwd, options.framing);
31
+ }
32
+ async call(request, timeoutMs) {
33
+ const tool = this.aliases[request.tool] ?? request.tool;
34
+ return valueFromResult(await this.client.call(tool, request.arguments, timeoutMs));
35
+ }
36
+ close() { return this.client.close(); }
37
+ }
@@ -0,0 +1,11 @@
1
+ import { McpBackendAdapter } from './mcp.js';
2
+ export const SERENA_VERSION = '1.7.1-dev';
3
+ export function createSerenaAdapter(cwd, override) {
4
+ return new McpBackendAdapter({
5
+ name: 'serena-lsp', version: SERENA_VERSION, cwd, command: override?.command ?? 'serena', args: override?.args ?? ['start-mcp-server', '--project-from-cwd', '--context', 'codex'], framing: 'content-length', semantic: true, documents: false,
6
+ aliases: {
7
+ symbol: 'find_symbol', references: 'find_referencing_symbols', implementations: 'find_implementations', overview: 'get_symbols_overview', diagnostics: 'get_diagnostics_for_file',
8
+ rename: 'rename_symbol', replace_symbol_body: 'replace_symbol_body', insert_before_symbol: 'insert_before_symbol', insert_after_symbol: 'insert_after_symbol', safe_delete: 'safe_delete_symbol',
9
+ },
10
+ });
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,137 @@
1
+ import { z } from 'zod';
2
+ export const CodeIntelligenceModeSchema = z.enum(['off', 'shadow', 'active']);
3
+ export const BackendNameSchema = z.enum(['graft', 'graphify', 'serena-lsp', 'serena-jetbrains', 'yoke']);
4
+ export const StatusSchema = z.enum(['success', 'partial', 'blocked', 'error']);
5
+ export const ResolutionSchema = z.enum(['resolved', 'unresolved', 'ambiguous', 'not_applicable']);
6
+ export const FreshnessSchema = z.enum(['current', 'stale', 'unknown']);
7
+ export const ProvenanceSchema = z.object({
8
+ backend: BackendNameSchema,
9
+ version: z.string().min(1),
10
+ source_path: z.string().nullable(),
11
+ content_hash: z.string().nullable(),
12
+ byte_range: z.object({ start: z.number().int().nonnegative(), end: z.number().int().nonnegative() }).nullable(),
13
+ origin: z.enum(['parser', 'resolver', 'lsp', 'ide', 'llm', 'manual']),
14
+ resolution: ResolutionSchema,
15
+ freshness: FreshnessSchema,
16
+ });
17
+ export const CoverageSchema = z.object({
18
+ backends_requested: z.array(BackendNameSchema),
19
+ backends_used: z.array(BackendNameSchema),
20
+ backends_missing: z.array(BackendNameSchema),
21
+ structural: z.enum(['complete', 'partial', 'unavailable']),
22
+ semantic: z.enum(['complete', 'partial', 'unavailable']),
23
+ documents: z.enum(['complete', 'partial', 'unavailable']),
24
+ });
25
+ export const MetricsSchema = z.object({
26
+ latency_ms: z.number().nonnegative(),
27
+ result_tokens: z.number().int().nonnegative(),
28
+ token_count_kind: z.enum(['exact', 'estimated']),
29
+ returned_bytes: z.number().int().nonnegative(),
30
+ });
31
+ export const ErrorSchema = z.object({
32
+ code: z.enum([
33
+ 'INVALID_ARGUMENT', 'WORKSPACE_DENIED', 'CAPABILITY_UNAVAILABLE', 'SNAPSHOT_STALE',
34
+ 'AMBIGUOUS_SYMBOL', 'BACKEND_TIMEOUT', 'BACKEND_UNAVAILABLE', 'BUDGET_EXCEEDED',
35
+ 'APPROVAL_REQUIRED', 'PLAN_EXPIRED', 'SCOPE_VIOLATION', 'VALIDATION_FAILED',
36
+ 'WRITE_CONFLICT', 'IDEMPOTENCY_CONFLICT', 'INTERNAL_ERROR',
37
+ ]),
38
+ message: z.string().min(1),
39
+ backend: BackendNameSchema.optional(),
40
+ });
41
+ export const ItemSchema = z.object({
42
+ item_id: z.string().min(1),
43
+ kind: z.enum(['symbol', 'code', 'document', 'memory', 'summary']),
44
+ path: z.string().nullable(),
45
+ excerpt: z.string().max(12000),
46
+ evidence_ids: z.array(z.string().min(1)),
47
+ rank: z.number().nonnegative(),
48
+ });
49
+ export const SymbolSchema = z.object({
50
+ symbol_id: z.string().min(1),
51
+ name: z.string().min(1),
52
+ path: z.string().nullable(),
53
+ kind: z.string().min(1),
54
+ signature: z.string().nullable(),
55
+ evidence_ids: z.array(z.string().min(1)),
56
+ });
57
+ export const ReferenceSchema = z.object({
58
+ symbol_id: z.string().min(1),
59
+ path: z.string().nullable(),
60
+ line: z.number().int().positive().nullable(),
61
+ excerpt: z.string().max(4000),
62
+ evidence_ids: z.array(z.string().min(1)),
63
+ });
64
+ export const DiagnosticSchema = z.object({
65
+ path: z.string().nullable(),
66
+ line: z.number().int().positive().nullable(),
67
+ severity: z.enum(['error', 'warning', 'info', 'unknown']),
68
+ message: z.string().max(4000),
69
+ evidence_ids: z.array(z.string().min(1)),
70
+ });
71
+ export const EdgeSchema = z.object({
72
+ from: z.string().min(1),
73
+ to: z.string().min(1),
74
+ relation: z.enum(['calls', 'references', 'imports', 'implements', 'extends', 'docs', 'rationale_for', 'related_to']),
75
+ evidence_ids: z.array(z.string().min(1)),
76
+ });
77
+ export const ResponseBaseSchema = z.object({
78
+ schema_version: z.literal('0.1.0'),
79
+ request_id: z.string().min(1),
80
+ workspace_id: z.string().min(1),
81
+ snapshot_id: z.string().min(1),
82
+ status: StatusSchema,
83
+ coverage: CoverageSchema,
84
+ provenance: z.array(ProvenanceSchema),
85
+ warnings: z.array(z.string()),
86
+ metrics: MetricsSchema,
87
+ artifact_uri: z.string().nullable(),
88
+ error: ErrorSchema.nullable(),
89
+ });
90
+ const BudgetSchema = z.object({
91
+ token_budget: z.number().int().min(128).max(16000).default(2400),
92
+ timeout_ms: z.number().int().min(100).max(60000).default(5000),
93
+ });
94
+ export const ContextInputSchema = z.object({
95
+ workspace_id: z.string().min(1), query: z.string().min(1).max(20000), snapshot_id: z.string().min(1).optional(),
96
+ mode: z.enum(['orient', 'locate', 'explain']).default('orient'), paths: z.array(z.string().min(1)).max(32).default([]),
97
+ include_docs: z.boolean().default(false), ...BudgetSchema.shape,
98
+ });
99
+ export const SymbolInputSchema = z.object({
100
+ workspace_id: z.string().min(1), snapshot_id: z.string().min(1), symbol_id: z.string().min(1).optional(),
101
+ query: z.string().min(1).optional(), path: z.string().min(1).optional(),
102
+ include: z.array(z.enum(['definition', 'references', 'implementations', 'diagnostics'])).default(['definition']), ...BudgetSchema.shape,
103
+ }).refine(v => Boolean(v.symbol_id) !== Boolean(v.query), 'exactly one of symbol_id or query is required');
104
+ export const TraceInputSchema = z.object({
105
+ workspace_id: z.string().min(1), snapshot_id: z.string().min(1), symbol_id: z.string().min(1),
106
+ direction: z.enum(['in', 'out']), relations: z.array(z.enum(['calls', 'references', 'imports', 'implements', 'extends'])).min(1),
107
+ max_depth: z.number().int().min(1).max(6).default(2), max_nodes: z.number().int().min(1).max(2000).default(200),
108
+ require_resolved: z.boolean().default(false), timeout_ms: z.number().int().min(100).max(60000).default(5000),
109
+ });
110
+ const TargetSchema = z.union([z.object({ path: z.string().min(1) }), z.object({ symbol_id: z.string().min(1) })]);
111
+ export const ImpactInputSchema = z.object({
112
+ workspace_id: z.string().min(1), snapshot_id: z.string().min(1), targets: z.array(TargetSchema).max(50).optional(),
113
+ plan_id: z.string().min(1).optional(), include_docs: z.boolean().default(true), require_semantic: z.boolean().default(true),
114
+ max_depth: z.number().int().min(1).max(6).default(3), timeout_ms: z.number().int().min(100).max(60000).default(10000),
115
+ }).refine(v => Boolean(v.targets) !== Boolean(v.plan_id), 'exactly one of targets or plan_id is required');
116
+ const OperationSchema = z.discriminatedUnion('kind', [
117
+ z.object({ kind: z.literal('rename'), symbol_id: z.string().min(1), new_name: z.string().regex(/^[A-Za-z_$][\w$]*$/) }),
118
+ z.object({ kind: z.literal('replace_symbol_body'), symbol_id: z.string().min(1), content: z.string().max(500000) }),
119
+ z.object({ kind: z.literal('insert_before_symbol'), symbol_id: z.string().min(1), content: z.string().max(500000) }),
120
+ z.object({ kind: z.literal('insert_after_symbol'), symbol_id: z.string().min(1), content: z.string().max(500000) }),
121
+ z.object({ kind: z.literal('safe_delete'), symbol_id: z.string().min(1) }),
122
+ ]);
123
+ export const EditPreviewInputSchema = z.object({
124
+ workspace_id: z.string().min(1), snapshot_id: z.string().min(1), operations: z.array(OperationSchema).min(1).max(20),
125
+ validation_profile: z.enum(['default', 'extended']), timeout_ms: z.number().int().min(100).max(600000).default(120000),
126
+ });
127
+ export const EditApplyInputSchema = z.object({
128
+ workspace_id: z.string().min(1), plan_id: z.string().min(1), expected_snapshot_id: z.string().min(1),
129
+ idempotency_key: z.string().min(8).max(128),
130
+ });
131
+ export const TOOL_NAMES = ['code_context', 'code_symbol', 'code_trace', 'code_impact', 'code_edit_preview', 'code_edit_apply'];
132
+ export const TOOL_DEFINITIONS = TOOL_NAMES.map(name => ({
133
+ name,
134
+ description: `Yoke-controlled ${name.replace('code_', '').replace('_', ' ')} with snapshot-bound evidence.`,
135
+ inputSchema: { type: 'object', additionalProperties: false },
136
+ annotations: { openWorldHint: false, destructiveHint: name === 'code_edit_apply', readOnlyHint: name !== 'code_edit_preview' && name !== 'code_edit_apply', idempotentHint: name !== 'code_edit_apply' },
137
+ }));