@elevasis/core 0.22.0 → 0.23.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 (112) hide show
  1. package/dist/index.d.ts +2330 -2391
  2. package/dist/index.js +2322 -1147
  3. package/dist/knowledge/index.d.ts +702 -1136
  4. package/dist/knowledge/index.js +9 -9
  5. package/dist/organization-model/index.d.ts +2330 -2391
  6. package/dist/organization-model/index.js +2322 -1147
  7. package/dist/test-utils/index.d.ts +703 -1106
  8. package/dist/test-utils/index.js +1735 -1089
  9. package/package.json +1 -1
  10. package/src/__tests__/template-core-compatibility.test.ts +11 -79
  11. package/src/_gen/__tests__/__snapshots__/contracts.md.snap +360 -98
  12. package/src/business/acquisition/api-schemas.test.ts +2 -2
  13. package/src/business/acquisition/api-schemas.ts +7 -9
  14. package/src/business/acquisition/build-templates.test.ts +4 -4
  15. package/src/business/acquisition/build-templates.ts +72 -30
  16. package/src/business/acquisition/crm-state-actions.test.ts +13 -11
  17. package/src/business/acquisition/types.ts +7 -3
  18. package/src/execution/engine/agent/core/types.ts +1 -1
  19. package/src/execution/engine/workflow/types.ts +2 -2
  20. package/src/knowledge/README.md +8 -7
  21. package/src/knowledge/__tests__/queries.test.ts +74 -73
  22. package/src/knowledge/format.ts +10 -9
  23. package/src/knowledge/index.ts +1 -1
  24. package/src/knowledge/published.ts +1 -1
  25. package/src/knowledge/queries.ts +26 -25
  26. package/src/organization-model/README.md +66 -26
  27. package/src/organization-model/__tests__/content-kinds-registry.test.ts +210 -0
  28. package/src/organization-model/__tests__/defaults.test.ts +72 -98
  29. package/src/organization-model/__tests__/domains/actions.test.ts +56 -0
  30. package/src/organization-model/__tests__/domains/customers.test.ts +299 -295
  31. package/src/organization-model/__tests__/domains/entities.test.ts +56 -0
  32. package/src/organization-model/__tests__/domains/goals.test.ts +493 -479
  33. package/src/organization-model/__tests__/domains/identity.test.ts +280 -279
  34. package/src/organization-model/__tests__/domains/navigation.test.ts +268 -212
  35. package/src/organization-model/__tests__/domains/offerings.test.ts +414 -419
  36. package/src/organization-model/__tests__/domains/policies.test.ts +323 -0
  37. package/src/organization-model/__tests__/domains/resource-mappings.test.ts +271 -271
  38. package/src/organization-model/__tests__/domains/resources.test.ts +159 -37
  39. package/src/organization-model/__tests__/domains/roles.test.ts +147 -86
  40. package/src/organization-model/__tests__/domains/statuses.test.ts +246 -243
  41. package/src/organization-model/__tests__/domains/systems.test.ts +67 -51
  42. package/src/organization-model/__tests__/flatten-additive-merge.test.ts +361 -0
  43. package/src/organization-model/__tests__/foundation.test.ts +74 -102
  44. package/src/organization-model/__tests__/get-resources-for-system.test.ts +144 -0
  45. package/src/organization-model/__tests__/graph.test.ts +899 -71
  46. package/src/organization-model/__tests__/knowledge.test.ts +173 -52
  47. package/src/organization-model/__tests__/lookup-helpers.test.ts +438 -0
  48. package/src/organization-model/__tests__/migration-helpers.test.ts +591 -0
  49. package/src/organization-model/__tests__/prospecting-ssot.test.ts +36 -27
  50. package/src/organization-model/__tests__/recursive-system-schema.test.ts +520 -0
  51. package/src/organization-model/__tests__/resolve.test.ts +174 -23
  52. package/src/organization-model/__tests__/schema.test.ts +291 -114
  53. package/src/organization-model/__tests__/surface-projection.test.ts +207 -97
  54. package/src/organization-model/catalogs/lead-gen.ts +144 -0
  55. package/src/organization-model/content-kinds/config.ts +36 -0
  56. package/src/organization-model/content-kinds/index.ts +74 -0
  57. package/src/organization-model/content-kinds/pipeline.ts +68 -0
  58. package/src/organization-model/content-kinds/registry.ts +44 -0
  59. package/src/organization-model/content-kinds/status.ts +71 -0
  60. package/src/organization-model/content-kinds/template.ts +83 -0
  61. package/src/organization-model/content-kinds/types.ts +117 -0
  62. package/src/organization-model/contracts.ts +13 -3
  63. package/src/organization-model/defaults.ts +488 -96
  64. package/src/organization-model/domains/actions.ts +239 -0
  65. package/src/organization-model/domains/customers.ts +78 -75
  66. package/src/organization-model/domains/entities.ts +144 -0
  67. package/src/organization-model/domains/goals.ts +83 -80
  68. package/src/organization-model/domains/knowledge.ts +74 -16
  69. package/src/organization-model/domains/navigation.ts +107 -384
  70. package/src/organization-model/domains/offerings.ts +71 -66
  71. package/src/organization-model/domains/policies.ts +102 -0
  72. package/src/organization-model/domains/projects.ts +14 -48
  73. package/src/organization-model/domains/prospecting.ts +62 -181
  74. package/src/organization-model/domains/resources.ts +81 -24
  75. package/src/organization-model/domains/roles.ts +13 -10
  76. package/src/organization-model/domains/sales.ts +10 -219
  77. package/src/organization-model/domains/shared.ts +57 -57
  78. package/src/organization-model/domains/statuses.ts +339 -130
  79. package/src/organization-model/domains/systems.ts +186 -29
  80. package/src/organization-model/foundation.ts +54 -67
  81. package/src/organization-model/graph/build.ts +682 -54
  82. package/src/organization-model/graph/link.ts +1 -1
  83. package/src/organization-model/graph/schema.ts +24 -9
  84. package/src/organization-model/graph/types.ts +20 -7
  85. package/src/organization-model/helpers.ts +231 -26
  86. package/src/organization-model/index.ts +116 -5
  87. package/src/organization-model/migration-helpers.ts +249 -0
  88. package/src/organization-model/organization-graph.mdx +16 -15
  89. package/src/organization-model/organization-model.mdx +89 -41
  90. package/src/organization-model/published.ts +120 -18
  91. package/src/organization-model/resolve.ts +117 -54
  92. package/src/organization-model/schema.ts +561 -140
  93. package/src/organization-model/surface-projection.ts +116 -122
  94. package/src/organization-model/types.ts +102 -21
  95. package/src/platform/constants/versions.ts +1 -1
  96. package/src/platform/registry/__tests__/command-view.test.ts +6 -8
  97. package/src/platform/registry/__tests__/resource-link.test.ts +13 -8
  98. package/src/platform/registry/__tests__/resource-registry.integration.test.ts +16 -31
  99. package/src/platform/registry/__tests__/resource-registry.nested-systems.test.ts +245 -0
  100. package/src/platform/registry/__tests__/resource-registry.test.ts +9 -7
  101. package/src/platform/registry/__tests__/validation.test.ts +15 -11
  102. package/src/platform/registry/resource-registry.ts +20 -8
  103. package/src/platform/registry/serialization.ts +7 -7
  104. package/src/platform/registry/types.ts +3 -3
  105. package/src/platform/registry/validation.ts +17 -15
  106. package/src/reference/_generated/contracts.md +362 -99
  107. package/src/reference/glossary.md +18 -18
  108. package/src/supabase/database.types.ts +60 -0
  109. package/src/test-utils/test-utils.test.ts +1 -6
  110. package/src/organization-model/__tests__/domains/operations.test.ts +0 -203
  111. package/src/organization-model/domains/features.ts +0 -31
  112. package/src/organization-model/domains/operations.ts +0 -85
@@ -1,5 +1,5 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { byFeature, byKind, byOwner, governs, governedBy, parsePath } from '../queries'
1
+ import { describe, expect, it } from 'vitest'
2
+ import { bySystem, byKind, byOwner, governs, governedBy, parsePath } from '../queries'
3
3
  import { formatText, formatJson, formatIdsOnly } from '../format'
4
4
  import type { OrganizationGraph } from '../../organization-model/graph/types'
5
5
  import type { OrgKnowledgeNode } from '../../organization-model/domains/knowledge'
@@ -10,8 +10,8 @@ import type { OrgKnowledgeNode } from '../../organization-model/domains/knowledg
10
10
 
11
11
  /**
12
12
  * Three knowledge nodes covering all three kinds.
13
- * - playbook-a governs feature:sales.crm and feature:sales.lead-gen
14
- * - strategy-b governs feature:sales.lead-gen
13
+ * - playbook-a governs system:sales.crm and system:sales.lead-gen
14
+ * - strategy-b governs system:sales.lead-gen
15
15
  * - reference-c has no links (no governs edges)
16
16
  * - playbook-d is owned by "role.ops-lead"
17
17
  */
@@ -22,7 +22,7 @@ const NODES: OrgKnowledgeNode[] = [
22
22
  title: 'Playbook A',
23
23
  summary: 'Playbook A summary.',
24
24
  body: '## Playbook A\n\nContent.',
25
- links: [{ nodeId: 'feature:sales.crm' }, { nodeId: 'feature:sales.lead-gen' }],
25
+ links: [{ nodeId: 'system:sales.crm' }, { nodeId: 'system:sales.lead-gen' }],
26
26
  ownerIds: [],
27
27
  updatedAt: '2026-01-01'
28
28
  },
@@ -32,7 +32,7 @@ const NODES: OrgKnowledgeNode[] = [
32
32
  title: 'Strategy B',
33
33
  summary: 'Strategy B summary.',
34
34
  body: '## Strategy B\n\nContent.',
35
- links: [{ nodeId: 'feature:sales.lead-gen' }],
35
+ links: [{ nodeId: 'system:sales.lead-gen' }],
36
36
  ownerIds: ['role.ops-lead'],
37
37
  updatedAt: '2026-01-02'
38
38
  },
