@dotdotgod/claude-code 0.6.3 → 0.6.5

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/README.md CHANGED
@@ -80,7 +80,7 @@ The plugin starts a local stdio MCP server. It exposes `execute` and `execute_fi
80
80
 
81
81
  Dotdotgod execution tools share a 10 MiB stdout/stderr capture ceiling per command. Crossing that ceiling terminates the child process and reports `captureLimitExceeded`; direct stdout and stderr excerpts are each capped at 1 MiB. Their child environments preserve compatibility-oriented inheritance after filtering runtime injection variables; this reports filtered names without values but does not isolate ordinary inherited credentials. Large output is indexed before retrieval in the ignored project-local `.dotdotgod/context/context.sqlite` FTS5 store, and searches return bounded excerpts. The store uses WAL, a bounded busy timeout, and transactional source replacement, expiry, and purge. Structural Markdown/JSON chunks, bounded typo-tolerant trigram candidates, reciprocal-rank fusion, and title/path/proximity signals improve retrieval while provenance and `instructionAuthority: "none"` identify retrieved text as non-authoritative data.
82
82
 
83
- The MCP `index` tool accepts project-contained files or bounded directories with deterministic traversal, configurable resource limits, explicit extension/path exclusions, and symlinks skipped by default. These capture and retrieval rules apply when Claude calls the dotdotgod MCP tools; Claude Code's built-in Bash tool keeps its native behavior. URL indexing performs application-level HTTP(S), DNS/address, connected-peer, redirect, and byte-limit validation. Accepted HTML is normalized as bounded untrusted text without browser rendering, JavaScript, subresource loading, or link following by default. The server bundles no browser renderer, so browser opt-in fails unless a host injects that capability. These controls are not a network sandbox or complete prompt-injection prevention. See the [`@dotdotgod/context` npm package](https://www.npmjs.com/package/@dotdotgod/context), its [GitHub README](https://github.com/dotdotgod/dotdotgod-kit/tree/main/packages/context), and the [maintained execution contract](https://github.com/dotdotgod/dotdotgod-kit/blob/main/docs/spec/CONTEXT_EXECUTION.md) for the complete runtime behavior.
83
+ The MCP `index` tool accepts files or explicitly requested bounded directories, including external paths, with deterministic traversal, configurable resource limits, explicit extension/path exclusions, and symlinks skipped by default. These capture and retrieval rules apply when Claude calls the dotdotgod MCP tools; Claude Code's built-in Bash tool keeps its native behavior. URL indexing performs application-level HTTP(S), DNS/address, connected-peer, redirect, and byte-limit validation. Accepted HTML is normalized as bounded untrusted text without browser rendering, JavaScript, subresource loading, or link following by default. The server bundles no browser renderer, so browser opt-in fails unless a host injects that capability. These controls are not a network sandbox or complete prompt-injection prevention. See the [`@dotdotgod/context` npm package](https://www.npmjs.com/package/@dotdotgod/context), its [GitHub README](https://github.com/dotdotgod/dotdotgod-kit/tree/main/packages/context), and the [maintained execution contract](https://github.com/dotdotgod/dotdotgod-kit/blob/main/docs/spec/CONTEXT_EXECUTION.md) for the complete runtime behavior.
84
84
 
85
85
  Bundled hooks mark project load as required at session start, record successful source/config edits, and deny broad verification or handoff commands until matching graph impact succeeds. Denial asks Claude to call the required MCP tool and retry; hooks do not silently change one tool type into another. See [`hooks/README.md`](https://github.com/dotdotgod/dotdotgod-kit/blob/main/packages/claude-code/hooks/README.md) for lifecycle and trust boundaries.
86
86
 
@@ -14,7 +14,7 @@ Task request: `$ARGUMENTS`
14
14
 
15
15
  ## CLI Context
16
16
 
17
- For focused documentation routing, run `dotdotgod query <root> "<focus>" --limit 30 --json`. Run `dotdotgod config <root> --json` when memory-area policy, documentation exclusions, or Plan Mode writable paths affect the plan. If the CLI is unavailable, continue from README indexes and focused file reads.
17
+ For focused documentation routing, run `dotdotgod query <root> "<focus>" --limit 30 --json`. Run `dotdotgod config <root> --json` when memory-area policy or documentation routing/exclusions affect the plan. If the CLI is unavailable, continue from README indexes and focused file reads.
18
18
 
19
19
  ## Goal
20
20
 
package/mcp/cli.mjs CHANGED
@@ -855,6 +855,15 @@ import { homedir } from "node:os";
855
855
  import { join } from "node:path";
856
856
  var DEFAULT_EMBEDDING_PROFILE = Object.freeze({ provider: "local", model: "Xenova/multilingual-e5-small" });
857
857
  var EMBEDDING_PROVIDERS = Object.freeze(["local", "openai-compatible", "ollama"]);
858
+ var DEFAULT_INPUT_FORMAT = Object.freeze({ queryPrefix: "query: ", documentPrefix: "passage: " });
859
+ function embeddingInputFormat(profile, label = "embedding") {
860
+ if (profile.inputFormat === void 0) return { ...DEFAULT_INPUT_FORMAT };
861
+ const format = profile.inputFormat;
862
+ if (!format || typeof format !== "object" || Array.isArray(format) || Object.keys(format).some((key) => !Object.hasOwn(DEFAULT_INPUT_FORMAT, key)) || Object.keys(DEFAULT_INPUT_FORMAT).some((key) => typeof format[key] !== "string")) {
863
+ throw new Error(`${label}.inputFormat must contain exactly queryPrefix and documentPrefix strings.`);
864
+ }
865
+ return { queryPrefix: format.queryPrefix, documentPrefix: format.documentPrefix };
866
+ }
858
867
  function readJson(path, label) {
859
868
  if (!existsSync(path)) return null;
860
869
  try {
@@ -865,7 +874,7 @@ function readJson(path, label) {
865
874
  }
866
875
  function validateEmbeddingProfile(profile, label = "embedding") {
867
876
  if (!profile || typeof profile !== "object" || Array.isArray(profile)) throw new Error(`${label} must be an object.`);
868
- const allowed = /* @__PURE__ */ new Set(["provider", "model", "baseUrl", "apiKey", "apiKeyEnv"]);
877
+ const allowed = /* @__PURE__ */ new Set(["provider", "model", "baseUrl", "apiKey", "apiKeyEnv", "inputFormat"]);
869
878
  const unknown = Object.keys(profile).find((key) => !allowed.has(key));
870
879
  if (unknown) throw new Error(`${label}.${unknown} is not supported.`);
871
880
  if (!EMBEDDING_PROVIDERS.includes(profile.provider)) throw new Error(`${label}.provider must be local, openai-compatible, or ollama.`);
@@ -883,6 +892,7 @@ function validateEmbeddingProfile(profile, label = "embedding") {
883
892
  if (!["http:", "https:"].includes(url.protocol)) throw new Error(`${label}.baseUrl must use HTTP or HTTPS.`);
884
893
  if (url.username || url.password) throw new Error(`${label}.baseUrl must not contain credentials.`);
885
894
  }
895
+ embeddingInputFormat(profile, label);
886
896
  return { ...profile, model: profile.model.trim(), ...profile.baseUrl ? { baseUrl: profile.baseUrl.replace(/\/$/, "") } : {} };
887
897
  }
888
898
  function sanitizeEmbeddingProfile(profile) {
@@ -890,8 +900,13 @@ function sanitizeEmbeddingProfile(profile) {
890
900
  return { ...safe, ...apiKey ? { apiKey: "[redacted]" } : {} };
891
901
  }
892
902
  function embeddingProfileIdentity(profile) {
893
- const { apiKey, apiKeyEnv, ...identity2 } = profile;
894
- return identity2;
903
+ return {
904
+ provider: profile.provider,
905
+ model: profile.model,
906
+ ...profile.baseUrl !== void 0 ? { baseUrl: profile.baseUrl } : {},
907
+ inputFormatVersion: 1,
908
+ inputFormat: embeddingInputFormat(profile)
909
+ };
895
910
  }
896
911
  function resolveEmbeddingProfile(root = ".", options = {}) {
897
912
  const home = options.home ?? homedir();
@@ -938,9 +953,6 @@ var DEFAULT_LOAD_POLICY = {
938
953
  exclude: ["docs/plan", "docs/archive"]
939
954
  }
940
955
  };
941
- var DEFAULT_PLAN_MODE_POLICY = {
942
- writablePaths: ["docs/plan/**", "docs/archive/**"]
943
- };
944
956
  var DEFAULT_IMPACT_RANKING_POLICY = {
945
957
  connectionCap: 80,
946
958
  memoryCap: 20,
@@ -1115,8 +1127,8 @@ function cloneLoadPolicy(policy = DEFAULT_LOAD_POLICY) {
1115
1127
  }
1116
1128
  };
1117
1129
  }
1118
- function clonePlanModePolicy(policy = DEFAULT_PLAN_MODE_POLICY) {
1119
- return { writablePaths: [...policy.writablePaths ?? DEFAULT_PLAN_MODE_POLICY.writablePaths] };
1130
+ function clonePlanModePolicy() {
1131
+ return {};
1120
1132
  }
1121
1133
  function normalizeDocumentationRoot(value = DEFAULT_DOCUMENTATION_ROOT) {
1122
1134
  return String(value).trim().replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/{2,}/g, "/").replace(/^\/+|\/+$/g, "");
@@ -1130,7 +1142,7 @@ function isValidDocumentationRoot(value) {
1130
1142
  }
1131
1143
  function defaultMemoryConfig({ documentationRoot = DEFAULT_DOCUMENTATION_ROOT } = {}) {
1132
1144
  const root = normalizeDocumentationRoot(documentationRoot);
1133
- return { source: "default", documentation: { root }, areas: memoryAreasForRoot(root).map(cloneArea), traceability: cloneTraceabilityPolicy({ ...DEFAULT_TRACEABILITY_POLICY, required: [`${root}/spec/**`] }), validation: cloneValidationPolicy(), impactRanking: cloneImpactRankingPolicy(), referenceExpansion: cloneReferenceExpansionPolicy(), load: cloneLoadPolicy({ ...DEFAULT_LOAD_POLICY, documentationSummary: { exclude: [`${root}/plan`, `${root}/archive`] } }), planMode: clonePlanModePolicy({ writablePaths: [`${root}/plan/**`, `${root}/archive/**`] }) };
1145
+ return { source: "default", documentation: { root }, areas: memoryAreasForRoot(root).map(cloneArea), traceability: cloneTraceabilityPolicy({ ...DEFAULT_TRACEABILITY_POLICY, required: [`${root}/spec/**`] }), validation: cloneValidationPolicy(), impactRanking: cloneImpactRankingPolicy(), referenceExpansion: cloneReferenceExpansionPolicy(), load: cloneLoadPolicy({ ...DEFAULT_LOAD_POLICY, documentationSummary: { exclude: [`${root}/plan`, `${root}/archive`] } }), planMode: clonePlanModePolicy() };
1134
1146
  }
1135
1147
  function normalizePathPattern(value = "") {
1136
1148
  return value.replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/+$|^\/+/, "");
@@ -1191,10 +1203,8 @@ function isSecretLikePathPattern(value = "") {
1191
1203
  const normalized = normalizePathPattern(value);
1192
1204
  return /(^|\/)(\.env|\.npmrc|\.pypirc|id_rsa|id_dsa|id_ed25519|credentials?|secrets?)(\.|\/|$)/i.test(normalized);
1193
1205
  }
1194
- function normalizePlanModePolicy(raw) {
1195
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) return clonePlanModePolicy();
1196
- const writablePaths = raw.writablePaths === void 0 ? DEFAULT_PLAN_MODE_POLICY.writablePaths : [...new Set((Array.isArray(raw.writablePaths) ? raw.writablePaths : []).map(normalizePathPattern))];
1197
- return clonePlanModePolicy({ writablePaths });
1206
+ function normalizePlanModePolicy(_raw) {
1207
+ return clonePlanModePolicy();
1198
1208
  }
1199
1209
  function normalizeLoadPolicy(raw) {
1200
1210
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return cloneLoadPolicy();
@@ -1339,10 +1349,6 @@ Fix: ${fix ?? `update ${field2 ?? "this config"} in ${file} to match the expecte
1339
1349
  if (planMode !== void 0) {
1340
1350
  if (!planMode || typeof planMode !== "object" || Array.isArray(planMode)) {
1341
1351
  add("PLAN_MODE_CONFIG_INVALID", "planMode", "Expected an object.");
1342
- } else if (planMode.writablePaths !== void 0) {
1343
- const paths = planMode.writablePaths;
1344
- if (!Array.isArray(paths)) add("PLAN_MODE_CONFIG_INVALID_WRITABLE_PATHS", "planMode.writablePaths", "Expected an array of documentation path strings.");
1345
- else if (paths.some((value) => !isValidPathPattern(value) || typeof value !== "string" || value.trim().startsWith("/") || !normalizePathPattern(value).startsWith(`${normalizeDocumentationRoot(data.documentation?.root ?? DEFAULT_DOCUMENTATION_ROOT)}/`) || normalizePathPattern(value).startsWith(`${normalizeDocumentationRoot(data.documentation?.root ?? DEFAULT_DOCUMENTATION_ROOT)}/.`) || isSecretLikePathPattern(value))) add("PLAN_MODE_CONFIG_INVALID_WRITABLE_PATHS", "planMode.writablePaths", "Expected safe repository-relative paths under documentation.root using exact paths or /** subtree patterns.");
1346
1352
  }
1347
1353
  }
1348
1354
  const referenceExpansion = data.referenceExpansion;
@@ -3804,7 +3810,6 @@ function formatConfigOutput(payload) {
3804
3810
  lines.push(`- load pinned paths: ${(payload.config.load?.pinnedPaths ?? []).join(", ") || "none"}`);
3805
3811
  lines.push(`- load pinned bodies: ${(payload.config.load?.pinnedBodies ?? []).join(", ") || "none"}`);
3806
3812
  lines.push(`- load documentation summary exclude: ${(payload.config.load?.documentationSummary?.exclude ?? []).join(", ") || "none"}`);
3807
- lines.push(`- plan mode writable paths: ${(payload.config.planMode?.writablePaths ?? []).join(", ") || "none"}`);
3808
3813
  const lowSignal = payload.config.referenceExpansion?.fuzzy?.lowSignal ?? { terms: [], add: [], remove: [] };
3809
3814
  lines.push(`- fuzzy low-signal terms: ${(lowSignal.terms ?? []).join(", ") || "none"} (add: ${(lowSignal.add ?? []).join(", ") || "none"}; remove: ${(lowSignal.remove ?? []).join(", ") || "none"})`);
3810
3815
  if (errors.length > 0) {
@@ -3954,7 +3959,12 @@ async function postJson(url, body, profile) {
3954
3959
  const response = await fetch(url, { method: "POST", headers: { "content-type": "application/json", ...key ? { authorization: `Bearer ${key}` } : {} }, body: JSON.stringify(body), signal: controller.signal });
3955
3960
  if (!response.ok) {
3956
3961
  const category = response.status === 401 || response.status === 403 ? "authentication" : response.status === 429 ? "rate limit" : `HTTP ${response.status}`;
3957
- throw new Error(`Remote embedding ${category} failure.`);
3962
+ const error = new Error(`Remote embedding ${category} failure.`);
3963
+ if (response.status === 400 || response.status === 413) {
3964
+ const body2 = (await response.text()).slice(0, 8192);
3965
+ error.tokenLimit = /(?:exceed\w*[\s\S]{0,120}tokens?|tokens?[\s\S]{0,120}(?:exceed|limit|too (?:long|many))|maximum context length)/i.test(body2);
3966
+ }
3967
+ throw error;
3958
3968
  }
3959
3969
  try {
3960
3970
  return await response.json();
@@ -3981,20 +3991,35 @@ function remoteEmbedder(profile) {
3981
3991
  return ordered.map(normalize);
3982
3992
  };
3983
3993
  }
3994
+ function withInputFormat(embed, profile) {
3995
+ const format = embeddingInputFormat(profile);
3996
+ return (texts, role) => {
3997
+ if (role !== void 0 && role !== "query" && role !== "document") throw new Error("Embedding role must be query or document.");
3998
+ return embed(role === void 0 ? texts : texts.map((text) => format[`${role}Prefix`] + text));
3999
+ };
4000
+ }
3984
4001
  async function createEmbedder(profile) {
3985
4002
  const embed = profile.provider === "local" ? await localEmbedder(profile) : remoteEmbedder(profile);
3986
- return async (texts) => {
4003
+ async function embedBatch(texts) {
3987
4004
  if (texts.length === 0) return [];
3988
- const rows = await embed(texts);
4005
+ let rows;
4006
+ try {
4007
+ rows = await embed(texts);
4008
+ } catch (error) {
4009
+ if (!error.tokenLimit || texts.length === 1) throw error;
4010
+ const middle = Math.ceil(texts.length / 2);
4011
+ rows = [...await embedBatch(texts.slice(0, middle)), ...await embedBatch(texts.slice(middle))];
4012
+ }
3989
4013
  if (!Array.isArray(rows) || rows.length !== texts.length) throw new Error("Embedding provider returned an unexpected vector count.");
3990
4014
  const dimensions = rows[0]?.length;
3991
4015
  if (!dimensions || rows.some((row) => row.length !== dimensions)) throw new Error("Embedding provider returned inconsistent vector dimensions.");
3992
4016
  return rows;
3993
- };
4017
+ }
4018
+ return withInputFormat(embedBatch, profile);
3994
4019
  }
3995
4020
  async function resolveEmbedder(root, options = {}) {
3996
4021
  const resolved = options.profile ? { profile: options.profile, source: options.source ?? "injected", path: null } : resolveEmbeddingProfile(root, options);
3997
- return { ...resolved, identity: embeddingProfileIdentity(resolved.profile), embed: options.embed ?? await createEmbedder(resolved.profile) };
4022
+ return { ...resolved, identity: embeddingProfileIdentity(resolved.profile), embed: options.embed ? withInputFormat(options.embed, resolved.profile) : await createEmbedder(resolved.profile) };
3998
4023
  }
3999
4024
 
4000
4025
  // packages/cli/src/query/store.mjs
@@ -4200,7 +4225,7 @@ async function buildVectorIndex(root, embedOrOptions = {}) {
4200
4225
  });
4201
4226
  for (let offset = 0; offset < missing.length; offset += EMBED_BATCH_SIZE) {
4202
4227
  const batch = missing.slice(offset, offset + EMBED_BATCH_SIZE);
4203
- const embedded = await resolved.embed(batch.map(({ chunk }) => `passage: ${chunk.passage}`));
4228
+ const embedded = await resolved.embed(batch.map(({ chunk }) => chunk.passage), "document");
4204
4229
  embedded.forEach((vector, index) => {
4205
4230
  rows[batch[index].index] = vector;
4206
4231
  });
@@ -4230,7 +4255,7 @@ async function queryProject(root, query, options = {}) {
4230
4255
  try {
4231
4256
  assertCodeEmbeddingConsent(root, options, corpus.chunks);
4232
4257
  index = await buildVectorIndex(root, { ...options, chunks: corpus.chunks });
4233
- const [queryVector] = await index.embedding.embed([`query: ${query}`]);
4258
+ const [queryVector] = await index.embedding.embed([query], "query");
4234
4259
  vector = index.chunks.map((chunk, position) => ({ ...chunk, vectorScore: cosineScore(queryVector, index.vectors, position * index.manifest.dimensions, index.manifest.dimensions) })).sort((a, b) => b.vectorScore - a.vectorScore || a.id.localeCompare(b.id));
4235
4260
  } catch (error) {
4236
4261
  if (search === "vector") throw error;
@@ -6006,7 +6031,7 @@ async function buildVectorImpactOverlay(root, index, changedPaths, options = {})
6006
6031
  if (!profile) continue;
6007
6032
  const embed = vectorIndex.embedding?.embed ?? options.embed;
6008
6033
  if (!embed) throw new Error("Vector index did not provide an embedder.");
6009
- const [queryVector] = await embed([`query: ${profile.text}`]);
6034
+ const [queryVector] = await embed([profile.text], "query");
6010
6035
  if (!queryVector) throw new Error("Changed-file embedding was empty.");
6011
6036
  const matches = rankVectorFiles(queryVector, vectorIndex).filter((match) => match.path !== changed && match.vectorScore >= threshold).slice(0, topK);
6012
6037
  for (const match of matches) {
package/mcp/server.mjs CHANGED
@@ -3650,49 +3650,49 @@ var require_fast_uri = __commonJS({
3650
3650
  schemelessOptions.skipEscape = true;
3651
3651
  return serialize(resolved, schemelessOptions);
3652
3652
  }
3653
- function resolveComponent(base, relative4, options, skipNormalization) {
3653
+ function resolveComponent(base, relative3, options, skipNormalization) {
3654
3654
  const target = {};
3655
3655
  if (!skipNormalization) {
3656
3656
  base = parse3(serialize(base, options), options);
3657
- relative4 = parse3(serialize(relative4, options), options);
3657
+ relative3 = parse3(serialize(relative3, options), options);
3658
3658
  }
3659
3659
  options = options || {};
3660
- if (!options.tolerant && relative4.scheme) {
3661
- target.scheme = relative4.scheme;
3662
- target.userinfo = relative4.userinfo;
3663
- target.host = relative4.host;
3664
- target.port = relative4.port;
3665
- target.path = removeDotSegments(relative4.path || "");
3666
- target.query = relative4.query;
3660
+ if (!options.tolerant && relative3.scheme) {
3661
+ target.scheme = relative3.scheme;
3662
+ target.userinfo = relative3.userinfo;
3663
+ target.host = relative3.host;
3664
+ target.port = relative3.port;
3665
+ target.path = removeDotSegments(relative3.path || "");
3666
+ target.query = relative3.query;
3667
3667
  } else {
3668
- if (relative4.userinfo !== void 0 || relative4.host !== void 0 || relative4.port !== void 0) {
3669
- target.userinfo = relative4.userinfo;
3670
- target.host = relative4.host;
3671
- target.port = relative4.port;
3672
- target.path = removeDotSegments(relative4.path || "");
3673
- target.query = relative4.query;
3668
+ if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
3669
+ target.userinfo = relative3.userinfo;
3670
+ target.host = relative3.host;
3671
+ target.port = relative3.port;
3672
+ target.path = removeDotSegments(relative3.path || "");
3673
+ target.query = relative3.query;
3674
3674
  } else {
3675
- if (!relative4.path) {
3675
+ if (!relative3.path) {
3676
3676
  target.path = base.path;
3677
- if (relative4.query !== void 0) {
3678
- target.query = relative4.query;
3677
+ if (relative3.query !== void 0) {
3678
+ target.query = relative3.query;
3679
3679
  } else {
3680
3680
  target.query = base.query;
3681
3681
  }
3682
3682
  } else {
3683
- if (relative4.path[0] === "/") {
3684
- target.path = removeDotSegments(relative4.path);
3683
+ if (relative3.path[0] === "/") {
3684
+ target.path = removeDotSegments(relative3.path);
3685
3685
  } else {
3686
3686
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3687
- target.path = "/" + relative4.path;
3687
+ target.path = "/" + relative3.path;
3688
3688
  } else if (!base.path) {
3689
- target.path = relative4.path;
3689
+ target.path = relative3.path;
3690
3690
  } else {
3691
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative4.path;
3691
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
3692
3692
  }
3693
3693
  target.path = removeDotSegments(target.path);
3694
3694
  }
3695
- target.query = relative4.query;
3695
+ target.query = relative3.query;
3696
3696
  }
3697
3697
  target.userinfo = base.userinfo;
3698
3698
  target.host = base.host;
@@ -3700,7 +3700,7 @@ var require_fast_uri = __commonJS({
3700
3700
  }
3701
3701
  target.scheme = base.scheme;
3702
3702
  }
3703
- target.fragment = relative4.fragment;
3703
+ target.fragment = relative3.fragment;
3704
3704
  return target;
3705
3705
  }
3706
3706
  function equal(uriA, uriB, options) {
@@ -21959,23 +21959,15 @@ import { finished } from "node:stream/promises";
21959
21959
 
21960
21960
  // packages/context/src/paths.mjs
21961
21961
  import { realpathSync } from "node:fs";
21962
- import { isAbsolute, relative, resolve as resolve2 } from "node:path";
21963
- function assertWithinRoot(base, target, candidate) {
21964
- const rel = relative(base, target);
21965
- if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute(rel)) {
21966
- throw new Error(`Path escapes project root: ${candidate}`);
21967
- }
21968
- }
21962
+ import { resolve as resolve2 } from "node:path";
21969
21963
  function resolveWithinRoot(root2, candidate = ".") {
21970
21964
  const base = resolve2(root2 || process.cwd());
21971
21965
  const target = resolve2(base, candidate);
21972
- assertWithinRoot(base, target, candidate);
21973
21966
  return target;
21974
21967
  }
21975
21968
  function resolveExistingWithinRoot(root2, candidate = ".") {
21976
21969
  const base = realpathSync(resolve2(root2 || process.cwd()));
21977
21970
  const target = realpathSync(resolveWithinRoot(base, candidate));
21978
- assertWithinRoot(base, target, candidate);
21979
21971
  return target;
21980
21972
  }
21981
21973
 
@@ -22271,7 +22263,7 @@ import { readFileSync as readFileSync3, statSync as statSync4 } from "node:fs";
22271
22263
 
22272
22264
  // packages/context/src/directory-ingestion.mjs
22273
22265
  import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync as readFileSync2, readdirSync, realpathSync as realpathSync2, statSync as statSync3 } from "node:fs";
22274
- import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep } from "node:path";
22266
+ import { relative, resolve as resolve3, sep } from "node:path";
22275
22267
  var DEFAULT_LIMITS = Object.freeze({
22276
22268
  maxDepth: 16,
22277
22269
  maxVisitedEntries: 1e4,
@@ -22279,12 +22271,8 @@ var DEFAULT_LIMITS = Object.freeze({
22279
22271
  maxFileBytes: 25 * 1024 * 1024,
22280
22272
  maxAggregateBytes: 100 * 1024 * 1024
22281
22273
  });
22282
- function inside(base, target) {
22283
- const rel = relative2(base, target);
22284
- return rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute2(rel);
22285
- }
22286
22274
  function relativePath(base, target) {
22287
- return relative2(base, target).split(sep).join("/");
22275
+ return relative(base, target).split(sep).join("/");
22288
22276
  }
22289
22277
  function boundedInteger(value, fallback, name) {
22290
22278
  const selected = value ?? fallback;
@@ -22335,10 +22323,7 @@ function snapshot(path, target, stat, symlink) {
22335
22323
  function walkDirectoryManifest(options = {}) {
22336
22324
  const root2 = realpathSync2(resolve3(options.root || process.cwd()));
22337
22325
  const requested = resolve3(root2, options.path ?? ".");
22338
- if (!inside(root2, requested)) throw new Error(`Directory escapes project root: ${options.path}`);
22339
- if (lstatSync(requested).isSymbolicLink()) throw new Error(`Directory symlinks are not followed: ${options.path ?? "."}`);
22340
22326
  const directory = realpathSync2(requested);
22341
- if (!inside(root2, directory)) throw new Error(`Directory escapes project root: ${options.path}`);
22342
22327
  if (!statSync3(directory).isDirectory()) throw new Error(`Not a directory: ${options.path ?? "."}`);
22343
22328
  const policy = normalizeOptions(options);
22344
22329
  const files = [];
@@ -22395,10 +22380,6 @@ function walkDirectoryManifest(options = {}) {
22395
22380
  let targetStat;
22396
22381
  try {
22397
22382
  target = realpathSync2(absolutePath);
22398
- if (!inside(root2, target)) {
22399
- skip(path, "symlink-outside-root");
22400
- continue;
22401
- }
22402
22383
  targetStat = statSync3(target);
22403
22384
  } catch {
22404
22385
  skip(path, "symlink-unresolvable");
@@ -22462,9 +22443,7 @@ function matchesIdentity(stat, identity) {
22462
22443
  return stat.dev === identity.dev && stat.ino === identity.ino && stat.size === identity.size && stat.mtimeMs === identity.mtimeMs;
22463
22444
  }
22464
22445
  function verifyManifestFile(entry, root2) {
22465
- const base = realpathSync2(resolve3(root2));
22466
22446
  const target = realpathSync2(entry.absolutePath);
22467
- if (!inside(base, target)) throw new Error(`Manifest file escapes project root: ${entry.path}`);
22468
22447
  const stat = statSync3(target);
22469
22448
  if (!stat.isFile()) throw new Error(`Manifest entry is not a regular file: ${entry.path}`);
22470
22449
  if (!matchesIdentity(stat, entry.identity ?? {})) throw new Error(`Manifest file changed before indexing: ${entry.path}`);
@@ -23197,7 +23176,7 @@ function runDoctor(options = {}) {
23197
23176
  // packages/context/src/project.mjs
23198
23177
  import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
23199
23178
  import { fileURLToPath } from "node:url";
23200
- import { relative as relative3, resolve as resolve5 } from "node:path";
23179
+ import { relative as relative2, resolve as resolve5 } from "node:path";
23201
23180
  import { spawn as spawn2 } from "node:child_process";
23202
23181
  var cli = fileURLToPath(new URL("./cli.mjs", import.meta.url));
23203
23182
  function markdownTree(root2, maxDepth = 5) {
@@ -23209,9 +23188,9 @@ function markdownTree(root2, maxDepth = 5) {
23209
23188
  for (const entry of readdirSync2(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
23210
23189
  const path = resolve5(dir, entry.name);
23211
23190
  if (entry.isDirectory()) {
23212
- output.push(`${" ".repeat(depth - 1)}- ${relative3(root2, path)}/`);
23191
+ output.push(`${" ".repeat(depth - 1)}- ${relative2(root2, path)}/`);
23213
23192
  walk(path, depth + 1);
23214
- } else if (entry.isFile() && entry.name.endsWith(".md")) output.push(`${" ".repeat(depth - 1)}- ${relative3(root2, path)}`);
23193
+ } else if (entry.isFile() && entry.name.endsWith(".md")) output.push(`${" ".repeat(depth - 1)}- ${relative2(root2, path)}`);
23215
23194
  }
23216
23195
  }
23217
23196
  walk(docs, 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotdotgod/claude-code",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Claude Code adapter for dotdotgod project memory workflows.",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "esbuild": "0.27.3",
28
- "@dotdotgod/context": "0.6.3"
28
+ "@dotdotgod/context": "0.6.5"
29
29
  },
30
30
  "files": [
31
31
  ".claude-plugin",
@@ -10,7 +10,7 @@ version: 1.0.0
10
10
 
11
11
  ## CLI Context
12
12
 
13
- For focused documentation routing, run `dotdotgod query <root> "<focus>" --limit 30 --json`. Run `dotdotgod config <root> --json` when memory-area policy, documentation exclusions, or Plan Mode writable paths affect the plan. If the CLI is unavailable, continue from README indexes and focused file reads.
13
+ For focused documentation routing, run `dotdotgod query <root> "<focus>" --limit 30 --json`. Run `dotdotgod config <root> --json` when memory-area policy or documentation routing/exclusions affect the plan. If the CLI is unavailable, continue from README indexes and focused file reads.
14
14
 
15
15
  ## Goal
16
16
 
@@ -215,10 +215,5 @@
215
215
  ]
216
216
  }
217
217
  },
218
- "planMode": {
219
- "writablePaths": [
220
- "docs/plan/**",
221
- "docs/archive/**"
222
- ]
223
- }
218
+ "planMode": {}
224
219
  }
@@ -230,10 +230,5 @@
230
230
  ]
231
231
  }
232
232
  },
233
- "planMode": {
234
- "writablePaths": [
235
- "docs/plan/**",
236
- "docs/archive/**"
237
- ]
238
- }
233
+ "planMode": {}
239
234
  }
@@ -237,10 +237,5 @@
237
237
  ]
238
238
  }
239
239
  },
240
- "planMode": {
241
- "writablePaths": [
242
- "docs/plan/**",
243
- "docs/archive/**"
244
- ]
245
- }
240
+ "planMode": {}
246
241
  }
@@ -251,10 +251,5 @@
251
251
  ]
252
252
  }
253
253
  },
254
- "planMode": {
255
- "writablePaths": [
256
- "docs/plan/**",
257
- "docs/archive/**"
258
- ]
259
- }
254
+ "planMode": {}
260
255
  }
@@ -261,10 +261,5 @@
261
261
  ]
262
262
  }
263
263
  },
264
- "planMode": {
265
- "writablePaths": [
266
- "docs/plan/**",
267
- "docs/archive/**"
268
- ]
269
- }
264
+ "planMode": {}
270
265
  }
@@ -283,10 +283,5 @@
283
283
  ]
284
284
  }
285
285
  },
286
- "planMode": {
287
- "writablePaths": [
288
- "docs/plan/**",
289
- "docs/archive/**"
290
- ]
291
- }
286
+ "planMode": {}
292
287
  }
@@ -230,10 +230,5 @@
230
230
  ]
231
231
  }
232
232
  },
233
- "planMode": {
234
- "writablePaths": [
235
- "docs/plan/**",
236
- "docs/archive/**"
237
- ]
238
- }
233
+ "planMode": {}
239
234
  }