@elevasis/core 0.24.1 → 0.25.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 (32) hide show
  1. package/dist/index.d.ts +75 -3
  2. package/dist/index.js +332 -4
  3. package/dist/knowledge/index.d.ts +30 -1
  4. package/dist/organization-model/index.d.ts +75 -3
  5. package/dist/organization-model/index.js +332 -4
  6. package/dist/test-utils/index.d.ts +1 -0
  7. package/dist/test-utils/index.js +4 -3
  8. package/package.json +1 -1
  9. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +94 -94
  10. package/src/execution/engine/llm/adapters/__tests__/openrouter.integration.test.ts +10 -10
  11. package/src/knowledge/__tests__/queries.test.ts +960 -546
  12. package/src/knowledge/format.ts +322 -100
  13. package/src/knowledge/index.ts +18 -5
  14. package/src/knowledge/queries.ts +1004 -239
  15. package/src/organization-model/__tests__/deprecate-helpers.test.ts +71 -0
  16. package/src/organization-model/__tests__/resolve.test.ts +9 -7
  17. package/src/organization-model/__tests__/scaffolders.test.ts +93 -0
  18. package/src/organization-model/defaults.ts +3 -3
  19. package/src/organization-model/helpers.ts +76 -9
  20. package/src/organization-model/icons.ts +1 -0
  21. package/src/organization-model/index.ts +3 -2
  22. package/src/organization-model/published.ts +15 -2
  23. package/src/organization-model/scaffolders/helpers.ts +84 -0
  24. package/src/organization-model/scaffolders/index.ts +19 -0
  25. package/src/organization-model/scaffolders/scaffoldKnowledgeNode.ts +48 -0
  26. package/src/organization-model/scaffolders/scaffoldOntologyRecord.ts +38 -0
  27. package/src/organization-model/scaffolders/scaffoldResource.ts +59 -0
  28. package/src/organization-model/scaffolders/scaffoldSystem.ts +110 -0
  29. package/src/organization-model/scaffolders/types.ts +81 -0
  30. package/src/platform/constants/versions.ts +1 -1
  31. package/src/reference/_generated/contracts.md +94 -94
  32. package/src/reference/glossary.md +71 -69
