@dotdotgod/cli 0.2.15 → 0.2.18

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.
@@ -1,8 +1,6 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
1
+ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { rel } from '../common/paths.mjs';
4
- import { isExcludedIndexDir } from '../index/files.mjs';
5
- import { defaultMemoryConfig, isSecretLikePathPattern, matchPathPattern, memoryAreaForPath } from './config.mjs';
3
+ import { defaultMemoryConfig, memoryAreaForPath } from './config.mjs';
6
4
 
7
5
  export function detectPackageManager(root) {
8
6
  const packageFile = join(root, 'package.json');
@@ -50,98 +48,6 @@ export function detectCommandGuidance(root) {
50
48
  };
51
49
  }
52
50
 
53
- const PINNED_WALK_CAP = 200;
54
-
55
- function walkPinnedMatches(root, base, matches, cap) {
56
- if (!existsSync(base)) return [];
57
- const results = [];
58
- const walk = (dir) => {
59
- if (results.length >= cap) return;
60
- let entries;
61
- try {
62
- entries = readdirSync(dir, { withFileTypes: true });
63
- } catch {
64
- return;
65
- }
66
- entries.sort((a, b) => a.name.localeCompare(b.name));
67
- for (const entry of entries) {
68
- if (results.length >= cap) return;
69
- const absolute = join(dir, entry.name);
70
- const path = rel(root, absolute);
71
- if (entry.isDirectory()) {
72
- if (!isExcludedIndexDir(path)) walk(absolute);
73
- } else if (entry.isFile() && matches(path)) results.push(path);
74
- }
75
- };
76
- walk(base);
77
- return results;
78
- }
79
-
80
- function expandPinnedPattern(root, pattern) {
81
- if (!pattern.includes('*')) {
82
- try {
83
- return statSync(join(root, pattern)).isFile() ? [pattern] : [];
84
- } catch {
85
- return [];
86
- }
87
- }
88
- const base = pattern.endsWith('/**') ? join(root, pattern.slice(0, -3)) : root;
89
- return walkPinnedMatches(root, base, (path) => !isSecretLikePathPattern(path) && matchPathPattern(path, pattern), PINNED_WALK_CAP);
90
- }
91
-
92
- function readPinnedBody(root, path, maxBodyChars) {
93
- if (isSecretLikePathPattern(path)) return { path, status: 'skipped', reason: 'secret-like-path' };
94
- let content;
95
- try {
96
- content = readFileSync(join(root, path));
97
- } catch {
98
- return { path, status: 'missing' };
99
- }
100
- if (content.includes(0)) return { path, status: 'skipped', reason: 'binary' };
101
- const text = content.toString('utf8');
102
- const truncated = text.length > maxBodyChars;
103
- return { path, status: truncated ? 'truncated' : 'present', truncated, chars: text.length, content: truncated ? text.slice(0, maxBodyChars) : text };
104
- }
105
-
106
- export function buildPinnedFiles(root, config = defaultMemoryConfig(), limits = {}) {
107
- const policy = config.load ?? { pinnedPaths: [], pinnedBodies: [] };
108
- const maxPaths = limits.paths ?? 20;
109
- const maxBodies = limits.bodies ?? 5;
110
- const maxBodyChars = limits.bodyChars ?? 10000;
111
- const entries = new Map();
112
- const addMatches = (pattern, kind) => {
113
- const matches = expandPinnedPattern(root, pattern);
114
- for (const path of matches.length > 0 ? matches : [pattern]) {
115
- const entry = entries.get(path) ?? { path, status: matches.length > 0 ? 'present' : 'missing', pinnedBy: new Set() };
116
- entry.pinnedBy.add(kind);
117
- entries.set(path, entry);
118
- }
119
- return matches;
120
- };
121
- for (const pattern of policy.pinnedPaths ?? []) addMatches(pattern, 'pinnedPaths');
122
- const bodyCandidates = new Set();
123
- for (const pattern of policy.pinnedBodies ?? []) {
124
- for (const path of addMatches(pattern, 'pinnedBodies')) bodyCandidates.add(path);
125
- }
126
- const allPaths = [...entries.values()].sort((a, b) => a.path.localeCompare(b.path));
127
- const paths = allPaths.slice(0, maxPaths).map((entry) => ({ path: entry.path, status: entry.status, pinnedBy: [...entry.pinnedBy].sort() }));
128
- const bodies = [];
129
- let omittedBodies = 0;
130
- for (const path of [...bodyCandidates].sort()) {
131
- if (bodies.length >= maxBodies) omittedBodies += 1;
132
- else bodies.push(readPinnedBody(root, path, maxBodyChars));
133
- }
134
- return {
135
- source: config.source ?? 'default',
136
- configured: { pinnedPaths: [...(policy.pinnedPaths ?? [])], pinnedBodies: [...(policy.pinnedBodies ?? [])] },
137
- paths,
138
- omittedPaths: allPaths.length - paths.length,
139
- bodies,
140
- omittedBodies,
141
- bounds: { maxPaths, maxBodies, maxBodyChars },
142
- };
143
- }
144
-
145
51
  export function buildMemoryAreas(index, limits = {}) {
146
52
  const graph = index?.graph ?? { nodes: [], edges: [] };
147
53
  const itemLimit = limits.items ?? 4;
@@ -5,6 +5,20 @@ export const VECTOR_SCHEMA_VERSION = 1;
5
5
  export const VECTOR_MODEL = 'Xenova/multilingual-e5-small';
6
6
  export const VECTOR_DIMENSIONS = 384;
7
7
 
8
+ export function isValidNormalizedVectorValues(values) {
9
+ if (!values || values.length % VECTOR_DIMENSIONS !== 0) return false;
10
+ for (let offset = 0; offset < values.length; offset += VECTOR_DIMENSIONS) {
11
+ let squaredNorm = 0;
12
+ for (let index = 0; index < VECTOR_DIMENSIONS; index += 1) {
13
+ const value = values[offset + index];
14
+ if (!Number.isFinite(value) || value < -1 || value > 1) return false;
15
+ squaredNorm += value * value;
16
+ }
17
+ if (Math.abs(Math.sqrt(squaredNorm) - 1) > 0.01) return false;
18
+ }
19
+ return true;
20
+ }
21
+
8
22
  export function vectorCachePaths(root) {
9
23
  const directory = join(root, '.dotdotgod', 'vectors');
10
24
  return {
@@ -25,6 +39,7 @@ export function readVectorCache(root) {
25
39
  const buffer = readFileSync(paths.embeddings);
26
40
  if (buffer.byteLength !== chunks.length * VECTOR_DIMENSIONS * 4) return null;
27
41
  const vectors = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / 4);
42
+ if (!isValidNormalizedVectorValues(vectors)) return null;
28
43
  return { manifest, chunks, vectors: new Float32Array(vectors) };
29
44
  } catch {
30
45
  return null;
@@ -41,6 +56,7 @@ export function writeVectorCache(root, chunks, vectors, metadata = {}) {
41
56
  const paths = vectorCachePaths(root);
42
57
  mkdirSync(paths.directory, { recursive: true });
43
58
  if (vectors.length !== chunks.length * VECTOR_DIMENSIONS) throw new Error('Vector count does not match chunk metadata.');
59
+ if (!isValidNormalizedVectorValues(vectors)) throw new Error('Vectors must contain finite normalized values.');
44
60
  const manifest = {
45
61
  schemaVersion: VECTOR_SCHEMA_VERSION,
46
62
  model: VECTOR_MODEL,
@@ -135,7 +135,7 @@ export function runValidate(argv) {
135
135
  if (block.error) addError(file, 'TRACEABILITY_INVALID_JSON', `Invalid \`json dotdotgod\` block: ${block.error}\nFix: repair the fenced \`json dotdotgod\` block so it is valid JSON and still matches the expected schema.\n\n${traceabilityExample()}`, block.line);
136
136
  else {
137
137
  for (const error of validateTraceabilityBlock(block.data, root, file, block.line, memoryConfig)) errors.push(error);
138
- const synced = syncTraceabilityLinksInContent(content, block.data, root, file);
138
+ const synced = syncTraceabilityLinksInContent(content, block.data, root, file, memoryConfig);
139
139
  if (synced.ok && synced.changed) addError(file, 'TRACEABILITY_LINKS_STALE', 'Generated traceability links or compact traceability JSON are missing or stale.', block.line, 'run `dotdotgod traceability links <root> --write` to synchronize the generated Markdown links and compact JSON block.');
140
140
  }
141
141
  }