@bramburn/pi-model-council 1.6.3 → 1.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,500 @@
1
+ import type {
2
+ CouncilDecision,
3
+ CouncilInput,
4
+ CouncilMode,
5
+ ModelOpinion,
6
+ } from "./types.js";
7
+
8
+ // JSON Schema for model opinion response
9
+ export const modelOpinionJsonSchema = {
10
+ type: "object",
11
+ additionalProperties: false,
12
+ required: [
13
+ "stance",
14
+ "recommendedApproach",
15
+ "steps",
16
+ "filesToConsider",
17
+ "risks",
18
+ "verification",
19
+ "confidence"
20
+ ],
21
+ properties: {
22
+ stance: { type: "string" },
23
+ recommendedApproach: { type: "string" },
24
+ steps: { type: "array", items: { type: "string" } },
25
+ filesToConsider: {
26
+ type: "array",
27
+ items: {
28
+ type: "object",
29
+ additionalProperties: false,
30
+ required: ["path", "reason", "suggestedAction"],
31
+ properties: {
32
+ path: { type: "string" },
33
+ reason: { type: "string" },
34
+ suggestedAction: { type: "string" }
35
+ }
36
+ }
37
+ },
38
+ risks: { type: "array", items: { type: "string" } },
39
+ verification: { type: "array", items: { type: "string" } },
40
+ confidence: { type: "string", enum: ["low", "medium", "high"] }
41
+ }
42
+ } as const;
43
+
44
+ // JSON Schema for council decision response
45
+ export const councilDecisionJsonSchema = {
46
+ type: "object",
47
+ additionalProperties: false,
48
+ required: [
49
+ "decisionId",
50
+ "mode",
51
+ "confidence",
52
+ "consensus",
53
+ "recommendedPlan",
54
+ "implementationGuidance",
55
+ "modelNotes",
56
+ "handoffPrompt"
57
+ ],
58
+ properties: {
59
+ decisionId: { type: "string" },
60
+ mode: { type: "string", enum: ["fix", "ask", "architecture"] },
61
+ confidence: { type: "string", enum: ["low", "medium", "high"] },
62
+ consensus: {
63
+ type: "object",
64
+ additionalProperties: false,
65
+ required: ["agreements", "disagreements", "unknowns"],
66
+ properties: {
67
+ agreements: { type: "array", items: { type: "string" } },
68
+ disagreements: { type: "array", items: { type: "string" } },
69
+ unknowns: { type: "array", items: { type: "string" } }
70
+ }
71
+ },
72
+ recommendedPlan: {
73
+ type: "object",
74
+ additionalProperties: false,
75
+ required: ["summary", "steps"],
76
+ properties: {
77
+ summary: { type: "string" },
78
+ steps: { type: "array", items: { type: "string" } }
79
+ }
80
+ },
81
+ implementationGuidance: {
82
+ type: "object",
83
+ additionalProperties: false,
84
+ required: ["filesToEdit", "testsToRun", "guardrails"],
85
+ properties: {
86
+ filesToEdit: {
87
+ type: "array",
88
+ items: {
89
+ type: "object",
90
+ additionalProperties: false,
91
+ required: ["path", "reason", "action"],
92
+ properties: {
93
+ path: { type: "string" },
94
+ reason: { type: "string" },
95
+ action: { type: "string" }
96
+ }
97
+ }
98
+ },
99
+ testsToRun: { type: "array", items: { type: "string" } },
100
+ guardrails: { type: "array", items: { type: "string" } }
101
+ }
102
+ },
103
+ modelNotes: {
104
+ type: "array",
105
+ items: {
106
+ type: "object",
107
+ additionalProperties: false,
108
+ required: ["model", "stance", "keyRisks"],
109
+ properties: {
110
+ model: { type: "string" },
111
+ stance: { type: "string" },
112
+ keyRisks: { type: "array", items: { type: "string" } }
113
+ }
114
+ }
115
+ },
116
+ handoffPrompt: { type: "string" }
117
+ }
118
+ } as const;
119
+
120
+ type ParseStatus = "valid" | "repaired" | "fallback";
121
+
122
+ interface ValidationResult<T> {
123
+ ok: boolean;
124
+ value?: T;
125
+ warnings: string[];
126
+ }
127
+
128
+ interface RepairResult<T> {
129
+ value: T;
130
+ parseStatus: ParseStatus;
131
+ warnings: string[];
132
+ }
133
+
134
+ /**
135
+ * Validate a model opinion object against required fields.
136
+ */
137
+ export function validateModelOpinion(value: unknown): ValidationResult<ModelOpinion> {
138
+ const warnings: string[] = [];
139
+
140
+ if (typeof value !== "object" || value === null) {
141
+ return { ok: false, warnings: ["Model opinion is not an object"] };
142
+ }
143
+
144
+ const obj = value as Record<string, unknown>;
145
+
146
+ // Check required fields
147
+ const requiredFields: (keyof ModelOpinion)[] = [
148
+ "stance",
149
+ "recommendedApproach",
150
+ "steps",
151
+ "filesToConsider",
152
+ "risks",
153
+ "verification",
154
+ "confidence"
155
+ ];
156
+
157
+ for (const field of requiredFields) {
158
+ if (!(field in obj)) {
159
+ warnings.push(`Missing required field: ${field}`);
160
+ }
161
+ }
162
+
163
+ // Validate confidence
164
+ const validConfidence = ["low", "medium", "high"];
165
+ if (obj.confidence && !validConfidence.includes(obj.confidence as string)) {
166
+ warnings.push(`Invalid confidence value: ${obj.confidence}`);
167
+ }
168
+
169
+ // Check if all required fields are present and valid
170
+ const hasStance = typeof obj.stance === "string";
171
+ const hasApproach = typeof obj.recommendedApproach === "string";
172
+ const hasSteps = Array.isArray(obj.steps);
173
+
174
+ if (hasStance && hasApproach && hasSteps) {
175
+ return {
176
+ ok: true,
177
+ value: {
178
+ stance: obj.stance as string,
179
+ recommendedApproach: obj.recommendedApproach as string,
180
+ steps: obj.steps as string[],
181
+ filesToConsider: Array.isArray(obj.filesToConsider) ? obj.filesToConsider as ModelOpinion["filesToConsider"] : [],
182
+ risks: Array.isArray(obj.risks) ? obj.risks as string[] : [],
183
+ verification: Array.isArray(obj.verification) ? obj.verification as string[] : [],
184
+ confidence: validConfidence.includes(obj.confidence as string)
185
+ ? obj.confidence as ModelOpinion["confidence"]
186
+ : "medium",
187
+ },
188
+ warnings,
189
+ };
190
+ }
191
+
192
+ return { ok: false, warnings };
193
+ }
194
+
195
+ /**
196
+ * Repair a model opinion object, filling in missing fields with defaults.
197
+ */
198
+ export function repairModelOpinion(value: unknown, rawText: string): RepairResult<ModelOpinion> {
199
+ const warnings: string[] = [];
200
+
201
+ if (typeof value !== "object" || value === null) {
202
+ warnings.push("Model opinion is not an object, creating fallback");
203
+ return {
204
+ value: createFallbackOpinion(rawText),
205
+ parseStatus: "fallback",
206
+ warnings,
207
+ };
208
+ }
209
+
210
+ const obj = value as Record<string, unknown>;
211
+
212
+ // Repair stance
213
+ let stance = "Unclear stance";
214
+ if (typeof obj.stance === "string" && obj.stance.length > 0) {
215
+ stance = obj.stance;
216
+ } else {
217
+ warnings.push("Missing or invalid stance, using default");
218
+ }
219
+
220
+ // Repair recommendedApproach
221
+ let recommendedApproach = rawText.substring(0, 300);
222
+ if (typeof obj.recommendedApproach === "string" && obj.recommendedApproach.length > 0) {
223
+ recommendedApproach = obj.recommendedApproach;
224
+ } else {
225
+ warnings.push("Missing or invalid recommendedApproach, using raw text");
226
+ }
227
+
228
+ // Repair steps
229
+ let steps: string[] = [];
230
+ if (Array.isArray(obj.steps)) {
231
+ steps = obj.steps.filter((s): s is string => typeof s === "string");
232
+ } else if (typeof obj.steps === "string") {
233
+ steps = [obj.steps];
234
+ warnings.push("steps was a string, converted to array");
235
+ } else {
236
+ warnings.push("Missing or invalid steps, using empty array");
237
+ }
238
+
239
+ // Repair filesToConsider
240
+ let filesToConsider: ModelOpinion["filesToConsider"] = [];
241
+ if (Array.isArray(obj.filesToConsider)) {
242
+ filesToConsider = obj.filesToConsider
243
+ .filter((f): f is Record<string, unknown> => typeof f === "object" && f !== null)
244
+ .filter((f) => typeof f.path === "string")
245
+ .map((f) => ({
246
+ path: f.path as string,
247
+ reason: typeof f.reason === "string" ? f.reason : "",
248
+ suggestedAction: typeof f.suggestedAction === "string" ? f.suggestedAction : "",
249
+ }));
250
+ } else {
251
+ warnings.push("Missing or invalid filesToConsider");
252
+ }
253
+
254
+ // Repair risks
255
+ let risks: string[] = [];
256
+ if (Array.isArray(obj.risks)) {
257
+ risks = obj.risks.filter((r): r is string => typeof r === "string");
258
+ } else if (typeof obj.risks === "string") {
259
+ risks = [obj.risks];
260
+ } else {
261
+ warnings.push("Missing or invalid risks");
262
+ }
263
+
264
+ // Repair verification
265
+ let verification: string[] = [];
266
+ if (Array.isArray(obj.verification)) {
267
+ verification = obj.verification.filter((v): v is string => typeof v === "string");
268
+ } else if (typeof obj.verification === "string") {
269
+ verification = [obj.verification];
270
+ warnings.push("verification was a string, converted to array");
271
+ } else {
272
+ warnings.push("Missing or invalid verification");
273
+ }
274
+
275
+ // Repair confidence
276
+ const validConfidence = ["low", "medium", "high"] as const;
277
+ let confidence: ModelOpinion["confidence"] = "medium";
278
+ if (typeof obj.confidence === "string" && validConfidence.includes(obj.confidence as "low" | "medium" | "high")) {
279
+ confidence = obj.confidence as "low" | "medium" | "high";
280
+ } else {
281
+ warnings.push("Missing or invalid confidence, defaulting to medium");
282
+ }
283
+
284
+ return {
285
+ value: {
286
+ stance,
287
+ recommendedApproach,
288
+ steps,
289
+ filesToConsider,
290
+ risks,
291
+ verification,
292
+ confidence,
293
+ },
294
+ parseStatus: warnings.length > 0 ? "repaired" : "valid",
295
+ warnings,
296
+ };
297
+ }
298
+
299
+ /**
300
+ * Validate a council decision object against required fields.
301
+ */
302
+ export function validateCouncilDecision(value: unknown): ValidationResult<CouncilDecision> {
303
+ const warnings: string[] = [];
304
+
305
+ if (typeof value !== "object" || value === null) {
306
+ return { ok: false, warnings: ["Council decision is not an object"] };
307
+ }
308
+
309
+ const obj = value as Record<string, unknown>;
310
+
311
+ // Check required top-level fields
312
+ if (!obj.decisionId) warnings.push("Missing required field: decisionId");
313
+ if (!obj.recommendedPlan) warnings.push("Missing required field: recommendedPlan");
314
+ if (!obj.implementationGuidance) warnings.push("Missing required field: implementationGuidance");
315
+
316
+ // Validate consensus structure if present
317
+ if (obj.consensus && typeof obj.consensus === "object") {
318
+ const consensus = obj.consensus as Record<string, unknown>;
319
+ if (!Array.isArray(consensus.agreements)) warnings.push("consensus.agreements should be an array");
320
+ if (!Array.isArray(consensus.disagreements)) warnings.push("consensus.disagreements should be an array");
321
+ if (!Array.isArray(consensus.unknowns)) warnings.push("consensus.unknowns should be an array");
322
+ }
323
+
324
+ // Check if we have enough to construct a valid decision
325
+ if (obj.decisionId && obj.recommendedPlan && obj.implementationGuidance) {
326
+ return {
327
+ ok: true,
328
+ value: normalizeCouncilDecision(obj, undefined),
329
+ warnings,
330
+ };
331
+ }
332
+
333
+ return { ok: false, warnings };
334
+ }
335
+
336
+ /**
337
+ * Repair a council decision object, filling in missing fields with defaults.
338
+ */
339
+ export function repairCouncilDecision(value: unknown, input: CouncilInput): RepairResult<CouncilDecision> {
340
+ const warnings: string[] = [];
341
+
342
+ // Handle string input (raw text fallback)
343
+ if (typeof value === "string") {
344
+ warnings.push("Council decision is raw text, creating fallback");
345
+ return {
346
+ value: createFallbackDecisionRepaired(input),
347
+ parseStatus: "fallback",
348
+ warnings,
349
+ };
350
+ }
351
+
352
+ if (typeof value !== "object" || value === null) {
353
+ warnings.push("Council decision is not an object, creating fallback");
354
+ return {
355
+ value: createFallbackDecisionRepaired(input),
356
+ parseStatus: "fallback",
357
+ warnings,
358
+ };
359
+ }
360
+
361
+ const obj = value as Record<string, unknown>;
362
+
363
+ // Add warnings for missing fields
364
+ if (!obj.decisionId) warnings.push("Missing decisionId, generating new one");
365
+ if (!obj.consensus) warnings.push("Missing consensus, using empty arrays");
366
+ if (!obj.modelNotes) warnings.push("Missing modelNotes");
367
+
368
+ return {
369
+ value: normalizeCouncilDecision(obj, input),
370
+ parseStatus: warnings.length > 0 ? "repaired" : "valid",
371
+ warnings,
372
+ };
373
+ }
374
+
375
+ function createFallbackOpinion(rawText: string): ModelOpinion {
376
+ return {
377
+ stance: "Unstructured response",
378
+ recommendedApproach: rawText.substring(0, 300),
379
+ steps: [],
380
+ filesToConsider: [],
381
+ risks: [],
382
+ verification: [],
383
+ confidence: "medium",
384
+ };
385
+ }
386
+
387
+ function createFallbackDecisionRepaired(input: CouncilInput): CouncilDecision {
388
+ return {
389
+ decisionId: `council-${Date.now()}`,
390
+ mode: input.mode,
391
+ confidence: "medium",
392
+ consensus: {
393
+ agreements: [],
394
+ disagreements: [],
395
+ unknowns: ["Council decision could not be parsed"],
396
+ },
397
+ recommendedPlan: {
398
+ summary: "Review the council outputs and implement the safest minimal plan.",
399
+ steps: [
400
+ "Review relevant files.",
401
+ "Implement the smallest safe change.",
402
+ "Run verification.",
403
+ ],
404
+ },
405
+ implementationGuidance: {
406
+ filesToEdit: [],
407
+ testsToRun: [],
408
+ guardrails: ["Prefer minimal, reversible changes."],
409
+ },
410
+ modelNotes: [],
411
+ handoffPrompt: "Implement the recommended plan cautiously and verify before reporting success.",
412
+ };
413
+ }
414
+
415
+ function normalizeCouncilDecision(
416
+ obj: Record<string, unknown>,
417
+ input?: CouncilInput
418
+ ): CouncilDecision {
419
+ const validModes: CouncilMode[] = ["fix", "ask", "architecture"];
420
+ const validConfidence = ["low", "medium", "high"];
421
+
422
+ return {
423
+ decisionId: typeof obj.decisionId === "string" ? obj.decisionId : `council-${Date.now()}`,
424
+ mode: validModes.includes(obj.mode as CouncilMode)
425
+ ? obj.mode as CouncilMode
426
+ : (input?.mode ?? "ask"),
427
+ confidence: validConfidence.includes(obj.confidence as string)
428
+ ? obj.confidence as CouncilDecision["confidence"]
429
+ : "medium",
430
+ consensus: normalizeConsensus(obj.consensus),
431
+ recommendedPlan: normalizeRecommendedPlan(obj.recommendedPlan),
432
+ implementationGuidance: normalizeImplementationGuidance(obj.implementationGuidance),
433
+ modelNotes: normalizeModelNotes(obj.modelNotes),
434
+ handoffPrompt: typeof obj.handoffPrompt === "string"
435
+ ? obj.handoffPrompt
436
+ : "Review and implement the recommended plan.",
437
+ };
438
+ }
439
+
440
+ function normalizeConsensus(consensus: unknown): CouncilDecision["consensus"] {
441
+ if (typeof consensus !== "object" || consensus === null) {
442
+ return { agreements: [], disagreements: [], unknowns: [] };
443
+ }
444
+ const c = consensus as Record<string, unknown>;
445
+ return {
446
+ agreements: Array.isArray(c.agreements) ? c.agreements.filter((s): s is string => typeof s === "string") : [],
447
+ disagreements: Array.isArray(c.disagreements) ? c.disagreements.filter((s): s is string => typeof s === "string") : [],
448
+ unknowns: Array.isArray(c.unknowns) ? c.unknowns.filter((s): s is string => typeof s === "string") : [],
449
+ };
450
+ }
451
+
452
+ function normalizeRecommendedPlan(plan: unknown): CouncilDecision["recommendedPlan"] {
453
+ if (typeof plan !== "object" || plan === null) {
454
+ return {
455
+ summary: "Review the council outputs and implement the safest minimal plan.",
456
+ steps: ["Review relevant files.", "Implement the smallest safe change.", "Run verification."],
457
+ };
458
+ }
459
+ const p = plan as Record<string, unknown>;
460
+ return {
461
+ summary: typeof p.summary === "string" ? p.summary : "Review the council outputs and implement the safest minimal plan.",
462
+ steps: Array.isArray(p.steps) ? p.steps.filter((s): s is string => typeof s === "string") : [],
463
+ };
464
+ }
465
+
466
+ function normalizeImplementationGuidance(guidance: unknown): CouncilDecision["implementationGuidance"] {
467
+ if (typeof guidance !== "object" || guidance === null) {
468
+ return { filesToEdit: [], testsToRun: [], guardrails: ["Prefer minimal, reversible changes."] };
469
+ }
470
+ const g = guidance as Record<string, unknown>;
471
+
472
+ const filesToEdit = Array.isArray(g.filesToEdit)
473
+ ? g.filesToEdit
474
+ .filter((f): f is Record<string, unknown> => typeof f === "object" && f !== null)
475
+ .filter((f) => typeof f.path === "string")
476
+ .map((f) => ({
477
+ path: f.path as string,
478
+ reason: typeof f.reason === "string" ? f.reason : "",
479
+ action: typeof f.action === "string" ? f.action : "",
480
+ }))
481
+ : [];
482
+
483
+ return {
484
+ filesToEdit,
485
+ testsToRun: Array.isArray(g.testsToRun) ? g.testsToRun.filter((s): s is string => typeof s === "string") : [],
486
+ guardrails: Array.isArray(g.guardrails) ? g.guardrails.filter((s): s is string => typeof s === "string") : ["Prefer minimal, reversible changes."],
487
+ };
488
+ }
489
+
490
+ function normalizeModelNotes(notes: unknown): CouncilDecision["modelNotes"] {
491
+ if (!Array.isArray(notes)) return [];
492
+ return notes
493
+ .filter((n): n is Record<string, unknown> => typeof n === "object" && n !== null)
494
+ .filter((n) => typeof n.model === "string")
495
+ .map((n) => ({
496
+ model: n.model as string,
497
+ stance: typeof n.stance === "string" ? n.stance : "",
498
+ keyRisks: Array.isArray(n.keyRisks) ? n.keyRisks.filter((r): r is string => typeof r === "string") : [],
499
+ }));
500
+ }
package/types.ts ADDED
@@ -0,0 +1,169 @@
1
+ export type CouncilMode = "fix" | "ask" | "architecture";
2
+
3
+ export type SecondOpinionMode = "fix" | "ask" | "architecture" | "general";
4
+
5
+ export type SecondOpinionInput = {
6
+ problem: string;
7
+ mode?: SecondOpinionMode;
8
+ currentUnderstanding?: string;
9
+ relevantFiles?: CouncilRelevantFile[];
10
+ constraints?: string[];
11
+ questions?: string[];
12
+ };
13
+
14
+ export type CouncilRelevantFile = {
15
+ path: string;
16
+ summary: string;
17
+ importantSnippets?: string;
18
+ };
19
+
20
+ export type CouncilInput = {
21
+ mode: CouncilMode;
22
+ problem: string;
23
+ currentUnderstanding?: string;
24
+ relevantFiles?: CouncilRelevantFile[];
25
+ constraints?: string[];
26
+ questionsToCouncil?: string[];
27
+ };
28
+
29
+ export type CouncilModelResult = {
30
+ model: string;
31
+ ok: boolean;
32
+ rawText?: string;
33
+ parsed?: ModelOpinion;
34
+ error?: string;
35
+ metadata?: {
36
+ attemptCount?: number;
37
+ durationMs?: number;
38
+ usedStructuredOutput?: boolean;
39
+ parseStatus?: "ok" | "repaired" | "fallback" | "failed";
40
+ warnings?: string[];
41
+ };
42
+ };
43
+
44
+ export type ModelOpinion = {
45
+ stance: string;
46
+ recommendedApproach: string;
47
+ steps: string[];
48
+ filesToConsider: Array<{
49
+ path: string;
50
+ reason: string;
51
+ suggestedAction: string;
52
+ }>;
53
+ risks: string[];
54
+ verification: string[];
55
+ confidence: "low" | "medium" | "high";
56
+ };
57
+
58
+ export type CouncilDecision = {
59
+ decisionId: string;
60
+ mode: CouncilMode;
61
+ confidence: "low" | "medium" | "high";
62
+ consensus: {
63
+ agreements: string[];
64
+ disagreements: string[];
65
+ unknowns: string[];
66
+ };
67
+ recommendedPlan: {
68
+ summary: string;
69
+ steps: string[];
70
+ };
71
+ implementationGuidance: {
72
+ filesToEdit: Array<{
73
+ path: string;
74
+ reason: string;
75
+ action: string;
76
+ }>;
77
+ testsToRun: string[];
78
+ guardrails: string[];
79
+ };
80
+ modelNotes: Array<{
81
+ model: string;
82
+ stance: string;
83
+ keyRisks: string[];
84
+ }>;
85
+ handoffPrompt: string;
86
+ metadata?: {
87
+ degraded?: boolean;
88
+ fallbackUsed?: boolean;
89
+ warnings?: string[];
90
+ };
91
+ };
92
+
93
+ // --- Settings types ---
94
+
95
+ export interface OpenRouterModel {
96
+ id: string;
97
+ name: string;
98
+ /** True if the model supports extended thinking / reasoning. Optional — only
99
+ * populated when sourced from pi's model registry, since OpenRouter's REST
100
+ * /models endpoint doesn't expose it. */
101
+ reasoning?: boolean;
102
+ /** Context window size in tokens, if known. */
103
+ contextWindow?: number;
104
+ }
105
+
106
+ export interface CouncilSettings {
107
+ version: 1;
108
+ openRouter: {
109
+ /** May be empty when the API key is sourced from pi's auth storage
110
+ * (`ctx.modelRegistry.getApiKeyForProvider("openrouter")`). */
111
+ apiKey: string;
112
+ models: {
113
+ model1: string;
114
+ model2: string;
115
+ model3: string;
116
+ };
117
+ };
118
+ opinion: {
119
+ provider: string;
120
+ modelId: string;
121
+ };
122
+ /** Optional override for the synthesis model. When omitted, the council
123
+ * runner falls back to `openRouter.models.model1`. */
124
+ synthesis?: {
125
+ modelId: string;
126
+ };
127
+ options: {
128
+ useStructuredOutput: boolean;
129
+ modelTimeoutMs: number;
130
+ synthesisTimeoutMs: number;
131
+ retryAttempts: number;
132
+ retryDelayMs: number;
133
+ };
134
+ lastUpdated: string;
135
+ }
136
+
137
+ export interface ValidationResult {
138
+ valid: boolean;
139
+ errors: string[];
140
+ warnings?: string[];
141
+ }
142
+
143
+ export interface PingResult {
144
+ ok: boolean;
145
+ error?: string;
146
+ quota?: string;
147
+ }
148
+
149
+ export interface ModelOption {
150
+ provider: string;
151
+ models: Array<{
152
+ id: string;
153
+ name: string;
154
+ }>;
155
+ }
156
+
157
+ export class CouncilSetupError extends Error {
158
+ constructor(message: string) {
159
+ super(message);
160
+ this.name = "CouncilSetupError";
161
+ }
162
+ }
163
+
164
+ export class OpinionSetupError extends Error {
165
+ constructor(message: string) {
166
+ super(message);
167
+ this.name = "OpinionSetupError";
168
+ }
169
+ }
package/index.js DELETED
@@ -1 +0,0 @@
1
- module.exports = {};