foliko 2.0.2 → 2.0.4

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 (80) hide show
  1. package/README.md +6 -6
  2. package/docs/public-api.md +91 -0
  3. package/docs/system-prompt.md +219 -0
  4. package/docs/usage.md +6 -6
  5. package/package.json +1 -1
  6. package/plugins/ambient/ExplorerLoop.js +1 -0
  7. package/plugins/ambient/index.js +1 -0
  8. package/plugins/core/coordinator/index.js +10 -5
  9. package/plugins/core/default/bootstrap.js +21 -3
  10. package/plugins/core/default/config.js +12 -3
  11. package/plugins/core/python-loader/index.js +3 -3
  12. package/plugins/core/scheduler/index.js +26 -2
  13. package/plugins/core/skill-manager/index.js +198 -151
  14. package/plugins/core/sub-agent/index.js +34 -15
  15. package/plugins/core/think/index.js +1 -0
  16. package/plugins/core/workflow/index.js +5 -4
  17. package/plugins/executors/data-splitter/index.js +14 -1
  18. package/plugins/executors/extension/index.js +52 -38
  19. package/plugins/executors/python/index.js +3 -3
  20. package/plugins/executors/shell/index.js +6 -4
  21. package/plugins/io/web/index.js +2 -1
  22. package/plugins/memory/index.js +29 -74
  23. package/plugins/messaging/email/handlers.js +1 -19
  24. package/plugins/messaging/email/monitor.js +2 -17
  25. package/plugins/messaging/email/reply.js +2 -1
  26. package/plugins/messaging/email/utils.js +20 -1
  27. package/plugins/messaging/feishu/index.js +1 -1
  28. package/plugins/messaging/qq/index.js +1 -1
  29. package/plugins/messaging/telegram/index.js +1 -1
  30. package/plugins/messaging/weixin/index.js +1 -1
  31. package/plugins/plugin-manager/index.js +1 -33
  32. package/plugins/tools/index.js +14 -1
  33. package/skills/plugins/SKILL.md +316 -0
  34. package/skills/skill-guide/SKILL.md +5 -5
  35. package/skills/{subagent-guide → subagents}/SKILL.md +1 -1
  36. package/skills/{workflow-guide → workflows}/SKILL.md +3 -3
  37. package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/DEBUGGING.md +197 -197
  38. package/skills/{workflow-troubleshooting → workflows/workflow-troubleshooting}/SKILL.md +391 -391
  39. package/src/agent/base.js +5 -20
  40. package/src/agent/chat.js +67 -5
  41. package/src/agent/index.js +20 -8
  42. package/src/agent/main.js +100 -23
  43. package/src/agent/prompt-registry.js +296 -0
  44. package/src/agent/sub-config.js +1 -78
  45. package/src/agent/sub.js +20 -25
  46. package/src/cli/commands/plugin.js +1 -60
  47. package/src/cli/ui/chat-ui-old.js +1 -1
  48. package/src/cli/ui/chat-ui.js +21 -17
  49. package/src/cli/ui/components/agent-mention-provider.js +1 -1
  50. package/src/common/constants.js +42 -0
  51. package/src/common/id.js +13 -0
  52. package/src/common/queue.js +3 -3
  53. package/src/context/compressor.js +17 -9
  54. package/src/framework/framework.js +218 -0
  55. package/src/framework/index.js +1 -2
  56. package/src/framework/lifecycle.js +102 -1
  57. package/src/framework/loader.js +1 -55
  58. package/src/index.js +11 -2
  59. package/src/plugin/base.js +69 -55
  60. package/src/plugin/loader.js +1 -57
  61. package/src/session/chat.js +1 -1
  62. package/src/session/entry.js +1 -11
  63. package/src/storage/manager.js +1 -12
  64. package/src/tool/executor.js +3 -2
  65. package/src/tool/router.js +5 -5
  66. package/src/utils/data-splitter.js +2 -1
  67. package/src/utils/index.js +150 -0
  68. package/src/utils/message-validator.js +21 -17
  69. package/src/utils/plugin-helpers.js +19 -5
  70. package/subagent.md +2 -2
  71. package/tests/core/plugin-prompts.test.js +219 -0
  72. package/tests/core/prompt-registry.test.js +209 -0
  73. package/src/cli/utils/plugin-config.js +0 -50
  74. package/src/config/plugin-config.js +0 -50
  75. /package/skills/{ambient-agent → ambient}/SKILL.md +0 -0
  76. /package/skills/{foliko-dev → foliko}/AGENTS.md +0 -0
  77. /package/skills/{foliko-dev → foliko}/SKILL.md +0 -0
  78. /package/skills/{mcp-usage → mcp}/SKILL.md +0 -0
  79. /package/skills/{plugin-guide → plugins-guide}/SKILL.md +0 -0
  80. /package/skills/{python-plugin-dev → python}/SKILL.md +0 -0
