@tekyzinc/gsd-t 2.51.10 → 2.53.11

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 (100) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +379 -373
  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 -482
  34. package/commands/gsd-t-discuss.md +174 -174
  35. package/commands/gsd-t-execute.md +758 -715
  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-scan-setup.md +1 -5
  41. package/commands/gsd-t-init.md +314 -280
  42. package/commands/gsd-t-integrate.md +365 -333
  43. package/commands/gsd-t-milestone.md +87 -87
  44. package/commands/gsd-t-partition.md +442 -361
  45. package/commands/gsd-t-pause.md +82 -82
  46. package/commands/gsd-t-plan.md +345 -344
  47. package/commands/gsd-t-populate.md +111 -111
  48. package/commands/gsd-t-prd.md +326 -326
  49. package/commands/gsd-t-project.md +211 -211
  50. package/commands/gsd-t-promote-debt.md +123 -123
  51. package/commands/gsd-t-prompt.md +137 -137
  52. package/commands/gsd-t-qa.md +266 -266
  53. package/commands/gsd-t-quick.md +357 -315
  54. package/commands/gsd-t-reflect.md +134 -134
  55. package/commands/gsd-t-resume.md +72 -72
  56. package/commands/gsd-t-scan.md +615 -615
  57. package/commands/gsd-t-setup.md +76 -0
  58. package/commands/gsd-t-status.md +192 -166
  59. package/commands/gsd-t-test-sync.md +381 -381
  60. package/commands/gsd-t-triage-and-merge.md +171 -171
  61. package/commands/gsd-t-verify.md +382 -382
  62. package/commands/gsd-t-visualize.md +118 -118
  63. package/commands/gsd-t-wave.md +401 -378
  64. package/docs/GSD-T-README.md +425 -424
  65. package/docs/architecture.md +385 -369
  66. package/docs/harness-design-analysis.md +371 -0
  67. package/docs/infrastructure.md +205 -205
  68. package/docs/prd-graph-engine.md +398 -398
  69. package/docs/prd-gsd2-hybrid.md +559 -559
  70. package/docs/prd-harness-evolution.md +583 -0
  71. package/docs/requirements.md +14 -0
  72. package/docs/workflows.md +226 -226
  73. package/examples/.gsd-t/domains/example-domain/scope.md +13 -13
  74. package/package.json +40 -40
  75. package/scripts/gsd-t-auto-route.js +39 -39
  76. package/scripts/gsd-t-dashboard-mockup.html +1143 -1143
  77. package/scripts/gsd-t-dashboard-server.js +171 -171
  78. package/scripts/gsd-t-dashboard.html +262 -262
  79. package/scripts/gsd-t-event-writer.js +128 -128
  80. package/scripts/gsd-t-statusline.js +94 -94
  81. package/scripts/gsd-t-tools.js +175 -175
  82. package/templates/CLAUDE-global.md +638 -634
  83. package/templates/CLAUDE-project.md +24 -0
  84. package/templates/backlog-settings.md +18 -18
  85. package/templates/backlog.md +1 -1
  86. package/templates/progress.md +40 -40
  87. package/templates/shared-services-contract.md +60 -60
  88. package/templates/stacks/desktop.ini +2 -2
  89. package/bin/desktop.ini +0 -2
  90. package/commands/desktop.ini +0 -2
  91. package/docs/ci-examples/desktop.ini +0 -2
  92. package/docs/desktop.ini +0 -2
  93. package/examples/.gsd-t/contracts/desktop.ini +0 -2
  94. package/examples/.gsd-t/desktop.ini +0 -2
  95. package/examples/.gsd-t/domains/desktop.ini +0 -2
  96. package/examples/.gsd-t/domains/example-domain/desktop.ini +0 -2
  97. package/examples/desktop.ini +0 -2
  98. package/examples/rules/desktop.ini +0 -2
  99. package/scripts/desktop.ini +0 -2
  100. 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
+ };