@soleri/core 9.5.0 → 9.7.0

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 (249) hide show
  1. package/dist/adapters/claude-code-adapter.d.ts +27 -0
  2. package/dist/adapters/claude-code-adapter.d.ts.map +1 -0
  3. package/dist/adapters/claude-code-adapter.js +111 -0
  4. package/dist/adapters/claude-code-adapter.js.map +1 -0
  5. package/dist/adapters/index.d.ts +9 -0
  6. package/dist/adapters/index.d.ts.map +1 -0
  7. package/dist/adapters/index.js +10 -0
  8. package/dist/adapters/index.js.map +1 -0
  9. package/dist/adapters/registry.d.ts +21 -0
  10. package/dist/adapters/registry.d.ts.map +1 -0
  11. package/dist/adapters/registry.js +44 -0
  12. package/dist/adapters/registry.js.map +1 -0
  13. package/dist/adapters/types.d.ts +93 -0
  14. package/dist/adapters/types.d.ts.map +1 -0
  15. package/dist/adapters/types.js +10 -0
  16. package/dist/adapters/types.js.map +1 -0
  17. package/dist/brain/brain.d.ts +12 -1
  18. package/dist/brain/brain.d.ts.map +1 -1
  19. package/dist/brain/brain.js +106 -44
  20. package/dist/brain/brain.js.map +1 -1
  21. package/dist/brain/intelligence.d.ts.map +1 -1
  22. package/dist/brain/intelligence.js +36 -30
  23. package/dist/brain/intelligence.js.map +1 -1
  24. package/dist/chat/agent-loop.js +1 -1
  25. package/dist/chat/agent-loop.js.map +1 -1
  26. package/dist/chat/notifications.d.ts.map +1 -1
  27. package/dist/chat/notifications.js +4 -0
  28. package/dist/chat/notifications.js.map +1 -1
  29. package/dist/control/intent-router.d.ts +1 -0
  30. package/dist/control/intent-router.d.ts.map +1 -1
  31. package/dist/control/intent-router.js +11 -5
  32. package/dist/control/intent-router.js.map +1 -1
  33. package/dist/curator/curator.d.ts +4 -0
  34. package/dist/curator/curator.d.ts.map +1 -1
  35. package/dist/curator/curator.js +141 -27
  36. package/dist/curator/curator.js.map +1 -1
  37. package/dist/index.d.ts +22 -2
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +18 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/llm/llm-client.d.ts.map +1 -1
  42. package/dist/llm/llm-client.js +1 -0
  43. package/dist/llm/llm-client.js.map +1 -1
  44. package/dist/packs/index.d.ts +3 -2
  45. package/dist/packs/index.d.ts.map +1 -1
  46. package/dist/packs/index.js +3 -2
  47. package/dist/packs/index.js.map +1 -1
  48. package/dist/packs/lockfile.d.ts +23 -1
  49. package/dist/packs/lockfile.d.ts.map +1 -1
  50. package/dist/packs/lockfile.js +50 -4
  51. package/dist/packs/lockfile.js.map +1 -1
  52. package/dist/packs/pack-installer.d.ts +10 -0
  53. package/dist/packs/pack-installer.d.ts.map +1 -1
  54. package/dist/packs/pack-installer.js +69 -2
  55. package/dist/packs/pack-installer.js.map +1 -1
  56. package/dist/packs/pack-lifecycle.d.ts +50 -0
  57. package/dist/packs/pack-lifecycle.d.ts.map +1 -0
  58. package/dist/packs/pack-lifecycle.js +91 -0
  59. package/dist/packs/pack-lifecycle.js.map +1 -0
  60. package/dist/packs/types.d.ts +76 -29
  61. package/dist/packs/types.d.ts.map +1 -1
  62. package/dist/packs/types.js +9 -0
  63. package/dist/packs/types.js.map +1 -1
  64. package/dist/persistence/sqlite-provider.d.ts +5 -1
  65. package/dist/persistence/sqlite-provider.d.ts.map +1 -1
  66. package/dist/persistence/sqlite-provider.js +22 -2
  67. package/dist/persistence/sqlite-provider.js.map +1 -1
  68. package/dist/planning/github-projection.d.ts +11 -9
  69. package/dist/planning/github-projection.d.ts.map +1 -1
  70. package/dist/planning/github-projection.js +47 -43
  71. package/dist/planning/github-projection.js.map +1 -1
  72. package/dist/planning/goal-ancestry.d.ts +72 -0
  73. package/dist/planning/goal-ancestry.d.ts.map +1 -0
  74. package/dist/planning/goal-ancestry.js +137 -0
  75. package/dist/planning/goal-ancestry.js.map +1 -0
  76. package/dist/planning/plan-lifecycle.d.ts +2 -0
  77. package/dist/planning/plan-lifecycle.d.ts.map +1 -1
  78. package/dist/planning/plan-lifecycle.js +1 -0
  79. package/dist/planning/plan-lifecycle.js.map +1 -1
  80. package/dist/planning/planner-types.d.ts +2 -0
  81. package/dist/planning/planner-types.d.ts.map +1 -1
  82. package/dist/plugins/types.d.ts +21 -21
  83. package/dist/queue/pipeline-runner.d.ts.map +1 -1
  84. package/dist/queue/pipeline-runner.js +4 -0
  85. package/dist/queue/pipeline-runner.js.map +1 -1
  86. package/dist/runtime/context-health.d.ts +14 -1
  87. package/dist/runtime/context-health.d.ts.map +1 -1
  88. package/dist/runtime/context-health.js +30 -2
  89. package/dist/runtime/context-health.js.map +1 -1
  90. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  91. package/dist/runtime/curator-extra-ops.js +9 -1
  92. package/dist/runtime/curator-extra-ops.js.map +1 -1
  93. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  94. package/dist/runtime/facades/memory-facade.js +169 -0
  95. package/dist/runtime/facades/memory-facade.js.map +1 -1
  96. package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
  97. package/dist/runtime/orchestrate-ops.js +133 -4
  98. package/dist/runtime/orchestrate-ops.js.map +1 -1
  99. package/dist/runtime/runtime.d.ts.map +1 -1
  100. package/dist/runtime/runtime.js +128 -90
  101. package/dist/runtime/runtime.js.map +1 -1
  102. package/dist/runtime/session-briefing.d.ts.map +1 -1
  103. package/dist/runtime/session-briefing.js +44 -11
  104. package/dist/runtime/session-briefing.js.map +1 -1
  105. package/dist/runtime/shutdown-registry.d.ts +36 -0
  106. package/dist/runtime/shutdown-registry.d.ts.map +1 -0
  107. package/dist/runtime/shutdown-registry.js +74 -0
  108. package/dist/runtime/shutdown-registry.js.map +1 -0
  109. package/dist/runtime/types.d.ts +10 -1
  110. package/dist/runtime/types.d.ts.map +1 -1
  111. package/dist/session/compaction-evaluator.d.ts +20 -0
  112. package/dist/session/compaction-evaluator.d.ts.map +1 -0
  113. package/dist/session/compaction-evaluator.js +73 -0
  114. package/dist/session/compaction-evaluator.js.map +1 -0
  115. package/dist/session/compaction-policy.d.ts +50 -0
  116. package/dist/session/compaction-policy.d.ts.map +1 -0
  117. package/dist/session/compaction-policy.js +17 -0
  118. package/dist/session/compaction-policy.js.map +1 -0
  119. package/dist/session/handoff-renderer.d.ts +22 -0
  120. package/dist/session/handoff-renderer.d.ts.map +1 -0
  121. package/dist/session/handoff-renderer.js +49 -0
  122. package/dist/session/handoff-renderer.js.map +1 -0
  123. package/dist/session/index.d.ts +6 -0
  124. package/dist/session/index.d.ts.map +1 -0
  125. package/dist/session/index.js +5 -0
  126. package/dist/session/index.js.map +1 -0
  127. package/dist/session/policy-resolver.d.ts +20 -0
  128. package/dist/session/policy-resolver.d.ts.map +1 -0
  129. package/dist/session/policy-resolver.js +28 -0
  130. package/dist/session/policy-resolver.js.map +1 -0
  131. package/dist/skills/sync-skills.d.ts +27 -0
  132. package/dist/skills/sync-skills.d.ts.map +1 -1
  133. package/dist/skills/sync-skills.js +92 -1
  134. package/dist/skills/sync-skills.js.map +1 -1
  135. package/dist/skills/trust-classifier.d.ts +32 -0
  136. package/dist/skills/trust-classifier.d.ts.map +1 -0
  137. package/dist/skills/trust-classifier.js +109 -0
  138. package/dist/skills/trust-classifier.js.map +1 -0
  139. package/dist/subagent/concurrency-manager.d.ts +29 -0
  140. package/dist/subagent/concurrency-manager.d.ts.map +1 -0
  141. package/dist/subagent/concurrency-manager.js +73 -0
  142. package/dist/subagent/concurrency-manager.js.map +1 -0
  143. package/dist/subagent/dispatcher.d.ts +45 -0
  144. package/dist/subagent/dispatcher.d.ts.map +1 -0
  145. package/dist/subagent/dispatcher.js +271 -0
  146. package/dist/subagent/dispatcher.js.map +1 -0
  147. package/dist/subagent/index.d.ts +14 -0
  148. package/dist/subagent/index.d.ts.map +1 -0
  149. package/dist/subagent/index.js +15 -0
  150. package/dist/subagent/index.js.map +1 -0
  151. package/dist/subagent/orphan-reaper.d.ts +37 -0
  152. package/dist/subagent/orphan-reaper.d.ts.map +1 -0
  153. package/dist/subagent/orphan-reaper.js +71 -0
  154. package/dist/subagent/orphan-reaper.js.map +1 -0
  155. package/dist/subagent/result-aggregator.d.ts +7 -0
  156. package/dist/subagent/result-aggregator.d.ts.map +1 -0
  157. package/dist/subagent/result-aggregator.js +57 -0
  158. package/dist/subagent/result-aggregator.js.map +1 -0
  159. package/dist/subagent/task-checkout.d.ts +36 -0
  160. package/dist/subagent/task-checkout.d.ts.map +1 -0
  161. package/dist/subagent/task-checkout.js +52 -0
  162. package/dist/subagent/task-checkout.js.map +1 -0
  163. package/dist/subagent/types.d.ts +114 -0
  164. package/dist/subagent/types.d.ts.map +1 -0
  165. package/dist/subagent/types.js +9 -0
  166. package/dist/subagent/types.js.map +1 -0
  167. package/dist/subagent/workspace-resolver.d.ts +35 -0
  168. package/dist/subagent/workspace-resolver.d.ts.map +1 -0
  169. package/dist/subagent/workspace-resolver.js +99 -0
  170. package/dist/subagent/workspace-resolver.js.map +1 -0
  171. package/dist/transport/http-server.d.ts.map +1 -1
  172. package/dist/transport/http-server.js +49 -3
  173. package/dist/transport/http-server.js.map +1 -1
  174. package/dist/transport/ws-server.d.ts.map +1 -1
  175. package/dist/transport/ws-server.js +7 -0
  176. package/dist/transport/ws-server.js.map +1 -1
  177. package/dist/vault/linking.d.ts +3 -4
  178. package/dist/vault/linking.d.ts.map +1 -1
  179. package/dist/vault/linking.js +79 -32
  180. package/dist/vault/linking.js.map +1 -1
  181. package/dist/vault/vault-maintenance.d.ts.map +1 -1
  182. package/dist/vault/vault-maintenance.js +7 -14
  183. package/dist/vault/vault-maintenance.js.map +1 -1
  184. package/dist/vault/vault-memories.d.ts.map +1 -1
  185. package/dist/vault/vault-memories.js +19 -9
  186. package/dist/vault/vault-memories.js.map +1 -1
  187. package/dist/vault/vault-schema.d.ts +1 -0
  188. package/dist/vault/vault-schema.d.ts.map +1 -1
  189. package/dist/vault/vault-schema.js +20 -0
  190. package/dist/vault/vault-schema.js.map +1 -1
  191. package/dist/vault/vault.d.ts.map +1 -1
  192. package/dist/vault/vault.js +7 -3
  193. package/dist/vault/vault.js.map +1 -1
  194. package/package.json +5 -2
  195. package/src/__tests__/adapters/claude-code-adapter.test.ts +167 -0
  196. package/src/__tests__/adapters/registry.test.ts +100 -0
  197. package/src/__tests__/packs/pack-lifecycle.test.ts +379 -0
  198. package/src/__tests__/subagent/concurrency-manager.test.ts +132 -0
  199. package/src/__tests__/subagent/dispatcher.test.ts +195 -0
  200. package/src/__tests__/subagent/orphan-reaper.test.ts +141 -0
  201. package/src/__tests__/subagent/result-aggregator.test.ts +141 -0
  202. package/src/__tests__/subagent/task-checkout.test.ts +86 -0
  203. package/src/__tests__/subagent/workspace-resolver.test.ts +138 -0
  204. package/src/adapters/claude-code-adapter.ts +163 -0
  205. package/src/adapters/index.ts +22 -0
  206. package/src/adapters/registry.ts +53 -0
  207. package/src/adapters/types.ts +114 -0
  208. package/src/curator/curator.ts +1 -0
  209. package/src/index.ts +78 -1
  210. package/src/packs/index.ts +9 -1
  211. package/src/packs/lockfile.ts +70 -5
  212. package/src/packs/pack-installer.ts +78 -2
  213. package/src/packs/pack-lifecycle.ts +115 -0
  214. package/src/packs/pack-lockfile.test.ts +1 -1
  215. package/src/packs/pack-system.test.ts +1 -1
  216. package/src/packs/types.ts +72 -2
  217. package/src/persistence/sqlite-provider.ts +26 -2
  218. package/src/planning/github-projection.ts +6 -0
  219. package/src/planning/goal-ancestry.test.ts +427 -0
  220. package/src/planning/goal-ancestry.ts +187 -0
  221. package/src/planning/plan-lifecycle.ts +3 -0
  222. package/src/planning/planner-types.ts +2 -0
  223. package/src/runtime/admin-setup-ops.test.ts +9 -4
  224. package/src/runtime/context-health.ts +42 -2
  225. package/src/runtime/orchestrate-ops.ts +153 -1
  226. package/src/runtime/runtime.ts +15 -0
  227. package/src/runtime/session-briefing.test.ts +94 -2
  228. package/src/runtime/session-briefing.ts +48 -12
  229. package/src/runtime/types.ts +6 -0
  230. package/src/session/compaction-evaluator.ts +87 -0
  231. package/src/session/compaction-policy.ts +66 -0
  232. package/src/session/compaction.test.ts +259 -0
  233. package/src/session/handoff-renderer.ts +56 -0
  234. package/src/session/index.ts +12 -0
  235. package/src/session/policy-resolver.ts +34 -0
  236. package/src/skills/sync-skills.ts +114 -1
  237. package/src/skills/trust-classifier.test.ts +252 -0
  238. package/src/skills/trust-classifier.ts +127 -0
  239. package/src/subagent/concurrency-manager.ts +89 -0
  240. package/src/subagent/dispatcher.ts +342 -0
  241. package/src/subagent/index.ts +28 -0
  242. package/src/subagent/orphan-reaper.ts +82 -0
  243. package/src/subagent/result-aggregator.ts +66 -0
  244. package/src/subagent/task-checkout.ts +60 -0
  245. package/src/subagent/types.ts +138 -0
  246. package/src/subagent/workspace-resolver.ts +117 -0
  247. package/src/vault/vault-scaling.test.ts +3 -2
  248. package/vitest.config.ts +2 -0
  249. package/src/hooks/index.ts +0 -6
