@synap-core/workspace-templates 0.7.0 → 0.7.2
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.
- package/dist/index.d.ts +172 -1
- package/dist/index.js +370 -28
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/business-developer.yaml +8 -0
- package/src/catalog.test.ts +2 -2
- package/src/composition-graph.test.ts +135 -0
- package/src/composition-graph.ts +298 -0
- package/src/concept-guard.test.ts +176 -0
- package/src/concept-guard.ts +219 -0
- package/src/ecosystem.yaml +4 -1
- package/src/generate.d.mts +15 -0
- package/src/generate.mjs +86 -0
- package/src/index.ts +24 -0
- package/src/meetings-knowledge.yaml +136 -22
- package/src/outreach-comms.yaml +5 -2
- package/src/templates-freshness.test.ts +55 -0
- package/src/templates.ts +126 -28
package/dist/index.js
CHANGED
|
@@ -6798,6 +6798,7 @@ var DATA = {
|
|
|
6798
6798
|
"description": "A person or company actively being qualified or contacted by this CRM workspace.",
|
|
6799
6799
|
"scope": "WORKSPACE",
|
|
6800
6800
|
"profileKind": "role",
|
|
6801
|
+
"semanticSlug": "crm.lead",
|
|
6801
6802
|
"applicableKinds": [
|
|
6802
6803
|
"person",
|
|
6803
6804
|
"company"
|
|
@@ -6905,6 +6906,7 @@ var DATA = {
|
|
|
6905
6906
|
"description": "A CRM lead converted by a won commercial proposal and ready for handoff.",
|
|
6906
6907
|
"scope": "WORKSPACE",
|
|
6907
6908
|
"profileKind": "role",
|
|
6909
|
+
"semanticSlug": "crm.client",
|
|
6908
6910
|
"applicableKinds": [
|
|
6909
6911
|
"person",
|
|
6910
6912
|
"company"
|
|
@@ -13260,7 +13262,7 @@ var DATA = {
|
|
|
13260
13262
|
]
|
|
13261
13263
|
},
|
|
13262
13264
|
"bento": {
|
|
13263
|
-
"viewName": "Ecosystem
|
|
13265
|
+
"viewName": "Ecosystem Home",
|
|
13264
13266
|
"blocks": [
|
|
13265
13267
|
{
|
|
13266
13268
|
"kind": "view",
|
|
@@ -22507,18 +22509,92 @@ var DATA = {
|
|
|
22507
22509
|
},
|
|
22508
22510
|
"profiles": [
|
|
22509
22511
|
{
|
|
22510
|
-
"slug": "
|
|
22511
|
-
"displayName": "
|
|
22512
|
-
"icon": "
|
|
22512
|
+
"slug": "person",
|
|
22513
|
+
"displayName": "Person",
|
|
22514
|
+
"icon": "user",
|
|
22515
|
+
"color": "amber",
|
|
22516
|
+
"description": "A meeting attendee.",
|
|
22517
|
+
"properties": [
|
|
22518
|
+
{
|
|
22519
|
+
"slug": "email",
|
|
22520
|
+
"label": "Email",
|
|
22521
|
+
"valueType": "string",
|
|
22522
|
+
"inputType": "email"
|
|
22523
|
+
}
|
|
22524
|
+
]
|
|
22525
|
+
},
|
|
22526
|
+
{
|
|
22527
|
+
"slug": "task",
|
|
22528
|
+
"displayName": "Task",
|
|
22529
|
+
"icon": "check-square",
|
|
22530
|
+
"color": "blue",
|
|
22531
|
+
"description": "An action item that came out of a meeting.",
|
|
22532
|
+
"properties": [
|
|
22533
|
+
{
|
|
22534
|
+
"slug": "status",
|
|
22535
|
+
"label": "Status",
|
|
22536
|
+
"valueType": "string",
|
|
22537
|
+
"inputType": "select",
|
|
22538
|
+
"enumValues": [
|
|
22539
|
+
"todo",
|
|
22540
|
+
"in-progress",
|
|
22541
|
+
"done",
|
|
22542
|
+
"cancelled"
|
|
22543
|
+
],
|
|
22544
|
+
"constraints": {
|
|
22545
|
+
"defaultValue": "todo"
|
|
22546
|
+
}
|
|
22547
|
+
},
|
|
22548
|
+
{
|
|
22549
|
+
"slug": "dueDate",
|
|
22550
|
+
"label": "Due",
|
|
22551
|
+
"valueType": "date",
|
|
22552
|
+
"inputType": "date"
|
|
22553
|
+
}
|
|
22554
|
+
]
|
|
22555
|
+
},
|
|
22556
|
+
{
|
|
22557
|
+
"slug": "note",
|
|
22558
|
+
"displayName": "Note",
|
|
22559
|
+
"icon": "file-text",
|
|
22513
22560
|
"color": "violet",
|
|
22514
|
-
"description": "A
|
|
22561
|
+
"description": "A note or summary captured from a meeting.",
|
|
22515
22562
|
"properties": [
|
|
22516
22563
|
{
|
|
22517
|
-
"slug": "
|
|
22518
|
-
"label": "
|
|
22564
|
+
"slug": "content",
|
|
22565
|
+
"label": "Content",
|
|
22566
|
+
"valueType": "string",
|
|
22567
|
+
"inputType": "textarea"
|
|
22568
|
+
}
|
|
22569
|
+
]
|
|
22570
|
+
},
|
|
22571
|
+
{
|
|
22572
|
+
"slug": "event",
|
|
22573
|
+
"displayName": "Event",
|
|
22574
|
+
"icon": "calendar",
|
|
22575
|
+
"color": "sky",
|
|
22576
|
+
"description": "A calendar event \u2014 the thing a meeting actually is.",
|
|
22577
|
+
"properties": [
|
|
22578
|
+
{
|
|
22579
|
+
"slug": "startDate",
|
|
22580
|
+
"label": "Start",
|
|
22519
22581
|
"valueType": "date",
|
|
22520
22582
|
"inputType": "date"
|
|
22521
|
-
}
|
|
22583
|
+
}
|
|
22584
|
+
]
|
|
22585
|
+
},
|
|
22586
|
+
{
|
|
22587
|
+
"slug": "meeting",
|
|
22588
|
+
"displayName": "Meeting",
|
|
22589
|
+
"icon": "video",
|
|
22590
|
+
"color": "violet",
|
|
22591
|
+
"scope": "shared",
|
|
22592
|
+
"profileKind": "role",
|
|
22593
|
+
"applicableKinds": [
|
|
22594
|
+
"event"
|
|
22595
|
+
],
|
|
22596
|
+
"description": "The meeting hat an event wears \u2014 platform, transcript, and what came out of it.",
|
|
22597
|
+
"properties": [
|
|
22522
22598
|
{
|
|
22523
22599
|
"slug": "platform",
|
|
22524
22600
|
"label": "Platform",
|
|
@@ -22532,12 +22608,6 @@ var DATA = {
|
|
|
22532
22608
|
"other"
|
|
22533
22609
|
]
|
|
22534
22610
|
},
|
|
22535
|
-
{
|
|
22536
|
-
"slug": "attendees",
|
|
22537
|
-
"label": "Attendees",
|
|
22538
|
-
"valueType": "array",
|
|
22539
|
-
"inputType": "text"
|
|
22540
|
-
},
|
|
22541
22611
|
{
|
|
22542
22612
|
"slug": "status",
|
|
22543
22613
|
"label": "Status",
|
|
@@ -22577,19 +22647,44 @@ var DATA = {
|
|
|
22577
22647
|
"config": {
|
|
22578
22648
|
"columns": [
|
|
22579
22649
|
"title",
|
|
22580
|
-
"
|
|
22650
|
+
"startDate",
|
|
22581
22651
|
"platform",
|
|
22582
22652
|
"status"
|
|
22583
22653
|
]
|
|
22654
|
+
}
|
|
22655
|
+
},
|
|
22656
|
+
{
|
|
22657
|
+
"name": "Needs Processing",
|
|
22658
|
+
"slug": "meetings-unprocessed",
|
|
22659
|
+
"type": "list",
|
|
22660
|
+
"scopeProfileSlug": "meeting",
|
|
22661
|
+
"description": "Meetings that happened but haven't been imported and summarized yet.",
|
|
22662
|
+
"config": {
|
|
22663
|
+
"filterBy": {
|
|
22664
|
+
"field": "status",
|
|
22665
|
+
"operator": "is",
|
|
22666
|
+
"value": "completed"
|
|
22667
|
+
}
|
|
22584
22668
|
},
|
|
22585
22669
|
"defaultView": true
|
|
22586
22670
|
},
|
|
22671
|
+
{
|
|
22672
|
+
"name": "Meeting Notes",
|
|
22673
|
+
"slug": "meeting-notes",
|
|
22674
|
+
"type": "list",
|
|
22675
|
+
"scopeProfileSlug": "note",
|
|
22676
|
+
"description": "Summaries and notes produced from meetings."
|
|
22677
|
+
},
|
|
22587
22678
|
{
|
|
22588
22679
|
"name": "Meetings Calendar",
|
|
22589
22680
|
"slug": "meetings-calendar",
|
|
22590
22681
|
"type": "calendar",
|
|
22591
22682
|
"scopeProfileSlug": "meeting",
|
|
22592
|
-
"description": "Meetings on a calendar, keyed on their date."
|
|
22683
|
+
"description": "Meetings on a calendar, keyed on their date.",
|
|
22684
|
+
"config": {
|
|
22685
|
+
"dateField": "properties.startDate",
|
|
22686
|
+
"colorBy": "status"
|
|
22687
|
+
}
|
|
22593
22688
|
},
|
|
22594
22689
|
{
|
|
22595
22690
|
"name": "Action Items",
|
|
@@ -22673,27 +22768,29 @@ var DATA = {
|
|
|
22673
22768
|
],
|
|
22674
22769
|
"blindSpots": [
|
|
22675
22770
|
`Recurring meetings (standups, 1:1s) get skipped because they feel routine \u2014 but their action items are exactly what falls through the cracks. Don't let "it's just a standup" excuse skipping capture.
|
|
22676
|
-
|
|
22771
|
+
`,
|
|
22772
|
+
`"Attendees" as a list of names is not attendee capture \u2014 it's a string. If the summary says "Sarah will send the deck" and Sarah isn't a tracked person linked to that task, the next meeting with Sarah starts from zero again. Resolve every attendee to a real person, or say out loud that you're choosing not to.
|
|
22773
|
+
`,
|
|
22774
|
+
"A summary nobody re-reads is theatre. The test for a good meeting note is whether someone who missed the call could act from it alone \u2014 if it needs the transcript to make sense, it isn't a summary yet.\n"
|
|
22677
22775
|
],
|
|
22678
|
-
"bar":
|
|
22776
|
+
"bar": 'The PIPE is live, not the rows: transcript source and calendar connected, the Import & Summarize playbook run once end-to-end on a real recent meeting, and the output inspected together \u2014 a note a human would actually keep, tasks that are genuinely actionable, and attendees resolved to tracked people rather than name strings. If the first import produces vague tasks ("follow up"), fix the extraction bar now, not after fifty meetings.\n'
|
|
22679
22777
|
},
|
|
22680
22778
|
"collect": [
|
|
22681
22779
|
{
|
|
22682
22780
|
"profileSlug": "meeting",
|
|
22683
|
-
"what": "
|
|
22781
|
+
"what": "Which recurring meetings are worth importing every time \u2014 the standing 1:1s, the weekly ops call. A watchlist, not a backlog.\n",
|
|
22684
22782
|
"cardinality": "few",
|
|
22685
22783
|
"keyFields": [
|
|
22686
|
-
"meetingDate",
|
|
22687
22784
|
"platform"
|
|
22688
22785
|
]
|
|
22689
22786
|
}
|
|
22690
22787
|
],
|
|
22691
22788
|
"openingQuestions": [
|
|
22692
|
-
"
|
|
22693
|
-
"
|
|
22694
|
-
"
|
|
22789
|
+
"Name a meeting from last week whose outcome you can't remember. What did that cost you?",
|
|
22790
|
+
"When a meeting produces an action item, where does it go today \u2014 and does it survive the week?",
|
|
22791
|
+
"Which recurring meeting would you cancel if you could see its action-item completion rate?"
|
|
22695
22792
|
],
|
|
22696
|
-
"doneWhen": "
|
|
22793
|
+
"doneWhen": "Transcript source connected and one real meeting imported end-to-end \u2014 summary note, at least one action item, and attendees resolved to people.\n"
|
|
22697
22794
|
},
|
|
22698
22795
|
"app": {
|
|
22699
22796
|
"kind": "stored",
|
|
@@ -22704,7 +22801,8 @@ var DATA = {
|
|
|
22704
22801
|
"defaultApp": null,
|
|
22705
22802
|
"pinnedApps": [
|
|
22706
22803
|
"dashboard",
|
|
22707
|
-
"data"
|
|
22804
|
+
"data",
|
|
22805
|
+
"chat"
|
|
22708
22806
|
],
|
|
22709
22807
|
"sidebarItems": [
|
|
22710
22808
|
{
|
|
@@ -22734,7 +22832,7 @@ var DATA = {
|
|
|
22734
22832
|
]
|
|
22735
22833
|
},
|
|
22736
22834
|
"bento": {
|
|
22737
|
-
"viewName": "Meetings",
|
|
22835
|
+
"viewName": "Meetings Home",
|
|
22738
22836
|
"blocks": [
|
|
22739
22837
|
{
|
|
22740
22838
|
"kind": "view",
|
|
@@ -23458,7 +23556,8 @@ var DATA = {
|
|
|
23458
23556
|
"defaultApp": null,
|
|
23459
23557
|
"pinnedApps": [
|
|
23460
23558
|
"dashboard",
|
|
23461
|
-
"data"
|
|
23559
|
+
"data",
|
|
23560
|
+
"chat"
|
|
23462
23561
|
],
|
|
23463
23562
|
"sidebarItems": [
|
|
23464
23563
|
{
|
|
@@ -23482,7 +23581,7 @@ var DATA = {
|
|
|
23482
23581
|
]
|
|
23483
23582
|
},
|
|
23484
23583
|
"bento": {
|
|
23485
|
-
"viewName": "
|
|
23584
|
+
"viewName": "Outreach Home",
|
|
23486
23585
|
"blocks": [
|
|
23487
23586
|
{
|
|
23488
23587
|
"kind": "view",
|
|
@@ -26876,6 +26975,243 @@ function computeTemplateUpdates(installed, catalog, bundleVersion) {
|
|
|
26876
26975
|
});
|
|
26877
26976
|
}
|
|
26878
26977
|
|
|
26978
|
+
// src/composition-graph.ts
|
|
26979
|
+
function bundledEdgesOf(slug) {
|
|
26980
|
+
return getWorkspaceTemplate(slug)?.dependencies ?? [];
|
|
26981
|
+
}
|
|
26982
|
+
function layerTemplateGraph(input) {
|
|
26983
|
+
const { roots, edgesOf } = input;
|
|
26984
|
+
const nameOf = input.nameOf ?? (() => void 0);
|
|
26985
|
+
const rootSet = new Set(roots);
|
|
26986
|
+
const installedBySlug = /* @__PURE__ */ new Map();
|
|
26987
|
+
for (const entry of input.installed ?? []) {
|
|
26988
|
+
const list = installedBySlug.get(entry.slug);
|
|
26989
|
+
if (list) list.push(entry);
|
|
26990
|
+
else installedBySlug.set(entry.slug, [entry]);
|
|
26991
|
+
}
|
|
26992
|
+
const updatesBySlug = new Map((input.updates ?? []).map((u) => [u.slug, u]));
|
|
26993
|
+
const edges = [];
|
|
26994
|
+
const cycles = [];
|
|
26995
|
+
const touched = new Set(roots);
|
|
26996
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
26997
|
+
const dependentsMap = /* @__PURE__ */ new Map();
|
|
26998
|
+
const composeEdges = [];
|
|
26999
|
+
const discovered = /* @__PURE__ */ new Set();
|
|
27000
|
+
function addAdjacency(from, to) {
|
|
27001
|
+
const set = adjacency.get(from);
|
|
27002
|
+
if (set) set.add(to);
|
|
27003
|
+
else adjacency.set(from, /* @__PURE__ */ new Set([to]));
|
|
27004
|
+
}
|
|
27005
|
+
function addDependent(dependency, dependent) {
|
|
27006
|
+
const set = dependentsMap.get(dependency);
|
|
27007
|
+
if (set) set.add(dependent);
|
|
27008
|
+
else dependentsMap.set(dependency, /* @__PURE__ */ new Set([dependent]));
|
|
27009
|
+
}
|
|
27010
|
+
function walk(slug, ancestors) {
|
|
27011
|
+
const deps = edgesOf(slug) ?? [];
|
|
27012
|
+
for (const dep of deps) {
|
|
27013
|
+
const relation = dep.relation ?? "require";
|
|
27014
|
+
touched.add(dep.slug);
|
|
27015
|
+
if (ancestors.has(dep.slug)) {
|
|
27016
|
+
cycles.push([slug, dep.slug]);
|
|
27017
|
+
edges.push({ from: slug, to: dep.slug, relation, depKind: dep.kind });
|
|
27018
|
+
continue;
|
|
27019
|
+
}
|
|
27020
|
+
edges.push({ from: slug, to: dep.slug, relation, depKind: dep.kind });
|
|
27021
|
+
addAdjacency(slug, dep.slug);
|
|
27022
|
+
addDependent(dep.slug, slug);
|
|
27023
|
+
if (relation === "compose") {
|
|
27024
|
+
composeEdges.push({ from: slug, to: dep.slug, depKind: dep.kind });
|
|
27025
|
+
}
|
|
27026
|
+
if (!discovered.has(dep.slug)) {
|
|
27027
|
+
discovered.add(dep.slug);
|
|
27028
|
+
const nextAncestors = new Set(ancestors);
|
|
27029
|
+
nextAncestors.add(slug);
|
|
27030
|
+
walk(dep.slug, nextAncestors);
|
|
27031
|
+
}
|
|
27032
|
+
}
|
|
27033
|
+
}
|
|
27034
|
+
for (const root of roots) {
|
|
27035
|
+
if (discovered.has(root)) continue;
|
|
27036
|
+
discovered.add(root);
|
|
27037
|
+
walk(root, /* @__PURE__ */ new Set([root]));
|
|
27038
|
+
}
|
|
27039
|
+
const composeSources = new Set(composeEdges.map((e) => e.from));
|
|
27040
|
+
const overlaysByBase = /* @__PURE__ */ new Map();
|
|
27041
|
+
for (const e of composeEdges) {
|
|
27042
|
+
const isInstalled = installedBySlug.has(e.from);
|
|
27043
|
+
const overlay = { slug: e.from, name: nameOf(e.from) ?? e.from, installed: isInstalled };
|
|
27044
|
+
const list = overlaysByBase.get(e.to);
|
|
27045
|
+
if (list) list.push(overlay);
|
|
27046
|
+
else overlaysByBase.set(e.to, [overlay]);
|
|
27047
|
+
}
|
|
27048
|
+
const nodeSlugs = [...touched].filter((s) => !composeSources.has(s)).sort();
|
|
27049
|
+
const rootsReaching = /* @__PURE__ */ new Map();
|
|
27050
|
+
for (const root of roots) {
|
|
27051
|
+
const visited = /* @__PURE__ */ new Set([root]);
|
|
27052
|
+
const queue = [root];
|
|
27053
|
+
const addReach = (s) => {
|
|
27054
|
+
const set = rootsReaching.get(s);
|
|
27055
|
+
if (set) set.add(root);
|
|
27056
|
+
else rootsReaching.set(s, /* @__PURE__ */ new Set([root]));
|
|
27057
|
+
};
|
|
27058
|
+
addReach(root);
|
|
27059
|
+
while (queue.length) {
|
|
27060
|
+
const cur = queue.shift();
|
|
27061
|
+
for (const next of adjacency.get(cur) ?? []) {
|
|
27062
|
+
if (visited.has(next)) continue;
|
|
27063
|
+
visited.add(next);
|
|
27064
|
+
addReach(next);
|
|
27065
|
+
queue.push(next);
|
|
27066
|
+
}
|
|
27067
|
+
}
|
|
27068
|
+
}
|
|
27069
|
+
const layerMemo = /* @__PURE__ */ new Map();
|
|
27070
|
+
function layerOf(slug, stack) {
|
|
27071
|
+
if (rootSet.has(slug)) return 0;
|
|
27072
|
+
const memoized = layerMemo.get(slug);
|
|
27073
|
+
if (memoized !== void 0) return memoized;
|
|
27074
|
+
if (stack.has(slug)) return 0;
|
|
27075
|
+
stack.add(slug);
|
|
27076
|
+
const dependents = dependentsMap.get(slug);
|
|
27077
|
+
const layer = !dependents || dependents.size === 0 ? 0 : 1 + Math.max(...[...dependents].map((d) => layerOf(d, stack)));
|
|
27078
|
+
stack.delete(slug);
|
|
27079
|
+
layerMemo.set(slug, layer);
|
|
27080
|
+
return layer;
|
|
27081
|
+
}
|
|
27082
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
27083
|
+
for (const slug of nodeSlugs) {
|
|
27084
|
+
const installedEntries = installedBySlug.get(slug) ?? [];
|
|
27085
|
+
const workspaceIds = installedEntries.map((e) => e.workspaceId).filter(Boolean);
|
|
27086
|
+
const installed = workspaceIds.length > 0;
|
|
27087
|
+
const update = updatesBySlug.get(slug);
|
|
27088
|
+
const installedVersion = update?.installedVersion || installedEntries[0]?.version;
|
|
27089
|
+
const noVersionInfo = installed && (update === void 0 || update.latestVersion === void 0);
|
|
27090
|
+
nodes.set(slug, {
|
|
27091
|
+
slug,
|
|
27092
|
+
name: nameOf(slug) ?? slug,
|
|
27093
|
+
layer: layerOf(slug, /* @__PURE__ */ new Set()),
|
|
27094
|
+
dependents: [...dependentsMap.get(slug) ?? []].sort(),
|
|
27095
|
+
roots: [...rootsReaching.get(slug) ?? []].sort(),
|
|
27096
|
+
installed,
|
|
27097
|
+
workspaceIds,
|
|
27098
|
+
installedVersion,
|
|
27099
|
+
latestVersion: update?.latestVersion ?? null,
|
|
27100
|
+
updateAvailable: update?.updateAvailable ?? false,
|
|
27101
|
+
noVersionInfo,
|
|
27102
|
+
overlays: overlaysByBase.get(slug) ?? []
|
|
27103
|
+
});
|
|
27104
|
+
}
|
|
27105
|
+
const maxLayer = Math.max(0, ...[...nodes.values()].map((n) => n.layer));
|
|
27106
|
+
const layers = Array.from({ length: maxLayer + 1 }, () => []);
|
|
27107
|
+
for (const node of nodes.values()) layers[node.layer].push(node);
|
|
27108
|
+
for (const bucket of layers) bucket.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
27109
|
+
return { nodes, edges, layers, cycles };
|
|
27110
|
+
}
|
|
27111
|
+
|
|
27112
|
+
// src/concept-guard.ts
|
|
27113
|
+
var DECLARED_DIVERGENCES = {
|
|
27114
|
+
// Two-tier lead vocabulary, documented at business-developer.yaml:1-16.
|
|
27115
|
+
// Foundation's `lead` is a SHARED pod-wide role: being a lead is true
|
|
27116
|
+
// everywhere. `crm-lead` is a WORKSPACE-scoped role so a person can be a lead
|
|
27117
|
+
// inside one CRM workspace without becoming a lead across the whole pod. The
|
|
27118
|
+
// tiers are intentionally separate rows; `semanticSlug: crm.lead` records the
|
|
27119
|
+
// kinship so the two are discoverable as one concept via getBySemanticSlug.
|
|
27120
|
+
"crm-lead|lead": "business-developer: workspace-tier lead, deliberately NOT pod-wide foundation `lead` (two-tier CRM design)",
|
|
27121
|
+
// Same reasoning for the post-sale side of the same lifecycle.
|
|
27122
|
+
"client|crm-client": "business-developer: workspace-tier client, deliberately NOT pod-wide foundation `client` (two-tier CRM design)"
|
|
27123
|
+
};
|
|
27124
|
+
function effectiveSemanticSlug(profile) {
|
|
27125
|
+
return profile.semanticSlug ?? profile.slug;
|
|
27126
|
+
}
|
|
27127
|
+
function conceptTerm(semanticSlug) {
|
|
27128
|
+
const tokens = semanticSlug.split(/[.\-_]/).filter(Boolean);
|
|
27129
|
+
return (tokens[tokens.length - 1] ?? semanticSlug).toLowerCase();
|
|
27130
|
+
}
|
|
27131
|
+
function declaredValue(profile, field) {
|
|
27132
|
+
const raw = profile[field];
|
|
27133
|
+
if (raw === void 0 || raw === null) return null;
|
|
27134
|
+
if (field === "applicableKinds") {
|
|
27135
|
+
return Array.isArray(raw) ? JSON.stringify([...raw].sort()) : null;
|
|
27136
|
+
}
|
|
27137
|
+
return JSON.stringify(String(raw).toLowerCase());
|
|
27138
|
+
}
|
|
27139
|
+
function pairKey(a, b) {
|
|
27140
|
+
return [a, b].sort().join("|");
|
|
27141
|
+
}
|
|
27142
|
+
function findConceptViolations(templates = WORKSPACE_TEMPLATES) {
|
|
27143
|
+
const violations = [];
|
|
27144
|
+
const all = [];
|
|
27145
|
+
for (const [templateSlug, template] of Object.entries(templates)) {
|
|
27146
|
+
for (const profile of template.profiles ?? []) {
|
|
27147
|
+
all.push({ template: templateSlug, profile });
|
|
27148
|
+
}
|
|
27149
|
+
}
|
|
27150
|
+
const roles = all.filter((r) => r.profile.profileKind === "role");
|
|
27151
|
+
const usedAllowlistKeys = /* @__PURE__ */ new Set();
|
|
27152
|
+
const reportedPairs = /* @__PURE__ */ new Set();
|
|
27153
|
+
for (let i = 0; i < roles.length; i++) {
|
|
27154
|
+
for (let j = i + 1; j < roles.length; j++) {
|
|
27155
|
+
const a = roles[i];
|
|
27156
|
+
const b = roles[j];
|
|
27157
|
+
if (a.template === b.template) continue;
|
|
27158
|
+
if (a.profile.slug === b.profile.slug) continue;
|
|
27159
|
+
const term = conceptTerm(effectiveSemanticSlug(a.profile));
|
|
27160
|
+
if (term !== conceptTerm(effectiveSemanticSlug(b.profile))) continue;
|
|
27161
|
+
const key = pairKey(a.profile.slug, b.profile.slug);
|
|
27162
|
+
if (key in DECLARED_DIVERGENCES) {
|
|
27163
|
+
usedAllowlistKeys.add(key);
|
|
27164
|
+
continue;
|
|
27165
|
+
}
|
|
27166
|
+
if (reportedPairs.has(key)) continue;
|
|
27167
|
+
reportedPairs.add(key);
|
|
27168
|
+
violations.push({
|
|
27169
|
+
rule: "concept-collision",
|
|
27170
|
+
concept: term,
|
|
27171
|
+
message: `two role profiles denote the concept "${term}" under different slugs: "${a.profile.slug}" (${a.template}) vs "${b.profile.slug}" (${b.template}). Either REUSE the existing slug, or \u2014 if the divergence is intentional \u2014 declare it: set an explicit \`semanticSlug\` on the new profile and add "${key}" to DECLARED_DIVERGENCES in src/concept-guard.ts with the reason.`
|
|
27172
|
+
});
|
|
27173
|
+
}
|
|
27174
|
+
}
|
|
27175
|
+
const bySemantic = /* @__PURE__ */ new Map();
|
|
27176
|
+
for (const ref of all) {
|
|
27177
|
+
const key = effectiveSemanticSlug(ref.profile);
|
|
27178
|
+
const bucket = bySemantic.get(key);
|
|
27179
|
+
if (bucket) bucket.push(ref);
|
|
27180
|
+
else bySemantic.set(key, [ref]);
|
|
27181
|
+
}
|
|
27182
|
+
for (const [semantic, group] of bySemantic) {
|
|
27183
|
+
if (new Set(group.map((r) => r.profile.slug)).size < 2) continue;
|
|
27184
|
+
for (const field of ["profileKind", "scope", "applicableKinds"]) {
|
|
27185
|
+
const claims = /* @__PURE__ */ new Map();
|
|
27186
|
+
for (const ref of group) {
|
|
27187
|
+
const value = declaredValue(ref.profile, field);
|
|
27188
|
+
if (value === null) continue;
|
|
27189
|
+
const existing = claims.get(value);
|
|
27190
|
+
if (!existing) claims.set(value, `${ref.template}:${ref.profile.slug}`);
|
|
27191
|
+
}
|
|
27192
|
+
if (claims.size > 1) {
|
|
27193
|
+
violations.push({
|
|
27194
|
+
rule: "alias-shape-divergence",
|
|
27195
|
+
concept: semantic,
|
|
27196
|
+
message: `profiles aliased to semanticSlug "${semantic}" disagree on \`${field}\`: ` + [...claims].map(([v, who]) => `${who} declares ${v}`).join(", ") + `. Aliased profiles assert one concept \u2014 make the shapes agree, or drop the alias.`
|
|
27197
|
+
});
|
|
27198
|
+
}
|
|
27199
|
+
}
|
|
27200
|
+
}
|
|
27201
|
+
for (const key of Object.keys(DECLARED_DIVERGENCES)) {
|
|
27202
|
+
if (usedAllowlistKeys.has(key)) continue;
|
|
27203
|
+
violations.push({
|
|
27204
|
+
rule: "stale-allowlist",
|
|
27205
|
+
concept: key,
|
|
27206
|
+
message: `DECLARED_DIVERGENCES entry "${key}" no longer matches any collision \u2014 the divergence was resolved or the profiles were renamed. Delete the entry.`
|
|
27207
|
+
});
|
|
27208
|
+
}
|
|
27209
|
+
return violations;
|
|
27210
|
+
}
|
|
27211
|
+
function formatConceptViolations(violations) {
|
|
27212
|
+
return violations.map((v) => ` \u2022 [${v.rule}] ${v.message}`).join("\n");
|
|
27213
|
+
}
|
|
27214
|
+
|
|
26879
27215
|
// src/define.ts
|
|
26880
27216
|
function toAutomationWire(automations) {
|
|
26881
27217
|
return automations?.map((a) => ({
|
|
@@ -27031,12 +27367,18 @@ function toPackageDefinition(slugOrYaml) {
|
|
|
27031
27367
|
};
|
|
27032
27368
|
}
|
|
27033
27369
|
export {
|
|
27370
|
+
DECLARED_DIVERGENCES,
|
|
27034
27371
|
PACKAGE_DEPENDENCY_KINDS,
|
|
27035
27372
|
WORKSPACE_TEMPLATES,
|
|
27036
27373
|
assembleCatalog,
|
|
27374
|
+
bundledEdgesOf,
|
|
27037
27375
|
computeTemplateUpdates,
|
|
27376
|
+
conceptTerm,
|
|
27377
|
+
findConceptViolations,
|
|
27378
|
+
formatConceptViolations,
|
|
27038
27379
|
getWorkspaceTemplate,
|
|
27039
27380
|
isHashVersion,
|
|
27381
|
+
layerTemplateGraph,
|
|
27040
27382
|
listPublicTemplates,
|
|
27041
27383
|
listWorkspaceTemplates,
|
|
27042
27384
|
mergeCatalog,
|