gitnexus 1.6.8-rc.20 → 1.6.8-rc.22

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 (43) hide show
  1. package/dist/cli/ai-context.js +1 -0
  2. package/dist/cli/skill-gen.js +1 -0
  3. package/dist/core/ingestion/cfg/emit.d.ts +16 -1
  4. package/dist/core/ingestion/cfg/emit.js +10 -3
  5. package/dist/core/ingestion/cfg/reaching-defs.d.ts +7 -0
  6. package/dist/core/ingestion/cfg/reaching-defs.js +9 -0
  7. package/dist/core/ingestion/cfg/types.d.ts +91 -0
  8. package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +38 -0
  9. package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +419 -3
  10. package/dist/core/ingestion/pipeline.d.ts +16 -0
  11. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +2 -0
  12. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +8 -0
  13. package/dist/core/ingestion/scope-resolution/pipeline/run.js +119 -3
  14. package/dist/core/ingestion/taint/emit.d.ts +124 -0
  15. package/dist/core/ingestion/taint/emit.js +204 -0
  16. package/dist/core/ingestion/taint/match.d.ts +153 -0
  17. package/dist/core/ingestion/taint/match.js +278 -0
  18. package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
  19. package/dist/core/ingestion/taint/path-codec.js +190 -0
  20. package/dist/core/ingestion/taint/propagate.d.ts +216 -0
  21. package/dist/core/ingestion/taint/propagate.js +664 -0
  22. package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
  23. package/dist/core/ingestion/taint/site-safety.js +98 -0
  24. package/dist/core/ingestion/taint/source-sink-config.d.ts +94 -23
  25. package/dist/core/ingestion/taint/source-sink-config.js +11 -11
  26. package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
  27. package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
  28. package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
  29. package/dist/core/ingestion/taint/typescript-model.js +102 -0
  30. package/dist/core/run-analyze.d.ts +8 -1
  31. package/dist/core/run-analyze.js +14 -0
  32. package/dist/mcp/local/local-backend.d.ts +29 -0
  33. package/dist/mcp/local/local-backend.js +241 -1
  34. package/dist/mcp/resources.js +1 -0
  35. package/dist/mcp/tools.d.ts +9 -0
  36. package/dist/mcp/tools.js +53 -0
  37. package/dist/storage/parse-cache.js +10 -1
  38. package/dist/storage/repo-manager.d.ts +18 -0
  39. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +89 -3
  40. package/hooks/claude/gitnexus-hook.cjs +88 -4
  41. package/hooks/claude/hook-db-lock-probe.cjs +53 -21
  42. package/package.json +1 -1
  43. package/skills/gitnexus-guide.md +11 -0
@@ -136,6 +136,7 @@ This project is indexed by GitNexus as **${projectName}**${noStats ? '' : ` (${s
136
136
  - **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
137
137
  - When exploring unfamiliar code, use \`query({query: "concept"})\` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
138
138
  - 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\`).
139
140
 
140
141
  ## Never Do
141
142
 
@@ -485,6 +485,7 @@ const renderSkillMarkdown = (community, projectName, members, files, entryPoints
485
485
  lines.push(`1. \`context({name: "${firstEntry}"})\` \u2014 see callers and callees`);
486
486
  lines.push(`2. \`query({query: "${community.label.toLowerCase()}"})\` \u2014 find related execution flows`);
487
487
  lines.push('3. Read key files listed above for implementation details');
488
+ lines.push('4. `explain({target: "<file or symbol>"})` — persisted taint findings (source→sink data flows), when indexed with `--pdg`');
488
489
  lines.push('');
489
490
  return lines.join('\n');
490
491
  };
@@ -19,7 +19,7 @@
19
19
  * kind cannot be its own edge type and is queried via `reason`.
20
20
  */
21
21
  import type { KnowledgeGraph } from '../../graph/types.js';
22
- import type { FunctionCfg } from './types.js';
22
+ import type { BindingEntry, FunctionCfg } from './types.js';
23
23
  /**
24
24
  * Default per-function CFG edge cap. A pathological generated function could
25
25
  * otherwise emit an unbounded edge set; the cap bounds graph growth and is
@@ -56,6 +56,13 @@ export interface CfgEmitResult {
56
56
  /** Number of functions that hit the cap. */
57
57
  cappedFunctions: number;
58
58
  }
59
+ /**
60
+ * The single BasicBlock id template (module doc). Exported for the M3 taint
61
+ * emit path (taint/emit.ts), whose TAINTED/SANITIZES edges must address the
62
+ * SAME persisted block nodes — a re-derived copy of this template would
63
+ * silently dangle the moment either drifted.
64
+ */
65
+ export declare const basicBlockId: (filePath: string, functionStartLine: number, functionStartColumn: number, blockIndex: number) => string;
59
66
  /**
60
67
  * Whether an untrusted `cfgSideChannel` element is safe to feed to
61
68
  * {@link emitFileCfgs}. Deliberately NOT full FunctionCfg validation — it
@@ -106,6 +113,14 @@ export interface ReachingDefEmitResult {
106
113
  /** Total statement-level facts the solver produced (pre-dedup telemetry). */
