apcore-js 0.5.0 → 0.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 (319) hide show
  1. package/README.md +1 -1
  2. package/dist/acl.d.ts +27 -0
  3. package/dist/acl.d.ts.map +1 -0
  4. package/dist/acl.js +175 -0
  5. package/dist/acl.js.map +1 -0
  6. package/dist/approval.d.ts +85 -0
  7. package/dist/approval.d.ts.map +1 -0
  8. package/dist/approval.js +73 -0
  9. package/dist/approval.js.map +1 -0
  10. package/dist/async-task.d.ts +90 -0
  11. package/dist/async-task.d.ts.map +1 -0
  12. package/dist/async-task.js +215 -0
  13. package/dist/async-task.js.map +1 -0
  14. package/dist/bindings.d.ts +12 -0
  15. package/dist/bindings.d.ts.map +1 -0
  16. package/dist/bindings.js +185 -0
  17. package/dist/bindings.js.map +1 -0
  18. package/dist/cancel.d.ts +14 -0
  19. package/dist/cancel.d.ts.map +1 -0
  20. package/dist/cancel.js +27 -0
  21. package/dist/cancel.js.map +1 -0
  22. package/dist/config.d.ts +9 -0
  23. package/dist/config.d.ts.map +1 -0
  24. package/dist/config.js +23 -0
  25. package/dist/config.js.map +1 -0
  26. package/dist/context.d.ts +50 -0
  27. package/dist/context.d.ts.map +1 -0
  28. package/dist/context.js +87 -0
  29. package/dist/context.js.map +1 -0
  30. package/dist/decorator.d.ts +57 -0
  31. package/dist/decorator.d.ts.map +1 -0
  32. package/dist/decorator.js +74 -0
  33. package/dist/decorator.js.map +1 -0
  34. package/dist/errors.d.ts +204 -0
  35. package/dist/errors.d.ts.map +1 -0
  36. package/dist/errors.js +364 -0
  37. package/dist/errors.js.map +1 -0
  38. package/dist/executor.d.ts +82 -0
  39. package/dist/executor.d.ts.map +1 -0
  40. package/dist/executor.js +489 -0
  41. package/dist/executor.js.map +1 -0
  42. package/dist/extensions.d.ts +58 -0
  43. package/dist/extensions.d.ts.map +1 -0
  44. package/dist/extensions.js +239 -0
  45. package/dist/extensions.js.map +1 -0
  46. package/{src/index.ts → dist/index.d.ts} +6 -63
  47. package/dist/index.d.ts.map +1 -0
  48. package/dist/index.js +45 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/middleware/adapters.d.ts +18 -0
  51. package/dist/middleware/adapters.d.ts.map +1 -0
  52. package/dist/middleware/adapters.js +25 -0
  53. package/dist/middleware/adapters.js.map +1 -0
  54. package/dist/middleware/base.d.ts +10 -0
  55. package/dist/middleware/base.d.ts.map +1 -0
  56. package/dist/middleware/base.js +15 -0
  57. package/dist/middleware/base.js.map +1 -0
  58. package/{src/middleware/index.ts → dist/middleware/index.d.ts} +1 -0
  59. package/dist/middleware/index.d.ts.map +1 -0
  60. package/dist/middleware/index.js +5 -0
  61. package/dist/middleware/index.js.map +1 -0
  62. package/dist/middleware/logging.d.ts +25 -0
  63. package/dist/middleware/logging.d.ts.map +1 -0
  64. package/dist/middleware/logging.js +64 -0
  65. package/dist/middleware/logging.js.map +1 -0
  66. package/dist/middleware/manager.d.ts +21 -0
  67. package/dist/middleware/manager.d.ts.map +1 -0
  68. package/dist/middleware/manager.js +77 -0
  69. package/dist/middleware/manager.js.map +1 -0
  70. package/dist/module.d.ts +31 -0
  71. package/dist/module.d.ts.map +1 -0
  72. package/dist/module.js +12 -0
  73. package/dist/module.js.map +1 -0
  74. package/dist/observability/context-logger.d.ts +54 -0
  75. package/dist/observability/context-logger.d.ts.map +1 -0
  76. package/dist/observability/context-logger.js +151 -0
  77. package/dist/observability/context-logger.js.map +1 -0
  78. package/{src/observability/index.ts → dist/observability/index.d.ts} +1 -0
  79. package/dist/observability/index.d.ts.map +1 -0
  80. package/dist/observability/index.js +4 -0
  81. package/dist/observability/index.js.map +1 -0
  82. package/dist/observability/metrics.d.ts +30 -0
  83. package/dist/observability/metrics.d.ts.map +1 -0
  84. package/dist/observability/metrics.js +177 -0
  85. package/dist/observability/metrics.js.map +1 -0
  86. package/dist/observability/tracing.d.ts +62 -0
  87. package/dist/observability/tracing.d.ts.map +1 -0
  88. package/dist/observability/tracing.js +184 -0
  89. package/dist/observability/tracing.js.map +1 -0
  90. package/dist/registry/dependencies.d.ts +6 -0
  91. package/dist/registry/dependencies.d.ts.map +1 -0
  92. package/dist/registry/dependencies.js +83 -0
  93. package/dist/registry/dependencies.js.map +1 -0
  94. package/dist/registry/entry-point.d.ts +6 -0
  95. package/dist/registry/entry-point.d.ts.map +1 -0
  96. package/dist/registry/entry-point.js +55 -0
  97. package/dist/registry/entry-point.js.map +1 -0
  98. package/{src/registry/index.ts → dist/registry/index.d.ts} +1 -0
  99. package/dist/registry/index.d.ts.map +1 -0
  100. package/dist/registry/index.js +8 -0
  101. package/dist/registry/index.js.map +1 -0
  102. package/dist/registry/metadata.d.ts +9 -0
  103. package/dist/registry/metadata.d.ts.map +1 -0
  104. package/dist/registry/metadata.js +105 -0
  105. package/dist/registry/metadata.js.map +1 -0
  106. package/dist/registry/registry.d.ts +102 -0
  107. package/dist/registry/registry.d.ts.map +1 -0
  108. package/dist/registry/registry.js +534 -0
  109. package/dist/registry/registry.js.map +1 -0
  110. package/dist/registry/scanner.d.ts +7 -0
  111. package/dist/registry/scanner.d.ts.map +1 -0
  112. package/dist/registry/scanner.js +164 -0
  113. package/dist/registry/scanner.js.map +1 -0
  114. package/dist/registry/schema-export.d.ts +9 -0
  115. package/dist/registry/schema-export.d.ts.map +1 -0
  116. package/dist/registry/schema-export.js +132 -0
  117. package/dist/registry/schema-export.js.map +1 -0
  118. package/dist/registry/types.d.ts +29 -0
  119. package/dist/registry/types.d.ts.map +1 -0
  120. package/dist/registry/types.js +5 -0
  121. package/dist/registry/types.js.map +1 -0
  122. package/dist/registry/validation.d.ts +9 -0
  123. package/dist/registry/validation.d.ts.map +1 -0
  124. package/dist/registry/validation.js +33 -0
  125. package/dist/registry/validation.js.map +1 -0
  126. package/dist/schema/annotations.d.ts +8 -0
  127. package/dist/schema/annotations.d.ts.map +1 -0
  128. package/dist/schema/annotations.js +52 -0
  129. package/dist/schema/annotations.js.map +1 -0
  130. package/dist/schema/exporter.d.ts +13 -0
  131. package/dist/schema/exporter.d.ts.map +1 -0
  132. package/dist/schema/exporter.js +71 -0
  133. package/dist/schema/exporter.js.map +1 -0
  134. package/dist/schema/index.d.ts +9 -0
  135. package/dist/schema/index.d.ts.map +1 -0
  136. package/{src/schema/index.ts → dist/schema/index.js} +1 -7
  137. package/dist/schema/index.js.map +1 -0
  138. package/dist/schema/loader.d.ts +30 -0
  139. package/dist/schema/loader.d.ts.map +1 -0
  140. package/dist/schema/loader.js +260 -0
  141. package/dist/schema/loader.js.map +1 -0
  142. package/dist/schema/ref-resolver.d.ts +19 -0
  143. package/dist/schema/ref-resolver.d.ts.map +1 -0
  144. package/dist/schema/ref-resolver.js +212 -0
  145. package/dist/schema/ref-resolver.js.map +1 -0
  146. package/dist/schema/strict.d.ts +7 -0
  147. package/dist/schema/strict.d.ts.map +1 -0
  148. package/dist/schema/strict.js +127 -0
  149. package/dist/schema/strict.js.map +1 -0
  150. package/dist/schema/types.d.ts +53 -0
  151. package/dist/schema/types.d.ts.map +1 -0
  152. package/dist/schema/types.js +31 -0
  153. package/dist/schema/types.js.map +1 -0
  154. package/dist/schema/validator.d.ts +16 -0
  155. package/dist/schema/validator.d.ts.map +1 -0
  156. package/dist/schema/validator.js +71 -0
  157. package/dist/schema/validator.js.map +1 -0
  158. package/dist/trace-context.d.ts +35 -0
  159. package/dist/trace-context.d.ts.map +1 -0
  160. package/dist/trace-context.js +86 -0
  161. package/dist/trace-context.js.map +1 -0
  162. package/dist/utils/index.d.ts +11 -0
  163. package/dist/utils/index.d.ts.map +1 -0
  164. package/dist/utils/index.js +32 -0
  165. package/dist/utils/index.js.map +1 -0
  166. package/dist/utils/pattern.d.ts +5 -0
  167. package/dist/utils/pattern.d.ts.map +1 -0
  168. package/dist/utils/pattern.js +31 -0
  169. package/dist/utils/pattern.js.map +1 -0
  170. package/package.json +24 -3
  171. package/.claude/settings.local.json +0 -12
  172. package/.github/workflows/ci.yml +0 -39
  173. package/.gitmessage +0 -60
  174. package/.pre-commit-config.yaml +0 -28
  175. package/CHANGELOG.md +0 -214
  176. package/CLAUDE.md +0 -68
  177. package/apcore-logo.svg +0 -79
  178. package/planning/acl-system/overview.md +0 -54
  179. package/planning/acl-system/plan.md +0 -92
  180. package/planning/acl-system/state.json +0 -76
  181. package/planning/acl-system/tasks/acl-core.md +0 -226
  182. package/planning/acl-system/tasks/acl-rule.md +0 -92
  183. package/planning/acl-system/tasks/conditional-rules.md +0 -259
  184. package/planning/acl-system/tasks/pattern-matching.md +0 -152
  185. package/planning/acl-system/tasks/yaml-loading.md +0 -271
  186. package/planning/core-executor/overview.md +0 -53
  187. package/planning/core-executor/plan.md +0 -88
  188. package/planning/core-executor/state.json +0 -76
  189. package/planning/core-executor/tasks/async-support.md +0 -106
  190. package/planning/core-executor/tasks/execution-pipeline.md +0 -113
  191. package/planning/core-executor/tasks/redaction.md +0 -85
  192. package/planning/core-executor/tasks/safety-checks.md +0 -65
  193. package/planning/core-executor/tasks/setup.md +0 -75
  194. package/planning/decorator-bindings/overview.md +0 -62
  195. package/planning/decorator-bindings/plan.md +0 -104
  196. package/planning/decorator-bindings/state.json +0 -87
  197. package/planning/decorator-bindings/tasks/binding-directory.md +0 -79
  198. package/planning/decorator-bindings/tasks/binding-loader.md +0 -148
  199. package/planning/decorator-bindings/tasks/explicit-schemas.md +0 -85
  200. package/planning/decorator-bindings/tasks/function-module.md +0 -127
  201. package/planning/decorator-bindings/tasks/module-factory.md +0 -89
  202. package/planning/decorator-bindings/tasks/schema-modes.md +0 -142
  203. package/planning/middleware-system/overview.md +0 -48
  204. package/planning/middleware-system/plan.md +0 -102
  205. package/planning/middleware-system/state.json +0 -65
  206. package/planning/middleware-system/tasks/adapters.md +0 -170
  207. package/planning/middleware-system/tasks/base.md +0 -115
  208. package/planning/middleware-system/tasks/logging-middleware.md +0 -304
  209. package/planning/middleware-system/tasks/manager.md +0 -313
  210. package/planning/observability/overview.md +0 -53
  211. package/planning/observability/plan.md +0 -119
  212. package/planning/observability/state.json +0 -98
  213. package/planning/observability/tasks/context-logger.md +0 -201
  214. package/planning/observability/tasks/exporters.md +0 -121
  215. package/planning/observability/tasks/metrics-collector.md +0 -162
  216. package/planning/observability/tasks/metrics-middleware.md +0 -141
  217. package/planning/observability/tasks/obs-logging-middleware.md +0 -179
  218. package/planning/observability/tasks/span-model.md +0 -120
  219. package/planning/observability/tasks/tracing-middleware.md +0 -179
  220. package/planning/overview.md +0 -81
  221. package/planning/registry-system/overview.md +0 -57
  222. package/planning/registry-system/plan.md +0 -114
  223. package/planning/registry-system/state.json +0 -109
  224. package/planning/registry-system/tasks/dependencies.md +0 -157
  225. package/planning/registry-system/tasks/entry-point.md +0 -148
  226. package/planning/registry-system/tasks/metadata.md +0 -198
  227. package/planning/registry-system/tasks/registry-core.md +0 -323
  228. package/planning/registry-system/tasks/scanner.md +0 -172
  229. package/planning/registry-system/tasks/schema-export.md +0 -261
  230. package/planning/registry-system/tasks/types.md +0 -124
  231. package/planning/registry-system/tasks/validation.md +0 -177
  232. package/planning/schema-system/overview.md +0 -56
  233. package/planning/schema-system/plan.md +0 -121
  234. package/planning/schema-system/state.json +0 -98
  235. package/planning/schema-system/tasks/exporter.md +0 -153
  236. package/planning/schema-system/tasks/loader.md +0 -106
  237. package/planning/schema-system/tasks/ref-resolver.md +0 -133
  238. package/planning/schema-system/tasks/strict-mode.md +0 -140
  239. package/planning/schema-system/tasks/typebox-generation.md +0 -133
  240. package/planning/schema-system/tasks/types-and-annotations.md +0 -160
  241. package/planning/schema-system/tasks/validator.md +0 -149
  242. package/src/acl.ts +0 -200
  243. package/src/async-task.ts +0 -267
  244. package/src/bindings.ts +0 -207
  245. package/src/cancel.ts +0 -32
  246. package/src/config.ts +0 -24
  247. package/src/context.ts +0 -160
  248. package/src/decorator.ts +0 -110
  249. package/src/errors.ts +0 -429
  250. package/src/executor.ts +0 -493
  251. package/src/extensions.ts +0 -265
  252. package/src/middleware/adapters.ts +0 -54
  253. package/src/middleware/base.ts +0 -33
  254. package/src/middleware/logging.ts +0 -103
  255. package/src/middleware/manager.ts +0 -105
  256. package/src/module.ts +0 -43
  257. package/src/observability/context-logger.ts +0 -203
  258. package/src/observability/metrics.ts +0 -214
  259. package/src/observability/tracing.ts +0 -252
  260. package/src/registry/dependencies.ts +0 -99
  261. package/src/registry/entry-point.ts +0 -64
  262. package/src/registry/metadata.ts +0 -111
  263. package/src/registry/registry.ts +0 -580
  264. package/src/registry/scanner.ts +0 -168
  265. package/src/registry/schema-export.ts +0 -181
  266. package/src/registry/types.ts +0 -32
  267. package/src/registry/validation.ts +0 -38
  268. package/src/schema/annotations.ts +0 -68
  269. package/src/schema/exporter.ts +0 -90
  270. package/src/schema/loader.ts +0 -273
  271. package/src/schema/ref-resolver.ts +0 -244
  272. package/src/schema/strict.ts +0 -136
  273. package/src/schema/types.ts +0 -73
  274. package/src/schema/validator.ts +0 -82
  275. package/src/trace-context.ts +0 -102
  276. package/src/utils/index.ts +0 -5
  277. package/src/utils/pattern.ts +0 -30
  278. package/tests/async-task.test.ts +0 -335
  279. package/tests/helpers.ts +0 -30
  280. package/tests/integration/test-acl-safety.test.ts +0 -269
  281. package/tests/integration/test-binding-executor.test.ts +0 -194
  282. package/tests/integration/test-e2e-flow.test.ts +0 -117
  283. package/tests/integration/test-error-propagation.test.ts +0 -259
  284. package/tests/integration/test-middleware-chain.test.ts +0 -120
  285. package/tests/integration/test-observability-integration.test.ts +0 -438
  286. package/tests/observability/test-context-logger.test.ts +0 -123
  287. package/tests/observability/test-metrics.test.ts +0 -186
  288. package/tests/observability/test-tracing.test.ts +0 -303
  289. package/tests/registry/test-dependencies.test.ts +0 -70
  290. package/tests/registry/test-entry-point.test.ts +0 -133
  291. package/tests/registry/test-metadata.test.ts +0 -265
  292. package/tests/registry/test-registry.test.ts +0 -1397
  293. package/tests/registry/test-scanner.test.ts +0 -257
  294. package/tests/registry/test-schema-export.test.ts +0 -355
  295. package/tests/registry/test-validation.test.ts +0 -75
  296. package/tests/schema/test-annotations.test.ts +0 -137
  297. package/tests/schema/test-exporter.test.ts +0 -172
  298. package/tests/schema/test-loader.test.ts +0 -461
  299. package/tests/schema/test-ref-resolver.test.ts +0 -530
  300. package/tests/schema/test-strict.test.ts +0 -348
  301. package/tests/schema/test-validator.test.ts +0 -64
  302. package/tests/test-acl.test.ts +0 -423
  303. package/tests/test-bindings.test.ts +0 -227
  304. package/tests/test-cancel.test.ts +0 -71
  305. package/tests/test-config.test.ts +0 -76
  306. package/tests/test-context.test.ts +0 -266
  307. package/tests/test-decorator.test.ts +0 -173
  308. package/tests/test-errors.test.ts +0 -647
  309. package/tests/test-executor-stream.test.ts +0 -208
  310. package/tests/test-executor.test.ts +0 -252
  311. package/tests/test-extensions.test.ts +0 -310
  312. package/tests/test-logging-middleware.test.ts +0 -150
  313. package/tests/test-middleware-manager.test.ts +0 -185
  314. package/tests/test-middleware.test.ts +0 -86
  315. package/tests/test-trace-context.test.ts +0 -251
  316. package/tests/utils/test-pattern.test.ts +0 -109
  317. package/tsconfig.build.json +0 -8
  318. package/tsconfig.json +0 -20
  319. package/vitest.config.ts +0 -18
