@moreih29/nexus-core 0.12.0 → 0.14.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 (245) hide show
  1. package/README.md +76 -57
  2. package/assets/agents/architect/body.ko.md +177 -0
  3. package/{agents → assets/agents}/architect/body.md +16 -0
  4. package/assets/agents/designer/body.ko.md +125 -0
  5. package/{agents → assets/agents}/designer/body.md +16 -0
  6. package/assets/agents/engineer/body.ko.md +106 -0
  7. package/{agents → assets/agents}/engineer/body.md +14 -0
  8. package/assets/agents/lead/body.ko.md +70 -0
  9. package/assets/agents/lead/body.md +70 -0
  10. package/assets/agents/postdoc/body.ko.md +122 -0
  11. package/{agents → assets/agents}/postdoc/body.md +16 -0
  12. package/assets/agents/researcher/body.ko.md +137 -0
  13. package/{agents → assets/agents}/researcher/body.md +15 -0
  14. package/assets/agents/reviewer/body.ko.md +138 -0
  15. package/{agents → assets/agents}/reviewer/body.md +15 -0
  16. package/assets/agents/strategist/body.ko.md +116 -0
  17. package/{agents → assets/agents}/strategist/body.md +16 -0
  18. package/assets/agents/tester/body.ko.md +195 -0
  19. package/{agents → assets/agents}/tester/body.md +15 -0
  20. package/assets/agents/writer/body.ko.md +122 -0
  21. package/{agents → assets/agents}/writer/body.md +14 -0
  22. package/assets/capability-matrix.yml +198 -0
  23. package/assets/hooks/agent-bootstrap/handler.test.ts +368 -0
  24. package/assets/hooks/agent-bootstrap/handler.ts +119 -0
  25. package/assets/hooks/agent-bootstrap/meta.yml +10 -0
  26. package/assets/hooks/agent-finalize/handler.test.ts +368 -0
  27. package/assets/hooks/agent-finalize/handler.ts +76 -0
  28. package/assets/hooks/agent-finalize/meta.yml +10 -0
  29. package/assets/hooks/capability-matrix.yml +313 -0
  30. package/assets/hooks/post-tool-telemetry/handler.test.ts +302 -0
  31. package/assets/hooks/post-tool-telemetry/handler.ts +49 -0
  32. package/assets/hooks/post-tool-telemetry/meta.yml +11 -0
  33. package/assets/hooks/prompt-router/handler.test.ts +801 -0
  34. package/assets/hooks/prompt-router/handler.ts +261 -0
  35. package/assets/hooks/prompt-router/meta.yml +11 -0
  36. package/assets/hooks/session-init/handler.test.ts +274 -0
  37. package/assets/hooks/session-init/handler.ts +30 -0
  38. package/assets/hooks/session-init/meta.yml +9 -0
  39. package/assets/lsp-servers.json +55 -0
  40. package/assets/schema/lsp-servers.schema.json +67 -0
  41. package/assets/skills/nx-init/body.ko.md +197 -0
  42. package/{skills → assets/skills}/nx-init/body.md +11 -0
  43. package/assets/skills/nx-plan/body.ko.md +361 -0
  44. package/{skills → assets/skills}/nx-plan/body.md +13 -0
  45. package/assets/skills/nx-run/body.ko.md +161 -0
  46. package/{skills → assets/skills}/nx-run/body.md +11 -0
  47. package/assets/skills/nx-sync/body.ko.md +92 -0
  48. package/{skills → assets/skills}/nx-sync/body.md +10 -0
  49. package/assets/tools/tool-name-map.yml +353 -0
  50. package/dist/assets/hooks/agent-bootstrap/handler.d.ts +4 -0
  51. package/dist/assets/hooks/agent-bootstrap/handler.d.ts.map +1 -0
  52. package/dist/assets/hooks/agent-bootstrap/handler.js +100 -0
  53. package/dist/assets/hooks/agent-bootstrap/handler.js.map +1 -0
  54. package/dist/assets/hooks/agent-finalize/handler.d.ts +4 -0
  55. package/dist/assets/hooks/agent-finalize/handler.d.ts.map +1 -0
  56. package/dist/assets/hooks/agent-finalize/handler.js +63 -0
  57. package/dist/assets/hooks/agent-finalize/handler.js.map +1 -0
  58. package/dist/assets/hooks/post-tool-telemetry/handler.d.ts +4 -0
  59. package/dist/assets/hooks/post-tool-telemetry/handler.d.ts.map +1 -0
  60. package/dist/assets/hooks/post-tool-telemetry/handler.js +40 -0
  61. package/dist/assets/hooks/post-tool-telemetry/handler.js.map +1 -0
  62. package/dist/assets/hooks/prompt-router/handler.d.ts +4 -0
  63. package/dist/assets/hooks/prompt-router/handler.d.ts.map +1 -0
  64. package/dist/assets/hooks/prompt-router/handler.js +204 -0
  65. package/dist/assets/hooks/prompt-router/handler.js.map +1 -0
  66. package/dist/assets/hooks/session-init/handler.d.ts +4 -0
  67. package/dist/assets/hooks/session-init/handler.d.ts.map +1 -0
  68. package/dist/assets/hooks/session-init/handler.js +23 -0
  69. package/dist/assets/hooks/session-init/handler.js.map +1 -0
  70. package/dist/hooks/agent-bootstrap.js +105 -0
  71. package/dist/hooks/agent-finalize.js +164 -0
  72. package/dist/hooks/post-tool-telemetry.js +55 -0
  73. package/dist/hooks/prompt-router.js +7300 -0
  74. package/dist/hooks/session-init.js +21 -0
  75. package/dist/manifests/claude-hooks.json +52 -0
  76. package/dist/manifests/codex-hooks.json +28 -0
  77. package/dist/manifests/opencode-manifest.json +44 -0
  78. package/dist/manifests/portability-report.json +87 -0
  79. package/dist/scripts/build-agents.d.ts +157 -0
  80. package/dist/scripts/build-agents.d.ts.map +1 -0
  81. package/dist/scripts/build-agents.js +737 -0
  82. package/dist/scripts/build-agents.js.map +1 -0
  83. package/dist/scripts/build-hooks.d.ts +16 -0
  84. package/dist/scripts/build-hooks.d.ts.map +1 -0
  85. package/dist/scripts/build-hooks.js +388 -0
  86. package/dist/scripts/build-hooks.js.map +1 -0
  87. package/dist/scripts/cli.d.ts +54 -0
  88. package/dist/scripts/cli.d.ts.map +1 -0
  89. package/dist/scripts/cli.js +467 -0
  90. package/dist/scripts/cli.js.map +1 -0
  91. package/dist/src/hooks/opencode-mount.d.ts +35 -0
  92. package/dist/src/hooks/opencode-mount.d.ts.map +1 -0
  93. package/dist/src/hooks/opencode-mount.js +352 -0
  94. package/dist/src/hooks/opencode-mount.js.map +1 -0
  95. package/dist/src/hooks/runtime.d.ts +37 -0
  96. package/dist/src/hooks/runtime.d.ts.map +1 -0
  97. package/dist/src/hooks/runtime.js +274 -0
  98. package/dist/src/hooks/runtime.js.map +1 -0
  99. package/dist/src/hooks/types.d.ts +196 -0
  100. package/dist/src/hooks/types.d.ts.map +1 -0
  101. package/dist/src/hooks/types.js +85 -0
  102. package/dist/src/hooks/types.js.map +1 -0
  103. package/dist/src/lsp/cache.d.ts +9 -0
  104. package/dist/src/lsp/cache.d.ts.map +1 -0
  105. package/dist/src/lsp/cache.js +216 -0
  106. package/dist/src/lsp/cache.js.map +1 -0
  107. package/dist/src/lsp/client.d.ts +24 -0
  108. package/dist/src/lsp/client.d.ts.map +1 -0
  109. package/dist/src/lsp/client.js +166 -0
  110. package/dist/src/lsp/client.js.map +1 -0
  111. package/dist/src/lsp/detect.d.ts +77 -0
  112. package/dist/src/lsp/detect.d.ts.map +1 -0
  113. package/dist/src/lsp/detect.js +116 -0
  114. package/dist/src/lsp/detect.js.map +1 -0
  115. package/dist/src/mcp/server.d.ts +5 -0
  116. package/dist/src/mcp/server.d.ts.map +1 -0
  117. package/dist/src/mcp/server.js +34 -0
  118. package/dist/src/mcp/server.js.map +1 -0
  119. package/dist/src/mcp/tools/artifact.d.ts +4 -0
  120. package/dist/src/mcp/tools/artifact.d.ts.map +1 -0
  121. package/dist/src/mcp/tools/artifact.js +36 -0
  122. package/dist/src/mcp/tools/artifact.js.map +1 -0
  123. package/dist/src/mcp/tools/history.d.ts +3 -0
  124. package/dist/src/mcp/tools/history.d.ts.map +1 -0
  125. package/dist/src/mcp/tools/history.js +29 -0
  126. package/dist/src/mcp/tools/history.js.map +1 -0
  127. package/dist/src/mcp/tools/lsp.d.ts +13 -0
  128. package/dist/src/mcp/tools/lsp.d.ts.map +1 -0
  129. package/dist/src/mcp/tools/lsp.js +225 -0
  130. package/dist/src/mcp/tools/lsp.js.map +1 -0
  131. package/dist/src/mcp/tools/plan.d.ts +3 -0
  132. package/dist/src/mcp/tools/plan.d.ts.map +1 -0
  133. package/dist/src/mcp/tools/plan.js +317 -0
  134. package/dist/src/mcp/tools/plan.js.map +1 -0
  135. package/dist/src/mcp/tools/task.d.ts +3 -0
  136. package/dist/src/mcp/tools/task.d.ts.map +1 -0
  137. package/dist/src/mcp/tools/task.js +252 -0
  138. package/dist/src/mcp/tools/task.js.map +1 -0
  139. package/dist/src/shared/invocations.d.ts +74 -0
  140. package/dist/src/shared/invocations.d.ts.map +1 -0
  141. package/dist/src/shared/invocations.js +247 -0
  142. package/dist/src/shared/invocations.js.map +1 -0
  143. package/dist/src/shared/json-store.d.ts +37 -0
  144. package/dist/src/shared/json-store.d.ts.map +1 -0
  145. package/dist/src/shared/json-store.js +163 -0
  146. package/dist/src/shared/json-store.js.map +1 -0
  147. package/dist/src/shared/mcp-utils.d.ts +3 -0
  148. package/dist/src/shared/mcp-utils.d.ts.map +1 -0
  149. package/dist/src/shared/mcp-utils.js +6 -0
  150. package/dist/src/shared/mcp-utils.js.map +1 -0
  151. package/dist/src/shared/paths.d.ts +21 -0
  152. package/dist/src/shared/paths.d.ts.map +1 -0
  153. package/dist/src/shared/paths.js +81 -0
  154. package/dist/src/shared/paths.js.map +1 -0
  155. package/dist/src/shared/tool-log.d.ts +8 -0
  156. package/dist/src/shared/tool-log.d.ts.map +1 -0
  157. package/dist/src/shared/tool-log.js +22 -0
  158. package/dist/src/shared/tool-log.js.map +1 -0
  159. package/dist/src/types/state.d.ts +862 -0
  160. package/dist/src/types/state.d.ts.map +1 -0
  161. package/dist/src/types/state.js +66 -0
  162. package/dist/src/types/state.js.map +1 -0
  163. package/docs/consuming/codex-lead-merge.md +106 -0
  164. package/docs/plugin-guide.md +396 -0
  165. package/docs/plugin-template/claude/.github/workflows/build.yml +60 -0
  166. package/docs/plugin-template/claude/README.md +110 -0
  167. package/docs/plugin-template/claude/package.json +16 -0
  168. package/docs/plugin-template/codex/.github/workflows/build.yml +51 -0
  169. package/docs/plugin-template/codex/README.md +147 -0
  170. package/docs/plugin-template/codex/package.json +17 -0
  171. package/docs/plugin-template/opencode/.github/workflows/build.yml +61 -0
  172. package/docs/plugin-template/opencode/README.md +121 -0
  173. package/docs/plugin-template/opencode/package.json +25 -0
  174. package/package.json +36 -28
  175. package/agents/architect/meta.yml +0 -13
  176. package/agents/designer/meta.yml +0 -13
  177. package/agents/engineer/meta.yml +0 -11
  178. package/agents/postdoc/meta.yml +0 -13
  179. package/agents/researcher/meta.yml +0 -12
  180. package/agents/reviewer/meta.yml +0 -12
  181. package/agents/strategist/meta.yml +0 -13
  182. package/agents/tester/meta.yml +0 -12
  183. package/agents/writer/meta.yml +0 -11
  184. package/conformance/README.md +0 -311
  185. package/conformance/examples/plan.extension.schema.example.json +0 -25
  186. package/conformance/lifecycle/README.md +0 -48
  187. package/conformance/lifecycle/agent-complete.json +0 -44
  188. package/conformance/lifecycle/agent-resume.json +0 -43
  189. package/conformance/lifecycle/agent-spawn.json +0 -36
  190. package/conformance/lifecycle/memory-access-record.json +0 -27
  191. package/conformance/lifecycle/session-end.json +0 -48
  192. package/conformance/scenarios/full-plan-cycle.json +0 -147
  193. package/conformance/scenarios/task-deps-ordering.json +0 -95
  194. package/conformance/schema/fixture.schema.json +0 -354
  195. package/conformance/state-schemas/agent-tracker.schema.json +0 -63
  196. package/conformance/state-schemas/history.schema.json +0 -134
  197. package/conformance/state-schemas/memory-access.schema.json +0 -36
  198. package/conformance/state-schemas/plan.schema.json +0 -77
  199. package/conformance/state-schemas/tasks.schema.json +0 -98
  200. package/conformance/tools/artifact-write.json +0 -97
  201. package/conformance/tools/context.json +0 -172
  202. package/conformance/tools/history-search.json +0 -219
  203. package/conformance/tools/plan-decide.json +0 -139
  204. package/conformance/tools/plan-start.json +0 -81
  205. package/conformance/tools/plan-status.json +0 -127
  206. package/conformance/tools/plan-update.json +0 -341
  207. package/conformance/tools/task-add.json +0 -156
  208. package/conformance/tools/task-close.json +0 -161
  209. package/conformance/tools/task-list.json +0 -177
  210. package/conformance/tools/task-update.json +0 -167
  211. package/docs/behavioral-contracts.md +0 -145
  212. package/docs/consumer-implementation-guide.md +0 -840
  213. package/docs/memory-lifecycle-contract.md +0 -119
  214. package/docs/nexus-layout.md +0 -224
  215. package/docs/nexus-outputs-contract.md +0 -344
  216. package/docs/nexus-state-overview.md +0 -170
  217. package/docs/nexus-tools-contract.md +0 -438
  218. package/manifest.json +0 -448
  219. package/schema/README.md +0 -69
  220. package/schema/agent.schema.json +0 -23
  221. package/schema/common.schema.json +0 -17
  222. package/schema/manifest.schema.json +0 -78
  223. package/schema/memory-policy.schema.json +0 -98
  224. package/schema/skill.schema.json +0 -54
  225. package/schema/task-exceptions.schema.json +0 -40
  226. package/schema/vocabulary.schema.json +0 -167
  227. package/scripts/.gitkeep +0 -0
  228. package/scripts/conformance-coverage.ts +0 -466
  229. package/scripts/import-from-claude-nexus.ts +0 -403
  230. package/scripts/lib/frontmatter.ts +0 -71
  231. package/scripts/lib/lint.ts +0 -348
  232. package/scripts/lib/structure.ts +0 -159
  233. package/scripts/lib/validate.ts +0 -796
  234. package/scripts/validate.ts +0 -90
  235. package/skills/nx-init/meta.yml +0 -8
  236. package/skills/nx-plan/meta.yml +0 -10
  237. package/skills/nx-run/meta.yml +0 -8
  238. package/skills/nx-sync/meta.yml +0 -7
  239. package/vocabulary/capabilities.yml +0 -65
  240. package/vocabulary/categories.yml +0 -11
  241. package/vocabulary/invocations.yml +0 -147
  242. package/vocabulary/memory_policy.yml +0 -88
  243. package/vocabulary/resume-tiers.yml +0 -11
  244. package/vocabulary/tags.yml +0 -60
  245. package/vocabulary/task-exceptions.yml +0 -29
