@dotdotgod/hermes 0.6.0 → 0.6.2

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.
Binary file
package/mcp/server.mjs CHANGED
@@ -23240,28 +23240,33 @@ async function cliJson(args, cwd) {
23240
23240
  throw new Error(`Expected JSON from dotdotgod: ${out.slice(0, 500)}`);
23241
23241
  }
23242
23242
  }
23243
+ function embeddingRuntimeRecovery() {
23244
+ return {
23245
+ code: "EMBEDDING_RUNTIME_MISSING",
23246
+ message: "Semantic vectors need an optional runtime; continue with keyword/map retrieval or ask before installation.",
23247
+ recovery: { kind: "embedding-runtime-install", requiresConfirmation: true, statusTool: "dotdotgod_embedding_status", installTool: "dotdotgod_embedding_install", cliCommand: "dotdotgod embedding install --confirm" }
23248
+ };
23249
+ }
23243
23250
  async function projectLoad(input) {
23244
23251
  const root2 = resolve5(input.root || process.cwd());
23245
23252
  const focus = input.focus?.trim() ?? "";
23246
23253
  const tree = markdownTree(root2, input.maxDepth ?? (focus ? 3 : 5));
23247
23254
  let query = null;
23248
23255
  let queryUnavailable;
23256
+ let vectorUnavailable;
23249
23257
  if (focus) {
23250
23258
  try {
23251
23259
  query = await cliJson(["query", root2, focus, "--limit", String(Math.min(input.limit ?? 30, 30)), "--json"], root2);
23260
+ if (query.warnings?.some((warning) => warning.includes("Optional local embedding runtime is not installed"))) vectorUnavailable = embeddingRuntimeRecovery();
23252
23261
  } catch (error2) {
23253
23262
  const missingRuntime = String(error2?.message ?? error2).includes("Optional local embedding runtime is not installed");
23254
- queryUnavailable = missingRuntime ? {
23255
- code: "EMBEDDING_RUNTIME_MISSING",
23256
- message: "Local semantic search requires an optional embedding runtime; continue with the documentation map or ask the user before installation.",
23257
- recovery: { kind: "embedding-runtime-install", requiresConfirmation: true, statusTool: "dotdotgod_embedding_status", installTool: "dotdotgod_embedding_install", cliCommand: "dotdotgod embedding install --confirm" }
23258
- } : {
23263
+ queryUnavailable = missingRuntime ? embeddingRuntimeRecovery() : {
23259
23264
  code: "QUERY_UNAVAILABLE",
23260
23265
  message: "Semantic project query is unavailable; continue with the documentation map and targeted reads."
23261
23266
  };
23262
23267
  }
23263
23268
  }
23264
- return { ok: true, root: root2, focus, documentationTree: tree, query, ...queryUnavailable ? { queryUnavailable } : {} };
23269
+ return { ok: true, root: root2, focus, documentationTree: tree, query, ...queryUnavailable ? { queryUnavailable } : {}, ...vectorUnavailable ? { vectorUnavailable } : {} };
23265
23270
  }
23266
23271
  async function projectEmbeddingStatus(input = {}) {
23267
23272
  const root2 = resolve5(input.root || process.cwd());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdotgod/hermes",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "Hermes CLI and messaging-gateway adapter without Plan Mode.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -13,7 +13,7 @@
13
13
  "devDependencies": {
14
14
  "@modelcontextprotocol/sdk": "^1.25.2",
15
15
  "esbuild": "0.27.3",
16
- "@dotdotgod/context": "0.6.0"
16
+ "@dotdotgod/context": "0.6.2"
17
17
  },
18
18
  "files": [
19
19
  "plugin.yaml",
package/plugin.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: dotdotgod
2
- version: 0.6.0
2
+ version: 0.6.2
3
3
  description: Project memory, isolated context tools and impact checks for Hermes CLI and messaging gateway; no Plan Mode.
4
4
  author: dotdotgod
5
5
  license: Elastic-2.0
package/runtime.py CHANGED
@@ -52,10 +52,13 @@ class Runtime:
52
52
  labels = access.get(session.principal, []) if isinstance(access, dict) else []
53
53
  return {label: roots[label] for label in labels if label in roots} if isinstance(labels, list) else {}
54
54
 
55
- def session(self, session_id):
55
+ def trusted_session(self, session_id):
56
56
  if not session_id or session_id not in self.sessions:
57
57
  raise ValueError("No trusted host session; start a turn before using dotdotgod")
58
- session = self.sessions[session_id]
58
+ return self.sessions[session_id]
59
+
60
+ def session(self, session_id):
61
+ session = self.trusted_session(session_id)
59
62
  if session.root is None:
60
63
  raise ValueError("Select an authorized repository with dotdotgod_select_root first")
61
64
  if session.parent_id:
@@ -122,8 +125,23 @@ class Runtime:
122
125
 
123
126
  def call(self, name, args, session_id):
124
127
  with self.lock:
128
+ session = self.trusted_session(session_id)
125
129
  if name == "dotdotgod_select_root":
126
- return self.select(self.sessions[session_id], args.get("label"))
130
+ if not self.allowed(session):
131
+ raise PermissionError("No authorized repository labels; only dotdotgod_project_initialize is available")
132
+ return self.select(session, args.get("label"))
133
+ if session.root is None and not session.parent_id and name == "dotdotgod_project_initialize":
134
+ target = args.get("root")
135
+ if not isinstance(target, str) or not Path(target).is_absolute():
136
+ raise ValueError("Initialization without selection requires an explicit absolute root directory")
137
+ root = Path(target).resolve(strict=True)
138
+ if not root.is_dir():
139
+ raise ValueError("Initialization root must be a directory")
140
+ proxy = Proxy(root, digest(session.principal + "\0" + session.id + "\0initialize"))
141
+ try:
142
+ return proxy.call(name, {**args, "root": str(root)})
143
+ finally:
144
+ proxy.close()
127
145
  session = self.session(session_id)
128
146
  if name == "dotdotgod_impact_status":
129
147
  return envelope({"ok": True, "pending": list(self.pending(session))})
@@ -160,6 +178,13 @@ class Runtime:
160
178
 
161
179
  def before_tool(self, tool_name, args, session_id="", **kwargs):
162
180
  try:
181
+ session = self.trusted_session(session_id)
182
+ if session.root is None and not session.parent_id:
183
+ if not tool_name.startswith("dotdotgod_") or tool_name == "dotdotgod_project_initialize":
184
+ return None
185
+ if tool_name == "dotdotgod_select_root" and self.allowed(session):
186
+ return None
187
+ return {"action": "block", "message": "dotdotgod is inactive without a selected repository; only initialization is available (selection requires authorized labels)"}
163
188
  if tool_name == "dotdotgod_select_root":
164
189
  return None
165
190
  session = self.session(session_id)
@@ -194,7 +219,11 @@ class Runtime:
194
219
  elif platform in {"", "cli"}:
195
220
  session.root = Path(self.ctx.get_config("cli_root", "") or self.launch_root).resolve(strict=True)
196
221
  if session.root is None:
197
- return {"context": "Select a repository using dotdotgod_select_root. Authorized labels: " + json.dumps(list(self.allowed(session)))}
222
+ labels = list(self.allowed(session))
223
+ context = "dotdotgod is inactive; ordinary host tools remain available. Only dotdotgod_project_initialize may run without selection, with an explicit absolute root and existing write confirmation. Initialization does not select or authorize a repository."
224
+ if labels:
225
+ context += " Optionally select a repository using dotdotgod_select_root. Authorized labels: " + json.dumps(labels)
226
+ return {"context": context}
198
227
  try:
199
228
  self.session(session_id)
200
229
  parts = []
@@ -22,9 +22,9 @@ Load the repository's dotdotgod project memory without modifying maintained file
22
22
  1. Identify the repository root. Expose `Help: dotdotgod --help` as optional CLI discovery guidance without checking or reporting installation status, and do not require Help execution for Load to continue.
23
23
  2. When baseline context is missing, read `AGENTS.md`, the adapter entrypoint, `README.md`, and `docs/README.md` when present.
24
24
  3. Without arguments, run `dotdotgod map <root> --depth 5 --json` and use its configured prefix-compressed documentation tree. The command owns `documentation.root`, `load.documentationSummary.exclude`, deterministic Markdown discovery, and boundary summaries.
25
- 4. With arguments, run `dotdotgod query <root> "<focus>" --limit 30 --json` for at most 30 distinct Markdown files and `dotdotgod map <root> --depth 3 --json` for the configured navigation tree.
25
+ 4. With arguments, run `dotdotgod query <root> "<focus>" --limit 30 --json` for at most 30 documentation-file or code-symbol results and `dotdotgod map <root> --depth 3 --json` for the configured navigation tree.
26
26
  5. If the CLI or shell execution is unavailable, preserve Load behavior by building the same tree from the configured documentation root when known, excluding local memory, and using README routing. At the depth boundary, list every direct Markdown file and immediate child directory, then summarize hidden descendants separately under each named child with exact recursive directory and Markdown-file counts.
27
- 6. Read bodies selectively from query results, explicit paths, and README indexes. Read relevant active plans only when needed, use `docs/archive/README.md` (or the configured archive `README.md` under an alternate documentation root) as the history map, and avoid broad generated, dependency, cache, or secret reads.
27
+ 6. Query searches full documentation content and code outlines/comments, not implementation bodies. Preserve result kind, source ranges and retrieval warnings; verify source freshness before reading a symbol's implementation. Read bodies selectively from query results, explicit paths, and README indexes. Read relevant active plans only when needed, use `docs/archive/README.md` (or the configured archive `README.md` under an alternate documentation root) as the history map, and avoid broad generated, dependency, cache, or secret reads.
28
28
 
29
29
  ## Output
30
30