107
114
  facts: number;
108
115
  }
116
+ /**
117
+ * Stable identity for a binding inside edge ids (#2082 M2 KTD3/KTD9):
118
+ * `name:declLine:declCol` for declared bindings, `name@module` for synthetic
119
+ * ones. Distinct same-name bindings never share a key; identifier characters
120
+ * cannot contain the id separators. Exported for the M3 taint emit path —
121
+ * TAINTED/SANITIZES ids key bindings with the same discipline.
122
+ */
123
+ export declare const bindingKey: (b: BindingEntry) => string;
109
124
  /**
110
125
  * Compute reaching definitions per function and persist the bounded
111
126
  * REACHING_DEF projection (#2082 M2 U4).
@@ -28,7 +28,13 @@ export const DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION = 4000;
28
28
  export const REACHING_DEF_FACTS_PER_EDGE_CAP = 4;
29
29
  /** Derived emit-path fact limit at the default edge cap (bench/doc anchor). */
30
30
  export const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION = REACHING_DEF_FACTS_PER_EDGE_CAP * DEFAULT_PDG_MAX_REACHING_DEF_EDGES_PER_FUNCTION;
31
- const basicBlockId = (filePath, functionStartLine, functionStartColumn, blockIndex) => `BasicBlock:${filePath}:${functionStartLine}:${functionStartColumn}:${blockIndex}`;
31
+ /**
32
+ * The single BasicBlock id template (module doc). Exported for the M3 taint
33
+ * emit path (taint/emit.ts), whose TAINTED/SANITIZES edges must address the
34
+ * SAME persisted block nodes — a re-derived copy of this template would
35
+ * silently dangle the moment either drifted.
36
+ */
37
+ export const basicBlockId = (filePath, functionStartLine, functionStartColumn, blockIndex) => `BasicBlock:${filePath}:${functionStartLine}:${functionStartColumn}:${blockIndex}`;
32
38
  /**
33
39
  * Whether an untrusted `cfgSideChannel` element is safe to feed to
34
40
  * {@link emitFileCfgs}. Deliberately NOT full FunctionCfg validation — it
@@ -185,9 +191,10 @@ export function emitFileCfgs(graph, cfgs, maxEdgesPerFunction = DEFAULT_MAX_CFG_
185
191
  * Stable identity for a binding inside edge ids (#2082 M2 KTD3/KTD9):
186
192
  * `name:declLine:declCol` for declared bindings, `name@module` for synthetic
187
193
  * ones. Distinct same-name bindings never share a key; identifier characters
188
- * cannot contain the id separators.
194
+ * cannot contain the id separators. Exported for the M3 taint emit path —
195
+ * TAINTED/SANITIZES ids key bindings with the same discipline.
189
196
  */
190
- const bindingKey = (b) => b.synthetic ? `${b.name}@module` : `${b.name}:${b.declLine}:${b.declColumn}`;
197
+ export const bindingKey = (b) => b.synthetic ? `${b.name}@module` : `${b.name}:${b.declLine}:${b.declColumn}`;
191
198
  /**
192
199
  * Compute reaching definitions per function and persist the bounded
193
200
  * REACHING_DEF projection (#2082 M2 U4).
@@ -41,6 +41,13 @@ export interface ProgramPoint {
41
41
  readonly stmtIndex: number;
42
42
  readonly line: number;
43
43
  }
44
+ /**
45
+ * Canonical `block:stmt` string key for a program point. Colon-separated to
46
+ * match the codebase's `blockIndex:stmtIndex` id conventions. Shared by the
47
+ * taint propagation engine (dedup/state keys) and the taint emit path
48
+ * (persisted edge-id material) so the two never drift.
49
+ */
50
+ export declare function pointKey(p: ProgramPoint): string;
44
51
  /** One def→use fact: the definition at `def` reaches the use at `use`. */
45
52
  export interface DefUseFact {
46
53
  /** Index into {@link FunctionDefUse.bindings}. */
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Canonical `block:stmt` string key for a program point. Colon-separated to
3
+ * match the codebase's `blockIndex:stmtIndex` id conventions. Shared by the
4
+ * taint propagation engine (dedup/state keys) and the taint emit path
5
+ * (persisted edge-id material) so the two never drift.
6
+ */
7
+ export function pointKey(p) {
8
+ return `${p.blockIndex}:${p.stmtIndex}`;
9
+ }
1
10
  /**
2
11
  * def-site key: packs (blockIndex, stmtIndex) into one number. The stride is
3
12
  * a per-BLOCK statement bound, and `maxFunctionLines` caps LINES, not
@@ -40,6 +40,90 @@ export interface BindingEntry {
40
40
  */
41
41
  readonly synthetic?: boolean;
42
42
  }
