openlore 2.0.10 → 2.0.12

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 (72) hide show
  1. package/dist/cli/commands/generate.d.ts.map +1 -1
  2. package/dist/cli/commands/generate.js +3 -2
  3. package/dist/cli/commands/generate.js.map +1 -1
  4. package/dist/cli/commands/mcp.d.ts.map +1 -1
  5. package/dist/cli/commands/mcp.js +106 -244
  6. package/dist/cli/commands/mcp.js.map +1 -1
  7. package/dist/cli/commands/serve.d.ts +49 -0
  8. package/dist/cli/commands/serve.d.ts.map +1 -0
  9. package/dist/cli/commands/serve.js +450 -0
  10. package/dist/cli/commands/serve.js.map +1 -0
  11. package/dist/cli/commands/setup.d.ts.map +1 -1
  12. package/dist/cli/commands/setup.js +32 -8
  13. package/dist/cli/commands/setup.js.map +1 -1
  14. package/dist/cli/index.js +2 -0
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/core/analyzer/artifact-generator.d.ts +1 -13
  17. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
  18. package/dist/core/analyzer/artifact-generator.js +5 -23
  19. package/dist/core/analyzer/artifact-generator.js.map +1 -1
  20. package/dist/core/analyzer/call-graph.d.ts.map +1 -1
  21. package/dist/core/analyzer/call-graph.js +19 -10
  22. package/dist/core/analyzer/call-graph.js.map +1 -1
  23. package/dist/core/analyzer/file-walker.d.ts.map +1 -1
  24. package/dist/core/analyzer/file-walker.js +9 -1
  25. package/dist/core/analyzer/file-walker.js.map +1 -1
  26. package/dist/core/analyzer/test-file.d.ts +19 -0
  27. package/dist/core/analyzer/test-file.d.ts.map +1 -0
  28. package/dist/core/analyzer/test-file.js +29 -0
  29. package/dist/core/analyzer/test-file.js.map +1 -0
  30. package/dist/core/decisions/consolidator.d.ts.map +1 -1
  31. package/dist/core/decisions/consolidator.js +7 -1
  32. package/dist/core/decisions/consolidator.js.map +1 -1
  33. package/dist/core/decisions/syncer.js +8 -0
  34. package/dist/core/decisions/syncer.js.map +1 -1
  35. package/dist/core/services/edge-store.d.ts.map +1 -1
  36. package/dist/core/services/edge-store.js +3 -0
  37. package/dist/core/services/edge-store.js.map +1 -1
  38. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
  39. package/dist/core/services/mcp-handlers/analysis.js +2 -1
  40. package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
  41. package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -1
  42. package/dist/core/services/mcp-handlers/semantic.js +7 -3
  43. package/dist/core/services/mcp-handlers/semantic.js.map +1 -1
  44. package/dist/core/services/mcp-handlers/utils.d.ts +13 -0
  45. package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
  46. package/dist/core/services/mcp-handlers/utils.js +49 -0
  47. package/dist/core/services/mcp-handlers/utils.js.map +1 -1
  48. package/dist/core/services/mcp-watcher.d.ts +9 -0
  49. package/dist/core/services/mcp-watcher.d.ts.map +1 -1
  50. package/dist/core/services/mcp-watcher.js +15 -5
  51. package/dist/core/services/mcp-watcher.js.map +1 -1
  52. package/dist/core/services/serve-client.d.ts +40 -0
  53. package/dist/core/services/serve-client.d.ts.map +1 -0
  54. package/dist/core/services/serve-client.js +115 -0
  55. package/dist/core/services/serve-client.js.map +1 -0
  56. package/dist/core/services/tool-dispatch.d.ts +35 -0
  57. package/dist/core/services/tool-dispatch.d.ts.map +1 -0
  58. package/dist/core/services/tool-dispatch.js +253 -0
  59. package/dist/core/services/tool-dispatch.js.map +1 -0
  60. package/dist/pi/extension.d.ts +62 -0
  61. package/dist/pi/extension.d.ts.map +1 -0
  62. package/dist/pi/extension.js +508 -0
  63. package/dist/pi/extension.js.map +1 -0
  64. package/examples/pi/README.md +98 -0
  65. package/package.json +16 -2
  66. package/skills/openlore-orient/README.md +73 -0
  67. package/skills/openlore-orient/SKILL.md +73 -0
  68. package/skills/openlore-orient/examples/example-orient-output.json +103 -0
  69. package/skills/openlore-orient/examples/example-task-prompt.md +26 -0
  70. package/skills/openlore-orient/scripts/orient-via-mcp.mjs +124 -0
  71. package/skills/openlore-orient/scripts/orient.ps1 +36 -0
  72. package/skills/openlore-orient/scripts/orient.sh +28 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openlore",
