gitnexus 1.6.8-rc.31 → 1.6.8-rc.33

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 (61) hide show
  1. package/dist/_shared/graph/types.d.ts +15 -1
  2. package/dist/_shared/graph/types.d.ts.map +1 -1
  3. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  4. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  5. package/dist/_shared/lbug/schema-constants.js +6 -0
  6. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  7. package/dist/cli/ai-context.d.ts +31 -1
  8. package/dist/cli/ai-context.js +14 -15
  9. package/dist/cli/analyze.js +1 -0
  10. package/dist/core/group/sync.d.ts +27 -1
  11. package/dist/core/group/sync.js +0 -0
  12. package/dist/core/ingestion/cfg/control-dependence.d.ts +47 -0
  13. package/dist/core/ingestion/cfg/control-dependence.js +120 -0
  14. package/dist/core/ingestion/cfg/emit.d.ts +59 -0
  15. package/dist/core/ingestion/cfg/emit.js +127 -0
  16. package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
  17. package/dist/core/ingestion/cfg/post-dominators.js +176 -0
  18. package/dist/core/ingestion/pipeline.d.ts +9 -0
  19. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +1 -0
  20. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +3 -0
  21. package/dist/core/ingestion/scope-resolution/pipeline/run.js +16 -1
  22. package/dist/core/lbug/pool-adapter.d.ts +35 -0
  23. package/dist/core/lbug/pool-adapter.js +90 -1
  24. package/dist/core/run-analyze.d.ts +5 -1
  25. package/dist/core/run-analyze.js +8 -1
  26. package/dist/mcp/local/local-backend.d.ts +42 -0
  27. package/dist/mcp/local/local-backend.js +264 -65
  28. package/dist/mcp/resources.js +11 -0
  29. package/dist/mcp/tools.d.ts +2 -0
  30. package/dist/mcp/tools.js +56 -1
  31. package/dist/storage/repo-manager.d.ts +8 -0
  32. package/package.json +1 -1
  33. package/skills/gitnexus-guide.md +11 -0
  34. package/skills/gitnexus-pdg-query.md +89 -0
  35. package/web/assets/{agent-C01BpbCi.js → agent-lSrfPH05.js} +1 -1
  36. package/web/assets/{architectureDiagram-UL44E2DR-BETib0Ig.js → architectureDiagram-UL44E2DR-CgHRMZJ8.js} +1 -1
  37. package/web/assets/{chunk-LCXTWHL2-ECvRIdTA.js → chunk-LCXTWHL2-03IqIAdZ.js} +1 -1
  38. package/web/assets/{chunk-RG4AUYOV-htm8QilK.js → chunk-RG4AUYOV-DLFjj1rt.js} +1 -1
  39. package/web/assets/{classDiagram-KGZ6W3CR-vJcJG7SW.js → classDiagram-KGZ6W3CR-BTr7xsR8.js} +1 -1
  40. package/web/assets/{classDiagram-v2-72OJOZXJ-BCJH9y5K.js → classDiagram-v2-72OJOZXJ-CakV709X.js} +1 -1
  41. package/web/assets/{diagram-3NCE3AQN-DgbGdjct.js → diagram-3NCE3AQN-BW6iSZhw.js} +1 -1
  42. package/web/assets/{diagram-GF46GFSD-DkQKRTzf.js → diagram-GF46GFSD-5IsY7kfK.js} +1 -1
  43. package/web/assets/{diagram-QXG6HAR7-fO9iaqTW.js → diagram-QXG6HAR7-Cia81gsm.js} +1 -1
  44. package/web/assets/{diagram-WEQXMOUZ-BFGWbiEm.js → diagram-WEQXMOUZ-CLfT1sdo.js} +1 -1
  45. package/web/assets/{erDiagram-L5TCEMPS-IErmkD3i.js → erDiagram-L5TCEMPS-DqEAcwC_.js} +1 -1
  46. package/web/assets/{flowDiagram-H6V6AXG4-BOtRGXXs.js → flowDiagram-H6V6AXG4-BUEpeW9m.js} +1 -1
  47. package/web/assets/{index-DKmSSk0F.js → index-64YMDMOE.js} +6 -6
  48. package/web/assets/{infoDiagram-3YFTVSEB-DKRlh1as.js → infoDiagram-3YFTVSEB-hHbd_QCD.js} +1 -1
  49. package/web/assets/{ishikawaDiagram-BNXS4ZKH-B07zhx7A.js → ishikawaDiagram-BNXS4ZKH-XxO6eDu4.js} +1 -1
  50. package/web/assets/{kanban-definition-75IXJCU3-_saj34_J.js → kanban-definition-75IXJCU3-DeSVq5eO.js} +1 -1
  51. package/web/assets/{mindmap-definition-2TDM6QVE-iyCoM6pR.js → mindmap-definition-2TDM6QVE-CgEl9e61.js} +1 -1
  52. package/web/assets/{pieDiagram-CU6KROY3-DOeaMPvU.js → pieDiagram-CU6KROY3-E3RA4hXq.js} +1 -1
  53. package/web/assets/{requirementDiagram-JXO7QTGE-DMhAsO2o.js → requirementDiagram-JXO7QTGE-B70bTqxy.js} +1 -1
  54. package/web/assets/{sequenceDiagram-VS2MUI6T-CQZ5FzAV.js → sequenceDiagram-VS2MUI6T-CLt7nidU.js} +1 -1
  55. package/web/assets/{stateDiagram-7D4R322I-kYVKDLkw.js → stateDiagram-7D4R322I-DfdeOa9A.js} +1 -1
  56. package/web/assets/{stateDiagram-v2-36443NZ5-DvvdUOce.js → stateDiagram-v2-36443NZ5-C-c52-8M.js} +1 -1
  57. package/web/assets/{timeline-definition-O6YCAMPW-CxBfIWWp.js → timeline-definition-O6YCAMPW-8DBKNdJr.js} +1 -1
  58. package/web/assets/{vennDiagram-MWXL3ELB-D0m2r7IU.js → vennDiagram-MWXL3ELB-DhqnfFx6.js} +1 -1
  59. package/web/assets/{wardleyDiagram-CUQ6CDDI-yBZJHfwp.js → wardleyDiagram-CUQ6CDDI-DcliVPWw.js} +1 -1
  60. package/web/assets/{xychartDiagram-N2JHSOCM-CQJMSAIO.js → xychartDiagram-N2JHSOCM-CYmW296g.js} +1 -1
  61. package/web/index.html +1 -1
@@ -79,7 +79,21 @@ export type RelationshipType = 'CONTAINS' | 'CALLS' | 'INHERITS' | 'METHOD_OVERR
79
79
  | 'SANITIZES'
80
80
  /** Materialized source→sink taint path. Working name — final name/representation
81
81
  * is confirmed when M3/M4 emits it; no persisted edge exists before then. */
82
- | 'TAINT_PATH';
82
+ | 'TAINT_PATH'
83
+ /** Control-dependence edge (PDG, issue #2085 M5): block `dependent` (target)
84
+ * executes only because the branch at block `controller` (source) took a
85
+ * given side. The branch sense (`'T'` | `'F'`) rides the relation's existing
86
+ * `reason` column — mirroring how `CFG` stores its edge kind there — since
87
+ * the single `CodeRelation` table has no dedicated label column. */
88
+ | 'CDG'
89
+ /** Debug-only post-dominator-tree edge (#2085 M5): a block → its immediate
90
+ * post-dominator, emitted behind the `GITNEXUS_PDG_EMIT_POST_DOMINATE` env
91
+ * flag for inspection. Never emitted in a normal `--pdg` run. Note: as a
92
+ * member of this exported union it is a forward-compatibility commitment —
93
+ * removing it later is a breaking schema change — and it is deliberately
94
+ * excluded from `VALID_RELATION_TYPES` so it never enters impact-style
95
+ * symbol-space traversal (same posture as the taint substrate edges). */
96
+ | 'POST_DOMINATE';
83
97
  export interface GraphNode {
84
98
  id: string;
85
99
  label: NodeLabel;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/graph/types.ts"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@
10
10
  export declare const NODE_TABLES: readonly ["File", "Folder", "Function", "Class", "Interface", "Method", "CodeElement", "Community", "Process", "Section", "Struct", "Enum", "Macro", "Typedef", "Union", "Namespace", "Trait", "Impl", "TypeAlias", "Const", "Static", "Variable", "Property", "Record", "Delegate", "Annotation", "Constructor", "Template", "Module", "Route", "Tool", "BasicBlock"];
11
11
  export type NodeTableName = (typeof NODE_TABLES)[number];
12
12
  export declare const REL_TABLE_NAME = "CodeRelation";
13
- export declare const REL_TYPES: readonly ["CONTAINS", "DEFINES", "IMPORTS", "CALLS", "EXTENDS", "IMPLEMENTS", "HAS_METHOD", "HAS_PROPERTY", "ACCESSES", "METHOD_OVERRIDES", "OVERRIDES", "METHOD_IMPLEMENTS", "MEMBER_OF", "STEP_IN_PROCESS", "HANDLES_ROUTE", "FETCHES", "HANDLES_TOOL", "ENTRY_POINT_OF", "WRAPS", "QUERIES", "CFG", "REACHING_DEF", "TAINTED", "SANITIZES", "TAINT_PATH"];
13
+ export declare const REL_TYPES: readonly ["CONTAINS", "DEFINES", "IMPORTS", "CALLS", "EXTENDS", "IMPLEMENTS", "HAS_METHOD", "HAS_PROPERTY", "ACCESSES", "METHOD_OVERRIDES", "OVERRIDES", "METHOD_IMPLEMENTS", "MEMBER_OF", "STEP_IN_PROCESS", "HANDLES_ROUTE", "FETCHES", "HANDLES_TOOL", "ENTRY_POINT_OF", "WRAPS", "QUERIES", "CFG", "REACHING_DEF", "TAINTED", "SANITIZES", "TAINT_PATH", "CDG", "POST_DOMINATE"];
14
14
  export type RelType = (typeof REL_TYPES)[number];
15
15
  export declare const EMBEDDING_TABLE_NAME = "CodeEmbedding";
16
16
  //# sourceMappingURL=schema-constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-constants.d.ts","sourceRoot":"","sources":["../../src/lbug/schema-constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,WAAW,wWAkCd,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAE7C,eAAO,MAAM,SAAS,8VA6BZ,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,eAAO,MAAM,oBAAoB,kBAAkB,CAAC"}
1
+ {"version":3,"file":"schema-constants.d.ts","sourceRoot":"","sources":["../../src/lbug/schema-constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,eAAO,MAAM,WAAW,wWAkCd,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,eAAO,MAAM,cAAc,iBAAiB,CAAC;AAE7C,eAAO,MAAM,SAAS,sXAmCZ,CAAC;AAEX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjD,eAAO,MAAM,oBAAoB,kBAAkB,CAAC"}
@@ -72,6 +72,12 @@ export const REL_TYPES = [
72
72
  'TAINTED',
73
73
  'SANITIZES',
74
74
  'TAINT_PATH',
75
+ // Control dependence (PDG, issue #2085 M5) — CDG carries its 'T'|'F' branch
76
+ // label in the relation's `reason` column; POST_DOMINATE is debug-only
77
+ // (behind GITNEXUS_PDG_EMIT_POST_DOMINATE). Both are BasicBlock→BasicBlock,
78
+ // reusing the existing FROM BasicBlock TO BasicBlock pair in RELATION_SCHEMA.
79
+ 'CDG',
80
+ 'POST_DOMINATE',
75
81
  ];
76
82
  export const EMBEDDING_TABLE_NAME = 'CodeEmbedding';
77
83
  //# sourceMappingURL=schema-constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema-constants.js","sourceRoot":"","sources":["../../src/lbug/schema-constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,QAAQ;IACR,UAAU;IACV,OAAO;IACP,WAAW;IACX,QAAQ;IACR,aAAa;IACb,WAAW;IACX,SAAS;IACT,SAAS;IACT,QAAQ;IACR,MAAM;IACN,OAAO;IACP,SAAS;IACT,OAAO;IACP,WAAW;IACX,OAAO;IACP,MAAM;IACN,WAAW;IACX,OAAO;IACP,QAAQ;IACR,UAAU;IACV,UAAU;IACV,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,aAAa;IACb,UAAU;IACV,QAAQ;IACR,OAAO;IACP,MAAM;IACN,2EAA2E;IAC3E,YAAY;CACJ,CAAC;AAIX,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC;AAE7C,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,UAAU;IACV,SAAS;IACT,SAAS;IACT,OAAO;IACP,SAAS;IACT,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,UAAU;IACV,kBAAkB;IAClB,WAAW,EAAE,mEAAmE;IAChF,mBAAmB;IACnB,WAAW;IACX,iBAAiB;IACjB,eAAe;IACf,SAAS;IACT,cAAc;IACd,gBAAgB;IAChB,OAAO;IACP,SAAS;IACT,yEAAyE;IACzE,yEAAyE;IACzE,6EAA6E;IAC7E,KAAK;IACL,cAAc;IACd,SAAS;IACT,WAAW;IACX,YAAY;CACJ,CAAC;AAIX,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC"}
1
+ {"version":3,"file":"schema-constants.js","sourceRoot":"","sources":["../../src/lbug/schema-constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,MAAM;IACN,QAAQ;IACR,UAAU;IACV,OAAO;IACP,WAAW;IACX,QAAQ;IACR,aAAa;IACb,WAAW;IACX,SAAS;IACT,SAAS;IACT,QAAQ;IACR,MAAM;IACN,OAAO;IACP,SAAS;IACT,OAAO;IACP,WAAW;IACX,OAAO;IACP,MAAM;IACN,WAAW;IACX,OAAO;IACP,QAAQ;IACR,UAAU;IACV,UAAU;IACV,QAAQ;IACR,UAAU;IACV,YAAY;IACZ,aAAa;IACb,UAAU;IACV,QAAQ;IACR,OAAO;IACP,MAAM;IACN,2EAA2E;IAC3E,YAAY;CACJ,CAAC;AAIX,MAAM,CAAC,MAAM,cAAc,GAAG,cAAc,CAAC;AAE7C,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,UAAU;IACV,SAAS;IACT,SAAS;IACT,OAAO;IACP,SAAS;IACT,YAAY;IACZ,YAAY;IACZ,cAAc;IACd,UAAU;IACV,kBAAkB;IAClB,WAAW,EAAE,mEAAmE;IAChF,mBAAmB;IACnB,WAAW;IACX,iBAAiB;IACjB,eAAe;IACf,SAAS;IACT,cAAc;IACd,gBAAgB;IAChB,OAAO;IACP,SAAS;IACT,yEAAyE;IACzE,yEAAyE;IACzE,6EAA6E;IAC7E,KAAK;IACL,cAAc;IACd,SAAS;IACT,WAAW;IACX,YAAY;IACZ,4EAA4E;IAC5E,uEAAuE;IACvE,4EAA4E;IAC5E,8EAA8E;IAC9E,KAAK;IACL,eAAe;CACP,CAAC;AAIX,MAAM,CAAC,MAAM,oBAAoB,GAAG,eAAe,CAAC"}
@@ -24,6 +24,12 @@ export interface AIContextOptions {
24
24
  * plain caller that omits it gets "main", preserving prior behavior.
25
25
  */
26
26
  defaultBranch?: string;
27
+ /**
28
+ * Whether the index was built with `--pdg` (#2086 M6). Gates the `pdg_query`
29
+ * line in the generated block — without the PDG layer the tool only returns a
30
+ * "no PDG layer" note, so advertising it on a non-`--pdg` index is noise.
31
+ */
32
+ hasPdg?: boolean;
27
33
  }
28
34
  /**
29
35
  * Strip backticks from a branch name before it is embedded in a Markdown
@@ -32,7 +38,31 @@ export interface AIContextOptions {
32
38
  * the generation sink so the embedding is provably safe regardless of caller.
33
39
  */
34
40
  export declare function markdownSafeBranch(branch: string): string;
35
- export declare function generateGitNexusContent(projectName: string, stats: RepoStats, generatedSkills?: GeneratedSkillInfo[], groupNames?: string[], noStats?: boolean, skipSkills?: boolean, runnerPath?: string, defaultBranch?: string): string;
41
+ /** Options for {@link generateGitNexusContent} (collapsed from positional
42
+ * params, #2188 review — six `undefined`s to reach `hasPdg` was the smell). */
43
+ export interface GitNexusContentOptions {
44
+ generatedSkills?: GeneratedSkillInfo[];
45
+ groupNames?: string[];
46
+ noStats?: boolean;
47
+ skipSkills?: boolean;
48
+ /** Project-relative path to the runner `gitnexus analyze` drops next to the
49
+ * index (#1945). Referenced by docs so a single CLI-neutral command resolves
50
+ * the available runner (global `gitnexus` → `pnpm dlx` → `npx`) at call time. */
51
+ runnerPath?: string;
52
+ /** Default branch for the regression-compare example (#243). Configurable so
53
+ * projects on `develop`/`master`/etc. don't get `base_ref: "main"` rewritten
54
+ * back over their fix on every analyze. The value is embedded inside a
55
+ * Markdown inline-code span: validateBranchName rejects backticks upstream,
56
+ * and `markdownSafeBranch` strips any remaining backtick here as defense in
57
+ * depth, so JSON.stringify's quote/escape handling is sufficient and the
58
+ * branch cannot break out of the span (#1996 tri-review P1). */
59
+ defaultBranch?: string;
60
+ /** Whether the index was built with `--pdg` (#2086 M6). Gates the pdg_query
61
+ * line below — false (default) omits it, so a non-pdg index doesn't advertise
62
+ * a tool that only returns a "no PDG layer" note. */
63
+ hasPdg?: boolean;
64
+ }
65
+ export declare function generateGitNexusContent(projectName: string, stats: RepoStats, opts?: GitNexusContentOptions): string;
36
66
  /**
37
67
  * Generate AI context files after indexing
38
68
  */
@@ -77,19 +77,8 @@ async function findGroupsContainingRegistryName(registryName) {
77
77
  export function markdownSafeBranch(branch) {
78
78
  return branch.replace(/`/g, '');
79
79
  }
80
- export function generateGitNexusContent(projectName, stats, generatedSkills, groupNames, noStats, skipSkills,
81
- // Project-relative path to the runner `gitnexus analyze` drops next to the
82
- // index (#1945). Referenced by docs so a single CLI-neutral command resolves
83
- // the available runner (global `gitnexus` → `pnpm dlx` → `npx`) at call time.
84
- runnerPath = '.gitnexus/run.cjs',
85
- // Default branch for the regression-compare example (#243). Configurable so
86
- // projects on `develop`/`master`/etc. don't get `base_ref: "main"` rewritten
87
- // back over their fix on every analyze. The value is embedded inside a
88
- // Markdown inline-code span: validateBranchName rejects backticks upstream,
89
- // and `markdownSafeBranch` strips any remaining backtick here as defense in
90
- // depth, so JSON.stringify's quote/escape handling is sufficient and the
91
- // branch cannot break out of the span (#1996 tri-review P1).
92
- defaultBranch = 'main') {
80
+ export function generateGitNexusContent(projectName, stats, opts = {}) {
81
+ const { generatedSkills, groupNames, noStats, skipSkills, runnerPath = '.gitnexus/run.cjs', defaultBranch = 'main', hasPdg = false, } = opts;
93
82
  const generatedRows = generatedSkills && generatedSkills.length > 0
94
83
  ? generatedSkills
95
84
  .map((s) => `| Work in the ${s.label} area (${s.symbolCount} symbols) | \`.claude/skills/generated/${s.name}/SKILL.md\` |`)
@@ -136,7 +125,9 @@ This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${s
136
125
  - **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
137
126
  - When exploring unfamiliar code, use \`query({search_query: "concept"})\` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
138
127
  - When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use \`context({name: "symbolName"})\`.
139
- - For security review, \`explain({target: "fileOrSymbol"})\` lists taint findings (source→sink flows; needs \`analyze --pdg\`).
128
+ - For security review, \`explain({target: "fileOrSymbol"})\` lists taint findings (source→sink flows; needs \`analyze --pdg\`).${hasPdg
129
+ ? `\n- For control/data dependence, \`pdg_query({mode: "controls", target: "fileOrSymbol"})\` answers "under what condition does X run?" (CDG, incl. guard clauses) and \`pdg_query({mode: "flows", target, variable})\` traces "where does variable Y flow?" (REACHING_DEF). \`--pdg\` layer.`
130
+ : ''}
140
131
 
141
132
  ## Never Do
142
133
 
@@ -349,7 +340,15 @@ export async function generateAIContextFiles(repoPath, storagePath, projectName,
349
340
  catch (err) {
350
341
  logger.warn(`Could not write GitNexus runner to ${runnerPath}: ${String(err)}`);
351
342
  }
352
- const content = generateGitNexusContent(projectName, stats, generatedSkills, groupNames, options?.noStats, options?.skipSkills, runnerPath, options?.defaultBranch ?? 'main');
343
+ const content = generateGitNexusContent(projectName, stats, {
344
+ generatedSkills,
345
+ groupNames,
346
+ noStats: options?.noStats,
347
+ skipSkills: options?.skipSkills,
348
+ runnerPath,
349
+ defaultBranch: options?.defaultBranch ?? 'main',
350
+ hasPdg: options?.hasPdg ?? false,
351
+ });
353
352
  const createdFiles = [];
354
353
  if (!options?.skipAgentsMd) {
355
354
  // Create AGENTS.md (standard for Cursor, Windsurf, OpenCode, Cline, etc.)
@@ -1073,6 +1073,7 @@ const analyzeCommandImpl = async (inputPath, cliOptions) => {
1073
1073
  // Mirror runFullAnalysis `noStats` bridge (#1477) — same expression;
1074
1074
  // exercised on the `--skills` path by analyze-no-stats-bridge.test.ts.
1075
1075
  noStats: options.stats === false,
1076
+ hasPdg: options.pdg === true,
1076
1077
  });
1077
1078
  }
1078
1079
  }
@@ -1,5 +1,5 @@
1
1
  import { type RegistryEntry } from '../../storage/repo-manager.js';
2
- import type { GroupConfig, RepoHandle, RepoSnapshot, StoredContract, CrossLink } from './types.js';
2
+ import type { GroupConfig, RepoHandle, RepoSnapshot, StoredContract, CrossLink, GroupManifestLink } from './types.js';
3
3
  export interface SyncOptions {
4
4
  extractorOverride?: ((repo: RepoHandle) => Promise<StoredContract[]>) | (() => Promise<StoredContract[]>);
5
5
  resolveRepoHandle?: (registryName: string, groupPath: string) => Promise<RepoHandle | null>;
@@ -18,4 +18,30 @@ export interface SyncResult {
18
18
  repoSnapshots: Record<string, RepoSnapshot>;
19
19
  }
20
20
  export declare function stableRepoPoolId(entry: RegistryEntry, allEntries: RegistryEntry[]): string;
21
+ /** A batch of manifest links whose referenced in-group repos fit one resident window. */
22
+ export interface ManifestWindow {
23
+ links: GroupManifestLink[];
24
+ /** In-group repos (group paths) this window's links reference; size ≤ maxResident. */
25
+ repos: Set<string>;
26
+ }
27
+ /**
28
+ * Partition manifest links into windows so each window references at most
29
+ * `maxResident` distinct in-group repos. Manifest resolution then materializes
30
+ * only one window's repos at a time, bounding peak pool residency regardless of
31
+ * group size (PR #2191 review, Finding 3 — windowed deferred resolution).
32
+ *
33
+ * Each link references ≤2 in-group repos, so every link fits a window when
34
+ * `maxResident ≥ 2`. Links are pre-sorted by their referenced-repo key so links
35
+ * sharing a repo land in contiguous windows — combined with release-not-close
36
+ * pooling, a hub repo stays warm across the windows that reference it (its
37
+ * lease is released, not closed, so the next window's initLbug fast-paths it).
38
+ * Every link lands in EXACTLY one window (a true partition): downstream
39
+ * dedupeCrossLinks dedupes cross-links but not contracts, so a link in two
40
+ * windows would emit duplicate contracts nothing absorbs.
41
+ *
42
+ * Repos not in `knownRepos` (dangling / unresolved) add 0 to a window's repo
43
+ * budget — the link is still placed (so it yields synthetic-UID contracts), it
44
+ * just consumes no residency.
45
+ */
46
+ export declare function partitionManifestWindows(links: GroupManifestLink[], knownRepos: Set<string>, maxResident: number): ManifestWindow[];
21
47
  export declare function syncGroup(config: GroupConfig, opts?: SyncOptions): Promise<SyncResult>;
Binary file
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Control dependence (#2085 M5 U3) — Ferrante, Ottenstein & Warren §3.1.1 over
3
+ * the post-dominator tree. A block `dependent` is control-dependent on a branch
4
+ * block `controller` when `controller` decides whether `dependent` executes:
5
+ * formally, there is a CFG edge `controller → B` such that `dependent`
6
+ * post-dominates `B` but does NOT strictly post-dominate `controller`.
7
+ *
8
+ * Construction (§3.1.1): for each CFG edge `(A, B)` where `B` does NOT
9
+ * post-dominate `A`, walk UP the post-dom tree from `B` to (but not including)
10
+ * `ipdom(A)`; every block on that path is control-dependent on `A`. The branch
11
+ * SENSE of the edge ('T' | 'F') becomes the edge label (KTD4 / KTD3 — it rides
12
+ * the persisted relation's `reason` column).
13
+ *
14
+ * PURE AND DETERMINISTIC (mirrors post-dominators.ts / reaching-defs.ts): no
15
+ * graph, no logger, importable outside the worker; output is deduped per
16
+ * (controller, dependent, label) and sorted, so snapshot tests and
17
+ * content-derived edge ids are stable. The loop header legitimately appears as
18
+ * control-dependent on ITSELF (`controller === dependent`) — the loop predicate
19
+ * gates its own re-execution; this is standard PDG behavior, not a bug.
20
+ */
21
+ import { type PostDomTree } from './post-dominators.js';
22
+ import type { FunctionCfg } from './types.js';
23
+ export type CdgLabel = 'T' | 'F';
24
+ export interface ControlDepEdge {
25
+ /** The branch block whose outcome controls `dependentBlock`. */
26
+ readonly controllerBlock: number;
27
+ /** The block that executes only because `controllerBlock` took `label`. */
28
+ readonly dependentBlock: number;
29
+ /** Branch sense of the controlling CFG edge — see {@link branchSense}. */
30
+ readonly label: CdgLabel;
31
+ }
32
+ export interface ControlDepResult {
33
+ /** Deduped, sorted (controller, dependent, label) control-dependence edges. */
34
+ readonly edges: readonly ControlDepEdge[];
35
+ /**
36
+ * True when the `maxEdges` ceiling was reached; `edges` is then a
37
+ * deterministic prefix (CFG-edge iteration order, sorted), never a silent
38
+ * drop. Mirrors {@link computeReachingDefs}'s `truncated`.
39
+ */
40
+ readonly truncated: boolean;
41
+ }
42
+ /**
43
+ * Compute control-dependence edges for one function's CFG. `postDom` may be
44
+ * supplied to reuse an already-built tree; otherwise it is computed. See the
45
+ * module doc for the purity/determinism contract.
46
+ */
47
+ export declare function computeControlDependence(cfg: FunctionCfg, postDom?: PostDomTree, maxEdges?: number): ControlDepResult;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Control dependence (#2085 M5 U3) — Ferrante, Ottenstein & Warren §3.1.1 over
3
+ * the post-dominator tree. A block `dependent` is control-dependent on a branch
4
+ * block `controller` when `controller` decides whether `dependent` executes:
5
+ * formally, there is a CFG edge `controller → B` such that `dependent`
6
+ * post-dominates `B` but does NOT strictly post-dominate `controller`.
7
+ *
8
+ * Construction (§3.1.1): for each CFG edge `(A, B)` where `B` does NOT
9
+ * post-dominate `A`, walk UP the post-dom tree from `B` to (but not including)
10
+ * `ipdom(A)`; every block on that path is control-dependent on `A`. The branch
11
+ * SENSE of the edge ('T' | 'F') becomes the edge label (KTD4 / KTD3 — it rides
12
+ * the persisted relation's `reason` column).
13
+ *
14
+ * PURE AND DETERMINISTIC (mirrors post-dominators.ts / reaching-defs.ts): no
15
+ * graph, no logger, importable outside the worker; output is deduped per
16
+ * (controller, dependent, label) and sorted, so snapshot tests and
17
+ * content-derived edge ids are stable. The loop header legitimately appears as
18
+ * control-dependent on ITSELF (`controller === dependent`) — the loop predicate
19
+ * gates its own re-execution; this is standard PDG behavior, not a bug.
20
+ */
21
+ import { computePostDominators, postDominates, NO_IPDOM, } from './post-dominators.js';
22
+ function buildArmSenses(cfg) {
23
+ const n = cfg.blocks.length;
24
+ const senses = Array.from({ length: n }, () => ({
25
+ hasTrueArm: false,
26
+ hasFalseArm: false,
27
+ }));
28
+ for (const e of cfg.edges) {
29
+ if (e.from < 0 || e.from >= n)
30
+ continue;
31
+ if (e.kind === 'cond-true' || e.kind === 'switch-case')
32
+ senses[e.from].hasTrueArm = true;
33
+ else if (e.kind === 'cond-false')
34
+ senses[e.from].hasFalseArm = true;
35
+ }
36
+ return senses;
37
+ }
38
+ /**
39
+ * The CDG label ('T'|'F') for a control-dependence edge, given the controlling
40
+ * block's arm senses. An explicitly-sensed edge is taken at face value; an
41
+ * ambiguous fall-through edge (`seq`/`loop-back`/`fallthrough`/jump) is the
42
+ * COMPLEMENT of the controller's explicit sibling arm. Per-case `switch` value
43
+ * labels are deferred to #2086 — every `switch-case` is 'T' in M5.
44
+ */
45
+ function labelFor(kind, controller) {
46
+ if (kind === 'cond-true' || kind === 'switch-case')
47
+ return 'T';
48
+ if (kind === 'cond-false')
49
+ return 'F';
50
+ // Ambiguous structural kind: take the complement of the controller's explicit
51
+ // arm. A block with a true arm reaches here via its false fall-through; a
52
+ // do/while bottom-test (false arm = cond-false) reaches here via its true
53
+ // loop-back. With neither explicit arm (a degenerate / exit-unreachable
54
+ // region — see #2188 F2, where the dependence itself is unsound) the sense is
55
+ // indeterminate; default 'F' since fall-through is the common case.
56
+ if (controller.hasTrueArm)
57
+ return 'F';
58
+ if (controller.hasFalseArm)
59
+ return 'T';
60
+ return 'F';
61
+ }
62
+ /**
63
+ * Compute control-dependence edges for one function's CFG. `postDom` may be
64
+ * supplied to reuse an already-built tree; otherwise it is computed. See the
65
+ * module doc for the purity/determinism contract.
66
+ */
67
+ export function computeControlDependence(cfg, postDom,
68
+ // Heap-safety ceiling on materialized edges, mirroring computeReachingDefs'
69
+ // `maxFacts` (#2188 review): the pre-dedup walk is O(edges × post-dom depth),
70
+ // so bound it before it can spike. `0` ⇒ unbounded. On overflow `edges` is a
71
+ // deterministic prefix and `truncated` is set — never a silent drop.
72
+ maxEdges = 0) {
73
+ const tree = postDom ?? computePostDominators(cfg);
74
+ const { ipdom } = tree;
75
+ const n = cfg.blocks.length;
76
+ const armSenses = buildArmSenses(cfg);
77
+ const cap = maxEdges > 0 ? maxEdges : Infinity;
78
+ const out = [];
79
+ const seen = new Set();
80
+ let truncated = false;
81
+ scan: for (const e of cfg.edges) {
82
+ const a = e.from;
83
+ const b = e.to;
84
+ if (a < 0 || a >= n || b < 0 || b >= n)
85
+ continue;
86
+ // No control dependence when B post-dominates A — every path leaving A
87
+ // through this edge still reaches B, so A does not decide B's execution.
88
+ // This guard is exactly AC2: a dependence exists IFF post-dominance fails.
89
+ if (postDominates(tree, b, a))
90
+ continue;
91
+ // Sense is read from the CONTROLLER's arms, not this edge's kind alone —
92
+ // seq/loop-back fall-through false arms would otherwise mislabel as 'T'
93
+ // (#2188 F1).
94
+ const label = labelFor(e.kind, armSenses[a]);
95
+ const stop = ipdom[a]; // walk up to ipdom(A), EXCLUSIVE (NO_IPDOM ⇒ to root)
96
+ let cur = b;
97
+ let steps = 0;
98
+ // `steps <= n` is defensive — the ipdom chain is a finite tree.
99
+ while (cur !== NO_IPDOM && cur !== stop && steps <= n) {
100
+ const key = `${a}:${cur}:${label}`;
101
+ if (!seen.has(key)) {
102
+ // Check BEFORE pushing so `truncated` means a genuine overflow (a new
103
+ // unique edge had to be dropped), not merely "reached the ceiling" —
104
+ // exactly `cap` edges is a full, non-truncated result.
105
+ if (out.length >= cap) {
106
+ truncated = true;
107
+ break scan;
108
+ }
109
+ seen.add(key);
110
+ out.push({ controllerBlock: a, dependentBlock: cur, label });
111
+ }
112
+ cur = ipdom[cur];
113
+ steps += 1;
114
+ }
115
+ }
116
+ out.sort((x, y) => x.controllerBlock - y.controllerBlock ||
117
+ x.dependentBlock - y.dependentBlock ||
118
+ (x.label < y.label ? -1 : x.label > y.label ? 1 : 0));
119
+ return { edges: out, truncated };
120
+ }
@@ -36,6 +36,34 @@ export declare const DEFAULT_MAX_CFG_EDGES_PER_FUNCTION = 5000;
36
36
  * facts. `0` ⇒ unlimited; `undefined` ⇒ this default.
37
37
  */
38
38
  export declare const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
39
+ /**
40
+ * Default per-function CDG edge cap (#2085 M5). CDG edge count is bounded by
41
+ * (blocks × control-nesting-depth) — comparable to the CFG edge count — so it
42
+ * reuses the CFG default of 5000. Counts DEDUPED (controller, dependent, label)
43
+ * edges (the pure {@link computeControlDependence} already dedups). `0` ⇒
44
+ * unlimited; `undefined` ⇒ this default. Folded into the `RepoMeta.pdg` stamp
45
+ * (U5) so introducing CDG forces a full writeback for pre-CDG `--pdg` indexes.
46
+ */
47
+ export declare const DEFAULT_PDG_MAX_CDG_EDGES_PER_FUNCTION = 5000;
48
+ /**
49
+ * Heap-safety ceiling on {@link computeControlDependence}'s pre-dedup
50
+ * materialization (#2188 review). The walk is O(edges × post-dom depth), and its
51
+ * `out` IS the deduped-edge quantity the per-function cap trims — so, UNLIKE
52
+ * REACHING_DEF's facts ceiling, this is deliberately NOT derived from the
53
+ * runtime edge cap (doing so would pre-truncate the very set the cap reports on,
54
+ * losing the exact dropped count). A fixed, generous multiple of the default
55
+ * edge cap: far above any real function — a catastrophe backstop only. When hit,
56
+ * the per-function cap reporting plus the `truncated` flag keep it observable
57
+ * (never a silent drop).
58
+ */
59
+ export declare const DEFAULT_PDG_MAX_CDG_MATERIALIZATION_PER_FUNCTION: number;
60
+ /**
61
+ * Env flag that additionally emits diagnostic `POST_DOMINATE` edges
62
+ * (block → its immediate post-dominator) alongside CDG (#2085 M5 KTD8). Off in
63
+ * every normal `--pdg` run — these are for inspecting the post-dom tree, not a
64
+ * queryable product surface. Accepts `1`/`true` (case-insensitive).
65
+ */
66
+ export declare const POST_DOMINATE_DEBUG_ENV = "GITNEXUS_PDG_EMIT_POST_DOMINATE";
39
67
  /**
40
68
  * Fact-materialization headroom over the edge cap (#2082 M2 U3/F3): facts are
41
69
  * O(defs×uses) BY SPEC in merge-heavy code, and the edge cap alone bounds the
@@ -142,3 +170,34 @@ export declare const bindingKey: (b: BindingEntry) => string;
142
170
  * M3 tuning wants.
143
171
  */
144
172
  export declare function emitFileReachingDefs(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): ReachingDefEmitResult;
173
+ export interface CdgEmitResult {
174
+ /** Deduped (controller, dependent, label) CDG edges persisted. */
175
+ edges: number;
176
+ /** CDG edges dropped by the per-function edge cap. */
177
+ droppedEdges: number;
178
+ /** Functions that hit the CDG edge cap. */
179
+ cappedFunctions: number;
180
+ /** Diagnostic POST_DOMINATE edges emitted (0 unless the debug env is set). */
181
+ postDominateEdges: number;
182
+ /**
183
+ * Functions skipped because EXIT was not reachable from every entry-reachable
184
+ * block — post-dominance would be unsound (#2188 review). CFG/REACHING_DEF for
185
+ * those functions are kept; only their CDG projection is omitted.
186
+ */
187
+ skippedUnsoundFunctions: number;
188
+ }
189
+ /**
190
+ * Compute control dependence per function and persist the bounded CDG
191
+ * projection (#2085 M5 U4). Mirrors {@link emitFileReachingDefs}: the pure
192
+ * {@link computeControlDependence} already dedups to (controller, dependent,
193
+ * label), so the per-function cap applies to deduped edges and overflow logs
194
+ * one unconditional `onWarn` naming the dropped count — no silent truncation
195
+ * (R6/R7). The branch label ('T'|'F') rides the `reason` column (KTD3),
196
+ * mirroring how CFG stores its edge kind.
197
+ *
198
+ * When {@link POST_DOMINATE_DEBUG_ENV} is set, also emits diagnostic
199
+ * `POST_DOMINATE` edges (block → its immediate post-dominator). These are NOT
200
+ * capped or counted against the CDG budget — they exist only for inspecting the
201
+ * post-dom tree and never appear in a normal run.
202
+ */
203
+ export declare function emitFileCdg(graph: KnowledgeGraph, cfgs: readonly FunctionCfg[], maxEdgesPerFunction?: number, onWarn?: (message: string) => void): CdgEmitResult;
@@ -1,5 +1,7 @@
1
1
  import { generateId } from '../../../lib/utils.js';
2
2
  import { computeReachingDefs } from './reaching-defs.js';
3
+ import { computeControlDependence } from './control-dependence.js';
4
+ import { computePostDominators, isExitReachableFromAllBlocks, NO_IPDOM, } from './post-dominators.js';
3
5
  /**
4
6
  * Default per-function CFG edge cap. A pathological generated function could
5
7
  * otherwise emit an unbounded edge set; the cap bounds graph growth and is
@@ -16,6 +18,34 @@ export const DEFAULT_MAX_CFG_EDGES_PER_FUNCTION = 5000;
16
18
  * facts. `0` ⇒ unlimited; `undefined` ⇒ this default.
17
19
  */
18
20
  export const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
21
+ /**
22
+ * Default per-function CDG edge cap (#2085 M5). CDG edge count is bounded by
23
+ * (blocks × control-nesting-depth) — comparable to the CFG edge count — so it
24
+ * reuses the CFG default of 5000. Counts DEDUPED (controller, dependent, label)
25
+ * edges (the pure {@link computeControlDependence} already dedups). `0` ⇒
26
+ * unlimited; `undefined` ⇒ this default. Folded into the `RepoMeta.pdg` stamp
27
+ * (U5) so introducing CDG forces a full writeback for pre-CDG `--pdg` indexes.
28
+ */
29
+ export const DEFAULT_PDG_MAX_CDG_EDGES_PER_FUNCTION = 5000;
30
+ /**
31
+ * Heap-safety ceiling on {@link computeControlDependence}'s pre-dedup
32
+ * materialization (#2188 review). The walk is O(edges × post-dom depth), and its
33
+ * `out` IS the deduped-edge quantity the per-function cap trims — so, UNLIKE
34
+ * REACHING_DEF's facts ceiling, this is deliberately NOT derived from the
35
+ * runtime edge cap (doing so would pre-truncate the very set the cap reports on,
36
+ * losing the exact dropped count). A fixed, generous multiple of the default
37
+ * edge cap: far above any real function — a catastrophe backstop only. When hit,
38
+ * the per-function cap reporting plus the `truncated` flag keep it observable
39
+ * (never a silent drop).
40
+ */
41
+ export const DEFAULT_PDG_MAX_CDG_MATERIALIZATION_PER_FUNCTION = 8 * DEFAULT_PDG_MAX_CDG_EDGES_PER_FUNCTION;
42
+ /**
43
+ * Env flag that additionally emits diagnostic `POST_DOMINATE` edges
44
+ * (block → its immediate post-dominator) alongside CDG (#2085 M5 KTD8). Off in
45
+ * every normal `--pdg` run — these are for inspecting the post-dom tree, not a
46
+ * queryable product surface. Accepts `1`/`true` (case-insensitive).
47
+ */
48
+ export const POST_DOMINATE_DEBUG_ENV = 'GITNEXUS_PDG_EMIT_POST_DOMINATE';
19
49
  /**
20
50
  * Fact-materialization headroom over the edge cap (#2082 M2 U3/F3): facts are
21
51
  * O(defs×uses) BY SPEC in merge-heavy code, and the edge cap alone bounds the
@@ -313,3 +343,100 @@ export function emitFileReachingDefs(graph, cfgs, maxEdgesPerFunction = DEFAULT_
313
343
  }
314
344
  return result;
315
345
  }
346
+ /** Whether the POST_DOMINATE debug env flag is enabled (`1`/`true`). */
347
+ const postDominateDebugEnabled = () => {
348
+ const v = process.env[POST_DOMINATE_DEBUG_ENV];
349
+ return v === '1' || v?.toLowerCase() === 'true';
350
+ };
351
+ /**
352
+ * Compute control dependence per function and persist the bounded CDG
353
+ * projection (#2085 M5 U4). Mirrors {@link emitFileReachingDefs}: the pure
354
+ * {@link computeControlDependence} already dedups to (controller, dependent,
355
+ * label), so the per-function cap applies to deduped edges and overflow logs
356
+ * one unconditional `onWarn` naming the dropped count — no silent truncation
357
+ * (R6/R7). The branch label ('T'|'F') rides the `reason` column (KTD3),
358
+ * mirroring how CFG stores its edge kind.
359
+ *
360
+ * When {@link POST_DOMINATE_DEBUG_ENV} is set, also emits diagnostic
361
+ * `POST_DOMINATE` edges (block → its immediate post-dominator). These are NOT
362
+ * capped or counted against the CDG budget — they exist only for inspecting the
363
+ * post-dom tree and never appear in a normal run.
364
+ */
365
+ export function emitFileCdg(graph, cfgs, maxEdgesPerFunction = DEFAULT_PDG_MAX_CDG_EDGES_PER_FUNCTION, onWarn) {
366
+ const result = {
367
+ edges: 0,
368
+ droppedEdges: 0,
369
+ cappedFunctions: 0,
370
+ postDominateEdges: 0,
371
+ skippedUnsoundFunctions: 0,
372
+ };
373
+ const cap = maxEdgesPerFunction > 0 ? maxEdgesPerFunction : Infinity;
374
+ const emitPostDom = postDominateDebugEnabled();
375
+ for (const cfg of cfgs) {
376
+ const { filePath, functionStartLine, functionStartColumn } = cfg;
377
+ // Sound post-dominance requires EXIT reachable from every entry-reachable
378
+ // block (#2188 review). A CFG that violates it — a future visitor's
379
+ // multi-terminal / non-terminating shape — would yield a CDG that both
380
+ // drops real and invents spurious dependences, so skip CDG for it. CFG and
381
+ // REACHING_DEF (emitted elsewhere, independent of post-dominance) are kept.
382
+ if (!isExitReachableFromAllBlocks(cfg)) {
383
+ result.skippedUnsoundFunctions++;
384
+ onWarn?.(`[cdg] ${filePath}:${functionStartLine}: EXIT not reachable from all ` +
385
+ `blocks — CDG skipped for this function (CFG/REACHING_DEF unaffected)`);
386
+ continue;
387
+ }
388
+ // Compute the post-dom tree once and feed it to the control-dependence
389
+ // pass (avoids recomputing it) and to the optional POST_DOMINATE emit.
390
+ const tree = computePostDominators(cfg);
391
+ // Bound the pre-dedup materialization (heap parity with REACHING_DEF). The
392
+ // fixed ceiling is a catastrophe backstop; the per-function edge cap below
393
+ // remains the reporting authority. A ceiling hit is surfaced, not silent.
394
+ const { edges: cdgEdges, truncated } = computeControlDependence(cfg, tree, DEFAULT_PDG_MAX_CDG_MATERIALIZATION_PER_FUNCTION);
395
+ if (truncated) {
396
+ onWarn?.(`[cdg] ${filePath}:${functionStartLine}: control-dependence materialization ` +
397
+ `ceiling (${DEFAULT_PDG_MAX_CDG_MATERIALIZATION_PER_FUNCTION}) reached — ` +
398
+ `edge counts for this function are a floor`);
399
+ }
400
+ let emittedForFn = 0;
401
+ for (const edge of cdgEdges) {
402
+ if (emittedForFn >= cap) {
403
+ const dropped = cdgEdges.length - emittedForFn;
404
+ result.droppedEdges += dropped;
405
+ result.cappedFunctions++;
406
+ onWarn?.(`[cdg] ${filePath}:${functionStartLine}: per-function CDG edge cap ` +
407
+ `(${maxEdgesPerFunction}) reached — dropped ${dropped} of ${cdgEdges.length} edges`);
408
+ break;
409
+ }
410
+ const sourceId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.controllerBlock);
411
+ const targetId = basicBlockId(filePath, functionStartLine, functionStartColumn, edge.dependentBlock);
412
+ graph.addRelationship({
413
+ id: generateId('CDG', `${filePath}:${functionStartLine}:${functionStartColumn}:` +
414
+ `${edge.controllerBlock}->${edge.dependentBlock}:${edge.label}`),
415
+ type: 'CDG',
416
+ sourceId,
417
+ targetId,
418
+ confidence: 1.0,
419
+ reason: edge.label, // 'T' | 'F' — queryable, mirrors CFG's kind-in-reason
420
+ });
421
+ result.edges++;
422
+ emittedForFn++;
423
+ }
424
+ if (emitPostDom) {
425
+ for (let b = 0; b < tree.ipdom.length; b++) {
426
+ const ip = tree.ipdom[b];
427
+ if (ip === NO_IPDOM)
428
+ continue;
429
+ graph.addRelationship({
430
+ id: generateId('POST_DOMINATE', `${filePath}:${functionStartLine}:${functionStartColumn}:${b}->${ip}`),
431
+ type: 'POST_DOMINATE',
432
+ sourceId: basicBlockId(filePath, functionStartLine, functionStartColumn, b),
433
+ targetId: basicBlockId(filePath, functionStartLine, functionStartColumn, ip),
434
+ confidence: 1.0,
435
+ reason: '',
436
+ });
437
+ result.postDominateEdges++;
438
+ }
439
+ }
440
+ }
441
+ return result;
442
+ }