43
+ /**
44
+ * One occurrence of a binding inside a call/new site's argument position
45
+ * (#2083 M3 U1). A bare `number` is a DIRECT occurrence (binding index into
46
+ * {@link FunctionCfg.bindings}); a `[bindingIdx, viaSiteIdx]` tuple marks an
47
+ * occurrence that reaches this argument THROUGH the nested site at
48
+ * `viaSiteIdx` (an index into the SAME statement's {@link StatementFacts.sites}
49
+ * array). The tag is load-bearing for sanitizer interposition (plan KTD4a):
50
+ * a flat per-arg binding set cannot distinguish `exec(escape(x))` (kill) from
51
+ * `exec(x)` (finding) — the single most common safe pattern would
52
+ * false-positive without it.
53
+ */
54
+ export type SiteArgOccurrence = number | readonly [number, number];
55
+ /**
56
+ * One call site, constructor call, or value-position member read harvested
57
+ * from a statement (#2083 M3 U1, plan KTD2). Worker-side substrate for the M3
58
+ * taint pass: the M2 facts carry no expression structure, and the main thread
59
+ * cannot re-parse (the #1983 OOM shape). Spec-AGNOSTIC — records structure
60
+ * only, never source/sink/sanitizer-ness (matching is a main-thread concern).
61
+ *
62
+ * Integer indices: binding fields (`receiver`/`object`/`resultDefs`/arg
63
+ * occurrences) index {@link FunctionCfg.bindings}; site references (`parent`,
64
+ * via-tags) index the OWNING statement's `sites` array. JSON-plain; NO field
65
+ * here may be named `nodeId` (durable parsedfile-store reviver hazard — see
66
+ * {@link BindingEntry}).
67
+ */
68
+ export interface SiteRecord {
69
+ readonly kind: 'call' | 'new' | 'member-read';
70
+ /**
71
+ * Dotted callee path for call/new sites whose callee chain is rooted at an
72
+ * identifier/`this`/`super` (`child_process.exec`, `req.body.toString`).
73
+ * Optional chaining is normalized (`a?.b()` ⇒ `a.b`); string-literal
74
+ * subscripts fold into the path (`cp["exec"]` ⇒ `cp.exec`). Absent when the
75
+ * chain is not statically resolvable (dynamic key, call-rooted chain).
76
+ */
77
+ readonly callee?: string;
78
+ /**
79
+ * Binding index of the callee chain's ROOT identifier when the callee is a
80
+ * member chain (`userInput.trim()` ⇒ `userInput`). Method calls launder
81
+ * taint without it (plan KTD5 receiver-position TITO). Absent for bare
82
+ * calls (`exec(x)`) and non-identifier roots.
83
+ */
84
+ readonly receiver?: number;
85
+ /**
86
+ * Per-argument-position occurrence entries (trailing empty positions are
87
+ * trimmed; absent when no argument carries a binding occurrence). For
88
+ * `template: true` sites every substitution occurrence aggregates at
89
+ * position 0 (tagged templates have no positional argument list).
90
+ */
91
+ readonly args?: ReadonlyArray<readonly SiteArgOccurrence[]>;
92
+ /**
93
+ * Bindings defined by a declarator/assignment whose ENTIRE value (after
94
+ * unwrapping parens/`await`/`as`/`!`) is this call — `const b = escape(t)`
95
+ * ⇒ `[b]`. Per-declarator: `const a = t, b = escape(t)` attaches `[b]`
96
+ * only. Kill placement (KTD4b) keys on this: a sanitizer kills exactly the
97
+ * defs that receive its result directly.
98
+ */
99
+ readonly resultDefs?: readonly number[];
100
+ /**
101
+ * `[siteIdx, argIdx]` of the innermost enclosing call/new site argument
102
+ * position this site occurs in (`exec(escape(x))` ⇒ escape's parent is
103
+ * `[execSiteIdx, 0]`). Absent for top-level sites.
104
+ */
105
+ readonly parent?: readonly [number, number];
106
+ /**
107
+ * Index of the FIRST spread argument (`exec(...args)` ⇒ 0). Presence means
108
+ * position matching must degrade soundly (any sink position ≥ this index —
109
+ * plan KTD2/U2). A number (not boolean) because the matcher needs the index.
110
+ */
111
+ readonly spread?: number;
112
+ /** Tagged-template call (`sql\`…${id}\``) — argument positions are not positional. */
113
+ readonly template?: boolean;
114
+ /**
115
+ * String-literal first argument when the callee is bare `require` —
116
+ * CommonJS aliases resolve like ESM imports on the main thread (KTD7).
117
+ */
118
+ readonly requireArg?: string;
119
+ /** Member read: binding index of the object root (`req.body` ⇒ `req`). */
120
+ readonly object?: number;
121
+ /**
122
+ * Member read: property name (`req.body` ⇒ `'body'`; `req["body"]`
123
+ * included; dynamic `req[key]` is never recorded — documented KTD10 FN).
124
+ */
125
+ readonly property?: string;
126
+ }
43
127
  /**
44
128
  * Def/use facts for one harvested statement (or construct header), in
45
129
  * execution order within its block (#2082 M2 U1). `defs`/`uses` are indices
@@ -55,12 +139,19 @@ export interface BindingEntry {
55
139
  * treating them as must-defs would falsely kill the prior def on the
56
140
  * not-taken path (a taint false negative on core JS idioms). Optional —
57
141
  * absent means none.
142
+ *
143
+ * `sites` (#2083 M3 U1): call/member-read structure for the taint pass —
144
+ * see {@link SiteRecord}. Optional and omit-when-empty; absent on pre-M3
145
+ * channels and on statements with no calls or member reads. Sites inside
146
+ * nested functions are NOT recorded (consistent with def/use invisibility —
147
+ * the enclosing `arr.forEach(...)` call IS, with receiver `arr`).
58
148
  */
