@dotdotgod/hermes 0.6.1 → 0.6.3
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 +1313 -254
- package/mcp/parsers/IGNORE_LICENSE +21 -0
- package/mcp/parsers/LICENSE +21 -0
- package/mcp/parsers/tree-sitter-bash.wasm +0 -0
- package/mcp/parsers/tree-sitter-c-sharp.wasm +0 -0
- package/mcp/parsers/tree-sitter-cpp.wasm +0 -0
- package/mcp/parsers/tree-sitter-css.wasm +0 -0
- package/mcp/parsers/tree-sitter-go.wasm +0 -0
- package/mcp/parsers/tree-sitter-ini.wasm +0 -0
- package/mcp/parsers/tree-sitter-java.wasm +0 -0
- package/mcp/parsers/tree-sitter-javascript.wasm +0 -0
- package/mcp/parsers/tree-sitter-php.wasm +0 -0
- package/mcp/parsers/tree-sitter-powershell.wasm +0 -0
- package/mcp/parsers/tree-sitter-python.wasm +0 -0
- package/mcp/parsers/tree-sitter-regex.wasm +0 -0
- package/mcp/parsers/tree-sitter-ruby.wasm +0 -0
- package/mcp/parsers/tree-sitter-rust.wasm +0 -0
- package/mcp/parsers/tree-sitter-tsx.wasm +0 -0
- package/mcp/parsers/tree-sitter-typescript.wasm +0 -0
- package/mcp/parsers/tree-sitter.cjs +4075 -0
- package/mcp/parsers/tree-sitter.wasm +0 -0
- package/mcp/server.mjs +11 -6
- package/package.json +2 -2
- package/plugin.yaml +1 -1
- package/skills/project-load/SKILL.md +2 -2
package/mcp/cli.mjs
CHANGED
|
@@ -1,5 +1,759 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Generated by scripts/build-adapter-runtime.mjs; optional embedding runtime is packaged separately.
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
13
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
14
|
+
};
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
28
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
29
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
30
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
31
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
32
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
33
|
+
mod
|
|
34
|
+
));
|
|
35
|
+
|
|
36
|
+
// node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
|
|
37
|
+
var require_ignore = __commonJS({
|
|
38
|
+
"node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
|
|
39
|
+
function makeArray(subject) {
|
|
40
|
+
return Array.isArray(subject) ? subject : [subject];
|
|
41
|
+
}
|
|
42
|
+
var UNDEFINED = void 0;
|
|
43
|
+
var EMPTY = "";
|
|
44
|
+
var SPACE = " ";
|
|
45
|
+
var ESCAPE = "\\";
|
|
46
|
+
var REGEX_TEST_BLANK_LINE = /^\s+$/;
|
|
47
|
+
var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
|
|
48
|
+
var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
|
|
49
|
+
var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
|
|
50
|
+
var REGEX_SPLITALL_CRLF = /\r?\n/g;
|
|
51
|
+
var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
|
|
52
|
+
var REGEX_TEST_TRAILING_SLASH = /\/$/;
|
|
53
|
+
var SLASH = "/";
|
|
54
|
+
var TMP_KEY_IGNORE = "node-ignore";
|
|
55
|
+
if (typeof Symbol !== "undefined") {
|
|
56
|
+
TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
|
|
57
|
+
}
|
|
58
|
+
var KEY_IGNORE = TMP_KEY_IGNORE;
|
|
59
|
+
var define = (object, key, value) => {
|
|
60
|
+
Object.defineProperty(object, key, { value });
|
|
61
|
+
return value;
|
|
62
|
+
};
|
|
63
|
+
var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
|
|
64
|
+
var RETURN_FALSE = () => false;
|
|
65
|
+
var sanitizeRange = (range) => range.replace(
|
|
66
|
+
REGEX_REGEXP_RANGE,
|
|
67
|
+
(match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
|
|
68
|
+
);
|
|
69
|
+
var cleanRangeBackSlash = (slashes) => {
|
|
70
|
+
const { length } = slashes;
|
|
71
|
+
return slashes.slice(0, length - length % 2);
|
|
72
|
+
};
|
|
73
|
+
var REPLACERS = [
|
|
74
|
+
[
|
|
75
|
+
// Remove BOM
|
|
76
|
+
// TODO:
|
|
77
|
+
// Other similar zero-width characters?
|
|
78
|
+
/^\uFEFF/,
|
|
79
|
+
() => EMPTY
|
|
80
|
+
],
|
|
81
|
+
// > Trailing spaces are ignored unless they are quoted with backslash ("\")
|
|
82
|
+
[
|
|
83
|
+
// (a\ ) -> (a )
|
|
84
|
+
// (a ) -> (a)
|
|
85
|
+
// (a ) -> (a)
|
|
86
|
+
// (a \ ) -> (a )
|
|
87
|
+
/((?:\\\\)*?)(\\?\s+)$/,
|
|
88
|
+
(_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
|
|
89
|
+
],
|
|
90
|
+
// Replace (\ ) with ' '
|
|
91
|
+
// (\ ) -> ' '
|
|
92
|
+
// (\\ ) -> '\\ '
|
|
93
|
+
// (\\\ ) -> '\\ '
|
|
94
|
+
[
|
|
95
|
+
/(\\+?)\s/g,
|
|
96
|
+
(_, m1) => {
|
|
97
|
+
const { length } = m1;
|
|
98
|
+
return m1.slice(0, length - length % 2) + SPACE;
|
|
99
|
+
}
|
|
100
|
+
],
|
|
101
|
+
// Escape metacharacters
|
|
102
|
+
// which is written down by users but means special for regular expressions.
|
|
103
|
+
// > There are 12 characters with special meanings:
|
|
104
|
+
// > - the backslash \,
|
|
105
|
+
// > - the caret ^,
|
|
106
|
+
// > - the dollar sign $,
|
|
107
|
+
// > - the period or dot .,
|
|
108
|
+
// > - the vertical bar or pipe symbol |,
|
|
109
|
+
// > - the question mark ?,
|
|
110
|
+
// > - the asterisk or star *,
|
|
111
|
+
// > - the plus sign +,
|
|
112
|
+
// > - the opening parenthesis (,
|
|
113
|
+
// > - the closing parenthesis ),
|
|
114
|
+
// > - and the opening square bracket [,
|
|
115
|
+
// > - the opening curly brace {,
|
|
116
|
+
// > These special characters are often called "metacharacters".
|
|
117
|
+
[
|
|
118
|
+
/[\\$.|*+(){^]/g,
|
|
119
|
+
(match) => `\\${match}`
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
// > a question mark (?) matches a single character
|
|
123
|
+
/(?!\\)\?/g,
|
|
124
|
+
() => "[^/]"
|
|
125
|
+
],
|
|
126
|
+
// leading slash
|
|
127
|
+
[
|
|
128
|
+
// > A leading slash matches the beginning of the pathname.
|
|
129
|
+
// > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
|
|
130
|
+
// A leading slash matches the beginning of the pathname
|
|
131
|
+
/^\//,
|
|
132
|
+
() => "^"
|
|
133
|
+
],
|
|
134
|
+
// replace special metacharacter slash after the leading slash
|
|
135
|
+
[
|
|
136
|
+
/\//g,
|
|
137
|
+
() => "\\/"
|
|
138
|
+
],
|
|
139
|
+
[
|
|
140
|
+
// > A leading "**" followed by a slash means match in all directories.
|
|
141
|
+
// > For example, "**/foo" matches file or directory "foo" anywhere,
|
|
142
|
+
// > the same as pattern "foo".
|
|
143
|
+
// > "**/foo/bar" matches file or directory "bar" anywhere that is directly
|
|
144
|
+
// > under directory "foo".
|
|
145
|
+
// Notice that the '*'s have been replaced as '\\*'
|
|
146
|
+
/^\^*\\\*\\\*\\\//,
|
|
147
|
+
// '**/foo' <-> 'foo'
|
|
148
|
+
() => "^(?:.*\\/)?"
|
|
149
|
+
],
|
|
150
|
+
// starting
|
|
151
|
+
[
|
|
152
|
+
// there will be no leading '/'
|
|
153
|
+
// (which has been replaced by section "leading slash")
|
|
154
|
+
// If starts with '**', adding a '^' to the regular expression also works
|
|
155
|
+
/^(?=[^^])/,
|
|
156
|
+
function startingReplacer() {
|
|
157
|
+
return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
|
|
158
|
+
}
|
|
159
|
+
],
|
|
160
|
+
// two globstars
|
|
161
|
+
[
|
|
162
|
+
// Use lookahead assertions so that we could match more than one `'/**'`
|
|
163
|
+
/\\\/\\\*\\\*(?=\\\/|$)/g,
|
|
164
|
+
// Zero, one or several directories
|
|
165
|
+
// should not use '*', or it will be replaced by the next replacer
|
|
166
|
+
// Check if it is not the last `'/**'`
|
|
167
|
+
(_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
|
|
168
|
+
],
|
|
169
|
+
// normal intermediate wildcards
|
|
170
|
+
[
|
|
171
|
+
// Never replace escaped '*'
|
|
172
|
+
// ignore rule '\*' will match the path '*'
|
|
173
|
+
// 'abc.*/' -> go
|
|
174
|
+
// 'abc.*' -> skip this rule,
|
|
175
|
+
// coz trailing single wildcard will be handed by [trailing wildcard]
|
|
176
|
+
/(^|[^\\]+)(\\\*)+(?=.+)/g,
|
|
177
|
+
// '*.js' matches '.js'
|
|
178
|
+
// '*.js' doesn't match 'abc'
|
|
179
|
+
(_, p1, p2) => {
|
|
180
|
+
const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
|
|
181
|
+
return p1 + unescaped;
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
[
|
|
185
|
+
// unescape, revert step 3 except for back slash
|
|
186
|
+
// For example, if a user escape a '\\*',
|
|
187
|
+
// after step 3, the result will be '\\\\\\*'
|
|
188
|
+
/\\\\\\(?=[$.|*+(){^])/g,
|
|
189
|
+
() => ESCAPE
|
|
190
|
+
],
|
|
191
|
+
[
|
|
192
|
+
// '\\\\' -> '\\'
|
|
193
|
+
/\\\\/g,
|
|
194
|
+
() => ESCAPE
|
|
195
|
+
],
|
|
196
|
+
[
|
|
197
|
+
// > The range notation, e.g. [a-zA-Z],
|
|
198
|
+
// > can be used to match one of the characters in a range.
|
|
199
|
+
// `\` is escaped by step 3
|
|
200
|
+
/(\\)?\[([^\]/]*?)(\\*)($|\])/g,
|
|
201
|
+
(match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
|
|
202
|
+
],
|
|
203
|
+
// ending
|
|
204
|
+
[
|
|
205
|
+
// 'js' will not match 'js.'
|
|
206
|
+
// 'ab' will not match 'abc'
|
|
207
|
+
/(?:[^*])$/,
|
|
208
|
+
// WTF!
|
|
209
|
+
// https://git-scm.com/docs/gitignore
|
|
210
|
+
// changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
|
|
211
|
+
// which re-fixes #24, #38
|
|
212
|
+
// > If there is a separator at the end of the pattern then the pattern
|
|
213
|
+
// > will only match directories, otherwise the pattern can match both
|
|
214
|
+
// > files and directories.
|
|
215
|
+
// 'js*' will not match 'a.js'
|
|
216
|
+
// 'js/' will not match 'a.js'
|
|
217
|
+
// 'js' will match 'a.js' and 'a.js/'
|
|
218
|
+
(match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
|
|
219
|
+
]
|
|
220
|
+
];
|
|
221
|
+
var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
|
|
222
|
+
var MODE_IGNORE = "regex";
|
|
223
|
+
var MODE_CHECK_IGNORE = "checkRegex";
|
|
224
|
+
var UNDERSCORE = "_";
|
|
225
|
+
var TRAILING_WILD_CARD_REPLACERS = {
|
|
226
|
+
[MODE_IGNORE](_, p1) {
|
|
227
|
+
const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
|
|
228
|
+
return `${prefix}(?=$|\\/$)`;
|
|
229
|
+
},
|
|
230
|
+
[MODE_CHECK_IGNORE](_, p1) {
|
|
231
|
+
const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
|
|
232
|
+
return `${prefix}(?=$|\\/$)`;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var makeRegexPrefix = (pattern) => REPLACERS.reduce(
|
|
236
|
+
(prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
|
|
237
|
+
pattern
|
|
238
|
+
);
|
|
239
|
+
var isString = (subject) => typeof subject === "string";
|
|
240
|
+
var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
|
|
241
|
+
var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
|
|
242
|
+
var IgnoreRule = class {
|
|
243
|
+
constructor(pattern, mark, body, ignoreCase, negative, prefix) {
|
|
244
|
+
this.pattern = pattern;
|
|
245
|
+
this.mark = mark;
|
|
246
|
+
this.negative = negative;
|
|
247
|
+
define(this, "body", body);
|
|
248
|
+
define(this, "ignoreCase", ignoreCase);
|
|
249
|
+
define(this, "regexPrefix", prefix);
|
|
250
|
+
}
|
|
251
|
+
get regex() {
|
|
252
|
+
const key = UNDERSCORE + MODE_IGNORE;
|
|
253
|
+
if (this[key]) {
|
|
254
|
+
return this[key];
|
|
255
|
+
}
|
|
256
|
+
return this._make(MODE_IGNORE, key);
|
|
257
|
+
}
|
|
258
|
+
get checkRegex() {
|
|
259
|
+
const key = UNDERSCORE + MODE_CHECK_IGNORE;
|
|
260
|
+
if (this[key]) {
|
|
261
|
+
return this[key];
|
|
262
|
+
}
|
|
263
|
+
return this._make(MODE_CHECK_IGNORE, key);
|
|
264
|
+
}
|
|
265
|
+
_make(mode, key) {
|
|
266
|
+
const str = this.regexPrefix.replace(
|
|
267
|
+
REGEX_REPLACE_TRAILING_WILDCARD,
|
|
268
|
+
// It does not need to bind pattern
|
|
269
|
+
TRAILING_WILD_CARD_REPLACERS[mode]
|
|
270
|
+
);
|
|
271
|
+
const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
|
|
272
|
+
return define(this, key, regex);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
var createRule = ({
|
|
276
|
+
pattern,
|
|
277
|
+
mark
|
|
278
|
+
}, ignoreCase) => {
|
|
279
|
+
let negative = false;
|
|
280
|
+
let body = pattern;
|
|
281
|
+
if (body.indexOf("!") === 0) {
|
|
282
|
+
negative = true;
|
|
283
|
+
body = body.substr(1);
|
|
284
|
+
}
|
|
285
|
+
body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
|
|
286
|
+
const regexPrefix = makeRegexPrefix(body);
|
|
287
|
+
return new IgnoreRule(
|
|
288
|
+
pattern,
|
|
289
|
+
mark,
|
|
290
|
+
body,
|
|
291
|
+
ignoreCase,
|
|
292
|
+
negative,
|
|
293
|
+
regexPrefix
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
var RuleManager = class {
|
|
297
|
+
constructor(ignoreCase) {
|
|
298
|
+
this._ignoreCase = ignoreCase;
|
|
299
|
+
this._rules = [];
|
|
300
|
+
}
|
|
301
|
+
_add(pattern) {
|
|
302
|
+
if (pattern && pattern[KEY_IGNORE]) {
|
|
303
|
+
this._rules = this._rules.concat(pattern._rules._rules);
|
|
304
|
+
this._added = true;
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (isString(pattern)) {
|
|
308
|
+
pattern = {
|
|
309
|
+
pattern
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (checkPattern(pattern.pattern)) {
|
|
313
|
+
const rule = createRule(pattern, this._ignoreCase);
|
|
314
|
+
this._added = true;
|
|
315
|
+
this._rules.push(rule);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// @param {Array<string> | string | Ignore} pattern
|
|
319
|
+
add(pattern) {
|
|
320
|
+
this._added = false;
|
|
321
|
+
makeArray(
|
|
322
|
+
isString(pattern) ? splitPattern(pattern) : pattern
|
|
323
|
+
).forEach(this._add, this);
|
|
324
|
+
return this._added;
|
|
325
|
+
}
|
|
326
|
+
// Test one single path without recursively checking parent directories
|
|
327
|
+
//
|
|
328
|
+
// - checkUnignored `boolean` whether should check if the path is unignored,
|
|
329
|
+
// setting `checkUnignored` to `false` could reduce additional
|
|
330
|
+
// path matching.
|
|
331
|
+
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
332
|
+
// @returns {TestResult} true if a file is ignored
|
|
333
|
+
test(path, checkUnignored, mode) {
|
|
334
|
+
let ignored2 = false;
|
|
335
|
+
let unignored = false;
|
|
336
|
+
let matchedRule;
|
|
337
|
+
this._rules.forEach((rule) => {
|
|
338
|
+
const { negative } = rule;
|
|
339
|
+
if (unignored === negative && ignored2 !== unignored || negative && !ignored2 && !unignored && !checkUnignored) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const matched = rule[mode].test(path);
|
|
343
|
+
if (!matched) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
ignored2 = !negative;
|
|
347
|
+
unignored = negative;
|
|
348
|
+
matchedRule = negative ? UNDEFINED : rule;
|
|
349
|
+
});
|
|
350
|
+
const ret = {
|
|
351
|
+
ignored: ignored2,
|
|
352
|
+
unignored
|
|
353
|
+
};
|
|
354
|
+
if (matchedRule) {
|
|
355
|
+
ret.rule = matchedRule;
|
|
356
|
+
}
|
|
357
|
+
return ret;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var throwError = (message, Ctor) => {
|
|
361
|
+
throw new Ctor(message);
|
|
362
|
+
};
|
|
363
|
+
var checkPath = (path, originalPath, doThrow) => {
|
|
364
|
+
if (!isString(path)) {
|
|
365
|
+
return doThrow(
|
|
366
|
+
`path must be a string, but got \`${originalPath}\``,
|
|
367
|
+
TypeError
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
if (!path) {
|
|
371
|
+
return doThrow(`path must not be empty`, TypeError);
|
|
372
|
+
}
|
|
373
|
+
if (checkPath.isNotRelative(path)) {
|
|
374
|
+
const r = "`path.relative()`d";
|
|
375
|
+
return doThrow(
|
|
376
|
+
`path should be a ${r} string, but got "${originalPath}"`,
|
|
377
|
+
RangeError
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
381
|
+
};
|
|
382
|
+
var isNotRelative = (path) => REGEX_TEST_INVALID_PATH.test(path);
|
|
383
|
+
checkPath.isNotRelative = isNotRelative;
|
|
384
|
+
checkPath.convert = (p) => p;
|
|
385
|
+
var Ignore = class {
|
|
386
|
+
constructor({
|
|
387
|
+
ignorecase = true,
|
|
388
|
+
ignoreCase = ignorecase,
|
|
389
|
+
allowRelativePaths = false
|
|
390
|
+
} = {}) {
|
|
391
|
+
define(this, KEY_IGNORE, true);
|
|
392
|
+
this._rules = new RuleManager(ignoreCase);
|
|
393
|
+
this._strictPathCheck = !allowRelativePaths;
|
|
394
|
+
this._initCache();
|
|
395
|
+
}
|
|
396
|
+
_initCache() {
|
|
397
|
+
this._ignoreCache = /* @__PURE__ */ Object.create(null);
|
|
398
|
+
this._testCache = /* @__PURE__ */ Object.create(null);
|
|
399
|
+
}
|
|
400
|
+
add(pattern) {
|
|
401
|
+
if (this._rules.add(pattern)) {
|
|
402
|
+
this._initCache();
|
|
403
|
+
}
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
406
|
+
// legacy
|
|
407
|
+
addPattern(pattern) {
|
|
408
|
+
return this.add(pattern);
|
|
409
|
+
}
|
|
410
|
+
// @returns {TestResult}
|
|
411
|
+
_test(originalPath, cache, checkUnignored, slices) {
|
|
412
|
+
const path = originalPath && checkPath.convert(originalPath);
|
|
413
|
+
checkPath(
|
|
414
|
+
path,
|
|
415
|
+
originalPath,
|
|
416
|
+
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
417
|
+
);
|
|
418
|
+
return this._t(path, cache, checkUnignored, slices);
|
|
419
|
+
}
|
|
420
|
+
checkIgnore(path) {
|
|
421
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path)) {
|
|
422
|
+
return this.test(path);
|
|
423
|
+
}
|
|
424
|
+
const slices = path.split(SLASH).filter(Boolean);
|
|
425
|
+
slices.pop();
|
|
426
|
+
if (slices.length) {
|
|
427
|
+
const parent = this._t(
|
|
428
|
+
slices.join(SLASH) + SLASH,
|
|
429
|
+
this._testCache,
|
|
430
|
+
true,
|
|
431
|
+
slices
|
|
432
|
+
);
|
|
433
|
+
if (parent.ignored) {
|
|
434
|
+
return parent;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
return this._rules.test(path, false, MODE_CHECK_IGNORE);
|
|
438
|
+
}
|
|
439
|
+
_t(path, cache, checkUnignored, slices) {
|
|
440
|
+
if (path in cache) {
|
|
441
|
+
return cache[path];
|
|
442
|
+
}
|
|
443
|
+
if (!slices) {
|
|
444
|
+
slices = path.split(SLASH).filter(Boolean);
|
|
445
|
+
}
|
|
446
|
+
slices.pop();
|
|
447
|
+
if (!slices.length) {
|
|
448
|
+
return cache[path] = this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
449
|
+
}
|
|
450
|
+
const parent = this._t(
|
|
451
|
+
slices.join(SLASH) + SLASH,
|
|
452
|
+
cache,
|
|
453
|
+
checkUnignored,
|
|
454
|
+
slices
|
|
455
|
+
);
|
|
456
|
+
return cache[path] = parent.ignored ? parent : this._rules.test(path, checkUnignored, MODE_IGNORE);
|
|
457
|
+
}
|
|
458
|
+
ignores(path) {
|
|
459
|
+
return this._test(path, this._ignoreCache, false).ignored;
|
|
460
|
+
}
|
|
461
|
+
createFilter() {
|
|
462
|
+
return (path) => !this.ignores(path);
|
|
463
|
+
}
|
|
464
|
+
filter(paths) {
|
|
465
|
+
return makeArray(paths).filter(this.createFilter());
|
|
466
|
+
}
|
|
467
|
+
// @returns {TestResult}
|
|
468
|
+
test(path) {
|
|
469
|
+
return this._test(path, this._testCache, true);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
var factory = (options) => new Ignore(options);
|
|
473
|
+
var isPathValid = (path) => checkPath(path && checkPath.convert(path), path, RETURN_FALSE);
|
|
474
|
+
var setupWindows = () => {
|
|
475
|
+
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
476
|
+
checkPath.convert = makePosix;
|
|
477
|
+
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
478
|
+
checkPath.isNotRelative = (path) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path) || isNotRelative(path);
|
|
479
|
+
};
|
|
480
|
+
if (
|
|
481
|
+
// Detect `process` so that it can run in browsers.
|
|
482
|
+
typeof process !== "undefined" && process.platform === "win32"
|
|
483
|
+
) {
|
|
484
|
+
setupWindows();
|
|
485
|
+
}
|
|
486
|
+
module.exports = factory;
|
|
487
|
+
factory.default = factory;
|
|
488
|
+
module.exports.isPathValid = isPathValid;
|
|
489
|
+
define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// packages/cli/src/symbols/languages.mjs
|
|
494
|
+
import { extname as extname4 } from "node:path";
|
|
495
|
+
function languageForPath(path) {
|
|
496
|
+
return Object.entries(LANGUAGE_EXTENSIONS).find(([, extensions]) => extensions.includes(extname4(path)))?.[0] ?? null;
|
|
497
|
+
}
|
|
498
|
+
var LANGUAGE_EXTENSIONS, SOURCE_EXTENSIONS, EXTRACTOR_ID, DECLARATIONS, COMMENT_TYPES;
|
|
499
|
+
var init_languages = __esm({
|
|
500
|
+
"packages/cli/src/symbols/languages.mjs"() {
|
|
501
|
+
LANGUAGE_EXTENSIONS = {
|
|
502
|
+
javascript: [".js", ".jsx", ".mjs", ".cjs"],
|
|
503
|
+
typescript: [".ts"],
|
|
504
|
+
tsx: [".tsx"],
|
|
505
|
+
python: [".py", ".pyw"],
|
|
506
|
+
go: [".go"],
|
|
507
|
+
rust: [".rs"],
|
|
508
|
+
java: [".java"],
|
|
509
|
+
ruby: [".rb"],
|
|
510
|
+
php: [".php"],
|
|
511
|
+
"c-sharp": [".cs"],
|
|
512
|
+
cpp: [".cpp", ".cc", ".cxx", ".hpp"],
|
|
513
|
+
bash: [".sh", ".bash"],
|
|
514
|
+
powershell: [".ps1"]
|
|
515
|
+
};
|
|
516
|
+
SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
517
|
+
...Object.values(LANGUAGE_EXTENSIONS).flat(),
|
|
518
|
+
".c",
|
|
519
|
+
".h",
|
|
520
|
+
".kt",
|
|
521
|
+
".kts",
|
|
522
|
+
".swift",
|
|
523
|
+
".scala",
|
|
524
|
+
".clj",
|
|
525
|
+
".cljs",
|
|
526
|
+
".ex",
|
|
527
|
+
".exs",
|
|
528
|
+
".erl",
|
|
529
|
+
".hrl",
|
|
530
|
+
".lua",
|
|
531
|
+
".pl",
|
|
532
|
+
".pm",
|
|
533
|
+
".r",
|
|
534
|
+
".R",
|
|
535
|
+
".sql",
|
|
536
|
+
".m",
|
|
537
|
+
".mm",
|
|
538
|
+
".zsh",
|
|
539
|
+
".fish"
|
|
540
|
+
]);
|
|
541
|
+
EXTRACTOR_ID = "outline-v3:vscode-tree-sitter-wasm-0.3.1";
|
|
542
|
+
DECLARATIONS = {
|
|
543
|
+
function_declaration: "function",
|
|
544
|
+
generator_function_declaration: "function",
|
|
545
|
+
function_definition: "function",
|
|
546
|
+
function_item: "function",
|
|
547
|
+
function_signature_item: "function",
|
|
548
|
+
function_signature: "function",
|
|
549
|
+
method_definition: "method",
|
|
550
|
+
method_declaration: "method",
|
|
551
|
+
method_signature: "method",
|
|
552
|
+
method: "method",
|
|
553
|
+
singleton_method: "method",
|
|
554
|
+
constructor_declaration: "constructor",
|
|
555
|
+
destructor_declaration: "destructor",
|
|
556
|
+
class_declaration: "class",
|
|
557
|
+
class_definition: "class",
|
|
558
|
+
class: "class",
|
|
559
|
+
class_specifier: "class",
|
|
560
|
+
struct_specifier: "struct",
|
|
561
|
+
interface_declaration: "interface",
|
|
562
|
+
interface_definition: "interface",
|
|
563
|
+
trait_item: "trait",
|
|
564
|
+
struct_item: "struct",
|
|
565
|
+
enum_item: "enum",
|
|
566
|
+
enum_declaration: "enum",
|
|
567
|
+
record_declaration: "class",
|
|
568
|
+
namespace_definition: "namespace",
|
|
569
|
+
mod_item: "module",
|
|
570
|
+
module: "module",
|
|
571
|
+
function_statement: "function"
|
|
572
|
+
};
|
|
573
|
+
COMMENT_TYPES = /* @__PURE__ */ new Set(["comment", "line_comment", "block_comment"]);
|
|
574
|
+
}
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
// packages/cli/src/symbols/runtime.mjs
|
|
578
|
+
import { createRequire } from "node:module";
|
|
579
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
|
|
580
|
+
import { dirname as dirname5, join as join8 } from "node:path";
|
|
581
|
+
import { fileURLToPath } from "node:url";
|
|
582
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
583
|
+
function parserEntry() {
|
|
584
|
+
const bundled = fileURLToPath(new URL("./parsers/tree-sitter.cjs", import.meta.url));
|
|
585
|
+
return existsSync9(bundled) ? bundled : require2.resolve("@vscode/tree-sitter-wasm");
|
|
586
|
+
}
|
|
587
|
+
function parserIdentity() {
|
|
588
|
+
if (!identity) {
|
|
589
|
+
const entry = parserEntry();
|
|
590
|
+
const hash2 = createHash4("sha256").update(readFileSync9(entry));
|
|
591
|
+
for (const name of ["tree-sitter.wasm", ...Object.keys(LANGUAGE_EXTENSIONS).map((language) => `tree-sitter-${language}.wasm`)]) hash2.update(name).update(readFileSync9(join8(dirname5(entry), name)));
|
|
592
|
+
identity = hash2.digest("hex");
|
|
593
|
+
}
|
|
594
|
+
return identity;
|
|
595
|
+
}
|
|
596
|
+
function loadParserApi() {
|
|
597
|
+
return require2(parserEntry());
|
|
598
|
+
}
|
|
599
|
+
var require2, identity;
|
|
600
|
+
var init_runtime = __esm({
|
|
601
|
+
"packages/cli/src/symbols/runtime.mjs"() {
|
|
602
|
+
init_languages();
|
|
603
|
+
require2 = createRequire(import.meta.url);
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
// packages/cli/src/symbols/parser.mjs
|
|
608
|
+
var parser_exports = {};
|
|
609
|
+
__export(parser_exports, {
|
|
610
|
+
extractSymbols: () => extractSymbols
|
|
611
|
+
});
|
|
612
|
+
import { dirname as dirname6, join as join10 } from "node:path";
|
|
613
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
614
|
+
async function parserFor(language) {
|
|
615
|
+
if (!runtime) runtime = (async () => {
|
|
616
|
+
const entry = parserEntry();
|
|
617
|
+
const api2 = loadParserApi();
|
|
618
|
+
await api2.Parser.init();
|
|
619
|
+
return { api: api2, directory: dirname6(entry) };
|
|
620
|
+
})();
|
|
621
|
+
const { api, directory } = await runtime;
|
|
622
|
+
if (!languages.has(language)) languages.set(language, api.Language.load(join10(directory, `tree-sitter-${language}.wasm`)));
|
|
623
|
+
const loaded = await languages.get(language);
|
|
624
|
+
const parser = new api.Parser();
|
|
625
|
+
parser.setLanguage(loaded);
|
|
626
|
+
return parser;
|
|
627
|
+
}
|
|
628
|
+
function declaredName(node2) {
|
|
629
|
+
const name = field(node2, "name") ?? node2.namedChildren.find((child) => child.type === "function_name");
|
|
630
|
+
if (name) return name.text;
|
|
631
|
+
const declarator = field(node2, "declarator");
|
|
632
|
+
if (declarator) return declaredName(declarator) || (["identifier", "field_identifier"].includes(declarator.type) ? declarator.text : "");
|
|
633
|
+
return "";
|
|
634
|
+
}
|
|
635
|
+
function declarationHeader(node2, source, end) {
|
|
636
|
+
const cuts = [];
|
|
637
|
+
function visit(child) {
|
|
638
|
+
const body = field(child, "body");
|
|
639
|
+
if (body && body.startIndex < end && body.endIndex <= end) {
|
|
640
|
+
cuts.push([body.startIndex, body.endIndex]);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
for (const nested of child.namedChildren) if (nested.startIndex < end) visit(nested);
|
|
644
|
+
}
|
|
645
|
+
visit(node2);
|
|
646
|
+
let header = source.slice(node2.startIndex, end);
|
|
647
|
+
for (const [start, finish] of cuts.sort((a, b) => b[0] - a[0])) header = header.slice(0, start - node2.startIndex) + "\u2026" + header.slice(finish - node2.startIndex);
|
|
648
|
+
return header.trim();
|
|
649
|
+
}
|
|
650
|
+
function documentation(node2, body, language, source) {
|
|
651
|
+
if (language === "python") {
|
|
652
|
+
const first = body?.namedChildren[0];
|
|
653
|
+
const string = first?.type === "expression_statement" ? first.namedChildren[0] : null;
|
|
654
|
+
const literals = string?.type === "concatenated_string" ? string.namedChildren : string ? [string] : [];
|
|
655
|
+
const valid = literals.length && literals.every((literal) => literal.type === "string" && /^[rRuU]*['"]/.test(literal.text));
|
|
656
|
+
return valid ? { text: string.text, startLine: string.startPosition.row + 1 } : { text: "" };
|
|
657
|
+
}
|
|
658
|
+
let anchor = node2;
|
|
659
|
+
while (anchor.parent && (["export_statement", "decorated_definition"].includes(anchor.parent.type) || ["lexical_declaration", "variable_declaration"].includes(anchor.parent.type) && !anchor.previousNamedSibling || language === "powershell" && anchor.parent.type === "statement_list" && !anchor.previousNamedSibling)) anchor = anchor.parent;
|
|
660
|
+
const comments = [];
|
|
661
|
+
let previous = anchor.previousNamedSibling;
|
|
662
|
+
let start = anchor.startIndex;
|
|
663
|
+
while (previous && source.slice(previous.endIndex, start).trim() === "") {
|
|
664
|
+
if (COMMENT_TYPES.has(previous.type)) comments.unshift(previous);
|
|
665
|
+
else if (!(language === "rust" && previous.type === "attribute_item")) break;
|
|
666
|
+
start = previous.startIndex;
|
|
667
|
+
previous = previous.previousNamedSibling;
|
|
668
|
+
}
|
|
669
|
+
const markers = ["javascript", "typescript", "tsx", "java", "php"].includes(language) ? /^\/\*\*/ : language === "rust" ? /^(\/\/\/(?!\/)|\/\*\*)/ : ["cpp", "c-sharp"].includes(language) ? /^(\/\*\*|\/\/\/|\/\/!|\/\*!)/ : null;
|
|
670
|
+
const docs = markers ? comments.filter((comment) => markers.test(comment.text)) : comments;
|
|
671
|
+
return { text: docs.map((comment) => comment.text).join("\n"), startLine: docs[0] ? docs[0].startPosition.row + 1 : void 0 };
|
|
672
|
+
}
|
|
673
|
+
async function extractSymbols(path, source, language) {
|
|
674
|
+
const parser = await parserFor(language);
|
|
675
|
+
let tree;
|
|
676
|
+
try {
|
|
677
|
+
let walk = function(node2, owner = null) {
|
|
678
|
+
let kind = DECLARATIONS[node2.type];
|
|
679
|
+
let declaration = node2;
|
|
680
|
+
let body = field(node2, "body") ?? (language === "powershell" ? node2.namedChildren.find((child) => child.type === "script_block") : null);
|
|
681
|
+
if (["variable_declarator", "public_field_definition"].includes(node2.type)) {
|
|
682
|
+
const value = field(node2, "value");
|
|
683
|
+
if (value && ["arrow_function", "function_expression", "generator_function"].includes(value.type)) {
|
|
684
|
+
kind = "function";
|
|
685
|
+
body = field(value, "body");
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (node2.type === "type_spec" && ["struct_type", "interface_type"].includes(field(node2, "type")?.type)) {
|
|
689
|
+
kind = "class";
|
|
690
|
+
body = field(node2, "type").namedChildren.find((child) => child.type.endsWith("_list"));
|
|
691
|
+
}
|
|
692
|
+
if (node2.type === "impl_item") {
|
|
693
|
+
const type = field(node2, "type");
|
|
694
|
+
if (type) owner = symbols.find((symbol) => symbol.qualifiedName === type.text && ["struct", "class"].includes(symbol.kind)) ?? { qualifiedName: type.text, id: null, kind: "struct" };
|
|
695
|
+
}
|
|
696
|
+
if (kind === "function" && owner && ["class", "struct", "interface", "trait"].includes(owner.kind)) kind = "method";
|
|
697
|
+
const name = kind ? declaredName(declaration) : "";
|
|
698
|
+
if (kind && name) {
|
|
699
|
+
const qualifiedName = owner ? `${owner.qualifiedName}.${name}` : name;
|
|
700
|
+
const signature = declarationHeader(declaration, source, body?.startIndex ?? declaration.endIndex);
|
|
701
|
+
const key = `${path}#${qualifiedName}:${kind}:${hash(signature)}`;
|
|
702
|
+
const occurrence = occurrences.get(key) ?? 0;
|
|
703
|
+
occurrences.set(key, occurrence + 1);
|
|
704
|
+
const doc = documentation(declaration, body, language, source);
|
|
705
|
+
const symbol = {
|
|
706
|
+
id: `symbol:${key}:${occurrence}`,
|
|
707
|
+
kind,
|
|
708
|
+
name,
|
|
709
|
+
qualifiedName,
|
|
710
|
+
ownerId: owner?.id ?? null,
|
|
711
|
+
language,
|
|
712
|
+
path,
|
|
713
|
+
signature,
|
|
714
|
+
documentation: doc.text,
|
|
715
|
+
startLine: Math.min(doc.startLine ?? Infinity, declaration.startPosition.row + 1),
|
|
716
|
+
declarationStartLine: declaration.startPosition.row + 1,
|
|
717
|
+
endLine: lastLine(declaration),
|
|
718
|
+
bodyStartLine: body ? body.startPosition.row + 1 : null,
|
|
719
|
+
bodyEndLine: body ? lastLine(body) : null,
|
|
720
|
+
declarationStartOffset: declaration.startIndex,
|
|
721
|
+
declarationEndOffset: declaration.endIndex,
|
|
722
|
+
bodyStartOffset: body?.startIndex ?? null,
|
|
723
|
+
bodyEndOffset: body?.endIndex ?? null
|
|
724
|
+
};
|
|
725
|
+
symbols.push(symbol);
|
|
726
|
+
owner = symbol;
|
|
727
|
+
}
|
|
728
|
+
if (["import_statement", "export_statement", "import_from_statement", "use_declaration", "include_directive", "preproc_include"].includes(node2.type)) {
|
|
729
|
+
const target = field(node2, "source") ?? field(node2, "module_name") ?? field(node2, "path") ?? field(node2, "argument");
|
|
730
|
+
if (target) imports.push({ specifier: target.text.replace(/^['"]|['"]$/g, ""), line: node2.startPosition.row + 1, syntax: node2.type });
|
|
731
|
+
}
|
|
732
|
+
for (const child of node2.namedChildren) walk(child, owner);
|
|
733
|
+
};
|
|
734
|
+
tree = parser.parse(source);
|
|
735
|
+
if (!tree) throw new Error("Parser returned no syntax tree");
|
|
736
|
+
const symbols = [];
|
|
737
|
+
const imports = [];
|
|
738
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
739
|
+
walk(tree.rootNode);
|
|
740
|
+
return { symbols, imports, status: tree.rootNode.hasError ? "partial" : "parsed" };
|
|
741
|
+
} finally {
|
|
742
|
+
tree?.delete();
|
|
743
|
+
parser.delete();
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
var runtime, languages, field, lastLine, hash;
|
|
747
|
+
var init_parser = __esm({
|
|
748
|
+
"packages/cli/src/symbols/parser.mjs"() {
|
|
749
|
+
init_runtime();
|
|
750
|
+
init_languages();
|
|
751
|
+
languages = /* @__PURE__ */ new Map();
|
|
752
|
+
field = (node2, name) => node2.childForFieldName(name);
|
|
753
|
+
lastLine = (node2) => Math.max(node2.startPosition.row + 1, node2.endPosition.row + (node2.endPosition.column ? 1 : 0));
|
|
754
|
+
hash = (text) => createHash5("sha256").update(text).digest("hex").slice(0, 16);
|
|
755
|
+
}
|
|
756
|
+
});
|
|
3
757
|
|
|
4
758
|
// packages/cli/src/init.mjs
|
|
5
759
|
import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync4, statSync, writeFileSync } from "node:fs";
|
|
@@ -136,8 +890,8 @@ function sanitizeEmbeddingProfile(profile) {
|
|
|
136
890
|
return { ...safe, ...apiKey ? { apiKey: "[redacted]" } : {} };
|
|
137
891
|
}
|
|
138
892
|
function embeddingProfileIdentity(profile) {
|
|
139
|
-
const { apiKey, apiKeyEnv, ...
|
|
140
|
-
return
|
|
893
|
+
const { apiKey, apiKeyEnv, ...identity2 } = profile;
|
|
894
|
+
return identity2;
|
|
141
895
|
}
|
|
142
896
|
function resolveEmbeddingProfile(root = ".", options = {}) {
|
|
143
897
|
const home = options.home ?? homedir();
|
|
@@ -334,10 +1088,10 @@ function cloneReferenceExpansionPolicy(policy = {}) {
|
|
|
334
1088
|
const defaults = uniqueNormalizedTerms(lowSignal.defaults ?? DEFAULT_FUZZY_LOW_SIGNAL_TERMS);
|
|
335
1089
|
const add = uniqueNormalizedTerms(lowSignal.add ?? []);
|
|
336
1090
|
const remove = uniqueNormalizedTerms(lowSignal.remove ?? []);
|
|
337
|
-
const
|
|
338
|
-
for (const term of remove)
|
|
339
|
-
for (const term of add)
|
|
340
|
-
return { fuzzy: { lowSignal: { defaults, add, remove, terms: [...
|
|
1091
|
+
const terms2 = new Set(defaults);
|
|
1092
|
+
for (const term of remove) terms2.delete(term);
|
|
1093
|
+
for (const term of add) terms2.add(term);
|
|
1094
|
+
return { fuzzy: { lowSignal: { defaults, add, remove, terms: [...terms2].sort() } } };
|
|
341
1095
|
}
|
|
342
1096
|
function normalizeReferenceExpansionPolicy(raw) {
|
|
343
1097
|
const lowSignal = raw?.fuzzy?.lowSignal ?? {};
|
|
@@ -481,11 +1235,11 @@ function isFiniteNumberInRange(value, min, max) {
|
|
|
481
1235
|
}
|
|
482
1236
|
function validateMemoryConfigData(data, root = ".", file = "dotdotgod.config.json") {
|
|
483
1237
|
const errors = [];
|
|
484
|
-
const add = (code,
|
|
1238
|
+
const add = (code, field2, message, fix = null) => errors.push({
|
|
485
1239
|
file: rel(root, resolve(root, file)),
|
|
486
1240
|
code,
|
|
487
|
-
message: `${
|
|
488
|
-
Fix: ${fix ?? `update ${
|
|
1241
|
+
message: `${field2 ? `Field "${field2}": ` : ""}${message}
|
|
1242
|
+
Fix: ${fix ?? `update ${field2 ?? "this config"} in ${file} to match the expected dotdotgod config schema.`}`
|
|
489
1243
|
});
|
|
490
1244
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
491
1245
|
add("MEMORY_CONFIG_INVALID", null, "Config must be a JSON object.");
|
|
@@ -1306,9 +2060,9 @@ Create dotdotgod.config.json from a bundled or user template. Without --template
|
|
|
1306
2060
|
dotdotgod status <root> [--json]`;
|
|
1307
2061
|
case "query":
|
|
1308
2062
|
return `Usage:
|
|
1309
|
-
dotdotgod query <root> <query> [--limit n] [--json]
|
|
2063
|
+
dotdotgod query <root> <query> [--limit n] [--scope docs|code|all] [--search hybrid|keyword|vector] [--allow-code-embedding] [--json]
|
|
1310
2064
|
|
|
1311
|
-
Search
|
|
2065
|
+
Search full documentation and code outlines/comments. Default: hybrid docs+code. Remote code metadata requires explicit consent.`;
|
|
1312
2066
|
case "embedding":
|
|
1313
2067
|
return `Usage:
|
|
1314
2068
|
dotdotgod embedding status [<root>] [--json]
|
|
@@ -1440,8 +2194,8 @@ function parseCommon(argv) {
|
|
|
1440
2194
|
}
|
|
1441
2195
|
|
|
1442
2196
|
// packages/cli/src/validate/run.mjs
|
|
1443
|
-
import { existsSync as
|
|
1444
|
-
import { basename as basename7, dirname as
|
|
2197
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync3 } from "node:fs";
|
|
2198
|
+
import { basename as basename7, dirname as dirname10, extname as extname6, join as join13, resolve as resolve8 } from "node:path";
|
|
1445
2199
|
|
|
1446
2200
|
// packages/cli/src/docs/traceability.mjs
|
|
1447
2201
|
import { existsSync as existsSync5 } from "node:fs";
|
|
@@ -1496,8 +2250,8 @@ function isLocalRelativeTraceabilityPath(value) {
|
|
|
1496
2250
|
if (value.split("/").includes("..")) return false;
|
|
1497
2251
|
return !isSecretIndexPath(value);
|
|
1498
2252
|
}
|
|
1499
|
-
function traceabilityFieldError(file, code,
|
|
1500
|
-
return { file, line, code, message: `${
|
|
2253
|
+
function traceabilityFieldError(file, code, field2, message, line = null, config = defaultMemoryConfig()) {
|
|
2254
|
+
return { file, line, code, message: `${field2 ? `Field "${field2}": ` : ""}${message}
|
|
1501
2255
|
Fix: update the traceability block so it matches the expected schema and points to existing project files or commands.
|
|
1502
2256
|
|
|
1503
2257
|
${traceabilityExample(config)}` };
|
|
@@ -1629,13 +2383,13 @@ ${generated}
|
|
|
1629
2383
|
${suffix}`;
|
|
1630
2384
|
return { ok: true, changed: next !== content, content: next };
|
|
1631
2385
|
}
|
|
1632
|
-
function validateTraceabilityPathArray(data,
|
|
1633
|
-
if (data[
|
|
1634
|
-
if (!Array.isArray(data[
|
|
2386
|
+
function validateTraceabilityPathArray(data, field2, add, root, config, { required = true, displayField = field2 } = {}) {
|
|
2387
|
+
if (data[field2] === void 0 && !required) return;
|
|
2388
|
+
if (!Array.isArray(data[field2])) {
|
|
1635
2389
|
add("TRACEABILITY_INVALID_FIELD", displayField, "must be an array of local relative paths.");
|
|
1636
2390
|
return;
|
|
1637
2391
|
}
|
|
1638
|
-
for (const value of data[
|
|
2392
|
+
for (const value of data[field2]) {
|
|
1639
2393
|
if (!isLocalRelativeTraceabilityPath(value)) {
|
|
1640
2394
|
add("TRACEABILITY_INVALID_PATH", displayField, `invalid local relative path: ${JSON.stringify(value)}.`);
|
|
1641
2395
|
continue;
|
|
@@ -1648,13 +2402,13 @@ function validateTraceabilityPathArray(data, field, add, root, config, { require
|
|
|
1648
2402
|
if (!existsSync5(resolve4(root, value))) add("TRACEABILITY_MISSING_TARGET", displayField, `target does not exist: ${value}.`);
|
|
1649
2403
|
}
|
|
1650
2404
|
}
|
|
1651
|
-
function validateTraceabilityCommandArray(data,
|
|
1652
|
-
if (data[
|
|
1653
|
-
if (!Array.isArray(data[
|
|
2405
|
+
function validateTraceabilityCommandArray(data, field2, add, { required = true, displayField = field2 } = {}) {
|
|
2406
|
+
if (data[field2] === void 0 && !required) return;
|
|
2407
|
+
if (!Array.isArray(data[field2])) {
|
|
1654
2408
|
add("TRACEABILITY_INVALID_FIELD", displayField, "must be an array of executable project-local command strings.");
|
|
1655
2409
|
return;
|
|
1656
2410
|
}
|
|
1657
|
-
for (const value of data[
|
|
2411
|
+
for (const value of data[field2]) if (typeof value !== "string" || value.trim().length === 0) add("TRACEABILITY_INVALID_COMMAND", displayField, `invalid command: ${JSON.stringify(value)}.`);
|
|
1658
2412
|
}
|
|
1659
2413
|
function validateConfiguredArray(data, definition, add, root, config, options) {
|
|
1660
2414
|
if (definition.target === "command") validateTraceabilityCommandArray(data, definition.key, add, options);
|
|
@@ -1675,8 +2429,8 @@ function validateTraceabilityContracts(data, root, add, config) {
|
|
|
1675
2429
|
add("TRACEABILITY_INVALID_FIELD", base, "must be an object with non-empty id and title fields.");
|
|
1676
2430
|
continue;
|
|
1677
2431
|
}
|
|
1678
|
-
for (const
|
|
1679
|
-
if (!allowedFields.has(
|
|
2432
|
+
for (const field2 of Object.keys(contract)) {
|
|
2433
|
+
if (!allowedFields.has(field2)) add("TRACEABILITY_INVALID_FIELD", `${base}.${field2}`, "is not a supported contract field.");
|
|
1680
2434
|
}
|
|
1681
2435
|
if (typeof contract.id !== "string" || contract.id.trim().length === 0) {
|
|
1682
2436
|
add("TRACEABILITY_INVALID_FIELD", `${base}.id`, "must be a non-empty stable contract ID string.");
|
|
@@ -1697,7 +2451,7 @@ function validateTraceabilityContracts(data, root, add, config) {
|
|
|
1697
2451
|
}
|
|
1698
2452
|
function validateTraceabilityBlock(data, root, file, line = null, config = defaultMemoryConfig()) {
|
|
1699
2453
|
const errors = [];
|
|
1700
|
-
const add = (code,
|
|
2454
|
+
const add = (code, field2, message) => errors.push(traceabilityFieldError(rel(root, file), code, field2, message, line, config));
|
|
1701
2455
|
if (!data || typeof data !== "object" || Array.isArray(data)) {
|
|
1702
2456
|
add("TRACEABILITY_INVALID_JSON", null, "Traceability block must be a JSON object.");
|
|
1703
2457
|
return errors;
|
|
@@ -1705,7 +2459,7 @@ function validateTraceabilityBlock(data, root, file, line = null, config = defau
|
|
|
1705
2459
|
if (data.kind !== "spec") add("TRACEABILITY_INVALID_KIND", "kind", 'must be "spec" for behavior specs.');
|
|
1706
2460
|
const definitions2 = traceabilityKeys(config);
|
|
1707
2461
|
const allowedFields = /* @__PURE__ */ new Set(["kind", "contracts", ...definitions2.map((definition) => definition.key)]);
|
|
1708
|
-
for (const
|
|
2462
|
+
for (const field2 of Object.keys(data)) if (!allowedFields.has(field2)) add("TRACEABILITY_INVALID_FIELD", field2, "is not configured as a traceability field.");
|
|
1709
2463
|
for (const definition of definitions2) validateConfiguredArray(data, definition, add, root, config, { required: true, displayField: definition.key });
|
|
1710
2464
|
validateTraceabilityContracts(data, root, add, config);
|
|
1711
2465
|
return errors;
|
|
@@ -1932,8 +2686,8 @@ function cacheFile(root) {
|
|
|
1932
2686
|
}
|
|
1933
2687
|
|
|
1934
2688
|
// packages/cli/src/index/cache.mjs
|
|
1935
|
-
import { existsSync as
|
|
1936
|
-
import { basename as basename6, join as
|
|
2689
|
+
import { existsSync as existsSync12, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
|
|
2690
|
+
import { basename as basename6, join as join12 } from "node:path";
|
|
1937
2691
|
|
|
1938
2692
|
// packages/cli/src/graph/store.mjs
|
|
1939
2693
|
import { existsSync as existsSync7, mkdirSync as mkdirSync2, statSync as statSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
@@ -2231,6 +2985,290 @@ function buildGraph(root, files, config = defaultMemoryConfig()) {
|
|
|
2231
2985
|
return graph;
|
|
2232
2986
|
}
|
|
2233
2987
|
|
|
2988
|
+
// packages/cli/src/symbols/store.mjs
|
|
2989
|
+
import { existsSync as existsSync11, lstatSync as lstatSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync11, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2990
|
+
import { randomUUID } from "node:crypto";
|
|
2991
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
2992
|
+
|
|
2993
|
+
// packages/cli/src/symbols/discovery.mjs
|
|
2994
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
2995
|
+
import { existsSync as existsSync10, lstatSync, readFileSync as readFileSync10, realpathSync } from "node:fs";
|
|
2996
|
+
import { extname as extname5, join as join9, relative as relative4, isAbsolute } from "node:path";
|
|
2997
|
+
|
|
2998
|
+
// packages/cli/src/query/chunks.mjs
|
|
2999
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync2 } from "node:fs";
|
|
3000
|
+
import { join as join7, relative as relative3 } from "node:path";
|
|
3001
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
3002
|
+
var MAX_CHUNK_CHARS = 1600;
|
|
3003
|
+
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
3004
|
+
function textFingerprint(text) {
|
|
3005
|
+
return createHash3("sha256").update(text).digest("hex");
|
|
3006
|
+
}
|
|
3007
|
+
function collectDocumentationMarkdown(root, exclude = ["docs/plan", "docs/archive"], documentationRoot = "docs") {
|
|
3008
|
+
const docsRoot = join7(root, documentationRoot);
|
|
3009
|
+
const files = [];
|
|
3010
|
+
const walk = (directory) => {
|
|
3011
|
+
let entries;
|
|
3012
|
+
try {
|
|
3013
|
+
entries = readdirSync2(directory, { withFileTypes: true, encoding: "utf8" });
|
|
3014
|
+
} catch {
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
3018
|
+
for (const entry of entries) {
|
|
3019
|
+
if (entry.name.startsWith(".")) continue;
|
|
3020
|
+
const absolute = join7(directory, entry.name);
|
|
3021
|
+
const path = relative3(root, absolute).replaceAll("\\", "/");
|
|
3022
|
+
if (isSecretLikePathPattern(path) || exclude.some((pattern) => matchPathPattern(path, pattern) || path === pattern || path.startsWith(`${pattern}/`))) continue;
|
|
3023
|
+
if (entry.isDirectory()) {
|
|
3024
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name)) walk(absolute);
|
|
3025
|
+
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) files.push(path);
|
|
3026
|
+
}
|
|
3027
|
+
};
|
|
3028
|
+
walk(docsRoot);
|
|
3029
|
+
return files.sort();
|
|
3030
|
+
}
|
|
3031
|
+
function splitSection(text, maxChars = MAX_CHUNK_CHARS) {
|
|
3032
|
+
if (text.length <= maxChars) return [text];
|
|
3033
|
+
const paragraphs = text.split(/\n\s*\n/);
|
|
3034
|
+
const chunks = [];
|
|
3035
|
+
let current = "";
|
|
3036
|
+
for (const paragraph of paragraphs) {
|
|
3037
|
+
if (current && current.length + paragraph.length + 2 > maxChars) {
|
|
3038
|
+
chunks.push(current.trim());
|
|
3039
|
+
current = "";
|
|
3040
|
+
}
|
|
3041
|
+
if (paragraph.length > maxChars) {
|
|
3042
|
+
for (let offset = 0; offset < paragraph.length; offset += maxChars) chunks.push(paragraph.slice(offset, offset + maxChars).trim());
|
|
3043
|
+
} else current += `${current ? "\n\n" : ""}${paragraph}`;
|
|
3044
|
+
}
|
|
3045
|
+
if (current.trim()) chunks.push(current.trim());
|
|
3046
|
+
return chunks.filter(Boolean);
|
|
3047
|
+
}
|
|
3048
|
+
function chunkMarkdown(path, content, maxChars = MAX_CHUNK_CHARS) {
|
|
3049
|
+
const lines = content.split("\n");
|
|
3050
|
+
const sections = [];
|
|
3051
|
+
let headings = [];
|
|
3052
|
+
let body = [];
|
|
3053
|
+
const flush = () => {
|
|
3054
|
+
const text = body.join("\n").trim();
|
|
3055
|
+
if (!text && headings.length === 0) return;
|
|
3056
|
+
const heading = headings.length > 0 ? headings.join(" > ") : path;
|
|
3057
|
+
const pieces = splitSection(text || heading, maxChars);
|
|
3058
|
+
pieces.forEach((piece, index) => {
|
|
3059
|
+
const passage = `Path: ${path}
|
|
3060
|
+
Section: ${heading}
|
|
3061
|
+
|
|
3062
|
+
${piece}`;
|
|
3063
|
+
const fingerprint2 = textFingerprint(passage);
|
|
3064
|
+
sections.push({ id: `${path}#${fingerprint2.slice(0, 16)}`, path, heading, part: index + 1, text: piece, passage, fingerprint: fingerprint2 });
|
|
3065
|
+
});
|
|
3066
|
+
};
|
|
3067
|
+
for (const line of lines) {
|
|
3068
|
+
const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
3069
|
+
if (!match) {
|
|
3070
|
+
body.push(line);
|
|
3071
|
+
continue;
|
|
3072
|
+
}
|
|
3073
|
+
flush();
|
|
3074
|
+
body = [];
|
|
3075
|
+
const level = match[1].length;
|
|
3076
|
+
headings = [...headings.slice(0, level - 1), match[2]];
|
|
3077
|
+
}
|
|
3078
|
+
flush();
|
|
3079
|
+
return sections;
|
|
3080
|
+
}
|
|
3081
|
+
function collectDocumentationChunks(root, exclude, documentationRoot = "docs") {
|
|
3082
|
+
return collectDocumentationMarkdown(root, exclude, documentationRoot).flatMap((path) => {
|
|
3083
|
+
const content = readFileSync8(join7(root, path), "utf8");
|
|
3084
|
+
return chunkMarkdown(path, content);
|
|
3085
|
+
});
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
// packages/cli/src/symbols/discovery.mjs
|
|
3089
|
+
init_languages();
|
|
3090
|
+
init_runtime();
|
|
3091
|
+
function inside(root, file) {
|
|
3092
|
+
if (!lstatSync(file).isFile()) return false;
|
|
3093
|
+
const path = relative4(realpathSync(root), realpathSync(file));
|
|
3094
|
+
return !path.startsWith("..") && !isAbsolute(path);
|
|
3095
|
+
}
|
|
3096
|
+
function ignoreRules(root, directory, cache) {
|
|
3097
|
+
if (!cache.has(directory)) {
|
|
3098
|
+
const file = join9(root, directory, ".gitignore");
|
|
3099
|
+
cache.set(directory, (0, import_ignore.default)().add(existsSync10(file) && inside(root, file) ? readFileSync10(file, "utf8") : ""));
|
|
3100
|
+
}
|
|
3101
|
+
return cache.get(directory);
|
|
3102
|
+
}
|
|
3103
|
+
function ignored(root, path, cache) {
|
|
3104
|
+
const parts = path.split("/");
|
|
3105
|
+
let excluded = false;
|
|
3106
|
+
for (let depth = 0; depth < parts.length; depth += 1) {
|
|
3107
|
+
const directory = parts.slice(0, depth).join("/");
|
|
3108
|
+
const remaining = parts.slice(depth).join("/");
|
|
3109
|
+
const rules = ignoreRules(root, directory, cache);
|
|
3110
|
+
for (let end = 1; end < parts.length - depth; end += 1) {
|
|
3111
|
+
if (rules.ignores(parts.slice(depth, depth + end).join("/") + "/")) return true;
|
|
3112
|
+
}
|
|
3113
|
+
const result = rules.test(remaining);
|
|
3114
|
+
if (result.ignored) excluded = true;
|
|
3115
|
+
else if (result.unignored) excluded = false;
|
|
3116
|
+
}
|
|
3117
|
+
return excluded;
|
|
3118
|
+
}
|
|
3119
|
+
function sourceFiles(root) {
|
|
3120
|
+
const cache = /* @__PURE__ */ new Map();
|
|
3121
|
+
return collectIndexFiles(root).filter((file) => {
|
|
3122
|
+
const path = relative4(root, file).replaceAll("\\", "/");
|
|
3123
|
+
if (!SOURCE_EXTENSIONS.has(extname5(path)) || path.split("/").some((part) => part.startsWith(".")) || isSecretLikePathPattern(path)) return false;
|
|
3124
|
+
if (!inside(root, file) || ignored(root, path, cache)) return false;
|
|
3125
|
+
return !/^#![^\n]*\n\/\/ Generated|^\/\/ Generated|@generated/m.test(readFileSync10(file, "utf8").slice(0, 256));
|
|
3126
|
+
}).map((file) => {
|
|
3127
|
+
const source = readFileSync10(file, "utf8");
|
|
3128
|
+
return { path: relative4(root, file).replaceAll("\\", "/"), hash: fingerprint(file), language: languageForPath(file), sourceLength: source.length, lineCount: source.split("\n").length };
|
|
3129
|
+
});
|
|
3130
|
+
}
|
|
3131
|
+
function outlinePolicy(root) {
|
|
3132
|
+
const config = readMemoryConfig(root);
|
|
3133
|
+
const ignores = collectIndexFiles(root).filter((file) => file.endsWith("/.gitignore") && inside(root, file)).map((file) => [relative4(root, file), fingerprint(file)]);
|
|
3134
|
+
return textFingerprint(JSON.stringify({ areas: config.areas, ignores, extractor: EXTRACTOR_ID, declarations: DECLARATIONS, extensions: LANGUAGE_EXTENSIONS, parser: parserIdentity() }));
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
// packages/cli/src/symbols/store.mjs
|
|
3138
|
+
init_languages();
|
|
3139
|
+
var OUTLINE_SCHEMA = 1;
|
|
3140
|
+
var outlinePath = (root) => join11(root, ".dotdotgod", "outlines.json");
|
|
3141
|
+
function assertCachePath(root) {
|
|
3142
|
+
for (const path of [dirname7(outlinePath(root)), outlinePath(root)]) {
|
|
3143
|
+
let stat;
|
|
3144
|
+
try {
|
|
3145
|
+
stat = lstatSync2(path);
|
|
3146
|
+
} catch (error) {
|
|
3147
|
+
if (error.code !== "ENOENT") throw error;
|
|
3148
|
+
}
|
|
3149
|
+
if (stat?.isSymbolicLink()) throw new Error(`Outline cache must not use symlinks: ${path}`);
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
function readOutlines(root) {
|
|
3153
|
+
try {
|
|
3154
|
+
assertCachePath(root);
|
|
3155
|
+
const index = JSON.parse(readFileSync11(outlinePath(root), "utf8"));
|
|
3156
|
+
if (index.schema !== OUTLINE_SCHEMA || index.extractor !== EXTRACTOR_ID || !Array.isArray(index.files)) return null;
|
|
3157
|
+
const paths = /* @__PURE__ */ new Set();
|
|
3158
|
+
const ids = /* @__PURE__ */ new Set();
|
|
3159
|
+
for (const file of index.files) {
|
|
3160
|
+
if (typeof file.path !== "string" || file.path.startsWith("/") || file.path.split("/").includes("..") || paths.has(file.path) || !/^[a-f0-9]{64}$/.test(file.hash)) return null;
|
|
3161
|
+
if (!Number.isInteger(file.sourceLength) || file.sourceLength < 0 || !Number.isInteger(file.lineCount) || file.lineCount < 1) return null;
|
|
3162
|
+
if (file.language !== languageForPath(file.path) || file.language === null !== (file.status === "unsupported")) return null;
|
|
3163
|
+
paths.add(file.path);
|
|
3164
|
+
if (!["parsed", "partial", "unsupported", "failed"].includes(file.status) || !Array.isArray(file.symbols) || !Array.isArray(file.imports)) return null;
|
|
3165
|
+
if (file.imports.some((item) => typeof item.specifier !== "string" || !Number.isInteger(item.line) || item.line < 1)) return null;
|
|
3166
|
+
for (const symbol of file.symbols) {
|
|
3167
|
+
if (symbol.path !== file.path || typeof symbol.id !== "string" || !symbol.id.startsWith(`symbol:${file.path}#`) || ids.has(symbol.id) || typeof symbol.signature !== "string" || typeof symbol.documentation !== "string") return null;
|
|
3168
|
+
if (!Number.isInteger(symbol.startLine) || !Number.isInteger(symbol.endLine) || symbol.startLine < 1 || symbol.endLine < symbol.startLine || symbol.endLine > file.lineCount) return null;
|
|
3169
|
+
if (typeof symbol.name !== "string" || typeof symbol.qualifiedName !== "string" || !Number.isInteger(symbol.declarationStartLine) || symbol.declarationStartLine < symbol.startLine || symbol.declarationStartLine > symbol.endLine) return null;
|
|
3170
|
+
if (symbol.bodyStartLine === null !== (symbol.bodyEndLine === null)) return null;
|
|
3171
|
+
if (symbol.bodyStartLine !== null && (!Number.isInteger(symbol.bodyStartLine) || !Number.isInteger(symbol.bodyEndLine) || symbol.bodyStartLine < symbol.declarationStartLine || symbol.bodyEndLine > symbol.endLine || symbol.bodyEndLine < symbol.bodyStartLine)) return null;
|
|
3172
|
+
if (!Number.isInteger(symbol.declarationStartOffset) || !Number.isInteger(symbol.declarationEndOffset) || symbol.declarationStartOffset < 0 || symbol.declarationEndOffset < symbol.declarationStartOffset || symbol.declarationEndOffset > file.sourceLength) return null;
|
|
3173
|
+
if (symbol.bodyStartOffset === null !== (symbol.bodyEndOffset === null)) return null;
|
|
3174
|
+
if (symbol.bodyStartOffset !== null && (!Number.isInteger(symbol.bodyStartOffset) || !Number.isInteger(symbol.bodyEndOffset) || symbol.bodyStartOffset < symbol.declarationStartOffset || symbol.bodyEndOffset > symbol.declarationEndOffset || symbol.bodyEndOffset < symbol.bodyStartOffset)) return null;
|
|
3175
|
+
ids.add(symbol.id);
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
for (const file of index.files) for (const symbol of file.symbols) if (symbol.ownerId !== null && !ids.has(symbol.ownerId)) return null;
|
|
3179
|
+
return index;
|
|
3180
|
+
} catch {
|
|
3181
|
+
return null;
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
function outlineStatus(root) {
|
|
3185
|
+
assertCachePath(root);
|
|
3186
|
+
const files = sourceFiles(root);
|
|
3187
|
+
const index = readOutlines(root);
|
|
3188
|
+
if (!index) return { ok: files.length === 0 && !existsSync11(outlinePath(root)), reason: existsSync11(outlinePath(root)) ? "missing-or-invalid" : "missing", paths: files.map((file) => file.path) };
|
|
3189
|
+
const old = new Map(index.files.map((file) => [file.path, file]));
|
|
3190
|
+
const current = new Set(files.map((file) => file.path));
|
|
3191
|
+
const changed = files.filter((file) => old.get(file.path)?.hash !== file.hash || old.get(file.path)?.sourceLength !== file.sourceLength || old.get(file.path)?.lineCount !== file.lineCount).map((file) => file.path);
|
|
3192
|
+
const removed = [...old.keys()].filter((path) => !current.has(path));
|
|
3193
|
+
const failures = index.files.filter((file) => ["failed", "partial"].includes(file.status)).map((file) => file.path);
|
|
3194
|
+
const policyOk = index.policy === outlinePolicy(root);
|
|
3195
|
+
return { ok: policyOk && !changed.length && !removed.length && !failures.length, reason: !policyOk ? "policy-changed" : failures.length ? "extraction-incomplete" : changed.length || removed.length ? "source-changed" : "fresh", paths: [.../* @__PURE__ */ new Set([...changed, ...removed, ...failures])], unsupported: index.files.filter((file) => file.status === "unsupported").map((file) => file.path) };
|
|
3196
|
+
}
|
|
3197
|
+
async function buildOutlines(root) {
|
|
3198
|
+
assertCachePath(root);
|
|
3199
|
+
const files = sourceFiles(root);
|
|
3200
|
+
const old = readOutlines(root);
|
|
3201
|
+
const previous = new Map(old?.policy === outlinePolicy(root) ? old.files.map((file) => [file.path, file]) : []);
|
|
3202
|
+
let parsed = 0;
|
|
3203
|
+
let reused = 0;
|
|
3204
|
+
const records = [];
|
|
3205
|
+
for (const file of files) {
|
|
3206
|
+
const cached = previous.get(file.path);
|
|
3207
|
+
if (cached?.hash === file.hash && ["parsed", "unsupported"].includes(cached.status)) {
|
|
3208
|
+
records.push(cached);
|
|
3209
|
+
reused += 1;
|
|
3210
|
+
continue;
|
|
3211
|
+
}
|
|
3212
|
+
if (!file.language) {
|
|
3213
|
+
records.push({ ...file, status: "unsupported", symbols: [], imports: [] });
|
|
3214
|
+
continue;
|
|
3215
|
+
}
|
|
3216
|
+
const { extractSymbols: extractSymbols2 } = await Promise.resolve().then(() => (init_parser(), parser_exports));
|
|
3217
|
+
const source = readFileSync11(join11(root, file.path), "utf8");
|
|
3218
|
+
const extracted = await extractSymbols2(file.path, source, file.language);
|
|
3219
|
+
if (fingerprint(join11(root, file.path)) !== file.hash) throw new Error(`Source changed during outline extraction: ${file.path}`);
|
|
3220
|
+
records.push({ ...file, ...extracted });
|
|
3221
|
+
parsed += 1;
|
|
3222
|
+
}
|
|
3223
|
+
const index = { schema: OUTLINE_SCHEMA, extractor: EXTRACTOR_ID, policy: outlinePolicy(root), files: records, refresh: { parsed, reused } };
|
|
3224
|
+
const target = outlinePath(root);
|
|
3225
|
+
mkdirSync3(dirname7(target), { recursive: true });
|
|
3226
|
+
const temporary = `${target}.tmp-${process.pid}-${randomUUID()}`;
|
|
3227
|
+
writeFileSync3(temporary, JSON.stringify(index), { flag: "wx" });
|
|
3228
|
+
renameSync(temporary, target);
|
|
3229
|
+
return index;
|
|
3230
|
+
}
|
|
3231
|
+
function outlineChunks(index) {
|
|
3232
|
+
return index.files.flatMap((file) => file.symbols.map((symbol) => {
|
|
3233
|
+
const text = `${symbol.signature}
|
|
3234
|
+
${symbol.documentation}`.trim();
|
|
3235
|
+
const passage = `Path: ${file.path}
|
|
3236
|
+
Symbol: ${symbol.qualifiedName}
|
|
3237
|
+
Kind: ${symbol.kind}
|
|
3238
|
+
|
|
3239
|
+
${text}`;
|
|
3240
|
+
return { ...symbol, kind: "symbol", symbolKind: symbol.kind, heading: symbol.qualifiedName, text, passage, sourceHash: file.hash, fingerprint: textFingerprint(passage) };
|
|
3241
|
+
}));
|
|
3242
|
+
}
|
|
3243
|
+
|
|
3244
|
+
// packages/cli/src/symbols/graph.mjs
|
|
3245
|
+
import { dirname as dirname8, posix } from "node:path";
|
|
3246
|
+
function importTarget(file, specifier, paths) {
|
|
3247
|
+
if (!specifier.startsWith(".")) return null;
|
|
3248
|
+
const base = posix.normalize(posix.join(dirname8(file).replaceAll("\\", "/"), specifier));
|
|
3249
|
+
if (base.startsWith("../")) return null;
|
|
3250
|
+
const stem = base.replace(/\.(js|jsx|mjs|cjs)$/, "");
|
|
3251
|
+
const candidates = [base, ...[".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"].map((extension) => `${stem}${extension}`), ...[".ts", ".js", ".py"].map((extension) => `${base}/index${extension}`)];
|
|
3252
|
+
return candidates.find((path) => paths.has(path)) ?? null;
|
|
3253
|
+
}
|
|
3254
|
+
function addOutlineGraph(graph, index) {
|
|
3255
|
+
const paths = new Set(index.files.map((file) => file.path));
|
|
3256
|
+
for (const file of index.files) {
|
|
3257
|
+
const source = `file:${file.path}`;
|
|
3258
|
+
for (const symbol of file.symbols) {
|
|
3259
|
+
addNode(graph, symbol.id, "symbol", { ...symbol, sourceHash: file.hash });
|
|
3260
|
+
addEdge(graph, symbol.ownerId ?? source, symbol.id, symbol.ownerId ? "contains_symbol" : "declares_symbol", { confidence: "PARSED", path: file.path });
|
|
3261
|
+
}
|
|
3262
|
+
for (const item of file.imports) {
|
|
3263
|
+
const resolved = importTarget(file.path, item.specifier, paths);
|
|
3264
|
+
const target = resolved ? `file:${resolved}` : `import:${file.path}#${item.specifier}`;
|
|
3265
|
+
if (!resolved) addNode(graph, target, "unresolved_import", { path: file.path, specifier: item.specifier });
|
|
3266
|
+
addEdge(graph, source, target, "imports", { ...item, resolved: Boolean(resolved), confidence: resolved ? "RESOLVED_RELATIVE_FILE" : "SYNTAX_ONLY", path: file.path });
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
return graph;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
2234
3272
|
// packages/cli/src/index/cache.mjs
|
|
2235
3273
|
function collectFingerprints(root, config = readMemoryConfig(root)) {
|
|
2236
3274
|
return collectIndexFiles(root, config).map((file) => {
|
|
@@ -2261,11 +3299,23 @@ function buildIndex(root, previous = readIndex(root)) {
|
|
|
2261
3299
|
const currentPaths = new Set(files.map((file) => file.path));
|
|
2262
3300
|
const removedPaths = (previous?.files ?? []).filter((file) => !currentPaths.has(file.path)).map((file) => file.path);
|
|
2263
3301
|
const changedPaths = [...files.filter((file) => indexed.get(file.path) !== file.sha256).map((file) => file.path), ...removedPaths];
|
|
2264
|
-
const changedFiles = files.filter((file) => changedPaths.includes(file.path)).map((file) =>
|
|
3302
|
+
const changedFiles = files.filter((file) => changedPaths.includes(file.path)).map((file) => join12(root, file.path));
|
|
2265
3303
|
const fullRebuild = !previous?.graph || previous.version !== CACHE_VERSION;
|
|
2266
3304
|
const refreshReason = !previous ? "missing" : previous.version !== CACHE_VERSION ? "schema-mismatch" : removedPaths.length > 0 ? "content-removed" : changedPaths.length > 0 ? "content-changed" : "fresh";
|
|
2267
|
-
const rawGraph = fullRebuild ? buildGraph(root, files.map((file) =>
|
|
2268
|
-
const graph = rawGraph;
|
|
3305
|
+
const rawGraph = fullRebuild ? buildGraph(root, files.map((file) => join12(root, file.path)), memoryConfig) : mergeIncrementalGraph(previous.graph, buildGraph(root, changedFiles, memoryConfig), changedPaths);
|
|
3306
|
+
const graph = { nodes: rawGraph.nodes.filter((node2) => !["symbol", "unresolved_import"].includes(node2.type)), edges: rawGraph.edges.filter((edge) => !["declares_symbol", "contains_symbol", "imports"].includes(edge.relation)) };
|
|
3307
|
+
const outlines = readOutlines(root);
|
|
3308
|
+
let outlinePolicyMatches = false;
|
|
3309
|
+
if (outlines) {
|
|
3310
|
+
try {
|
|
3311
|
+
outlinePolicyMatches = outlines.policy === outlinePolicy(root);
|
|
3312
|
+
} catch {
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
if (outlinePolicyMatches) {
|
|
3316
|
+
const hashes = new Map(files.map((file) => [file.path, file.sha256]));
|
|
3317
|
+
addOutlineGraph(graph, { files: outlines.files.filter((file) => hashes.get(file.path) === file.hash && file.status === "parsed") });
|
|
3318
|
+
}
|
|
2269
3319
|
const archiveBodiesIncluded = (memoryConfig.areas ?? []).some((area2) => area2.id === "archive-body" && area2.includeBodiesByDefault !== false);
|
|
2270
3320
|
return { version: CACHE_VERSION, schemaVersion: CACHE_VERSION, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), archiveBodiesIncluded, memoryConfig: memoryConfigSummary(memoryConfig), files, graph, stats: graphStats(graph), incremental: { enabled: true, fullRebuild, changedFiles: changedPaths.length, refreshReason, elapsedMs: Date.now() - startedAt } };
|
|
2271
3321
|
}
|
|
@@ -2276,20 +3326,20 @@ function writeIndex(root, index) {
|
|
|
2276
3326
|
const graphShards = { nodes: Object.fromEntries(Object.keys(compact.nodes).map((name) => [name, rel(root, shardFile(root, "nodes", name))])), edges: Object.fromEntries(Object.keys(compact.edges).map((name) => [name, rel(root, shardFile(root, "edges", name))])) };
|
|
2277
3327
|
const manifest = { version: index.version, schemaVersion: index.schemaVersion ?? index.version, generatedAt: index.generatedAt, archiveBodiesIncluded: index.archiveBodiesIncluded, memoryConfig: index.memoryConfig, files: index.files, graph: { ...graphStats(index.graph), compactSchema: true, shards: graphShards }, incremental: index.incremental };
|
|
2278
3328
|
writeJson(cacheFile(root), manifest);
|
|
2279
|
-
const shardBytes = [...Object.values(graphShards.nodes), ...Object.values(graphShards.edges)].reduce((sum, path) => sum + jsonSize(
|
|
3329
|
+
const shardBytes = [...Object.values(graphShards.nodes), ...Object.values(graphShards.edges)].reduce((sum, path) => sum + jsonSize(join12(root, path)), 0);
|
|
2280
3330
|
const manifestBytes = jsonSize(cacheFile(root));
|
|
2281
3331
|
return { ...manifest, indexSizeBytes: manifestBytes + shardBytes, manifestBytes, shardBytes };
|
|
2282
3332
|
}
|
|
2283
3333
|
function readIndex(root) {
|
|
2284
3334
|
const file = cacheFile(root);
|
|
2285
|
-
if (!
|
|
3335
|
+
if (!existsSync12(file)) return null;
|
|
2286
3336
|
try {
|
|
2287
|
-
const manifest = JSON.parse(
|
|
3337
|
+
const manifest = JSON.parse(readFileSync12(file, "utf8"));
|
|
2288
3338
|
const shards = manifest.graph?.shards;
|
|
2289
3339
|
if (!shards) return manifest;
|
|
2290
3340
|
const compact = { nodes: {}, edges: {} };
|
|
2291
|
-
for (const [name, path] of Object.entries(shards.nodes ?? {})) compact.nodes[name] =
|
|
2292
|
-
for (const [name, path] of Object.entries(shards.edges ?? {})) compact.edges[name] =
|
|
3341
|
+
for (const [name, path] of Object.entries(shards.nodes ?? {})) compact.nodes[name] = existsSync12(join12(root, path)) ? JSON.parse(readFileSync12(join12(root, path), "utf8")) : [];
|
|
3342
|
+
for (const [name, path] of Object.entries(shards.edges ?? {})) compact.edges[name] = existsSync12(join12(root, path)) ? JSON.parse(readFileSync12(join12(root, path), "utf8")) : [];
|
|
2293
3343
|
return { ...manifest, graph: expandGraph(compact) };
|
|
2294
3344
|
} catch {
|
|
2295
3345
|
return null;
|
|
@@ -2311,11 +3361,12 @@ function getStatus(root) {
|
|
|
2311
3361
|
const reason = ok ? "fresh" : !schemaOk ? "schema-mismatch" : staleFiles.length > 0 ? "content-changed" : "unknown";
|
|
2312
3362
|
return { ok, status: ok ? "fresh" : "stale", cachePath: rel(root, cacheFile(root)), indexedFiles: index.files?.length ?? 0, currentFiles: currentFiles.length, staleFiles: staleFiles.length, examples: staleFiles.slice(0, 10), archiveBodiesIncluded: index.archiveBodiesIncluded === true, schemaVersion, expectedSchemaVersion: CACHE_VERSION, schemaOk, reason, graph: graphStats(index.graph ?? { nodes: [], edges: [] }) };
|
|
2313
3363
|
}
|
|
2314
|
-
function runIndex(argv) {
|
|
3364
|
+
async function runIndex(argv) {
|
|
2315
3365
|
const options = parseCommon(argv);
|
|
3366
|
+
const outlines = await buildOutlines(options.root);
|
|
2316
3367
|
const index = buildIndex(options.root);
|
|
2317
3368
|
const manifest = writeIndex(options.root, index);
|
|
2318
|
-
const result = { ok: true, cachePath: rel(options.root, cacheFile(options.root)), schemaVersion: CACHE_VERSION, indexedFiles: index.files.length, nodes: index.graph.nodes.length, edges: index.graph.edges.length, indexSizeBytes: manifest.indexSizeBytes, shards: manifest.graph.shards, incremental: index.incremental, archiveBodiesIncluded: index.archiveBodiesIncluded === true, memoryConfig: index.memoryConfig };
|
|
3369
|
+
const result = { ok: true, cachePath: rel(options.root, cacheFile(options.root)), schemaVersion: CACHE_VERSION, indexedFiles: index.files.length, outlines: { refresh: outlines.refresh, unsupported: outlines.files.filter((file) => file.status === "unsupported").map((file) => file.path), partial: outlines.files.filter((file) => file.status === "partial").map((file) => file.path) }, nodes: index.graph.nodes.length, edges: index.graph.edges.length, indexSizeBytes: manifest.indexSizeBytes, shards: manifest.graph.shards, incremental: index.incremental, archiveBodiesIncluded: index.archiveBodiesIncluded === true, memoryConfig: index.memoryConfig };
|
|
2319
3370
|
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
2320
3371
|
else console.log(`\u2705 index written (${result.indexedFiles} files, ${result.nodes} nodes, ${result.edges} edges, ${(result.indexSizeBytes / 1024).toFixed(1)} KiB, cache: ${result.cachePath})`);
|
|
2321
3372
|
}
|
|
@@ -2344,7 +3395,7 @@ function graphSummary(index) {
|
|
|
2344
3395
|
|
|
2345
3396
|
// packages/cli/src/validate/references.mjs
|
|
2346
3397
|
import { statSync as statSync5 } from "node:fs";
|
|
2347
|
-
import { dirname as
|
|
3398
|
+
import { dirname as dirname9, relative as relative5, resolve as resolve7 } from "node:path";
|
|
2348
3399
|
var blank = (text) => text.replace(/[^\n\r]/g, " ");
|
|
2349
3400
|
var unescape = (text) => text.replace(/\\([!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~])/g, "$1");
|
|
2350
3401
|
var labelKey = (text) => unescape(text).trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -2444,7 +3495,7 @@ function extractMemoryReferences(content) {
|
|
|
2444
3495
|
return references;
|
|
2445
3496
|
}
|
|
2446
3497
|
function sharedLocalReferences(content, root, file, config) {
|
|
2447
|
-
const source = resolveMemoryArea(
|
|
3498
|
+
const source = resolveMemoryArea(relative5(root, file).split("\\").join("/"), config);
|
|
2448
3499
|
if (source?.scope !== "shared") return [];
|
|
2449
3500
|
const errors = [];
|
|
2450
3501
|
for (const item of extractMemoryReferences(content)) {
|
|
@@ -2456,8 +3507,8 @@ function sharedLocalReferences(content, root, file, config) {
|
|
|
2456
3507
|
continue;
|
|
2457
3508
|
}
|
|
2458
3509
|
if (!path) continue;
|
|
2459
|
-
const base = item.kind === "markdown-link" || /^\.\.?\//.test(path) ?
|
|
2460
|
-
const target =
|
|
3510
|
+
const base = item.kind === "markdown-link" || /^\.\.?\//.test(path) ? dirname9(file) : root;
|
|
3511
|
+
const target = relative5(root, resolve7(base, path)).split("\\").join("/");
|
|
2461
3512
|
if (target === ".." || target.startsWith("../")) continue;
|
|
2462
3513
|
if (item.kind === "inline-code") {
|
|
2463
3514
|
const configuredDirectory = (config.areas ?? []).some((area3) => (area3.paths ?? []).some((pattern) => pattern === `${target}/**`));
|
|
@@ -2491,7 +3542,7 @@ function runValidate(argv) {
|
|
|
2491
3542
|
const root = resolve8(options.root);
|
|
2492
3543
|
const memoryConfig = readMemoryConfig(root);
|
|
2493
3544
|
const documentationRoot = memoryConfig.documentation?.root ?? "docs";
|
|
2494
|
-
const docs =
|
|
3545
|
+
const docs = join13(root, documentationRoot);
|
|
2495
3546
|
const errors = [];
|
|
2496
3547
|
const warnings = [];
|
|
2497
3548
|
const markdownFiles = [];
|
|
@@ -2509,9 +3560,9 @@ Fix: ${fix}` : message, ...prompt ? { prompt } : {} });
|
|
|
2509
3560
|
return false;
|
|
2510
3561
|
};
|
|
2511
3562
|
const walk = (dir) => {
|
|
2512
|
-
if (!
|
|
2513
|
-
for (const entry of
|
|
2514
|
-
const path =
|
|
3563
|
+
if (!existsSync13(dir) || shouldSkipDir(dir)) return;
|
|
3564
|
+
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
3565
|
+
const path = join13(dir, entry.name);
|
|
2515
3566
|
if (entry.isDirectory()) {
|
|
2516
3567
|
const docsRel = rel(docs, path);
|
|
2517
3568
|
if (docsRel && !docsRel.startsWith("..")) {
|
|
@@ -2521,7 +3572,7 @@ Fix: ${fix}` : message, ...prompt ? { prompt } : {} });
|
|
|
2521
3572
|
} else if (entry.isFile() && entry.name.endsWith(".md")) markdownFiles.push(path);
|
|
2522
3573
|
}
|
|
2523
3574
|
};
|
|
2524
|
-
if (!
|
|
3575
|
+
if (!existsSync13(docs)) usage(`docs directory not found: ${docs}`);
|
|
2525
3576
|
const validationPolicy = cloneValidationPolicy(memoryConfig.validation ?? DEFAULT_VALIDATION_POLICY);
|
|
2526
3577
|
const maxLines = options.maxLines ?? validationPolicy.markdown.maxLines;
|
|
2527
3578
|
const maxChars = options.maxChars ?? validationPolicy.markdown.maxChars;
|
|
@@ -2552,7 +3603,7 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
|
|
|
2552
3603
|
walk(docs);
|
|
2553
3604
|
const dirSiblingMap = /* @__PURE__ */ new Map();
|
|
2554
3605
|
for (const f of markdownFiles) {
|
|
2555
|
-
const dir =
|
|
3606
|
+
const dir = dirname10(f);
|
|
2556
3607
|
if (!dirSiblingMap.has(dir)) dirSiblingMap.set(dir, []);
|
|
2557
3608
|
dirSiblingMap.get(dir).push(basename7(f));
|
|
2558
3609
|
}
|
|
@@ -2561,14 +3612,14 @@ README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK
|
|
|
2561
3612
|
const docsRel = rel(docs, file);
|
|
2562
3613
|
const rootRel = rel(root, file);
|
|
2563
3614
|
if (docsRel && !docsRel.startsWith("..") && !isUpperSnakeMarkdown(name)) addError(file, "FILE_NAMING", `Markdown file must be UPPER_SNAKE_CASE.md or README.md: ${name}`, null, "rename the markdown file to UPPER_SNAKE_CASE.md or README.md and update any links that reference it.");
|
|
2564
|
-
const content =
|
|
3615
|
+
const content = readFileSync13(file, "utf8");
|
|
2565
3616
|
fileCache.set(file, content);
|
|
2566
3617
|
const docArea = docsRel?.split("/")[0];
|
|
2567
3618
|
if (name !== "README.md" && isUpperSnakeMarkdown(name) && FILENAME_QUALITY_AREAS.has(docArea)) {
|
|
2568
3619
|
const fp = validationPolicy.markdown?.filename ?? {};
|
|
2569
3620
|
const allow = fp.allow ?? [];
|
|
2570
3621
|
if (!allow.some((p) => matchPathPattern(rootRel, p)) && (fp.warnNumberedSeries ?? true)) {
|
|
2571
|
-
const siblings = dirSiblingMap.get(
|
|
3622
|
+
const siblings = dirSiblingMap.get(dirname10(file)) ?? [];
|
|
2572
3623
|
if (isNumberedSeriesFilename(name, siblings)) {
|
|
2573
3624
|
const heading = extractFirstHeading(content);
|
|
2574
3625
|
const suggestion = suggestFilenameFromHeading(heading);
|
|
@@ -2610,7 +3661,7 @@ ${traceabilityExample()}`, block.line);
|
|
|
2610
3661
|
}
|
|
2611
3662
|
}
|
|
2612
3663
|
const byDir = /* @__PURE__ */ new Map();
|
|
2613
|
-
for (const file of markdownFiles) byDir.set(
|
|
3664
|
+
for (const file of markdownFiles) byDir.set(dirname10(file), [...byDir.get(dirname10(file)) ?? [], file]);
|
|
2614
3665
|
for (const [dir, files] of byDir) {
|
|
2615
3666
|
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.");
|
|
2616
3667
|
}
|
|
@@ -2621,24 +3672,32 @@ ${traceabilityExample()}`, block.line);
|
|
|
2621
3672
|
}
|
|
2622
3673
|
if (options.linkCheck) {
|
|
2623
3674
|
for (const [file, content] of fileCache) {
|
|
2624
|
-
const fileDir =
|
|
3675
|
+
const fileDir = dirname10(file);
|
|
2625
3676
|
for (const { href, line } of extractLinks(content)) {
|
|
2626
3677
|
const hashIndex = href.indexOf("#");
|
|
2627
3678
|
const pathPart = hashIndex === -1 ? href : href.slice(0, hashIndex);
|
|
2628
3679
|
const anchor = hashIndex === -1 ? "" : href.slice(hashIndex + 1);
|
|
2629
3680
|
const target = pathPart ? resolve8(fileDir, pathPart) : file;
|
|
2630
|
-
if (pathPart && !
|
|
3681
|
+
if (pathPart && !existsSync13(target)) {
|
|
2631
3682
|
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.");
|
|
2632
3683
|
continue;
|
|
2633
3684
|
}
|
|
2634
|
-
if (anchor &&
|
|
2635
|
-
const targetContent = fileCache.get(target) ?? (
|
|
3685
|
+
if (anchor && extname6(target) === ".md") {
|
|
3686
|
+
const targetContent = fileCache.get(target) ?? (existsSync13(target) ? readFileSync13(target, "utf8") : "");
|
|
2636
3687
|
if (targetContent && !extractAnchors(targetContent).has(decodeURIComponent(anchor))) addError(file, "BROKEN_ANCHOR", `Local anchor target does not exist: ${href}`, line, "update the fragment to a heading that exists in the target markdown file, or add the missing heading.");
|
|
2637
3688
|
}
|
|
2638
3689
|
}
|
|
2639
3690
|
}
|
|
2640
3691
|
}
|
|
2641
3692
|
if (options.checkIndex) {
|
|
3693
|
+
let outlines;
|
|
3694
|
+
try {
|
|
3695
|
+
outlines = outlineStatus(root);
|
|
3696
|
+
} catch (error) {
|
|
3697
|
+
outlines = { ok: false, reason: `inspection-unavailable: ${error instanceof Error ? error.message : String(error)}`, paths: [] };
|
|
3698
|
+
}
|
|
3699
|
+
if (!outlines.ok) addError(outlinePath(root), "OUTLINE_INDEX_STALE", `Outline index: ${outlines.reason}${outlines.paths.length ? ` (${outlines.paths.join(", ")})` : ""}`, null, "run `dotdotgod index <root>` to refresh outlines and graph metadata; validation does not parse, embed or repair.");
|
|
3700
|
+
for (const path of outlines.unsupported ?? []) addWarning(join13(root, path), "OUTLINE_LANGUAGE_UNSUPPORTED", "Source language is not supported by the current outline extraction matrix.");
|
|
2642
3701
|
const index = readIndex(root);
|
|
2643
3702
|
if (!index) addError(cacheFile(root), "INDEX_MISSING", "Expected .dotdotgod index cache.", null, "run `dotdotgod index <root>` to build the graph index.");
|
|
2644
3703
|
else {
|
|
@@ -2658,20 +3717,20 @@ ${traceabilityExample()}`, block.line);
|
|
|
2658
3717
|
}
|
|
2659
3718
|
if (options.includeLocalMemory) {
|
|
2660
3719
|
for (const area2 of ["plan", "archive/plan", "archive/report"]) {
|
|
2661
|
-
const areaRoot =
|
|
2662
|
-
if (!
|
|
2663
|
-
for (const entry of
|
|
3720
|
+
const areaRoot = join13(docs, area2);
|
|
3721
|
+
if (!existsSync13(areaRoot)) continue;
|
|
3722
|
+
for (const entry of readdirSync3(areaRoot, { withFileTypes: true })) {
|
|
2664
3723
|
if (!entry.isDirectory()) continue;
|
|
2665
|
-
if (!isKebabCase(entry.name)) addError(
|
|
2666
|
-
const readme =
|
|
2667
|
-
if (!
|
|
3724
|
+
if (!isKebabCase(entry.name)) addError(join13(areaRoot, entry.name), "SLUG_NAMING", `Archive/plan/report slug must be kebab-case: ${entry.name}`, null, "rename the task/report directory to kebab-case and update any README index links that reference it.");
|
|
3725
|
+
const readme = join13(areaRoot, entry.name, "README.md");
|
|
3726
|
+
if (!existsSync13(readme)) addError(readme, "MISSING_README", `Expected README.md in ${documentationRoot}/${area2}/${entry.name}/`, null, "add a README.md that summarizes the task/report and links any supporting files.");
|
|
2668
3727
|
}
|
|
2669
3728
|
}
|
|
2670
3729
|
}
|
|
2671
|
-
const gitignore =
|
|
2672
|
-
if (!
|
|
3730
|
+
const gitignore = join13(root, ".gitignore");
|
|
3731
|
+
if (!existsSync13(gitignore)) addError(gitignore, "MISSING_GITIGNORE", "Expected .gitignore", null, `create .gitignore and include ${documentationRoot}/plan, ${documentationRoot}/archive, and .dotdotgod entries.`);
|
|
2673
3732
|
else {
|
|
2674
|
-
const content =
|
|
3733
|
+
const content = readFileSync13(gitignore, "utf8").split("\n").map((line) => line.trim());
|
|
2675
3734
|
for (const required of [`${documentationRoot}/plan`, `${documentationRoot}/archive`, CACHE_DIR]) if (!content.includes(required)) addError(gitignore, "MISSING_GITIGNORE_ENTRY", `Expected .gitignore entry: ${required}`, null, `add ${required} to .gitignore so local plans, archives, and cache files stay untracked.`);
|
|
2676
3735
|
}
|
|
2677
3736
|
if (options.json) console.log(JSON.stringify({ ok: errors.length === 0, errors, warnings }, null, 2));
|
|
@@ -2696,8 +3755,8 @@ Prompt: ${error.prompt}` : ""}`);
|
|
|
2696
3755
|
}
|
|
2697
3756
|
|
|
2698
3757
|
// packages/cli/src/commands/config.mjs
|
|
2699
|
-
import { existsSync as
|
|
2700
|
-
import { join as
|
|
3758
|
+
import { existsSync as existsSync14, statSync as statSync6, writeFileSync as writeFileSync4 } from "node:fs";
|
|
3759
|
+
import { join as join14, resolve as resolve9 } from "node:path";
|
|
2701
3760
|
function parseConfigOptions(argv, usageKey = "config") {
|
|
2702
3761
|
const options = { root: ".", json: false, template: null };
|
|
2703
3762
|
let rootSet = false;
|
|
@@ -2758,17 +3817,17 @@ function runConfig(argv) {
|
|
|
2758
3817
|
const isInit = argv[0] === "init";
|
|
2759
3818
|
const options = parseConfigOptions(isInit ? argv.slice(1) : argv, isInit ? "config init" : "config");
|
|
2760
3819
|
if (isInit) {
|
|
2761
|
-
if (!
|
|
3820
|
+
if (!existsSync14(options.root)) configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
|
|
2762
3821
|
try {
|
|
2763
3822
|
if (!statSync6(options.root).isDirectory()) configInitError(options, "ROOT_NOT_DIRECTORY", `Project root is not a directory: ${options.root}`);
|
|
2764
3823
|
} catch {
|
|
2765
3824
|
configInitError(options, "ROOT_NOT_FOUND", `Project root not found: ${options.root}`);
|
|
2766
3825
|
}
|
|
2767
|
-
const target =
|
|
2768
|
-
if (
|
|
3826
|
+
const target = join14(options.root, "dotdotgod.config.json");
|
|
3827
|
+
if (existsSync14(target)) configInitError(options, "CONFIG_EXISTS", "dotdotgod.config.json already exists.", target);
|
|
2769
3828
|
const template = resolveInitializationTemplate(options.template);
|
|
2770
3829
|
if (!template.ok) configInitError(options, template.error.code, template.error.message, template.error.path ?? target);
|
|
2771
|
-
|
|
3830
|
+
writeFileSync4(target, template.text);
|
|
2772
3831
|
const payload2 = { ok: true, command: "config init", root: options.root, path: target, created: true, template: { name: template.name, source: template.source, selectedBy: template.selectedBy } };
|
|
2773
3832
|
if (options.json) console.log(JSON.stringify(payload2, null, 2));
|
|
2774
3833
|
else console.log(`dotdotgod config init: created ${target} from ${template.source}:${template.name} (${template.selectedBy})`);
|
|
@@ -2784,7 +3843,7 @@ function runConfig(argv) {
|
|
|
2784
3843
|
errors.push({ file: "embedding config", code: "EMBEDDING_CONFIG_INVALID", message: error instanceof Error ? error.message : String(error) });
|
|
2785
3844
|
embedding = { source: "invalid", path: null, profile: sanitizeEmbeddingProfile({ provider: "local", model: "Xenova/multilingual-e5-small" }) };
|
|
2786
3845
|
}
|
|
2787
|
-
const payload = { ok: errors.length === 0, command: "config", root: options.root, source: config.source ?? "default", path: config.source && config.source !== "default" ?
|
|
3846
|
+
const payload = { ok: errors.length === 0, command: "config", root: options.root, source: config.source ?? "default", path: config.source && config.source !== "default" ? join14(options.root, config.source) : null, embedding, config: memoryConfigSummary(config), errors };
|
|
2788
3847
|
if (options.json) console.log(JSON.stringify(payload, null, 2));
|
|
2789
3848
|
else console.log(formatConfigOutput(payload));
|
|
2790
3849
|
if (errors.length > 0) process.exit(1);
|
|
@@ -2793,102 +3852,12 @@ function runConfig(argv) {
|
|
|
2793
3852
|
// packages/cli/src/commands/query.mjs
|
|
2794
3853
|
import { resolve as resolve10 } from "node:path";
|
|
2795
3854
|
|
|
2796
|
-
// packages/cli/src/query/chunks.mjs
|
|
2797
|
-
import { readFileSync as readFileSync10, readdirSync as readdirSync3 } from "node:fs";
|
|
2798
|
-
import { join as join10, relative as relative4 } from "node:path";
|
|
2799
|
-
import { createHash as createHash3 } from "node:crypto";
|
|
2800
|
-
var MAX_CHUNK_CHARS = 1600;
|
|
2801
|
-
var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
2802
|
-
function textFingerprint(text) {
|
|
2803
|
-
return createHash3("sha256").update(text).digest("hex");
|
|
2804
|
-
}
|
|
2805
|
-
function collectDocumentationMarkdown(root, exclude = ["docs/plan", "docs/archive"], documentationRoot = "docs") {
|
|
2806
|
-
const docsRoot = join10(root, documentationRoot);
|
|
2807
|
-
const files = [];
|
|
2808
|
-
const walk = (directory) => {
|
|
2809
|
-
let entries;
|
|
2810
|
-
try {
|
|
2811
|
-
entries = readdirSync3(directory, { withFileTypes: true, encoding: "utf8" });
|
|
2812
|
-
} catch {
|
|
2813
|
-
return;
|
|
2814
|
-
}
|
|
2815
|
-
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
2816
|
-
for (const entry of entries) {
|
|
2817
|
-
if (entry.name.startsWith(".")) continue;
|
|
2818
|
-
const absolute = join10(directory, entry.name);
|
|
2819
|
-
const path = relative4(root, absolute).replaceAll("\\", "/");
|
|
2820
|
-
if (isSecretLikePathPattern(path) || exclude.some((pattern) => matchPathPattern(path, pattern) || path === pattern || path.startsWith(`${pattern}/`))) continue;
|
|
2821
|
-
if (entry.isDirectory()) {
|
|
2822
|
-
if (!SKIPPED_DIRECTORIES.has(entry.name)) walk(absolute);
|
|
2823
|
-
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) files.push(path);
|
|
2824
|
-
}
|
|
2825
|
-
};
|
|
2826
|
-
walk(docsRoot);
|
|
2827
|
-
return files.sort();
|
|
2828
|
-
}
|
|
2829
|
-
function splitSection(text, maxChars = MAX_CHUNK_CHARS) {
|
|
2830
|
-
if (text.length <= maxChars) return [text];
|
|
2831
|
-
const paragraphs = text.split(/\n\s*\n/);
|
|
2832
|
-
const chunks = [];
|
|
2833
|
-
let current = "";
|
|
2834
|
-
for (const paragraph of paragraphs) {
|
|
2835
|
-
if (current && current.length + paragraph.length + 2 > maxChars) {
|
|
2836
|
-
chunks.push(current.trim());
|
|
2837
|
-
current = "";
|
|
2838
|
-
}
|
|
2839
|
-
if (paragraph.length > maxChars) {
|
|
2840
|
-
for (let offset = 0; offset < paragraph.length; offset += maxChars) chunks.push(paragraph.slice(offset, offset + maxChars).trim());
|
|
2841
|
-
} else current += `${current ? "\n\n" : ""}${paragraph}`;
|
|
2842
|
-
}
|
|
2843
|
-
if (current.trim()) chunks.push(current.trim());
|
|
2844
|
-
return chunks.filter(Boolean);
|
|
2845
|
-
}
|
|
2846
|
-
function chunkMarkdown(path, content, maxChars = MAX_CHUNK_CHARS) {
|
|
2847
|
-
const lines = content.split("\n");
|
|
2848
|
-
const sections = [];
|
|
2849
|
-
let headings = [];
|
|
2850
|
-
let body = [];
|
|
2851
|
-
const flush = () => {
|
|
2852
|
-
const text = body.join("\n").trim();
|
|
2853
|
-
if (!text && headings.length === 0) return;
|
|
2854
|
-
const heading = headings.length > 0 ? headings.join(" > ") : path;
|
|
2855
|
-
const pieces = splitSection(text || heading, maxChars);
|
|
2856
|
-
pieces.forEach((piece, index) => {
|
|
2857
|
-
const passage = `Path: ${path}
|
|
2858
|
-
Section: ${heading}
|
|
2859
|
-
|
|
2860
|
-
${piece}`;
|
|
2861
|
-
const fingerprint2 = textFingerprint(passage);
|
|
2862
|
-
sections.push({ id: `${path}#${fingerprint2.slice(0, 16)}`, path, heading, part: index + 1, text: piece, passage, fingerprint: fingerprint2 });
|
|
2863
|
-
});
|
|
2864
|
-
};
|
|
2865
|
-
for (const line of lines) {
|
|
2866
|
-
const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
2867
|
-
if (!match) {
|
|
2868
|
-
body.push(line);
|
|
2869
|
-
continue;
|
|
2870
|
-
}
|
|
2871
|
-
flush();
|
|
2872
|
-
body = [];
|
|
2873
|
-
const level = match[1].length;
|
|
2874
|
-
headings = [...headings.slice(0, level - 1), match[2]];
|
|
2875
|
-
}
|
|
2876
|
-
flush();
|
|
2877
|
-
return sections;
|
|
2878
|
-
}
|
|
2879
|
-
function collectDocumentationChunks(root, exclude, documentationRoot = "docs") {
|
|
2880
|
-
return collectDocumentationMarkdown(root, exclude, documentationRoot).flatMap((path) => {
|
|
2881
|
-
const content = readFileSync10(join10(root, path), "utf8");
|
|
2882
|
-
return chunkMarkdown(path, content);
|
|
2883
|
-
});
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
3855
|
// packages/cli/src/query/embedding-runtime.mjs
|
|
2887
|
-
import { existsSync as
|
|
3856
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync4, readFileSync as readFileSync14, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
2888
3857
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2889
|
-
import { createRequire } from "node:module";
|
|
3858
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
2890
3859
|
import { homedir as homedir3 } from "node:os";
|
|
2891
|
-
import { join as
|
|
3860
|
+
import { join as join15 } from "node:path";
|
|
2892
3861
|
import { pathToFileURL } from "node:url";
|
|
2893
3862
|
var EMBEDDING_RUNTIME_PACKAGE = "@huggingface/transformers";
|
|
2894
3863
|
var EMBEDDING_RUNTIME_VERSION = "4.2.0";
|
|
@@ -2900,13 +3869,13 @@ var EmbeddingRuntimeMissingError = class extends Error {
|
|
|
2900
3869
|
}
|
|
2901
3870
|
};
|
|
2902
3871
|
function embeddingRuntimeRoot(options = {}) {
|
|
2903
|
-
return
|
|
3872
|
+
return join15(options.home ?? homedir3(), ".dotdotgod", "runtime", "embedding");
|
|
2904
3873
|
}
|
|
2905
3874
|
function resolvePersistentTransformers(options = {}) {
|
|
2906
3875
|
const root = embeddingRuntimeRoot(options);
|
|
2907
3876
|
try {
|
|
2908
|
-
const
|
|
2909
|
-
return
|
|
3877
|
+
const require3 = createRequire2(join15(root, "package.json"));
|
|
3878
|
+
return require3.resolve(EMBEDDING_RUNTIME_PACKAGE);
|
|
2910
3879
|
} catch {
|
|
2911
3880
|
throw new EmbeddingRuntimeMissingError();
|
|
2912
3881
|
}
|
|
@@ -2914,10 +3883,10 @@ function resolvePersistentTransformers(options = {}) {
|
|
|
2914
3883
|
function embeddingRuntimeStatus(options = {}) {
|
|
2915
3884
|
const root = embeddingRuntimeRoot(options);
|
|
2916
3885
|
try {
|
|
2917
|
-
const
|
|
2918
|
-
|
|
2919
|
-
const packagePath =
|
|
2920
|
-
const version = JSON.parse(
|
|
3886
|
+
const require3 = createRequire2(join15(root, "package.json"));
|
|
3887
|
+
require3.resolve(EMBEDDING_RUNTIME_PACKAGE);
|
|
3888
|
+
const packagePath = join15(root, "node_modules", ...EMBEDDING_RUNTIME_PACKAGE.split("/"), "package.json");
|
|
3889
|
+
const version = JSON.parse(readFileSync14(packagePath, "utf8")).version;
|
|
2921
3890
|
return { ok: true, installed: true, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: version, location: root };
|
|
2922
3891
|
} catch {
|
|
2923
3892
|
return { ok: true, installed: false, installAvailable: true, package: EMBEDDING_RUNTIME_PACKAGE, requiredVersion: EMBEDDING_RUNTIME_VERSION, packageVersion: null, location: root };
|
|
@@ -2926,10 +3895,10 @@ function embeddingRuntimeStatus(options = {}) {
|
|
|
2926
3895
|
function installEmbeddingRuntime(options = {}) {
|
|
2927
3896
|
if (options.confirm !== true) throw new Error("Embedding runtime installation requires explicit confirmation.");
|
|
2928
3897
|
const root = embeddingRuntimeRoot(options);
|
|
2929
|
-
const lock =
|
|
2930
|
-
if (
|
|
2931
|
-
|
|
2932
|
-
|
|
3898
|
+
const lock = join15(root, ".installing");
|
|
3899
|
+
if (existsSync15(lock)) throw new Error("Embedding runtime installation is already in progress.");
|
|
3900
|
+
mkdirSync4(root, { recursive: true });
|
|
3901
|
+
writeFileSync5(lock, `${process.pid}
|
|
2933
3902
|
`, { flag: "wx" });
|
|
2934
3903
|
try {
|
|
2935
3904
|
const spawn = options.spawnImpl ?? spawnSync2;
|
|
@@ -3029,13 +3998,13 @@ async function resolveEmbedder(root, options = {}) {
|
|
|
3029
3998
|
}
|
|
3030
3999
|
|
|
3031
4000
|
// packages/cli/src/query/store.mjs
|
|
3032
|
-
import { createHash as
|
|
3033
|
-
import { existsSync as
|
|
3034
|
-
import { join as
|
|
4001
|
+
import { createHash as createHash6 } from "node:crypto";
|
|
4002
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync as renameSync2, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4003
|
+
import { join as join16 } from "node:path";
|
|
3035
4004
|
var VECTOR_SCHEMA_VERSION = 2;
|
|
3036
4005
|
var VECTOR_DIMENSIONS = 384;
|
|
3037
|
-
function profileFingerprint(
|
|
3038
|
-
return
|
|
4006
|
+
function profileFingerprint(identity2) {
|
|
4007
|
+
return createHash6("sha256").update(JSON.stringify(identity2)).digest("hex");
|
|
3039
4008
|
}
|
|
3040
4009
|
function isValidNormalizedVectorValues(values, dimensions = VECTOR_DIMENSIONS) {
|
|
3041
4010
|
if (!Number.isInteger(dimensions) || dimensions < 1 || !values || values.length % dimensions !== 0) return false;
|
|
@@ -3051,18 +4020,18 @@ function isValidNormalizedVectorValues(values, dimensions = VECTOR_DIMENSIONS) {
|
|
|
3051
4020
|
return true;
|
|
3052
4021
|
}
|
|
3053
4022
|
function vectorCachePaths(root) {
|
|
3054
|
-
const directory =
|
|
3055
|
-
return { directory, manifest:
|
|
4023
|
+
const directory = join16(root, ".dotdotgod", "vectors");
|
|
4024
|
+
return { directory, manifest: join16(directory, "manifest.json"), chunks: join16(directory, "chunks.jsonl"), embeddings: join16(directory, "embeddings.f32") };
|
|
3056
4025
|
}
|
|
3057
|
-
function readVectorCache(root,
|
|
4026
|
+
function readVectorCache(root, identity2 = null) {
|
|
3058
4027
|
const paths = vectorCachePaths(root);
|
|
3059
|
-
if (!
|
|
4028
|
+
if (!existsSync16(paths.manifest) || !existsSync16(paths.chunks) || !existsSync16(paths.embeddings)) return null;
|
|
3060
4029
|
try {
|
|
3061
|
-
const manifest = JSON.parse(
|
|
4030
|
+
const manifest = JSON.parse(readFileSync15(paths.manifest, "utf8"));
|
|
3062
4031
|
if (manifest.schemaVersion !== VECTOR_SCHEMA_VERSION || !Number.isInteger(manifest.dimensions) || manifest.dimensions < 1) return null;
|
|
3063
|
-
if (
|
|
3064
|
-
const chunks =
|
|
3065
|
-
const buffer =
|
|
4032
|
+
if (identity2 && manifest.profileFingerprint !== profileFingerprint(identity2)) return null;
|
|
4033
|
+
const chunks = readFileSync15(paths.chunks, "utf8").split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
4034
|
+
const buffer = readFileSync15(paths.embeddings);
|
|
3066
4035
|
if (buffer.byteLength !== chunks.length * manifest.dimensions * 4) return null;
|
|
3067
4036
|
const vectors = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
|
|
3068
4037
|
if (!isValidNormalizedVectorValues(vectors, manifest.dimensions)) return null;
|
|
@@ -3073,14 +4042,14 @@ function readVectorCache(root, identity = null) {
|
|
|
3073
4042
|
}
|
|
3074
4043
|
function atomicWrite(path, data) {
|
|
3075
4044
|
const temporary = `${path}.tmp-${process.pid}`;
|
|
3076
|
-
|
|
3077
|
-
|
|
4045
|
+
writeFileSync6(temporary, data);
|
|
4046
|
+
renameSync2(temporary, path);
|
|
3078
4047
|
}
|
|
3079
4048
|
function writeVectorCache(root, chunks, vectors, metadata = {}) {
|
|
3080
4049
|
const paths = vectorCachePaths(root);
|
|
3081
4050
|
const dimensions = metadata.dimensions;
|
|
3082
4051
|
if (!Number.isInteger(dimensions) || dimensions < 1) throw new Error("Vector dimensions must be a positive integer.");
|
|
3083
|
-
|
|
4052
|
+
mkdirSync5(paths.directory, { recursive: true });
|
|
3084
4053
|
if (vectors.length !== chunks.length * dimensions) throw new Error("Vector count does not match chunk metadata.");
|
|
3085
4054
|
if (!isValidNormalizedVectorValues(vectors, dimensions)) throw new Error("Vectors must contain finite normalized values.");
|
|
3086
4055
|
const manifest = { schemaVersion: VECTOR_SCHEMA_VERSION, chunks: chunks.length, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), ...metadata };
|
|
@@ -3092,17 +4061,85 @@ function writeVectorCache(root, chunks, vectors, metadata = {}) {
|
|
|
3092
4061
|
return manifest;
|
|
3093
4062
|
}
|
|
3094
4063
|
|
|
4064
|
+
// packages/cli/src/query/hybrid.mjs
|
|
4065
|
+
var resultKey = (chunk) => chunk.kind === "symbol" ? chunk.id : chunk.path;
|
|
4066
|
+
var terms = (text) => [...new Set(text.replace(/([a-z])([A-Z])/g, "$1 $2").toLowerCase().split(/[^\p{L}\p{N}]+/u).filter(Boolean))];
|
|
4067
|
+
function keywordCandidates(query, chunks) {
|
|
4068
|
+
const words = terms(query);
|
|
4069
|
+
if (!words.length) return [];
|
|
4070
|
+
const literal = query.toLowerCase();
|
|
4071
|
+
return chunks.map((chunk) => {
|
|
4072
|
+
const names = `${chunk.qualifiedName ?? ""} ${chunk.heading ?? ""} ${chunk.path}`.toLowerCase();
|
|
4073
|
+
const body = `${chunk.signature ?? ""} ${chunk.documentation ?? chunk.text}`.toLowerCase();
|
|
4074
|
+
const haystack = new Set(terms(`${names} ${body}`));
|
|
4075
|
+
const matched = words.filter((word) => haystack.has(word)).length;
|
|
4076
|
+
const exact = chunk.qualifiedName?.toLowerCase() === literal || chunk.name?.toLowerCase() === literal;
|
|
4077
|
+
return { ...chunk, exactNameMatch: Boolean(exact), keywordScore: exact ? 3 : matched / words.length + (names.includes(literal) ? 0.5 : body.includes(literal) ? 0.25 : 0) };
|
|
4078
|
+
}).filter((chunk) => chunk.keywordScore > 0).sort((a, b) => b.keywordScore - a.keywordScore || a.id.localeCompare(b.id));
|
|
4079
|
+
}
|
|
4080
|
+
function fuseCandidates(keyword, vector, limit) {
|
|
4081
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
4082
|
+
for (const [channel, rows] of [["keyword", keyword], ["vector", vector]]) {
|
|
4083
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4084
|
+
let rank = 0;
|
|
4085
|
+
rows.forEach((chunk) => {
|
|
4086
|
+
if (seenIds.has(chunk.id)) return;
|
|
4087
|
+
seenIds.add(chunk.id);
|
|
4088
|
+
rank += 1;
|
|
4089
|
+
const existing = candidates.get(chunk.id) ?? { ...chunk, score: 0, retrieval: [] };
|
|
4090
|
+
existing.score += 1 / (60 + rank);
|
|
4091
|
+
existing.retrieval.push(channel);
|
|
4092
|
+
if (chunk.vectorScore !== void 0) existing.vectorScore = chunk.vectorScore;
|
|
4093
|
+
if (chunk.keywordScore !== void 0) existing.keywordScore = chunk.keywordScore;
|
|
4094
|
+
candidates.set(chunk.id, existing);
|
|
4095
|
+
});
|
|
4096
|
+
}
|
|
4097
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4098
|
+
return [...candidates.values()].sort((a, b) => Number(b.exactNameMatch ?? false) - Number(a.exactNameMatch ?? false) || b.score - a.score || a.id.localeCompare(b.id)).filter((chunk) => {
|
|
4099
|
+
const key = resultKey(chunk);
|
|
4100
|
+
if (seen.has(key)) return false;
|
|
4101
|
+
seen.add(key);
|
|
4102
|
+
return true;
|
|
4103
|
+
}).slice(0, limit);
|
|
4104
|
+
}
|
|
4105
|
+
async function projectCorpus(root, scope) {
|
|
4106
|
+
const config = readMemoryConfig(root);
|
|
4107
|
+
const docs = scope === "code" ? [] : collectDocumentationChunks(root, config.load?.documentationSummary?.exclude, config.documentation?.root ?? "docs").map((chunk) => ({ ...chunk, kind: "document" }));
|
|
4108
|
+
const outlines = scope === "docs" ? null : await buildOutlines(root);
|
|
4109
|
+
return { chunks: [...docs, ...outlines ? outlineChunks(outlines) : []], outlines };
|
|
4110
|
+
}
|
|
4111
|
+
function assertCodeEmbeddingConsent(root, options, chunks) {
|
|
4112
|
+
if (!chunks.some((chunk) => chunk.kind === "symbol")) return;
|
|
4113
|
+
const profile = options.profile ?? resolveEmbeddingProfile(root).profile;
|
|
4114
|
+
if (profile.provider !== "local" && options.allowCodeEmbedding !== true) throw new Error("Remote code-metadata embedding requires --allow-code-embedding; use --search keyword or --scope docs instead.");
|
|
4115
|
+
}
|
|
4116
|
+
function attachRelationships(results, outlines) {
|
|
4117
|
+
if (!outlines) return results;
|
|
4118
|
+
const graph = addOutlineGraph({ nodes: [], edges: [] }, outlines);
|
|
4119
|
+
return results.map((result) => {
|
|
4120
|
+
const relationships = graph.edges.filter((edge) => edge.source === result.id || edge.target === result.id || edge.source === `file:${result.path}` && edge.relation === "imports");
|
|
4121
|
+
return { ...result, relationshipCount: relationships.length, relationships: relationships.slice(0, 10) };
|
|
4122
|
+
});
|
|
4123
|
+
}
|
|
4124
|
+
|
|
3095
4125
|
// packages/cli/src/commands/query.mjs
|
|
3096
4126
|
var DEFAULT_LIMIT = 30;
|
|
3097
4127
|
var MAX_LIMIT = 100;
|
|
3098
4128
|
var EMBED_BATCH_SIZE = 16;
|
|
3099
4129
|
function parseQueryOptions(argv) {
|
|
3100
|
-
const options = { root: ".", query: "", limit: DEFAULT_LIMIT, json: false };
|
|
4130
|
+
const options = { root: ".", query: "", limit: DEFAULT_LIMIT, json: false, scope: "all", search: "hybrid", allowCodeEmbedding: false };
|
|
3101
4131
|
let rootSet = false;
|
|
3102
4132
|
const queryParts = [];
|
|
3103
4133
|
for (let i = 0; i < argv.length; i += 1) {
|
|
3104
4134
|
const arg = argv[i];
|
|
3105
4135
|
if (arg === "--json") options.json = true;
|
|
4136
|
+
else if (arg === "--scope") {
|
|
4137
|
+
options.scope = argv[++i];
|
|
4138
|
+
if (!["docs", "code", "all"].includes(options.scope)) usage("--scope must be docs, code or all.", "query");
|
|
4139
|
+
} else if (arg === "--search") {
|
|
4140
|
+
options.search = argv[++i];
|
|
4141
|
+
if (!["hybrid", "keyword", "vector"].includes(options.search)) usage("--search must be hybrid, keyword or vector.", "query");
|
|
4142
|
+
} else if (arg === "--allow-code-embedding") options.allowCodeEmbedding = true;
|
|
3106
4143
|
else if (arg === "--limit") {
|
|
3107
4144
|
const value = Number(argv[++i]);
|
|
3108
4145
|
if (!Number.isInteger(value) || value < 1 || value > MAX_LIMIT) usage(`--limit must be an integer from 1 to ${MAX_LIMIT}.`, "query");
|
|
@@ -3141,20 +4178,12 @@ function rankVectorFiles(queryVector, index) {
|
|
|
3141
4178
|
}
|
|
3142
4179
|
return results;
|
|
3143
4180
|
}
|
|
3144
|
-
function lexicalBoost(query, chunk) {
|
|
3145
|
-
const terms = new Set(query.toLowerCase().split(/[^\p{L}\p{N}_.:/-]+/u).filter((term) => term.length > 1));
|
|
3146
|
-
if (!terms.size) return 0;
|
|
3147
|
-
const haystack = `${chunk.path} ${chunk.heading}`.toLowerCase();
|
|
3148
|
-
let matches = 0;
|
|
3149
|
-
for (const term of terms) if (haystack.includes(term)) matches += 1;
|
|
3150
|
-
return Math.min(0.08, matches / terms.size * 0.08);
|
|
3151
|
-
}
|
|
3152
4181
|
async function buildVectorIndex(root, embedOrOptions = {}) {
|
|
3153
4182
|
const options = typeof embedOrOptions === "function" ? { embed: embedOrOptions } : embedOrOptions;
|
|
3154
4183
|
const resolved = await resolveEmbedder(root, options);
|
|
3155
4184
|
const config = readMemoryConfig(root);
|
|
3156
4185
|
const exclude = config.load?.documentationSummary?.exclude ?? ["docs/plan", "docs/archive"];
|
|
3157
|
-
const chunks = collectDocumentationChunks(root, exclude, config.documentation?.root ?? "docs");
|
|
4186
|
+
const chunks = options.chunks ?? collectDocumentationChunks(root, exclude, config.documentation?.root ?? "docs");
|
|
3158
4187
|
const cached = readVectorCache(root, resolved.identity);
|
|
3159
4188
|
const cachedOffsets = new Map((cached?.chunks ?? []).map((chunk, index) => [chunk.fingerprint, index]));
|
|
3160
4189
|
const rows = new Array(chunks.length);
|
|
@@ -3188,19 +4217,49 @@ async function buildVectorIndex(root, embedOrOptions = {}) {
|
|
|
3188
4217
|
const manifest = writeVectorCache(root, storedChunks, vectors, { provider: resolved.profile.provider, model: resolved.profile.model, dimensions, profileFingerprint: profileFingerprint(resolved.identity), excluded: exclude, embedded: missing.length, reused });
|
|
3189
4218
|
return { chunks: storedChunks, vectors, manifest, embedding: resolved };
|
|
3190
4219
|
}
|
|
3191
|
-
async function
|
|
4220
|
+
async function queryProject(root, query, options = {}) {
|
|
4221
|
+
const scope = options.scope ?? "all";
|
|
4222
|
+
const search = options.search ?? "hybrid";
|
|
3192
4223
|
const limit = options.limit ?? DEFAULT_LIMIT;
|
|
3193
|
-
const
|
|
3194
|
-
const
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
4224
|
+
const corpus = await projectCorpus(root, scope);
|
|
4225
|
+
const keyword = search === "vector" ? [] : keywordCandidates(query, corpus.chunks);
|
|
4226
|
+
let vector = [];
|
|
4227
|
+
let index;
|
|
4228
|
+
let vectorError;
|
|
4229
|
+
if (search !== "keyword" && corpus.chunks.length) {
|
|
4230
|
+
try {
|
|
4231
|
+
assertCodeEmbeddingConsent(root, options, corpus.chunks);
|
|
4232
|
+
index = await buildVectorIndex(root, { ...options, chunks: corpus.chunks });
|
|
4233
|
+
const [queryVector] = await index.embedding.embed([`query: ${query}`]);
|
|
4234
|
+
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
|
+
} catch (error) {
|
|
4236
|
+
if (search === "vector") throw error;
|
|
4237
|
+
vectorError = error instanceof Error ? error.message : String(error);
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
const results = attachRelationships(fuseCandidates(keyword, vector, limit), corpus.outlines).map(({ passage, ...result }) => result);
|
|
4241
|
+
return {
|
|
4242
|
+
ok: true,
|
|
4243
|
+
command: "query",
|
|
4244
|
+
root,
|
|
4245
|
+
query,
|
|
4246
|
+
scope,
|
|
4247
|
+
search,
|
|
4248
|
+
limit,
|
|
4249
|
+
provider: index?.embedding.profile.provider ?? null,
|
|
4250
|
+
model: index?.embedding.profile.model ?? null,
|
|
4251
|
+
embeddingSource: index?.embedding.source ?? "not-used",
|
|
4252
|
+
dimensions: index?.manifest.dimensions ?? null,
|
|
4253
|
+
index: index?.manifest ?? null,
|
|
4254
|
+
outlines: corpus.outlines ? { refresh: corpus.outlines.refresh, indexedFiles: corpus.outlines.files.length, diagnostics: corpus.outlines.files.filter((file) => file.status !== "parsed").map(({ path, status }) => ({ path, status })) } : null,
|
|
4255
|
+
warnings: [...vectorError ? [`Vector retrieval unavailable; keyword results only: ${vectorError}`] : [], ...corpus.outlines?.files.filter((file) => file.status !== "parsed").map((file) => `Outline ${file.status}: ${file.path}`) ?? []],
|
|
4256
|
+
results
|
|
4257
|
+
};
|
|
3199
4258
|
}
|
|
3200
4259
|
function formatQueryResult(payload) {
|
|
3201
|
-
const lines = [`dotdotgod query: ${payload.query}`, `- embedding: ${payload.provider}/${payload.model} (${payload.embeddingSource})`, `- results: ${payload.results.length}
|
|
4260
|
+
const lines = [`dotdotgod query: ${payload.query}`, `- embedding: ${payload.provider ? `${payload.provider}/${payload.model}` : "not used"} (${payload.embeddingSource})`, `- results: ${payload.results.length}`, ...payload.warnings ?? []];
|
|
3202
4261
|
payload.results.forEach((result, index) => {
|
|
3203
|
-
lines.push(`${index + 1}. ${result.path}${result.heading ? ` \u2014 ${result.heading}` : ""} (${result.score.toFixed(3)})`);
|
|
4262
|
+
lines.push(`${index + 1}. ${result.path}${result.startLine ? `:${result.startLine}\u2013${result.endLine}` : ""}${result.heading ? ` \u2014 ${result.heading}` : ""} (${result.score.toFixed(3)})`);
|
|
3204
4263
|
const excerpt = result.text.replace(/\s+/g, " ").trim().slice(0, 240);
|
|
3205
4264
|
if (excerpt) lines.push(` ${excerpt}${result.text.length > 240 ? "\u2026" : ""}`);
|
|
3206
4265
|
});
|
|
@@ -3209,7 +4268,7 @@ function formatQueryResult(payload) {
|
|
|
3209
4268
|
async function runQuery(argv) {
|
|
3210
4269
|
const options = parseQueryOptions(argv);
|
|
3211
4270
|
try {
|
|
3212
|
-
const payload = await
|
|
4271
|
+
const payload = await queryProject(options.root, options.query, options);
|
|
3213
4272
|
console.log(options.json ? JSON.stringify(payload, null, 2) : formatQueryResult(payload));
|
|
3214
4273
|
} catch (error) {
|
|
3215
4274
|
const message = `dotdotgod query failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
@@ -3255,8 +4314,8 @@ function runEmbedding(argv, options = {}) {
|
|
|
3255
4314
|
import { resolve as resolve13 } from "node:path";
|
|
3256
4315
|
|
|
3257
4316
|
// packages/cli/src/memory/documentation-map.mjs
|
|
3258
|
-
import { existsSync as
|
|
3259
|
-
import { join as
|
|
4317
|
+
import { existsSync as existsSync17, readdirSync as readdirSync4, statSync as statSync7 } from "node:fs";
|
|
4318
|
+
import { join as join17, resolve as resolve12 } from "node:path";
|
|
3260
4319
|
var SKIPPED_DIRECTORIES2 = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "coverage", ".git", ".dotdotgod"]);
|
|
3261
4320
|
function node() {
|
|
3262
4321
|
return { directories: /* @__PURE__ */ new Map(), files: [] };
|
|
@@ -3319,7 +4378,7 @@ function discoverMarkdown(root, documentationRoot, exclude) {
|
|
|
3319
4378
|
const walk = (relativeDirectory) => {
|
|
3320
4379
|
let entries;
|
|
3321
4380
|
try {
|
|
3322
|
-
entries = readdirSync4(
|
|
4381
|
+
entries = readdirSync4(join17(root, relativeDirectory), { withFileTypes: true, encoding: "utf8" });
|
|
3323
4382
|
} catch {
|
|
3324
4383
|
return;
|
|
3325
4384
|
}
|
|
@@ -3337,7 +4396,7 @@ function discoverMarkdown(root, documentationRoot, exclude) {
|
|
|
3337
4396
|
}
|
|
3338
4397
|
function buildDocumentationMap(projectRoot = ".", { depth = 5 } = {}) {
|
|
3339
4398
|
const root = resolve12(projectRoot);
|
|
3340
|
-
if (!
|
|
4399
|
+
if (!existsSync17(root)) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
3341
4400
|
try {
|
|
3342
4401
|
if (!statSync7(root).isDirectory()) return { ok: false, error: { code: "ROOT_NOT_FOUND", message: `Project root not found: ${root}` } };
|
|
3343
4402
|
} catch {
|
|
@@ -4768,8 +5827,8 @@ function compareImpactItems(seeds) {
|
|
|
4768
5827
|
}
|
|
4769
5828
|
|
|
4770
5829
|
// packages/cli/src/impact/references.mjs
|
|
4771
|
-
import { readFileSync as
|
|
4772
|
-
import { extname as
|
|
5830
|
+
import { readFileSync as readFileSync16, statSync as statSync8 } from "node:fs";
|
|
5831
|
+
import { extname as extname7, resolve as resolve14 } from "node:path";
|
|
4773
5832
|
var WARNING_LIMIT = 20;
|
|
4774
5833
|
var edgeKey = (source, target, relation) => `${source}\0${target}\0${relation}`;
|
|
4775
5834
|
function prepareImpactReferences(index, root, changedPaths) {
|
|
@@ -4784,7 +5843,7 @@ function prepareImpactReferences(index, root, changedPaths) {
|
|
|
4784
5843
|
const absolute = resolve14(root, path);
|
|
4785
5844
|
const stat = statSync8(absolute);
|
|
4786
5845
|
result = { status: "exists" };
|
|
4787
|
-
if (stat.isFile() &&
|
|
5846
|
+
if (stat.isFile() && extname7(absolute) === ".md") result.anchors = extractAnchors(readFileSync16(absolute, "utf8"));
|
|
4788
5847
|
} catch (error) {
|
|
4789
5848
|
result = { status: ["ENOENT", "ENOTDIR"].includes(error.code) ? "missing" : "unavailable" };
|
|
4790
5849
|
}
|
|
@@ -4846,8 +5905,8 @@ function prepareImpactReferences(index, root, changedPaths) {
|
|
|
4846
5905
|
}
|
|
4847
5906
|
|
|
4848
5907
|
// packages/cli/src/impact/vector-profile.mjs
|
|
4849
|
-
import { lstatSync, readFileSync as
|
|
4850
|
-
import { extname as
|
|
5908
|
+
import { lstatSync as lstatSync3, readFileSync as readFileSync17, realpathSync as realpathSync2 } from "node:fs";
|
|
5909
|
+
import { extname as extname8, relative as relative6, resolve as resolve15, sep } from "node:path";
|
|
4851
5910
|
var MAX_VECTOR_PROFILE_FILE_BYTES = 64 * 1024;
|
|
4852
5911
|
var MAX_VECTOR_PROFILE_CHARS = 4e3;
|
|
4853
5912
|
var MAX_VECTOR_PROFILE_METADATA_ITEMS = 20;
|
|
@@ -4869,13 +5928,13 @@ function canonicalizeChangedPath(root, path) {
|
|
|
4869
5928
|
const absolute = resolve15(rootAbsolute, normalized);
|
|
4870
5929
|
if (!insideRoot(rootAbsolute, absolute)) return normalized;
|
|
4871
5930
|
try {
|
|
4872
|
-
const canonicalRoot =
|
|
4873
|
-
const canonicalAbsolute =
|
|
5931
|
+
const canonicalRoot = realpathSync2(rootAbsolute);
|
|
5932
|
+
const canonicalAbsolute = realpathSync2(absolute);
|
|
4874
5933
|
if (!insideRoot(canonicalRoot, canonicalAbsolute)) return normalized;
|
|
4875
|
-
const canonical =
|
|
5934
|
+
const canonical = relative6(canonicalRoot, canonicalAbsolute).replaceAll("\\", "/");
|
|
4876
5935
|
return canonical || normalized;
|
|
4877
5936
|
} catch {
|
|
4878
|
-
return
|
|
5937
|
+
return relative6(rootAbsolute, absolute).replaceAll("\\", "/") || normalized;
|
|
4879
5938
|
}
|
|
4880
5939
|
}
|
|
4881
5940
|
function unsafeProfilePath(path) {
|
|
@@ -4895,17 +5954,17 @@ function graphMetadata(graph, seedId) {
|
|
|
4895
5954
|
return values;
|
|
4896
5955
|
}
|
|
4897
5956
|
function safeTextPrefix(root, path) {
|
|
4898
|
-
if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(
|
|
5957
|
+
if (unsafeProfilePath(path) || !TEXT_EXTENSIONS.has(extname8(path).toLowerCase())) return "";
|
|
4899
5958
|
try {
|
|
4900
5959
|
const absolute = resolve15(root, path);
|
|
4901
5960
|
if (!insideRoot(root, absolute)) return "";
|
|
4902
|
-
const canonicalAbsolute =
|
|
4903
|
-
if (!insideRoot(
|
|
4904
|
-
const canonicalPath =
|
|
4905
|
-
if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(
|
|
4906
|
-
const stats =
|
|
5961
|
+
const canonicalAbsolute = realpathSync2(absolute);
|
|
5962
|
+
if (!insideRoot(realpathSync2(root), canonicalAbsolute)) return "";
|
|
5963
|
+
const canonicalPath = relative6(realpathSync2(root), canonicalAbsolute).replaceAll("\\", "/");
|
|
5964
|
+
if (canonicalPath !== path || unsafeProfilePath(canonicalPath) || !TEXT_EXTENSIONS.has(extname8(canonicalPath).toLowerCase())) return "";
|
|
5965
|
+
const stats = lstatSync3(canonicalAbsolute);
|
|
4907
5966
|
if (!stats.isFile() || stats.isSymbolicLink() || stats.size > MAX_VECTOR_PROFILE_FILE_BYTES) return "";
|
|
4908
|
-
const buffer =
|
|
5967
|
+
const buffer = readFileSync17(canonicalAbsolute);
|
|
4909
5968
|
if (buffer.includes(0)) return "";
|
|
4910
5969
|
const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
|
|
4911
5970
|
return text.slice(0, MAX_VECTOR_PROFILE_CHARS);
|
|
@@ -5125,7 +6184,7 @@ function buildCompactImpactReport(impact, limits = {}) {
|
|
|
5125
6184
|
}
|
|
5126
6185
|
|
|
5127
6186
|
// packages/cli/src/reference/extract.mjs
|
|
5128
|
-
import { basename as basename8, dirname as
|
|
6187
|
+
import { basename as basename8, dirname as dirname11, extname as extname9 } from "node:path";
|
|
5129
6188
|
var DEFAULT_REFERENCE_LIMIT = 5;
|
|
5130
6189
|
function extractBracketReferences(prompt = "") {
|
|
5131
6190
|
const refs = [];
|
|
@@ -5170,13 +6229,13 @@ function isArchiveBodyPath(path = "", memoryConfig) {
|
|
|
5170
6229
|
function aliasEntriesForPath(path = "") {
|
|
5171
6230
|
const entries = [];
|
|
5172
6231
|
const base = basename8(path);
|
|
5173
|
-
const ext =
|
|
6232
|
+
const ext = extname9(base);
|
|
5174
6233
|
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
5175
6234
|
const withoutMd = path.replace(/\.md$/i, "");
|
|
5176
6235
|
const parts = withoutMd.split("/").filter(Boolean);
|
|
5177
6236
|
for (let i = 0; i < parts.length; i += 1) entries.push({ alias: parts.slice(i).join("/"), kind: "path-suffix" });
|
|
5178
6237
|
if (base === "README.md") {
|
|
5179
|
-
const dir =
|
|
6238
|
+
const dir = dirname11(path).replace(/\\/g, "/");
|
|
5180
6239
|
entries.push({ alias: basename8(dir), kind: "path" });
|
|
5181
6240
|
}
|
|
5182
6241
|
for (const alias of [path, withoutMd, base, stem]) entries.push({ alias, kind: "path" });
|
|
@@ -5187,7 +6246,7 @@ function referenceCandidateAliases(node2) {
|
|
|
5187
6246
|
const entries = [];
|
|
5188
6247
|
if (node2.type === "file" && path) entries.push(...aliasEntriesForPath(path));
|
|
5189
6248
|
if (node2.type === "heading") {
|
|
5190
|
-
const fileStem = path ? basename8(path,
|
|
6249
|
+
const fileStem = path ? basename8(path, extname9(path)) : "";
|
|
5191
6250
|
const anchor = typeof node2.id === "string" && node2.id.includes("#") ? node2.id.split("#").pop() : "";
|
|
5192
6251
|
for (const alias of [node2.title, anchor, fileStem && node2.title ? `${fileStem}#${node2.title}` : "", fileStem && anchor ? `${fileStem}#${anchor}` : "", path && node2.title ? `${path}#${node2.title}` : "", path && anchor ? `${path}#${anchor}` : ""]) entries.push({ alias, kind: "heading" });
|
|
5193
6252
|
}
|
|
@@ -5355,15 +6414,15 @@ function runExpand(argv) {
|
|
|
5355
6414
|
}
|
|
5356
6415
|
|
|
5357
6416
|
// packages/cli/src/commands/traceability.mjs
|
|
5358
|
-
import { existsSync as
|
|
5359
|
-
import { join as
|
|
6417
|
+
import { existsSync as existsSync18, readFileSync as readFileSync18, readdirSync as readdirSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
6418
|
+
import { join as join18, resolve as resolve17 } from "node:path";
|
|
5360
6419
|
function collectDocsMarkdownFiles(root) {
|
|
5361
|
-
const docs =
|
|
6420
|
+
const docs = join18(root, "docs");
|
|
5362
6421
|
const files = [];
|
|
5363
6422
|
const walk = (dir) => {
|
|
5364
|
-
if (!
|
|
6423
|
+
if (!existsSync18(dir)) return;
|
|
5365
6424
|
for (const entry of readdirSync5(dir, { withFileTypes: true })) {
|
|
5366
|
-
const path =
|
|
6425
|
+
const path = join18(dir, entry.name);
|
|
5367
6426
|
if (entry.isDirectory()) walk(path);
|
|
5368
6427
|
else if (entry.isFile() && entry.name.endsWith(".md")) files.push(path);
|
|
5369
6428
|
}
|
|
@@ -5399,7 +6458,7 @@ function runTraceability(argv) {
|
|
|
5399
6458
|
const errors = [...config.errors ?? []];
|
|
5400
6459
|
for (const file of files) {
|
|
5401
6460
|
if (options.write && errors.length > 0) break;
|
|
5402
|
-
const content =
|
|
6461
|
+
const content = readFileSync18(file, "utf8");
|
|
5403
6462
|
for (const error of validateTraceabilityLinksRegion(content, options.root, file)) errors.push(error);
|
|
5404
6463
|
const blocks = extractDotdotgodTraceabilityBlocks(content).filter((block) => !block.error);
|
|
5405
6464
|
if (blocks.length === 0) continue;
|
|
@@ -5408,7 +6467,7 @@ function runTraceability(argv) {
|
|
|
5408
6467
|
for (const error of result.errors ?? []) errors.push(error);
|
|
5409
6468
|
continue;
|
|
5410
6469
|
}
|
|
5411
|
-
if (options.write && result.changed)
|
|
6470
|
+
if (options.write && result.changed) writeFileSync7(file, result.content);
|
|
5412
6471
|
if (result.changed) results.push({ file: rel(options.root, file), changed: true });
|
|
5413
6472
|
}
|
|
5414
6473
|
const ok = errors.length === 0 && (!options.check || results.length === 0);
|
|
@@ -5429,9 +6488,9 @@ function runTraceability(argv) {
|
|
|
5429
6488
|
|
|
5430
6489
|
// packages/cli/src/graph-view/server.mjs
|
|
5431
6490
|
import { createServer } from "node:http";
|
|
5432
|
-
import { readFileSync as
|
|
5433
|
-
import { dirname as
|
|
5434
|
-
import { fileURLToPath } from "node:url";
|
|
6491
|
+
import { readFileSync as readFileSync19 } from "node:fs";
|
|
6492
|
+
import { dirname as dirname12, extname as extname10, join as join19 } from "node:path";
|
|
6493
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5435
6494
|
|
|
5436
6495
|
// packages/cli/src/graph-view/payload.mjs
|
|
5437
6496
|
function edgeKey2(edge) {
|
|
@@ -5542,7 +6601,7 @@ function buildImpactGraphPayload(index, impact) {
|
|
|
5542
6601
|
}
|
|
5543
6602
|
|
|
5544
6603
|
// packages/cli/src/graph-view/server.mjs
|
|
5545
|
-
var ASSET_ROOT =
|
|
6604
|
+
var ASSET_ROOT = join19(dirname12(fileURLToPath2(import.meta.url)), "assets");
|
|
5546
6605
|
var CONTENT_TYPES = { ".css": "text/css; charset=utf-8", ".html": "text/html; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".map": "application/json; charset=utf-8" };
|
|
5547
6606
|
var VENDOR_FILES = {
|
|
5548
6607
|
"/vendor/sigma.js": new URL("../../node_modules/sigma/dist/sigma.min.js", import.meta.url),
|
|
@@ -5555,8 +6614,8 @@ function json(response, status, value) {
|
|
|
5555
6614
|
}
|
|
5556
6615
|
function asset(response, file) {
|
|
5557
6616
|
try {
|
|
5558
|
-
const body =
|
|
5559
|
-
response.writeHead(200, { "content-type": CONTENT_TYPES[
|
|
6617
|
+
const body = readFileSync19(file);
|
|
6618
|
+
response.writeHead(200, { "content-type": CONTENT_TYPES[extname10(file)] ?? "application/octet-stream", "cache-control": "no-store" });
|
|
5560
6619
|
response.end(body);
|
|
5561
6620
|
} catch {
|
|
5562
6621
|
response.writeHead(404);
|
|
@@ -5591,13 +6650,13 @@ async function startImpactGraphServer({ root, changed, host = "127.0.0.1", port
|
|
|
5591
6650
|
return json(response, 500, { ok: false, error: { code: "GRAPH_SERVE_FAILED", message: error instanceof Error ? error.message : String(error) } });
|
|
5592
6651
|
}
|
|
5593
6652
|
}
|
|
5594
|
-
if (VENDOR_FILES[url.pathname]) return asset(response,
|
|
5595
|
-
const
|
|
5596
|
-
if (!["index.html", "app.css", "app.js"].includes(
|
|
6653
|
+
if (VENDOR_FILES[url.pathname]) return asset(response, fileURLToPath2(VENDOR_FILES[url.pathname]));
|
|
6654
|
+
const relative7 = url.pathname === "/" ? "index.html" : url.pathname.slice(1);
|
|
6655
|
+
if (!["index.html", "app.css", "app.js"].includes(relative7)) {
|
|
5597
6656
|
response.writeHead(404);
|
|
5598
6657
|
return response.end("Not found");
|
|
5599
6658
|
}
|
|
5600
|
-
return asset(response,
|
|
6659
|
+
return asset(response, join19(ASSET_ROOT, relative7));
|
|
5601
6660
|
});
|
|
5602
6661
|
await new Promise((resolve18, reject) => {
|
|
5603
6662
|
server.once("error", reject);
|
|
@@ -5825,7 +6884,7 @@ async function runCli(argv = process.argv.slice(2)) {
|
|
|
5825
6884
|
if (hasHelpToken(args)) usage("", helpCommandFromArgs([command, ...args]));
|
|
5826
6885
|
if (command === "validate") runValidate(args);
|
|
5827
6886
|
else if (command === "init") runInit(args, usage);
|
|
5828
|
-
else if (command === "index") runIndex(args);
|
|
6887
|
+
else if (command === "index") await runIndex(args);
|
|
5829
6888
|
else if (command === "config") runConfig(args);
|
|
5830
6889
|
else if (command === "status") runStatus(args);
|
|
5831
6890
|
else if (command === "query") await runQuery(args);
|