@@ -0,0 +1,219 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { Framework, Plugin } from '../../src/index';
3
+
4
+ describe('Plugin declarative prompts', () => {
5
+ let fw;
6
+
7
+ beforeEach(() => {
8
+ fw = new Framework({ silent: true });
9
+ });
10
+
11
+ it('auto-registers prompts declared in Plugin.prompts array', () => {
12
+ class DemoPlugin extends Plugin {
13
+ install(f) {
14
+ this._framework = f;
15
+ return this;
16
+ }
17
+ prompts = [
18
+ {
19
+ name: 'demo',
20
+ slot: 'BEHAVIOR',
21
+ description: 'demo part',
22
+ provider: function () {
23
+ return 'demo-content';
24
+ },
25
+ },
26
+ ];
27
+ }
28
+ const plugin = new DemoPlugin();
29
+ plugin.install(fw);
30
+ plugin.start(fw);
31
+
32
+ expect(fw.prompts.has('unnamed-plugin', 'demo')).toBe(true);
33
+ expect(fw.prompts.getPart('unnamed-plugin', 'demo').get()).toBe('demo-content');
34
+ });
35
+
36
+ it('uses plugin.name as owner', () => {
37
+ class NamedPlugin extends Plugin {
38
+ name = 'my-plugin';
39
+ install(f) {
40
+ this._framework = f;
41
+ return this;
42
+ }
43
+ prompts = [
44
+ {
45
+ name: 'foo',
46
+ slot: 'CAPABILITY',
47
+ provider: function () {
48
+ return 'foo';
49
+ },
50
+ },
51
+ ];
52
+ }
53
+ const plugin = new NamedPlugin();
54
+ plugin.install(fw);
55
+ plugin.start(fw);
56
+
57
+ expect(fw.prompts.has('my-plugin', 'foo')).toBe(true);
58
+ });
59
+
60
+ it('skips invalid entries without breaking others', () => {
61
+ class MixedPlugin extends Plugin {
62
+ name = 'mixed';
63
+ install(f) {
64
+ this._framework = f;
65
+ return this;
66
+ }
67
+ prompts = [
68
+ null,
69
+ { name: 'no-provider' },
70
+ { provider: () => 'no-name' },
71
+ {
72
+ name: 'valid',
73
+ slot: 'CUSTOM',
74
+ provider: function () {
75
+ return 'valid-content';
76
+ },
77
+ },
78
+ ];
79
+ }
80
+ const plugin = new MixedPlugin();
81
+ plugin.install(fw);
82
+ plugin.start(fw);
83
+
84
+ // 合法条目注册成功
85
+ expect(fw.prompts.has('mixed', 'valid')).toBe(true);
86
+ // 非法条目被跳过(不抛错,不影响合法条目)
87
+ expect(fw.prompts.has('mixed', 'no-provider')).toBe(false);
88
+ expect(fw.prompts.has('mixed', 'no-name')).toBe(false);
89
+ });
90
+
91
+ it('binds this to the plugin instance inside provider', () => {
92
+ let capturedThis;
93
+ class CapturePlugin extends Plugin {
94
+ name = 'capture';
95
+ install(f) {
96
+ this._framework = f;
97
+ return this;
98
+ }
99
+ prompts = [
100
+ {
101
+ name: 'capture',
102
+ slot: 'BEHAVIOR',
103
+ provider: function () {
104
+ capturedThis = this;
105
+ return 'x';
106
+ },
107
+ },
108
+ ];
109
+ }
110
+ const plugin = new CapturePlugin();
111
+ plugin.install(fw);
112
+ plugin.start(fw);
113
+ fw.prompts.getPart('capture', 'capture').get();
114
+ expect(capturedThis).toBe(plugin);
115
+ });
116
+
117
+ it('cleanup on uninstall removes all parts owned by plugin', () => {
118
+ class CleanupPlugin extends Plugin {
119
+ name = 'cleanup';
120
+ install(f) {
121
+ this._framework = f;
122
+ return this;
123
+ }
124
+ prompts = [
125
+ { name: 'a', slot: 'BEHAVIOR', provider: () => 'a' },
126
+ { name: 'b', slot: 'BEHAVIOR', provider: () => 'b' },
127
+ ];
128
+ }
129
+ const plugin = new CleanupPlugin();
130
+ plugin.install(fw);
131
+ plugin.start(fw);
132
+ expect(fw.prompts.count()).toBe(2);
133
+
134
+ plugin.uninstall(fw);
135
+ expect(fw.prompts.has('cleanup', 'a')).toBe(false);
136
+ expect(fw.prompts.has('cleanup', 'b')).toBe(false);
137
+ });
138
+
139
+ it('reload re-registers declarative prompts', () => {
140
+ class ReloadPlugin extends Plugin {
141
+ name = 'reload-test';
142
+ install(f) {
143
+ this._framework = f;
144
+ return this;
145
+ }
146
+ prompts = [
147
+ {
148
+ name: 'reloadable',
149
+ slot: 'CUSTOM',
150
+ provider: function () {
151
+ return 'v1';
152
+ },
153
+ },
154
+ ];
155
+ }
156
+ const plugin = new ReloadPlugin();
157
+ plugin.install(fw);
158
+ plugin.start(fw);
159
+ expect(fw.prompts.getPart('reload-test', 'reloadable').get()).toBe('v1');
160
+
161
+ // 修改后 reload
162
+ plugin.prompts[0].provider = function () {
163
+ return 'v2';
164
+ };
165
+ plugin.reload(fw);
166
+ fw.prompts.invalidate('reload-test', 'reloadable');
167
+ expect(fw.prompts.getPart('reload-test', 'reloadable').get()).toBe('v2');
168
+ });
169
+
170
+ it('declarative prompts with owner matching plugin name', () => {
171
+ class NamedPlugin extends Plugin {
172
+ name = 'test-owner';
173
+ install(f) {
174
+ this._framework = f;
175
+ return this;
176
+ }
177
+ prompts = [
178
+ {
179
+ name: 'demo',
180
+ slot: 'CUSTOM',
181
+ provider: function () {
182
+ return 'demo-content';
183
+ },
184
+ },
185
+ ];
186
+ }
187
+ const plugin = new NamedPlugin();
188
+ plugin.install(fw);
189
+ plugin.start(fw);
190
+
191
+ expect(fw.prompts.has('test-owner', 'demo')).toBe(true);
192
+ expect(fw.prompts.getPart('test-owner', 'demo').get()).toBe('demo-content');
193
+ });
194
+
195
+ it('renders in slot order', () => {
196
+ class MultiPlugin extends Plugin {
197
+ name = 'multi';
198
+ install(f) {
199
+ this._framework = f;
200
+ return this;
201
+ }
202
+ prompts = [
203
+ { name: 'behavior', slot: 'BEHAVIOR', provider: () => 'BEHAVIOR_CONTENT' },
204
+ { name: 'identity', slot: 'IDENTITY', provider: () => 'IDENTITY_CONTENT' },
205
+ { name: 'capability', slot: 'CAPABILITY', provider: () => 'CAPABILITY_CONTENT' },
206
+ ];
207
+ }
208
+ const plugin = new MultiPlugin();
209
+ plugin.install(fw);
210
+ plugin.start(fw);
211
+
212
+ const built = fw.prompts.build();
213
+ const identityIdx = built.indexOf('IDENTITY_CONTENT');
214
+ const capabilityIdx = built.indexOf('CAPABILITY_CONTENT');
215
+ const behaviorIdx = built.indexOf('BEHAVIOR_CONTENT');
216
+ expect(identityIdx).toBeLessThan(capabilityIdx);
217
+ expect(capabilityIdx).toBeLessThan(behaviorIdx);
218
+ });
219
+ });
@@ -0,0 +1,209 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { PromptRegistry, PROMPT_SLOT, PROMPT_PRIORITY_TO_SLOT } from '../../src/index';
3
+
4
+ describe('PromptRegistry', () => {
5
+ let reg;
6
+
7
+ beforeEach(() => {
8
+ reg = new PromptRegistry();
9
+ });
10
+
11
+ describe('basic API', () => {
12
+ it('registers and retrieves a part', () => {
13
+ reg.register('plugin-a', 'foo', () => 'hello', { slot: 'IDENTITY' });
14
+ const part = reg.getPart('plugin-a', 'foo');
15
+ expect(part).toBeTruthy();
16
+ expect(part.get()).toBe('hello');
17
+ expect(reg.count()).toBe(1);
18
+ });
19
+
20
+ it('requires owner/name/provider', () => {
21
+ expect(() => reg.register('', 'foo', () => 'x')).toThrow(/owner/);
22
+ expect(() => reg.register('p', '', () => 'x')).toThrow(/name/);
23
+ expect(() => reg.register('p', 'n', 'not-fn')).toThrow(/provider/);
24
+ });
25
+
26
+ it('unregisters a part', () => {
27
+ reg.register('p', 'n', () => 'x');
28
+ expect(reg.unregister('p', 'n')).toBe(true);
29
+ expect(reg.unregister('p', 'n')).toBe(false);
30
+ expect(reg.count()).toBe(0);
31
+ });
32
+
33
+ it('clears all parts of an owner', () => {
34
+ reg.register('p', 'a', () => '1');
35
+ reg.register('p', 'b', () => '2');
36
+ reg.register('q', 'c', () => '3');
37
+ const cleared = reg.clearOwner('p');
38
+ expect(cleared).toBe(2);
39
+ expect(reg.count()).toBe(1);
40
+ });
41
+
42
+ it('isolates namespaces by owner', () => {
43
+ reg.register('x', 'foo', () => 'x-foo');
44
+ reg.register('y', 'foo', () => 'y-foo');
45
+ expect(reg.getPart('x', 'foo').get()).toBe('x-foo');
46
+ expect(reg.getPart('y', 'foo').get()).toBe('y-foo');
47
+ });
48
+ });
49
+
50
+ describe('slot ordering', () => {
51
+ it('orders by slot order', () => {
52
+ reg.register('p', 'a', () => 'BEHAVIOR', { slot: 'BEHAVIOR' });
53
+ reg.register('p', 'b', () => 'IDENTITY', { slot: 'IDENTITY' });
54
+ reg.register('p', 'c', () => 'CAPABILITY', { slot: 'CAPABILITY' });
55
+ const order = reg.list().map((p) => p.slot);
56
+ expect(order).toEqual(['IDENTITY', 'CAPABILITY', 'BEHAVIOR']);
57
+ });
58
+
59
+ it('uses explicit order within same slot', () => {
60
+ reg.register('p', 'late', () => 'late', { slot: 'IDENTITY', order: 5 });
61
+ reg.register('p', 'early', () => 'early', { slot: 'IDENTITY', order: 1 });
62
+ const names = reg.list().map((p) => p.name);
63
+ expect(names).toEqual(['early', 'late']);
64
+ });
65
+
66
+ it('falls back to alphabetical when order is equal', () => {
67
+ reg.register('zeta', 'z', () => 'z', { slot: 'IDENTITY' });
68
+ reg.register('alpha', 'a', () => 'a', { slot: 'IDENTITY' });
69
+ const owners = reg.list().map((p) => p.owner);
70
+ expect(owners).toEqual(['alpha', 'zeta']);
71
+ });
72
+
73
+ it('PROMPT_SLOT exposes 7 named slots', () => {
74
+ const names = Object.keys(PROMPT_SLOT);
75
+ expect(names).toContain('IDENTITY');
76
+ expect(names).toContain('ENVIRONMENT');
77
+ expect(names).toContain('USER');
78
+ expect(names).toContain('CAPABILITY');
79
+ expect(names).toContain('BEHAVIOR');
80
+ expect(names).toContain('CONTEXT');
81
+ expect(names).toContain('CUSTOM');
82
+ });
83
+
84
+ it('PROMPT_PRIORITY_TO_SLOT maps all legacy priorities', () => {
85
+ expect(PROMPT_PRIORITY_TO_SLOT.DATETIME).toBe('ENVIRONMENT');
86
+ expect(PROMPT_PRIORITY_TO_SLOT.ORIGINAL_PROMPT).toBe('IDENTITY');
87
+ expect(PROMPT_PRIORITY_TO_SLOT.SHARED_PROMPT).toBe('USER');
88
+ expect(PROMPT_PRIORITY_TO_SLOT.CAPABILITIES).toBe('CAPABILITY');
89
+ expect(PROMPT_PRIORITY_TO_SLOT.TOOL_CORE_RULES).toBe('BEHAVIOR');
90
+ });
91
+ });
92
+
93
+ describe('build', () => {
94
+ it('joins non-empty parts with double newline', () => {
95
+ reg.register('p', 'a', () => 'AAA');
96
+ reg.register('p', 'b', () => 'BBB');
97
+ expect(reg.build()).toBe('AAA\n\nBBB');
98
+ });
99
+
100
+ it('skips null/empty content', () => {
101
+ reg.register('p', 'a', () => 'AAA');
102
+ reg.register('p', 'b', () => null);
103
+ reg.register('p', 'c', () => ' ');
104
+ reg.register('p', 'd', () => 'DDD');
105
+ expect(reg.build()).toBe('AAA\n\nDDD');
106
+ });
107
+
108
+ it('does not break when provider throws', () => {
109
+ reg.register('p', 'good', () => 'AAA');
110
+ reg.register('p', 'bad', () => {
111
+ throw new Error('boom');
112
+ });
113
+ reg.register('p', 'after', () => 'BBB');
114
+ const original = console.warn;
115
+ const warnings = [];
116
+ console.warn = (msg) => warnings.push(msg);
117
+ try {
118
+ const result = reg.build();
119
+ // 排序:after → bad(fail) → good → "BBB\n\nAAA"
120
+ expect(result).toBe('BBB\n\nAAA');
121
+ expect(warnings.length).toBe(1);
122
+ } finally {
123
+ console.warn = original;
124
+ }
125
+ });
126
+
127
+ it('preview is an alias for build', () => {
128
+ reg.register('p', 'a', () => 'AAA');
129
+ expect(reg.preview()).toBe(reg.build());
130
+ });
131
+ });
132
+
133
+ describe('caching & invalidation', () => {
134
+ it('caches provider result until invalidated', () => {
135
+ let calls = 0;
136
+ reg.register('p', 'a', () => {
137
+ calls++;
138
+ return 'v' + calls;
139
+ });
140
+ expect(reg.getPart('p', 'a').get()).toBe('v1');
141
+ expect(reg.getPart('p', 'a').get()).toBe('v1');
142
+ reg.invalidate('p', 'a');
143
+ expect(reg.getPart('p', 'a').get()).toBe('v2');
144
+ });
145
+
146
+ it('invalidateAll resets all caches', () => {
147
+ let n = 0;
148
+ reg.register('p', 'a', () => ++n);
149
+ reg.register('p', 'b', () => ++n);
150
+ reg.build();
151
+ reg.invalidateAll();
152
+ reg.build();
153
+ expect(n).toBeGreaterThanOrEqual(4);
154
+ });
155
+ });
156
+
157
+ describe('events', () => {
158
+ it('emits register / unregister / clear-owner', () => {
159
+ const events = [];
160
+ reg.on('register', (e) => events.push('reg:' + e.owner));
161
+ reg.on('unregister', (e) => events.push('unreg:' + e.owner));
162
+ reg.on('clear-owner', (e) => events.push('clear-owner:' + e.owner));
163
+ reg.register('p', 'a', () => 'x');
164
+ reg.register('q', 'b', () => 'x');
165
+ reg.unregister('p', 'a');
166
+ reg.clearOwner('q');
167
+ expect(events).toEqual(['reg:p', 'reg:q', 'unreg:p', 'clear-owner:q']);
168
+ });
169
+ });
170
+
171
+ describe('inspect', () => {
172
+ it('returns structured per-part metadata', () => {
173
+ reg.register('plugin-a', 'foo', () => 'hello world', { slot: 'IDENTITY' });
174
+ const out = reg.inspect();
175
+ expect(out).toHaveLength(1);
176
+ expect(out[0]).toMatchObject({
177
+ owner: 'plugin-a',
178
+ name: 'foo',
179
+ slot: 'IDENTITY',
180
+ order: 10,
181
+ hasContent: true,
182
+ length: 11,
183
+ });
184
+ expect(out[0].contentPreview).toContain('hello');
185
+ });
186
+ });
187
+
188
+ describe('Framework integration', () => {
189
+ it('Framework exposes prompts registry', () => {
190
+ const { Framework } = require('../../src/index');
191
+ const fw = new Framework({ silent: true });
192
+ expect(fw.prompts).toBeDefined();
193
+ expect(fw.prompts).toBe(fw.promptRegistry);
194
+ expect(typeof fw.prompts.register).toBe('function');
195
+ expect(typeof fw.prompts.preview).toBe('function');
196
+ });
197
+
198
+ it('each agent has its own promptRegistry via BaseAgent', () => {
199
+ const { BaseAgent } = require('../../src/agent/base');
200
+ const { Framework } = require('../../src/index');
201
+ const fw = new Framework({ silent: true });
202
+ const agent = new BaseAgent(fw, { name: 'Test' });
203
+ expect(agent.promptRegistry).toBeDefined();
204
+ expect(agent.promptRegistry).toBe(agent._promptRegistry);
205
+ agent.promptRegistry.register('test', 'foo', () => 'x', { slot: 'CUSTOM' });
206
+ expect(agent.promptRegistry.count()).toBe(1);
207
+ });
208
+ });
209
+ });
@@ -1,50 +0,0 @@
1
- /**
2
- * 插件管理器公共配置
3
- */
4
-
5
- // 默认插件仓库
6
- const DEFAULT_REPO = 'https://github.com/chnak/foliko-plugins.git';
7
-
8
- // 发布时忽略的文件和目录
9
- const IGNORE_PATTERNS = [
10
- 'node_modules',
11
- '.git',
12
- '.env',
13
- '.DS_Store',
14
- 'Thumbs.db',
15
- '*.log',
16
- '*.lock',
17
- '*.bak',
18
- '.claude',
19
- '.foliko',
20
- 'examples',
21
- 'dist',
22
- 'build',
23
- 'coverage',
24
- 'tests',
25
- '__tests__',
26
- '*.test.js',
27
- '*.spec.js',
28
- 'package-lock.json',
29
- 'yarn.lock',
30
- 'pnpm-lock.yaml',
31
- ];
32
-
33
- /**
34
- * 检查文件/目录是否应该被忽略
35
- */
36
- function shouldIgnore(name) {
37
- return IGNORE_PATTERNS.some((pattern) => {
38
- if (pattern.startsWith('*.')) {
39
- const ext = pattern.slice(1);
40
- return name.endsWith(ext);
41
- }
42
- return name === pattern;
43
- });
44
- }
45
-
46
- module.exports = {
47
- DEFAULT_REPO,
48
- IGNORE_PATTERNS,
49
- shouldIgnore,
50
- };
@@ -1,50 +0,0 @@
1
- /**
2
- * 插件管理器公共配置
3
- */
4
-
5
- // 默认插件仓库
6
- const DEFAULT_REPO = 'https://github.com/chnak/foliko-plugins.git';
7
-
8
- // 发布时忽略的文件和目录
9
- const IGNORE_PATTERNS = [
10
- 'node_modules',
11
- '.git',
12
- '.env',
13
- '.DS_Store',
14
- 'Thumbs.db',
15
- '*.log',
16
- '*.lock',
17
- '*.bak',
18
- '.claude',
19
- '.foliko',
20
- 'examples',
21
- 'dist',
22
- 'build',
23
- 'coverage',
24
- 'tests',
25
- '__tests__',
26
- '*.test.js',
27
- '*.spec.js',
28
- 'package-lock.json',
29
- 'yarn.lock',
30
- 'pnpm-lock.yaml',
31
- ];
32
-
33
- /**
34
- * 检查文件/目录是否应该被忽略
35
- */
36
- function shouldIgnore(name) {
37
- return IGNORE_PATTERNS.some((pattern) => {
38
- if (pattern.startsWith('*.')) {
39
- const ext = pattern.slice(1);
40
- return name.endsWith(ext);
41
- }
42
- return name === pattern;
43
- });
44
- }
45
-
46
- module.exports = {
47
- DEFAULT_REPO,
48
- IGNORE_PATTERNS,
49
- shouldIgnore,
50
- };
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes