@tekyzinc/gsd-t 2.50.12 → 2.53.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +379 -372
  3. package/bin/component-registry.js +250 -0
  4. package/bin/graph-cgc.js +510 -510
  5. package/bin/graph-indexer.js +147 -147
  6. package/bin/graph-overlay.js +195 -195
  7. package/bin/graph-parsers.js +327 -327
  8. package/bin/graph-query.js +453 -452
  9. package/bin/graph-store.js +154 -154
  10. package/bin/qa-calibrator.js +194 -0
  11. package/bin/scan-data-collector.js +153 -153
  12. package/bin/scan-diagrams-generators.js +187 -187
  13. package/bin/scan-diagrams.js +79 -79
  14. package/bin/scan-renderer.js +92 -92
  15. package/bin/scan-report-sections.js +121 -121
  16. package/bin/scan-report.js +184 -184
  17. package/bin/scan-schema-parsers.js +199 -199
  18. package/bin/scan-schema.js +103 -103
  19. package/bin/token-budget.js +246 -0
  20. package/commands/Claude-md.md +10 -10
  21. package/commands/branch.md +15 -15
  22. package/commands/checkin.md +45 -45
  23. package/commands/global-change.md +209 -209
  24. package/commands/gsd-t-audit.md +199 -0
  25. package/commands/gsd-t-backlog-add.md +94 -94
  26. package/commands/gsd-t-backlog-edit.md +111 -111
  27. package/commands/gsd-t-backlog-list.md +63 -63
  28. package/commands/gsd-t-backlog-move.md +94 -94
  29. package/commands/gsd-t-backlog-promote.md +123 -123
  30. package/commands/gsd-t-backlog-remove.md +86 -86
  31. package/commands/gsd-t-backlog-settings.md +158 -158
  32. package/commands/gsd-t-complete-milestone.md +528 -515
  33. package/commands/gsd-t-debug.md +506 -399
  34. package/commands/gsd-t-discuss.md +174 -174
  35. package/commands/gsd-t-execute.md +758 -634
  36. package/commands/gsd-t-feature.md +276 -276
  37. package/commands/gsd-t-health.md +142 -142
  38. package/commands/gsd-t-help.md +465 -457
  39. package/commands/gsd-t-impact.md +302 -302
  40. package/commands/gsd-t-init.md +320 -280
  41. package/commands/gsd-t-integrate.md +365 -249
  42. package/commands/gsd-t-milestone.md +87 -87
  43. package/commands/gsd-t-partition.md +442 -361
  44. package/commands/gsd-t-pause.md +82 -82
  45. package/commands/gsd-t-plan.md +345 -344
  46. package/commands/gsd-t-populate.md +111 -111
  47. package/commands/gsd-t-prd.md +326 -326
  48. package/commands/gsd-t-project.md +211 -211
  49. package/commands/gsd-t-promote-debt.md +123 -123
  50. package/commands/gsd-t-prompt.md +137 -137
  51. package/commands/gsd-t-qa.md +266 -266
  52. package/commands/gsd-t-quick.md +357 -234
  53. package/commands/gsd-t-reflect.md +134 -134
  54. package/commands/gsd-t-resume.md +72 -72
  55. package/commands/gsd-t-scan.md +615 -615
  56. package/commands/gsd-t-setup.md +76 -0
  57. package/commands/gsd-t-status.md +192 -166
  58. package/commands/gsd-t-test-sync.md +381 -381
  59. package/commands/gsd-t-triage-and-merge.md +171 -171
  60. package/commands/gsd-t-verify.md +382 -382
  61. package/commands/gsd-t-visualize.md +118 -118
  62. package/commands/gsd-t-wave.md +401 -378
  63. package/docs/GSD-T-README.md +425 -422
  64. package/docs/architecture.md +385 -369
  65. package/docs/harness-design-analysis.md +371 -0
  66. package/docs/infrastructure.md +205 -205
  67. package/docs/prd-graph-engine.md +398 -398
  68. package/docs/prd-gsd2-hybrid.md +559 -559
  69. package/docs/prd-harness-evolution.md +583 -0
  70. package/docs/requirements.md +14 -0
  71. package/docs/workflows.md +226 -226
  72. package/examples/.gsd-t/domains/example-domain/scope.md +13 -13
  73. package/package.json +40 -40
  74. package/scripts/gsd-t-auto-route.js +39 -39
  75. package/scripts/gsd-t-dashboard-mockup.html +1143 -1143
  76. package/scripts/gsd-t-dashboard-server.js +171 -171
  77. package/scripts/gsd-t-dashboard.html +262 -262
  78. package/scripts/gsd-t-event-writer.js +128 -128
  79. package/scripts/gsd-t-statusline.js +94 -94
  80. package/scripts/gsd-t-tools.js +175 -175
  81. package/templates/CLAUDE-global.md +639 -614
  82. package/templates/CLAUDE-project.md +24 -0
  83. package/templates/backlog-settings.md +18 -18
  84. package/templates/backlog.md +1 -1
  85. package/templates/progress.md +40 -40
  86. package/templates/shared-services-contract.md +60 -60
  87. package/templates/stacks/desktop.ini +2 -2
  88. package/bin/desktop.ini +0 -2
  89. package/commands/desktop.ini +0 -2
  90. package/docs/ci-examples/desktop.ini +0 -2
  91. package/docs/desktop.ini +0 -2
  92. package/examples/.gsd-t/contracts/desktop.ini +0 -2
  93. package/examples/.gsd-t/desktop.ini +0 -2
  94. package/examples/.gsd-t/domains/desktop.ini +0 -2
  95. package/examples/.gsd-t/domains/example-domain/desktop.ini +0 -2
  96. package/examples/desktop.ini +0 -2
  97. package/examples/rules/desktop.ini +0 -2
  98. package/scripts/desktop.ini +0 -2
  99. package/templates/desktop.ini +0 -2