@@ -0,0 +1,252 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { tmpdir } from 'node:os';
5
+ import { classifyTrust } from './trust-classifier.js';
6
+ import { classifySkills, checkSkillCompatibility, ApprovalRequiredError } from './sync-skills.js';
7
+ import type { SkillEntry } from './sync-skills.js';
8
+
9
+ // =============================================================================
10
+ // HELPERS
11
+ // =============================================================================
12
+
13
+ let testDir: string;
14
+
15
+ function setup(): string {
16
+ testDir = join(tmpdir(), `soleri-trust-test-${Date.now()}`);
17
+ mkdirSync(testDir, { recursive: true });
18
+ return testDir;
19
+ }
20
+
21
+ function createSkillDir(parentDir: string, name: string, files: Record<string, string>): string {
22
+ const dir = join(parentDir, name);
23
+ mkdirSync(dir, { recursive: true });
24
+ for (const [filePath, content] of Object.entries(files)) {
25
+ const fullPath = join(dir, filePath);
26
+ mkdirSync(join(fullPath, '..'), { recursive: true });
27
+ writeFileSync(fullPath, content);
28
+ }
29
+ return dir;
30
+ }
31
+
32
+ // =============================================================================
33
+ // TrustClassifier
34
+ // =============================================================================
35
+
36
+ describe('TrustClassifier', () => {
37
+ beforeEach(() => setup());
38
+ afterEach(() => {
39
+ if (testDir) rmSync(testDir, { recursive: true, force: true });
40
+ });
41
+
42
+ it('classifies markdown-only directory', () => {
43
+ const dir = createSkillDir(testDir, 'md-skill', {
44
+ 'SKILL.md': '---\nname: test\n---\n# Test Skill',
45
+ 'reference.md': '# Reference doc',
46
+ });
47
+
48
+ const result = classifyTrust(dir);
49
+
50
+ expect(result.trust).toBe('markdown_only');
51
+ expect(result.inventory).toHaveLength(2);
52
+ expect(result.inventory.find((i) => i.path === 'SKILL.md')?.kind).toBe('skill');
53
+ expect(result.inventory.find((i) => i.path === 'reference.md')?.kind).toBe('reference');
54
+ });
55
+
56
+ it('classifies directory with assets', () => {
57
+ const dir = createSkillDir(testDir, 'asset-skill', {
58
+ 'SKILL.md': '# Skill',
59
+ 'logo.png': 'fake-png-data',
60
+ 'config.json': '{}',
61
+ });
62
+
63
+ const result = classifyTrust(dir);
64
+
65
+ expect(result.trust).toBe('assets');
66
+ expect(result.inventory.find((i) => i.path === 'logo.png')?.kind).toBe('asset');
67
+ expect(result.inventory.find((i) => i.path === 'config.json')?.kind).toBe('asset');
68
+ });
69
+
70
+ it('classifies directory with scripts', () => {
71
+ const dir = createSkillDir(testDir, 'script-skill', {
72
+ 'SKILL.md': '# Skill',
73
+ 'setup.sh': '#!/bin/bash\necho hi',
74
+ 'helper.ts': 'export const x = 1;',
75
+ });
76
+
77
+ const result = classifyTrust(dir);
78
+
79
+ expect(result.trust).toBe('scripts');
80
+ expect(result.inventory.filter((i) => i.kind === 'script')).toHaveLength(2);
81
+ });
82
+
83
+ it('treats .d.ts files as reference, not scripts', () => {
84
+ const dir = createSkillDir(testDir, 'decl-skill', {
85
+ 'SKILL.md': '# Skill',
86
+ 'types.d.ts': 'export type Foo = string;',
87
+ });
88
+
89
+ const result = classifyTrust(dir);
90
+
91
+ expect(result.trust).toBe('markdown_only');
92
+ expect(result.inventory.find((i) => i.path === 'types.d.ts')?.kind).toBe('reference');
93
+ });
94
+
95
+ it('returns markdown_only for empty directory', () => {
96
+ const dir = join(testDir, 'empty-skill');
97
+ mkdirSync(dir, { recursive: true });
98
+
99
+ const result = classifyTrust(dir);
100
+
101
+ expect(result.trust).toBe('markdown_only');
102
+ expect(result.inventory).toHaveLength(0);
103
+ });
104
+
105
+ it('returns markdown_only for non-existent directory', () => {
106
+ const result = classifyTrust(join(testDir, 'nonexistent'));
107
+
108
+ expect(result.trust).toBe('markdown_only');
109
+ expect(result.inventory).toHaveLength(0);
110
+ });
111
+
112
+ it('handles nested directories', () => {
113
+ const dir = createSkillDir(testDir, 'nested-skill', {
114
+ 'SKILL.md': '# Skill',
115
+ 'sub/helper.js': 'module.exports = {};',
116
+ 'sub/deep/readme.md': '# Deep',
117
+ });
118
+
119
+ const result = classifyTrust(dir);
120
+
121
+ expect(result.trust).toBe('scripts');
122
+ expect(result.inventory).toHaveLength(3);
123
+ expect(result.inventory.find((i) => i.path === 'sub/helper.js')?.kind).toBe('script');
124
+ });
125
+
126
+ it('skips hidden directories', () => {
127
+ const dir = createSkillDir(testDir, 'hidden-skill', {
128
+ 'SKILL.md': '# Skill',
129
+ '.git/config': 'gitconfig',
130
+ });
131
+
132
+ const result = classifyTrust(dir);
133
+
134
+ expect(result.inventory.some((i) => i.path.includes('.git'))).toBe(false);
135
+ });
136
+ });
137
+
138
+ // =============================================================================
139
+ // checkSkillCompatibility
140
+ // =============================================================================
141
+
142
+ describe('checkSkillCompatibility', () => {
143
+ it('returns unknown when no engine version specified', () => {
144
+ expect(checkSkillCompatibility(undefined, '9.6.0')).toBe('unknown');
145
+ });
146
+
147
+ it('returns unknown when no current version available', () => {
148
+ expect(checkSkillCompatibility('>=9.0.0', undefined)).toBe('unknown');
149
+ });
150
+
151
+ it('returns compatible for matching version', () => {
152
+ expect(checkSkillCompatibility('>=9.0.0', '9.6.0')).toBe('compatible');
153
+ });
154
+
155
+ it('returns invalid for incompatible version', () => {
156
+ expect(checkSkillCompatibility('>=10.0.0', '9.6.0')).toBe('invalid');
157
+ });
158
+
159
+ it('returns compatible for caret range', () => {
160
+ expect(checkSkillCompatibility('^9.0.0', '9.6.0')).toBe('compatible');
161
+ });
162
+
163
+ it('returns invalid for caret range with major mismatch', () => {
164
+ expect(checkSkillCompatibility('^10.0.0', '9.6.0')).toBe('invalid');
165
+ });
166
+ });
167
+
168
+ // =============================================================================
169
+ // classifySkills (integration with approval gate)
170
+ // =============================================================================
171
+
172
+ describe('classifySkills', () => {
173
+ beforeEach(() => setup());
174
+ afterEach(() => {
175
+ if (testDir) rmSync(testDir, { recursive: true, force: true });
176
+ });
177
+
178
+ it('classifies markdown-only skills without error', () => {
179
+ createSkillDir(testDir, 'safe-skill', {
180
+ 'SKILL.md': '---\nname: safe\n---\n# Safe Skill',
181
+ });
182
+
183
+ const skills: SkillEntry[] = [
184
+ { name: 'safe-skill', sourcePath: join(testDir, 'safe-skill', 'SKILL.md') },
185
+ ];
186
+
187
+ const result = classifySkills(skills);
188
+
189
+ expect(result).toHaveLength(1);
190
+ expect(result[0].metadata?.trust).toBe('markdown_only');
191
+ expect(result[0].metadata?.compatibility).toBe('unknown');
192
+ expect(result[0].metadata?.source.type).toBe('local');
193
+ });
194
+
195
+ it('throws ApprovalRequiredError for scripts without approval', () => {
196
+ createSkillDir(testDir, 'risky-skill', {
197
+ 'SKILL.md': '# Risky',
198
+ 'run.sh': '#!/bin/bash\nrm -rf /',
199
+ });
200
+
201
+ const skills: SkillEntry[] = [
202
+ { name: 'risky-skill', sourcePath: join(testDir, 'risky-skill', 'SKILL.md') },
203
+ ];
204
+
205
+ expect(() => classifySkills(skills)).toThrow(ApprovalRequiredError);
206
+ });
207
+
208
+ it('allows scripts when explicitly approved', () => {
209
+ createSkillDir(testDir, 'approved-skill', {
210
+ 'SKILL.md': '# Approved',
211
+ 'setup.sh': '#!/bin/bash\necho ok',
212
+ });
213
+
214
+ const skills: SkillEntry[] = [
215
+ { name: 'approved-skill', sourcePath: join(testDir, 'approved-skill', 'SKILL.md') },
216
+ ];
217
+
218
+ const result = classifySkills(skills, {
219
+ approvedScripts: new Set(['approved-skill']),
220
+ });
221
+
222
+ expect(result).toHaveLength(1);
223
+ expect(result[0].metadata?.trust).toBe('scripts');
224
+ });
225
+
226
+ it('reads engine version from SKILL.md frontmatter', () => {
227
+ createSkillDir(testDir, 'versioned-skill', {
228
+ 'SKILL.md': '---\nname: versioned\nengineVersion: ">=9.0.0"\n---\n# Versioned',
229
+ });
230
+
231
+ const skills: SkillEntry[] = [
232
+ { name: 'versioned-skill', sourcePath: join(testDir, 'versioned-skill', 'SKILL.md') },
233
+ ];
234
+
235
+ const result = classifySkills(skills, { currentEngineVersion: '9.6.0' });
236
+
237
+ expect(result[0].metadata?.engineVersion).toBe('>=9.0.0');
238
+ expect(result[0].metadata?.compatibility).toBe('compatible');
239
+ });
240
+
241
+ it('detects npm source type from node_modules path', () => {
242
+ const npmDir = join(testDir, 'node_modules', '@soleri', 'pack-test');
243
+ mkdirSync(npmDir, { recursive: true });
244
+ writeFileSync(join(npmDir, 'SKILL.md'), '---\nname: npm-skill\n---\n# NPM Skill');
245
+
246
+ const skills: SkillEntry[] = [{ name: 'npm-skill', sourcePath: join(npmDir, 'SKILL.md') }];
247
+
248
+ const result = classifySkills(skills);
249
+
250
+ expect(result[0].metadata?.source.type).toBe('npm');
251
+ });
252
+ });
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Trust Classifier — scans a skill directory and determines its trust level.
3
+ *
4
+ * Classification rules:
5
+ * - `.sh`, `.ts`, `.js` files (non-declaration) -> `scripts`
6
+ * - Non-`.md` files (images, JSON, etc.) -> `assets`
7
+ * - `.md` files only -> `markdown_only`
8
+ *
9
+ * Also builds a full inventory of all files with their classified kind.
10
+ */
11
+
12
+ import { existsSync, readdirSync, statSync } from 'node:fs';
13
+ import { join, extname, relative } from 'node:path';
14
+ import type { TrustLevel, SkillInventoryItem } from '../packs/types.js';
15
+
16
+ /** File extensions that indicate executable scripts */
17
+ const SCRIPT_EXTENSIONS = new Set(['.sh', '.ts', '.js', '.mjs', '.cjs', '.py', '.rb', '.bash']);
18
+
19
+ /** File extensions considered markdown/documentation */
20
+ const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']);
21
+
22
+ /** File extensions for TypeScript declaration files (not executable) */
23
+ const DECLARATION_PATTERN = /\.d\.[mc]?ts$/;
24
+
25
+ /**
26
+ * Classify a skill directory and return its trust level and inventory.
27
+ *
28
+ * @param dirPath - Absolute path to the skill directory
29
+ * @returns Trust level and full file inventory
30
+ */
31
+ export function classifyTrust(dirPath: string): {
32
+ trust: TrustLevel;
33
+ inventory: SkillInventoryItem[];
34
+ } {
35
+ if (!existsSync(dirPath)) {
36
+ return { trust: 'markdown_only', inventory: [] };
37
+ }
38
+
39
+ const inventory: SkillInventoryItem[] = [];
40
+ walkDir(dirPath, dirPath, inventory);
41
+
42
+ // Determine trust level from inventory
43
+ const hasScripts = inventory.some((item) => item.kind === 'script');
44
+ const hasAssets = inventory.some((item) => item.kind === 'asset');
45
+
46
+ let trust: TrustLevel;
47
+ if (hasScripts) {
48
+ trust = 'scripts';
49
+ } else if (hasAssets) {
50
+ trust = 'assets';
51
+ } else {
52
+ trust = 'markdown_only';
53
+ }
54
+
55
+ return { trust, inventory };
56
+ }
57
+
58
+ /**
59
+ * Namespace object for backward compatibility and namespaced access.
60
+ * Delegates to standalone `classifyTrust` function.
61
+ */
62
+ export const TrustClassifier = {
63
+ classify(dirPath: string): Promise<{ trust: TrustLevel; inventory: SkillInventoryItem[] }> {
64
+ return Promise.resolve(classifyTrust(dirPath));
65
+ },
66
+ };
67
+
68
+ /** Recursively walk a directory and classify all files */
69
+ function walkDir(rootDir: string, currentDir: string, inventory: SkillInventoryItem[]): void {
70
+ let names: string[];
71
+ try {
72
+ names = readdirSync(currentDir);
73
+ } catch {
74
+ return;
75
+ }
76
+
77
+ for (const name of names) {
78
+ const fullPath = join(currentDir, name);
79
+ let stat;
80
+ try {
81
+ stat = statSync(fullPath);
82
+ } catch {
83
+ continue;
84
+ }
85
+
86
+ if (stat.isDirectory()) {
87
+ // Skip hidden directories and node_modules
88
+ if (name.startsWith('.') || name === 'node_modules') continue;
89
+ walkDir(rootDir, fullPath, inventory);
90
+ continue;
91
+ }
92
+
93
+ if (!stat.isFile()) continue;
94
+
95
+ const relPath = relative(rootDir, fullPath);
96
+ const ext = extname(name).toLowerCase();
97
+ const kind = classifyFile(name, ext);
98
+
99
+ inventory.push({ path: relPath, kind });
100
+ }
101
+ }
102
+
103
+ /** Classify a single file by its extension and name */
104
+ function classifyFile(fileName: string, ext: string): SkillInventoryItem['kind'] {
105
+ // SKILL.md is the primary skill definition
106
+ if (fileName === 'SKILL.md' || fileName === 'skill.md') {
107
+ return 'skill';
108
+ }
109
+
110
+ // Declaration files are not executable
111
+ if (DECLARATION_PATTERN.test(fileName)) {
112
+ return 'reference';
113
+ }
114
+
115
+ // Script files
116
+ if (SCRIPT_EXTENSIONS.has(ext)) {
117
+ return 'script';
118
+ }
119
+
120
+ // Markdown files are references
121
+ if (MARKDOWN_EXTENSIONS.has(ext)) {
122
+ return 'reference';
123
+ }
124
+
125
+ // Everything else is an asset
126
+ return 'asset';
127
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Per-agent-type semaphore for controlling concurrent subagent runs.
3
+ *
4
+ * Pure promise-based — zero external deps. FIFO ordering guarantees
5
+ * the first waiter is the first to acquire a slot when one frees up.
6
+ */
7
+
8
+ interface TypeState {
9
+ active: number;
10
+ waiters: Array<() => void>;
11
+ }
12
+
13
+ const DEFAULT_MAX_CONCURRENT = 3;
14
+
15
+ export class ConcurrencyManager {
16
+ private state: Map<string, TypeState> = new Map();
17
+
18
+ /**
19
+ * Acquire a concurrency slot for the given adapter type.
20
+ * Resolves immediately if a slot is available, otherwise queues
21
+ * and resolves when a slot frees up (FIFO).
22
+ */
23
+ async acquire(type: string, maxConcurrent: number = DEFAULT_MAX_CONCURRENT): Promise<void> {
24
+ const entry = this.getOrCreate(type);
25
+
26
+ if (entry.active < maxConcurrent) {
27
+ entry.active++;
28
+ return;
29
+ }
30
+
31
+ // At capacity — park until a slot opens.
32
+ return new Promise<void>((resolve) => {
33
+ entry.waiters.push(() => {
34
+ entry.active++;
35
+ resolve();
36
+ });
37
+ });
38
+ }
39
+
40
+ /**
41
+ * Release a concurrency slot for the given adapter type.
42
+ * If waiters exist, the first one (FIFO) is unblocked.
43
+ * No-op if the type is not tracked.
44
+ */
45
+ release(type: string): void {
46
+ const entry = this.state.get(type);
47
+ if (!entry) return;
48
+
49
+ entry.active = Math.max(0, entry.active - 1);
50
+
51
+ if (entry.waiters.length > 0) {
52
+ const next = entry.waiters.shift()!;
53
+ next();
54
+ }
55
+ }
56
+
57
+ /** Return the number of active slots for a type (0 if untracked). */
58
+ getActive(type: string): number {
59
+ return this.state.get(type)?.active ?? 0;
60
+ }
61
+
62
+ /** Return the number of waiters queued for a type (0 if untracked). */
63
+ getWaiting(type: string): number {
64
+ return this.state.get(type)?.waiters.length ?? 0;
65
+ }
66
+
67
+ /** Clear all state, resolving any pending waiters immediately. */
68
+ reset(): void {
69
+ for (const entry of this.state.values()) {
70
+ for (const waiter of entry.waiters) {
71
+ waiter();
72
+ }
73
+ entry.waiters.length = 0;
74
+ entry.active = 0;
75
+ }
76
+ this.state.clear();
77
+ }
78
+
79
+ // ── internal ──────────────────────────────────────────────────────
80
+
81
+ private getOrCreate(type: string): TypeState {
82
+ let entry = this.state.get(type);
83
+ if (!entry) {
84
+ entry = { active: 0, waiters: [] };
85
+ this.state.set(type, entry);
86
+ }
87
+ return entry;
88
+ }
89
+ }