@@ -1,796 +0,0 @@
1
- import Ajv2020 from 'ajv/dist/2020';
2
- import type { ValidateFunction } from 'ajv';
3
- import addFormats from 'ajv-formats';
4
- import ajvErrors from 'ajv-errors';
5
- import { glob } from 'tinyglobby';
6
- import { readFile, writeFile } from 'node:fs/promises';
7
- import { createHash } from 'node:crypto';
8
- import { parse as parseYaml } from 'yaml';
9
- import path from 'node:path';
10
- import { parseFrontmatter, frontmatterLineToSourceLine } from './frontmatter.ts';
11
-
12
- export interface ValidationResult {
13
- file: string;
14
- gate: string;
15
- severity: 'error' | 'warning';
16
- line?: number;
17
- message: string;
18
- }
19
-
20
- // ─── Manifest types ──────────────────────────────────────────────────────────
21
-
22
- interface AgentMeta {
23
- id: string;
24
- name: string;
25
- alias_ko?: string;
26
- description: string;
27
- task?: string;
28
- category: string;
29
- capabilities: string[];
30
- resume_tier: string;
31
- model_tier: string;
32
- }
33
-
34
- interface SkillMeta {
35
- id: string;
36
- name: string;
37
- alias_ko?: string;
38
- description: string;
39
- summary?: string;
40
- harness_docs_refs?: string[];
41
- triggers: string[];
42
- manual_only?: boolean;
43
- }
44
-
45
- interface CapabilityEntry {
46
- id: string;
47
- description: string;
48
- intent: string;
49
- blocks_semantic_classes: string[];
50
- prose_guidance: string;
51
- }
52
-
53
- interface SimpleEntry {
54
- id: string;
55
- description: string;
56
- }
57
-
58
- interface TagEntry {
59
- id: string;
60
- trigger: string;
61
- type: 'skill' | 'inline_action';
62
- description: string;
63
- skill?: string;
64
- handler?: string;
65
- variants?: string[];
66
- }
67
-
68
- interface InvocationParam {
69
- name: string;
70
- description: string;
71
- required: boolean;
72
- value_type?: string;
73
- }
74
-
75
- interface InvocationEntry {
76
- id: string;
77
- description: string;
78
- intent: string;
79
- semantic_params: InvocationParam[];
80
- prose_guidance: string;
81
- fallback_behavior: string;
82
- }
83
-
84
- interface TaskExceptionEntry {
85
- id: string;
86
- description: string;
87
- applies_when: string;
88
- treatment: string;
89
- rationale: string;
90
- }
91
-
92
- interface MemoryPolicy {
93
- categories: unknown[];
94
- naming: unknown;
95
- access_tracking: unknown;
96
- forgetting: unknown;
97
- merge: unknown;
98
- }
99
-
100
- interface Vocab {
101
- capabilities: CapabilityEntry[];
102
- categories: SimpleEntry[];
103
- resume_tiers: SimpleEntry[];
104
- tags: TagEntry[];
105
- invocations: InvocationEntry[];
106
- task_exceptions: TaskExceptionEntry[];
107
- memory_policy: MemoryPolicy;
108
- }
109
-
110
- interface ManifestAgent extends AgentMeta {
111
- body_hash: string;
112
- }
113
-
114
- interface ManifestSkill extends SkillMeta {
115
- body_hash: string;
116
- }
117
-
118
- interface ManifestInvocationEntry {
119
- id: string;
120
- description: string;
121
- intent: string;
122
- fallback_behavior: string;
123
- }
124
-
125
- interface Manifest {
126
- nexus_core_version: string;
127
- nexus_core_commit: string;
128
- schema_contract_version: string;
129
- agents: ManifestAgent[];
130
- skills: ManifestSkill[];
131
- vocabulary: {
132
- capabilities: CapabilityEntry[];
133
- categories: SimpleEntry[];
134
- resume_tiers: SimpleEntry[];
135
- tags: TagEntry[];
136
- invocations: ManifestInvocationEntry[];
137
- task_exceptions: TaskExceptionEntry[];
138
- memory_policy: MemoryPolicy;
139
- };
140
- }
141
-
142
- // ─── AJV setup ───────────────────────────────────────────────────────────────
143
-
144
- let ajvInstance: Ajv2020 | null = null;
145
- let schemaDir = '';
146
-
147
- interface LoadedSchemas {
148
- agentValidator: ValidateFunction;
149
- skillValidator: ValidateFunction;
150
- vocabValidator: ValidateFunction;
151
- manifestValidator: ValidateFunction;
152
- taskExceptionValidator: ValidateFunction;
153
- memoryPolicyValidator: ValidateFunction;
154
- }
155
-
156
- let cachedSchemas: LoadedSchemas | null = null;
157
-
158
- /** G1: Load and compile JSON schemas. Must be called before runAll. */
159
- export async function loadSchemas(root: string): Promise<void> {
160
- schemaDir = path.join(root, 'schema');
161
-
162
- const ajv = new Ajv2020({
163
- strict: true,
164
- allErrors: true,
165
- verbose: true,
166
- loadSchema: async (uri: string) => {
167
- // Resolve relative $ref URIs within schema directory
168
- const basename = path.basename(uri);
169
- const schemaPath = path.join(schemaDir, basename);
170
- const content = await readFile(schemaPath, 'utf8');
171
- return JSON.parse(content) as Record<string, unknown>;
172
- },
173
- });
174
- addFormats(ajv);
175
- ajvErrors(ajv);
176
-
177
- const [commonRaw, agentRaw, skillRaw, vocabRaw, manifestRaw, taskExceptionSchemaRaw, memoryPolicySchemaRaw] = await Promise.all([
178
- readFile(path.join(schemaDir, 'common.schema.json'), 'utf8'),
179
- readFile(path.join(schemaDir, 'agent.schema.json'), 'utf8'),
180
- readFile(path.join(schemaDir, 'skill.schema.json'), 'utf8'),
181
- readFile(path.join(schemaDir, 'vocabulary.schema.json'), 'utf8'),
182
- readFile(path.join(schemaDir, 'manifest.schema.json'), 'utf8'),
183
- readFile(path.join(schemaDir, 'task-exceptions.schema.json'), 'utf8'),
184
- readFile(path.join(schemaDir, 'memory-policy.schema.json'), 'utf8'),
185
- ]);
186
-
187
- const commonSchema = JSON.parse(commonRaw) as Record<string, unknown>;
188
- const agentSchema = JSON.parse(agentRaw) as Record<string, unknown>;
189
- const skillSchema = JSON.parse(skillRaw) as Record<string, unknown>;
190
- const vocabSchema = JSON.parse(vocabRaw) as Record<string, unknown>;
191
- const manifestSchema = JSON.parse(manifestRaw) as Record<string, unknown>;
192
- const taskExceptionSchema = JSON.parse(taskExceptionSchemaRaw) as Record<string, unknown>;
193
- const memoryPolicySchema = JSON.parse(memoryPolicySchemaRaw) as Record<string, unknown>;
194
-
195
- ajv.addSchema(commonSchema);
196
- ajv.addSchema(vocabSchema);
197
-
198
- const agentValidator = await ajv.compileAsync(agentSchema);
199
- const skillValidator = await ajv.compileAsync(skillSchema);
200
-
201
- // Vocabulary files use named $defs — compile per sub-schema
202
- const vocabDefs = (vocabSchema['$defs'] ?? {}) as Record<string, Record<string, unknown>>;
203
- const capabilityFileSchema = {
204
- ...vocabDefs['capabilityFile'],
205
- $schema: 'https://json-schema.org/draft/2020-12/schema',
206
- $id: 'vocabulary-capability-file',
207
- $defs: vocabDefs,
208
- };
209
- const categoryFileSchema = {
210
- ...vocabDefs['categoryFile'],
211
- $schema: 'https://json-schema.org/draft/2020-12/schema',
212
- $id: 'vocabulary-category-file',
213
- $defs: vocabDefs,
214
- };
215
- const resumeTierFileSchema = {
216
- ...vocabDefs['resumeTierFile'],
217
- $schema: 'https://json-schema.org/draft/2020-12/schema',
218
- $id: 'vocabulary-resume-tier-file',
219
- $defs: vocabDefs,
220
- };
221
- const tagFileSchema = {
222
- ...vocabDefs['tagFile'],
223
- $schema: 'https://json-schema.org/draft/2020-12/schema',
224
- $id: 'vocabulary-tag-file',
225
- $defs: vocabDefs,
226
- };
227
- const invocationFileSchema = {
228
- ...vocabDefs['invocationFile'],
229
- $schema: 'https://json-schema.org/draft/2020-12/schema',
230
- $id: 'vocabulary-invocation-file',
231
- $defs: vocabDefs,
232
- };
233
-
234
- const capabilityValidator = await ajv.compileAsync(capabilityFileSchema);
235
- const categoryValidator = await ajv.compileAsync(categoryFileSchema);
236
- const resumeTierValidator = await ajv.compileAsync(resumeTierFileSchema);
237
- const tagValidator = await ajv.compileAsync(tagFileSchema);
238
- const invocationValidator = await ajv.compileAsync(invocationFileSchema);
239
- const taskExceptionValidator = await ajv.compileAsync(taskExceptionSchema);
240
- const memoryPolicyValidator = await ajv.compileAsync(memoryPolicySchema);
241
-
242
- const manifestAjv = new Ajv2020({
243
- strict: false,
244
- allErrors: true,
245
- loadSchema: async (uri: string) => {
246
- const basename = path.basename(uri);
247
- const schemaPath = path.join(schemaDir, basename);
248
- const content = await readFile(schemaPath, 'utf8');
249
- return JSON.parse(content) as Record<string, unknown>;
250
- },
251
- });
252
- addFormats(manifestAjv);
253
- ajvErrors(manifestAjv);
254
- manifestAjv.addSchema(commonSchema);
255
- const manifestValidator = await manifestAjv.compileAsync(manifestSchema);
256
-
257
- ajvInstance = ajv;
258
- cachedSchemas = {
259
- agentValidator,
260
- skillValidator,
261
- // Store vocabulary validators and manifest as composite
262
- vocabValidator: capabilityValidator, // placeholder — we handle vocab separately below
263
- manifestValidator,
264
- taskExceptionValidator,
265
- memoryPolicyValidator,
266
- };
267
-
268
- // Store all vocab validators for internal use
269
- _vocabValidators = { capabilityValidator, categoryValidator, resumeTierValidator, tagValidator, invocationValidator, taskExceptionValidator, memoryPolicyValidator };
270
- }
271
-
272
- interface VocabValidators {
273
- capabilityValidator: ValidateFunction;
274
- categoryValidator: ValidateFunction;
275
- resumeTierValidator: ValidateFunction;
276
- tagValidator: ValidateFunction;
277
- invocationValidator: ValidateFunction;
278
- taskExceptionValidator: ValidateFunction;
279
- memoryPolicyValidator: ValidateFunction;
280
- }
281
-
282
- let _vocabValidators: VocabValidators | null = null;
283
-
284
- // ─── offsetToLine helper ──────────────────────────────────────────────────────
285
-
286
- /**
287
- * Converts a character offset in source text to a 1-based line number.
288
- * Works for both frontmatter and body regions.
289
- */
290
- export function offsetToLine(source: string, offset: number): number {
291
- return source.slice(0, offset).split('\n').length;
292
- }
293
-
294
- // ─── G1: Schema validation ────────────────────────────────────────────────────
295
-
296
- async function validateAgentMeta(
297
- filePath: string,
298
- rel: string
299
- ): Promise<{ result: ValidationResult[]; data: AgentMeta | null }> {
300
- if (!cachedSchemas) return { result: [], data: null };
301
- const results: ValidationResult[] = [];
302
-
303
- let source: string;
304
- try {
305
- source = await readFile(filePath, 'utf8');
306
- } catch (err) {
307
- return {
308
- result: [{ file: rel, gate: 'G1-schema', severity: 'error', message: `Cannot read file: ${(err as Error).message}` }],
309
- data: null,
310
- };
311
- }
312
-
313
- let data: unknown;
314
- try {
315
- data = parseYaml(source);
316
- } catch (err) {
317
- return {
318
- result: [{ file: rel, gate: 'G1-schema', severity: 'error', message: `YAML parse error: ${(err as Error).message}` }],
319
- data: null,
320
- };
321
- }
322
-
323
- const valid = cachedSchemas.agentValidator(data);
324
- if (!valid) {
325
- for (const e of cachedSchemas.agentValidator.errors ?? []) {
326
- results.push({
327
- file: rel,
328
- gate: 'G1-schema',
329
- severity: 'error',
330
- message: `${e.instancePath || '(root)'}: ${e.message ?? 'validation failed'}`,
331
- });
332
- }
333
- return { result: results, data: null };
334
- }
335
-
336
- return { result: [], data: data as AgentMeta };
337
- }
338
-
339
- async function validateSkillMeta(
340
- filePath: string,
341
- rel: string
342
- ): Promise<{ result: ValidationResult[]; data: SkillMeta | null }> {
343
- if (!cachedSchemas) return { result: [], data: null };
344
-
345
- let source: string;
346
- try {
347
- source = await readFile(filePath, 'utf8');
348
- } catch (err) {
349
- return {
350
- result: [{ file: rel, gate: 'G1-schema', severity: 'error', message: `Cannot read file: ${(err as Error).message}` }],
351
- data: null,
352
- };
353
- }
354
-
355
- let data: unknown;
356
- try {
357
- data = parseYaml(source);
358
- } catch (err) {
359
- return {
360
- result: [{ file: rel, gate: 'G1-schema', severity: 'error', message: `YAML parse error: ${(err as Error).message}` }],
361
- data: null,
362
- };
363
- }
364
-
365
- const valid = cachedSchemas.skillValidator(data);
366
- if (!valid) {
367
- const results: ValidationResult[] = [];
368
- for (const e of cachedSchemas.skillValidator.errors ?? []) {
369
- results.push({
370
- file: rel,
371
- gate: 'G1-schema',
372
- severity: 'error',
373
- message: `${e.instancePath || '(root)'}: ${e.message ?? 'validation failed'}`,
374
- });
375
- }
376
- return { result: results, data: null };
377
- }
378
-
379
- return { result: [], data: data as SkillMeta };
380
- }
381
-
382
- // ─── G2-G5: Referential integrity ────────────────────────────────────────────
383
-
384
- function checkCapabilityIntegrity(
385
- agents: Array<{ meta: AgentMeta; rel: string }>,
386
- capabilityIds: Set<string>
387
- ): ValidationResult[] {
388
- const results: ValidationResult[] = [];
389
- for (const { meta, rel } of agents) {
390
- for (const cap of meta.capabilities) {
391
- if (!capabilityIds.has(cap)) {
392
- results.push({
393
- file: rel,
394
- gate: 'G2-capability-integrity',
395
- severity: 'error',
396
- message: `Unknown capability '${cap}' — not defined in vocabulary/capabilities.yml`,
397
- });
398
- }
399
- }
400
- }
401
- return results;
402
- }
403
-
404
- function checkCategoryIntegrity(
405
- agents: Array<{ meta: AgentMeta; rel: string }>,
406
- categoryIds: Set<string>
407
- ): ValidationResult[] {
408
- const results: ValidationResult[] = [];
409
- for (const { meta, rel } of agents) {
410
- if (!categoryIds.has(meta.category)) {
411
- results.push({
412
- file: rel,
413
- gate: 'G3-category-integrity',
414
- severity: 'error',
415
- message: `Unknown category '${meta.category}' — not defined in vocabulary/categories.yml`,
416
- });
417
- }
418
- }
419
- return results;
420
- }
421
-
422
- function checkResumeTierIntegrity(
423
- agents: Array<{ meta: AgentMeta; rel: string }>,
424
- resumeTierIds: Set<string>
425
- ): ValidationResult[] {
426
- const results: ValidationResult[] = [];
427
- for (const { meta, rel } of agents) {
428
- if (!resumeTierIds.has(meta.resume_tier)) {
429
- results.push({
430
- file: rel,
431
- gate: 'G4-resume-tier-integrity',
432
- severity: 'error',
433
- message: `Unknown resume_tier '${meta.resume_tier}' — not defined in vocabulary/resume-tiers.yml`,
434
- });
435
- }
436
- }
437
- return results;
438
- }
439
-
440
- function checkTagIntegrity(
441
- skills: Array<{ meta: SkillMeta; rel: string }>,
442
- tags: TagEntry[]
443
- ): ValidationResult[] {
444
- // G5: skill.triggers references must be tag ids of type=skill
445
- const skillTagIds = new Set(tags.filter((t) => t.type === 'skill').map((t) => t.id));
446
- const results: ValidationResult[] = [];
447
- for (const { meta, rel } of skills) {
448
- for (const trigger of meta.triggers ?? []) {
449
- if (!skillTagIds.has(trigger)) {
450
- results.push({
451
- file: rel,
452
- gate: 'G5-tag-integrity',
453
- severity: 'error',
454
- message: `Trigger '${trigger}' is not a known skill-type tag id in vocabulary/tags.yml`,
455
- });
456
- }
457
- }
458
- }
459
- return results;
460
- }
461
-
462
- // ─── G5': Capability entry integrity ─────────────────────────────────────────
463
-
464
- const SNAKE_CASE_RE = /^[a-z][a-z0-9_]*$/;
465
-
466
- export function checkCapabilityEntryIntegrity(capabilities: CapabilityEntry[]): ValidationResult[] {
467
- const results: ValidationResult[] = [];
468
- for (const cap of capabilities) {
469
- if (!SNAKE_CASE_RE.test(cap.intent)) {
470
- results.push({
471
- file: 'vocabulary/capabilities.yml',
472
- gate: 'G5-capability-integrity',
473
- severity: 'error',
474
- message: `Capability '${cap.id}': 'intent' must match snake_case /^[a-z][a-z0-9_]*$/, got '${cap.intent}'`,
475
- });
476
- }
477
- if (!cap.blocks_semantic_classes || cap.blocks_semantic_classes.length === 0) {
478
- results.push({
479
- file: 'vocabulary/capabilities.yml',
480
- gate: 'G5-capability-integrity',
481
- severity: 'error',
482
- message: `Capability '${cap.id}': 'blocks_semantic_classes' must have at least 1 entry`,
483
- });
484
- } else {
485
- for (const cls of cap.blocks_semantic_classes) {
486
- if (!SNAKE_CASE_RE.test(cls)) {
487
- results.push({
488
- file: 'vocabulary/capabilities.yml',
489
- gate: 'G5-capability-integrity',
490
- severity: 'error',
491
- message: `Capability '${cap.id}': class '${cls}' must match snake_case /^[a-z][a-z0-9_]*$/`,
492
- });
493
- }
494
- }
495
- }
496
- if (!cap.prose_guidance || cap.prose_guidance.trim().length < 40) {
497
- results.push({
498
- file: 'vocabulary/capabilities.yml',
499
- gate: 'G5-capability-integrity',
500
- severity: 'error',
501
- message: `Capability '${cap.id}': 'prose_guidance' must be at least 40 characters`,
502
- });
503
- }
504
- }
505
- return results;
506
- }
507
-
508
- // ─── G6': Invocation entry integrity ─────────────────────────────────────────
509
-
510
- export function checkInvocationEntryIntegrity(invocations: InvocationEntry[]): ValidationResult[] {
511
- const results: ValidationResult[] = [];
512
- for (const inv of invocations) {
513
- if (!SNAKE_CASE_RE.test(inv.intent)) {
514
- results.push({
515
- file: 'vocabulary/invocations.yml',
516
- gate: 'G6-invocation-integrity',
517
- severity: 'error',
518
- message: `Invocation '${inv.id}': 'intent' must match snake_case /^[a-z][a-z0-9_]*$/, got '${inv.intent}'`,
519
- });
520
- }
521
- if (!inv.prose_guidance || inv.prose_guidance.trim().length < 40) {
522
- results.push({
523
- file: 'vocabulary/invocations.yml',
524
- gate: 'G6-invocation-integrity',
525
- severity: 'error',
526
- message: `Invocation '${inv.id}': 'prose_guidance' must be at least 40 characters`,
527
- });
528
- }
529
- if (!inv.fallback_behavior || inv.fallback_behavior.trim().length < 20) {
530
- results.push({
531
- file: 'vocabulary/invocations.yml',
532
- gate: 'G6-invocation-integrity',
533
- severity: 'error',
534
- message: `Invocation '${inv.id}': 'fallback_behavior' must be at least 20 characters`,
535
- });
536
- }
537
- for (const param of inv.semantic_params ?? []) {
538
- if (!SNAKE_CASE_RE.test(param.name)) {
539
- results.push({
540
- file: 'vocabulary/invocations.yml',
541
- gate: 'G6-invocation-integrity',
542
- severity: 'error',
543
- message: `Invocation '${inv.id}': param name '${param.name}' must match snake_case /^[a-z][a-z0-9_]*$/`,
544
- });
545
- }
546
- }
547
- }
548
- return results;
549
- }
550
-
551
- // ─── Vocabulary loading ───────────────────────────────────────────────────────
552
-
553
- async function loadVocab(root: string): Promise<{ vocab: Vocab | null; results: ValidationResult[] }> {
554
- if (!_vocabValidators) return { vocab: null, results: [] };
555
- const results: ValidationResult[] = [];
556
-
557
- const vocabDir = path.join(root, 'vocabulary');
558
-
559
- async function loadYaml<T>(filename: string, validator: ValidateFunction): Promise<T | null> {
560
- const filePath = path.join(vocabDir, filename);
561
- const rel = path.join('vocabulary', filename);
562
- let source: string;
563
- try {
564
- source = await readFile(filePath, 'utf8');
565
- } catch {
566
- // Vocabulary file absent — skip silently (no agents/skills to validate)
567
- return null;
568
- }
569
-
570
- let data: unknown;
571
- try {
572
- data = parseYaml(source);
573
- } catch (err) {
574
- results.push({ file: rel, gate: 'G1-schema', severity: 'error', message: `YAML parse error: ${(err as Error).message}` });
575
- return null;
576
- }
577
-
578
- const valid = validator(data);
579
- if (!valid) {
580
- for (const e of validator.errors ?? []) {
581
- results.push({
582
- file: rel,
583
- gate: 'G1-schema',
584
- severity: 'error',
585
- message: `${e.instancePath || '(root)'}: ${e.message ?? 'validation failed'}`,
586
- });
587
- }
588
- return null;
589
- }
590
-
591
- return data as T;
592
- }
593
-
594
- const [capData, catData, resumeData, tagData, invocationData, taskExceptionData, memoryPolicyData] = await Promise.all([
595
- loadYaml<{ capabilities: CapabilityEntry[] }>('capabilities.yml', _vocabValidators.capabilityValidator),
596
- loadYaml<{ categories: SimpleEntry[] }>('categories.yml', _vocabValidators.categoryValidator),
597
- loadYaml<{ resume_tiers: SimpleEntry[] }>('resume-tiers.yml', _vocabValidators.resumeTierValidator),
598
- loadYaml<{ tags: TagEntry[] }>('tags.yml', _vocabValidators.tagValidator),
599
- loadYaml<{ invocations: InvocationEntry[] }>('invocations.yml', _vocabValidators.invocationValidator),
600
- loadYaml<{ task_exceptions: TaskExceptionEntry[] }>('task-exceptions.yml', _vocabValidators.taskExceptionValidator),
601
- loadYaml<MemoryPolicy>('memory_policy.yml', _vocabValidators.memoryPolicyValidator),
602
- ]);
603
-
604
- if (!capData || !catData || !resumeData || !tagData || !invocationData || !taskExceptionData || !memoryPolicyData) {
605
- return { vocab: null, results };
606
- }
607
-
608
- return {
609
- vocab: {
610
- capabilities: capData.capabilities,
611
- categories: catData.categories,
612
- resume_tiers: resumeData.resume_tiers,
613
- tags: tagData.tags,
614
- invocations: invocationData.invocations,
615
- task_exceptions: taskExceptionData.task_exceptions,
616
- memory_policy: memoryPolicyData,
617
- },
618
- results,
619
- };
620
- }
621
-
622
- // ─── body_hash ────────────────────────────────────────────────────────────────
623
-
624
- async function computeBodyHash(bodyPath: string): Promise<string> {
625
- const content = await readFile(bodyPath, 'utf8');
626
- // Normalize line endings before hashing
627
- const normalized = content.replace(/\r\n/g, '\n').trimEnd() + '\n';
628
- const hash = createHash('sha256').update(normalized, 'utf8').digest('hex');
629
- return `sha256:${hash}`;
630
- }
631
-
632
- // ─── Manifest generation ──────────────────────────────────────────────────────
633
-
634
- /** Generate manifest.json structure from validated agents, skills, and vocabulary. */
635
- export async function generateManifest(
636
- agents: Array<{ meta: AgentMeta; dir: string }>,
637
- skills: Array<{ meta: SkillMeta; dir: string }>,
638
- vocab: Vocab,
639
- version: string,
640
- commit: string
641
- ): Promise<Manifest> {
642
- const agentEntries: ManifestAgent[] = await Promise.all(
643
- agents.map(async ({ meta, dir }) => {
644
- const body_hash = await computeBodyHash(path.join(dir, 'body.md'));
645
- return { ...meta, body_hash };
646
- })
647
- );
648
- agentEntries.sort((a, b) => a.id.localeCompare(b.id));
649
-
650
- const skillEntries: ManifestSkill[] = await Promise.all(
651
- skills.map(async ({ meta, dir }) => {
652
- const body_hash = await computeBodyHash(path.join(dir, 'body.md'));
653
- return { ...meta, body_hash };
654
- })
655
- );
656
- skillEntries.sort((a, b) => a.id.localeCompare(b.id));
657
-
658
- const invocationSummaries: ManifestInvocationEntry[] = vocab.invocations.map((inv) => ({
659
- id: inv.id,
660
- description: inv.description,
661
- intent: inv.intent,
662
- fallback_behavior: inv.fallback_behavior,
663
- }));
664
-
665
- return {
666
- nexus_core_version: version,
667
- nexus_core_commit: commit,
668
- schema_contract_version: '2.0',
669
- agents: agentEntries,
670
- skills: skillEntries,
671
- vocabulary: {
672
- capabilities: vocab.capabilities,
673
- categories: vocab.categories,
674
- resume_tiers: vocab.resume_tiers,
675
- tags: vocab.tags,
676
- invocations: invocationSummaries,
677
- task_exceptions: vocab.task_exceptions,
678
- memory_policy: vocab.memory_policy,
679
- },
680
- };
681
- }
682
-
683
- /** Write manifest to <root>/manifest.json. */
684
- export async function writeManifest(root: string, manifest: Manifest): Promise<void> {
685
- const dest = path.join(root, 'manifest.json');
686
- await writeFile(dest, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
687
- }
688
-
689
- // ─── runAll ───────────────────────────────────────────────────────────────────
690
-
691
- /**
692
- * G1-G5: per-file fail-forward schema + referential integrity validation.
693
- * On success (no errors), generates and writes manifest.json.
694
- */
695
- export async function runAll(root: string): Promise<ValidationResult[]> {
696
- const allResults: ValidationResult[] = [];
697
-
698
- // Load vocabulary first (G1 for vocab files)
699
- const { vocab, results: vocabResults } = await loadVocab(root);
700
- allResults.push(...vocabResults);
701
-
702
- // Collect capability/category/resumeTier/tag ID sets (may be empty if vocab missing)
703
- const capabilityIds = new Set((vocab?.capabilities ?? []).map((c) => c.id));
704
- const categoryIds = new Set((vocab?.categories ?? []).map((c) => c.id));
705
- const resumeTierIds = new Set((vocab?.resume_tiers ?? []).map((r) => r.id));
706
- const tags = vocab?.tags ?? [];
707
-
708
- // Discover agent directories
709
- const agentMetaPaths = await glob(['agents/*/meta.yml'], {
710
- cwd: root,
711
- absolute: true,
712
- onlyFiles: true,
713
- });
714
-
715
- const validAgents: Array<{ meta: AgentMeta; rel: string; dir: string }> = [];
716
-
717
- for (const metaPath of agentMetaPaths) {
718
- const rel = path.relative(root, metaPath);
719
- const { result, data } = await validateAgentMeta(metaPath, rel);
720
- allResults.push(...result);
721
- if (data) {
722
- validAgents.push({ meta: data, rel, dir: path.dirname(metaPath) });
723
- }
724
- // per-file fail-forward: if G1 fails for this file, G2-G5 checks skip it (data is null)
725
- }
726
-
727
- // Discover skill directories
728
- const skillMetaPaths = await glob(['skills/*/meta.yml'], {
729
- cwd: root,
730
- absolute: true,
731
- onlyFiles: true,
732
- });
733
-
734
- const validSkills: Array<{ meta: SkillMeta; rel: string; dir: string }> = [];
735
-
736
- for (const metaPath of skillMetaPaths) {
737
- const rel = path.relative(root, metaPath);
738
- const { result, data } = await validateSkillMeta(metaPath, rel);
739
- allResults.push(...result);
740
- if (data) {
741
- validSkills.push({ meta: data, rel, dir: path.dirname(metaPath) });
742
- }
743
- }
744
-
745
- // G2-G5: referential integrity (only on files that passed G1)
746
- if (vocab) {
747
- allResults.push(...checkCapabilityIntegrity(validAgents, capabilityIds));
748
- allResults.push(...checkCategoryIntegrity(validAgents, categoryIds));
749
- allResults.push(...checkResumeTierIntegrity(validAgents, resumeTierIds));
750
- allResults.push(...checkTagIntegrity(validSkills, tags));
751
- // G5': capability entry field integrity
752
- allResults.push(...checkCapabilityEntryIntegrity(vocab.capabilities));
753
- // G6': invocation entry field integrity
754
- allResults.push(...checkInvocationEntryIntegrity(vocab.invocations));
755
- }
756
-
757
- // Manifest generation — only on full success (no errors)
758
- const hasErrors = allResults.some((r) => r.severity === 'error');
759
- if (!hasErrors && vocab) {
760
- try {
761
- // Determine version and commit from env or package.json
762
- let version = process.env['npm_package_version'] ?? '0.0.0';
763
- if (version === '0.0.0') {
764
- try {
765
- const pkg = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8')) as { version?: string };
766
- version = pkg.version ?? '0.0.0';
767
- } catch {
768
- // ignore
769
- }
770
- }
771
-
772
- let commit = process.env['GITHUB_SHA'] ?? 'local';
773
- if (commit === 'local') {
774
- try {
775
- const { execSync } = await import('node:child_process');
776
- const sha = execSync('git rev-parse --short HEAD', { cwd: root, encoding: 'utf8' }).trim();
777
- if (sha) commit = sha;
778
- } catch {
779
- // ignore — keep 'local'
780
- }
781
- }
782
-
783
- const manifest = await generateManifest(validAgents, validSkills, vocab, version, commit);
784
- await writeManifest(root, manifest);
785
- } catch (err) {
786
- allResults.push({
787
- file: 'manifest.json',
788
- gate: 'G1-schema',
789
- severity: 'error',
790
- message: `Manifest generation failed: ${(err as Error).message}`,
791
- });
792
- }
793
- }
794
-
795
- return allResults;
796
- }