@siduri-x/core 2.0.4 → 2.0.6

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.
package/dist/siduri-db.js CHANGED
@@ -26,6 +26,7 @@ class SiduriDatabase {
26
26
  companion_id TEXT PRIMARY KEY,
27
27
  name TEXT NOT NULL,
28
28
  archetype TEXT,
29
+ role TEXT,
29
30
  origin TEXT,
30
31
  ethos TEXT,
31
32
  version TEXT NOT NULL,
@@ -58,6 +59,8 @@ class SiduriDatabase {
58
59
  companion_id TEXT NOT NULL,
59
60
  entity_id TEXT NOT NULL,
60
61
  entity_type TEXT NOT NULL DEFAULT 'human',
62
+ name TEXT,
63
+ affiliation TEXT,
61
64
  role TEXT DEFAULT 'user',
62
65
  stance TEXT DEFAULT 'neutral',
63
66
  trust_score REAL DEFAULT 0.5,
@@ -215,6 +218,24 @@ class SiduriDatabase {
215
218
  catch {
216
219
  // Column already exists
217
220
  }
221
+ try {
222
+ this.db.exec("ALTER TABLE self_identity ADD COLUMN role TEXT");
223
+ }
224
+ catch {
225
+ // Column already exists
226
+ }
227
+ try {
228
+ this.db.exec("ALTER TABLE self_relationships ADD COLUMN name TEXT");
229
+ }
230
+ catch {
231
+ // Column already exists
232
+ }
233
+ try {
234
+ this.db.exec("ALTER TABLE self_relationships ADD COLUMN affiliation TEXT");
235
+ }
236
+ catch {
237
+ // Column already exists
238
+ }
218
239
  }
219
240
  close() {
220
241
  this.db.close();
@@ -231,6 +252,7 @@ class SiduriDatabase {
231
252
  companionId: row.companion_id,
232
253
  name: row.name,
233
254
  archetype: row.archetype || undefined,
255
+ role: row.role || row.archetype || undefined,
234
256
  origin: row.origin || undefined,
235
257
  ethos: row.ethos || undefined,
236
258
  version: row.version,
@@ -238,18 +260,21 @@ class SiduriDatabase {
238
260
  };
239
261
  }
240
262
  setIdentity(identity) {
263
+ const role = identity.role || identity.archetype || null;
264
+ const archetype = identity.archetype || identity.role || null;
241
265
  const stmt = this.db.prepare(`
242
- INSERT INTO self_identity (companion_id, name, archetype, origin, ethos, version, updated_at)
243
- VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
266
+ INSERT INTO self_identity (companion_id, name, archetype, role, origin, ethos, version, updated_at)
267
+ VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
244
268
  ON CONFLICT(companion_id) DO UPDATE SET
245
269
  name = excluded.name,
246
270
  archetype = excluded.archetype,
271
+ role = excluded.role,
247
272
  origin = excluded.origin,
248
273
  ethos = excluded.ethos,
249
274
  version = excluded.version,
250
275
  updated_at = datetime('now')
251
276
  `);
252
- stmt.run(identity.companionId, identity.name, identity.archetype || null, identity.origin || null, identity.ethos || null, identity.version);
277
+ stmt.run(identity.companionId, identity.name, archetype, role, identity.origin || null, identity.ethos || null, identity.version);
253
278
  }
254
279
  getPersonality(companionId) {
255
280
  const stmt = this.db.prepare('SELECT * FROM self_personality WHERE companion_id = ?');
@@ -311,6 +336,14 @@ class SiduriDatabase {
311
336
  const stmt = this.db.prepare(`
312
337
  INSERT INTO self_directives (id, companion_id, priority, directive, status, category, scope_actor, supersedes_id, created_at)
313
338
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
339
+ ON CONFLICT(id) DO UPDATE SET
340
+ companion_id = excluded.companion_id,
341
+ priority = excluded.priority,
342
+ directive = excluded.directive,
343
+ status = excluded.status,
344
+ category = excluded.category,
345
+ scope_actor = excluded.scope_actor,
346
+ supersedes_id = excluded.supersedes_id
314
347
  `);
315
348
  stmt.run(directive.id, directive.companionId, directive.priority !== undefined ? directive.priority : 50, directive.directive, normalizeStatus(directive.status, 'active'), directive.category || 'behavioral', directive.scopeActor || null, directive.supersedesId || null, directive.createdAt || new Date().toISOString());
316
349
  }
@@ -332,6 +365,9 @@ class SiduriDatabase {
332
365
  return;
333
366
  }
334
367
  if (normalizeStatus(row.status) !== 'pending') {
368
+ if (normalizeStatus(row.status) === 'active') {
369
+ return;
370
+ }
335
371
  throw new Error(`Cannot approve directive '${id}': invalid transition from status '${row.status}' to 'active' (only pending directives can be approved)`);
336
372
  }
337
373
  // If this directive supersedes an earlier directive, transition that prior directive to superseded
@@ -407,14 +443,18 @@ class SiduriDatabase {
407
443
  }
408
444
  }
409
445
  getRelationship(companionId, entityId) {
410
- const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND entity_id = ?');
411
- const row = stmt.get(companionId, entityId);
446
+ const stripped = entityId.startsWith('actor:') ? entityId.slice(6) : entityId;
447
+ const prefixed = entityId.startsWith('actor:') ? entityId : `actor:${entityId}`;
448
+ const stmt = this.db.prepare('SELECT * FROM self_relationships WHERE companion_id = ? AND (entity_id = ? OR entity_id = ? OR entity_id = ?)');
449
+ const row = stmt.get(companionId, entityId, stripped, prefixed);
412
450
  if (!row)
413
451
  return undefined;
414
452
  return {
415
453
  companionId: row.companion_id,
416
454
  entityId: row.entity_id,
417
455
  entityType: row.entity_type || 'human',
456
+ name: row.name || undefined,
457
+ affiliation: row.affiliation || undefined,
418
458
  role: row.role || 'user',
419
459
  stance: row.stance || 'neutral',
420
460
  trustScore: row.trust_score !== undefined && row.trust_score !== null ? row.trust_score : 0.5,
@@ -429,6 +469,8 @@ class SiduriDatabase {
429
469
  companionId: row.companion_id,
430
470
  entityId: row.entity_id,
431
471
  entityType: row.entity_type || 'human',
472
+ name: row.name || undefined,
473
+ affiliation: row.affiliation || undefined,
432
474
  role: row.role || 'user',
433
475
  stance: row.stance || 'neutral',
434
476
  trustScore: row.trust_score !== undefined && row.trust_score !== null ? row.trust_score : 0.5,
@@ -439,18 +481,20 @@ class SiduriDatabase {
439
481
  }
440
482
  upsertRelationship(rel) {
441
483
  const stmt = this.db.prepare(`
442
- INSERT INTO self_relationships (companion_id, entity_id, entity_type, role, stance, trust_score, familiarity, interaction_conventions, updated_at)
443
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
484
+ INSERT INTO self_relationships (companion_id, entity_id, entity_type, name, affiliation, role, stance, trust_score, familiarity, interaction_conventions, updated_at)
485
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
444
486
  ON CONFLICT(companion_id, entity_id) DO UPDATE SET
445
487
  entity_type = excluded.entity_type,
446
- role = excluded.role,
447
- stance = excluded.stance,
488
+ name = COALESCE(excluded.name, self_relationships.name),
489
+ affiliation = COALESCE(excluded.affiliation, self_relationships.affiliation),
490
+ role = COALESCE(excluded.role, self_relationships.role),
491
+ stance = COALESCE(excluded.stance, self_relationships.stance),
448
492
  trust_score = excluded.trust_score,
449
493
  familiarity = excluded.familiarity,
450
494
  interaction_conventions = excluded.interaction_conventions,
451
495
  updated_at = datetime('now')
452
496
  `);
453
- stmt.run(rel.companionId, rel.entityId, rel.entityType || 'human', rel.role || 'user', rel.stance || 'neutral', rel.trustScore !== undefined && rel.trustScore !== null ? rel.trustScore : 0.5, rel.familiarity !== undefined && rel.familiarity !== null ? rel.familiarity : 0.5, JSON.stringify(rel.interactionConventions || []));
497
+ stmt.run(rel.companionId, rel.entityId, rel.entityType || 'human', rel.name || null, rel.affiliation || null, rel.role || 'user', rel.stance || 'neutral', rel.trustScore !== undefined && rel.trustScore !== null ? rel.trustScore : 0.5, rel.familiarity !== undefined && rel.familiarity !== null ? rel.familiarity : 0.5, JSON.stringify(rel.interactionConventions || []));
454
498
  }
455
499
  getExemplars(companionId) {
456
500
  const stmt = this.db.prepare('SELECT * FROM self_exemplars WHERE companion_id = ? ORDER BY created_at ASC');
@@ -636,7 +680,7 @@ class SiduriDatabase {
636
680
  INSERT INTO memory_claims (id, companion_id, subject, predicate, value, status, confidence, valid_from, valid_until, evidence, asserted_at, supersedes, source_event_id)
637
681
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, coalesce(?, datetime('now')), ?, ?)
638
682
  `);
639
- stmt.run(id, claim.companionId, claim.subject, claim.predicate, claim.value, status, confidence, claim.validFrom || null, claim.validUntil || null, claim.evidence ? JSON.stringify(claim.evidence) : null, assertedAt || null, claim.supersedes || null, claim.sourceEventId || null);
683
+ stmt.run(id, claim.companionId || 'default', claim.subject, claim.predicate, claim.value, status, confidence, claim.validFrom || null, claim.validUntil || null, claim.evidence ? JSON.stringify(claim.evidence) : null, assertedAt || null, claim.supersedes || null, claim.sourceEventId || null);
640
684
  return {
641
685
  ...claim,
642
686
  id,
@@ -656,6 +700,9 @@ class SiduriDatabase {
656
700
  return;
657
701
  }
658
702
  if (normalizeStatus(row.status) !== 'pending') {
703
+ if (normalizeStatus(row.status) === 'approved') {
704
+ return;
705
+ }
659
706
  throw new Error(`Cannot approve claim '${id}': invalid transition from status '${row.status}' to 'approved' (only pending claims can be approved)`);
660
707
  }
661
708
  // If this claim supersedes an earlier claim, transition that prior claim to superseded
@@ -678,6 +725,120 @@ class SiduriDatabase {
678
725
  const stmt = this.db.prepare("UPDATE memory_claims SET status = 'approved' WHERE id = ? AND LOWER(status) = 'pending'");
679
726
  stmt.run(id);
680
727
  }
728
+ // Canonically promote approved claim to Self domain state (identity, role, relationships)
729
+ const approvedClaim = this.getClaim(id);
730
+ if (approvedClaim) {
731
+ this.promoteClaimToSelf(approvedClaim);
732
+ }
733
+ }
734
+ /**
735
+ * Canonically promotes a Claim into Self domain tables.
736
+ */
737
+ promoteClaimToSelf(claim) {
738
+ const companionId = claim.companionId || 'default';
739
+ const subject = (claim.subject || '').toLowerCase();
740
+ const predicate = (claim.predicate || '').toLowerCase();
741
+ const value = claim.value || '';
742
+ if (!value)
743
+ return;
744
+ // 1. Identity mutations: companion identity/role/origin/name/ethos
745
+ if (subject.startsWith('companion:') ||
746
+ subject === 'companion' ||
747
+ subject === 'siduri' ||
748
+ subject === 'self') {
749
+ const existing = this.getIdentity(companionId) || {
750
+ companionId,
751
+ name: 'Siduri',
752
+ version: '1.0.0',
753
+ updatedAt: new Date().toISOString(),
754
+ };
755
+ if (predicate === 'role' || predicate === 'archetype') {
756
+ existing.archetype = value;
757
+ existing.role = value;
758
+ this.setIdentity(existing);
759
+ this.commitDirective({
760
+ id: `dir-role-${claim.id || Date.now()}`,
761
+ companionId,
762
+ priority: 70,
763
+ directive: `Acknowledge role as ${value}`,
764
+ status: 'active',
765
+ category: 'relational',
766
+ createdAt: new Date().toISOString(),
767
+ });
768
+ }
769
+ else if (predicate === 'origin' || predicate === 'created_by') {
770
+ existing.origin = value;
771
+ this.setIdentity(existing);
772
+ }
773
+ else if (predicate === 'name') {
774
+ existing.name = value;
775
+ this.setIdentity(existing);
776
+ }
777
+ else if (predicate === 'ethos') {
778
+ existing.ethos = value;
779
+ this.setIdentity(existing);
780
+ }
781
+ return;
782
+ }
783
+ // 2. Relationship mutations: creator or user stated relationship, name, or affiliation
784
+ if (claim.claimType === 'relationship' ||
785
+ predicate === 'stated_relationship' ||
786
+ predicate === 'relationship' ||
787
+ predicate === 'relationship_to_siduri' ||
788
+ (predicate === 'name' && (subject.startsWith('actor:') || subject === 'user' || subject === 'primary_user')) ||
789
+ predicate === 'preferred_address' ||
790
+ predicate === 'affiliation') {
791
+ const rawSubject = (claim.subject || 'actor:user').replace(/^actor:actor:/, 'actor:');
792
+ const isCreator = value.toLowerCase() === 'creator';
793
+ const isName = predicate === 'name' || predicate === 'preferred_address';
794
+ const isAffil = predicate === 'affiliation';
795
+ const existingRel = this.getRelationship(companionId, rawSubject);
796
+ const role = isCreator ? value : (existingRel?.role || (isName || isAffil ? existingRel?.role : value));
797
+ const name = isName ? value : existingRel?.name;
798
+ const affiliation = isAffil ? value : existingRel?.affiliation;
799
+ const stance = isCreator ? 'familiar_loyal' : (existingRel?.stance || 'neutral');
800
+ const trustScore = isCreator ? 1.0 : (existingRel?.trustScore ?? 0.8);
801
+ const familiarity = isCreator ? 0.9 : (existingRel?.familiarity ?? 0.5);
802
+ const interactionConventions = isCreator
803
+ ? Array.from(new Set([...(existingRel?.interactionConventions || []), 'Direct communication', 'Highest administrative trust']))
804
+ : (existingRel?.interactionConventions || []);
805
+ this.upsertRelationship({
806
+ companionId,
807
+ entityId: rawSubject,
808
+ entityType: 'human',
809
+ name,
810
+ affiliation,
811
+ role,
812
+ stance,
813
+ trustScore,
814
+ familiarity,
815
+ interactionConventions,
816
+ });
817
+ if (isName) {
818
+ this.commitDirective({
819
+ id: `dir-name-${claim.id || Date.now()}`,
820
+ companionId,
821
+ priority: 75,
822
+ directive: `Address ${rawSubject} as ${value}`,
823
+ status: 'active',
824
+ category: 'relational',
825
+ createdAt: new Date().toISOString(),
826
+ });
827
+ }
828
+ return;
829
+ }
830
+ // 3. Behavioral rule claim
831
+ if (predicate === 'behavioral_rule' || predicate === 'rule') {
832
+ this.commitDirective({
833
+ id: `dir-rule-${claim.id || Date.now()}`,
834
+ companionId,
835
+ priority: 60,
836
+ directive: value,
837
+ status: 'active',
838
+ category: 'behavioral',
839
+ createdAt: new Date().toISOString(),
840
+ });
841
+ }
681
842
  }
682
843
  rejectClaim(id, companionId) {
683
844
  if (companionId) {
@@ -730,7 +730,8 @@ describe('SiduriDatabase', () => {
730
730
  status: 'active',
731
731
  category: 'behavioral',
732
732
  });
733
- expect(() => db.approveDirective('dir-active-1')).toThrow(/invalid transition from status 'active' to 'active'/i);
733
+ // Approving an already-active directive is now idempotent (no-op, does not throw)
734
+ expect(() => db.approveDirective('dir-active-1')).not.toThrow();
734
735
  // 3. Rejecting an already ACTIVE directive throws
735
736
  expect(() => db.rejectDirective('dir-active-1')).toThrow(/invalid transition from status 'active' to 'rejected'/i);
736
737
  });
@@ -4,12 +4,11 @@ export interface ExtractedTeaching {
4
4
  behaviorProposals: BehaviorProposal[];
5
5
  }
6
6
  /**
7
- * Deterministically extracts teaching candidates from user messages according to single-owner model.
7
+ * Deterministic teaching extraction placeholder.
8
8
  *
9
- * Rules:
10
- * - Scoped to the requesting actor context (subject: `actor:${actorId}`), NEVER `primary_user`.
11
- * - Candidates are pending proposals only, never active/approved.
12
- * - In a single-owner companion, preferences apply across the companion instance without audience partitioning.
13
- * - Companion identity is isolated.
9
+ * In Siduri-X, conversational memory and behavioral proposals are generated
10
+ * directly by the Brain LLM via structured cognitive planning (e.g. `submitResponsePlan`).
11
+ * Deterministic regex matching has been removed in favor of pure LLM comprehension,
12
+ * as all candidates are quarantined in 'pending' status until explicitly approved by the user.
14
13
  */
15
- export declare function extractDeterministicTeaching(message: string, context?: RequestContext, sourceEventId?: string): ExtractedTeaching;
14
+ export declare function extractDeterministicTeaching(_message: string, _context?: RequestContext, _sourceEventId?: string): ExtractedTeaching;
package/dist/teaching.js CHANGED
@@ -1,132 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.extractDeterministicTeaching = extractDeterministicTeaching;
4
- function cleanValue(value, limit = 160) {
5
- return value.replace(/\s+/g, ' ').replace(/^[ .,!?:;"']+|[ .,!?:;"']+$/g, '').slice(0, limit);
6
- }
7
4
  /**
8
- * Deterministically extracts teaching candidates from user messages according to single-owner model.
5
+ * Deterministic teaching extraction placeholder.
9
6
  *
10
- * Rules:
11
- * - Scoped to the requesting actor context (subject: `actor:${actorId}`), NEVER `primary_user`.
12
- * - Candidates are pending proposals only, never active/approved.
13
- * - In a single-owner companion, preferences apply across the companion instance without audience partitioning.
14
- * - Companion identity is isolated.
7
+ * In Siduri-X, conversational memory and behavioral proposals are generated
8
+ * directly by the Brain LLM via structured cognitive planning (e.g. `submitResponsePlan`).
9
+ * Deterministic regex matching has been removed in favor of pure LLM comprehension,
10
+ * as all candidates are quarantined in 'pending' status until explicitly approved by the user.
15
11
  */
16
- function extractDeterministicTeaching(message, context, sourceEventId) {
17
- const text = cleanValue(message, 1000);
18
- const claims = [];
19
- const behaviorProposals = [];
20
- if (!text) {
21
- return { claims, behaviorProposals };
22
- }
23
- const actorId = context?.actor?.actorId;
24
- const actorSubject = actorId ? `actor:${actorId}` : 'actor:anonymous';
25
- const companionId = context?.companionId || 'default';
26
- const sensitivity = context?.conversation?.channel === 'public' ? 'public' : 'private';
27
- // 1. Companion's Name: "your name is X" / "you are called X"
28
- const companionNameMatch = text.match(/\b(?:your name is|you are called)\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
29
- if (companionNameMatch) {
30
- const name = cleanValue(companionNameMatch[1], 80);
31
- claims.push({
32
- subject: `companion:${companionId}`,
33
- predicate: 'name',
34
- value: name,
35
- content: `The companion's name is ${name}.`,
36
- claimType: 'semantic',
37
- provenance: 'deterministic_teaching',
38
- sensitivity: 'public',
39
- sourceEventId,
40
- });
41
- behaviorProposals.push({
42
- directive: `Acknowledge configured name as ${name}`,
43
- priority: 70,
44
- subject: `companion:${companionId}`,
45
- predicate: 'name',
46
- value: name,
47
- memoryClass: 'identity',
48
- sourceEventId,
49
- });
50
- }
51
- // 2. Actor's Name: "my name is X"
52
- const myNameMatch = text.match(/\bmy name is\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
53
- if (myNameMatch && !/\b(?:private|public|everywhere)\b/i.test(text)) {
54
- const name = cleanValue(myNameMatch[1], 80);
55
- claims.push({
56
- subject: actorSubject,
57
- predicate: 'name',
58
- value: name,
59
- content: `The actor's name is ${name}.`,
60
- claimType: 'preference',
61
- provenance: 'deterministic_teaching',
62
- sensitivity,
63
- sourceEventId,
64
- });
65
- }
66
- // 3. Preferred Address / Call me X: "call me X"
67
- const callMeMatch = text.match(/\b(?:(?:from now on|only),?\s*)?call me\s+(.+?)(?:\s+(?:in private|privately|in public|publicly|everywhere|in direct conversations))?(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
68
- if (callMeMatch) {
69
- const address = cleanValue(callMeMatch[1], 80);
70
- const directiveInstruction = `Address ${actorSubject} as ${address}`;
71
- claims.push({
72
- subject: actorSubject,
73
- predicate: 'preferred_address',
74
- value: address,
75
- content: `The actor's preferred address is ${address}.`,
76
- claimType: 'relationship',
77
- provenance: 'deterministic_teaching',
78
- sensitivity,
79
- sourceEventId,
80
- });
81
- behaviorProposals.push({
82
- directive: directiveInstruction,
83
- priority: 80,
84
- subject: actorSubject,
85
- predicate: 'preferred_address',
86
- value: address,
87
- memoryClass: 'behavioral',
88
- sourceEventId,
89
- });
90
- }
91
- // 4. Stated relationship: "I am your X" / "I'm your creator"
92
- const relMatch = text.match(/\b(?:i am|i'm)\s+your\s+([A-Za-z0-9_\s-]+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
93
- if (relMatch) {
94
- const relationship = cleanValue(relMatch[1], 60);
95
- claims.push({
96
- subject: actorSubject,
97
- predicate: 'stated_relationship',
98
- value: relationship,
99
- content: `The actor stated their relationship as ${relationship}.`,
100
- claimType: 'relationship',
101
- provenance: 'deterministic_teaching',
102
- sensitivity: 'private',
103
- sourceEventId,
104
- });
105
- behaviorProposals.push({
106
- directive: `Recognize ${actorSubject} stated relationship as ${relationship}`,
107
- priority: 75,
108
- subject: actorSubject,
109
- predicate: 'stated_relationship',
110
- value: relationship,
111
- memoryClass: 'relationship',
112
- sourceEventId,
113
- });
114
- }
115
- // 5. Explicit Domain / Preference fact: "my preferred X is Y" / "my X is Y"
116
- const prefMatch = text.match(/\bmy\s+preferred\s+([A-Za-z0-9_]+)\s+is\s+(.+?)(?=\s+and\s+(?:i\b|my\b|you\b)|[.;,]|$)/i);
117
- if (prefMatch) {
118
- const predicate = cleanValue(prefMatch[1], 40);
119
- const val = cleanValue(prefMatch[2], 100);
120
- claims.push({
121
- subject: actorSubject,
122
- predicate: `preferred_${predicate}`,
123
- value: val,
124
- content: `The actor's preferred ${predicate} is ${val}.`,
125
- claimType: 'preference',
126
- provenance: 'deterministic_teaching',
127
- sensitivity,
128
- sourceEventId,
129
- });
130
- }
131
- return { claims, behaviorProposals };
12
+ function extractDeterministicTeaching(_message, _context, _sourceEventId) {
13
+ return { claims: [], behaviorProposals: [] };
132
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siduri-x/core",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "Core runtime types, evidence protocol, action dispatcher, capability validation, and SiduriRuntime protocol",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {