cwtools-shared 0.2.1 → 0.2.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/knowledge/rules.d.ts +1 -1
- package/dist/knowledge/rules.js +213 -36
- package/package.json +24 -24
|
@@ -147,7 +147,7 @@ export interface CwtRuleValueReference {
|
|
|
147
147
|
}
|
|
148
148
|
export interface RuleSemanticHint {
|
|
149
149
|
text: string;
|
|
150
|
-
source: 'trigger_docs.log' | 'scopes.cwt' | 'cwt-comment' | 'modifiers.log';
|
|
150
|
+
source: 'trigger_docs.log' | 'scopes.cwt' | 'links.cwt' | 'cwt-comment' | 'modifiers.log';
|
|
151
151
|
file?: string;
|
|
152
152
|
line?: number;
|
|
153
153
|
confidence: 'hint';
|
package/dist/knowledge/rules.js
CHANGED
|
@@ -50,8 +50,11 @@ async function queryRulesWithHost(host, args) {
|
|
|
50
50
|
? cache.modifiers
|
|
51
51
|
: cache.scopeChanges;
|
|
52
52
|
if (args.name) {
|
|
53
|
-
const needle = args.name.
|
|
54
|
-
const filtered = rules
|
|
53
|
+
const needle = normalizeRuleNameQuery(args.name, args.category);
|
|
54
|
+
const filtered = rules
|
|
55
|
+
.filter(rule => rule.name.toLowerCase().includes(needle))
|
|
56
|
+
.sort((a, b) => scoreRuleNameMatch(a.name, needle) - scoreRuleNameMatch(b.name, needle)
|
|
57
|
+
|| a.name.localeCompare(b.name));
|
|
55
58
|
if (filtered.length === 0 && rules.length > 0) {
|
|
56
59
|
rules = rules
|
|
57
60
|
.map(rule => ({ rule, score: levenshtein(needle, rule.name.toLowerCase()) }))
|
|
@@ -322,13 +325,45 @@ function normalizeCwtSchemaTarget(host, value) {
|
|
|
322
325
|
}
|
|
323
326
|
async function findCwtSchemaFiles(host, root, maxFiles) {
|
|
324
327
|
if (host.rules?.listCwtFiles) {
|
|
325
|
-
return (await host.rules.listCwtFiles(root, { limit: maxFiles }))
|
|
328
|
+
return (await host.rules.listCwtFiles(root, { limit: maxFiles }))
|
|
329
|
+
.slice(0, maxFiles)
|
|
330
|
+
.sort((a, b) => a.localeCompare(b));
|
|
331
|
+
}
|
|
332
|
+
if (fs.existsSync(root)) {
|
|
333
|
+
const results = [];
|
|
334
|
+
const ignoredDirs = new Set(['.git', 'node_modules', 'logs', 'config']);
|
|
335
|
+
const walkDisk = (dir, depth) => {
|
|
336
|
+
if (results.length >= maxFiles || depth > 8)
|
|
337
|
+
return;
|
|
338
|
+
let entries;
|
|
339
|
+
try {
|
|
340
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
346
|
+
for (const entry of entries) {
|
|
347
|
+
if (results.length >= maxFiles)
|
|
348
|
+
break;
|
|
349
|
+
const fullPath = path.join(dir, entry.name);
|
|
350
|
+
if (entry.isDirectory()) {
|
|
351
|
+
if (!ignoredDirs.has(entry.name))
|
|
352
|
+
walkDisk(fullPath, depth + 1);
|
|
353
|
+
}
|
|
354
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith('.cwt')) {
|
|
355
|
+
results.push(fullPath);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
walkDisk(root, 0);
|
|
360
|
+
return results.sort((a, b) => a.localeCompare(b));
|
|
326
361
|
}
|
|
327
362
|
const rootRelative = workspaceRelativePath(host.workspaceRoot, root);
|
|
328
363
|
if (!rootRelative)
|
|
329
364
|
return [];
|
|
330
365
|
const results = [];
|
|
331
|
-
const ignoredDirs = new Set(['.git', 'node_modules', 'logs']);
|
|
366
|
+
const ignoredDirs = new Set(['.git', 'node_modules', 'logs', 'config']);
|
|
332
367
|
const walk = async (relativeDir, depth) => {
|
|
333
368
|
if (results.length >= maxFiles || depth > 8)
|
|
334
369
|
return;
|
|
@@ -339,6 +374,7 @@ async function findCwtSchemaFiles(host, root, maxFiles) {
|
|
|
339
374
|
catch {
|
|
340
375
|
return;
|
|
341
376
|
}
|
|
377
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
342
378
|
for (const entry of entries) {
|
|
343
379
|
if (results.length >= maxFiles)
|
|
344
380
|
break;
|
|
@@ -354,7 +390,16 @@ async function findCwtSchemaFiles(host, root, maxFiles) {
|
|
|
354
390
|
}
|
|
355
391
|
};
|
|
356
392
|
await walk(rootRelative, 0);
|
|
357
|
-
return results;
|
|
393
|
+
return results.sort((a, b) => a.localeCompare(b));
|
|
394
|
+
}
|
|
395
|
+
async function collectCwtRuleSourceFiles(host, root, includeMissingLogs) {
|
|
396
|
+
const files = await findCwtSchemaFiles(host, root, CWT_RULE_FILE_SCAN_LIMIT);
|
|
397
|
+
for (const relativeLog of CWT_RULE_LOG_CANDIDATES) {
|
|
398
|
+
const fullPath = path.join(root, relativeLog);
|
|
399
|
+
if (includeMissingLogs || fs.existsSync(fullPath))
|
|
400
|
+
files.push(fullPath);
|
|
401
|
+
}
|
|
402
|
+
return Array.from(new Set(files)).sort((a, b) => a.localeCompare(b));
|
|
358
403
|
}
|
|
359
404
|
function workspaceRelativePath(workspaceRoot, fullPath) {
|
|
360
405
|
const relative = path.relative(workspaceRoot, fullPath);
|
|
@@ -715,27 +760,18 @@ function scoreCwtSchemaEntity(summary, normalizedTarget, name) {
|
|
|
715
760
|
//
|
|
716
761
|
// loadCwtRules used to re-read and re-parse every rule file on each query.
|
|
717
762
|
// The memo keeps one parsed CwtRuleCache per host identity, invalidated by an
|
|
718
|
-
// mtime/size signature over
|
|
719
|
-
//
|
|
720
|
-
// sha256 (16 hex chars) over the length-prefixed concatenation
|
|
721
|
-
//
|
|
763
|
+
// mtime/size signature over every .cwt file under the active config roots plus
|
|
764
|
+
// the docs/modifier logs. `generation` is a per-host monotonic reload counter;
|
|
765
|
+
// `contentHash` is sha256 (16 hex chars) over the length-prefixed concatenation
|
|
766
|
+
// of the same files' content — the same algorithm the extension-side
|
|
722
767
|
// LspToolHandler uses, so both ends describe rule revisions with the same
|
|
723
768
|
// hash semantics. The cache is process-local and bounded
|
|
724
769
|
// (CWT_RULES_MEMO_MAX_ENTRIES).
|
|
725
|
-
const
|
|
726
|
-
'scopes.cwt',
|
|
770
|
+
const CWT_RULE_LOG_CANDIDATES = [
|
|
727
771
|
path.join('logs', 'trigger_docs.log'),
|
|
728
772
|
path.join('logs', 'modifiers.log'),
|
|
729
|
-
'triggers.cwt',
|
|
730
|
-
'trigger.cwt',
|
|
731
|
-
path.join('generated', 'triggers.generated.cwt'),
|
|
732
|
-
'effects.cwt',
|
|
733
|
-
'effect.cwt',
|
|
734
|
-
path.join('generated', 'effects.generated.cwt'),
|
|
735
|
-
'modifier.cwt',
|
|
736
|
-
'scope_changes.cwt',
|
|
737
|
-
path.join('generated', 'scope_changes.generated.cwt'),
|
|
738
773
|
];
|
|
774
|
+
const CWT_RULE_FILE_SCAN_LIMIT = 5000;
|
|
739
775
|
const CWT_RULES_MEMO_MAX_ENTRIES = 8;
|
|
740
776
|
/**
|
|
741
777
|
* When no candidate rule file exists on disk, the mtime signature cannot
|
|
@@ -744,12 +780,11 @@ const CWT_RULES_MEMO_MAX_ENTRIES = 8;
|
|
|
744
780
|
*/
|
|
745
781
|
const CWT_RULES_MEMO_REFRESH_MS = 30000;
|
|
746
782
|
const cwtRulesMemo = new Map();
|
|
747
|
-
function computeRulesSignature(configPaths) {
|
|
783
|
+
async function computeRulesSignature(host, configPaths) {
|
|
748
784
|
const parts = [];
|
|
749
785
|
let sawDiskFiles = false;
|
|
750
786
|
for (const configPath of configPaths) {
|
|
751
|
-
for (const
|
|
752
|
-
const fullPath = path.join(configPath, file);
|
|
787
|
+
for (const fullPath of await collectCwtRuleSourceFiles(host, configPath, true)) {
|
|
753
788
|
try {
|
|
754
789
|
const stat = fs.statSync(fullPath);
|
|
755
790
|
parts.push(`${fullPath}:${stat.mtimeMs}:${stat.size}`);
|
|
@@ -772,8 +807,8 @@ function computeRulesSignature(configPaths) {
|
|
|
772
807
|
async function computeRulesContentHash(host, configPaths) {
|
|
773
808
|
const hash = crypto.createHash('sha256');
|
|
774
809
|
for (const configPath of configPaths) {
|
|
775
|
-
for (const file of
|
|
776
|
-
const read = await readRulesTextFile(host,
|
|
810
|
+
for (const file of await collectCwtRuleSourceFiles(host, configPath, true)) {
|
|
811
|
+
const read = await readRulesTextFile(host, file).catch(() => ({ exists: false, content: '', hasBom: false }));
|
|
777
812
|
if (!read.exists)
|
|
778
813
|
continue;
|
|
779
814
|
hash.update(`${read.content.length}:`);
|
|
@@ -788,7 +823,7 @@ function cwtRulesHostKey(host) {
|
|
|
788
823
|
async function loadCwtRulesMemoized(host) {
|
|
789
824
|
const configPaths = await resolveRulesConfigPaths(host);
|
|
790
825
|
const hostKey = cwtRulesHostKey(host);
|
|
791
|
-
const { signature, sawDiskFiles } = computeRulesSignature(configPaths);
|
|
826
|
+
const { signature, sawDiskFiles } = await computeRulesSignature(host, configPaths);
|
|
792
827
|
const memo = cwtRulesMemo.get(hostKey);
|
|
793
828
|
if (memo && memo.signature === signature && (memo.sawDiskFiles || host.now() - memo.computedAt < CWT_RULES_MEMO_REFRESH_MS)) {
|
|
794
829
|
return { cache: memo.cache, meta: { generation: memo.generation, contentHash: memo.contentHash } };
|
|
@@ -824,10 +859,25 @@ async function loadCwtRulesFromPaths(host, configPaths) {
|
|
|
824
859
|
const scopes = scopesRead.exists
|
|
825
860
|
? parseScopesFile(scopesRead.content, path.join(configPath, 'scopes.cwt'))
|
|
826
861
|
: new Map();
|
|
827
|
-
const triggers =
|
|
828
|
-
const effects =
|
|
829
|
-
const scopeChanges =
|
|
830
|
-
const modifierAliases =
|
|
862
|
+
const triggers = [];
|
|
863
|
+
const effects = [];
|
|
864
|
+
const scopeChanges = [];
|
|
865
|
+
const modifierAliases = [];
|
|
866
|
+
for (const file of await findCwtSchemaFiles(host, configPath, CWT_RULE_FILE_SCAN_LIMIT)) {
|
|
867
|
+
const relativeFile = path.relative(configPath, file).replace(/\\/g, '/');
|
|
868
|
+
const parsed = await readRulesFile(host, file, scopeChangeFileCategoryOverride(relativeFile), docs, scopes);
|
|
869
|
+
for (const rule of parsed) {
|
|
870
|
+
if (rule.category === 'trigger')
|
|
871
|
+
triggers.push(rule);
|
|
872
|
+
else if (rule.category === 'effect')
|
|
873
|
+
effects.push(rule);
|
|
874
|
+
else if (rule.category === 'modifier')
|
|
875
|
+
modifierAliases.push(rule);
|
|
876
|
+
else
|
|
877
|
+
scopeChanges.push(rule);
|
|
878
|
+
}
|
|
879
|
+
scopeChanges.push(...await readLinksFile(host, file, scopes));
|
|
880
|
+
}
|
|
831
881
|
const modifierLog = await readModifiersLog(host, path.join(configPath, 'logs', 'modifiers.log'));
|
|
832
882
|
const modifiers = [...modifierAliases];
|
|
833
883
|
const modifierNames = new Set(modifiers.map(rule => rule.name.toLowerCase()));
|
|
@@ -925,6 +975,12 @@ async function readRulesFile(host, filePath, category, docs, scopes) {
|
|
|
925
975
|
return [];
|
|
926
976
|
return parseCwtFile(read.content, filePath, category, docs, scopes);
|
|
927
977
|
}
|
|
978
|
+
function scopeChangeFileCategoryOverride(relativeRuleFile) {
|
|
979
|
+
const base = path.posix.basename(relativeRuleFile.replace(/\\/g, '/')).toLowerCase();
|
|
980
|
+
return base === 'scope_changes.cwt' || base === 'scope_changes.generated.cwt' || base === 'scope_change.cwt'
|
|
981
|
+
? 'scope_change'
|
|
982
|
+
: undefined;
|
|
983
|
+
}
|
|
928
984
|
async function readModifiersLog(host, filePath) {
|
|
929
985
|
const read = await readRulesTextFile(host, filePath).catch(() => ({ exists: false, content: '', hasBom: false }));
|
|
930
986
|
if (!read.exists)
|
|
@@ -1035,7 +1091,7 @@ function parseScopesFile(content, filePath) {
|
|
|
1035
1091
|
}
|
|
1036
1092
|
return scopes;
|
|
1037
1093
|
}
|
|
1038
|
-
function parseCwtFile(content, filePath,
|
|
1094
|
+
function parseCwtFile(content, filePath, categoryOverride, docs, scopes) {
|
|
1039
1095
|
const results = [];
|
|
1040
1096
|
let currentScopes = [];
|
|
1041
1097
|
let currentSupportedScopes = [];
|
|
@@ -1080,9 +1136,11 @@ function parseCwtFile(content, filePath, category, docs, scopes) {
|
|
|
1080
1136
|
currentDesc = comment;
|
|
1081
1137
|
continue;
|
|
1082
1138
|
}
|
|
1083
|
-
const nameMatch = line.match(/^alias\[(
|
|
1084
|
-
if (nameMatch?.[1]) {
|
|
1085
|
-
const
|
|
1139
|
+
const nameMatch = line.match(/^alias\[(trigger|effect|modifier):([^\]]+)\]\s*=\s*(.*)/);
|
|
1140
|
+
if (nameMatch?.[1] && nameMatch[2]) {
|
|
1141
|
+
const aliasKind = nameMatch[1];
|
|
1142
|
+
const category = categoryOverride ?? aliasKind;
|
|
1143
|
+
const name = nameMatch[2];
|
|
1086
1144
|
const doc = docs.get(name);
|
|
1087
1145
|
const cwtBlockText = collectCwtBlockText(lines, i);
|
|
1088
1146
|
const scopesForRule = doc?.scopes.length
|
|
@@ -1090,7 +1148,7 @@ function parseCwtFile(content, filePath, category, docs, scopes) {
|
|
|
1090
1148
|
: currentSupportedScopes.length
|
|
1091
1149
|
? currentSupportedScopes
|
|
1092
1150
|
: currentScopes;
|
|
1093
|
-
const syntax = doc?.syntax || normalizeInlineSyntax(name, nameMatch[
|
|
1151
|
+
const syntax = doc?.syntax || normalizeInlineSyntax(name, nameMatch[3] ?? '');
|
|
1094
1152
|
const description = doc?.description || currentDesc;
|
|
1095
1153
|
const semanticHints = buildSemanticHints({
|
|
1096
1154
|
description,
|
|
@@ -1134,6 +1192,92 @@ function parseCwtFile(content, filePath, category, docs, scopes) {
|
|
|
1134
1192
|
}
|
|
1135
1193
|
return results;
|
|
1136
1194
|
}
|
|
1195
|
+
async function readLinksFile(host, filePath, scopes) {
|
|
1196
|
+
const read = await readRulesTextFile(host, filePath).catch(() => ({ exists: false, content: '', hasBom: false }));
|
|
1197
|
+
if (!read.exists || !/^\s*links\s*=\s*\{/im.test(read.content))
|
|
1198
|
+
return [];
|
|
1199
|
+
return parseLinksCwtFile(read.content, filePath, scopes);
|
|
1200
|
+
}
|
|
1201
|
+
function parseLinksCwtFile(content, filePath, scopes) {
|
|
1202
|
+
const results = [];
|
|
1203
|
+
const lines = content.split(/\r?\n/);
|
|
1204
|
+
let inLinks = false;
|
|
1205
|
+
let depth = 0;
|
|
1206
|
+
let current;
|
|
1207
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1208
|
+
const rawLine = lines[i] ?? '';
|
|
1209
|
+
const line = stripCwtLineComment(rawLine).trim();
|
|
1210
|
+
if (!inLinks) {
|
|
1211
|
+
if (/^links\s*=\s*\{/.test(line)) {
|
|
1212
|
+
inLinks = true;
|
|
1213
|
+
depth = countBraceDelta(line);
|
|
1214
|
+
}
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
if (!current && depth === 1) {
|
|
1218
|
+
const linkMatch = line.match(/^([A-Za-z_][\w.-]*)\s*=\s*\{\s*$/);
|
|
1219
|
+
if (linkMatch?.[1])
|
|
1220
|
+
current = { name: linkMatch[1], line: i + 1, inputScopes: [] };
|
|
1221
|
+
}
|
|
1222
|
+
else if (current) {
|
|
1223
|
+
const inputMatch = line.match(/^input_scopes\s*=\s*(.*)$/i);
|
|
1224
|
+
if (inputMatch?.[1])
|
|
1225
|
+
current.inputScopes = splitRuleValueList(inputMatch[1]).map(normalizeScopeName);
|
|
1226
|
+
const outputMatch = line.match(/^output_scope\s*=\s*(.*)$/i);
|
|
1227
|
+
if (outputMatch?.[1]) {
|
|
1228
|
+
const outputScope = stripRuleValueBraces(outputMatch[1]).split(/\s+/)[0];
|
|
1229
|
+
if (outputScope)
|
|
1230
|
+
current.outputScope = normalizeScopeName(outputScope);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
depth += countBraceDelta(line);
|
|
1234
|
+
if (current && depth <= 1) {
|
|
1235
|
+
if (current.outputScope) {
|
|
1236
|
+
const inputScopes = current.inputScopes.length ? current.inputScopes : ['all'];
|
|
1237
|
+
const syntax = `${current.name} = scope link (${inputScopes.join(' | ')} -> ${current.outputScope})`;
|
|
1238
|
+
const linkHint = {
|
|
1239
|
+
text: `Legal scope link '${current.name}' accepts input scopes { ${inputScopes.join(' ')} } and outputs '${current.outputScope}'. Context pointers such as from/prev/root/this select the current input scope; they are not fixed object fields.`,
|
|
1240
|
+
source: 'links.cwt',
|
|
1241
|
+
file: filePath,
|
|
1242
|
+
line: current.line,
|
|
1243
|
+
confidence: 'hint',
|
|
1244
|
+
};
|
|
1245
|
+
const scopeHints = buildSemanticHints({
|
|
1246
|
+
description: '',
|
|
1247
|
+
cwtDescription: '',
|
|
1248
|
+
scopes,
|
|
1249
|
+
relatedScopeNames: [...inputScopes, current.outputScope],
|
|
1250
|
+
cwtFile: filePath,
|
|
1251
|
+
cwtLine: current.line,
|
|
1252
|
+
});
|
|
1253
|
+
results.push({
|
|
1254
|
+
name: current.name,
|
|
1255
|
+
description: `Legal scope link from { ${inputScopes.join(' ')} } to ${current.outputScope}.`,
|
|
1256
|
+
scopes: inputScopes,
|
|
1257
|
+
syntax,
|
|
1258
|
+
category: 'scope_change',
|
|
1259
|
+
sourceFile: filePath,
|
|
1260
|
+
sourceLine: current.line,
|
|
1261
|
+
hardFacts: {
|
|
1262
|
+
category: 'scope_change',
|
|
1263
|
+
supportedScopes: inputScopes,
|
|
1264
|
+
pushScope: current.outputScope,
|
|
1265
|
+
valueReferences: [],
|
|
1266
|
+
syntax,
|
|
1267
|
+
cwtSource: { file: filePath, line: current.line },
|
|
1268
|
+
},
|
|
1269
|
+
semanticHints: [linkHint, ...scopeHints].slice(0, 8),
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
current = undefined;
|
|
1273
|
+
}
|
|
1274
|
+
if (inLinks && depth <= 0) {
|
|
1275
|
+
inLinks = false;
|
|
1276
|
+
current = undefined;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
return results;
|
|
1280
|
+
}
|
|
1137
1281
|
function buildSemanticHints(args) {
|
|
1138
1282
|
const hints = [];
|
|
1139
1283
|
const seen = new Set();
|
|
@@ -1287,7 +1431,12 @@ function expandIntentTokens(intent) {
|
|
|
1287
1431
|
[/触发器|觸發器/g, ['trigger']],
|
|
1288
1432
|
[/效果|效应|效應/g, ['effect']],
|
|
1289
1433
|
];
|
|
1290
|
-
const expanded = [
|
|
1434
|
+
const expanded = [];
|
|
1435
|
+
for (const token of direct) {
|
|
1436
|
+
expanded.push(token);
|
|
1437
|
+
if (/[.:]/.test(token))
|
|
1438
|
+
expanded.push(...token.split(/[.:]+/).filter(Boolean));
|
|
1439
|
+
}
|
|
1291
1440
|
for (const [pattern, tokens] of synonyms) {
|
|
1292
1441
|
pattern.lastIndex = 0;
|
|
1293
1442
|
if (pattern.test(intent))
|
|
@@ -1363,6 +1512,34 @@ function splitRuleValueList(value) {
|
|
|
1363
1512
|
function stripRuleValueBraces(value) {
|
|
1364
1513
|
return value.replace(/^\{\s*/, '').replace(/\s*\}$/, '').trim();
|
|
1365
1514
|
}
|
|
1515
|
+
function normalizeRuleNameQuery(name, category) {
|
|
1516
|
+
const lowered = name.trim().toLowerCase();
|
|
1517
|
+
if (category !== 'scope_change' || !lowered.includes('.'))
|
|
1518
|
+
return lowered;
|
|
1519
|
+
const parts = lowered.split('.').map(part => part.trim()).filter(Boolean);
|
|
1520
|
+
return parts[parts.length - 1] ?? lowered;
|
|
1521
|
+
}
|
|
1522
|
+
function scoreRuleNameMatch(name, needle) {
|
|
1523
|
+
const lower = name.toLowerCase();
|
|
1524
|
+
if (lower === needle)
|
|
1525
|
+
return 0;
|
|
1526
|
+
if (lower.startsWith(needle))
|
|
1527
|
+
return 1;
|
|
1528
|
+
return 2;
|
|
1529
|
+
}
|
|
1530
|
+
function normalizeScopeName(scope) {
|
|
1531
|
+
return scope.replace(/^["']|["']$/g, '').trim().toLowerCase();
|
|
1532
|
+
}
|
|
1533
|
+
function countBraceDelta(line) {
|
|
1534
|
+
let delta = 0;
|
|
1535
|
+
for (const ch of line) {
|
|
1536
|
+
if (ch === '{')
|
|
1537
|
+
delta += 1;
|
|
1538
|
+
else if (ch === '}')
|
|
1539
|
+
delta -= 1;
|
|
1540
|
+
}
|
|
1541
|
+
return delta;
|
|
1542
|
+
}
|
|
1366
1543
|
function splitWords(value) {
|
|
1367
1544
|
return value.split(/\s+/).map(part => part.trim()).filter(Boolean);
|
|
1368
1545
|
}
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cwtools-shared",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Shared protocol and tool contracts for the CWTools MCP server",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"repository": {
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/Aa728848/cwtools-mcp.git"
|
|
9
|
-
},
|
|
10
|
-
"engines": {
|
|
11
|
-
"node": ">=18"
|
|
12
|
-
},
|
|
13
|
-
"main": "dist/index.js",
|
|
14
|
-
"types": "dist/index.d.ts",
|
|
15
|
-
"files": [
|
|
16
|
-
"dist",
|
|
17
|
-
"package.json"
|
|
18
|
-
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"build": "tsc -p tsconfig.json",
|
|
21
|
-
"prepack": "npm run build",
|
|
22
|
-
"test:contracts": "ts-mocha -p tsconfig.test.json \"src/test/**/*.test.ts\""
|
|
23
|
-
}
|
|
24
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "cwtools-shared",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Shared protocol and tool contracts for the CWTools MCP server",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Aa728848/cwtools-mcp.git"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"main": "dist/index.js",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"package.json"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"prepack": "npm run build",
|
|
22
|
+
"test:contracts": "ts-mocha -p tsconfig.test.json \"src/test/**/*.test.ts\""
|
|
23
|
+
}
|
|
24
|
+
}
|