3
- "version": "2.0.10",
3
+ "version": "2.0.12",
4
4
  "description": "Reverse-engineer OpenSpec specifications from existing codebases",
5
5
  "type": "module",
6
6
  "main": "dist/api/index.js",
@@ -50,8 +50,18 @@
50
50
  "documentation",
51
51
  "reverse-engineering",
52
52
  "specification",
53
- "cli"
53
+ "cli",
54
+ "pi-package"
54
55
  ],
56
+ "pi": {
57
+ "extensions": [
58
+ "./dist/pi/extension.js"
59
+ ],
60
+ "skills": [
61
+ "./examples/opencode-skills",
62
+ "./skills/openlore-orient"
63
+ ]
64
+ },
55
65
  "author": "Clay Good",
56
66
  "license": "MIT",
57
67
  "repository": {
@@ -70,6 +80,7 @@
70
80
  "!dist/**/*.test.*",
71
81
  "src/viewer",
72
82
  "examples",
83
+ "skills/openlore-orient",
73
84
  "stubs",
74
85
  "schemas",
75
86
  "README.md",
@@ -126,6 +137,8 @@
126
137
  "tree-sitter-cli": "file:stubs/tree-sitter-cli-stub"
127
138
  },
128
139
  "devDependencies": {
140
+ "@earendil-works/pi-ai": "^0.78.1",
141
+ "@earendil-works/pi-coding-agent": "^0.78.1",
129
142
  "@eslint/js": "^9.17.0",
130
143
  "@types/node": "^22.10.5",
131
144
  "@vitest/coverage-v8": "^4.0.18",
@@ -133,6 +146,7 @@
133
146
  "globals": "^16.0.0",
134
147
  "memfs": "^4.15.0",
135
148
  "tsx": "^4.19.2",
149
+ "typebox": "1.1.38",
136
150
  "typescript": "^5.7.2",
137
151
  "typescript-eslint": "^8.19.1",
138
152
  "vitest": "^4.0.18"
@@ -0,0 +1,73 @@
1
+ # openlore-orient — Claude Code Skill
2
+
3
+ A drop-in Claude Code [Skill](https://docs.claude.com/en/docs/claude-code/skills) that teaches the model to call OpenLore's `orient()` at the start of every task in this repo, instead of grepping blind.
4
+
5
+ ## What it is
6
+
7
+ OpenLore maintains a deterministic, graph-native model of your codebase — every function, every caller, every spec section, every file. The `orient` tool collapses what would otherwise be a chain of `analyze_codebase → search_code → search_specs → suggest_insertion_points` into a single call that returns the exact context the agent needs for the task at hand.
8
+
9
+ This skill bundle ships the prompt and the wrappers Claude Code needs to know about `orient`. Once installed, Claude Code's system prompt picks it up automatically and the model invokes it when relevant — no `CLAUDE.md` editing required.
10
+
11
+ ## Install
12
+
13
+ ### Option 1 — user-scope (recommended)
14
+
15
+ Available across every project on your machine. From the OpenLore repo root:
16
+
17
+ ```sh
18
+ npm run skill:install-local
19
+ ```
20
+
21
+ This copies `skills/openlore-orient/` into `~/.claude/skills/openlore-orient/`. Idempotent — re-run any time to upgrade.
22
+
23
+ ### Option 2 — project-scope
24
+
25
+ Available only in the current project. From this OpenLore repo:
26
+
27
+ ```sh
28
+ cp -R skills/openlore-orient /path/to/your-project/.claude/skills/
29
+ ```
30
+
31
+ ### Option 3 — via `openlore install` *(future)*
32
+
33
+ Once OpenLore spec-01's `openlore install --agent claude-code` is shipped on npm, it will copy this skill into the target project's `.claude/skills/` automatically as part of the standard install flow. No separate step needed.
34
+
35
+ ## What's in the bundle
36
+
37
+ | File | Purpose |
38
+ |---|---|
39
+ | `SKILL.md` | The manifest + instructions Claude Code reads. Frontmatter declares the skill; body sections describe when/how/what-not-to-do. |
40
+ | `scripts/orient.sh` | POSIX wrapper. Prefers `npx openlore orient --json --task ...` (once shipped); falls back to the MCP path. |
41
+ | `scripts/orient.ps1` | PowerShell equivalent for Windows. |
42
+ | `scripts/orient-via-mcp.mjs` | Node helper that drives `openlore mcp` over stdio JSON-RPC. Used by the wrappers as a fallback. Pure Node built-ins, no deps. |
43
+ | `examples/example-orient-output.json` | Real (redacted) output captured from this repo, so a reader can see the JSON shape without us writing a schema doc. |
44
+ | `examples/example-task-prompt.md` | A short worked example of the full loop: task → orient call → output → next step. |
45
+
46
+ ## Performance & transparency (opt-in)
47
+
48
+ `orient` is deterministic and local — no LLM, no API key, no network call. A typical call against a
49
+ warm graph returns in well under a second; a cold first call may take a couple of seconds while the
50
+ on-disk index loads, after which the in-memory cache serves the rest of the session.
51
+
52
+ We deliberately keep these numbers **out of `SKILL.md`** so nothing is loaded into the agent's context
53
+ unless you ask for it. If you want to measure on your own repo, the capability is built in as a flag:
54
+
55
+ ```sh
56
+ openlore orient --metrics --task "your task"
57
+ # wall time + output size are reported on stderr; the result on stdout is unchanged
58
+ ```
59
+
60
+ The end-to-end, task-dependent benchmark numbers (wins *and* the small-repo loss cases) live in the
61
+ main project README's [Value Scorecard](https://github.com/clay-good/OpenLore#value-scorecard--does-it-pay-for-itself).
62
+ Links here use absolute URLs on purpose: copying this bundle into another project's `.claude/skills/`
63
+ must never produce a broken relative link.
64
+
65
+ ## Known limitations
66
+
67
+ The `openlore orient --json --task "..."` CLI subcommand is shipped, and `orient` is also exposed as an MCP tool. The shell wrappers prefer the CLI subcommand and transparently fall back to driving `openlore mcp` over stdio JSON-RPC via the bundled [`scripts/orient-via-mcp.mjs`](scripts/orient-via-mcp.mjs) helper on older openlore versions that predate the subcommand.
68
+
69
+ See `TODO(spec-02-followup)` markers in `SKILL.md` and the wrappers.
70
+
71
+ ## License
72
+
73
+ MIT, matching the [parent OpenLore repository](https://github.com/clay-good/OpenLore).
@@ -0,0 +1,73 @@
1
+ ---
2
+ name: openlore-orient
3
+ version: 2.1
4
+ description: Persistent architectural memory for this codebase. Call `orient(task)` before reading source files to get the relevant functions, callers, spec sections, and insertion points for any task — one structural lookup instead of file-by-file rediscovery.
5
+ ---
6
+
7
+ # OpenLore — orient before you read
8
+
9
+ This project uses **OpenLore** to maintain a deterministic, graph-native model of the codebase: every function, every caller, every spec section, every file. The `orient` tool collapses what would otherwise be a chain of `analyze_codebase → search_code → search_specs → suggest_insertion_points` into a single call.
10
+
11
+ The single most important habit when working in this repo: **call `orient` before opening source files for any non-trivial task.**
12
+
13
+ ## When to use this skill
14
+
15
+ Call `orient` at the start of any of these:
16
+
17
+ - **New task in the repo** — even ones that feel small. The graph knows things the file tree doesn't.
18
+ - **Unknown function or symbol** — instead of grepping blind, ask `orient` and let it return the function, its callers, and the spec section that owns it.
19
+ - **Planning a cross-module change** — `orient` returns insertion points ranked by structural fit, so you start at the right boundary instead of the most-recently-viewed file.
20
+ - **After an Epistemic Lease prefix appears** — when a tool response says you're stale, re-orient before continuing. Acting on stale context is the most common failure mode.
21
+
22
+ If you are reading source files without having called `orient` first, you are probably wasting tokens.
23
+
24
+ ## How to use it
25
+
26
+ ### Preferred: via the OpenLore MCP server
27
+
28
+ If `openlore` is registered as an MCP server in this project (look for `.mcp.json` → `mcpServers.openlore` — the project-scope file Claude Code reads; **not** `.claude/settings.json`, which it ignores for MCP), call the `orient` tool directly through the MCP interface. This is the lowest-latency path and gives the model access to the full openlore tool surface (~50 tools), not just `orient`.
29
+
30
+ ### Fallback: via the shell wrapper
31
+
32
+ ```sh
33
+ bash scripts/orient.sh "<task description>"
34
+ ```
35
+
36
+ (On Windows: `powershell -File scripts/orient.ps1 "<task description>"`)
37
+
38
+ The wrapper tries the direct CLI subcommand first (`npx --yes openlore orient --json --task "<task>"`) and falls back to driving the `openlore mcp` server over stdio JSON-RPC via the sibling `orient-via-mcp.mjs` helper on older openlore versions that predate the CLI subcommand. Either path produces real orient JSON on stdout. Parse these arrays from the result:
39
+
40
+ The result fields are **camelCase** (matching `orient --json`):
41
+
42
+ - **`relevantFunctions`** — top scored functions for the task, each with `name`/`file`/`line`, role classification, and a short reason.
43
+ - **`callPaths`** — caller/callee neighbourhood for the top functions. This is where "who breaks if I change this" lives.
44
+ - **`specDomains`** — OpenSpec domains that own the relevant files, including the requirements they encode. Read these before reading code.
45
+ - **`insertionPoints`** — ranked candidate locations to make the change, with structural justification.
46
+ - **`relevantFiles`**, **`provenance`**, **`changeCoupling`**, **`suggestedTools`**, **`nextSteps`** — supporting context. **`searchMode`** is `semantic` when embeddings are built or `bm25_fallback` otherwise.
47
+
48
+ Always start by reading **`specDomains`**, then **`callPaths`**, then jump into source only at the **`insertionPoints`** you actually plan to edit.
49
+
50
+ ### Lean mode for shallow lookups (cheaper)
51
+
52
+ For a quick "who calls X" / "where is Y defined" lookup, pass **`lean: true`** (or `orient --lean`): it returns just the navigation core (`relevantFunctions` with `expand` handles, `callPaths`, `specDomains`) — ~40% smaller — and drops the provenance / change-coupling / insertion-points / specs / decisions enrichment, each still one `expand` handle or dedicated tool call away. Omit `lean` when you actually need that enrichment (planning an edit, checking specs/decisions). Caveat (measured, Spec 27): on a *trivial* lookup in a small/familiar repo, even a lean orient call can cost more than just grepping — `orient` earns its keep on unfamiliar or multi-hop work, not one-line facts you could find in 2 reads.
53
+
54
+ > **Note:** the `openlore orient --json --task` CLI subcommand is available, so the wrappers use it directly. The MCP fallback in the wrappers only kicks in on older openlore versions that predate the subcommand.
55
+
56
+ > **Want to measure it on your own repo?** Pass `--metrics` (off by default) to have `orient` report its wall time and output size to stderr. Nothing is measured or printed unless you ask for it.
57
+
58
+ ## What NOT to do
59
+
60
+ - **Do not open source files before `orient` has returned.** This is the single most expensive mistake — you'll re-derive what the graph already knows.
61
+ - **Do not call `orient` on every edit.** It's a session-start and re-orient-on-staleness tool, not a per-call helper. Respect the Epistemic Lease signal — if no prefix appears, your context is still fresh.
62
+ - **Do not paraphrase the task** when passing it to `orient`. Use the user's words. The semantic search matches better when the query language matches the eventual prompt.
63
+ - **Do not ignore `specDomains`.** Reading specs first is faster and more accurate than reading code first, in this repo.
64
+
65
+ ## Failure modes
66
+
67
+ If `orient` returns an empty result or errors:
68
+
69
+ 1. **Empty `relevantFunctions` array** — the task description didn't match the graph. Try rephrasing using a known module name, function name, or domain word from the spec. Do not fall back silently — tell the user that `orient` didn't match and you're going to grep.
70
+ 2. **Wrapper output contains `"error": "No analysis found"`** — the codebase hasn't been analyzed yet. Run `npx openlore analyze` once to seed the graph, then retry. The wrapper itself is healthy in this case; the underlying tool is telling you what's missing.
71
+ 3. **Graph is stale (mtime older than recent edits)** — the JSON output will still come back, but the insertion points may be wrong. Re-run `npx openlore analyze` to rebuild.
72
+
73
+ In all failure cases, the correct fallback is a *targeted* `grep`/file read scoped to a single module — not opening the whole repo. And **always tell the user the skill silently degraded** so they can rebuild the graph if needed.
@@ -0,0 +1,103 @@
1
+ {
2
+ "_meta": {
3
+ "captured_from": "this repository (OpenLore)",
4
+ "task_used": "add a rate limiter to the API client",
5
+ "note": "Function names, file paths, and line numbers below are real and verifiable in src/. The shape exactly matches the response produced by handleOrient() in src/core/services/mcp-handlers/orient.ts. To regenerate from a live run once the codebase has a vector index built: `bash skills/openlore-orient/scripts/orient.sh \"add a rate limiter to the API client\" > skills/openlore-orient/examples/example-orient-output.json`."
6
+ },
7
+ "task": "add a rate limiter to the API client",
8
+ "searchMode": "bm25_fallback",
9
+ "note": "Embedding server unavailable — results use keyword matching. Run \"openlore analyze --embed\" for semantic search.",
10
+ "relevantFiles": [
11
+ "src/core/services/llm-service.ts",
12
+ "src/core/services/chat-agent.ts",
13
+ "src/core/services/chat-tools.ts",
14
+ "src/cli/commands/doctor.ts",
15
+ "src/cli/commands/generate.ts"
16
+ ],
17
+ "relevantFunctions": [
18
+ {
19
+ "id": "src/core/services/llm-service.ts::createLLMService",
20
+ "name": "createLLMService",
21
+ "filePath": "src/core/services/llm-service.ts",
22
+ "lineStart": 1852,
23
+ "score": 0.61,
24
+ "role": "factory",
25
+ "isHub": true,
26
+ "reason": "Top match: factory for the LLM client used by every CLI command. Hub: 14 inbound callers."
27
+ },
28
+ {
29
+ "id": "src/core/services/chat-agent.ts::fetchWithRetry",
30
+ "name": "fetchWithRetry",
31
+ "filePath": "src/core/services/chat-agent.ts",
32
+ "lineStart": 196,
33
+ "score": 0.42,
34
+ "role": "boundary",
35
+ "isHub": false,
36
+ "reason": "Outbound HTTP entry with retry; rate-limit would wrap each request here."
37
+ },
38
+ {
39
+ "id": "src/core/services/chat-tools.ts::toChatToolDefinitions",
40
+ "name": "toChatToolDefinitions",
41
+ "filePath": "src/core/services/chat-tools.ts",
42
+ "lineStart": 607,
43
+ "score": 0.31,
44
+ "role": "schema",
45
+ "isHub": false,
46
+ "reason": "Tool dispatcher schema; per-tool rate budgets would attach here if needed."
47
+ }
48
+ ],
49
+ "specDomains": [
50
+ {
51
+ "domain": "services",
52
+ "specFile": "openspec/specs/services/spec.md",
53
+ "purpose": "Manages reading, writing, and merging configuration files for openlore and OpenSpec."
54
+ },
55
+ {
56
+ "domain": "chat",
57
+ "specFile": "openspec/specs/chat/spec.md",
58
+ "purpose": "Handles interactions with language model providers (Gemini, Anthropic, OpenAI-compatible) to process chat messages and tool calls."
59
+ }
60
+ ],
61
+ "callPaths": [
62
+ {
63
+ "function": "createLLMService",
64
+ "filePath": "src/core/services/llm-service.ts",
65
+ "callers": [
66
+ { "name": "doctorCommand", "filePath": "src/cli/commands/doctor.ts" },
67
+ { "name": "verifyCommand", "filePath": "src/cli/commands/verify.ts" },
68
+ { "name": "driftCommand", "filePath": "src/cli/commands/drift.ts" },
69
+ { "name": "generateCommand", "filePath": "src/cli/commands/generate.ts" }
70
+ ],
71
+ "callees": [
72
+ { "name": "fetchWithRetry", "filePath": "src/core/services/chat-agent.ts" }
73
+ ]
74
+ }
75
+ ],
76
+ "insertionPoints": [
77
+ {
78
+ "name": "createLLMService",
79
+ "filePath": "src/core/services/llm-service.ts",
80
+ "lineStart": 1852,
81
+ "rationale": "Highest-leverage point: every caller passes through this factory. Wrap the returned LLMService with a rate-limited proxy here."
82
+ },
83
+ {
84
+ "name": "fetchWithRetry",
85
+ "filePath": "src/core/services/chat-agent.ts",
86
+ "lineStart": 196,
87
+ "rationale": "Alternative: instrument at the HTTP boundary if rate-limit needs awareness of provider-specific tokens-per-minute rather than per-call counts."
88
+ }
89
+ ],
90
+ "suggestedTools": [
91
+ "record_decision",
92
+ "analyze_impact",
93
+ "get_subgraph",
94
+ "get_spec",
95
+ "check_spec_drift"
96
+ ],
97
+ "nextSteps": [
98
+ "Before making an architectural choice, call record_decision(title, rationale, consequences, affectedFiles) to document it",
99
+ "Call get_subgraph(\"createLLMService\") to trace the call neighbourhood",
100
+ "Call get_spec(\"services\") to read the full spec before writing code",
101
+ "After implementing, run check_spec_drift to verify the code matches the spec"
102
+ ]
103
+ }
@@ -0,0 +1,26 @@
1
+ # Example: from task to first edit
2
+
3
+ **User:** "Add a rate limiter to the API client."
4
+
5
+ The agent picks up this skill and, before opening any source files, calls:
6
+
7
+ ```sh
8
+ bash scripts/orient.sh "add a rate limiter to the API client"
9
+ ```
10
+
11
+ The output (see `example-orient-output.json` for the actual shape) returns:
12
+
13
+ - `relevantFunctions[]` — pointing at `createLLMService` in `src/core/services/llm-service.ts` (the factory every command flows through) and `fetchWithRetry` in `src/core/services/chat-agent.ts` (the HTTP boundary).
14
+ - `callPaths[]` — every CLI command that calls into the LLM service (`doctor`, `verify`, `drift`, `generate`), so the agent knows the blast radius of a behavior change.
15
+ - `specDomains[]` — surfacing the `services` and `chat` spec domains and the requirements about how request handling is documented.
16
+ - `insertionPoints[]` — ranking the factory as the top candidate (wrap once, every caller benefits), with `fetchWithRetry` as a fallback if the rate limit needs token-bucket-per-provider awareness.
17
+
18
+ The agent then proceeds in this order, *without* opening anything else first:
19
+
20
+ 1. Reads the two `spec_sections` to understand the documented contract.
21
+ 2. Reads the callers list to confirm which call paths must keep working.
22
+ 3. Opens `llm-service.ts` at the line from the top insertion point — not the top of the file.
23
+
24
+ Total tokens consumed before the first edit: ~3k for the `orient` output + targeted reads. Without the skill, the same task typically costs 30k–50k tokens of file-by-file exploration before the agent finds the right place to edit.
25
+
26
+ The Epistemic Lease keeps watch from this point on: if a tool response later carries a "stale" prefix (because some unrelated edit moved the graph forward), the agent re-orients before continuing.
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Drive the existing `openlore mcp` server over stdio JSON-RPC to call the
4
+ * `orient` tool, then print its JSON output to stdout. Used by orient.sh /
5
+ * orient.ps1 so the skill works today, against the currently shipped CLI
6
+ * surface (no new subcommand required — see TODO(spec-02-followup) in
7
+ * SKILL.md for context).
8
+ *
9
+ * Pure Node built-ins only. Spawns `npx --yes openlore mcp`, performs the
10
+ * MCP initialize handshake, calls tools/call("orient", {task, directory}),
11
+ * prints the result, and exits.
12
+ */
13
+
14
+ import { spawn, spawnSync } from 'node:child_process';
15
+ import { createInterface } from 'node:readline';
16
+
17
+ const task = process.argv[2];
18
+ const directory = process.argv[3] ?? process.cwd();
19
+
20
+ if (!task) {
21
+ process.stderr.write('usage: orient-via-mcp.mjs "<task description>" [directory]\n');
22
+ process.exit(2);
23
+ }
24
+
25
+ // This is a one-shot call (initialize → orient → exit), so the MCP server must
26
+ // NOT start its file watcher: auto-watch recursively watches the whole repo —
27
+ // including huge build dirs like Rust's target/ — and EMFILEs before we ever
28
+ // get a response. Pass --no-watch-auto, but only if this openlore build
29
+ // supports it: older versions error on unknown options. Detect via `mcp --help`.
30
+ const mcpArgs = ['--yes', 'openlore', 'mcp'];
31
+ try {
32
+ const help = spawnSync('npx', ['--yes', 'openlore', 'mcp', '--help'], {
33
+ encoding: 'utf8',
34
+ timeout: 60_000,
35
+ });
36
+ if ((help.stdout ?? '').includes('--no-watch-auto')) {
37
+ mcpArgs.push('--no-watch-auto');
38
+ }
39
+ } catch {
40
+ // If detection fails, fall through without the flag (safe default).
41
+ }
42
+
43
+ const child = spawn('npx', mcpArgs, {
44
+ stdio: ['pipe', 'pipe', 'inherit'],
45
+ });
46
+
47
+ const rl = createInterface({ input: child.stdout });
48
+
49
+ let nextId = 1;
50
+ const pending = new Map(); // id → { resolve, reject }
51
+
52
+ rl.on('line', (line) => {
53
+ if (!line.trim()) return;
54
+ let msg;
55
+ try {
56
+ msg = JSON.parse(line);
57
+ } catch {
58
+ // MCP server occasionally logs non-JSON to stdout during boot; ignore.
59
+ return;
60
+ }
61
+ if (msg.id !== undefined && pending.has(msg.id)) {
62
+ const { resolve, reject } = pending.get(msg.id);
63
+ pending.delete(msg.id);
64
+ if (msg.error) reject(new Error(msg.error.message ?? 'MCP error'));
65
+ else resolve(msg.result);
66
+ }
67
+ });
68
+
69
+ function send(method, params) {
70
+ const id = nextId++;
71
+ const payload = { jsonrpc: '2.0', id, method, params };
72
+ child.stdin.write(JSON.stringify(payload) + '\n');
73
+ return new Promise((resolve, reject) => {
74
+ pending.set(id, { resolve, reject });
75
+ });
76
+ }
77
+
78
+ function notify(method, params) {
79
+ child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method, params }) + '\n');
80
+ }
81
+
82
+ const SHUTDOWN_TIMEOUT_MS = 30_000;
83
+ const timeout = setTimeout(() => {
84
+ process.stderr.write('orient-via-mcp: timed out waiting for MCP server\n');
85
+ child.kill('SIGTERM');
86
+ process.exit(124);
87
+ }, SHUTDOWN_TIMEOUT_MS);
88
+
89
+ try {
90
+ await send('initialize', {
91
+ protocolVersion: '2024-11-05',
92
+ capabilities: {},
93
+ clientInfo: { name: 'openlore-orient-skill', version: '1.0.0' },
94
+ });
95
+ notify('notifications/initialized', {});
96
+
97
+ const result = await send('tools/call', {
98
+ name: 'orient',
99
+ arguments: { directory, task },
100
+ });
101
+
102
+ clearTimeout(timeout);
103
+
104
+ // Tool responses come back as { content: [{ type: 'text', text: '<json>' }] }
105
+ const textBlock = result?.content?.find?.((c) => c.type === 'text');
106
+ if (textBlock?.text) {
107
+ // Try to parse + reformat; if not JSON, emit raw.
108
+ try {
109
+ const parsed = JSON.parse(textBlock.text);
110
+ process.stdout.write(JSON.stringify(parsed, null, 2) + '\n');
111
+ } catch {
112
+ process.stdout.write(textBlock.text + '\n');
113
+ }
114
+ } else {
115
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
116
+ }
117
+ } catch (err) {
118
+ clearTimeout(timeout);
119
+ process.stderr.write(`orient-via-mcp: ${err.message ?? err}\n`);
120
+ process.exitCode = 1;
121
+ } finally {
122
+ child.stdin.end();
123
+ child.kill();
124
+ }
@@ -0,0 +1,36 @@
1
+ # Wrapper: ask `openlore orient` for the relevant functions/callers/specs/
2
+ # insertion-points for a task, and print the JSON to stdout.
3
+ #
4
+ # Strategy (in order):
5
+ # 1. `npx --yes openlore orient --json --task "<task>"` — preferred path,
6
+ # uses the orient CLI subcommand.
7
+ # 2. Drive the existing `openlore mcp` server over stdio JSON-RPC via the
8
+ # sibling orient-via-mcp.mjs helper — fallback for older openlore versions
9
+ # that predate the CLI subcommand.
10
+
11
+ param(
12
+ [Parameter(Mandatory=$false, Position=0)]
13
+ [string]$Task
14
+ )
15
+
16
+ $ErrorActionPreference = 'Stop'
17
+
18
+ if ([string]::IsNullOrWhiteSpace($Task)) {
19
+ [Console]::Error.WriteLine('usage: orient.ps1 "<task description>"')
20
+ exit 2
21
+ }
22
+
23
+ $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
24
+
25
+ # Preferred path: direct CLI subcommand (if/when it ships). Detect by
26
+ # scanning `openlore --help` — commander returns exit 0 even for unknown
27
+ # subcommands, so we can't rely on `orient --help`'s exit code.
28
+ $help = & npx --yes openlore --help 2>$null
29
+ if ($help -match '(?m)^ orient( |$|\[)') {
30
+ & npx --yes openlore orient --json --task $Task
31
+ exit $LASTEXITCODE
32
+ }
33
+
34
+ # Fallback path: drive the MCP server over stdio JSON-RPC.
35
+ & node (Join-Path $ScriptDir 'orient-via-mcp.mjs') $Task
36
+ exit $LASTEXITCODE
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env sh
2
+ # Wrapper: ask `openlore orient` for the relevant functions/callers/specs/
3
+ # insertion-points for a task, and print the JSON to stdout.
4
+ #
5
+ # Strategy (in order):
6
+ # 1. `npx --yes openlore orient --json --task "<task>"` — preferred path,
7
+ # uses the orient CLI subcommand.
8
+ # 2. Drive the existing `openlore mcp` server over stdio JSON-RPC via the
9
+ # sibling orient-via-mcp.mjs helper — fallback for older openlore versions
10
+ # that predate the CLI subcommand.
11
+ set -eu
12
+ TASK="${1:-}"
13
+ if [ -z "$TASK" ]; then
14
+ echo "usage: orient.sh \"<task description>\"" >&2
15
+ exit 2
16
+ fi
17
+
18
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19
+
20
+ # Preferred path: direct CLI subcommand (if/when it ships). Detect by
21
+ # inspecting `openlore --help` output — commander returns exit 0 even for
22
+ # unknown subcommands, so we can't rely on `orient --help`'s exit code.
23
+ if npx --yes openlore --help 2>/dev/null | grep -Eq '^ orient( |$|\[)'; then
24
+ exec npx --yes openlore orient --json --task "$TASK"
25
+ fi
26
+
27
+ # Fallback path: drive the MCP server over stdio JSON-RPC.
28
+ exec node "$SCRIPT_DIR/orient-via-mcp.mjs" "$TASK"