@dotdotgod/claude-code 0.3.9 → 0.3.10
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/mcp/cli.mjs +180 -42
- package/mcp/server.mjs +12 -8
- package/package.json +2 -2
- package/skills/document-clarify/SKILL.md +17 -6
package/mcp/cli.mjs
CHANGED
|
@@ -1434,7 +1434,7 @@ function parseCommon(argv) {
|
|
|
1434
1434
|
|
|
1435
1435
|
// packages/cli/src/validate/run.mjs
|
|
1436
1436
|
import { existsSync as existsSync10, readFileSync as readFileSync9, readdirSync as readdirSync2 } from "node:fs";
|
|
1437
|
-
import { basename as basename7, dirname as
|
|
1437
|
+
import { basename as basename7, dirname as dirname6, extname as extname4, join as join8, resolve as resolve8 } from "node:path";
|
|
1438
1438
|
|
|
1439
1439
|
// packages/cli/src/docs/traceability.mjs
|
|
1440
1440
|
import { existsSync as existsSync5 } from "node:fs";
|
|
@@ -1533,7 +1533,8 @@ function stripTraceabilityLinksRegion(content) {
|
|
|
1533
1533
|
if (region.status === "present") ranges.push({ start: region.start, end: region.end });
|
|
1534
1534
|
for (const block of extractDotdotgodTraceabilityBlocks(content)) ranges.push({ start: block.index, end: block.end });
|
|
1535
1535
|
if (ranges.length === 0) return content;
|
|
1536
|
-
|
|
1536
|
+
const stripped = ranges.sort((a, b) => b.start - a.start).reduce((next, range) => `${next.slice(0, range.start)}${next.slice(range.end)}`, content);
|
|
1537
|
+
return stripped.replace(/(?:^[ \t]*\r?\n)*^##[ \t]+Traceability[ \t]*\r?(?:\n(?:[ \t]*\r?\n)*|$)/gm, "");
|
|
1537
1538
|
}
|
|
1538
1539
|
function markdownLinkPath(root, file, target) {
|
|
1539
1540
|
const fromDir = dirname2(file);
|
|
@@ -2321,6 +2322,138 @@ function graphSummary(index) {
|
|
|
2321
2322
|
return { nodes: graph.nodes.length, edges: graph.edges.length, byType, byRelation };
|
|
2322
2323
|
}
|
|
2323
2324
|
|
|
2325
|
+
// packages/cli/src/validate/references.mjs
|
|
2326
|
+
import { statSync as statSync5 } from "node:fs";
|
|
2327
|
+
import { dirname as dirname5, relative as relative3, resolve as resolve7 } from "node:path";
|
|
2328
|
+
var blank = (text) => text.replace(/[^\n\r]/g, " ");
|
|
2329
|
+
var unescape = (text) => text.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~])/g, "$1");
|
|
2330
|
+
var labelKey = (text) => unescape(text).trim().replace(/\s+/g, " ").toLowerCase();
|
|
2331
|
+
var escaped = (text, index) => {
|
|
2332
|
+
let count = 0;
|
|
2333
|
+
while (index > 0 && text[--index] === "\\") count++;
|
|
2334
|
+
return count % 2 === 1;
|
|
2335
|
+
};
|
|
2336
|
+
function visibleContent(content) {
|
|
2337
|
+
const region = findTraceabilityLinksRegion(content);
|
|
2338
|
+
let text = region.status === "present" ? content.slice(0, region.start) + blank(content.slice(region.start, region.end)) + content.slice(region.end) : content;
|
|
2339
|
+
const tokens = /<!--[\s\S]*?(?:-->|(?![\s\S]))|^ {0,3}(`{3,}|~{3,})[^\n]*/gm;
|
|
2340
|
+
let token;
|
|
2341
|
+
while (token = tokens.exec(text)) {
|
|
2342
|
+
let end = tokens.lastIndex;
|
|
2343
|
+
if (token[1]) {
|
|
2344
|
+
const closing = new RegExp(`^ {0,3}${token[1][0]}{${token[1].length},}[ \\t]*\\r?$`, "gm");
|
|
2345
|
+
closing.lastIndex = end + 1;
|
|
2346
|
+
end = closing.exec(text) ? closing.lastIndex : text.length;
|
|
2347
|
+
}
|
|
2348
|
+
text = text.slice(0, token.index) + blank(text.slice(token.index, end)) + text.slice(end);
|
|
2349
|
+
tokens.lastIndex = end;
|
|
2350
|
+
}
|
|
2351
|
+
return text;
|
|
2352
|
+
}
|
|
2353
|
+
function destination(text, start) {
|
|
2354
|
+
let i = start;
|
|
2355
|
+
while (/\s/.test(text[i] ?? "") && i < text.length) i++;
|
|
2356
|
+
const begin = i;
|
|
2357
|
+
if (text[i] === "<") {
|
|
2358
|
+
for (++i; i < text.length; i++) {
|
|
2359
|
+
if (text[i] === ">" && !escaped(text, i)) return { href: text.slice(begin + 1, i), end: i + 1 };
|
|
2360
|
+
if (text[i] === "\n") return null;
|
|
2361
|
+
}
|
|
2362
|
+
return null;
|
|
2363
|
+
}
|
|
2364
|
+
let depth = 0;
|
|
2365
|
+
for (; i < text.length; i++) {
|
|
2366
|
+
if (escaped(text, i)) continue;
|
|
2367
|
+
if (/\s/.test(text[i]) || text[i] === ")" && depth === 0) break;
|
|
2368
|
+
if (text[i] === "(") depth++;
|
|
2369
|
+
if (text[i] === ")") depth--;
|
|
2370
|
+
}
|
|
2371
|
+
return depth === 0 && i > begin ? { href: text.slice(begin, i), end: i } : null;
|
|
2372
|
+
}
|
|
2373
|
+
function extractMemoryReferences(content) {
|
|
2374
|
+
let text = visibleContent(content);
|
|
2375
|
+
const references = [];
|
|
2376
|
+
const code = [];
|
|
2377
|
+
const lineAt = (offset) => content.slice(0, offset).split("\n").length;
|
|
2378
|
+
text = text.replace(/(`+)([\s\S]*?)\1(?!`)/g, (match2, ticks, value, offset) => {
|
|
2379
|
+
if (escaped(text, offset)) return match2;
|
|
2380
|
+
code.push({ href: value.trim(), offset, end: offset + match2.length, kind: "inline-code", line: lineAt(offset) });
|
|
2381
|
+
return blank(match2);
|
|
2382
|
+
});
|
|
2383
|
+
const definitions2 = /* @__PURE__ */ new Map();
|
|
2384
|
+
text = text.replace(/^ {0,3}\[([^\]\n]+)\]:[ \t]*(.*)$/gm, (match2, label, value) => {
|
|
2385
|
+
const parsed = destination(value, 0);
|
|
2386
|
+
if (parsed && !definitions2.has(labelKey(label))) definitions2.set(labelKey(label), parsed.href);
|
|
2387
|
+
return blank(match2);
|
|
2388
|
+
});
|
|
2389
|
+
const linkRanges = [];
|
|
2390
|
+
const labels = /!?\[((?:\\.|[^\]\\\n])*)\]/g;
|
|
2391
|
+
let match;
|
|
2392
|
+
while (match = labels.exec(text)) {
|
|
2393
|
+
if (escaped(text, match.index) || match[0][0] === "!" && escaped(text, match.index + 1)) continue;
|
|
2394
|
+
const after = labels.lastIndex;
|
|
2395
|
+
let end = after;
|
|
2396
|
+
let href;
|
|
2397
|
+
if (text[after] === "(") {
|
|
2398
|
+
const parsed = destination(text, after + 1);
|
|
2399
|
+
if (parsed) {
|
|
2400
|
+
const tail = text.slice(parsed.end).match(/^\s*(?:(?:"[^"\n]*"|'[^'\n]*'|\([^\n)]*\))\s*)?\)/);
|
|
2401
|
+
if (tail) {
|
|
2402
|
+
href = parsed.href;
|
|
2403
|
+
end = parsed.end + tail[0].length;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
} else {
|
|
2407
|
+
const suffix = text.slice(after).match(/^\[([^\]\n]*)\]/);
|
|
2408
|
+
const label = suffix?.[1] || match[1];
|
|
2409
|
+
href = definitions2.get(labelKey(label));
|
|
2410
|
+
if (suffix) end += suffix[0].length;
|
|
2411
|
+
}
|
|
2412
|
+
if (href !== void 0) {
|
|
2413
|
+
references.push({ href: unescape(href), kind: "markdown-link", line: lineAt(match.index) });
|
|
2414
|
+
linkRanges.push([match.index, end]);
|
|
2415
|
+
labels.lastIndex = end;
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
for (const item of code) {
|
|
2419
|
+
if (linkRanges.some(([start, end]) => item.offset >= start && item.end <= end)) continue;
|
|
2420
|
+
const path = item.href.split("#")[0];
|
|
2421
|
+
if (!path.includes("/") && !/\.[\w-]+$/.test(path) || /[\s*?<>\[\]{}]/.test(path) || path.endsWith("/")) continue;
|
|
2422
|
+
references.push({ href: item.href, kind: item.kind, line: item.line });
|
|
2423
|
+
}
|
|
2424
|
+
return references;
|
|
2425
|
+
}
|
|
2426
|
+
function sharedLocalReferences(content, root, file, config) {
|
|
2427
|
+
const source = resolveMemoryArea(relative3(root, file).split("\\").join("/"), config);
|
|
2428
|
+
if (source?.scope !== "shared") return [];
|
|
2429
|
+
const errors = [];
|
|
2430
|
+
for (const item of extractMemoryReferences(content)) {
|
|
2431
|
+
if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(item.href)) continue;
|
|
2432
|
+
let path;
|
|
2433
|
+
try {
|
|
2434
|
+
path = decodeURIComponent(item.href.split(/[?#]/)[0]);
|
|
2435
|
+
} catch {
|
|
2436
|
+
continue;
|
|
2437
|
+
}
|
|
2438
|
+
if (!path) continue;
|
|
2439
|
+
const base = item.kind === "markdown-link" || /^\.\.?\//.test(path) ? dirname5(file) : root;
|
|
2440
|
+
const target = relative3(root, resolve7(base, path)).split("\\").join("/");
|
|
2441
|
+
if (target === ".." || target.startsWith("../")) continue;
|
|
2442
|
+
if (item.kind === "inline-code") {
|
|
2443
|
+
const configuredDirectory = (config.areas ?? []).some((area3) => (area3.paths ?? []).some((pattern) => pattern === `${target}/**`));
|
|
2444
|
+
let directory = configuredDirectory;
|
|
2445
|
+
try {
|
|
2446
|
+
directory ||= statSync5(resolve7(root, target)).isDirectory();
|
|
2447
|
+
} catch {
|
|
2448
|
+
}
|
|
2449
|
+
if (directory) continue;
|
|
2450
|
+
}
|
|
2451
|
+
const area2 = resolveMemoryArea(target, config);
|
|
2452
|
+
if (area2?.scope === "local") errors.push({ ...item, target, sourceArea: source.id, targetArea: area2.id });
|
|
2453
|
+
}
|
|
2454
|
+
return errors;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2324
2457
|
// packages/cli/src/validate/run.mjs
|
|
2325
2458
|
function runValidate(argv) {
|
|
2326
2459
|
const options = { root: ".", includeLocalMemory: false, checkIndex: false, maxLines: null, maxChars: null, linkCheck: true, json: false };
|
|
@@ -2335,7 +2468,7 @@ function runValidate(argv) {
|
|
|
2335
2468
|
else if (!arg.startsWith("-")) options.root = arg;
|
|
2336
2469
|
else usage(`Unknown option: ${arg}`, "validate");
|
|
2337
2470
|
}
|
|
2338
|
-
const root =
|
|
2471
|
+
const root = resolve8(options.root);
|
|
2339
2472
|
const memoryConfig = readMemoryConfig(root);
|
|
2340
2473
|
const documentationRoot = memoryConfig.documentation?.root ?? "docs";
|
|
2341
2474
|
const docs = join8(root, documentationRoot);
|
|
@@ -2399,7 +2532,7 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
|
|
|
2399
2532
|
walk(docs);
|
|
2400
2533
|
const dirSiblingMap = /* @__PURE__ */ new Map();
|
|
2401
2534
|
for (const f of markdownFiles) {
|
|
2402
|
-
const dir =
|
|
2535
|
+
const dir = dirname6(f);
|
|
2403
2536
|
if (!dirSiblingMap.has(dir)) dirSiblingMap.set(dir, []);
|
|
2404
2537
|
dirSiblingMap.get(dir).push(basename7(f));
|
|
2405
2538
|
}
|
|
@@ -2415,7 +2548,7 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
|
|
|
2415
2548
|
const fp = validationPolicy.markdown?.filename ?? {};
|
|
2416
2549
|
const allow = fp.allow ?? [];
|
|
2417
2550
|
if (!allow.some((p) => matchPathPattern(rootRel, p)) && (fp.warnNumberedSeries ?? true)) {
|
|
2418
|
-
const siblings = dirSiblingMap.get(
|
|
2551
|
+
const siblings = dirSiblingMap.get(dirname6(file)) ?? [];
|
|
2419
2552
|
if (isNumberedSeriesFilename(name, siblings)) {
|
|
2420
2553
|
const heading = extractFirstHeading(content);
|
|
2421
2554
|
const suggestion = suggestFilenameFromHeading(heading);
|
|
@@ -2457,18 +2590,23 @@ ${traceabilityExample()}`, block.line);
|
|
|
2457
2590
|
}
|
|
2458
2591
|
}
|
|
2459
2592
|
const byDir = /* @__PURE__ */ new Map();
|
|
2460
|
-
for (const file of markdownFiles) byDir.set(
|
|
2593
|
+
for (const file of markdownFiles) byDir.set(dirname6(file), [...byDir.get(dirname6(file)) ?? [], file]);
|
|
2461
2594
|
for (const [dir, files] of byDir) {
|
|
2462
2595
|
if (files.length > 1 && !files.some((file) => basename7(file) === "README.md")) addError(dir, "MISSING_README", "Directory with multiple markdown files must include README.md", null, "add a README.md in this directory that indexes the important markdown files and their purpose.");
|
|
2463
2596
|
}
|
|
2597
|
+
for (const [file, content] of fileCache) {
|
|
2598
|
+
for (const item of sharedLocalReferences(content, root, file, memoryConfig)) {
|
|
2599
|
+
addError(file, "SHARED_LOCAL_MEMORY_REFERENCE", `${item.kind} from shared area "${item.sourceArea}" targets local area "${item.targetArea}": ${item.target}`, item.line, "replace the local reference with shared durable documentation, or remove the dependency. Inline-code directory, glob, and placeholder usage examples are allowed.");
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2464
2602
|
if (options.linkCheck) {
|
|
2465
2603
|
for (const [file, content] of fileCache) {
|
|
2466
|
-
const fileDir =
|
|
2604
|
+
const fileDir = dirname6(file);
|
|
2467
2605
|
for (const { href, line } of extractLinks(content)) {
|
|
2468
2606
|
const hashIndex = href.indexOf("#");
|
|
2469
2607
|
const pathPart = hashIndex === -1 ? href : href.slice(0, hashIndex);
|
|
2470
2608
|
const anchor = hashIndex === -1 ? "" : href.slice(hashIndex + 1);
|
|
2471
|
-
const target = pathPart ?
|
|
2609
|
+
const target = pathPart ? resolve8(fileDir, pathPart) : file;
|
|
2472
2610
|
if (pathPart && !existsSync10(target)) {
|
|
2473
2611
|
addError(file, "BROKEN_LINK", `Local link target does not exist: ${pathPart}`, line, "update the link target to an existing local file, create the intended file, or remove the stale link.");
|
|
2474
2612
|
continue;
|
|
@@ -2538,8 +2676,8 @@ Prompt: ${error.prompt}` : ""}`);
|
|
|
2538
2676
|
}
|
|
2539
2677
|
|
|
2540
2678
|
// packages/cli/src/commands/config.mjs
|
|
2541
|
-
import { existsSync as existsSync11, statSync as
|
|
2542
|
-
import { join as join9, resolve as
|
|
2679
|
+
import { existsSync as existsSync11, statSync as statSync6, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2680
|
+
import { join as join9, resolve as resolve9 } from "node:path";
|
|
2543
2681
|
function parseConfigOptions(argv, usageKey = "config") {
|
|
2544
2682
|
const options = { root: ".", json: false, template: null };
|
|
2545
2683
|
let rootSet = false;
|
|
@@ -2557,7 +2695,7 @@ function parseConfigOptions(argv, usageKey = "config") {
|
|
|
2557
2695
|
} else if (!arg.startsWith("-")) usage(`Unexpected argument: ${arg}`, usageKey);
|
|
2558
2696
|
else usage(`Unknown option: ${arg}`, usageKey);
|
|
2559
2697
|
}
|
|
2560
|
-
options.root =
|
|
2698
|
+
options.root = resolve9(options.root);
|
|
2561
2699
|
return options;
|
|
2562
2700
|
}
|
|
2563
2701
|
function configInitError(options, code, message, path = null) {
|
|
@@ -2602,7 +2740,7 @@ function runConfig(argv) {
|
|
|
2602
2740
|
if (isInit) {
|
|
2603
2741
|
if (!existsSync11(options.root)) configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
|
|
2604
2742
|
try {
|
|
2605
|
-
if (!
|
|
2743
|
+
if (!statSync6(options.root).isDirectory()) configInitError(options, "ROOT_NOT_DIRECTORY", `Project root is not a directory: ${options.root}`);
|
|
2606
2744
|
} catch {
|
|
2607
2745
|
configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
|
|
2608
2746
|
}
|
|
@@ -2633,11 +2771,11 @@ function runConfig(argv) {
|
|
|
2633
2771
|
}
|
|
2634
2772
|
|
|
2635
2773
|
// packages/cli/src/commands/query.mjs
|
|
2636
|
-
import { resolve as
|
|
2774
|
+
import { resolve as resolve10 } from "node:path";
|
|
2637
2775
|
|
|
2638
2776
|
// packages/cli/src/query/chunks.mjs
|
|
2639
2777
|
import { readFileSync as readFileSync10, readdirSync as readdirSync3 } from "node:fs";
|
|
2640
|
-
import { join as join10, relative as
|
|
2778
|
+
import { join as join10, relative as relative4 } from "node:path";
|
|
2641
2779
|
import { createHash as createHash3 } from "node:crypto";
|
|
2642
2780
|
var MAX_CHUNK_CHARS = 1600;
|
|
2643
2781
|
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
@@ -2658,7 +2796,7 @@ function collectDocumentationMarkdown(root, exclude = ["docs/plan", "docs/archiv
|
|
|
2658
2796
|
for (const entry of entries) {
|
|
2659
2797
|
if (entry.name.startsWith(".")) continue;
|
|
2660
2798
|
const absolute = join10(directory, entry.name);
|
|
2661
|
-
const path =
|
|
2799
|
+
const path = relative4(root, absolute).replaceAll("\\", "/");
|
|
2662
2800
|
if (isSecretLikePathPattern(path) || exclude.some((pattern) => matchPathPattern(path, pattern) || path === pattern || path.startsWith(`${pattern}/`))) continue;
|
|
2663
2801
|
if (entry.isDirectory()) {
|
|
2664
2802
|
if (!SKIPPED_DIRECTORIES.has(entry.name)) walk(absolute);
|
|
@@ -2951,7 +3089,7 @@ function parseQueryOptions(argv) {
|
|
|
2951
3089
|
options.limit = value;
|
|
2952
3090
|
} else if (arg.startsWith("-")) usage(`Unknown option: ${arg}`, "query");
|
|
2953
3091
|
else if (!rootSet) {
|
|
2954
|
-
options.root =
|
|
3092
|
+
options.root = resolve10(arg);
|
|
2955
3093
|
rootSet = true;
|
|
2956
3094
|
} else queryParts.push(arg);
|
|
2957
3095
|
}
|
|
@@ -3062,7 +3200,7 @@ async function runQuery(argv) {
|
|
|
3062
3200
|
}
|
|
3063
3201
|
|
|
3064
3202
|
// packages/cli/src/commands/embedding.mjs
|
|
3065
|
-
import { resolve as
|
|
3203
|
+
import { resolve as resolve11 } from "node:path";
|
|
3066
3204
|
function parse(argv) {
|
|
3067
3205
|
const [action2 = "status", ...rest] = argv;
|
|
3068
3206
|
let root = ".";
|
|
@@ -3074,7 +3212,7 @@ function parse(argv) {
|
|
|
3074
3212
|
else if (!arg.startsWith("-") && root === ".") root = arg;
|
|
3075
3213
|
else usage(`Unknown embedding option: ${arg}`, `embedding ${action2}`);
|
|
3076
3214
|
}
|
|
3077
|
-
return { action: action2, root:
|
|
3215
|
+
return { action: action2, root: resolve11(root), json, confirm };
|
|
3078
3216
|
}
|
|
3079
3217
|
function runEmbedding(argv, options = {}) {
|
|
3080
3218
|
const parsed = parse(argv);
|
|
@@ -3094,11 +3232,11 @@ function runEmbedding(argv, options = {}) {
|
|
|
3094
3232
|
}
|
|
3095
3233
|
|
|
3096
3234
|
// packages/cli/src/commands/map.mjs
|
|
3097
|
-
import { resolve as
|
|
3235
|
+
import { resolve as resolve13 } from "node:path";
|
|
3098
3236
|
|
|
3099
3237
|
// packages/cli/src/memory/documentation-map.mjs
|
|
3100
|
-
import { existsSync as existsSync14, readdirSync as readdirSync4, statSync as
|
|
3101
|
-
import { join as join13, resolve as
|
|
3238
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
|
|
3239
|
+
import { join as join13, resolve as resolve12 } from "node:path";
|
|
3102
3240
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
3103
3241
|
function node() {
|
|
3104
3242
|
return { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
@@ -3178,10 +3316,10 @@ function discoverMarkdown(root, documentationRoot, exclude) {
|
|
|
3178
3316
|
return paths.sort();
|
|
3179
3317
|
}
|
|
3180
3318
|
function buildDocumentationMap(projectRoot = ".", { depth = 5 } = {}) {
|
|
3181
|
-
const root =
|
|
3319
|
+
const root = resolve12(projectRoot);
|
|
3182
3320
|
if (!existsSync14(root)) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
3183
3321
|
try {
|
|
3184
|
-
if (!
|
|
3322
|
+
if (!statSync7(root).isDirectory()) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
3185
3323
|
} catch {
|
|
3186
3324
|
return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
3187
3325
|
}
|
|
@@ -3227,7 +3365,7 @@ function parseMapOptions(argv) {
|
|
|
3227
3365
|
} else if (!arg.startsWith("-")) fail(options.json, "UNEXPECTED_ARGUMENT", `Unexpected argument: ${arg}`);
|
|
3228
3366
|
else fail(options.json, "UNKNOWN_OPTION", `Unknown option: ${arg}`);
|
|
3229
3367
|
}
|
|
3230
|
-
options.root =
|
|
3368
|
+
options.root = resolve13(options.root);
|
|
3231
3369
|
return options;
|
|
3232
3370
|
}
|
|
3233
3371
|
function runMap(argv) {
|
|
@@ -3239,7 +3377,7 @@ function runMap(argv) {
|
|
|
3239
3377
|
}
|
|
3240
3378
|
|
|
3241
3379
|
// packages/cli/src/reference/resolve.mjs
|
|
3242
|
-
import { resolve as
|
|
3380
|
+
import { resolve as resolve15 } from "node:path";
|
|
3243
3381
|
|
|
3244
3382
|
// node_modules/.pnpm/leiden-ts@0.1.0/node_modules/leiden-ts/dist/index.js
|
|
3245
3383
|
var GraphValidationError = class extends Error {
|
|
@@ -4475,8 +4613,8 @@ function buildCommunities(index, limits = {}) {
|
|
|
4475
4613
|
byCommunity.get(communityId).push(projection.durable[index2]);
|
|
4476
4614
|
});
|
|
4477
4615
|
const communities = [...byCommunity.entries()].map(([communityId, nodes]) => {
|
|
4478
|
-
const
|
|
4479
|
-
const community = makeCommunity(`leiden-${communityId}`, communityLabel(
|
|
4616
|
+
const labelKey2 = labelForLeidenCommunity(nodes, config);
|
|
4617
|
+
const community = makeCommunity(`leiden-${communityId}`, communityLabel(labelKey2));
|
|
4480
4618
|
for (const node2 of nodes.sort((a, b) => a.id.localeCompare(b.id))) addCommunityDetails(community, node2, itemLimit, config);
|
|
4481
4619
|
return community;
|
|
4482
4620
|
});
|
|
@@ -4611,14 +4749,14 @@ function compareImpactItems(seeds) {
|
|
|
4611
4749
|
|
|
4612
4750
|
// packages/cli/src/impact/vector-profile.mjs
|
|
4613
4751
|
import { lstatSync, readFileSync as readFileSync13, realpathSync } from "node:fs";
|
|
4614
|
-
import { extname as extname5, relative as
|
|
4752
|
+
import { extname as extname5, relative as relative5, resolve as resolve14, sep } from "node:path";
|
|
4615
4753
|
var MAX_VECTOR_PROFILE_FILE_BYTES = 64 * 1024;
|
|
4616
4754
|
var MAX_VECTOR_PROFILE_CHARS = 4e3;
|
|
4617
4755
|
var MAX_VECTOR_PROFILE_METADATA_ITEMS = 20;
|
|
4618
4756
|
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".c", ".cc", ".cpp", ".css", ".go", ".h", ".hpp", ".html", ".java", ".js", ".json", ".jsx", ".md", ".mjs", ".py", ".rb", ".rs", ".sh", ".ts", ".tsx", ".txt", ".yaml", ".yml"]);
|
|
4619
4757
|
var GENERATED_SEGMENTS = /* @__PURE__ */ new Set([".dotdotgod", "build", "coverage", "dist", "node_modules"]);
|
|
4620
4758
|
function insideRoot(root, absolute) {
|
|
4621
|
-
const normalizedRoot =
|
|
4759
|
+
const normalizedRoot = resolve14(root);
|
|
4622
4760
|
return absolute === normalizedRoot || absolute.startsWith(`${normalizedRoot}${sep}`);
|
|
4623
4761
|
}
|
|
4624
4762
|
function normalizeChangedPath(path) {
|
|
@@ -4629,17 +4767,17 @@ function normalizeChangedPath(path) {
|
|
|
4629
4767
|
function canonicalizeChangedPath(root, path) {
|
|
4630
4768
|
const normalized = normalizeChangedPath(path);
|
|
4631
4769
|
if (!normalized) return null;
|
|
4632
|
-
const rootAbsolute =
|
|
4633
|
-
const absolute =
|
|
4770
|
+
const rootAbsolute = resolve14(root);
|
|
4771
|
+
const absolute = resolve14(rootAbsolute, normalized);
|
|
4634
4772
|
if (!insideRoot(rootAbsolute, absolute)) return normalized;
|
|
4635
4773
|
try {
|
|
4636
4774
|
const canonicalRoot = realpathSync(rootAbsolute);
|
|
4637
4775
|
const canonicalAbsolute = realpathSync(absolute);
|
|
4638
4776
|
if (!insideRoot(canonicalRoot, canonicalAbsolute)) return normalized;
|
|
4639
|
-
const canonical =
|
|
4777
|
+
const canonical = relative5(canonicalRoot, canonicalAbsolute).replaceAll("\\", "/");
|
|
4640
4778
|
return canonical || normalized;
|
|
4641
4779
|
} catch {
|
|
4642
|
-
return
|
|
4780
|
+
return relative5(rootAbsolute, absolute).replaceAll("\\", "/") || normalized;
|
|
4643
4781
|
}
|
|
4644
4782
|
}
|
|
4645
4783
|
function unsafeProfilePath(path) {
|
|
@@ -4661,11 +4799,11 @@ function graphMetadata(graph, seedId) {
|
|
|
4661
4799
|
function safeTextPrefix(root, path) {
|
|
4662
4800
|
if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname5(path).toLowerCase())) return "";
|
|
4663
4801
|
try {
|
|
4664
|
-
const absolute =
|
|
4802
|
+
const absolute = resolve14(root, path);
|
|
4665
4803
|
if (!insideRoot(root, absolute)) return "";
|
|
4666
4804
|
const canonicalAbsolute = realpathSync(absolute);
|
|
4667
4805
|
if (!insideRoot(realpathSync(root), canonicalAbsolute)) return "";
|
|
4668
|
-
const canonicalPath =
|
|
4806
|
+
const canonicalPath = relative5(realpathSync(root), canonicalAbsolute).replaceAll("\\", "/");
|
|
4669
4807
|
if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(extname5(canonicalPath).toLowerCase())) return "";
|
|
4670
4808
|
const stats = lstatSync(canonicalAbsolute);
|
|
4671
4809
|
if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_VECTOR_PROFILE_FILE_BYTES) return "";
|
|
@@ -4881,7 +5019,7 @@ function buildCompactImpactReport(impact, limits = {}) {
|
|
|
4881
5019
|
}
|
|
4882
5020
|
|
|
4883
5021
|
// packages/cli/src/reference/extract.mjs
|
|
4884
|
-
import { basename as basename8, dirname as
|
|
5022
|
+
import { basename as basename8, dirname as dirname7, extname as extname6 } from "node:path";
|
|
4885
5023
|
var DEFAULT_REFERENCE_LIMIT = 5;
|
|
4886
5024
|
function extractBracketReferences(prompt = "") {
|
|
4887
5025
|
const refs = [];
|
|
@@ -4932,7 +5070,7 @@ function aliasEntriesForPath(path = "") {
|
|
|
4932
5070
|
const parts = withoutMd.split("/").filter(Boolean);
|
|
4933
5071
|
for (let i = 0; i < parts.length; i += 1) entries.push({ alias: parts.slice(i).join("/"), kind: "path-suffix" });
|
|
4934
5072
|
if (base === "README.md") {
|
|
4935
|
-
const dir =
|
|
5073
|
+
const dir = dirname7(path).replace(/\\/g, "/");
|
|
4936
5074
|
entries.push({ alias: basename8(dir), kind: "path" });
|
|
4937
5075
|
}
|
|
4938
5076
|
for (const alias of [path, withoutMd, base, stem]) entries.push({ alias, kind: "path" });
|
|
@@ -4986,8 +5124,8 @@ function extractFuzzyReferences(prompt = "", index = null, options = {}) {
|
|
|
4986
5124
|
}
|
|
4987
5125
|
}
|
|
4988
5126
|
for (const { alias } of [...aliasByKey.values()].sort((a, b) => b.priority - a.priority || b.alias.length - a.alias.length)) {
|
|
4989
|
-
const
|
|
4990
|
-
const pattern = new RegExp(`(^|[^a-z0-9])${
|
|
5127
|
+
const escaped2 = alias.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\s+/g, "[\\s_-]+");
|
|
5128
|
+
const pattern = new RegExp(`(^|[^a-z0-9])${escaped2}([^a-z0-9]|$)`, "i");
|
|
4991
5129
|
if (pattern.test(lower)) add(alias, "known_alias", "medium");
|
|
4992
5130
|
if (refs.length >= (options.maxFuzzyRefs ?? 5)) break;
|
|
4993
5131
|
}
|
|
@@ -5062,7 +5200,7 @@ function parseReferenceOptions(argv, command) {
|
|
|
5062
5200
|
else filtered.push(arg);
|
|
5063
5201
|
}
|
|
5064
5202
|
const operands = filtered.filter((arg) => !arg.startsWith("-"));
|
|
5065
|
-
return { ...options, root:
|
|
5203
|
+
return { ...options, root: resolve15(operands[0] ?? "."), json: filtered.includes("--json"), rootArgv: operands.slice(1) };
|
|
5066
5204
|
}
|
|
5067
5205
|
function formatReferenceOutput(payload) {
|
|
5068
5206
|
const refreshNote = payload.metadata.cacheRefreshed ? ", refreshed" : "";
|
|
@@ -5112,7 +5250,7 @@ function runExpand(argv) {
|
|
|
5112
5250
|
|
|
5113
5251
|
// packages/cli/src/commands/traceability.mjs
|
|
5114
5252
|
import { existsSync as existsSync15, readFileSync as readFileSync14, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
5115
|
-
import { join as join14, resolve as
|
|
5253
|
+
import { join as join14, resolve as resolve16 } from "node:path";
|
|
5116
5254
|
function collectDocsMarkdownFiles(root) {
|
|
5117
5255
|
const docs = join14(root, "docs");
|
|
5118
5256
|
const files = [];
|
|
@@ -5144,7 +5282,7 @@ function parseTraceabilityOptions(argv) {
|
|
|
5144
5282
|
}
|
|
5145
5283
|
if (options.check && options.write) usage("Choose only one traceability links mode: --check or --write.", "traceability links");
|
|
5146
5284
|
options.check = options.check || !options.write;
|
|
5147
|
-
options.root =
|
|
5285
|
+
options.root = resolve16(options.root);
|
|
5148
5286
|
return options;
|
|
5149
5287
|
}
|
|
5150
5288
|
function runTraceability(argv) {
|
package/mcp/server.mjs
CHANGED
|
@@ -23157,7 +23157,7 @@ function markdownTree(root2, maxDepth = 5) {
|
|
|
23157
23157
|
walk(docs, 1);
|
|
23158
23158
|
return output;
|
|
23159
23159
|
}
|
|
23160
|
-
async function
|
|
23160
|
+
async function cliText(args, cwd) {
|
|
23161
23161
|
return await new Promise((resolvePromise, reject) => {
|
|
23162
23162
|
const child = spawn2(process.execPath, [cli, ...args], { cwd, stdio: ["ignore", "pipe", "pipe"] });
|
|
23163
23163
|
const stdout = [];
|
|
@@ -23168,14 +23168,18 @@ async function cliJson(args, cwd) {
|
|
|
23168
23168
|
child.once("close", (code) => {
|
|
23169
23169
|
const out = Buffer.concat(stdout).toString("utf8");
|
|
23170
23170
|
if (code !== 0) return reject(new Error(Buffer.concat(stderr).toString("utf8") || out || `dotdotgod exited ${code}`));
|
|
23171
|
-
|
|
23172
|
-
resolvePromise(JSON.parse(out));
|
|
23173
|
-
} catch {
|
|
23174
|
-
reject(new Error(`Expected JSON from dotdotgod: ${out.slice(0, 500)}`));
|
|
23175
|
-
}
|
|
23171
|
+
resolvePromise(out);
|
|
23176
23172
|
});
|
|
23177
23173
|
});
|
|
23178
23174
|
}
|
|
23175
|
+
async function cliJson(args, cwd) {
|
|
23176
|
+
const out = await cliText(args, cwd);
|
|
23177
|
+
try {
|
|
23178
|
+
return JSON.parse(out);
|
|
23179
|
+
} catch {
|
|
23180
|
+
throw new Error(`Expected JSON from dotdotgod: ${out.slice(0, 500)}`);
|
|
23181
|
+
}
|
|
23182
|
+
}
|
|
23179
23183
|
async function projectLoad(input) {
|
|
23180
23184
|
const root2 = resolve5(input.root || process.cwd());
|
|
23181
23185
|
const focus = input.focus?.trim() ?? "";
|
|
@@ -23217,8 +23221,8 @@ async function projectImpact(input) {
|
|
|
23217
23221
|
if (paths.length > 20) throw new Error("paths is limited to 20 entries per call");
|
|
23218
23222
|
const args = ["graph", "impact", root2];
|
|
23219
23223
|
for (const path of paths) args.push("--changed", path);
|
|
23220
|
-
args.push("--
|
|
23221
|
-
return await
|
|
23224
|
+
args.push("--compact");
|
|
23225
|
+
return { ok: true, summary: (await cliText(args, root2)).trim() };
|
|
23222
23226
|
}
|
|
23223
23227
|
async function projectInitialize(input) {
|
|
23224
23228
|
const root2 = resolve5(input.root || process.cwd());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotdotgod/claude-code",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
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.3.
|
|
28
|
+
"@dotdotgod/context": "0.3.10"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
31
|
".claude-plugin",
|
|
@@ -10,20 +10,31 @@ version: 1.0.0
|
|
|
10
10
|
|
|
11
11
|
## Memory-Area Context
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Use `dotdotgod config <root> --json` (or the source-checkout CLI equivalent) to obtain resolved `config.areas`; reuse current resolved settings already in context when the project/config has not changed. Normalize the target to a repository-relative path. Check areas in order, applying each area's `excludePaths` before selecting the first matching `paths` entry. Keep the matched area's relevant guidance in context rather than repeatedly injecting the entire configuration. If no area matches, use the document's purpose and repository conventions without inventing metadata.
|
|
14
14
|
|
|
15
15
|
## Goal
|
|
16
16
|
|
|
17
17
|
Make project documentation easier to understand and act on while preserving established behavior, decisions, task state, historical meaning, and traceability.
|
|
18
18
|
|
|
19
|
+
## Editing Checklist
|
|
20
|
+
|
|
21
|
+
Adapted from [Google Technical Writing One](https://developers.google.com/tech-writing/one); these principles supplement repository policy. Apply them directly without requiring runtime web access.
|
|
22
|
+
|
|
23
|
+
- **Audience:** distinguish what readers already know from what they need to learn or do. Add only evidence-backed context that closes that gap.
|
|
24
|
+
- **Terminology:** use one consistent name per concept; explain unfamiliar terms and abbreviations when readers need them. Preserve exact API names and identifiers.
|
|
25
|
+
- **Sentences:** use specific verbs and make responsibility clear. Prefer active voice when it helps; retain passive voice when appropriate and never invent an unknown actor. Keep one main idea per sentence while preserving conditions, exceptions, and logical relationships.
|
|
26
|
+
- **Paragraphs:** focus each paragraph on one topic and make its main point clear near the beginning. Remove sentences that do not support that topic or relocate them within the approved scope.
|
|
27
|
+
- **Lists and tables:** use numbered lists when order matters, bullets otherwise, and parallel phrasing for comparable items. Use tables when readers need to compare the same attributes across items, not merely to decorate prose.
|
|
28
|
+
- **Language:** apply these principles idiomatically in the document's language. Do not impose English word order, a passive-voice ban, or sentence-length quotas on Korean or other languages.
|
|
29
|
+
|
|
19
30
|
## Workflow
|
|
20
31
|
|
|
21
|
-
1. Confirm the target
|
|
32
|
+
1. Confirm the target, audience, requested outcome, and edit scope. Diagnose ambiguity, repetition, or missing information before editing; small changes need no separate diagnostic report. Use matched memory-area metadata when available: `clarify` guidance first, then `description`, `label`, and `role`.
|
|
22
33
|
2. Read only the context needed to preserve meaning: the nearest README, directly linked documents, relevant query results, and targeted history when a past decision matters.
|
|
23
|
-
3.
|
|
24
|
-
4. Prefer direct affirmative statements.
|
|
25
|
-
5. Follow
|
|
34
|
+
3. Apply criteria suited to the document: README navigation and starting points; spec conditions, behavior, and exceptions; test procedures and pass criteria; architecture boundaries, rationale, and constraints; plan ordering, dependencies, and completion criteria; archive decisions and outcomes in their historical context. Repository-specific guidance takes priority. Clarify terms, ownership, headings, links, and runnable examples. Preserve commands, paths, package/API names, requirement strength, conditions, exceptions, unresolved questions, limitations, and evidence unless verified sources support a change. Do not invent missing facts.
|
|
35
|
+
4. Prefer direct affirmative statements. Remove repeated framing, indirect wording, mixed responsibilities, and irrelevant background. Remove negative or absence statements only when deleting them leaves the reader's understanding, actions, and decisions unchanged. Rewrite redundant contrasts affirmatively: “It is not automatic; the user starts it” becomes “The user starts it.” Preserve meaningful prohibitions, unsupported cases, exceptions, security constraints, and unverified status. Use “X is not Y; it is Z” when the contrast resolves a likely ambiguity. Accuracy takes priority over brevity.
|
|
36
|
+
5. Keep edits within the agreed scope. Confirm scope before splitting or moving files, changing meaning, or editing additional documents outside that scope. Update the nearest README when approved structural changes affect navigation. Follow generated markers and canonical-source instructions. For dotdotgod traceability, edit the fenced `json dotdotgod` block as the canonical mapping. Inspect `dotdotgod traceability links <root> --check --json` before using `--write`; the root-wide write can change unrelated documents, so obtain approval if its affected files exceed the agreed scope. Apply ordinary reference policy: shared documents must not depend on concrete local file paths in inline code or Markdown links; inline-code directory, glob, and placeholder usage examples remain allowed. Preserve traceability's separate stricter local-target prohibition.
|
|
26
37
|
6. Ask for a decision when the requested clarification would change established meaning or when current sources conflict.
|
|
27
|
-
7. Run verification that matches the changed surface. Use documentation and traceability checks for ordinary docs; add generation checks, focused package tests, dry-runs, or workspace verification when shared resources or product behavior are affected.
|
|
38
|
+
7. Run verification that matches the changed surface. Use documentation and traceability checks for ordinary docs; add generation checks, focused package tests, dry-runs, or workspace verification when shared resources or product behavior are affected. Report briefly: what became clearer; important meaning preserved and unresolved questions; checks run and checks not run. Do not equate shorter text with improved accuracy.
|
|
28
39
|
|
|
29
40
|
When CLI-backed routing is unavailable, continue from the target document, nearest README, direct links, and repository conventions.
|