gitnexus 1.6.4-rc.97 → 1.6.4-rc.99

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.
@@ -3,6 +3,11 @@ import { SupportedLanguages } from '../../../../_shared/index.js';
3
3
  import { findVisibility, hasKeyword, hasModifier, collectModifierTexts } from './helpers.js';
4
4
  import { extractSimpleTypeName } from '../../type-extractors/shared.js';
5
5
  const CSHARP_VIS = new Set(['public', 'private', 'protected', 'internal']);
6
+ const extractCsharpDeclaredType = (typeNode) => {
7
+ if (typeNode.type === 'generic_name')
8
+ return typeNode.text.trim();
9
+ return extractSimpleTypeName(typeNode) ?? typeNode.text?.trim();
10
+ };
6
11
  /**
7
12
  * C# field extraction config.
8
13
  *
@@ -47,18 +52,18 @@ export const csharpConfig = {
47
52
  if (child?.type === 'variable_declaration') {
48
53
  const typeNode = child.childForFieldName('type');
49
54
  if (typeNode)
50
- return extractSimpleTypeName(typeNode) ?? typeNode.text?.trim();
55
+ return extractCsharpDeclaredType(typeNode);
51
56
  // fallback: first child that is a type
52
57
  const first = child.firstNamedChild;
53
58
  if (first && first.type !== 'variable_declarator') {
54
- return extractSimpleTypeName(first) ?? first.text?.trim();
59
+ return extractCsharpDeclaredType(first);
55
60
  }
56
61
  }
57
62
  }
58
63
  // property_declaration: type is first named child
59
64
  const typeNode = node.childForFieldName('type');
60
65
  if (typeNode)
61
- return extractSimpleTypeName(typeNode) ?? typeNode.text?.trim();
66
+ return extractCsharpDeclaredType(typeNode);
62
67
  return undefined;
63
68
  },
64
69
  extractVisibility(node) {
@@ -37,6 +37,37 @@ const FUNCTION_NODE_TYPES = [
37
37
  'conversion_operator_declaration',
38
38
  'local_function_statement',
39
39
  ];
40
+ const BUILTIN_TYPE_NAMES = new Set([
41
+ 'bool',
42
+ 'byte',
43
+ 'char',
44
+ 'decimal',
45
+ 'double',
46
+ 'float',
47
+ 'int',
48
+ 'long',
49
+ 'object',
50
+ 'sbyte',
51
+ 'short',
52
+ 'string',
53
+ 'uint',
54
+ 'ulong',
55
+ 'ushort',
56
+ 'void',
57
+ ]);
58
+ function shouldEmitReadMember(memberNode) {
59
+ const parent = memberNode.parent;
60
+ if (parent === null)
61
+ return true;
62
+ switch (parent.type) {
63
+ case 'invocation_expression':
64
+ return parent.childForFieldName('function')?.id !== memberNode.id;
65
+ case 'assignment_expression':
66
+ return parent.childForFieldName('left')?.id !== memberNode.id;
67
+ default:
68
+ return true;
69
+ }
70
+ }
40
71
  export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
41
72
  // Skip the parse when the caller (parse phase's scopeTreeCache)
42
73
  // already produced a Tree for this source. Cache miss = re-parse,
@@ -83,6 +114,13 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
83
114
  out.push(grouped);
84
115
  continue;
85
116
  }
117
+ if (grouped['@reference.read.member'] !== undefined) {
118
+ const anchor = grouped['@reference.read.member'];
119
+ const memberNode = findNodeAtRange(tree.rootNode, anchor.range, 'member_access_expression');
120
+ if (memberNode === null || !shouldEmitReadMember(memberNode)) {
121
+ continue;
122
+ }
123
+ }
86
124
  // Synthesize `this` / `base` receiver type-bindings on every
87
125
  // instance method-like. Tree-sitter can't cleanly express "the
88
126
  // implicit receiver of a non-static member of a class/struct/
@@ -166,8 +204,63 @@ export function emitCsharpScopeCaptures(sourceText, _filePath, cachedTree) {
166
204
  }
167
205
  }
168
206
  }
207
+ out.push(...synthesizeGenericTypeArgumentReferences(tree.rootNode));
208
+ return out;
209
+ }
210
+ function synthesizeGenericTypeArgumentReferences(root) {
211
+ const out = [];
212
+ // Treat all generic type arguments as static type references, including
213
+ // declaration signatures and call-site generic instantiations.
214
+ visit(root, (node) => {
215
+ if (node.type !== 'generic_name')
216
+ return;
217
+ const args = findNamedChild(node, 'type_argument_list');
218
+ if (args === null)
219
+ return;
220
+ for (const arg of args.namedChildren) {
221
+ if (arg === null)
222
+ continue;
223
+ const nameNode = terminalTypeNameNode(arg);
224
+ if (nameNode === null)
225
+ continue;
226
+ if (BUILTIN_TYPE_NAMES.has(nameNode.text))
227
+ continue;
228
+ out.push({
229
+ '@reference.type': nodeToCapture('@reference.type', nameNode),
230
+ '@reference.name': nodeToCapture('@reference.name', nameNode),
231
+ });
232
+ }
233
+ });
169
234
  return out;
170
235
  }
236
+ function terminalTypeNameNode(node) {
237
+ switch (node.type) {
238
+ case 'identifier':
239
+ return node;
240
+ case 'nullable_type':
241
+ return node.firstNamedChild === null ? null : terminalTypeNameNode(node.firstNamedChild);
242
+ case 'qualified_name':
243
+ return node.lastNamedChild;
244
+ case 'generic_name':
245
+ return node.childForFieldName('name') ?? node.firstNamedChild;
246
+ default:
247
+ return null;
248
+ }
249
+ }
250
+ function findNamedChild(node, type) {
251
+ for (const child of node.namedChildren) {
252
+ if (child !== null && child.type === type)
253
+ return child;
254
+ }
255
+ return null;
256
+ }
257
+ function visit(node, cb) {
258
+ cb(node);
259
+ for (const child of node.namedChildren) {
260
+ if (child !== null)
261
+ visit(child, cb);
262
+ }
263
+ }
171
264
  /** C# 12 primary constructor: `class X(a, b) { }` / `record X(a, b)`.
172
265
  * The parameters are a bare `parameter_list` named child of the type
173
266
  * declaration (no `constructor_declaration` node). Emit a synthetic
@@ -497,6 +497,12 @@ const CSHARP_SCOPE_QUERY = `
497
497
  left: (member_access_expression
498
498
  expression: "base" @reference.receiver
499
499
  name: (identifier) @reference.name)) @reference.write.member
500
+
501
+ ;; References — field/property reads: \`obj.Name\`
502
+ ;; Emit-side filtering drops call targets and assignment left-hand sides.
503
+ (member_access_expression
504
+ expression: (_) @reference.receiver
505
+ name: (identifier) @reference.name) @reference.read.member
500
506
  `;
501
507
  let _parser = null;
502
508
  let _query = null;
@@ -237,9 +237,10 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
237
237
  'Template',
238
238
  'Module',
239
239
  ];
240
+ const propertyHeader = 'id,name,filePath,startLine,endLine,content,description,declaredType';
240
241
  const multiLangWriters = new Map();
241
242
  for (const t of MULTI_LANG_TYPES) {
242
- multiLangWriters.set(t, new BufferedCSVWriter(path.join(csvDir, `${t.toLowerCase()}.csv`), multiLangHeader));
243
+ multiLangWriters.set(t, new BufferedCSVWriter(path.join(csvDir, `${t.toLowerCase()}.csv`), t === 'Property' ? propertyHeader : multiLangHeader));
243
244
  }
244
245
  const codeWriterMap = {
245
246
  Function: functionWriter,
@@ -390,6 +391,9 @@ export const streamAllCSVsToDisk = async (graph, repoPath, csvDir) => {
390
391
  escapeCSVNumber(node.properties.endLine, -1),
391
392
  escapeCSVField(content),
392
393
  escapeCSVField(node.properties.description || ''),
394
+ ...(node.label === 'Property'
395
+ ? [escapeCSVField(node.properties.declaredType || '')]
396
+ : []),
393
397
  ].join(','));
394
398
  }
395
399
  }
@@ -520,6 +520,9 @@ const getCopyQuery = (table, filePath) => {
520
520
  if (table === 'Method') {
521
521
  return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description, parameterCount, returnType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
522
522
  }
523
+ if (table === 'Property') {
524
+ return `COPY ${t}(id, name, filePath, startLine, endLine, content, description, declaredType) FROM "${filePath}" ${COPY_CSV_OPTS}`;
525
+ }
523
526
  // TypeScript/JS code element tables have isExported; multi-language tables do not
524
527
  if (TABLES_WITH_EXPORTED.has(table)) {
525
528
  return `COPY ${t}(id, name, filePath, startLine, endLine, isExported, content, description) FROM "${filePath}" ${COPY_CSV_OPTS}`;
@@ -569,6 +572,12 @@ export const insertNodeToLbug = async (label, properties, dbPath) => {
569
572
  : '';
570
573
  query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, isExported: ${!!properties.isExported}, content: ${escapeValue(properties.content || '')}${descPart}})`;
571
574
  }
575
+ else if (label === 'Property') {
576
+ const descPart = properties.description
577
+ ? `, description: ${escapeValue(properties.description)}`
578
+ : '';
579
+ query = `CREATE (n:${t} {id: ${escapeValue(properties.id)}, name: ${escapeValue(properties.name)}, filePath: ${escapeValue(properties.filePath)}, startLine: ${properties.startLine || 0}, endLine: ${properties.endLine || 0}, content: ${escapeValue(properties.content || '')}${descPart}, declaredType: ${escapeValue(properties.declaredType || '')}})`;
580
+ }
572
581
  else {
573
582
  // Multi-language tables (Struct, Impl, Trait, Macro, etc.) — no isExported
574
583
  const descPart = properties.description
@@ -646,6 +655,12 @@ export const batchInsertNodesToLbug = async (nodes, dbPath) => {
646
655
  : '';
647
656
  query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.isExported = ${!!properties.isExported}, n.content = ${escapeValue(properties.content || '')}${descPart}`;
648
657
  }
658
+ else if (label === 'Property') {
659
+ const descPart = properties.description
660
+ ? `, n.description = ${escapeValue(properties.description)}`
661
+ : '';
662
+ query = `MERGE (n:${t} {id: ${escapeValue(properties.id)}}) SET n.name = ${escapeValue(properties.name)}, n.filePath = ${escapeValue(properties.filePath)}, n.startLine = ${properties.startLine || 0}, n.endLine = ${properties.endLine || 0}, n.content = ${escapeValue(properties.content || '')}${descPart}, n.declaredType = ${escapeValue(properties.declaredType || '')}`;
663
+ }
649
664
  else {
650
665
  const descPart = properties.description
651
666
  ? `, n.description = ${escapeValue(properties.description)}`
@@ -32,7 +32,7 @@ export declare const TYPE_ALIAS_SCHEMA: string;
32
32
  export declare const CONST_SCHEMA: string;
33
33
  export declare const STATIC_SCHEMA: string;
34
34
  export declare const VARIABLE_SCHEMA: string;
35
- export declare const PROPERTY_SCHEMA: string;
35
+ export declare const PROPERTY_SCHEMA = "\nCREATE NODE TABLE `Property` (\n id STRING,\n name STRING,\n filePath STRING,\n startLine INT64,\n endLine INT64,\n content STRING,\n description STRING,\n declaredType STRING,\n PRIMARY KEY (id)\n)";
36
36
  export declare const RECORD_SCHEMA: string;
37
37
  export declare const DELEGATE_SCHEMA: string;
38
38
  export declare const ANNOTATION_SCHEMA: string;
@@ -150,7 +150,18 @@ export const TYPE_ALIAS_SCHEMA = CODE_ELEMENT_BASE('TypeAlias');
150
150
  export const CONST_SCHEMA = CODE_ELEMENT_BASE('Const');
151
151
  export const STATIC_SCHEMA = CODE_ELEMENT_BASE('Static');
152
152
  export const VARIABLE_SCHEMA = CODE_ELEMENT_BASE('Variable');
153
- export const PROPERTY_SCHEMA = CODE_ELEMENT_BASE('Property');
153
+ export const PROPERTY_SCHEMA = `
154
+ CREATE NODE TABLE \`Property\` (
155
+ id STRING,
156
+ name STRING,
157
+ filePath STRING,
158
+ startLine INT64,
159
+ endLine INT64,
160
+ content STRING,
161
+ description STRING,
162
+ declaredType STRING,
163
+ PRIMARY KEY (id)
164
+ )`;
154
165
  export const RECORD_SCHEMA = CODE_ELEMENT_BASE('Record');
155
166
  export const DELEGATE_SCHEMA = CODE_ELEMENT_BASE('Delegate');
156
167
  export const ANNOTATION_SCHEMA = CODE_ELEMENT_BASE('Annotation');
@@ -1443,10 +1443,11 @@ export class LocalBackend {
1443
1443
  // Categorized incoming refs
1444
1444
  const incomingRows = await executeParameterized(repo.id, `
1445
1445
  MATCH (caller)-[r:CodeRelation]->(n {id: $symId})
1446
- WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
1446
+ WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
1447
1447
  RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
1448
1448
  LIMIT 30
1449
1449
  `, { symId });
1450
+ let typedPropertyRows = [];
1450
1451
  // Fix #480: Class/Interface nodes have no direct CALLS/IMPORTS edges —
1451
1452
  // those point to Constructor and File nodes respectively. Fetch those
1452
1453
  // extra incoming refs and merge them in so context() shows real callers.
@@ -1476,13 +1477,13 @@ export class LocalBackend {
1476
1477
  }
1477
1478
  if (isClassLike) {
1478
1479
  try {
1479
- // Run both incoming-ref queries in parallel — they are independent.
1480
- const [ctorIncoming, fileIncoming] = await Promise.all([
1480
+ // Run incoming-ref queries in parallel — they are independent.
1481
+ const [ctorIncoming, fileIncoming, typedPropertyIncoming, typedProperties] = await Promise.all([
1481
1482
  executeParameterized(repo.id, `
1482
1483
  MATCH (n)-[hm:CodeRelation]->(ctor:Constructor)
1483
1484
  WHERE n.id = $symId AND hm.type = 'HAS_METHOD'
1484
1485
  MATCH (caller)-[r:CodeRelation]->(ctor)
1485
- WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'ACCESSES']
1486
+ WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'ACCESSES']
1486
1487
  RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
1487
1488
  LIMIT 30
1488
1489
  `, { symId }),
@@ -1494,12 +1495,40 @@ export class LocalBackend {
1494
1495
  RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
1495
1496
  LIMIT 30
1496
1497
  `, { symId }),
1498
+ executeParameterized(repo.id, `
1499
+ MATCH (p:\`Property\`)
1500
+ WHERE p.declaredType = $name
1501
+ OR p.declaredType STARTS WITH $genericPrefix
1502
+ OR p.declaredType CONTAINS $genericArg
1503
+ MATCH (caller)-[r:CodeRelation]->(p)
1504
+ WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'ACCESSES']
1505
+ RETURN r.type AS relType, caller.id AS uid, caller.name AS name, caller.filePath AS filePath, labels(caller)[0] AS kind
1506
+ LIMIT 30
1507
+ `, {
1508
+ name: sym.name,
1509
+ genericPrefix: `${sym.name}<`,
1510
+ genericArg: `<${sym.name}>`,
1511
+ }),
1512
+ executeParameterized(repo.id, `
1513
+ MATCH (p:\`Property\`)
1514
+ WHERE p.declaredType = $name
1515
+ OR p.declaredType STARTS WITH $genericPrefix
1516
+ OR p.declaredType CONTAINS $genericArg
1517
+ RETURN p.id AS uid, p.name AS name, p.filePath AS filePath, labels(p)[0] AS kind,
1518
+ p.declaredType AS declaredType
1519
+ LIMIT 30
1520
+ `, {
1521
+ name: sym.name,
1522
+ genericPrefix: `${sym.name}<`,
1523
+ genericArg: `<${sym.name}>`,
1524
+ }),
1497
1525
  ]);
1526
+ typedPropertyRows = typedProperties;
1498
1527
  // Deduplicate by (relType, uid) — a caller can have multiple relation
1499
1528
  // types to the same target (e.g. both IMPORTS and CALLS), and each
1500
1529
  // must be preserved so every category appears in the output.
1501
1530
  const seenKeys = new Set(incomingRows.map((r) => `${r.relType || r[0]}:${r.uid || r[1]}`));
1502
- for (const r of [...ctorIncoming, ...fileIncoming]) {
1531
+ for (const r of [...ctorIncoming, ...fileIncoming, ...typedPropertyIncoming]) {
1503
1532
  const key = `${r.relType || r[0]}:${r.uid || r[1]}`;
1504
1533
  if (!seenKeys.has(key)) {
1505
1534
  seenKeys.add(key);
@@ -1514,7 +1543,7 @@ export class LocalBackend {
1514
1543
  // Categorized outgoing refs
1515
1544
  const outgoingRows = await executeParameterized(repo.id, `
1516
1545
  MATCH (n {id: $symId})-[r:CodeRelation]->(target)
1517
- WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
1546
+ WHERE r.type IN ['CALLS', 'IMPORTS', 'EXTENDS', 'IMPLEMENTS', 'USES', 'HAS_METHOD', 'HAS_PROPERTY', 'METHOD_OVERRIDES', 'OVERRIDES', 'METHOD_IMPLEMENTS', 'ACCESSES']
1518
1547
  RETURN r.type AS relType, target.id AS uid, target.name AS name, target.filePath AS filePath, labels(target)[0] AS kind
1519
1548
  LIMIT 30
1520
1549
  `, { symId });
@@ -1593,6 +1622,17 @@ export class LocalBackend {
1593
1622
  },
1594
1623
  incoming: categorize(incomingRows),
1595
1624
  outgoing: categorize(outgoingRows),
1625
+ ...(typedPropertyRows.length > 0
1626
+ ? {
1627
+ typed_properties: typedPropertyRows.map((r) => ({
1628
+ uid: r.uid || r[0],
1629
+ name: r.name || r[1],
1630
+ filePath: r.filePath || r[2],
1631
+ kind: r.kind || r[3],
1632
+ declaredType: r.declaredType || r[4],
1633
+ })),
1634
+ }
1635
+ : {}),
1596
1636
  processes: processRows.map((r) => ({
1597
1637
  id: r.pid || r[0],
1598
1638
  name: r.label || r[1],
@@ -2024,6 +2064,7 @@ export class LocalBackend {
2024
2064
  const maxDepth = params.maxDepth || 3;
2025
2065
  // Map legacy relation type names before filtering (backward compat for OVERRIDES → METHOD_OVERRIDES)
2026
2066
  const mappedRelTypes = params.relationTypes?.flatMap((t) => t === 'OVERRIDES' ? ['OVERRIDES', 'METHOD_OVERRIDES'] : [t]);
2067
+ const hasExplicitRelationTypes = mappedRelTypes !== undefined && mappedRelTypes.length > 0;
2027
2068
  const rawRelTypes = mappedRelTypes && mappedRelTypes.length > 0
2028
2069
  ? mappedRelTypes.filter((t) => VALID_RELATION_TYPES.has(t))
2029
2070
  : [
@@ -2031,6 +2072,7 @@ export class LocalBackend {
2031
2072
  'IMPORTS',
2032
2073
  'EXTENDS',
2033
2074
  'IMPLEMENTS',
2075
+ 'USES',
2034
2076
  'METHOD_OVERRIDES',
2035
2077
  'OVERRIDES',
2036
2078
  'METHOD_IMPLEMENTS',
@@ -2042,6 +2084,7 @@ export class LocalBackend {
2042
2084
  'IMPORTS',
2043
2085
  'EXTENDS',
2044
2086
  'IMPLEMENTS',
2087
+ 'USES',
2045
2088
  'METHOD_OVERRIDES',
2046
2089
  'OVERRIDES',
2047
2090
  'METHOD_IMPLEMENTS',
@@ -2092,9 +2135,14 @@ export class LocalBackend {
2092
2135
  filePath: outcome.symbol.filePath,
2093
2136
  };
2094
2137
  const symType = outcome.resolvedLabel || outcome.symbol.type || '';
2138
+ const effectiveRelationTypes = (symType === 'Class' || symType === 'Interface') &&
2139
+ !hasExplicitRelationTypes &&
2140
+ !relationTypes.includes('ACCESSES')
2141
+ ? [...relationTypes, 'ACCESSES']
2142
+ : relationTypes;
2095
2143
  return this._runImpactBFS(repo, sym, symType, direction, {
2096
2144
  maxDepth,
2097
- relationTypes,
2145
+ relationTypes: effectiveRelationTypes,
2098
2146
  includeTests,
2099
2147
  minConfidence,
2100
2148
  });
@@ -2149,6 +2197,24 @@ export class LocalBackend {
2149
2197
  frontier.push(rid);
2150
2198
  }
2151
2199
  }
2200
+ const typedPropertyRows = await executeParameterized(repo.id, `
2201
+ MATCH (p:\`Property\`)
2202
+ WHERE p.declaredType = $name
2203
+ OR p.declaredType STARTS WITH $genericPrefix
2204
+ OR p.declaredType CONTAINS $genericArg
2205
+ RETURN p.id AS id, p.name AS name, labels(p)[0] AS type, p.filePath AS filePath
2206
+ `, {
2207
+ name: sym.name,
2208
+ genericPrefix: `${sym.name}<`,
2209
+ genericArg: `<${sym.name}>`,
2210
+ });
2211
+ for (const r of typedPropertyRows) {
2212
+ const rid = r.id || r[0];
2213
+ if (rid && !visited.has(rid)) {
2214
+ visited.add(rid);
2215
+ frontier.push(rid);
2216
+ }
2217
+ }
2152
2218
  }
2153
2219
  catch (e) {
2154
2220
  logQueryError('impact:class-node-expansion', e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.4-rc.97",
3
+ "version": "1.6.4-rc.99",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",