cwtools-shared 0.2.2 → 0.2.3

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.
@@ -49,6 +49,9 @@ async function queryRulesWithHost(host, args) {
49
49
  : args.category === 'modifier'
50
50
  ? cache.modifiers
51
51
  : cache.scopeChanges;
52
+ const dottedScopeChain = args.name && args.category === 'scope_change'
53
+ ? buildDottedScopeChainRule(args.name, args.scope, cache.scopeChanges)
54
+ : undefined;
52
55
  if (args.name) {
53
56
  const needle = normalizeRuleNameQuery(args.name, args.category);
54
57
  const filtered = rules
@@ -69,6 +72,12 @@ async function queryRulesWithHost(host, args) {
69
72
  rules = filtered;
70
73
  }
71
74
  }
75
+ if (dottedScopeChain) {
76
+ rules = [
77
+ dottedScopeChain,
78
+ ...rules.filter(rule => rule.name !== dottedScopeChain.name),
79
+ ];
80
+ }
72
81
  if (args.scope) {
73
82
  const scope = args.scope.toLowerCase();
74
83
  rules = rules.filter(rule => rule.scopes.length === 0
@@ -1519,6 +1528,70 @@ function normalizeRuleNameQuery(name, category) {
1519
1528
  const parts = lowered.split('.').map(part => part.trim()).filter(Boolean);
1520
1529
  return parts[parts.length - 1] ?? lowered;
1521
1530
  }
1531
+ function buildDottedScopeChainRule(query, contextScope, scopeChanges) {
1532
+ const rawParts = query.trim().split('.').map(normalizeScopeName).filter(Boolean);
1533
+ if (rawParts.length < 2)
1534
+ return undefined;
1535
+ const contextPointers = new Set(['from', 'prev', 'root', 'this']);
1536
+ const first = rawParts[0];
1537
+ const startsWithPointer = contextPointers.has(first);
1538
+ const initialScope = startsWithPointer ? contextScope?.trim().toLowerCase() : first;
1539
+ const linkNames = rawParts.slice(1);
1540
+ if (!initialScope || linkNames.length === 0)
1541
+ return undefined;
1542
+ const hops = [];
1543
+ let currentScope = initialScope;
1544
+ for (const linkName of linkNames) {
1545
+ const link = scopeChanges.find(rule => rule.name.toLowerCase() === linkName
1546
+ && !!rule.hardFacts?.pushScope
1547
+ && scopeListContains(rule.hardFacts.supportedScopes ?? rule.scopes, currentScope));
1548
+ const outputScope = link?.hardFacts?.pushScope?.trim().toLowerCase();
1549
+ if (!link || !outputScope)
1550
+ return undefined;
1551
+ hops.push({ link, inputScope: currentScope, outputScope });
1552
+ currentScope = outputScope;
1553
+ }
1554
+ if (hops.length === 0)
1555
+ return undefined;
1556
+ const normalizedQuery = rawParts.join('.');
1557
+ const syntax = `${normalizedQuery} = { ... }`;
1558
+ const source = hops[0].link;
1559
+ const hopText = hops.map(hop => `${hop.inputScope}.${hop.link.name} -> ${hop.outputScope}`).join('; ');
1560
+ const linkHints = hops.map((hop) => ({
1561
+ text: `Dotted scope chain hop '${hop.inputScope}.${hop.link.name}' is legal because links.cwt declares '${hop.link.name}' with input scope '${hop.inputScope}' and output scope '${hop.outputScope}'.`,
1562
+ source: 'links.cwt',
1563
+ file: hop.link.sourceFile,
1564
+ line: hop.link.sourceLine,
1565
+ confidence: 'hint',
1566
+ }));
1567
+ return {
1568
+ name: normalizedQuery,
1569
+ description: `Legal dotted scope chain from ${initialScope} to ${currentScope}. Hops: ${hopText}.`,
1570
+ scopes: [initialScope],
1571
+ syntax,
1572
+ category: 'scope_change',
1573
+ sourceFile: source.sourceFile,
1574
+ sourceLine: source.sourceLine,
1575
+ hardFacts: {
1576
+ category: 'scope_change',
1577
+ supportedScopes: [initialScope],
1578
+ pushScope: currentScope,
1579
+ valueReferences: [],
1580
+ syntax,
1581
+ cwtSource: source.sourceFile && source.sourceLine
1582
+ ? { file: source.sourceFile, line: source.sourceLine }
1583
+ : undefined,
1584
+ },
1585
+ semanticHints: linkHints.slice(0, 8),
1586
+ };
1587
+ }
1588
+ function scopeListContains(scopes, scope) {
1589
+ const lowerScope = scope.toLowerCase();
1590
+ return scopes.some(candidate => {
1591
+ const lower = candidate.toLowerCase();
1592
+ return lower === lowerScope || lower === 'all' || lower === 'any';
1593
+ });
1594
+ }
1522
1595
  function scoreRuleNameMatch(name, needle) {
1523
1596
  const lower = name.toLowerCase();
1524
1597
  if (lower === needle)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cwtools-shared",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Shared protocol and tool contracts for the CWTools MCP server",
5
5
  "license": "MIT",
6
6
  "repository": {