@@ -67,13 +67,13 @@ const GRAPH: OrganizationGraph = {
67
67
  organizationModelVersion: 1,
68
68
  nodes: [
69
69
  { id: 'organization-model', kind: 'organization', label: 'Organization Model' },
70
- { id: 'feature:sales.crm', kind: 'feature', label: 'CRM', sourceId: 'sales.crm', featureId: 'sales.crm' },
70
+ { id: 'system:sales.crm', kind: 'system', label: 'CRM', sourceId: 'sales.crm', systemId: 'sales.crm' },
71
71
  {
72
- id: 'feature:sales.lead-gen',
73
- kind: 'feature',
72
+ id: 'system:sales.lead-gen',
73
+ kind: 'system',
74
74
  label: 'Lead Gen',
75
75
  sourceId: 'sales.lead-gen',
76
- featureId: 'sales.lead-gen'
76
+ systemId: 'sales.lead-gen'
77
77
  },
78
78
  {
79
79
  id: 'knowledge:knowledge.playbook-a',
@@ -128,50 +128,50 @@ const GRAPH: OrganizationGraph = {
128
128
  },
129
129
  // governs edges
130
130
  {
131
- id: 'edge:governs:knowledge:knowledge.playbook-a:feature:sales.crm',
131
+ id: 'edge:governs:knowledge:knowledge.playbook-a:system:sales.crm',
132
132
  kind: 'governs',
133
133
  sourceId: 'knowledge:knowledge.playbook-a',
134
- targetId: 'feature:sales.crm'
134
+ targetId: 'system:sales.crm'
135
135
  },
136
136
  {
137
- id: 'edge:governs:knowledge:knowledge.playbook-a:feature:sales.lead-gen',
137
+ id: 'edge:governs:knowledge:knowledge.playbook-a:system:sales.lead-gen',
138
138
  kind: 'governs',
139
139
  sourceId: 'knowledge:knowledge.playbook-a',
140
- targetId: 'feature:sales.lead-gen'
140
+ targetId: 'system:sales.lead-gen'
141
141
  },
142
142
  {
143
- id: 'edge:governs:knowledge:knowledge.strategy-b:feature:sales.lead-gen',
143
+ id: 'edge:governs:knowledge:knowledge.strategy-b:system:sales.lead-gen',
144
144
  kind: 'governs',
145
145
  sourceId: 'knowledge:knowledge.strategy-b',
146
- targetId: 'feature:sales.lead-gen'
146
+ targetId: 'system:sales.lead-gen'
147
147
  }
148
148
  ]
149
149
  }
150
150
 
151
151
  // ---------------------------------------------------------------------------
152
- // byFeature
152
+ // bySystem
153
153
  // ---------------------------------------------------------------------------
154
154
 
155
- describe('byFeature', () => {
156
- it('returns nodes that have governs edges to the given feature', () => {
157
- const result = byFeature(GRAPH, 'sales.crm', NODES)
155
+ describe('bySystem', () => {
156
+ it('returns nodes that have governs edges to the given system', () => {
157
+ const result = bySystem(GRAPH, 'sales.crm', NODES)
158
158
  expect(result).toHaveLength(1)
159
159
  expect(result[0].id).toBe('knowledge.playbook-a')
160
160
  })
161
161
 
162
- it('returns multiple nodes when multiple knowledge nodes govern the same feature', () => {
163
- const result = byFeature(GRAPH, 'sales.lead-gen', NODES)
162
+ it('returns multiple nodes when multiple knowledge nodes govern the same system', () => {
163
+ const result = bySystem(GRAPH, 'sales.lead-gen', NODES)
164
164
  const ids = result.map((n) => n.id).sort()
165
165
  expect(ids).toEqual(['knowledge.playbook-a', 'knowledge.strategy-b'].sort())
166
166
  })
167
167
 
168
- it('returns empty array when no node governs the feature', () => {
169
- const result = byFeature(GRAPH, 'feature.does-not-exist', NODES)
168
+ it('returns empty array when no node governs the system', () => {
169
+ const result = bySystem(GRAPH, 'system.does-not-exist', NODES)
170
170
  expect(result).toHaveLength(0)
171
171
  })
172
172
 
173
173
  it('returns full OrgKnowledgeNode objects (not just graph stubs)', () => {
174
- const result = byFeature(GRAPH, 'sales.crm', NODES)
174
+ const result = bySystem(GRAPH, 'sales.crm', NODES)
175
175
  expect(result[0].body).toBeDefined()
176
176
  expect(result[0].links).toBeDefined()
177
177
  expect(result[0].ownerIds).toBeDefined()
@@ -237,17 +237,17 @@ describe('byOwner', () => {
237
237
  describe('governs', () => {
238
238
  it('returns target graph node IDs for outgoing governs edges (OM node id input)', () => {
239
239
  const result = governs(GRAPH, 'knowledge.playbook-a')
240
- expect(result.sort()).toEqual(['feature:sales.crm', 'feature:sales.lead-gen'].sort())
240
+ expect(result.sort()).toEqual(['system:sales.crm', 'system:sales.lead-gen'].sort())
241
241
  })
242
242
 
243
243
  it('accepts graph node ID format input', () => {
244
244
  const result = governs(GRAPH, 'knowledge:knowledge.playbook-a')
245
- expect(result.sort()).toEqual(['feature:sales.crm', 'feature:sales.lead-gen'].sort())
245
+ expect(result.sort()).toEqual(['system:sales.crm', 'system:sales.lead-gen'].sort())
246
246
  })
247
247
 
248
248
  it('returns single target for a node with one link', () => {
249
249
  const result = governs(GRAPH, 'knowledge.strategy-b')
250
- expect(result).toEqual(['feature:sales.lead-gen'])
250
+ expect(result).toEqual(['system:sales.lead-gen'])
251
251
  })
252
252
 
253
253
  it('returns empty array for a node with no governs edges', () => {
@@ -266,69 +266,69 @@ describe('governs', () => {
266
266
  // ---------------------------------------------------------------------------
267
267
 
268
268
  describe('governedBy', () => {
269
- it('returns knowledge graph node IDs for incoming governs edges (prefixed feature id)', () => {
270
- const result = governedBy(GRAPH, 'feature:sales.lead-gen')
269
+ it('returns knowledge graph node IDs for incoming governs edges (prefixed system id)', () => {
270
+ const result = governedBy(GRAPH, 'system:sales.lead-gen')
271
271
  const sorted = result.sort()
272
272
  expect(sorted).toEqual(['knowledge:knowledge.playbook-a', 'knowledge:knowledge.strategy-b'].sort())
273
273
  })
274
274
 
275
- it('accepts bare feature id (auto-prefixes with feature:)', () => {
275
+ it('accepts bare system id (auto-prefixes with system:)', () => {
276
276
  const result = governedBy(GRAPH, 'sales.crm')
277
277
  expect(result).toEqual(['knowledge:knowledge.playbook-a'])
278
278
  })
279
279
 
280
280
  it('returns empty array when no knowledge node governs the target', () => {
281
- const result = governedBy(GRAPH, 'feature:dashboard')
281
+ const result = governedBy(GRAPH, 'system:dashboard')
282
282
  expect(result).toHaveLength(0)
283
283
  })
284
284
 
285
285
  it('returns empty array for unknown target node id', () => {
286
- const result = governedBy(GRAPH, 'feature:does-not-exist')
286
+ const result = governedBy(GRAPH, 'system:does-not-exist')
287
287
  expect(result).toHaveLength(0)
288
288
  })
289
289
  })
290
290
 
291
291
  // ---------------------------------------------------------------------------
292
- // parsePath happy paths (all five mount axes)
292
+ // parsePath — happy paths (all five mount axes)
293
293
  // ---------------------------------------------------------------------------
294
294
 
295
- describe('parsePath happy paths', () => {
296
- it('/by-kind/playbook mount:by-kind args:[playbook]', () => {
295
+ describe('parsePath — happy paths', () => {
296
+ it('/by-kind/playbook → mount:by-kind args:[playbook]', () => {
297
297
  expect(parsePath('/by-kind/playbook')).toEqual({ mount: 'by-kind', args: ['playbook'] })
298
298
  })
299
299
 
300
- it('/by-kind/strategy mount:by-kind args:[strategy]', () => {
300
+ it('/by-kind/strategy → mount:by-kind args:[strategy]', () => {
301
301
  expect(parsePath('/by-kind/strategy')).toEqual({ mount: 'by-kind', args: ['strategy'] })
302
302
  })
303
303
 
304
- it('/by-feature/sales.crm mount:by-feature args:[sales.crm]', () => {
305
- expect(parsePath('/by-feature/sales.crm')).toEqual({ mount: 'by-feature', args: ['sales.crm'] })
304
+ it('/by-system/sales.crm → mount:by-system args:[sales.crm]', () => {
305
+ expect(parsePath('/by-system/sales.crm')).toEqual({ mount: 'by-system', args: ['sales.crm'] })
306
306
  })
307
307
 
308
- it('/by-feature/sales/crm (slash-delimited) mount:by-feature args:[sales/crm]', () => {
309
- // featureId with slashes is joined and passed as-is for the caller to interpret
310
- expect(parsePath('/by-feature/sales/crm')).toEqual({ mount: 'by-feature', args: ['sales/crm'] })
308
+ it('/by-system/sales/crm (slash-delimited) → mount:by-system args:[sales/crm]', () => {
309
+ // systemId with slashes is joined and passed as-is for the caller to interpret
310
+ expect(parsePath('/by-system/sales/crm')).toEqual({ mount: 'by-system', args: ['sales/crm'] })
311
311
  })
312
312
 
313
- it('/by-owner/role.ops-lead mount:by-owner args:[role.ops-lead]', () => {
313
+ it('/by-owner/role.ops-lead → mount:by-owner args:[role.ops-lead]', () => {
314
314
  expect(parsePath('/by-owner/role.ops-lead')).toEqual({ mount: 'by-owner', args: ['role.ops-lead'] })
315
315
  })
316
316
 
317
- it('/graph/knowledge.outreach-playbook/governs mount:graph args:[knowledge.outreach-playbook,governs]', () => {
317
+ it('/graph/knowledge.outreach-playbook/governs → mount:graph args:[knowledge.outreach-playbook,governs]', () => {
318
318
  expect(parsePath('/graph/knowledge.outreach-playbook/governs')).toEqual({
319
319
  mount: 'graph',
320
320
  args: ['knowledge.outreach-playbook', 'governs']
321
321
  })
322
322
  })
323
323
 
324
- it('/graph/knowledge.outreach-playbook/governed-by mount:graph args:[knowledge.outreach-playbook,governed-by]', () => {
324
+ it('/graph/knowledge.outreach-playbook/governed-by → mount:graph args:[knowledge.outreach-playbook,governed-by]', () => {
325
325
  expect(parsePath('/graph/knowledge.outreach-playbook/governed-by')).toEqual({
326
326
  mount: 'graph',
327
327
  args: ['knowledge.outreach-playbook', 'governed-by']
328
328
  })
329
329
  })
330
330
 
331
- it('/<nodeId> single segment mount:node args:[nodeId]', () => {
331
+ it('/<nodeId> single segment → mount:node args:[nodeId]', () => {
332
332
  expect(parsePath('/knowledge.outreach-playbook')).toEqual({
333
333
  mount: 'node',
334
334
  args: ['knowledge.outreach-playbook']
@@ -341,10 +341,10 @@ describe('parsePath — happy paths', () => {
341
341
  })
342
342
 
343
343
  // ---------------------------------------------------------------------------
344
- // parsePath invalid inputs (5+)
344
+ // parsePath — invalid inputs (5+)
345
345
  // ---------------------------------------------------------------------------
346
346
 
347
- describe('parsePath invalid inputs', () => {
347
+ describe('parsePath — invalid inputs', () => {
348
348
  it('throws on empty string', () => {
349
349
  expect(() => parsePath('')).toThrow('parsePath: path must be a non-empty string')
350
350
  })
@@ -357,8 +357,8 @@ describe('parsePath — invalid inputs', () => {
357
357
  expect(() => parsePath('/')).toThrow('parsePath: path resolves to root with no mount')
358
358
  })
359
359
 
360
- it('throws on /by-feature with no featureId argument', () => {
361
- expect(() => parsePath('/by-feature')).toThrow('/by-feature requires a featureId argument')
360
+ it('throws on /by-system with no systemId argument', () => {
361
+ expect(() => parsePath('/by-system')).toThrow('/by-system requires a systemId argument')
362
362
  })
363
363
 
364
364
  it('throws on /by-kind with no kind argument', () => {
@@ -385,11 +385,11 @@ describe('parsePath — invalid inputs', () => {
385
385
  })
386
386
 
387
387
  // ---------------------------------------------------------------------------
388
- // parsePath edge cases
388
+ // parsePath — edge cases
389
389
  // ---------------------------------------------------------------------------
390
390
 
391
391
  describe('parsePath edge cases', () => {
392
- // Whitespace handling the parser does NOT trim the incoming string.
392
+ // Whitespace handling — the parser does NOT trim the incoming string.
393
393
  // A path with leading whitespace does not start with '/' so it throws.
394
394
  it('throws on path with leading whitespace', () => {
395
395
  expect(() => parsePath(' /by-kind/playbook')).toThrow('parsePath: path must start with "/"')
@@ -409,13 +409,13 @@ describe('parsePath edge cases', () => {
409
409
  expect(parsePath('/by-kind/playbook///')).toEqual({ mount: 'by-kind', args: ['playbook'] })
410
410
  })
411
411
 
412
- // Lone-slash variants a path of only slashes normalises to an empty
412
+ // Lone-slash variants — a path of only slashes normalises to an empty
413
413
  // segments list and must throw the root-with-no-mount error.
414
414
  it('throws on path consisting only of slashes', () => {
415
415
  expect(() => parsePath('///')).toThrow('parsePath: path resolves to root with no mount')
416
416
  })
417
417
 
418
- // Case sensitivity parsePath does not normalise case.
418
+ // Case sensitivity — parsePath does not normalise case.
419
419
  // 'Playbook' (capitalised) is passed through as-is to the caller.
420
420
  it('preserves node-id case (no case normalisation)', () => {
421
421
  // /by-kind takes rest[0] verbatim
@@ -423,16 +423,16 @@ describe('parsePath edge cases', () => {
423
423
  expect(result.args[0]).toBe('Playbook')
424
424
  })
425
425
 
426
- // Feature ID: dot notation passes through unchanged.
427
- it('/by-feature/sales.crm preserves dot notation', () => {
428
- expect(parsePath('/by-feature/sales.crm')).toEqual({ mount: 'by-feature', args: ['sales.crm'] })
426
+ // system id: dot notation passes through unchanged.
427
+ it('/by-system/sales.crm preserves dot notation', () => {
428
+ expect(parsePath('/by-system/sales.crm')).toEqual({ mount: 'by-system', args: ['sales.crm'] })
429
429
  })
430
430
 
431
- // Feature ID: slash notation joins rest segments with '/'.
432
- // The parser does NOT convert slashes to dots the caller must normalise.
433
- it('/by-feature/sales/crm joins segments with slash (no dot conversion)', () => {
434
- const result = parsePath('/by-feature/sales/crm')
435
- expect(result.mount).toBe('by-feature')
431
+ // system id: slash notation joins rest segments with '/'.
432
+ // The parser does NOT convert slashes to dots — the caller must normalise.
433
+ it('/by-system/sales/crm joins segments with slash (no dot conversion)', () => {
434
+ const result = parsePath('/by-system/sales/crm')
435
+ expect(result.mount).toBe('by-system')
436
436
  // args[0] is 'sales/crm', NOT 'sales.crm'
437
437
  expect(result.args[0]).toBe('sales/crm')
438
438
  })
@@ -446,7 +446,7 @@ describe('parsePath edge cases', () => {
446
446
  expect(result.args).toHaveLength(1)
447
447
  })
448
448
 
449
- // /by-owner joins rest with '/' (same behaviour as /by-feature).
449
+ // /by-owner joins rest with '/' (same behaviour as /by-system).
450
450
  it('/by-owner/role.ops-lead/sub joins segments with slash', () => {
451
451
  const result = parsePath('/by-owner/role.ops-lead/sub')
452
452
  expect(result.mount).toBe('by-owner')
@@ -454,7 +454,7 @@ describe('parsePath edge cases', () => {
454
454
  })
455
455
 
456
456
  // Graph node ID may contain a colon prefix (e.g. knowledge:knowledge.foo).
457
- // The parser accepts it the rest before the verb is joined with '/'.
457
+ // The parser accepts it — the rest before the verb is joined with '/'.
458
458
  it('/graph/<prefixed-nodeId>/governs round-trips the prefixed id', () => {
459
459
  const result = parsePath('/graph/knowledge:knowledge.test-node/governs')
460
460
  expect(result.mount).toBe('graph')
@@ -467,7 +467,7 @@ describe('parsePath edge cases', () => {
467
467
  expect(() => parsePath('/graph')).toThrow('/graph requires <nodeId>/<verb>')
468
468
  })
469
469
 
470
- // /node mount single-segment path works for any non-reserved first segment.
470
+ // /node mount — single-segment path works for any non-reserved first segment.
471
471
  it('single-segment non-reserved path is treated as node mount', () => {
472
472
  expect(parsePath('/knowledge.my-doc')).toEqual({ mount: 'node', args: ['knowledge.my-doc'] })
473
473
  })
@@ -504,7 +504,7 @@ describe('formatText', () => {
504
504
  })
505
505
 
506
506
  // ---------------------------------------------------------------------------
507
- // formatJson envelope shape
507
+ // formatJson — envelope shape
508
508
  // ---------------------------------------------------------------------------
509
509
 
510
510
  describe('formatJson', () => {
@@ -538,8 +538,8 @@ describe('formatJson', () => {
538
538
 
539
539
  it('preserves args array in envelope', () => {
540
540
  const raw = formatJson({
541
- path: '/by-feature/sales.crm',
542
- parsed: { mount: 'by-feature', args: ['sales.crm'] },
541
+ path: '/by-system/sales.crm',
542
+ parsed: { mount: 'by-system', args: ['sales.crm'] },
543
543
  results: []
544
544
  })
545
545
  expect(JSON.parse(raw)).toMatchObject({ args: ['sales.crm'] })
@@ -549,13 +549,13 @@ describe('formatJson', () => {
549
549
  const raw = formatJson({
550
550
  path: '/graph/knowledge.playbook-a/governs',
551
551
  parsed: { mount: 'graph', args: ['knowledge.playbook-a', 'governs'] },
552
- results: ['feature:sales.crm', 'feature:sales.lead-gen']
552
+ results: ['system:sales.crm', 'system:sales.lead-gen']
553
553
  })
554
554
  const parsed = JSON.parse(raw) as { results: string[] }
555
- expect(parsed.results).toEqual(['feature:sales.crm', 'feature:sales.lead-gen'])
555
+ expect(parsed.results).toEqual(['system:sales.crm', 'system:sales.lead-gen'])
556
556
  })
557
557
 
558
- it('envelope is NOT flat { results } must have path/mount/args siblings', () => {
558
+ it('envelope is NOT flat { results } — must have path/mount/args siblings', () => {
559
559
  const raw = formatJson({
560
560
  path: '/by-kind/playbook',
561
561
  parsed: { mount: 'by-kind', args: ['playbook'] },
@@ -587,7 +587,8 @@ describe('formatIdsOnly', () => {
587
587
  })
588
588
 
589
589
  it('returns newline-separated strings for string[] (governs results)', () => {
590
- const output = formatIdsOnly(['feature:sales.crm', 'feature:sales.lead-gen'])
591
- expect(output).toBe('feature:sales.crm\nfeature:sales.lead-gen')
590
+ const output = formatIdsOnly(['system:sales.crm', 'system:sales.lead-gen'])
591
+ expect(output).toBe('system:sales.crm\nsystem:sales.lead-gen')
592
592
  })
593
593
  })
594
+
@@ -1,4 +1,4 @@
1
- import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
1
+ import type { OrgKnowledgeNode } from '../organization-model/domains/knowledge'
2
2
  import type { KnowledgeMount, ParsedKnowledgePath } from './queries'
3
3
 
4
4
  // ---------------------------------------------------------------------------
@@ -9,7 +9,7 @@ import type { KnowledgeMount, ParsedKnowledgePath } from './queries'
9
9
  * Renders a list of `OrgKnowledgeNode` results as a human-friendly text table.
10
10
  *
11
11
  * Output format (one row per node):
12
- * `<kind> <id> <title> <summary (truncated to 80 chars)>`
12
+ * `<kind> <id> <title> — <summary (truncated to 80 chars)>`
13
13
  *
14
14
  * Returns `"(no results)"` when the array is empty.
15
15
  */
@@ -26,7 +26,7 @@ export function formatText(results: OrgKnowledgeNode[]): string {
26
26
 
27
27
  const rows = results.map((n) => {
28
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}`
29
+ return `${n.kind.padEnd(kindWidth)} ${n.id.padEnd(idWidth)} ${n.title} — ${summary}`
30
30
  })
31
31
 
32
32
  return [header, divider, ...rows].join('\n')
@@ -41,10 +41,10 @@ export function formatText(results: OrgKnowledgeNode[]): string {
41
41
  *
42
42
  * Shape: `{ path, mount, args, results }`
43
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)
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
48
  */
49
49
  export interface KnowledgeJsonEnvelope {
50
50
  path: string
@@ -57,10 +57,10 @@ export interface KnowledgeJsonEnvelope {
57
57
  * Formats query results as a wrapped JSON envelope string.
58
58
  *
59
59
  * The envelope shape is `{ path, mount, args, results }`. This is intentionally
60
- * NOT flat `{ results }` consumers (agent skills, jq pipelines) need the
60
+ * NOT flat `{ results }` — consumers (agent skills, jq pipelines) need the
61
61
  * mount + args to know how to interpret the results array.
62
62
  *
63
- * @param input.path - The original path string (e.g. `"/by-feature/sales.crm"`).
63
+ * @param input.path - The original path string (e.g. `"/by-system/sales.crm"`).
64
64
  * @param input.mount - The resolved mount axis.
65
65
  * @param input.args - The parsed argument array.
66
66
  * @param input.results - The query results.
@@ -97,3 +97,4 @@ export function formatIdsOnly(results: OrgKnowledgeNode[] | string[]): string {
97
97
  const ids = results.map((r) => (typeof r === 'string' ? r : r.id))
98
98
  return ids.join('\n')
99
99
  }
100
+
@@ -1,4 +1,4 @@
1
- export { byFeature, byKind, byOwner, governs, governedBy, parsePath } from './queries'
1
+ export { bySystem, byKind, byOwner, governs, governedBy, parsePath } from './queries'
2
2
  export type { KnowledgeMount, ParsedKnowledgePath } from './queries'
3
3
 
4
4
  export { formatText, formatJson, formatIdsOnly } from './format'
@@ -1,4 +1,4 @@
1
- export { byFeature, byKind, byOwner, governs, governedBy, parsePath } from './queries'
1
+ export { bySystem, byKind, byOwner, governs, governedBy, parsePath } from './queries'
2
2
  export type { KnowledgeMount, ParsedKnowledgePath } from './queries'
3
3
 
4
4
  export { formatText, formatJson, formatIdsOnly } from './format'
@@ -1,4 +1,4 @@
1
- import type { OrganizationGraph } from '../organization-model/graph/types'
1
+ import type { OrganizationGraph } from '../organization-model/graph/types'
2
2
  import type { OrgKnowledgeKind, OrgKnowledgeNode } from '../organization-model/domains/knowledge'
3
3
 
4
4
  // ---------------------------------------------------------------------------
@@ -35,17 +35,17 @@ function buildKnowledgeSourceIdMap(graph: OrganizationGraph): Map<string, string
35
35
 
36
36
  /**
37
37
  * Returns all knowledge nodes whose `governs` edges point to the given
38
- * featureId (graph node ID: `feature:<featureId>`).
38
+ * systemId (graph node ID: `system:<systemId>`).
39
39
  *
40
40
  * @param graph - The built OrganizationGraph.
41
- * @param featureId - The dotted feature id (e.g. `sales.crm`).
41
+ * @param systemId - The dotted system id (e.g. `sales.crm`).
42
42
  */
43
- export function byFeature(
43
+ export function bySystem(
44
44
  graph: OrganizationGraph,
45
- featureId: string,
45
+ systemId: string,
46
46
  knowledgeNodes: OrgKnowledgeNode[]
47
47
  ): OrgKnowledgeNode[] {
48
- const targetGraphNodeId = `feature:${featureId}`
48
+ const targetGraphNodeId = `system:${systemId}`
49
49
 
50
50
  // Find all knowledge graph node IDs that have a `governs` edge to the target
51
51
  const governingKnowledgeNodeIds = new Set<string>()
@@ -107,7 +107,7 @@ export function byOwner(
107
107
  * @param graph - The built OrganizationGraph.
108
108
  * @param nodeId - The OM knowledge node id (e.g. `knowledge.outreach-playbook`).
109
109
  * Also accepts the graph node ID format (`knowledge:knowledge.outreach-playbook`).
110
- * @returns Array of target graph node IDs (e.g. `['feature:sales.crm', ...]`).
110
+ * @returns Array of target graph node IDs (e.g. `['system:sales.crm', ...]`).
111
111
  */
112
112
  export function governs(graph: OrganizationGraph, nodeId: string): string[] {
113
113
  const graphNodeId = nodeId.startsWith('knowledge:') ? nodeId : toGraphNodeId(nodeId)
@@ -126,16 +126,16 @@ export function governs(graph: OrganizationGraph, nodeId: string): string[] {
126
126
  * (incoming `governs` edges pointing to `nodeId`).
127
127
  *
128
128
  * @param graph - The built OrganizationGraph.
129
- * @param nodeId - The target graph node ID (e.g. `feature:sales.crm`) or a
130
- * bare feature id (e.g. `sales.crm` will be prefixed with `feature:`).
129
+ * @param nodeId - The target graph node ID (e.g. `system:sales.crm`) or a
130
+ * bare system id (e.g. `sales.crm` — will be prefixed with `system:`).
131
131
  * @returns Array of source graph node IDs (e.g. `['knowledge:knowledge.outreach-playbook', ...]`).
132
132
  */
133
133
  export function governedBy(graph: OrganizationGraph, nodeId: string): string[] {
134
- // Accept both `feature:sales.crm` and bare `sales.crm`
134
+ // Accept both `system:sales.crm` and bare `sales.crm`
135
135
  const targetId =
136
- nodeId.startsWith('feature:') || nodeId.startsWith('knowledge:') || nodeId.startsWith('resource:')
136
+ nodeId.startsWith('system:') || nodeId.startsWith('knowledge:') || nodeId.startsWith('resource:')
137
137
  ? nodeId
138
- : `feature:${nodeId}`
138
+ : `system:${nodeId}`
139
139
 
140
140
  const results: string[] = []
141
141
  for (const edge of graph.edges) {
@@ -151,7 +151,7 @@ export function governedBy(graph: OrganizationGraph, nodeId: string): string[] {
151
151
  // ---------------------------------------------------------------------------
152
152
 
153
153
  /** The recognized mount axes for Knowledge Map paths. */
154
- export type KnowledgeMount = 'by-feature' | 'by-kind' | 'by-owner' | 'graph' | 'node'
154
+ export type KnowledgeMount = 'by-system' | 'by-kind' | 'by-owner' | 'graph' | 'node'
155
155
 
156
156
  /**
157
157
  * The result of parsing a Knowledge Map path string.
@@ -159,7 +159,7 @@ export type KnowledgeMount = 'by-feature' | 'by-kind' | 'by-owner' | 'graph' | '
159
159
  * Shape: `{ mount: KnowledgeMount, args: string[] }`
160
160
  *
161
161
  * Per-mount arg arrays:
162
- * - `by-feature`: `[featureId]` (e.g. `['sales.crm']`)
162
+ * - `by-system`: `[systemId]` (e.g. `['sales.crm']`)
163
163
  * - `by-kind`: `[kind]` (e.g. `['playbook']`)
164
164
  * - `by-owner`: `[ownerId]` (e.g. `['role.ops-lead']`)
165
165
  * - `graph`: `[nodeId, verb]` where verb is `'governs'` or `'governed-by'`
@@ -174,12 +174,12 @@ export interface ParsedKnowledgePath {
174
174
  * Parses a Knowledge Map path string into a `{ mount, args }` descriptor.
175
175
  *
176
176
  * Supported path patterns:
177
- * `/by-feature/<featureId>` `{ mount: 'by-feature', args: ['<featureId>'] }`
178
- * `/by-kind/<kind>` `{ mount: 'by-kind', args: ['<kind>'] }`
179
- * `/by-owner/<ownerId>` `{ mount: 'by-owner', args: ['<ownerId>'] }`
180
- * `/graph/<nodeId>/governs` `{ mount: 'graph', args: ['<nodeId>', 'governs'] }`
181
- * `/graph/<nodeId>/governed-by` `{ mount: 'graph', args: ['<nodeId>', 'governed-by'] }`
182
- * `/<nodeId>` `{ mount: 'node', args: ['<nodeId>'] }`
177
+ * `/by-system/<systemId>` -> `{ mount: 'by-system', args: ['<systemId>'] }`
178
+ * `/by-kind/<kind>` → `{ mount: 'by-kind', args: ['<kind>'] }`
179
+ * `/by-owner/<ownerId>` → `{ mount: 'by-owner', args: ['<ownerId>'] }`
180
+ * `/graph/<nodeId>/governs` → `{ mount: 'graph', args: ['<nodeId>', 'governs'] }`
181
+ * `/graph/<nodeId>/governed-by` → `{ mount: 'graph', args: ['<nodeId>', 'governed-by'] }`
182
+ * `/<nodeId>` → `{ mount: 'node', args: ['<nodeId>'] }`
183
183
  *
184
184
  * The path MUST start with `/`. Trailing slashes are stripped before parsing.
185
185
  *
@@ -205,12 +205,12 @@ export function parsePath(pathString: string): ParsedKnowledgePath {
205
205
 
206
206
  const [first, ...rest] = segments
207
207
 
208
- // /by-feature/<featureId>
209
- if (first === 'by-feature') {
208
+ // /by-system/<systemId>
209
+ if (first === 'by-system') {
210
210
  if (rest.length === 0) {
211
- throw new Error(`parsePath: /by-feature requires a featureId argument, got: "${pathString}"`)
211
+ throw new Error(`parsePath: /by-system requires a systemId argument, got: "${pathString}"`)
212
212
  }
213
- return { mount: 'by-feature', args: [rest.join('/')] }
213
+ return { mount: 'by-system', args: [rest.join('/')] }
214
214
  }
215
215
 
216
216
  // /by-kind/<kind>
@@ -251,6 +251,7 @@ export function parsePath(pathString: string): ParsedKnowledgePath {
251
251
  }
252
252
 
253
253
  throw new Error(
254
- `parsePath: unrecognized path pattern "${pathString}". Supported: /by-feature/<id>, /by-kind/<kind>, /by-owner/<id>, /graph/<nodeId>/governs, /graph/<nodeId>/governed-by, /<nodeId>`
254
+ `parsePath: unrecognized path pattern "${pathString}". Supported: /by-system/<id>, /by-kind/<kind>, /by-owner/<id>, /graph/<nodeId>/governs, /graph/<nodeId>/governed-by, /<nodeId>`
255
255
  )
256
256
  }
257
+