59
149
  export interface StatementFacts {
60
150
  readonly line: number;
61
151
  readonly defs: readonly number[];
62
152
  readonly uses: readonly number[];
63
153
  readonly mayDefs?: readonly number[];
154
+ readonly sites?: readonly SiteRecord[];
64
155
  }
65
156
  /** A basic block: a maximal straight-line run of statements between leaders. */
66
157
  export interface BasicBlockData {
@@ -64,6 +64,16 @@ export declare class TsHarvester {
64
64
  * here falsely kills the prior def on the not-taken path).
65
65
  */
66
66
  private conditionalDepth;
67
+ /**
68
+ * Call/new node id → bindings whose declarator/assignment VALUE is exactly
69
+ * that call (#2083 M3 U1). Registered by the declarator/assignment handlers
70
+ * BEFORE the value walk, consumed by {@link visitCall} when it reaches the
71
+ * node — the indirection keeps result-def attribution per-declarator
72
+ * (`const a = t, b = escape(t)` attaches `[b]` to the escape site only) and
73
+ * top-level-only (`const c = cond ? escape(b) : b` attaches nothing — the
74
+ * bypass occurrence must keep `c` taintable, plan KTD4a).
75
+ */
76
+ private readonly resultDefTargets;
67
77
  constructor(fnNode: SyntaxNode);
68
78
  /** The completed binding table — pass to `CfgBuilder.finish`. */
69
79
  table(): readonly BindingEntry[];
@@ -103,4 +113,32 @@ export declare class TsHarvester {
103
113
  private walkValue;
104
114
  /** Assignment-target walk: identifiers bind; member/subscript targets are uses. */
105
115
  private walkDefPattern;
116
+ /** Strip value-transparent wrappers (`(x)`, `x!`, `x as T`, `await x`). */
117
+ private unwrapValueWrappers;
118
+ /**
119
+ * When `value`'s root (after unwrapping) is a call/new node, remember that
120
+ * its site should carry `resultDefs: defs` — consumed by {@link visitCall}
121
+ * once the value walk reaches the node.
122
+ */
123
+ private registerResultDefs;
124
+ /**
125
+ * Explicit call/new handler: records a call site (callee path, receiver,
126
+ * per-arg occurrence entries, spread/template markers, require literal,
127
+ * result defs) while reproducing EXACTLY the uses the old default descent
128
+ * recorded — callee chain root + dynamic subscript indices + arguments.
129
+ */
130
+ private visitCall;
131
+ /**
132
+ * Member/subscript chain walk shared by value position, write position, and
133
+ * callee position. Use-recording is identical to the old default descent
134
+ * (chain-root identifier once, dynamic subscript index expressions, full
135
+ * walk of non-identifier roots) — NO double-recording. Member-read sites:
136
+ * at most ONE per chain — the INNERMOST access — and only when the chain
137
+ * root is an identifier and the access's key is static (`.prop` or a
138
+ * string-literal subscript); `skipFinalRead` suppresses it when that access
139
+ * is the final one (callee / write target). Optional chaining (`?.`) never
140
+ * appears in the output (field-based traversal normalizes it); dynamic
141
+ * computed keys record nothing (documented KTD10 FN).
142
+ */
143
+ private walkChain;
106
144
  }