@@ -1,452 +1,453 @@
1
- 'use strict';
2
- const store = require('./graph-store');
3
- const { indexProject } = require('./graph-indexer');
4
- const { cgcProvider, cgcQuery } = require('./graph-cgc');
5
-
6
- /**
7
- * Graph Abstraction Layer — unified query interface.
8
- * Routes queries to best available provider: CGC → native → grep.
9
- * Commands call query() and never interact with providers directly.
10
- */
11
-
12
- const providers = [];
13
- let sessionProvider = null; // cached per session
14
- let _lastFreshnessCheck = 0; // timestamp of last staleness check
15
-
16
- function registerProvider(provider) {
17
- providers.push(provider);
18
- providers.sort((a, b) => a.priority - b.priority);
19
- }
20
-
21
- function getProviders() {
22
- return [...providers];
23
- }
24
-
25
- function selectProvider() {
26
- if (sessionProvider) return sessionProvider;
27
- for (const p of providers) {
28
- if (p.available()) {
29
- sessionProvider = p;
30
- return p;
31
- }
32
- }
33
- return null;
34
- }
35
-
36
- function resetSession() {
37
- sessionProvider = null;
38
- }
39
-
40
- // --- Native provider ---
41
-
42
- function nativeAvailable(projectRoot) {
43
- const meta = store.readMeta(projectRoot);
44
- return meta !== null && meta.entityCount > 0;
45
- }
46
-
47
- function nativeQuery(type, params, projectRoot) {
48
- const idx = store.readIndex(projectRoot);
49
- const calls = store.readCalls(projectRoot);
50
- const imps = store.readImports(projectRoot);
51
- const contracts = store.readContracts(projectRoot);
52
- const requirements = store.readRequirements(projectRoot);
53
- const tests = store.readTests(projectRoot);
54
- const surfaces = store.readSurfaces(projectRoot);
55
-
56
- switch (type) {
57
- case 'getEntity': {
58
- return idx.entities.find(e =>
59
- e.name === params.name &&
60
- (!params.file || e.file === params.file)
61
- ) || null;
62
- }
63
-
64
- case 'getEntities': {
65
- return idx.entities.filter(e => e.file === params.file);
66
- }
67
-
68
- case 'getEntitiesByDomain': {
69
- return idx.entities.filter(
70
- e => e.domain === params.domain
71
- );
72
- }
73
-
74
- case 'getCallers': {
75
- const edges = calls.edges.filter(
76
- e => e.callee === params.entity ||
77
- e.callee.endsWith(':' + params.entity)
78
- );
79
- return edges.map(e => {
80
- const caller = idx.entities.find(
81
- ent => ent.id === e.caller
82
- );
83
- return caller || { id: e.caller, name: e.caller };
84
- });
85
- }
86
-
87
- case 'getCallees': {
88
- const edges = calls.edges.filter(
89
- e => e.caller === params.entity ||
90
- e.caller.endsWith(':' + params.entity)
91
- );
92
- return edges.map(e => {
93
- const callee = idx.entities.find(
94
- ent => ent.id === e.callee
95
- );
96
- return callee || { id: e.callee, name: e.callee };
97
- });
98
- }
99
-
100
- case 'getTransitiveCallers':
101
- case 'getTransitiveCallees': {
102
- // Native: 1-level only (CGC enhances to N-level)
103
- const direction = type === 'getTransitiveCallers'
104
- ? 'getCallers' : 'getCallees';
105
- return nativeQuery(direction, params, projectRoot);
106
- }
107
-
108
- case 'getImports': {
109
- return imps.edges.filter(e => e.source === params.file);
110
- }
111
-
112
- case 'getImporters': {
113
- return imps.edges.filter(e => e.target === params.file ||
114
- e.target.endsWith('/' + params.file));
115
- }
116
-
117
- case 'getDomainOwner': {
118
- const entity = typeof params.entity === 'string'
119
- ? idx.entities.find(e => e.id === params.entity ||
120
- e.name === params.entity)
121
- : params.entity;
122
- return entity ? entity.domain : null;
123
- }
124
-
125
- case 'getContractFor': {
126
- const m = contracts.mappings.find(
127
- c => c.entity === params.entity ||
128
- c.entity.endsWith(':' + params.entity)
129
- );
130
- return m ? m.contract : null;
131
- }
132
-
133
- case 'getRequirementFor': {
134
- const m = requirements.mappings.find(
135
- r => r.entity === params.entity ||
136
- r.entity.endsWith(':' + params.entity)
137
- );
138
- return m ? m.requirement : null;
139
- }
140
-
141
- case 'getTestsFor': {
142
- return tests.mappings.filter(
143
- t => t.entity === params.entity ||
144
- t.entity.endsWith(':' + params.entity)
145
- );
146
- }
147
-
148
- case 'getDebtFor': {
149
- return []; // debt mapping is basic
150
- }
151
-
152
- case 'getSurfaceConsumers': {
153
- const m = surfaces.mappings.find(
154
- s => s.entity === params.entity ||
155
- s.entity.endsWith(':' + params.entity)
156
- );
157
- return m ? m.surfaces : [];
158
- }
159
-
160
- case 'findDuplicates': {
161
- // Native: name-based only (CGC adds AST comparison)
162
- const names = {};
163
- for (const e of idx.entities) {
164
- if (!names[e.name]) names[e.name] = [];
165
- names[e.name].push(e);
166
- }
167
- const dupes = [];
168
- for (const [name, ents] of Object.entries(names)) {
169
- if (ents.length > 1) {
170
- for (let i = 0; i < ents.length - 1; i++) {
171
- dupes.push({
172
- entityA: ents[i],
173
- entityB: ents[i + 1],
174
- similarity: 1.0
175
- });
176
- }
177
- }
178
- }
179
- return dupes;
180
- }
181
-
182
- case 'findDeadCode': {
183
- // Entities that are not called and not exported
184
- const calledIds = new Set(
185
- calls.edges.map(e => e.callee)
186
- );
187
- return idx.entities.filter(e =>
188
- !e.exported && !calledIds.has(e.id)
189
- );
190
- }
191
-
192
- case 'findCircularDeps': {
193
- // Native: import-level cycle detection
194
- const graph = {};
195
- for (const edge of imps.edges) {
196
- if (!graph[edge.source]) graph[edge.source] = [];
197
- graph[edge.source].push(edge.target);
198
- }
199
- const cycles = [];
200
- const visited = new Set();
201
- const stack = new Set();
202
-
203
- function dfs(node, pathArr) {
204
- if (stack.has(node)) {
205
- const cycleStart = pathArr.indexOf(node);
206
- if (cycleStart >= 0) {
207
- cycles.push({
208
- path: pathArr.slice(cycleStart),
209
- entities: []
210
- });
211
- }
212
- return;
213
- }
214
- if (visited.has(node)) return;
215
- visited.add(node);
216
- stack.add(node);
217
- pathArr.push(node);
218
- for (const neighbor of (graph[node] || [])) {
219
- dfs(neighbor, [...pathArr]);
220
- }
221
- stack.delete(node);
222
- }
223
-
224
- for (const node of Object.keys(graph)) {
225
- dfs(node, []);
226
- }
227
- return cycles;
228
- }
229
-
230
- case 'getDomainBoundaryViolations': {
231
- const violations = [];
232
- for (const edge of calls.edges) {
233
- const callerEnt = idx.entities.find(
234
- e => e.id === edge.caller
235
- );
236
- const calleeEnt = idx.entities.find(
237
- e => e.id === edge.callee
238
- );
239
- if (callerEnt && calleeEnt &&
240
- callerEnt.domain && calleeEnt.domain &&
241
- callerEnt.domain !== calleeEnt.domain) {
242
- violations.push({
243
- entity: calleeEnt,
244
- ownerDomain: calleeEnt.domain,
245
- accessedBy: callerEnt,
246
- accessorDomain: callerEnt.domain
247
- });
248
- }
249
- }
250
- return violations;
251
- }
252
-
253
- case 'getProvider': {
254
- const p = selectProvider();
255
- return p ? p.name : 'grep';
256
- }
257
-
258
- case 'getIndexStatus': {
259
- const meta = store.readMeta(projectRoot);
260
- return {
261
- provider: meta ? meta.provider : 'none',
262
- indexed: meta !== null,
263
- entityCount: meta ? meta.entityCount : 0,
264
- lastIndexed: meta ? meta.lastIndexed : null,
265
- stale: false,
266
- stalePaths: []
267
- };
268
- }
269
-
270
- case 'reindex': {
271
- return indexProject(projectRoot, {
272
- force: params.force || false
273
- });
274
- }
275
-
276
- default:
277
- return null;
278
- }
279
- }
280
-
281
- const nativeProvider = {
282
- name: 'native',
283
- priority: 2,
284
- _projectRoot: null,
285
- setProjectRoot(root) { this._projectRoot = root; },
286
- available() {
287
- return this._projectRoot
288
- ? nativeAvailable(this._projectRoot) : false;
289
- },
290
- query(type, params) {
291
- return nativeQuery(type, params, this._projectRoot);
292
- }
293
- };
294
-
295
- // --- Grep fallback provider ---
296
-
297
- const fs = require('fs');
298
- const path = require('path');
299
- const { execFileSync } = require('child_process');
300
-
301
- const SAFE_ENTITY_RE = /^[\w.\-/\\:]+$/;
302
-
303
- function grepQuery(type, params, projectRoot) {
304
- switch (type) {
305
- case 'getCallers': {
306
- const name = params.entity;
307
- if (!name || !SAFE_ENTITY_RE.test(name)) return [];
308
- try {
309
- const out = execFileSync('grep', ['-rn', name + '(', '--include=*.js', '--include=*.ts', '--include=*.py', projectRoot], { encoding: 'utf8', timeout: 5000 });
310
- return out.split('\n').filter(Boolean).map(line => {
311
- const [file] = line.split(':');
312
- return {
313
- id: file,
314
- name: 'grep_result',
315
- file: path.relative(projectRoot, file)
316
- };
317
- });
318
- } catch { return []; }
319
- }
320
-
321
- case 'getImporters': {
322
- const name = params.file || params.entity;
323
- if (!name || !SAFE_ENTITY_RE.test(name)) return [];
324
- try {
325
- const out = execFileSync('grep', ['-rn', '-e', 'import.*' + name, '-e', 'require.*' + name, '--include=*.js', '--include=*.ts', projectRoot], { encoding: 'utf8', timeout: 5000 });
326
- return out.split('\n').filter(Boolean).map(line => ({
327
- source: line.split(':')[0],
328
- target: name,
329
- names: [],
330
- line: parseInt(line.split(':')[1]) || 0
331
- }));
332
- } catch { return []; }
333
- }
334
-
335
- case 'getProvider':
336
- return 'grep';
337
-
338
- case 'getIndexStatus':
339
- return {
340
- provider: 'grep',
341
- indexed: false,
342
- entityCount: 0,
343
- lastIndexed: null,
344
- stale: true,
345
- stalePaths: []
346
- };
347
-
348
- default:
349
- return null;
350
- }
351
- }
352
-
353
- const grepProvider = {
354
- name: 'grep',
355
- priority: 3,
356
- _projectRoot: null,
357
- setProjectRoot(root) { this._projectRoot = root; },
358
- available() { return true; }, // always available
359
- query(type, params) {
360
- return grepQuery(type, params, this._projectRoot);
361
- }
362
- };
363
-
364
- // --- CGC Sync (Phase 2: keep Neo4j in sync at command boundary) ---
365
-
366
- function _syncCgc(projectRoot) {
367
- if (!cgcProvider.available()) return;
368
- // Use CLI instead of MCP tool call — add_code_to_graph MCP is broken
369
- // on Windows in CGC 0.3.1 (directory param arrives as None)
370
- const { execFileSync } = require('child_process');
371
- const cgcEnv = { ...process.env, PYTHONIOENCODING: 'utf-8' };
372
- const cgcOpts = { timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'], env: cgcEnv };
373
-
374
- // Attempt 1: normal sync
375
- try {
376
- execFileSync('cgc', ['index', projectRoot], cgcOpts);
377
- return;
378
- } catch (err1) {
379
- const msg1 = err1.stderr ? err1.stderr.toString() : err1.message;
380
- // Attempt 2: force re-index to recover from corrupt state
381
- try {
382
- execFileSync('cgc', ['index', projectRoot, '--force'], cgcOpts);
383
- process.stderr.write(
384
- '[GSD-T] CGC sync recovered via force re-index for ' +
385
- projectRoot + '\n'
386
- );
387
- return;
388
- } catch (err2) {
389
- const msg2 = err2.stderr ? err2.stderr.toString() : err2.message;
390
- // Both attempts failed warn the user clearly
391
- process.stderr.write(
392
- '[GSD-T] ⚠ CGC sync FAILED for ' + projectRoot + '\n' +
393
- ' Error: ' + (msg2 || msg1).split('\n')[0] + '\n' +
394
- ' Impact: Neo4j graph is stale deep call chain analysis ' +
395
- 'may return outdated results\n' +
396
- ' Fix: run "cgc index ' + projectRoot + ' --force" manually\n'
397
- );
398
- }
399
- }
400
- }
401
-
402
- // --- Main query function ---
403
-
404
- function query(type, params, projectRoot) {
405
- // Set project root on providers
406
- nativeProvider.setProjectRoot(projectRoot);
407
- grepProvider.setProjectRoot(projectRoot);
408
-
409
- // Auto-trigger reindex if stale (command-boundary freshness check)
410
- if (type !== 'reindex' && type !== 'getIndexStatus' &&
411
- type !== 'getProvider') {
412
- const now = Date.now();
413
- if (now - _lastFreshnessCheck > 500) {
414
- _lastFreshnessCheck = now;
415
- const meta = store.readMeta(projectRoot);
416
- if (!meta) {
417
- // No index exists — run initial index
418
- indexProject(projectRoot);
419
- _syncCgc(projectRoot);
420
- } else {
421
- // Check staleness — reindex if any files changed
422
- const result = indexProject(projectRoot);
423
- if (result.filesProcessed > 0) {
424
- _syncCgc(projectRoot);
425
- }
426
- }
427
- }
428
- }
429
-
430
- // Try providers in priority order
431
- for (const p of providers) {
432
- if (!p.available()) continue;
433
- if (p._projectRoot !== undefined) {
434
- p.setProjectRoot(projectRoot);
435
- }
436
- const result = p.query(type, params, projectRoot);
437
- if (result !== null && result !== undefined) return result;
438
- }
439
-
440
- return null;
441
- }
442
-
443
- // Register default providers
444
- registerProvider(cgcProvider);
445
- registerProvider(nativeProvider);
446
- registerProvider(grepProvider);
447
-
448
- module.exports = {
449
- query, registerProvider, getProviders,
450
- selectProvider, resetSession,
451
- nativeProvider, grepProvider
452
- };
1
+ 'use strict';
2
+ const store = require('./graph-store');
3
+ const { indexProject } = require('./graph-indexer');
4
+ const { cgcProvider, cgcQuery } = require('./graph-cgc');
5
+
6
+ /**
7
+ * Graph Abstraction Layer — unified query interface.
8
+ * Routes queries to best available provider: CGC → native → grep.
9
+ * Commands call query() and never interact with providers directly.
10
+ */
11
+
12
+ const providers = [];
13
+ let sessionProvider = null; // cached per session
14
+ let _lastFreshnessCheck = 0; // timestamp of last staleness check
15
+
16
+ function registerProvider(provider) {
17
+ providers.push(provider);
18
+ providers.sort((a, b) => a.priority - b.priority);
19
+ }
20
+
21
+ function getProviders() {
22
+ return [...providers];
23
+ }
24
+
25
+ function selectProvider() {
26
+ if (sessionProvider) return sessionProvider;
27
+ for (const p of providers) {
28
+ if (p.available()) {
29
+ sessionProvider = p;
30
+ return p;
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+
36
+ function resetSession() {
37
+ sessionProvider = null;
38
+ _lastFreshnessCheck = 0;
39
+ }
40
+
41
+ // --- Native provider ---
42
+
43
+ function nativeAvailable(projectRoot) {
44
+ const meta = store.readMeta(projectRoot);
45
+ return meta !== null && meta.entityCount > 0;
46
+ }
47
+
48
+ function nativeQuery(type, params, projectRoot) {
49
+ const idx = store.readIndex(projectRoot);
50
+ const calls = store.readCalls(projectRoot);
51
+ const imps = store.readImports(projectRoot);
52
+ const contracts = store.readContracts(projectRoot);
53
+ const requirements = store.readRequirements(projectRoot);
54
+ const tests = store.readTests(projectRoot);
55
+ const surfaces = store.readSurfaces(projectRoot);
56
+
57
+ switch (type) {
58
+ case 'getEntity': {
59
+ return idx.entities.find(e =>
60
+ e.name === params.name &&
61
+ (!params.file || e.file === params.file)
62
+ ) || null;
63
+ }
64
+
65
+ case 'getEntities': {
66
+ return idx.entities.filter(e => e.file === params.file);
67
+ }
68
+
69
+ case 'getEntitiesByDomain': {
70
+ return idx.entities.filter(
71
+ e => e.domain === params.domain
72
+ );
73
+ }
74
+
75
+ case 'getCallers': {
76
+ const edges = calls.edges.filter(
77
+ e => e.callee === params.entity ||
78
+ e.callee.endsWith(':' + params.entity)
79
+ );
80
+ return edges.map(e => {
81
+ const caller = idx.entities.find(
82
+ ent => ent.id === e.caller
83
+ );
84
+ return caller || { id: e.caller, name: e.caller };
85
+ });
86
+ }
87
+
88
+ case 'getCallees': {
89
+ const edges = calls.edges.filter(
90
+ e => e.caller === params.entity ||
91
+ e.caller.endsWith(':' + params.entity)
92
+ );
93
+ return edges.map(e => {
94
+ const callee = idx.entities.find(
95
+ ent => ent.id === e.callee
96
+ );
97
+ return callee || { id: e.callee, name: e.callee };
98
+ });
99
+ }
100
+
101
+ case 'getTransitiveCallers':
102
+ case 'getTransitiveCallees': {
103
+ // Native: 1-level only (CGC enhances to N-level)
104
+ const direction = type === 'getTransitiveCallers'
105
+ ? 'getCallers' : 'getCallees';
106
+ return nativeQuery(direction, params, projectRoot);
107
+ }
108
+
109
+ case 'getImports': {
110
+ return imps.edges.filter(e => e.source === params.file);
111
+ }
112
+
113
+ case 'getImporters': {
114
+ return imps.edges.filter(e => e.target === params.file ||
115
+ e.target.endsWith('/' + params.file));
116
+ }
117
+
118
+ case 'getDomainOwner': {
119
+ const entity = typeof params.entity === 'string'
120
+ ? idx.entities.find(e => e.id === params.entity ||
121
+ e.name === params.entity)
122
+ : params.entity;
123
+ return entity ? entity.domain : null;
124
+ }
125
+
126
+ case 'getContractFor': {
127
+ const m = contracts.mappings.find(
128
+ c => c.entity === params.entity ||
129
+ c.entity.endsWith(':' + params.entity)
130
+ );
131
+ return m ? m.contract : null;
132
+ }
133
+
134
+ case 'getRequirementFor': {
135
+ const m = requirements.mappings.find(
136
+ r => r.entity === params.entity ||
137
+ r.entity.endsWith(':' + params.entity)
138
+ );
139
+ return m ? m.requirement : null;
140
+ }
141
+
142
+ case 'getTestsFor': {
143
+ return tests.mappings.filter(
144
+ t => t.entity === params.entity ||
145
+ t.entity.endsWith(':' + params.entity)
146
+ );
147
+ }
148
+
149
+ case 'getDebtFor': {
150
+ return []; // debt mapping is basic
151
+ }
152
+
153
+ case 'getSurfaceConsumers': {
154
+ const m = surfaces.mappings.find(
155
+ s => s.entity === params.entity ||
156
+ s.entity.endsWith(':' + params.entity)
157
+ );
158
+ return m ? m.surfaces : [];
159
+ }
160
+
161
+ case 'findDuplicates': {
162
+ // Native: name-based only (CGC adds AST comparison)
163
+ const names = {};
164
+ for (const e of idx.entities) {
165
+ if (!names[e.name]) names[e.name] = [];
166
+ names[e.name].push(e);
167
+ }
168
+ const dupes = [];
169
+ for (const [name, ents] of Object.entries(names)) {
170
+ if (ents.length > 1) {
171
+ for (let i = 0; i < ents.length - 1; i++) {
172
+ dupes.push({
173
+ entityA: ents[i],
174
+ entityB: ents[i + 1],
175
+ similarity: 1.0
176
+ });
177
+ }
178
+ }
179
+ }
180
+ return dupes;
181
+ }
182
+
183
+ case 'findDeadCode': {
184
+ // Entities that are not called and not exported
185
+ const calledIds = new Set(
186
+ calls.edges.map(e => e.callee)
187
+ );
188
+ return idx.entities.filter(e =>
189
+ !e.exported && !calledIds.has(e.id)
190
+ );
191
+ }
192
+
193
+ case 'findCircularDeps': {
194
+ // Native: import-level cycle detection
195
+ const graph = {};
196
+ for (const edge of imps.edges) {
197
+ if (!graph[edge.source]) graph[edge.source] = [];
198
+ graph[edge.source].push(edge.target);
199
+ }
200
+ const cycles = [];
201
+ const visited = new Set();
202
+ const stack = new Set();
203
+
204
+ function dfs(node, pathArr) {
205
+ if (stack.has(node)) {
206
+ const cycleStart = pathArr.indexOf(node);
207
+ if (cycleStart >= 0) {
208
+ cycles.push({
209
+ path: pathArr.slice(cycleStart),
210
+ entities: []
211
+ });
212
+ }
213
+ return;
214
+ }
215
+ if (visited.has(node)) return;
216
+ visited.add(node);
217
+ stack.add(node);
218
+ pathArr.push(node);
219
+ for (const neighbor of (graph[node] || [])) {
220
+ dfs(neighbor, [...pathArr]);
221
+ }
222
+ stack.delete(node);
223
+ }
224
+
225
+ for (const node of Object.keys(graph)) {
226
+ dfs(node, []);
227
+ }
228
+ return cycles;
229
+ }
230
+
231
+ case 'getDomainBoundaryViolations': {
232
+ const violations = [];
233
+ for (const edge of calls.edges) {
234
+ const callerEnt = idx.entities.find(
235
+ e => e.id === edge.caller
236
+ );
237
+ const calleeEnt = idx.entities.find(
238
+ e => e.id === edge.callee
239
+ );
240
+ if (callerEnt && calleeEnt &&
241
+ callerEnt.domain && calleeEnt.domain &&
242
+ callerEnt.domain !== calleeEnt.domain) {
243
+ violations.push({
244
+ entity: calleeEnt,
245
+ ownerDomain: calleeEnt.domain,
246
+ accessedBy: callerEnt,
247
+ accessorDomain: callerEnt.domain
248
+ });
249
+ }
250
+ }
251
+ return violations;
252
+ }
253
+
254
+ case 'getProvider': {
255
+ const p = selectProvider();
256
+ return p ? p.name : 'grep';
257
+ }
258
+
259
+ case 'getIndexStatus': {
260
+ const meta = store.readMeta(projectRoot);
261
+ return {
262
+ provider: meta ? meta.provider : 'none',
263
+ indexed: meta !== null,
264
+ entityCount: meta ? meta.entityCount : 0,
265
+ lastIndexed: meta ? meta.lastIndexed : null,
266
+ stale: false,
267
+ stalePaths: []
268
+ };
269
+ }
270
+
271
+ case 'reindex': {
272
+ return indexProject(projectRoot, {
273
+ force: params.force || false
274
+ });
275
+ }
276
+
277
+ default:
278
+ return null;
279
+ }
280
+ }
281
+
282
+ const nativeProvider = {
283
+ name: 'native',
284
+ priority: 2,
285
+ _projectRoot: null,
286
+ setProjectRoot(root) { this._projectRoot = root; },
287
+ available() {
288
+ return this._projectRoot
289
+ ? nativeAvailable(this._projectRoot) : false;
290
+ },
291
+ query(type, params) {
292
+ return nativeQuery(type, params, this._projectRoot);
293
+ }
294
+ };
295
+
296
+ // --- Grep fallback provider ---
297
+
298
+ const fs = require('fs');
299
+ const path = require('path');
300
+ const { execFileSync } = require('child_process');
301
+
302
+ const SAFE_ENTITY_RE = /^[\w.\-/\\:]+$/;
303
+
304
+ function grepQuery(type, params, projectRoot) {
305
+ switch (type) {
306
+ case 'getCallers': {
307
+ const name = params.entity;
308
+ if (!name || !SAFE_ENTITY_RE.test(name)) return [];
309
+ try {
310
+ const out = execFileSync('grep', ['-rn', name + '(', '--include=*.js', '--include=*.ts', '--include=*.py', projectRoot], { encoding: 'utf8', timeout: 5000 });
311
+ return out.split('\n').filter(Boolean).map(line => {
312
+ const [file] = line.split(':');
313
+ return {
314
+ id: file,
315
+ name: 'grep_result',
316
+ file: path.relative(projectRoot, file)
317
+ };
318
+ });
319
+ } catch { return []; }
320
+ }
321
+
322
+ case 'getImporters': {
323
+ const name = params.file || params.entity;
324
+ if (!name || !SAFE_ENTITY_RE.test(name)) return [];
325
+ try {
326
+ const out = execFileSync('grep', ['-rn', '-e', 'import.*' + name, '-e', 'require.*' + name, '--include=*.js', '--include=*.ts', projectRoot], { encoding: 'utf8', timeout: 5000 });
327
+ return out.split('\n').filter(Boolean).map(line => ({
328
+ source: line.split(':')[0],
329
+ target: name,
330
+ names: [],
331
+ line: parseInt(line.split(':')[1]) || 0
332
+ }));
333
+ } catch { return []; }
334
+ }
335
+
336
+ case 'getProvider':
337
+ return 'grep';
338
+
339
+ case 'getIndexStatus':
340
+ return {
341
+ provider: 'grep',
342
+ indexed: false,
343
+ entityCount: 0,
344
+ lastIndexed: null,
345
+ stale: true,
346
+ stalePaths: []
347
+ };
348
+
349
+ default:
350
+ return null;
351
+ }
352
+ }
353
+
354
+ const grepProvider = {
355
+ name: 'grep',
356
+ priority: 3,
357
+ _projectRoot: null,
358
+ setProjectRoot(root) { this._projectRoot = root; },
359
+ available() { return true; }, // always available
360
+ query(type, params) {
361
+ return grepQuery(type, params, this._projectRoot);
362
+ }
363
+ };
364
+
365
+ // --- CGC Sync (Phase 2: keep Neo4j in sync at command boundary) ---
366
+
367
+ function _syncCgc(projectRoot) {
368
+ if (!cgcProvider.available()) return;
369
+ // Use CLI instead of MCP tool call add_code_to_graph MCP is broken
370
+ // on Windows in CGC 0.3.1 (directory param arrives as None)
371
+ const { execFileSync } = require('child_process');
372
+ const cgcEnv = { ...process.env, PYTHONIOENCODING: 'utf-8' };
373
+ const cgcOpts = { timeout: 30000, stdio: ['pipe', 'pipe', 'pipe'], env: cgcEnv };
374
+
375
+ // Attempt 1: normal sync
376
+ try {
377
+ execFileSync('cgc', ['index', projectRoot], cgcOpts);
378
+ return;
379
+ } catch (err1) {
380
+ const msg1 = err1.stderr ? err1.stderr.toString() : err1.message;
381
+ // Attempt 2: force re-index to recover from corrupt state
382
+ try {
383
+ execFileSync('cgc', ['index', projectRoot, '--force'], cgcOpts);
384
+ process.stderr.write(
385
+ '[GSD-T] CGC sync recovered via force re-index for ' +
386
+ projectRoot + '\n'
387
+ );
388
+ return;
389
+ } catch (err2) {
390
+ const msg2 = err2.stderr ? err2.stderr.toString() : err2.message;
391
+ // Both attempts failed — warn the user clearly
392
+ process.stderr.write(
393
+ '[GSD-T] CGC sync FAILED for ' + projectRoot + '\n' +
394
+ ' Error: ' + (msg2 || msg1).split('\n')[0] + '\n' +
395
+ ' Impact: Neo4j graph is stale — deep call chain analysis ' +
396
+ 'may return outdated results\n' +
397
+ ' Fix: run "cgc index ' + projectRoot + ' --force" manually\n'
398
+ );
399
+ }
400
+ }
401
+ }
402
+
403
+ // --- Main query function ---
404
+
405
+ function query(type, params, projectRoot) {
406
+ // Set project root on providers
407
+ nativeProvider.setProjectRoot(projectRoot);
408
+ grepProvider.setProjectRoot(projectRoot);
409
+
410
+ // Auto-trigger reindex if stale (command-boundary freshness check)
411
+ if (type !== 'reindex' && type !== 'getIndexStatus' &&
412
+ type !== 'getProvider') {
413
+ const now = Date.now();
414
+ if (now - _lastFreshnessCheck > 500) {
415
+ _lastFreshnessCheck = now;
416
+ const meta = store.readMeta(projectRoot);
417
+ if (!meta) {
418
+ // No index exists — run initial index
419
+ indexProject(projectRoot);
420
+ _syncCgc(projectRoot);
421
+ } else {
422
+ // Check staleness — reindex if any files changed
423
+ const result = indexProject(projectRoot);
424
+ if (result.filesProcessed > 0) {
425
+ _syncCgc(projectRoot);
426
+ }
427
+ }
428
+ }
429
+ }
430
+
431
+ // Try providers in priority order
432
+ for (const p of providers) {
433
+ if (!p.available()) continue;
434
+ if (p._projectRoot !== undefined) {
435
+ p.setProjectRoot(projectRoot);
436
+ }
437
+ const result = p.query(type, params, projectRoot);
438
+ if (result !== null && result !== undefined) return result;
439
+ }
440
+
441
+ return null;
442
+ }
443
+
444
+ // Register default providers
445
+ registerProvider(cgcProvider);
446
+ registerProvider(nativeProvider);
447
+ registerProvider(grepProvider);
448
+
449
+ module.exports = {
450
+ query, registerProvider, getProviders,
451
+ selectProvider, resetSession,
452
+ nativeProvider, grepProvider
453
+ };