@dotdotgod/cli 0.2.14 → 0.2.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/package.json +1 -1
- package/src/cli/usage.mjs +0 -16
- package/src/commands/config.mjs +4 -2
- package/src/commands/graph.mjs +5 -1
- package/src/commands/query.mjs +37 -7
- package/src/commands/traceability.mjs +5 -2
- package/src/core.mjs +6 -20
- package/src/docs/traceability.mjs +40 -29
- package/src/graph/communities.mjs +1 -2
- package/src/graph/metadata.mjs +18 -14
- package/src/graph/semantic.mjs +1 -1
- package/src/impact/format.mjs +3 -1
- package/src/impact/report.mjs +27 -11
- package/src/impact/scoring.mjs +29 -41
- package/src/impact/vector-overlay.mjs +49 -0
- package/src/impact/vector-profile.mjs +87 -0
- package/src/index/cache.mjs +1 -2
- package/src/index/constants.mjs +1 -1
- package/src/memory/config.mjs +82 -128
- package/src/memory/summary.mjs +2 -96
- package/src/query/store.mjs +16 -0
- package/src/validate/run.mjs +19 -15
- package/src/trello/client.mjs +0 -93
- package/src/trello/custom-fields.mjs +0 -102
- package/src/trello/github-url.mjs +0 -107
- package/src/trello/metadata.mjs +0 -52
- package/src/trello/report.mjs +0 -60
- package/src/trello/summary.mjs +0 -60
- package/src/trello/sync.mjs +0 -205
package/src/memory/config.mjs
CHANGED
|
@@ -6,9 +6,16 @@ import { isKebabCase } from '../docs/markdown.mjs';
|
|
|
6
6
|
const MEMORY_CONFIG_FILE = 'dotdotgod.config.json';
|
|
7
7
|
const MEMORY_SCOPES = new Set(['shared', 'local']);
|
|
8
8
|
const MEMORY_FRESHNESS = new Set(['fresh', 'stale']);
|
|
9
|
+
export const DEFAULT_TRACEABILITY_KEYS = [
|
|
10
|
+
{ key: 'implementedBy', label: 'Implemented by', description: 'Files that implement the behavior.', target: 'path', relation: 'implemented_by', weight: 4 },
|
|
11
|
+
{ key: 'verifiedBy', label: 'Verified by', description: 'Tests or maintained verification documents.', target: 'path', relation: 'verified_by', weight: 4 },
|
|
12
|
+
{ key: 'relatedDocs', label: 'Related docs', description: 'Documents needed to interpret the behavior.', target: 'path', relation: 'related_doc', weight: 3 },
|
|
13
|
+
{ key: 'verificationCommands', label: 'Verification commands', description: 'Project-local verification commands.', target: 'command', relation: 'verification_command', weight: 3 },
|
|
14
|
+
];
|
|
9
15
|
export const DEFAULT_TRACEABILITY_POLICY = {
|
|
10
16
|
required: ['docs/spec/**'],
|
|
11
17
|
exclude: ['**/README.md'],
|
|
18
|
+
keys: DEFAULT_TRACEABILITY_KEYS,
|
|
12
19
|
};
|
|
13
20
|
export const DEFAULT_VALIDATION_POLICY = {
|
|
14
21
|
markdown: {
|
|
@@ -21,9 +28,6 @@ export const DEFAULT_VALIDATION_POLICY = {
|
|
|
21
28
|
},
|
|
22
29
|
},
|
|
23
30
|
};
|
|
24
|
-
export const DEFAULT_INTEGRATIONS_POLICY = {
|
|
25
|
-
trello: { syncPaths: [] },
|
|
26
|
-
};
|
|
27
31
|
export const DEFAULT_LOAD_POLICY = {
|
|
28
32
|
pinnedPaths: [],
|
|
29
33
|
pinnedBodies: [],
|
|
@@ -35,37 +39,27 @@ export const DEFAULT_PLAN_MODE_POLICY = {
|
|
|
35
39
|
writablePaths: ['docs/plan/**', 'docs/archive/**'],
|
|
36
40
|
};
|
|
37
41
|
export const DEFAULT_IMPACT_RANKING_POLICY = {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
ppr: {
|
|
42
|
+
connectionCap: 80,
|
|
43
|
+
memoryCap: 20,
|
|
44
|
+
ppr: { damping: 0.85, iterations: 20, tolerance: 0.000001, reference: 0.4 },
|
|
41
45
|
relationWeights: {
|
|
42
|
-
implemented_by: 4,
|
|
43
|
-
verified_by: 4,
|
|
44
|
-
related_doc: 3,
|
|
45
|
-
verification_command: 3,
|
|
46
46
|
links_to: 2,
|
|
47
|
+
routes_to: 2,
|
|
47
48
|
belongs_to_area: 2,
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
vector_similarity: 2,
|
|
50
|
+
includes_resource: 1,
|
|
51
|
+
defines_contract: 2,
|
|
50
52
|
},
|
|
51
|
-
|
|
52
|
-
verificationBoosts: { verified_by: 15, 'incoming:verified_by': 15, verification_command: 12, 'incoming:verification_command': 12 },
|
|
53
|
-
semanticBoosts: { semantic_similarity: 8, 'incoming:semantic_similarity': 8, mentions_package: 4, 'incoming:mentions_package': 4 },
|
|
54
|
-
proximityBoosts: { links_to: 6, 'incoming:links_to': 6, routes_to: 5, 'incoming:routes_to': 5 },
|
|
55
|
-
semantic: { enabled: true, threshold: 0.5, topKPerFile: 5, includeArchiveBodies: false, signals: ['path', 'filename', 'heading', 'package'] },
|
|
56
|
-
};
|
|
57
|
-
const IMPACT_RANKING_PRESETS = {
|
|
58
|
-
balanced: {},
|
|
59
|
-
'docs-first': { weights: { ppr: 35, traceability: 35, memoryPolicy: 15, verification: 15, proximity: 5, semantic: 8, freshness: 5, archivePenalty: -30 } },
|
|
60
|
-
'code-proximity': { weights: { ppr: 45, traceability: 20, memoryPolicy: 8, verification: 12, proximity: 20, semantic: 8, freshness: 3, archivePenalty: -25 } },
|
|
61
|
-
'test-focused': { weights: { ppr: 35, traceability: 25, memoryPolicy: 8, verification: 25, proximity: 10, semantic: 7, freshness: 5, archivePenalty: -25 } },
|
|
62
|
-
'archive-aware': { weights: { ppr: 35, traceability: 25, memoryPolicy: 10, verification: 15, proximity: 10, semantic: 8, freshness: 3, archivePenalty: -10 } },
|
|
53
|
+
semantic: { enabled: true, threshold: 0.5, topKPerFile: 5 },
|
|
63
54
|
};
|
|
64
|
-
export const SEMANTIC_RELATIONS = new Set(['
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
55
|
+
export const SEMANTIC_RELATIONS = new Set(['vector_similarity']);
|
|
56
|
+
const TRACEABILITY_TARGETS = new Set(['path', 'command']);
|
|
57
|
+
const TRACEABILITY_RESERVED_KEYS = new Set(['kind', 'contracts', 'id', 'title', 'sections']);
|
|
58
|
+
export const BUILT_IN_GRAPH_RELATIONS = new Set([
|
|
59
|
+
'belongs_to_area', 'includes_resource', 'defines_contract', 'contains_heading', 'links_to', 'routes_to',
|
|
60
|
+
'declares_package', 'declares_script', 'declares_bin', 'depends_on', 'vector_similarity',
|
|
61
|
+
]);
|
|
62
|
+
const TRACEABILITY_RESERVED_RELATIONS = BUILT_IN_GRAPH_RELATIONS;
|
|
69
63
|
const DEFAULT_FUZZY_LOW_SIGNAL_TERMS = [
|
|
70
64
|
'a', 'an', 'and', 'are', 'as', 'by', 'docs', 'document', 'for', 'from', 'it', 'of', 'on', 'plan', 'test', 'the', 'to', 'update', 'version', 'with',
|
|
71
65
|
'계획', '문서', '수정', '업데이트', '버전', '정보', '확인', '테스트',
|
|
@@ -112,13 +106,22 @@ function cloneArea(area) {
|
|
|
112
106
|
}, area);
|
|
113
107
|
}
|
|
114
108
|
|
|
109
|
+
function cloneTraceabilityKey(definition) {
|
|
110
|
+
return { key: definition.key, label: definition.label, description: definition.description, target: definition.target, relation: definition.relation, weight: definition.weight };
|
|
111
|
+
}
|
|
112
|
+
|
|
115
113
|
export function cloneTraceabilityPolicy(policy = DEFAULT_TRACEABILITY_POLICY) {
|
|
116
114
|
return {
|
|
117
115
|
required: [...(policy.required ?? [])],
|
|
118
116
|
exclude: [...(policy.exclude ?? [])],
|
|
117
|
+
keys: (policy.keys ?? DEFAULT_TRACEABILITY_KEYS).map(cloneTraceabilityKey),
|
|
119
118
|
};
|
|
120
119
|
}
|
|
121
120
|
|
|
121
|
+
export function traceabilityRelationWeights(policy = DEFAULT_TRACEABILITY_POLICY) {
|
|
122
|
+
return Object.fromEntries((policy.keys ?? DEFAULT_TRACEABILITY_KEYS).map((definition) => [definition.relation, definition.weight]));
|
|
123
|
+
}
|
|
124
|
+
|
|
122
125
|
export function cloneValidationPolicy(policy = DEFAULT_VALIDATION_POLICY) {
|
|
123
126
|
const filename = policy.markdown?.filename;
|
|
124
127
|
return {
|
|
@@ -135,15 +138,11 @@ export function cloneValidationPolicy(policy = DEFAULT_VALIDATION_POLICY) {
|
|
|
135
138
|
|
|
136
139
|
export function cloneImpactRankingPolicy(policy = DEFAULT_IMPACT_RANKING_POLICY) {
|
|
137
140
|
return {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
ppr: { ...DEFAULT_IMPACT_RANKING_POLICY.ppr
|
|
141
|
-
relationWeights: { ...DEFAULT_IMPACT_RANKING_POLICY.relationWeights
|
|
142
|
-
|
|
143
|
-
verificationBoosts: { ...DEFAULT_IMPACT_RANKING_POLICY.verificationBoosts, ...(policy.verificationBoosts ?? {}) },
|
|
144
|
-
semanticBoosts: { ...DEFAULT_IMPACT_RANKING_POLICY.semanticBoosts, ...(policy.semanticBoosts ?? {}) },
|
|
145
|
-
proximityBoosts: { ...DEFAULT_IMPACT_RANKING_POLICY.proximityBoosts, ...(policy.proximityBoosts ?? {}) },
|
|
146
|
-
semantic: { ...DEFAULT_IMPACT_RANKING_POLICY.semantic, ...(policy.semantic ?? {}), signals: [...(policy.semantic?.signals ?? DEFAULT_IMPACT_RANKING_POLICY.semantic.signals)] },
|
|
141
|
+
connectionCap: DEFAULT_IMPACT_RANKING_POLICY.connectionCap,
|
|
142
|
+
memoryCap: DEFAULT_IMPACT_RANKING_POLICY.memoryCap,
|
|
143
|
+
ppr: { ...DEFAULT_IMPACT_RANKING_POLICY.ppr },
|
|
144
|
+
relationWeights: { ...DEFAULT_IMPACT_RANKING_POLICY.relationWeights },
|
|
145
|
+
semantic: { ...DEFAULT_IMPACT_RANKING_POLICY.semantic, ...(policy.semantic ?? {}) },
|
|
147
146
|
};
|
|
148
147
|
}
|
|
149
148
|
|
|
@@ -172,13 +171,16 @@ function normalizeReferenceExpansionPolicy(raw) {
|
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
function normalizeImpactRankingPolicy(raw) {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
174
|
+
const candidate = raw && typeof raw === 'object' && !Array.isArray(raw)
|
|
175
|
+
&& raw.semantic && typeof raw.semantic === 'object' && !Array.isArray(raw.semantic)
|
|
176
|
+
? raw.semantic
|
|
177
|
+
: {};
|
|
178
|
+
const semantic = {
|
|
179
|
+
...(typeof candidate.enabled === 'boolean' ? { enabled: candidate.enabled } : {}),
|
|
180
|
+
...(isFiniteNumberInRange(candidate.threshold, 0, 1) ? { threshold: candidate.threshold } : {}),
|
|
181
|
+
...(Number.isInteger(candidate.topKPerFile) && candidate.topKPerFile >= 0 && candidate.topKPerFile <= 20 ? { topKPerFile: candidate.topKPerFile } : {}),
|
|
182
|
+
};
|
|
183
|
+
return cloneImpactRankingPolicy({ semantic });
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
export function cloneLoadPolicy(policy = DEFAULT_LOAD_POLICY) {
|
|
@@ -196,7 +198,7 @@ export function clonePlanModePolicy(policy = DEFAULT_PLAN_MODE_POLICY) {
|
|
|
196
198
|
}
|
|
197
199
|
|
|
198
200
|
export function defaultMemoryConfig() {
|
|
199
|
-
return { source: 'default', areas: DEFAULT_MEMORY_AREAS.map(cloneArea), traceability: cloneTraceabilityPolicy(), validation: cloneValidationPolicy(), impactRanking: cloneImpactRankingPolicy(), referenceExpansion: cloneReferenceExpansionPolicy(),
|
|
201
|
+
return { source: 'default', areas: DEFAULT_MEMORY_AREAS.map(cloneArea), traceability: cloneTraceabilityPolicy(), validation: cloneValidationPolicy(), impactRanking: cloneImpactRankingPolicy(), referenceExpansion: cloneReferenceExpansionPolicy(), load: cloneLoadPolicy(), planMode: clonePlanModePolicy() };
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
export function normalizePathPattern(value = '') {
|
|
@@ -244,6 +246,7 @@ function normalizeTraceabilityPolicy(raw) {
|
|
|
244
246
|
return {
|
|
245
247
|
required: Array.isArray(raw.required) ? raw.required.map(normalizePathPattern) : [],
|
|
246
248
|
exclude: Array.isArray(raw.exclude) ? raw.exclude.map(normalizePathPattern) : [],
|
|
249
|
+
keys: (raw.keys === undefined ? DEFAULT_TRACEABILITY_KEYS : raw.keys).map(cloneTraceabilityKey),
|
|
247
250
|
};
|
|
248
251
|
}
|
|
249
252
|
|
|
@@ -264,12 +267,6 @@ function normalizeValidationPolicy(raw) {
|
|
|
264
267
|
});
|
|
265
268
|
}
|
|
266
269
|
|
|
267
|
-
function normalizeIntegrationsPolicy(raw) {
|
|
268
|
-
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return cloneIntegrationsPolicy();
|
|
269
|
-
const syncPaths = Array.isArray(raw.trello?.syncPaths) ? raw.trello.syncPaths.map(normalizePathPattern) : [];
|
|
270
|
-
return cloneIntegrationsPolicy({ trello: { syncPaths: [...new Set(syncPaths)] } });
|
|
271
|
-
}
|
|
272
|
-
|
|
273
270
|
export function isSecretLikePathPattern(value = '') {
|
|
274
271
|
const normalized = normalizePathPattern(value);
|
|
275
272
|
return /(^|\/)(\.env|\.npmrc|\.pypirc|id_rsa|id_dsa|id_ed25519|credentials?|secrets?)(\.|\/|$)/i.test(normalized);
|
|
@@ -287,8 +284,8 @@ function normalizeLoadPolicy(raw) {
|
|
|
287
284
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return cloneLoadPolicy();
|
|
288
285
|
const normalize = (values) => (Array.isArray(values) ? [...new Set(values.map(normalizePathPattern))] : []);
|
|
289
286
|
return cloneLoadPolicy({
|
|
290
|
-
pinnedPaths:
|
|
291
|
-
pinnedBodies:
|
|
287
|
+
pinnedPaths: [],
|
|
288
|
+
pinnedBodies: [],
|
|
292
289
|
documentationSummary: {
|
|
293
290
|
exclude: raw.documentationSummary?.exclude === undefined
|
|
294
291
|
? DEFAULT_LOAD_POLICY.documentationSummary.exclude
|
|
@@ -297,10 +294,6 @@ function normalizeLoadPolicy(raw) {
|
|
|
297
294
|
});
|
|
298
295
|
}
|
|
299
296
|
|
|
300
|
-
export function trelloSyncPaths(config = defaultMemoryConfig()) {
|
|
301
|
-
return [...new Set(['docs/trello/**', ...((config.integrations ?? DEFAULT_INTEGRATIONS_POLICY).trello?.syncPaths ?? [])])];
|
|
302
|
-
}
|
|
303
|
-
|
|
304
297
|
export function isMarkdownSizeExcluded(path = '', config = defaultMemoryConfig()) {
|
|
305
298
|
const policy = config.validation ?? DEFAULT_VALIDATION_POLICY;
|
|
306
299
|
return (policy.markdown?.exclude ?? []).some((pattern) => matchPathPattern(path, pattern));
|
|
@@ -331,10 +324,6 @@ function isFiniteNumberInRange(value, min, max) {
|
|
|
331
324
|
return typeof value === 'number' && Number.isFinite(value) && value >= min && value <= max;
|
|
332
325
|
}
|
|
333
326
|
|
|
334
|
-
function validateNumberMap(map, keys, min, max) {
|
|
335
|
-
return map && typeof map === 'object' && !Array.isArray(map) && Object.entries(map).every(([key, value]) => keys.has(key) && isFiniteNumberInRange(value, min, max));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
327
|
export function validateMemoryConfigData(data, root = '.', file = 'dotdotgod.config.json') {
|
|
339
328
|
const errors = [];
|
|
340
329
|
const add = (code, field, message, fix = null) => errors.push({
|
|
@@ -355,6 +344,26 @@ export function validateMemoryConfigData(data, root = '.', file = 'dotdotgod.con
|
|
|
355
344
|
else if (traceability.required.some((value) => !isValidPathPattern(value))) add('TRACEABILITY_CONFIG_INVALID_REQUIRED', 'traceability.required', 'Expected path strings using exact paths, /** subtree patterns, or **/suffix patterns.');
|
|
356
345
|
if (traceability.exclude !== undefined && !Array.isArray(traceability.exclude)) add('TRACEABILITY_CONFIG_INVALID_EXCLUDE', 'traceability.exclude', 'Expected an array of path strings.');
|
|
357
346
|
else if (Array.isArray(traceability.exclude) && traceability.exclude.some((value) => !isValidPathPattern(value))) add('TRACEABILITY_CONFIG_INVALID_EXCLUDE', 'traceability.exclude', 'Expected path strings using exact paths, /** subtree patterns, or **/suffix patterns.');
|
|
347
|
+
if (traceability.keys !== undefined && !Array.isArray(traceability.keys)) add('TRACEABILITY_CONFIG_INVALID_KEYS', 'traceability.keys', 'Expected an array of traceability key definitions.');
|
|
348
|
+
else if (Array.isArray(traceability.keys)) {
|
|
349
|
+
const keys = new Set();
|
|
350
|
+
const labels = new Set();
|
|
351
|
+
const relations = new Set();
|
|
352
|
+
for (const [index, definition] of traceability.keys.entries()) {
|
|
353
|
+
const prefix = `traceability.keys[${index}]`;
|
|
354
|
+
if (!definition || typeof definition !== 'object' || Array.isArray(definition)) { add('TRACEABILITY_CONFIG_INVALID_KEY', prefix, 'Expected an object.'); continue; }
|
|
355
|
+
const allowed = new Set(['key', 'label', 'description', 'target', 'relation', 'weight']);
|
|
356
|
+
if (Object.keys(definition).some((key) => !allowed.has(key))) add('TRACEABILITY_CONFIG_INVALID_KEY', prefix, 'Contains unsupported fields.');
|
|
357
|
+
if (typeof definition.key !== 'string' || !/^[A-Za-z][A-Za-z0-9]*$/.test(definition.key) || TRACEABILITY_RESERVED_KEYS.has(definition.key) || keys.has(definition.key)) add('TRACEABILITY_CONFIG_INVALID_KEY', `${prefix}.key`, 'Expected a unique non-reserved identifier.'); else keys.add(definition.key);
|
|
358
|
+
const trimmedLabel = typeof definition.label === 'string' ? definition.label.trim() : '';
|
|
359
|
+
const normalizedLabel = trimmedLabel.toLocaleLowerCase();
|
|
360
|
+
if (!normalizedLabel || /[\u0000-\u001f\u007f]/.test(trimmedLabel) || labels.has(normalizedLabel)) add('TRACEABILITY_CONFIG_INVALID_LABEL', `${prefix}.label`, 'Expected a unique single-line non-empty label after trimming and case normalization.'); else labels.add(normalizedLabel);
|
|
361
|
+
if (typeof definition.description !== 'string' || !definition.description.trim()) add('TRACEABILITY_CONFIG_INVALID_DESCRIPTION', `${prefix}.description`, 'Expected a non-empty description.');
|
|
362
|
+
if (!TRACEABILITY_TARGETS.has(definition.target)) add('TRACEABILITY_CONFIG_INVALID_TARGET', `${prefix}.target`, 'Expected "path" or "command".');
|
|
363
|
+
if (typeof definition.relation !== 'string' || !/^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/.test(definition.relation) || TRACEABILITY_RESERVED_RELATIONS.has(definition.relation) || relations.has(definition.relation)) add('TRACEABILITY_CONFIG_INVALID_RELATION', `${prefix}.relation`, 'Expected a unique canonical snake_case relation not reserved by the graph.'); else relations.add(definition.relation);
|
|
364
|
+
if (!isFiniteNumberInRange(definition.weight, 0, 20)) add('TRACEABILITY_CONFIG_INVALID_WEIGHT', `${prefix}.weight`, 'Expected a finite number from 0 to 20.');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
358
367
|
}
|
|
359
368
|
}
|
|
360
369
|
const validation = data.validation;
|
|
@@ -383,37 +392,11 @@ export function validateMemoryConfigData(data, root = '.', file = 'dotdotgod.con
|
|
|
383
392
|
}
|
|
384
393
|
}
|
|
385
394
|
}
|
|
386
|
-
const integrations = data.integrations;
|
|
387
|
-
if (integrations !== undefined) {
|
|
388
|
-
if (!integrations || typeof integrations !== 'object' || Array.isArray(integrations)) {
|
|
389
|
-
add('INTEGRATIONS_CONFIG_INVALID', 'integrations', 'Expected an object.');
|
|
390
|
-
} else if (integrations.trello !== undefined) {
|
|
391
|
-
const trello = integrations.trello;
|
|
392
|
-
if (!trello || typeof trello !== 'object' || Array.isArray(trello)) {
|
|
393
|
-
add('INTEGRATIONS_TRELLO_CONFIG_INVALID', 'integrations.trello', 'Expected an object.');
|
|
394
|
-
} else if (trello.syncPaths !== undefined) {
|
|
395
|
-
if (!Array.isArray(trello.syncPaths)) add('INTEGRATIONS_TRELLO_INVALID_SYNC_PATHS', 'integrations.trello.syncPaths', 'Expected an array of path strings.');
|
|
396
|
-
else {
|
|
397
|
-
if (trello.syncPaths.some((value) => !isValidPathPattern(value) || (typeof value === 'string' && value.trim().startsWith('/')))) add('INTEGRATIONS_TRELLO_INVALID_SYNC_PATHS', 'integrations.trello.syncPaths', 'Expected repository-relative path strings using exact paths, /** subtree patterns, or **/suffix patterns.');
|
|
398
|
-
if (trello.syncPaths.some((value) => typeof value === 'string' && isSecretLikePathPattern(value))) add('INTEGRATIONS_TRELLO_SECRET_SYNC_PATH', 'integrations.trello.syncPaths', 'Sync paths must not target secrets, credentials, environment files, or private keys.');
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
395
|
const load = data.load;
|
|
404
396
|
if (load !== undefined) {
|
|
405
397
|
if (!load || typeof load !== 'object' || Array.isArray(load)) {
|
|
406
398
|
add('LOAD_CONFIG_INVALID', 'load', 'Expected an object.');
|
|
407
399
|
} else {
|
|
408
|
-
for (const [key, code] of [['pinnedPaths', 'LOAD_CONFIG_INVALID_PINNED_PATHS'], ['pinnedBodies', 'LOAD_CONFIG_INVALID_PINNED_BODIES']]) {
|
|
409
|
-
const values = load[key];
|
|
410
|
-
if (values === undefined) continue;
|
|
411
|
-
if (!Array.isArray(values)) add(code, `load.${key}`, 'Expected an array of path strings.');
|
|
412
|
-
else {
|
|
413
|
-
if (values.some((value) => !isValidPathPattern(value) || (typeof value === 'string' && value.trim().startsWith('/')))) add(code, `load.${key}`, 'Expected repository-relative path strings using exact paths, /** subtree patterns, or **/suffix patterns.');
|
|
414
|
-
if (values.some((value) => typeof value === 'string' && isSecretLikePathPattern(value))) add('LOAD_CONFIG_SECRET_PINNED_PATH', `load.${key}`, 'Pinned load paths must not target secrets, credentials, environment files, or private keys.');
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
400
|
if (load.documentationSummary !== undefined) {
|
|
418
401
|
const summary = load.documentationSummary;
|
|
419
402
|
if (!summary || typeof summary !== 'object' || Array.isArray(summary)) {
|
|
@@ -455,40 +438,6 @@ export function validateMemoryConfigData(data, root = '.', file = 'dotdotgod.con
|
|
|
455
438
|
}
|
|
456
439
|
}
|
|
457
440
|
}
|
|
458
|
-
const impactRanking = data.impactRanking;
|
|
459
|
-
if (impactRanking !== undefined) {
|
|
460
|
-
if (!impactRanking || typeof impactRanking !== 'object' || Array.isArray(impactRanking)) {
|
|
461
|
-
add('IMPACT_RANKING_CONFIG_INVALID', 'impactRanking', 'Expected an object.');
|
|
462
|
-
} else {
|
|
463
|
-
if (impactRanking.preset !== undefined && !Object.hasOwn(IMPACT_RANKING_PRESETS, impactRanking.preset)) add('IMPACT_RANKING_CONFIG_INVALID_PRESET', 'impactRanking.preset', 'Expected one of balanced, docs-first, code-proximity, test-focused, or archive-aware.');
|
|
464
|
-
if (impactRanking.weights !== undefined && !validateNumberMap(impactRanking.weights, IMPACT_RANKING_WEIGHT_KEYS, -100, 100)) add('IMPACT_RANKING_CONFIG_INVALID_WEIGHTS', 'impactRanking.weights', 'Expected known numeric weight keys with finite values from -100 to 100.');
|
|
465
|
-
if (impactRanking.relationWeights !== undefined && !validateNumberMap(impactRanking.relationWeights, IMPACT_RANKING_RELATION_KEYS, 0, 20)) add('IMPACT_RANKING_CONFIG_INVALID_RELATION_WEIGHTS', 'impactRanking.relationWeights', 'Expected known relation keys with finite values from 0 to 20.');
|
|
466
|
-
for (const key of ['traceabilityBoosts', 'verificationBoosts', 'semanticBoosts', 'proximityBoosts']) {
|
|
467
|
-
if (impactRanking[key] !== undefined && !validateNumberMap(impactRanking[key], IMPACT_RANKING_REASON_KEYS, 0, 100)) add('IMPACT_RANKING_CONFIG_INVALID_BOOSTS', `impactRanking.${key}`, 'Expected known reason keys with finite values from 0 to 100.');
|
|
468
|
-
}
|
|
469
|
-
if (impactRanking.ppr !== undefined) {
|
|
470
|
-
const ppr = impactRanking.ppr;
|
|
471
|
-
if (!ppr || typeof ppr !== 'object' || Array.isArray(ppr)) add('IMPACT_RANKING_CONFIG_INVALID_PPR', 'impactRanking.ppr', 'Expected an object.');
|
|
472
|
-
else {
|
|
473
|
-
if (ppr.enabled !== undefined && typeof ppr.enabled !== 'boolean') add('IMPACT_RANKING_CONFIG_INVALID_PPR', 'impactRanking.ppr.enabled', 'Expected a boolean.');
|
|
474
|
-
if (ppr.damping !== undefined && !isFiniteNumberInRange(ppr.damping, 0.01, 0.99)) add('IMPACT_RANKING_CONFIG_INVALID_PPR', 'impactRanking.ppr.damping', 'Expected a number greater than 0 and less than 1.');
|
|
475
|
-
if (ppr.iterations !== undefined && (!Number.isInteger(ppr.iterations) || ppr.iterations < 1 || ppr.iterations > 100)) add('IMPACT_RANKING_CONFIG_INVALID_PPR', 'impactRanking.ppr.iterations', 'Expected an integer from 1 to 100.');
|
|
476
|
-
if (ppr.tolerance !== undefined && !isFiniteNumberInRange(ppr.tolerance, 0, 1)) add('IMPACT_RANKING_CONFIG_INVALID_PPR', 'impactRanking.ppr.tolerance', 'Expected a number from 0 to 1.');
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
if (impactRanking.semantic !== undefined) {
|
|
480
|
-
const semantic = impactRanking.semantic;
|
|
481
|
-
if (!semantic || typeof semantic !== 'object' || Array.isArray(semantic)) add('IMPACT_RANKING_CONFIG_INVALID_SEMANTIC', 'impactRanking.semantic', 'Expected an object.');
|
|
482
|
-
else {
|
|
483
|
-
if (semantic.enabled !== undefined && typeof semantic.enabled !== 'boolean') add('IMPACT_RANKING_CONFIG_INVALID_SEMANTIC', 'impactRanking.semantic.enabled', 'Expected a boolean.');
|
|
484
|
-
if (semantic.threshold !== undefined && !isFiniteNumberInRange(semantic.threshold, 0, 1)) add('IMPACT_RANKING_CONFIG_INVALID_SEMANTIC', 'impactRanking.semantic.threshold', 'Expected a number from 0 to 1.');
|
|
485
|
-
if (semantic.topKPerFile !== undefined && (!Number.isInteger(semantic.topKPerFile) || semantic.topKPerFile < 0 || semantic.topKPerFile > 20)) add('IMPACT_RANKING_CONFIG_INVALID_SEMANTIC', 'impactRanking.semantic.topKPerFile', 'Expected an integer from 0 to 20.');
|
|
486
|
-
if (semantic.includeArchiveBodies !== undefined && typeof semantic.includeArchiveBodies !== 'boolean') add('IMPACT_RANKING_CONFIG_INVALID_SEMANTIC', 'impactRanking.semantic.includeArchiveBodies', 'Expected a boolean.');
|
|
487
|
-
if (semantic.signals !== undefined && (!Array.isArray(semantic.signals) || semantic.signals.some((value) => !SEMANTIC_SIGNAL_KEYS.has(value)))) add('IMPACT_RANKING_CONFIG_INVALID_SEMANTIC', 'impactRanking.semantic.signals', 'Expected an array of known deterministic signal names.');
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
441
|
const areas = data.memory?.areas;
|
|
493
442
|
if (areas === undefined) return errors;
|
|
494
443
|
if (!Array.isArray(areas)) {
|
|
@@ -544,10 +493,9 @@ export function readMemoryConfig(root = '.') {
|
|
|
544
493
|
const validation = data.validation === undefined ? cloneValidationPolicy() : normalizeValidationPolicy(data.validation);
|
|
545
494
|
const impactRanking = normalizeImpactRankingPolicy(data.impactRanking);
|
|
546
495
|
const referenceExpansion = normalizeReferenceExpansionPolicy(data.referenceExpansion);
|
|
547
|
-
const integrations = normalizeIntegrationsPolicy(data.integrations);
|
|
548
496
|
const load = normalizeLoadPolicy(data.load);
|
|
549
497
|
const planMode = normalizePlanModePolicy(data.planMode);
|
|
550
|
-
return configuredAreas.length > 0 ? { source: MEMORY_CONFIG_FILE, areas: configuredAreas, traceability, validation, impactRanking, referenceExpansion,
|
|
498
|
+
return configuredAreas.length > 0 ? { source: MEMORY_CONFIG_FILE, areas: configuredAreas, traceability, validation, impactRanking, referenceExpansion, load, planMode, errors: [] } : { ...defaultMemoryConfig(), traceability, validation, impactRanking, referenceExpansion, load, planMode, source: MEMORY_CONFIG_FILE, errors: [] };
|
|
551
499
|
} catch (error) {
|
|
552
500
|
return { ...defaultMemoryConfig(), source: MEMORY_CONFIG_FILE, errors: [{ file: MEMORY_CONFIG_FILE, code: 'MEMORY_CONFIG_INVALID_JSON', message: `Invalid JSON: ${error instanceof Error ? error.message : String(error)}\nFix: repair ${MEMORY_CONFIG_FILE} so it is valid JSON, or remove it before regenerating the default config with \`dotdotgod config init <root>\`.` }] };
|
|
553
501
|
}
|
|
@@ -575,9 +523,8 @@ export function defaultDotdotgodConfigData() {
|
|
|
575
523
|
},
|
|
576
524
|
traceability: cloneTraceabilityPolicy(config.traceability),
|
|
577
525
|
validation: cloneValidationPolicy(config.validation),
|
|
578
|
-
impactRanking:
|
|
526
|
+
impactRanking: { semantic: { ...config.impactRanking.semantic } },
|
|
579
527
|
referenceExpansion: { fuzzy: { lowSignal: { add: [], remove: [] } } },
|
|
580
|
-
integrations: cloneIntegrationsPolicy(config.integrations),
|
|
581
528
|
load: cloneLoadPolicy(config.load),
|
|
582
529
|
planMode: clonePlanModePolicy(config.planMode),
|
|
583
530
|
};
|
|
@@ -603,9 +550,16 @@ export function memoryConfigSummary(config) {
|
|
|
603
550
|
}, area)),
|
|
604
551
|
traceability: cloneTraceabilityPolicy(config.traceability ?? DEFAULT_TRACEABILITY_POLICY),
|
|
605
552
|
validation: cloneValidationPolicy(config.validation ?? DEFAULT_VALIDATION_POLICY),
|
|
606
|
-
impactRanking:
|
|
553
|
+
impactRanking: {
|
|
554
|
+
fixed: {
|
|
555
|
+
connectionCap: DEFAULT_IMPACT_RANKING_POLICY.connectionCap,
|
|
556
|
+
memoryCap: DEFAULT_IMPACT_RANKING_POLICY.memoryCap,
|
|
557
|
+
ppr: { ...DEFAULT_IMPACT_RANKING_POLICY.ppr },
|
|
558
|
+
relationWeights: { ...DEFAULT_IMPACT_RANKING_POLICY.relationWeights },
|
|
559
|
+
},
|
|
560
|
+
semantic: { ...cloneImpactRankingPolicy(config.impactRanking ?? DEFAULT_IMPACT_RANKING_POLICY).semantic },
|
|
561
|
+
},
|
|
607
562
|
referenceExpansion: cloneReferenceExpansionPolicy(config.referenceExpansion),
|
|
608
|
-
integrations: cloneIntegrationsPolicy(config.integrations),
|
|
609
563
|
load: cloneLoadPolicy(config.load),
|
|
610
564
|
planMode: clonePlanModePolicy(config.planMode),
|
|
611
565
|
};
|
package/src/memory/summary.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { existsSync,
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
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;
|
package/src/query/store.mjs
CHANGED
|
@@ -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,
|
package/src/validate/run.mjs
CHANGED
|
@@ -61,9 +61,17 @@ export function runValidate(argv) {
|
|
|
61
61
|
const maxChars = options.maxChars ?? validationPolicy.markdown.maxChars;
|
|
62
62
|
const markdownSizePrompt = (path) => {
|
|
63
63
|
const area = resolveMemoryArea(path, memoryConfig);
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
64
|
+
const clarify = area?.clarify;
|
|
65
|
+
const guidance = [
|
|
66
|
+
clarify?.documentType ? `document type: ${clarify.documentType}` : null,
|
|
67
|
+
clarify?.audience?.length ? `audience: ${clarify.audience.join(', ')}` : null,
|
|
68
|
+
clarify?.clarityGoal ? `clarity goal: ${clarify.clarityGoal}` : null,
|
|
69
|
+
clarify?.editRules?.length ? `edit rules: ${clarify.editRules.join('; ')}` : null,
|
|
70
|
+
].filter(Boolean);
|
|
71
|
+
const evidence = guidance.length > 0
|
|
72
|
+
? ` Follow this resolved clarification guidance: ${guidance.join('; ')}.`
|
|
73
|
+
: ' Preserve the document\'s current purpose and established meaning, using its content, links, and nearest README.md as evidence.';
|
|
74
|
+
return `Split ${path} into focused UPPER_SNAKE_CASE markdown files.${evidence} Keep each file focused and update the nearest README.md index with every new file's purpose.`;
|
|
67
75
|
};
|
|
68
76
|
for (const error of memoryConfig.errors ?? []) errors.push(error);
|
|
69
77
|
const FILENAME_QUALITY_AREAS = new Set(
|
|
@@ -73,18 +81,14 @@ export function runValidate(argv) {
|
|
|
73
81
|
.map(p => { const m = p.match(/^docs\/([^/*]+)\/\*\*$/); return m ? m[1] : null; })
|
|
74
82
|
.filter(Boolean)
|
|
75
83
|
);
|
|
84
|
+
const filenameQualityScope = [...FILENAME_QUALITY_AREAS].sort().map(area => `docs/${area}`).join(', ') || 'configured shared documentation areas';
|
|
76
85
|
const FILENAME_QUALITY_PROMPT =
|
|
77
|
-
'
|
|
78
|
-
|
|
79
|
-
'
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
'Avoid - numbered splits: API_1.md, API_2.md, API_3_1_*.md, 01_*.md, *_01.md\n' +
|
|
84
|
-
'Avoid - overly abstract: COMMON.md, OVERVIEW.md, DETAILS.md, NOTES.md, MISC.md, PART_1.md, SECTION_1.md\n' +
|
|
85
|
-
'Acceptable exceptions: README.md (directory index), files with clear HTTP status code semantics (e.g. RESPONSE_200_OK.md), ' +
|
|
86
|
-
'files in the project validation.markdown.filename.allow allowlist.\n' +
|
|
87
|
-
'Good renames: API_1.md → BIZ_RESERVATIONS_SCHEDULE.md, COMMON.md → BIZ_AUTH_ERROR_CONVENTIONS.md, OVERVIEW.md → REGISTRATION_HANDOFF_SUMMARY.md';
|
|
86
|
+
'Filenames and paths are LLM/agent context signals used for impact ranking, search, and retrieval.\n' +
|
|
87
|
+
`Use meaning-based filenames in ${filenameQualityScope}. A filename should identify its primary endpoint, screen, policy, state, response type, or domain subject. ` +
|
|
88
|
+
'Derive split-document names from headings, API paths, and established domain terms.\n' +
|
|
89
|
+
'Rename numbered or abstract series such as API_1.md, PART_1.md, COMMON.md, or DETAILS.md to subject-specific names. ' +
|
|
90
|
+
'Examples: API_1.md → BIZ_RESERVATIONS_SCHEDULE.md; COMMON.md → BIZ_AUTH_ERROR_CONVENTIONS.md.\n' +
|
|
91
|
+
'README.md directory indexes, clear HTTP-status filenames such as RESPONSE_200_OK.md, and configured filename allowlist entries remain valid.';
|
|
88
92
|
walk(docs);
|
|
89
93
|
const dirSiblingMap = new Map();
|
|
90
94
|
for (const f of markdownFiles) {
|
|
@@ -131,7 +135,7 @@ export function runValidate(argv) {
|
|
|
131
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);
|
|
132
136
|
else {
|
|
133
137
|
for (const error of validateTraceabilityBlock(block.data, root, file, block.line, memoryConfig)) errors.push(error);
|
|
134
|
-
const synced = syncTraceabilityLinksInContent(content, block.data, root, file);
|
|
138
|
+
const synced = syncTraceabilityLinksInContent(content, block.data, root, file, memoryConfig);
|
|
135
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.');
|
|
136
140
|
}
|
|
137
141
|
}
|