@@ -1,323 +0,0 @@
1
- # Task: Registry Core
2
-
3
- ## Goal
4
-
5
- Implement the central `Registry` class that orchestrates the full 8-step async discovery pipeline and provides module query, registration, event, and cache management APIs. The constructor accepts optional configuration for extension roots and ID maps. The async `discover()` method coordinates scanning, metadata loading, entry point resolution, validation, dependency resolution, and ordered registration. Manual `register()`/`unregister()` methods support programmatic module management with lifecycle hooks (`onLoad`/`onUnload`) and event callbacks.
6
-
7
- ## Files Involved
8
-
9
- - `src/registry/registry.ts` -- Registry class implementation
10
- - `src/registry/scanner.ts` -- `scanExtensions()`, `scanMultiRoot()`
11
- - `src/registry/metadata.ts` -- `loadMetadata()`, `mergeModuleMetadata()`, `loadIdMap()`, `parseDependencies()`
12
- - `src/registry/dependencies.ts` -- `resolveDependencies()`
13
- - `src/registry/entry-point.ts` -- `resolveEntryPoint()`
14
- - `src/registry/validation.ts` -- `validateModule()`
15
- - `src/registry/types.ts` -- `ModuleDescriptor`, `DependencyInfo`
16
- - `src/config.ts` -- `Config` class
17
- - `src/errors.ts` -- `InvalidInputError`, `ModuleNotFoundError`
18
- - `tests/registry/test-registry.test.ts` -- Registry integration tests
19
-
20
- ## Steps (TDD)
21
-
22
- ### Step 1: Constructor with extension root options
23
-
24
- ```typescript
25
- import { describe, it, expect } from 'vitest';
26
- import { Registry } from '../../src/registry/registry.js';
27
-
28
- describe('Registry constructor', () => {
29
- it('should default to ./extensions root', () => {
30
- const reg = new Registry();
31
- expect(reg.count).toBe(0);
32
- });
33
-
34
- it('should accept extensionsDir option', () => {
35
- const reg = new Registry({ extensionsDir: '/custom/path' });
36
- expect(reg.count).toBe(0);
37
- });
38
-
39
- it('should throw when both extensionsDir and extensionsDirs are provided', () => {
40
- expect(
41
- () => new Registry({ extensionsDir: '/a', extensionsDirs: ['/b'] }),
42
- ).toThrow(/Cannot specify both/);
43
- });
44
- });
45
- ```
46
-
47
- ### Step 2: Manual register() and unregister()
48
-
49
- ```typescript
50
- describe('register/unregister', () => {
51
- it('should register a module and make it queryable', () => {
52
- const reg = new Registry();
53
- const mod = {
54
- inputSchema: { type: 'object' },
55
- outputSchema: { type: 'object' },
56
- description: 'Test',
57
- execute: async () => ({}),
58
- };
59
- reg.register('test.mod', mod);
60
- expect(reg.has('test.mod')).toBe(true);
61
- expect(reg.get('test.mod')).toBe(mod);
62
- expect(reg.count).toBe(1);
63
- });
64
-
65
- it('should throw on duplicate registration', () => {
66
- const reg = new Registry();
67
- const mod = { execute: async () => ({}) };
68
- reg.register('dup', mod);
69
- expect(() => reg.register('dup', mod)).toThrow(/already exists/);
70
- });
71
-
72
- it('should throw on empty module ID', () => {
73
- const reg = new Registry();
74
- expect(() => reg.register('', {})).toThrow(/non-empty/);
75
- });
76
-
77
- it('should unregister and return true', () => {
78
- const reg = new Registry();
79
- reg.register('mod', {});
80
- expect(reg.unregister('mod')).toBe(true);
81
- expect(reg.has('mod')).toBe(false);
82
- expect(reg.count).toBe(0);
83
- });
84
-
85
- it('should return false for unregistering non-existent module', () => {
86
- const reg = new Registry();
87
- expect(reg.unregister('nonexistent')).toBe(false);
88
- });
89
- });
90
- ```
91
-
92
- ### Step 3: Lifecycle hooks (onLoad/onUnload)
93
-
94
- ```typescript
95
- describe('lifecycle hooks', () => {
96
- it('should call onLoad during register()', () => {
97
- const reg = new Registry();
98
- let loaded = false;
99
- const mod = { onLoad: () => { loaded = true; } };
100
- reg.register('hook.mod', mod);
101
- expect(loaded).toBe(true);
102
- });
103
-
104
- it('should rollback registration if onLoad throws', () => {
105
- const reg = new Registry();
106
- const mod = { onLoad: () => { throw new Error('init failed'); } };
107
- expect(() => reg.register('fail.mod', mod)).toThrow('init failed');
108
- expect(reg.has('fail.mod')).toBe(false);
109
- });
110
-
111
- it('should call onUnload during unregister()', () => {
112
- const reg = new Registry();
113
- let unloaded = false;
114
- const mod = { onUnload: () => { unloaded = true; } };
115
- reg.register('unload.mod', mod);
116
- reg.unregister('unload.mod');
117
- expect(unloaded).toBe(true);
118
- });
119
-
120
- it('should swallow onUnload errors', () => {
121
- const reg = new Registry();
122
- const mod = { onUnload: () => { throw new Error('cleanup failed'); } };
123
- reg.register('err.mod', mod);
124
- expect(() => reg.unregister('err.mod')).not.toThrow();
125
- });
126
- });
127
- ```
128
-
129
- ### Step 4: Query methods (get, has, list, iter, count, moduleIds)
130
-
131
- ```typescript
132
- describe('query methods', () => {
133
- it('should return null for non-existent module via get()', () => {
134
- const reg = new Registry();
135
- expect(reg.get('nope')).toBeNull();
136
- });
137
-
138
- it('should throw ModuleNotFoundError for empty string via get()', () => {
139
- const reg = new Registry();
140
- expect(() => reg.get('')).toThrow();
141
- });
142
-
143
- it('should list module IDs in sorted order', () => {
144
- const reg = new Registry();
145
- reg.register('z.mod', {});
146
- reg.register('a.mod', {});
147
- reg.register('m.mod', {});
148
- expect(reg.list()).toEqual(['a.mod', 'm.mod', 'z.mod']);
149
- });
150
-
151
- it('should filter list by prefix', () => {
152
- const reg = new Registry();
153
- reg.register('math.add', {});
154
- reg.register('math.sub', {});
155
- reg.register('text.upper', {});
156
- expect(reg.list({ prefix: 'math.' })).toEqual(['math.add', 'math.sub']);
157
- });
158
-
159
- it('should filter list by tags', () => {
160
- const reg = new Registry();
161
- reg.register('tagged', { tags: ['math', 'core'] });
162
- reg.register('untagged', {});
163
- expect(reg.list({ tags: ['math'] })).toEqual(['tagged']);
164
- });
165
-
166
- it('should iterate over modules via iter()', () => {
167
- const reg = new Registry();
168
- reg.register('a', { value: 1 });
169
- reg.register('b', { value: 2 });
170
- const entries = [...reg.iter()];
171
- expect(entries).toHaveLength(2);
172
- });
173
-
174
- it('should return sorted moduleIds', () => {
175
- const reg = new Registry();
176
- reg.register('z', {});
177
- reg.register('a', {});
178
- expect(reg.moduleIds).toEqual(['a', 'z']);
179
- });
180
- });
181
- ```
182
-
183
- ### Step 5: getDefinition() returns ModuleDescriptor
184
-
185
- ```typescript
186
- describe('getDefinition', () => {
187
- it('should return ModuleDescriptor for registered module', () => {
188
- const reg = new Registry();
189
- const mod = {
190
- name: 'Adder',
191
- description: 'Adds numbers',
192
- inputSchema: { type: 'object' },
193
- outputSchema: { type: 'object' },
194
- version: '2.0.0',
195
- tags: ['math'],
196
- };
197
- reg.register('math.add', mod);
198
- const def = reg.getDefinition('math.add');
199
- expect(def).not.toBeNull();
200
- expect(def!.moduleId).toBe('math.add');
201
- expect(def!.name).toBe('Adder');
202
- expect(def!.description).toBe('Adds numbers');
203
- expect(def!.version).toBe('2.0.0');
204
- });
205
-
206
- it('should return null for non-existent module', () => {
207
- const reg = new Registry();
208
- expect(reg.getDefinition('nope')).toBeNull();
209
- });
210
- });
211
- ```
212
-
213
- ### Step 6: Event system via on()
214
-
215
- ```typescript
216
- describe('event system', () => {
217
- it('should fire register event callback', () => {
218
- const reg = new Registry();
219
- const events: string[] = [];
220
- reg.on('register', (id) => events.push(id));
221
- reg.register('evented', {});
222
- expect(events).toEqual(['evented']);
223
- });
224
-
225
- it('should fire unregister event callback', () => {
226
- const reg = new Registry();
227
- const events: string[] = [];
228
- reg.on('unregister', (id) => events.push(id));
229
- reg.register('temp', {});
230
- reg.unregister('temp');
231
- expect(events).toEqual(['temp']);
232
- });
233
-
234
- it('should throw on invalid event name', () => {
235
- const reg = new Registry();
236
- expect(() => reg.on('invalid', () => {})).toThrow(/Invalid event/);
237
- });
238
-
239
- it('should swallow callback errors silently', () => {
240
- const reg = new Registry();
241
- reg.on('register', () => { throw new Error('callback failed'); });
242
- expect(() => reg.register('safe', {})).not.toThrow();
243
- });
244
- });
245
- ```
246
-
247
- ### Step 7: clearCache()
248
-
249
- ```typescript
250
- describe('clearCache', () => {
251
- it('should clear the schema cache without affecting modules', () => {
252
- const reg = new Registry();
253
- reg.register('cached', {});
254
- reg.clearCache();
255
- expect(reg.has('cached')).toBe(true);
256
- });
257
- });
258
- ```
259
-
260
- ### Step 8: Async discover() pipeline (integration)
261
-
262
- ```typescript
263
- describe('discover', () => {
264
- it('should execute the 8-step pipeline and return registered count', async () => {
265
- // Create temp directory with valid module files
266
- // const reg = new Registry({ extensionsDir: tempDir });
267
- // const count = await reg.discover();
268
- // expect(count).toBeGreaterThanOrEqual(0);
269
- // expect(reg.count).toBe(count);
270
- });
271
- });
272
- ```
273
-
274
- The 8-step `discover()` pipeline:
275
-
276
- ```typescript
277
- async discover(): Promise<number> {
278
- // Step 1: Scan extension roots (scanExtensions or scanMultiRoot)
279
- // Step 2: Apply ID map overrides
280
- // Step 3: Load metadata (loadMetadata for each discovered module)
281
- // Step 4: Resolve entry points (await resolveEntryPoint for each)
282
- // Step 5: Validate modules (validateModule, drop invalid)
283
- // Step 6: Collect dependencies (parseDependencies from metadata)
284
- // Step 7: Resolve dependency order (resolveDependencies via Kahn's sort)
285
- // Step 8: Register in dependency order (mergeModuleMetadata, onLoad, trigger events)
286
- return registeredCount;
287
- }
288
- ```
289
-
290
- ## Acceptance Criteria
291
-
292
- - [x] Constructor accepts optional `config`, `extensionsDir`, `extensionsDirs`, `idMapPath`
293
- - [x] `extensionsDir` and `extensionsDirs` are mutually exclusive (throws `InvalidInputError`)
294
- - [x] `discover()` is async, returns `Promise<number>` of registered modules
295
- - [x] `discover()` executes all 8 pipeline steps in order
296
- - [x] `register()` adds modules with `onLoad` lifecycle hook
297
- - [x] `register()` throws on empty ID or duplicate ID
298
- - [x] `register()` rolls back on `onLoad` failure
299
- - [x] `unregister()` removes module, clears metadata and schema cache, calls `onUnload`
300
- - [x] `unregister()` swallows `onUnload` errors
301
- - [x] `get()` returns module or null; throws `ModuleNotFoundError` for empty string
302
- - [x] `has()` returns boolean for module existence
303
- - [x] `list()` returns sorted IDs with optional prefix and tags filtering
304
- - [x] `iter()` returns `IterableIterator<[string, unknown]>`
305
- - [x] `count` getter returns module count
306
- - [x] `moduleIds` getter returns sorted ID array
307
- - [x] `getDefinition()` returns `ModuleDescriptor` or null
308
- - [x] `on()` registers event callbacks for 'register' and 'unregister'
309
- - [x] Event callbacks that throw are silently swallowed
310
- - [x] `clearCache()` clears schema cache without affecting registered modules
311
- - [x] All tests pass with `vitest`
312
-
313
- ## Dependencies
314
-
315
- - `scanner` task
316
- - `metadata` task
317
- - `dependencies` task
318
- - `entry-point` task
319
- - `validation` task
320
-
321
- ## Estimated Time
322
-
323
- 5 hours
@@ -1,172 +0,0 @@
1
- # Task: Scanner
2
-
3
- ## Goal
4
-
5
- Implement `scanExtensions()` and `scanMultiRoot()` functions that recursively walk extension directories, discover `.ts`/`.js` module files, build dot-notation canonical IDs from relative paths, detect companion `_meta.yaml` files, handle case collisions, and support configurable depth limits and symlink following. `scanMultiRoot()` coordinates multiple extension roots with namespace prefixing and uniqueness enforcement.
6
-
7
- ## Files Involved
8
-
9
- - `src/registry/scanner.ts` -- Scanner implementation
10
- - `src/registry/types.ts` -- `DiscoveredModule` interface
11
- - `src/errors.ts` -- `ConfigError`, `ConfigNotFoundError`
12
- - `tests/registry/test-scanner.test.ts` -- Scanner tests with temp directory fixtures
13
-
14
- ## Steps (TDD)
15
-
16
- ### Step 1: Implement scanExtensions() basic file discovery
17
-
18
- Write a test that creates a temp directory with `.ts` files and verifies discovery:
19
-
20
- ```typescript
21
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
22
- import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
23
- import { join } from 'node:path';
24
- import { tmpdir } from 'node:os';
25
- import { scanExtensions } from '../../src/registry/scanner.js';
26
-
27
- describe('scanExtensions', () => {
28
- let tempDir: string;
29
-
30
- beforeEach(() => {
31
- tempDir = mkdtempSync(join(tmpdir(), 'scan-'));
32
- });
33
-
34
- afterEach(() => {
35
- rmSync(tempDir, { recursive: true, force: true });
36
- });
37
-
38
- it('should discover .ts and .js files', () => {
39
- writeFileSync(join(tempDir, 'add.ts'), 'export default {}');
40
- writeFileSync(join(tempDir, 'sub.js'), 'module.exports = {}');
41
- const results = scanExtensions(tempDir);
42
- const ids = results.map((r) => r.canonicalId).sort();
43
- expect(ids).toEqual(['add', 'sub']);
44
- });
45
- });
46
- ```
47
-
48
- Implement the recursive directory walker with `readdirSync`/`statSync`, filtering valid extensions.
49
-
50
- ### Step 2: Skip .d.ts, test files, dot/underscore prefixed entries
51
-
52
- ```typescript
53
- it('should skip declaration and test files', () => {
54
- writeFileSync(join(tempDir, 'types.d.ts'), '');
55
- writeFileSync(join(tempDir, 'foo.test.ts'), '');
56
- writeFileSync(join(tempDir, 'bar.spec.js'), '');
57
- writeFileSync(join(tempDir, 'valid.ts'), 'export default {}');
58
- const results = scanExtensions(tempDir);
59
- expect(results).toHaveLength(1);
60
- expect(results[0].canonicalId).toBe('valid');
61
- });
62
-
63
- it('should skip dot-prefixed and underscore-prefixed entries', () => {
64
- writeFileSync(join(tempDir, '.hidden.ts'), '');
65
- writeFileSync(join(tempDir, '_private.ts'), '');
66
- writeFileSync(join(tempDir, 'public.ts'), 'export default {}');
67
- const results = scanExtensions(tempDir);
68
- expect(results).toHaveLength(1);
69
- });
70
- ```
71
-
72
- Add `SKIP_SUFFIXES` array and entry name filters.
73
-
74
- ### Step 3: Build canonical IDs with dot notation from nested paths
75
-
76
- ```typescript
77
- it('should build dot-notation canonical IDs from nested directories', () => {
78
- mkdirSync(join(tempDir, 'math'), { recursive: true });
79
- writeFileSync(join(tempDir, 'math', 'add.ts'), 'export default {}');
80
- const results = scanExtensions(tempDir);
81
- expect(results[0].canonicalId).toBe('math.add');
82
- });
83
- ```
84
-
85
- Use `relative()` and replace path separators with dots, strip extensions.
86
-
87
- ### Step 4: Detect companion _meta.yaml files
88
-
89
- ```typescript
90
- it('should detect companion _meta.yaml metadata files', () => {
91
- writeFileSync(join(tempDir, 'greet.ts'), 'export default {}');
92
- writeFileSync(join(tempDir, 'greet_meta.yaml'), 'description: Hello');
93
- const results = scanExtensions(tempDir);
94
- expect(results[0].metaPath).toContain('greet_meta.yaml');
95
- });
96
- ```
97
-
98
- ### Step 5: Respect maxDepth configuration
99
-
100
- ```typescript
101
- it('should respect maxDepth limit', () => {
102
- const deep = join(tempDir, 'a', 'b', 'c');
103
- mkdirSync(deep, { recursive: true });
104
- writeFileSync(join(deep, 'mod.ts'), 'export default {}');
105
- const shallow = scanExtensions(tempDir, 2);
106
- expect(shallow).toHaveLength(0);
107
- const deeper = scanExtensions(tempDir, 4);
108
- expect(deeper).toHaveLength(1);
109
- });
110
- ```
111
-
112
- ### Step 6: Throw ConfigNotFoundError for missing root directory
113
-
114
- ```typescript
115
- it('should throw ConfigNotFoundError for non-existent directory', () => {
116
- expect(() => scanExtensions('/nonexistent/path')).toThrow();
117
- });
118
- ```
119
-
120
- ### Step 7: Implement scanMultiRoot() with namespace prefixing
121
-
122
- ```typescript
123
- import { scanMultiRoot } from '../../src/registry/scanner.js';
124
-
125
- describe('scanMultiRoot', () => {
126
- it('should prepend namespace to canonical IDs', () => {
127
- const root1 = mkdtempSync(join(tmpdir(), 'root1-'));
128
- writeFileSync(join(root1, 'add.ts'), 'export default {}');
129
- const roots = [{ root: root1, namespace: 'math' }];
130
- const results = scanMultiRoot(roots);
131
- expect(results[0].canonicalId).toBe('math.add');
132
- expect(results[0].namespace).toBe('math');
133
- rmSync(root1, { recursive: true, force: true });
134
- });
135
-
136
- it('should throw on duplicate namespaces', () => {
137
- const root1 = mkdtempSync(join(tmpdir(), 'dup1-'));
138
- const root2 = mkdtempSync(join(tmpdir(), 'dup2-'));
139
- const roots = [
140
- { root: root1, namespace: 'ns' },
141
- { root: root2, namespace: 'ns' },
142
- ];
143
- expect(() => scanMultiRoot(roots)).toThrow(/Duplicate namespace/);
144
- rmSync(root1, { recursive: true, force: true });
145
- rmSync(root2, { recursive: true, force: true });
146
- });
147
- });
148
- ```
149
-
150
- ## Acceptance Criteria
151
-
152
- - [x] `scanExtensions()` discovers `.ts` and `.js` files recursively
153
- - [x] `.d.ts`, `.test.ts`, `.test.js`, `.spec.ts`, `.spec.js` files are skipped
154
- - [x] Dot-prefixed and underscore-prefixed entries are skipped
155
- - [x] `node_modules` and `__pycache__` directories are skipped
156
- - [x] Canonical IDs use dot notation derived from relative paths
157
- - [x] Case collisions are detected (warning-level, not blocking)
158
- - [x] Companion `_meta.yaml` files are detected and recorded in `metaPath`
159
- - [x] `maxDepth` parameter limits recursion depth
160
- - [x] `followSymlinks` parameter controls symlink traversal (note: `statSync` bug)
161
- - [x] `ConfigNotFoundError` thrown for non-existent root directories
162
- - [x] `scanMultiRoot()` prepends namespace to canonical IDs
163
- - [x] `scanMultiRoot()` throws `ConfigError` on duplicate namespaces
164
- - [x] All tests pass with `vitest`
165
-
166
- ## Dependencies
167
-
168
- - `types` task (DiscoveredModule interface)
169
-
170
- ## Estimated Time
171
-
172
- 3 hours