@@ -1,100 +1,322 @@
1
- import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
2
- import type { KnowledgeMount, ParsedKnowledgePath } from './queries'
3
-
4
- // ---------------------------------------------------------------------------
5
- // formatText
6
- // ---------------------------------------------------------------------------
7
-
8
- /**
9
- * Renders a list of `OrgKnowledgeNode` results as a human-friendly text table.
10
- *
11
- * Output format (one row per node):
12
- * `<kind> <id> <title> — <summary (truncated to 80 chars)>`
13
- *
14
- * Returns `"(no results)"` when the array is empty.
15
- */
16
- export function formatText(results: OrgKnowledgeNode[]): string {
17
- if (results.length === 0) {
18
- return '(no results)'
19
- }
20
-
21
- const kindWidth = Math.max(...results.map((n) => n.kind.length), 8)
22
- const idWidth = Math.max(...results.map((n) => n.id.length), 4)
23
-
24
- const header = `${'KIND'.padEnd(kindWidth)} ${'ID'.padEnd(idWidth)} TITLE`
25
- const divider = '-'.repeat(header.length + 20)
26
-
27
- const rows = results.map((n) => {
28
- const summary = n.summary.length > 80 ? n.summary.slice(0, 77) + '...' : n.summary
29
- return `${n.kind.padEnd(kindWidth)} ${n.id.padEnd(idWidth)} ${n.title} — ${summary}`
30
- })
31
-
32
- return [header, divider, ...rows].join('\n')
33
- }
34
-
35
- // ---------------------------------------------------------------------------
36
- // formatJson
37
- // ---------------------------------------------------------------------------
38
-
39
- /**
40
- * Wrapped JSON envelope for machine-readable output.
41
- *
42
- * Shape: `{ path, mount, args, results }`
43
- *
44
- * - `path` — the original path string passed to `parsePath`
45
- * - `mount` — the resolved mount axis
46
- * - `args` — the parsed arguments array
47
- * - `results` — the query results (array of `OrgKnowledgeNode` or string IDs)
48
- */
49
- export interface KnowledgeJsonEnvelope {
50
- path: string
51
- mount: KnowledgeMount
52
- args: string[]
53
- results: OrgKnowledgeNode[] | string[]
54
- }
55
-
56
- /**
57
- * Formats query results as a wrapped JSON envelope string.
58
- *
59
- * The envelope shape is `{ path, mount, args, results }`. This is intentionally
60
- * NOT flat `{ results }` — consumers (agent skills, jq pipelines) need the
61
- * mount + args to know how to interpret the results array.
62
- *
63
- * @param input.path - The original path string (e.g. `"/by-system/sales.crm"`).
64
- * @param input.mount - The resolved mount axis.
65
- * @param input.args - The parsed argument array.
66
- * @param input.results - The query results.
67
- */
68
- export function formatJson(input: {
69
- path: string
70
- parsed: ParsedKnowledgePath
71
- results: OrgKnowledgeNode[] | string[]
72
- }): string {
73
- const envelope: KnowledgeJsonEnvelope = {
74
- path: input.path,
75
- mount: input.parsed.mount,
76
- args: input.parsed.args,
77
- results: input.results
78
- }
79
- return JSON.stringify(envelope, null, 2)
80
- }
81
-
82
- // ---------------------------------------------------------------------------
83
- // formatIdsOnly
84
- // ---------------------------------------------------------------------------
85
-
86
- /**
87
- * Renders results as newline-separated IDs for piping.
88
- *
89
- * - For `OrgKnowledgeNode[]` results: emits `node.id` per line.
90
- * - For `string[]` results (governs / governedBy): emits each string per line.
91
- *
92
- * Returns an empty string when the array is empty (suitable for `wc -l` piping).
93
- */
94
- export function formatIdsOnly(results: OrgKnowledgeNode[] | string[]): string {
95
- if (results.length === 0) return ''
96
-
97
- const ids = results.map((r) => (typeof r === 'string' ? r : r.id))
98
- return ids.join('\n')
99
- }
100
-
1
+ import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
2
+ import type {
3
+ KnowledgeMount,
4
+ OmDescribeKnowledge,
5
+ OmDescribeOntology,
6
+ OmDescribePolicy,
7
+ OmDescribeResource,
8
+ OmDescribeResult,
9
+ OmDescribeRole,
10
+ OmDescribeSystem,
11
+ OmSearchHit,
12
+ ParsedKnowledgePath
13
+ } from './queries'
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // formatText
17
+ // ---------------------------------------------------------------------------
18
+
19
+ /**
20
+ * Renders a list of `OrgKnowledgeNode` results as a human-friendly text table.
21
+ *
22
+ * Output format (one row per node):
23
+ * `<kind> <id> <title> — <summary (truncated to 80 chars)>`
24
+ *
25
+ * Returns `"(no results)"` when the array is empty.
26
+ */
27
+ export function formatText(results: OrgKnowledgeNode[]): string {
28
+ if (results.length === 0) {
29
+ return '(no results)'
30
+ }
31
+
32
+ const kindWidth = Math.max(...results.map((n) => n.kind.length), 8)
33
+ const idWidth = Math.max(...results.map((n) => n.id.length), 4)
34
+
35
+ const header = `${'KIND'.padEnd(kindWidth)} ${'ID'.padEnd(idWidth)} TITLE`
36
+ const divider = '-'.repeat(header.length + 20)
37
+
38
+ const rows = results.map((n) => {
39
+ const summary = n.summary.length > 80 ? n.summary.slice(0, 77) + '...' : n.summary
40
+ return `${n.kind.padEnd(kindWidth)} ${n.id.padEnd(idWidth)} ${n.title} — ${summary}`
41
+ })
42
+
43
+ return [header, divider, ...rows].join('\n')
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // formatJson
48
+ // ---------------------------------------------------------------------------
49
+
50
+ /**
51
+ * Wrapped JSON envelope for machine-readable output.
52
+ *
53
+ * Shape: `{ path, mount, args, results }`
54
+ *
55
+ * - `path` — the original path string passed to `parsePath`
56
+ * - `mount` — the resolved mount axis
57
+ * - `args` — the parsed arguments array
58
+ * - `results` — the query results (array of `OrgKnowledgeNode` or string IDs)
59
+ */
60
+ export interface KnowledgeJsonEnvelope {
61
+ path: string
62
+ mount: KnowledgeMount
63
+ args: string[]
64
+ results: OrgKnowledgeNode[] | string[]
65
+ }
66
+
67
+ /**
68
+ * Formats query results as a wrapped JSON envelope string.
69
+ *
70
+ * The envelope shape is `{ path, mount, args, results }`. This is intentionally
71
+ * NOT flat `{ results }` — consumers (agent skills, jq pipelines) need the
72
+ * mount + args to know how to interpret the results array.
73
+ *
74
+ * @param input.path - The original path string (e.g. `"/by-system/sales.crm"`).
75
+ * @param input.mount - The resolved mount axis.
76
+ * @param input.args - The parsed argument array.
77
+ * @param input.results - The query results.
78
+ */
79
+ export function formatJson(input: {
80
+ path: string
81
+ parsed: ParsedKnowledgePath
82
+ results: OrgKnowledgeNode[] | string[]
83
+ }): string {
84
+ const envelope: KnowledgeJsonEnvelope = {
85
+ path: input.path,
86
+ mount: input.parsed.mount,
87
+ args: input.parsed.args,
88
+ results: input.results
89
+ }
90
+ return JSON.stringify(envelope, null, 2)
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // formatIdsOnly
95
+ // ---------------------------------------------------------------------------
96
+
97
+ /**
98
+ * Renders results as newline-separated IDs for piping.
99
+ *
100
+ * - For `OrgKnowledgeNode[]` results: emits `node.id` per line.
101
+ * - For `string[]` results (governs / governedBy): emits each string per line.
102
+ *
103
+ * Returns an empty string when the array is empty (suitable for `wc -l` piping).
104
+ */
105
+ export function formatIdsOnly(results: OrgKnowledgeNode[] | string[] | OmSearchHit[]): string {
106
+ if (results.length === 0) return ''
107
+
108
+ const ids = results.map((r) => (typeof r === 'string' ? r : r.id))
109
+ return ids.join('\n')
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // formatOmSearchHits
114
+ // ---------------------------------------------------------------------------
115
+
116
+ /**
117
+ * Renders `omSearch()` results as a human-readable table.
118
+ *
119
+ * Format (one row per hit):
120
+ * `[<kind>] <id> — <title> — <summary (truncated to 80 chars)>`
121
+ *
122
+ * Hits are pre-sorted by score (descending) by the caller. Returns
123
+ * `"(no results)"` when the array is empty.
124
+ */
125
+ export function formatOmSearchHits(hits: OmSearchHit[]): string {
126
+ if (hits.length === 0) return '(no results)'
127
+
128
+ const kindWidth = Math.max(...hits.map((h) => labelForKind(h).length), 6)
129
+ const idWidth = Math.max(...hits.map((h) => h.id.length), 4)
130
+
131
+ const rows = hits.map((hit) => {
132
+ const summary = hit.summary.length > 80 ? hit.summary.slice(0, 77) + '...' : hit.summary
133
+ const kindLabel = labelForKind(hit).padEnd(kindWidth)
134
+ const idLabel = hit.id.padEnd(idWidth)
135
+ const tail = summary ? ` — ${hit.title} — ${summary}` : ` — ${hit.title}`
136
+ return `${kindLabel} ${idLabel}${tail}`
137
+ })
138
+
139
+ return rows.join('\n')
140
+ }
141
+
142
+ function labelForKind(hit: OmSearchHit): string {
143
+ return hit.subKind ? `[${hit.kind}/${hit.subKind}]` : `[${hit.kind}]`
144
+ }
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // formatOmDescribe
148
+ // ---------------------------------------------------------------------------
149
+
150
+ /**
151
+ * Renders an `OmDescribeResult` neighborhood view as multi-section text.
152
+ *
153
+ * Each kind gets a tailored layout (system shows resources + governing
154
+ * knowledge; knowledge shows body excerpt + governs; etc.). Returns
155
+ * `"(node not found)"` when the result is `undefined`.
156
+ */
157
+ export function formatOmDescribe(result: OmDescribeResult | undefined): string {
158
+ if (!result) return '(node not found)'
159
+
160
+ switch (result.kind) {
161
+ case 'system':
162
+ return formatSystemDescribe(result)
163
+ case 'resource':
164
+ return formatResourceDescribe(result)
165
+ case 'knowledge':
166
+ return formatKnowledgeDescribe(result)
167
+ case 'ontology':
168
+ return formatOntologyDescribe(result)
169
+ case 'role':
170
+ return formatRoleDescribe(result)
171
+ case 'policy':
172
+ return formatPolicyDescribe(result)
173
+ }
174
+ }
175
+
176
+ function section(title: string, body: string): string {
177
+ return body ? `${title}:\n${body}` : ''
178
+ }
179
+
180
+ function bullet(items: string[]): string {
181
+ if (items.length === 0) return ' (none)'
182
+ return items.map((i) => ` - ${i}`).join('\n')
183
+ }
184
+
185
+ function field(label: string, value: string | undefined | null): string {
186
+ return value === undefined || value === null || value === '' ? '' : `${label}: ${value}`
187
+ }
188
+
189
+ function join(parts: string[]): string {
190
+ return parts.filter((p) => p.length > 0).join('\n\n')
191
+ }
192
+
193
+ function formatSystemDescribe(r: OmDescribeSystem): string {
194
+ const header = join([
195
+ `System: ${r.id}`,
196
+ [
197
+ field('Label', r.label),
198
+ field('Kind', r.systemKind),
199
+ field('Lifecycle', r.lifecycle),
200
+ field('Parent', r.parentSystemId),
201
+ field('Responsible role', r.responsibleRoleId)
202
+ ]
203
+ .filter((s) => s.length > 0)
204
+ .join('\n'),
205
+ r.description ? `Description: ${r.description}` : ''
206
+ ])
207
+
208
+ const govSection = section('Governed by knowledge', bullet(r.governingKnowledgeIds))
209
+
210
+ const resourceLines: string[] = []
211
+ const totalResources = Object.values(r.resourceCountsByKind).reduce((a, b) => a + b, 0)
212
+ if (totalResources > 0) {
213
+ for (const [kind, count] of Object.entries(r.resourceCountsByKind)) {
214
+ const ids = r.resourceIdsByKind[kind] ?? []
215
+ const preview = ids.slice(0, 5).join(', ')
216
+ const tail = ids.length > 5 ? `, ... (+${ids.length - 5})` : ''
217
+ resourceLines.push(` ${kind}s (${count}): ${preview}${tail}`)
218
+ }
219
+ } else {
220
+ resourceLines.push(' (none)')
221
+ }
222
+ const resourceSection = `Resources (${totalResources}):\n${resourceLines.join('\n')}`
223
+
224
+ const ontologyEntries = Object.entries(r.ontologyCountsByKind)
225
+ const ontologySection = ontologyEntries.length
226
+ ? `Ontology: ${ontologyEntries.map(([k, n]) => `${k}s: ${n}`).join(', ')}`
227
+ : ''
228
+
229
+ const childSection = section('Subsystems', bullet(r.childSystemPaths))
230
+
231
+ return join([header, govSection, resourceSection, ontologySection, childSection])
232
+ }
233
+
234
+ function formatResourceDescribe(r: OmDescribeResource): string {
235
+ const header = join([
236
+ `Resource: ${r.id}`,
237
+ [
238
+ field('Kind', r.resourceKind),
239
+ field('System', r.systemPath),
240
+ field('Title', r.title),
241
+ field('Status', r.status),
242
+ field('Owner role', r.ownerRoleId)
243
+ ]
244
+ .filter((s) => s.length > 0)
245
+ .join('\n'),
246
+ r.description ? `Description: ${r.description}` : ''
247
+ ])
248
+
249
+ const ontologyLines: string[] = []
250
+ if (r.ontology) {
251
+ if (r.ontology.primaryAction) ontologyLines.push(` primary action: ${r.ontology.primaryAction}`)
252
+ if (r.ontology.actions?.length) ontologyLines.push(` actions: ${r.ontology.actions.join(', ')}`)
253
+ if (r.ontology.reads?.length) ontologyLines.push(` reads: ${r.ontology.reads.join(', ')}`)
254
+ if (r.ontology.writes?.length) ontologyLines.push(` writes: ${r.ontology.writes.join(', ')}`)
255
+ if (r.ontology.emits?.length) ontologyLines.push(` emits: ${r.ontology.emits.join(', ')}`)
256
+ if (r.ontology.usesCatalogs?.length) ontologyLines.push(` uses catalogs: ${r.ontology.usesCatalogs.join(', ')}`)
257
+ }
258
+ const ontologySection = ontologyLines.length ? `Ontology bindings:\n${ontologyLines.join('\n')}` : ''
259
+
260
+ const codeRefsSection = r.codeRefPaths.length ? `Code refs:\n${bullet(r.codeRefPaths)}` : ''
261
+
262
+ return join([header, ontologySection, codeRefsSection])
263
+ }
264
+
265
+ function formatKnowledgeDescribe(r: OmDescribeKnowledge): string {
266
+ const header = join([
267
+ `Knowledge: ${r.id}`,
268
+ [field('Kind', r.knowledgeKind), field('Title', r.title), field('Updated', r.updatedAt)]
269
+ .filter((s) => s.length > 0)
270
+ .join('\n'),
271
+ r.summary ? `Summary: ${r.summary}` : ''
272
+ ])
273
+
274
+ const ownerSection = r.ownerIds.length ? `Owned by:\n${bullet(r.ownerIds)}` : ''
275
+ const govSection = section('Governs', bullet(r.governs))
276
+ const bodySection = `Body excerpt (${r.bodyLineCount} lines total — use knowledge:cat for full):\n${r.bodyExcerpt}`
277
+
278
+ return join([header, ownerSection, govSection, bodySection])
279
+ }
280
+
281
+ function formatOntologyDescribe(r: OmDescribeOntology): string {
282
+ const header = join([
283
+ `Ontology: ${r.id}`,
284
+ [field('Kind', r.ontologyKind), field('Scope', r.scope), field('Local id', r.localId), field('Label', r.label)]
285
+ .filter((s) => s.length > 0)
286
+ .join('\n'),
287
+ r.description ? `Description: ${r.description}` : ''
288
+ ])
289
+
290
+ const govSection = section('Governed by knowledge', bullet(r.governingKnowledgeIds))
291
+ return join([header, govSection])
292
+ }
293
+
294
+ function formatRoleDescribe(r: OmDescribeRole): string {
295
+ const header = join([
296
+ `Role: ${r.id}`,
297
+ [field('Title', r.title), field('Reports to', r.reportsToId)].filter((s) => s.length > 0).join('\n')
298
+ ])
299
+
300
+ const respSection = section('Responsibilities', bullet(r.responsibilities))
301
+ const responsibleForSection = section('Responsible for systems', bullet(r.responsibleFor))
302
+
303
+ return join([header, respSection, responsibleForSection])
304
+ }
305
+
306
+ function formatPolicyDescribe(r: OmDescribePolicy): string {
307
+ const header = join([
308
+ `Policy: ${r.id}`,
309
+ [field('Label', r.label), field('Trigger', r.triggerKind)].filter((s) => s.length > 0).join('\n'),
310
+ r.description ? `Description: ${r.description}` : ''
311
+ ])
312
+
313
+ const effectSection = section('Effect kinds', bullet(r.effectKinds))
314
+ const appliesLines: string[] = []
315
+ if (r.appliesToSystemIds.length) appliesLines.push(` systems: ${r.appliesToSystemIds.join(', ')}`)
316
+ if (r.appliesToActionIds.length) appliesLines.push(` actions: ${r.appliesToActionIds.join(', ')}`)
317
+ if (r.appliesToResourceIds.length) appliesLines.push(` resources: ${r.appliesToResourceIds.join(', ')}`)
318
+ if (r.appliesToRoleIds.length) appliesLines.push(` roles: ${r.appliesToRoleIds.join(', ')}`)
319
+ const appliesSection = appliesLines.length ? `Applies to:\n${appliesLines.join('\n')}` : ''
320
+
321
+ return join([header, effectSection, appliesSection])
322
+ }
@@ -1,5 +1,18 @@
1
- export { bySystem, byOntology, byKind, byOwner, governs, governedBy, parsePath } from './queries'
2
- export type { KnowledgeMount, ParsedKnowledgePath } from './queries'
3
-
4
- export { formatText, formatJson, formatIdsOnly } from './format'
5
- export type { KnowledgeJsonEnvelope } from './format'
1
+ export { bySystem, byOntology, byKind, byOwner, governs, governedBy, parsePath, omSearch, omDescribe } from './queries'
2
+ export type {
3
+ KnowledgeMount,
4
+ ParsedKnowledgePath,
5
+ OmSearchHit,
6
+ OmSearchHitKind,
7
+ OmSearchOptions,
8
+ OmDescribeResult,
9
+ OmDescribeSystem,
10
+ OmDescribeResource,
11
+ OmDescribeKnowledge,
12
+ OmDescribeOntology,
13
+ OmDescribeRole,
14
+ OmDescribePolicy
15
+ } from './queries'
16
+
17
+ export { formatText, formatJson, formatIdsOnly, formatOmSearchHits, formatOmDescribe } from './format'
18
+ export type { KnowledgeJsonEnvelope } from './format'