cwtools-shared 0.1.0 → 0.2.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.
@@ -317,7 +317,7 @@ export declare const GENERATED_MCP_TOOLS: readonly [{
317
317
  readonly properties: {
318
318
  readonly section: {
319
319
  readonly type: "string";
320
- readonly enum: readonly ["summary", "routing", "directories", "localisation", "identifiers", "validation", "promptCards", "all"];
320
+ readonly enum: readonly ["summary", "routing", "directories", "localisation", "identifiers", "validation", "compatibility", "promptCards", "all"];
321
321
  readonly description: "Targeted profile section to return. Default summary. Use all only when you need the whole profile.";
322
322
  };
323
323
  readonly mode: {
@@ -450,7 +450,11 @@ export declare const GENERATED_MCP_TOOLS: readonly [{
450
450
  };
451
451
  readonly includeReferences: {
452
452
  readonly type: "boolean";
453
- readonly description: "If true, include lightweight cross-file reference contexts captured by the index. Default false.";
453
+ readonly description: "Include lightweight cross-file reference contexts. Default false.";
454
+ };
455
+ readonly includeAssetChain: {
456
+ readonly type: "boolean";
457
+ readonly description: "Check bounded GUI/GFX/model targets, path case, and DDS frame layout.";
454
458
  };
455
459
  readonly limit: {
456
460
  readonly type: "number";
@@ -471,7 +475,7 @@ export declare const GENERATED_MCP_TOOLS: readonly [{
471
475
  }, {
472
476
  readonly tool: {
473
477
  readonly name: "explore_pdx_project";
474
- readonly description: "Primary semantic exploration entry point for large Paradox projects. Queries the live CWTools type/reference graph and returns bounded entry points, typed nodes, dependency edges, file facts, provenance, truncation, and freshness without scanning or reading whole files. Use this FIRST for questions such as how an entity is connected, what calls or references an ID, what a file depends on, or what may be affected by a change. Follow with exact query_rules/query_scope/query_types/get_pdx_block checks before writing.";
478
+ readonly description: "Primary semantic exploration for large Paradox projects: live CWTools type/reference graph with bounded nodes, edges, file facts, provenance and freshness. Use FIRST for connectivity and impact; follow with exact query_rules/query_scope/query_types/get_pdx_block before writing.";
475
479
  readonly inputSchema: {
476
480
  readonly type: "object";
477
481
  readonly properties: {
@@ -513,6 +517,14 @@ export declare const GENERATED_MCP_TOOLS: readonly [{
513
517
  readonly type: "boolean";
514
518
  readonly description: "Include documentation, variables, event targets, and block metadata. Default true.";
515
519
  };
520
+ readonly relationshipKinds: {
521
+ readonly type: "array";
522
+ readonly items: {
523
+ readonly type: "string";
524
+ readonly enum: readonly ["inline_invocation", "inline_expansion"];
525
+ };
526
+ readonly description: "Extra graph kinds: inline_invocation or inline_expansion.";
527
+ };
516
528
  };
517
529
  readonly required: readonly [];
518
530
  };
@@ -525,6 +537,103 @@ export declare const GENERATED_MCP_TOOLS: readonly [{
525
537
  readonly riskLevel: 0;
526
538
  readonly concurrencyClass: "lsp-limited";
527
539
  };
540
+ }, {
541
+ readonly tool: {
542
+ readonly name: "query_inline_instantiation";
543
+ readonly description: "Preview one inline_script template instantiation: parameters, arguments, expanded symbol, and problems (missing/unused/unresolved). Precise template path or file+line only; no whole-project expansion. Use before editing a template or caller.";
544
+ readonly inputSchema: {
545
+ readonly type: "object";
546
+ readonly properties: {
547
+ readonly template: {
548
+ readonly type: "string";
549
+ readonly description: "Template path, e.g. common/inline_scripts/example.txt.";
550
+ };
551
+ readonly file: {
552
+ readonly type: "string";
553
+ readonly description: "Caller file; narrows to one invocation.";
554
+ };
555
+ readonly line: {
556
+ readonly type: "number";
557
+ readonly description: "Caller line; combined with file selects one invocation.";
558
+ };
559
+ readonly limit: {
560
+ readonly type: "number";
561
+ readonly description: "Maximum invocations to return. Default 50, max 200.";
562
+ };
563
+ };
564
+ readonly required: readonly [];
565
+ };
566
+ };
567
+ readonly registry: {
568
+ readonly name: "query_inline_instantiation";
569
+ readonly isWrite: false;
570
+ readonly isReadOnly: true;
571
+ readonly effect: "workspace_read";
572
+ readonly riskLevel: 0;
573
+ readonly concurrencyClass: "lsp-limited";
574
+ };
575
+ }, {
576
+ readonly tool: {
577
+ readonly name: "analyze_pdx_flow";
578
+ readonly description: "Static cost model and gameplay relations for a file or definition: traversals (every_*/while), pulse handlers, nested fan-out, and typed edges (tech prerequisites, special project events, megastructure upgrades). Relative weights only.";
579
+ readonly inputSchema: {
580
+ readonly type: "object";
581
+ readonly properties: {
582
+ readonly file: {
583
+ readonly type: "string";
584
+ readonly description: "Optional file path to analyze; when omitted a definitionId must be given.";
585
+ };
586
+ readonly definitionId: {
587
+ readonly type: "string";
588
+ readonly description: "Optional exact definition ID; combined with file to scope the analysis.";
589
+ };
590
+ readonly entityType: {
591
+ readonly type: "string";
592
+ readonly description: "Optional exact CWTools entity type; use with definitionId to disambiguate duplicate IDs.";
593
+ };
594
+ readonly limit: {
595
+ readonly type: "number";
596
+ readonly description: "Maximum costs and relations returned per collection. Default 100, max 500.";
597
+ };
598
+ };
599
+ readonly required: readonly [];
600
+ };
601
+ };
602
+ readonly registry: {
603
+ readonly name: "analyze_pdx_flow";
604
+ readonly isWrite: false;
605
+ readonly isReadOnly: true;
606
+ readonly effect: "workspace_read";
607
+ readonly riskLevel: 0;
608
+ readonly concurrencyClass: "lsp-limited";
609
+ };
610
+ }, {
611
+ readonly tool: {
612
+ readonly name: "compare_definition_with_vanilla";
613
+ readonly description: "Field-level diff of one definition against its vanilla counterpart: added, removed and modified fields with stable source locations. Accepts exact entityType + symbolId only; returns the resolved winner and never fabricates a conclusion when the winner is ambiguous. Use before editing any file that overrides vanilla.";
614
+ readonly inputSchema: {
615
+ readonly type: "object";
616
+ readonly properties: {
617
+ readonly entityType: {
618
+ readonly type: "string";
619
+ readonly description: "Exact CWTools type name (e.g. event, technology, ship_size).";
620
+ };
621
+ readonly symbolId: {
622
+ readonly type: "string";
623
+ readonly description: "Exact definition ID shared by workspace and vanilla candidates.";
624
+ };
625
+ };
626
+ readonly required: readonly ["entityType", "symbolId"];
627
+ };
628
+ };
629
+ readonly registry: {
630
+ readonly name: "compare_definition_with_vanilla";
631
+ readonly isWrite: false;
632
+ readonly isReadOnly: true;
633
+ readonly effect: "workspace_read";
634
+ readonly riskLevel: 0;
635
+ readonly concurrencyClass: "lsp-limited";
636
+ };
528
637
  }, {
529
638
  readonly tool: {
530
639
  readonly name: "query_localisation_index";
@@ -552,6 +661,26 @@ export declare const GENERATED_MCP_TOOLS: readonly [{
552
661
  readonly type: "boolean";
553
662
  readonly description: "Only applies to prefix/contains searches. Default false.";
554
663
  };
664
+ readonly includeDuplicates: {
665
+ readonly type: "boolean";
666
+ readonly description: "Include per-key duplicate groups with all occurrences (file/line), so duplicate keys are auditable instead of trusting the last write. Default false.";
667
+ };
668
+ readonly compareLanguages: {
669
+ readonly type: "boolean";
670
+ readonly description: "Include true missing/extra key-set differences per language, independent of result truncation. Default false.";
671
+ };
672
+ readonly referenceLanguage: {
673
+ readonly type: "string";
674
+ readonly description: "Reference language for missing/extra differences. Defaults to l_english when indexed.";
675
+ };
676
+ readonly referenceStatus: {
677
+ readonly type: "boolean";
678
+ readonly description: "Include bounded LSP reference status and origin; dynamic keys remain uncertain.";
679
+ };
680
+ readonly auditMode: {
681
+ readonly type: "boolean";
682
+ readonly description: "Include authoritative CWTools localisation diagnostics: completely missing script-referenced keys, inline/dynamic-key provenance, and localisation command/scope issues.";
683
+ };
555
684
  readonly limit: {
556
685
  readonly type: "number";
557
686
  readonly description: "Maximum entries to return. Default 20, max 100.";
@@ -368,6 +368,7 @@ exports.GENERATED_MCP_TOOLS = [
368
368
  "localisation",
369
369
  "identifiers",
370
370
  "validation",
371
+ "compatibility",
371
372
  "promptCards",
372
373
  "all"
373
374
  ],
@@ -527,7 +528,11 @@ exports.GENERATED_MCP_TOOLS = [
527
528
  },
528
529
  "includeReferences": {
529
530
  "type": "boolean",
530
- "description": "If true, include lightweight cross-file reference contexts captured by the index. Default false."
531
+ "description": "Include lightweight cross-file reference contexts. Default false."
532
+ },
533
+ "includeAssetChain": {
534
+ "type": "boolean",
535
+ "description": "Check bounded GUI/GFX/model targets, path case, and DDS frame layout."
531
536
  },
532
537
  "limit": {
533
538
  "type": "number",
@@ -549,7 +554,7 @@ exports.GENERATED_MCP_TOOLS = [
549
554
  {
550
555
  "tool": {
551
556
  "name": "explore_pdx_project",
552
- "description": "Primary semantic exploration entry point for large Paradox projects. Queries the live CWTools type/reference graph and returns bounded entry points, typed nodes, dependency edges, file facts, provenance, truncation, and freshness without scanning or reading whole files. Use this FIRST for questions such as how an entity is connected, what calls or references an ID, what a file depends on, or what may be affected by a change. Follow with exact query_rules/query_scope/query_types/get_pdx_block checks before writing.",
557
+ "description": "Primary semantic exploration for large Paradox projects: live CWTools type/reference graph with bounded nodes, edges, file facts, provenance and freshness. Use FIRST for connectivity and impact; follow with exact query_rules/query_scope/query_types/get_pdx_block before writing.",
553
558
  "inputSchema": {
554
559
  "type": "object",
555
560
  "properties": {
@@ -590,6 +595,17 @@ exports.GENERATED_MCP_TOOLS = [
590
595
  "includeMetadata": {
591
596
  "type": "boolean",
592
597
  "description": "Include documentation, variables, event targets, and block metadata. Default true."
598
+ },
599
+ "relationshipKinds": {
600
+ "type": "array",
601
+ "items": {
602
+ "type": "string",
603
+ "enum": [
604
+ "inline_invocation",
605
+ "inline_expansion"
606
+ ]
607
+ },
608
+ "description": "Extra graph kinds: inline_invocation or inline_expansion."
593
609
  }
594
610
  },
595
611
  "required": []
@@ -604,6 +620,109 @@ exports.GENERATED_MCP_TOOLS = [
604
620
  "concurrencyClass": "lsp-limited"
605
621
  }
606
622
  },
623
+ {
624
+ "tool": {
625
+ "name": "query_inline_instantiation",
626
+ "description": "Preview one inline_script template instantiation: parameters, arguments, expanded symbol, and problems (missing/unused/unresolved). Precise template path or file+line only; no whole-project expansion. Use before editing a template or caller.",
627
+ "inputSchema": {
628
+ "type": "object",
629
+ "properties": {
630
+ "template": {
631
+ "type": "string",
632
+ "description": "Template path, e.g. common/inline_scripts/example.txt."
633
+ },
634
+ "file": {
635
+ "type": "string",
636
+ "description": "Caller file; narrows to one invocation."
637
+ },
638
+ "line": {
639
+ "type": "number",
640
+ "description": "Caller line; combined with file selects one invocation."
641
+ },
642
+ "limit": {
643
+ "type": "number",
644
+ "description": "Maximum invocations to return. Default 50, max 200."
645
+ }
646
+ },
647
+ "required": []
648
+ }
649
+ },
650
+ "registry": {
651
+ "name": "query_inline_instantiation",
652
+ "isWrite": false,
653
+ "isReadOnly": true,
654
+ "effect": "workspace_read",
655
+ "riskLevel": 0,
656
+ "concurrencyClass": "lsp-limited"
657
+ }
658
+ },
659
+ {
660
+ "tool": {
661
+ "name": "analyze_pdx_flow",
662
+ "description": "Static cost model and gameplay relations for a file or definition: traversals (every_*/while), pulse handlers, nested fan-out, and typed edges (tech prerequisites, special project events, megastructure upgrades). Relative weights only.",
663
+ "inputSchema": {
664
+ "type": "object",
665
+ "properties": {
666
+ "file": {
667
+ "type": "string",
668
+ "description": "Optional file path to analyze; when omitted a definitionId must be given."
669
+ },
670
+ "definitionId": {
671
+ "type": "string",
672
+ "description": "Optional exact definition ID; combined with file to scope the analysis."
673
+ },
674
+ "entityType": {
675
+ "type": "string",
676
+ "description": "Optional exact CWTools entity type; use with definitionId to disambiguate duplicate IDs."
677
+ },
678
+ "limit": {
679
+ "type": "number",
680
+ "description": "Maximum costs and relations returned per collection. Default 100, max 500."
681
+ }
682
+ },
683
+ "required": []
684
+ }
685
+ },
686
+ "registry": {
687
+ "name": "analyze_pdx_flow",
688
+ "isWrite": false,
689
+ "isReadOnly": true,
690
+ "effect": "workspace_read",
691
+ "riskLevel": 0,
692
+ "concurrencyClass": "lsp-limited"
693
+ }
694
+ },
695
+ {
696
+ "tool": {
697
+ "name": "compare_definition_with_vanilla",
698
+ "description": "Field-level diff of one definition against its vanilla counterpart: added, removed and modified fields with stable source locations. Accepts exact entityType + symbolId only; returns the resolved winner and never fabricates a conclusion when the winner is ambiguous. Use before editing any file that overrides vanilla.",
699
+ "inputSchema": {
700
+ "type": "object",
701
+ "properties": {
702
+ "entityType": {
703
+ "type": "string",
704
+ "description": "Exact CWTools type name (e.g. event, technology, ship_size)."
705
+ },
706
+ "symbolId": {
707
+ "type": "string",
708
+ "description": "Exact definition ID shared by workspace and vanilla candidates."
709
+ }
710
+ },
711
+ "required": [
712
+ "entityType",
713
+ "symbolId"
714
+ ]
715
+ }
716
+ },
717
+ "registry": {
718
+ "name": "compare_definition_with_vanilla",
719
+ "isWrite": false,
720
+ "isReadOnly": true,
721
+ "effect": "workspace_read",
722
+ "riskLevel": 0,
723
+ "concurrencyClass": "lsp-limited"
724
+ }
725
+ },
607
726
  {
608
727
  "tool": {
609
728
  "name": "query_localisation_index",
@@ -631,6 +750,26 @@ exports.GENERATED_MCP_TOOLS = [
631
750
  "type": "boolean",
632
751
  "description": "Only applies to prefix/contains searches. Default false."
633
752
  },
753
+ "includeDuplicates": {
754
+ "type": "boolean",
755
+ "description": "Include per-key duplicate groups with all occurrences (file/line), so duplicate keys are auditable instead of trusting the last write. Default false."
756
+ },
757
+ "compareLanguages": {
758
+ "type": "boolean",
759
+ "description": "Include true missing/extra key-set differences per language, independent of result truncation. Default false."
760
+ },
761
+ "referenceLanguage": {
762
+ "type": "string",
763
+ "description": "Reference language for missing/extra differences. Defaults to l_english when indexed."
764
+ },
765
+ "referenceStatus": {
766
+ "type": "boolean",
767
+ "description": "Include bounded LSP reference status and origin; dynamic keys remain uncertain."
768
+ },
769
+ "auditMode": {
770
+ "type": "boolean",
771
+ "description": "Include authoritative CWTools localisation diagnostics: completely missing script-referenced keys, inline/dynamic-key provenance, and localisation command/scope issues."
772
+ },
634
773
  "limit": {
635
774
  "type": "number",
636
775
  "description": "Maximum entries to return. Default 20, max 100."
@@ -28,6 +28,11 @@ export interface LocalisationIndexQuery {
28
28
  prefix?: boolean;
29
29
  contains?: boolean;
30
30
  caseSensitive?: boolean;
31
+ includeDuplicates?: boolean;
32
+ compareLanguages?: boolean;
33
+ referenceLanguage?: string;
34
+ referenceStatus?: boolean;
35
+ auditMode?: boolean;
31
36
  limit?: number;
32
37
  }
33
38
  export interface LocalisationIndexEntry {
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.queryProjectKnowledgeWithHost = queryProjectKnowledgeWithHost;
37
37
  const path = __importStar(require("path"));
38
38
  const KNOWLEDGE_DIR = path.join('.cwtools', 'project', 'knowledge');
39
+ const CURRENT_KNOWLEDGE_SCHEMA_VERSION = 7;
39
40
  function asRecord(value) {
40
41
  return value && typeof value === 'object' && !Array.isArray(value)
41
42
  ? value
@@ -44,19 +45,6 @@ function asRecord(value) {
44
45
  function stringArray(value) {
45
46
  return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
46
47
  }
47
- function tokensFor(args) {
48
- return [args.intent ?? '', ...(args.identifiers ?? []), ...(args.entityTypes ?? [])]
49
- .join(' ')
50
- .toLowerCase()
51
- .match(/[@a-z0-9_.:-]{2,}/g)
52
- ?.slice(0, 30) ?? [];
53
- }
54
- function score(value, tokens) {
55
- if (tokens.length === 0)
56
- return 1;
57
- const text = JSON.stringify(value).toLowerCase();
58
- return tokens.reduce((total, token) => total + (text.includes(token) ? 3 : 0), 0);
59
- }
60
48
  async function readJson(host, filePath) {
61
49
  const read = await host.filesystem.readTextFile(filePath);
62
50
  if (!read.exists)
@@ -69,19 +57,9 @@ async function readJson(host, filePath) {
69
57
  }
70
58
  }
71
59
  async function queryProjectKnowledgeWithHost(host, args = {}) {
72
- let root = path.join(host.workspaceRoot, KNOWLEDGE_DIR);
73
- let manifestPath = path.join(root, 'manifest.json');
74
- let manifest = await readJson(host, manifestPath);
75
- if (!manifest) {
76
- const legacyRoot = path.join(host.workspaceRoot, '.cwtools-ai', 'project', 'knowledge');
77
- const legacyManifestPath = path.join(legacyRoot, 'manifest.json');
78
- const legacyManifest = await readJson(host, legacyManifestPath);
79
- if (legacyManifest) {
80
- root = legacyRoot;
81
- manifestPath = legacyManifestPath;
82
- manifest = legacyManifest;
83
- }
84
- }
60
+ const root = path.join(host.workspaceRoot, KNOWLEDGE_DIR);
61
+ const manifestPath = path.join(root, 'manifest.json');
62
+ const manifest = await readJson(host, manifestPath);
85
63
  if (!manifest) {
86
64
  return {
87
65
  ok: false,
@@ -98,8 +76,30 @@ async function queryProjectKnowledgeWithHost(host, args = {}) {
98
76
  },
99
77
  };
100
78
  }
101
- if (Number(manifest.schemaVersion) >= 2) {
102
- const database = asRecord(manifest.database);
79
+ const database = asRecord(manifest.database);
80
+ const foundSchemaVersion = Number(database.schemaVersion ?? manifest.schemaVersion) || 0;
81
+ if (Number(manifest.schemaVersion) !== CURRENT_KNOWLEDGE_SCHEMA_VERSION
82
+ || foundSchemaVersion !== CURRENT_KNOWLEDGE_SCHEMA_VERSION) {
83
+ return {
84
+ ok: false,
85
+ status: 'stale',
86
+ source: 'cwtools-project-knowledge-sqlite',
87
+ error: {
88
+ code: 'knowledge_schema_obsolete',
89
+ message: `Project knowledge schema V${foundSchemaVersion} is obsolete. Rebuild it with the current V${CURRENT_KNOWLEDGE_SCHEMA_VERSION} extension.`,
90
+ },
91
+ data: {
92
+ status: 'stale',
93
+ manifestPath,
94
+ rebuildRequired: true,
95
+ foundSchemaVersion,
96
+ currentSchemaVersion: CURRENT_KNOWLEDGE_SCHEMA_VERSION,
97
+ staleReasons: ['schema_version_obsolete'],
98
+ _hint: 'Run /init or reopen the project and wait for the automatic full rebuild. Old database schemas are not queried.',
99
+ },
100
+ };
101
+ }
102
+ {
103
103
  const relativeDatabasePath = typeof database.path === 'string' && database.path.trim()
104
104
  ? database.path
105
105
  : 'knowledge.sqlite';
@@ -143,67 +143,4 @@ async function queryProjectKnowledgeWithHost(host, args = {}) {
143
143
  },
144
144
  };
145
145
  }
146
- const manifestDomains = stringArray(manifest.domains);
147
- const domains = (args.domains?.length ? args.domains : manifestDomains)
148
- .map(value => value.trim().toLowerCase())
149
- .filter(Boolean)
150
- .slice(0, 12);
151
- const tokens = tokensFor(args);
152
- const limit = Math.max(1, Math.min(Number(args.limit ?? 80) || 80, 300));
153
- const evidence = [];
154
- const capabilities = [];
155
- for (const domain of domains) {
156
- const capability = await readJson(host, path.join(root, 'capabilities', `${domain}.json`));
157
- if (!capability)
158
- continue;
159
- capabilities.push({ domain, summary: capability.summary, evidencePolicy: capability.evidencePolicy });
160
- const candidates = [];
161
- if (Array.isArray(capability.definitions))
162
- candidates.push(...capability.definitions.map(asRecord));
163
- if (args.includeProjectPatterns !== false && Array.isArray(capability.projectExamples))
164
- candidates.push(...capability.projectExamples.map(asRecord));
165
- if (args.includeVanillaArchetypes !== false && Array.isArray(capability.vanillaArchetypes))
166
- candidates.push(...capability.vanillaArchetypes.map(asRecord));
167
- if (args.includeTopology !== false) {
168
- const topology = asRecord(capability.topology);
169
- if (Array.isArray(topology.edges))
170
- candidates.push(...topology.edges.map(asRecord));
171
- }
172
- evidence.push(...candidates
173
- .map(item => ({ ...item, domain, score: score(item, tokens) }))
174
- .filter(item => tokens.length === 0 || Number(item.score) > 0)
175
- .sort((a, b) => Number(b.score) - Number(a.score))
176
- .slice(0, Math.max(5, Math.ceil(limit / Math.max(1, domains.length)))));
177
- }
178
- const unresolvedFile = args.includeUnresolved === false
179
- ? undefined
180
- : await readJson(host, path.join(root, 'unresolved.json'));
181
- const unresolved = Array.isArray(unresolvedFile?.entries)
182
- ? unresolvedFile.entries.map(asRecord).slice(0, 100)
183
- : [];
184
- const manifestStatus = String(manifest.status ?? 'stale');
185
- const staleReasons = stringArray(manifest.staleReasons);
186
- const ready = manifestStatus === 'ready' && staleReasons.length === 0;
187
- return {
188
- ok: true,
189
- status: ready ? 'ready' : 'stale',
190
- source: 'cwtools-project-knowledge',
191
- data: {
192
- status: ready ? 'ready' : 'stale',
193
- manifestPath,
194
- generatedAt: manifest.generatedAt,
195
- game: manifest.game,
196
- graphVersion: manifest.graphVersion,
197
- staleReasons,
198
- domains,
199
- capabilities,
200
- evidence: evidence.slice(0, limit),
201
- unresolved,
202
- requiredNextChecks: [
203
- 'Use query_cwt_schema/query_rules/query_scope for legality before writing.',
204
- 'Use query_override_modes for target directories with vanilla definitions.',
205
- 'Read exact source blocks before approving a complex blueprint.',
206
- ],
207
- },
208
- };
209
146
  }
@@ -2,21 +2,31 @@ import type { HostServices } from '../host/hostServices';
2
2
  import type { SharedToolResult } from '../tools/schema';
3
3
  export type AgentMode = 'build' | 'plan' | 'explore' | 'general' | 'utility' | 'review' | 'gui_expert' | 'script_reviewer' | 'loc_translator' | 'loc_writer' | 'orchestrator' | 'script';
4
4
  export interface QueryProjectProfileArgs {
5
- section?: 'summary' | 'routing' | 'directories' | 'localisation' | 'identifiers' | 'validation' | 'promptCards' | 'all';
5
+ section?: 'summary' | 'routing' | 'directories' | 'localisation' | 'identifiers' | 'validation' | 'compatibility' | 'promptCards' | 'all';
6
6
  mode?: AgentMode | 'asset';
7
7
  }
8
8
  export interface ProjectProfile {
9
- schemaVersion: 1;
9
+ schemaVersion: 2;
10
10
  generatedAt: string;
11
11
  workspaceRoot: string;
12
12
  workspaceKind: string;
13
13
  projectName: string;
14
+ /** True when a schemaVersion 1 profile was read and normalized. */
15
+ legacyProfile?: boolean;
14
16
  game: {
15
17
  id: string;
16
18
  displayName: string;
17
19
  confidence: 'high' | 'medium' | 'low';
18
20
  evidence: string[];
19
21
  };
22
+ modInfo?: {
23
+ name?: string;
24
+ version?: string;
25
+ tags?: string[];
26
+ supportedVersion?: string;
27
+ remoteFileId?: string;
28
+ dependencies?: string[];
29
+ };
20
30
  keyDirectories: Array<{
21
31
  key: string;
22
32
  path: string;
@@ -26,12 +36,16 @@ export interface ProjectProfile {
26
36
  localisation: {
27
37
  roots: string[];
28
38
  languages: string[];
39
+ defaultLanguage?: string;
29
40
  encoding: string;
41
+ encodingByLanguage?: Record<string, string>;
30
42
  sampleFiles: string[];
31
43
  };
32
44
  identifiers: Record<string, unknown>;
33
45
  routing: Record<string, unknown>;
34
46
  validation: Record<string, unknown>;
47
+ freshness?: Record<string, unknown>;
48
+ warnings?: string[];
35
49
  promptCards: Partial<Record<AgentMode | 'asset', string>>;
36
50
  efficiencyHints: string[];
37
51
  [key: string]: unknown;
@@ -60,7 +60,7 @@ function getProjectProfilePath(workspaceRoot) {
60
60
  function isProjectProfile(value) {
61
61
  return !!value
62
62
  && typeof value === 'object'
63
- && value.schemaVersion === 1
63
+ && (value.schemaVersion === 1 || value.schemaVersion === 2)
64
64
  && typeof value.projectName === 'string';
65
65
  }
66
66
  function buildProfileSummary(profile) {
@@ -69,10 +69,11 @@ function buildProfileSummary(profile) {
69
69
  ? profile.identifiers.namespaces.slice(0, 8).join(', ') || 'none'
70
70
  : 'none';
71
71
  const languages = profile.localisation.languages.join(', ') || 'unknown';
72
+ const supportedVersion = typeof profile.modInfo?.supportedVersion === 'string' ? ` (${profile.modInfo.supportedVersion})` : '';
72
73
  return [
73
74
  `Project: ${profile.projectName}`,
74
75
  `Kind: ${profile.workspaceKind}`,
75
- `Game: ${profile.game.displayName}`,
76
+ `Game: ${profile.game.displayName}${supportedVersion}`,
76
77
  `Key dirs: ${dirs}`,
77
78
  `Namespaces: ${namespaces}`,
78
79
  `Localisation: ${languages} (${profile.localisation.encoding})`,
@@ -96,6 +97,13 @@ function selectProfileSection(profile, section) {
96
97
  case 'localisation': return profile.localisation;
97
98
  case 'identifiers': return profile.identifiers;
98
99
  case 'validation': return profile.validation;
100
+ case 'compatibility': return {
101
+ supportedVersion: profile.modInfo?.supportedVersion,
102
+ remoteFileId: profile.modInfo?.remoteFileId,
103
+ dependencies: profile.modInfo?.dependencies ?? [],
104
+ vanillaCache: profile.validation?.vanillaCache,
105
+ game: profile.game,
106
+ };
99
107
  case 'promptCards': return profile.promptCards;
100
108
  case 'all': return profile;
101
109
  case 'summary':
@@ -104,7 +112,10 @@ function selectProfileSection(profile, section) {
104
112
  workspaceKind: profile.workspaceKind,
105
113
  projectName: profile.projectName,
106
114
  game: profile.game,
115
+ supportedVersion: profile.modInfo?.supportedVersion,
107
116
  generatedAt: profile.generatedAt,
117
+ freshness: profile.freshness,
118
+ warnings: profile.warnings ?? [],
108
119
  efficiencyHints: profile.efficiencyHints,
109
120
  };
110
121
  }
@@ -137,7 +148,7 @@ async function queryProjectProfileWithHost(host, args = {}) {
137
148
  source: 'cwtools-shared',
138
149
  error: {
139
150
  code: 'invalid_profile',
140
- message: 'Project profile exists but is not schemaVersion 1.',
151
+ message: 'Project profile exists but is not a valid schemaVersion 1 or 2 profile.',
141
152
  },
142
153
  };
143
154
  }
@@ -1,6 +1,6 @@
1
- export declare const MCP_TOOL_NAMES: readonly ["query_types", "query_rules", "query_cwt_schema", "search_rule_capabilities", "explain_scope", "parse_pdx_fragment", "query_scope", "get_diagnostics", "analyze_diagnostic_error", "query_project_profile", "query_project_knowledge", "query_workspace_index", "explore_pdx_project", "query_localisation_index", "get_pdx_block", "get_completion_at", "document_symbols", "workspace_symbols", "query_definition", "query_definition_by_name", "query_references", "query_scripted_effects", "query_scripted_triggers", "query_enums", "query_static_modifiers", "query_variables", "get_entity_info", "query_shader_symbol", "query_shader_compile_unit", "query_shader_platform_variants", "query_shader_callers", "explain_shader_reachability", "validate_shader", "compare_shader_with_vanilla"];
1
+ export declare const MCP_TOOL_NAMES: readonly ["query_types", "query_rules", "query_cwt_schema", "search_rule_capabilities", "explain_scope", "parse_pdx_fragment", "query_scope", "get_diagnostics", "analyze_diagnostic_error", "query_project_profile", "query_project_knowledge", "query_workspace_index", "explore_pdx_project", "query_inline_instantiation", "analyze_pdx_flow", "compare_definition_with_vanilla", "query_localisation_index", "get_pdx_block", "get_completion_at", "document_symbols", "workspace_symbols", "query_definition", "query_definition_by_name", "query_references", "query_scripted_effects", "query_scripted_triggers", "query_enums", "query_static_modifiers", "query_variables", "get_entity_info", "query_shader_symbol", "query_shader_compile_unit", "query_shader_platform_variants", "query_shader_callers", "explain_shader_reachability", "validate_shader", "compare_shader_with_vanilla"];
2
2
  export type McpToolName = typeof MCP_TOOL_NAMES[number];
3
3
  export declare const MCP_WRITE_TOOL_NAMES: readonly [];
4
- export declare const MCP_READONLY_TOOL_NAMES: readonly ["query_types", "query_rules", "query_cwt_schema", "search_rule_capabilities", "explain_scope", "parse_pdx_fragment", "query_scope", "get_diagnostics", "analyze_diagnostic_error", "query_project_profile", "query_project_knowledge", "query_workspace_index", "explore_pdx_project", "query_localisation_index", "get_pdx_block", "get_completion_at", "document_symbols", "workspace_symbols", "query_definition", "query_definition_by_name", "query_references", "query_scripted_effects", "query_scripted_triggers", "query_enums", "query_static_modifiers", "query_variables", "get_entity_info", "query_shader_symbol", "query_shader_compile_unit", "query_shader_platform_variants", "query_shader_callers", "explain_shader_reachability", "validate_shader", "compare_shader_with_vanilla"];
4
+ export declare const MCP_READONLY_TOOL_NAMES: readonly ["query_types", "query_rules", "query_cwt_schema", "search_rule_capabilities", "explain_scope", "parse_pdx_fragment", "query_scope", "get_diagnostics", "analyze_diagnostic_error", "query_project_profile", "query_project_knowledge", "query_workspace_index", "explore_pdx_project", "query_inline_instantiation", "analyze_pdx_flow", "compare_definition_with_vanilla", "query_localisation_index", "get_pdx_block", "get_completion_at", "document_symbols", "workspace_symbols", "query_definition", "query_definition_by_name", "query_references", "query_scripted_effects", "query_scripted_triggers", "query_enums", "query_static_modifiers", "query_variables", "get_entity_info", "query_shader_symbol", "query_shader_compile_unit", "query_shader_platform_variants", "query_shader_callers", "explain_shader_reachability", "validate_shader", "compare_shader_with_vanilla"];
5
5
  export declare function isMcpToolName(value: string): value is McpToolName;
6
6
  export declare function isMcpWriteToolName(value: string): value is typeof MCP_WRITE_TOOL_NAMES[number];
@@ -17,6 +17,9 @@ exports.MCP_TOOL_NAMES = [
17
17
  'query_project_knowledge',
18
18
  'query_workspace_index',
19
19
  'explore_pdx_project',
20
+ 'query_inline_instantiation',
21
+ 'analyze_pdx_flow',
22
+ 'compare_definition_with_vanilla',
20
23
  'query_localisation_index',
21
24
  'get_pdx_block',
22
25
  'get_completion_at',
@@ -108,17 +108,83 @@ const defaultSharedToolDispatcher = async (host, name, rawArgs) => {
108
108
  args.maxEdges ?? 80,
109
109
  args.includeMetadata !== false,
110
110
  ], 'Start the CWTools LSP process and wait for the game model to load before exploring the semantic graph.');
111
+ case 'analyze_pdx_flow': {
112
+ const file = typeof args.file === 'string' ? args.file.trim() : '';
113
+ const definitionId = typeof args.definitionId === 'string' ? args.definitionId.trim() : '';
114
+ if (!file && !definitionId) {
115
+ return {
116
+ ok: false,
117
+ status: 'error',
118
+ source: 'cwtools-shared',
119
+ error: {
120
+ code: 'invalid_arguments',
121
+ message: 'analyze_pdx_flow requires a file or a definitionId.',
122
+ },
123
+ };
124
+ }
125
+ return executeLspTool(host, 'cwtools.ai.analyzePdxFlow', [{
126
+ ...(file ? { file } : {}),
127
+ ...(definitionId ? { definitionId } : {}),
128
+ ...(typeof args.entityType === 'string' && args.entityType.trim() ? { entityType: args.entityType.trim() } : {}),
129
+ limit: Math.max(1, Math.min(Number(args.limit ?? 100) || 100, 500)),
130
+ }], 'Start the CWTools LSP process and wait for the game model to load before analyzing flow cost and gameplay relations.');
131
+ }
132
+ case 'compare_definition_with_vanilla': {
133
+ if (typeof args.entityType !== 'string' || typeof args.symbolId !== 'string' || !args.entityType.trim() || !args.symbolId.trim()) {
134
+ return {
135
+ ok: false,
136
+ status: 'error',
137
+ source: 'cwtools-shared',
138
+ error: {
139
+ code: 'invalid_arguments',
140
+ message: 'compare_definition_with_vanilla requires entityType and symbolId.',
141
+ },
142
+ };
143
+ }
144
+ return executeLspTool(host, 'cwtools.ai.compareDefinitionWithVanilla', [args.entityType.trim(), args.symbolId.trim()], 'Start the CWTools LSP process and wait for the game model to load before comparing the definition with vanilla.');
145
+ }
146
+ case 'query_inline_instantiation': {
147
+ const template = typeof args.template === 'string' ? args.template.trim() : '';
148
+ const file = typeof args.file === 'string' ? args.file.trim() : '';
149
+ if (!template && !file) {
150
+ return {
151
+ ok: false,
152
+ status: 'error',
153
+ source: 'cwtools-shared',
154
+ error: {
155
+ code: 'invalid_arguments',
156
+ message: 'query_inline_instantiation requires a template path or a caller file (optionally with line).',
157
+ },
158
+ };
159
+ }
160
+ return executeLspTool(host, 'cwtools.ai.exploreInlineGraph', [{
161
+ ...(template ? { template } : {}),
162
+ ...(file ? { file } : {}),
163
+ ...(Number.isInteger(args.line) ? { line: Number(args.line) } : {}),
164
+ limit: Math.max(1, Math.min(Number(args.limit ?? 50) || 50, 200)),
165
+ }], 'Start the CWTools LSP process and wait for the game model to load before resolving the inline template instantiation.');
166
+ }
111
167
  case 'query_localisation_index': {
112
168
  if (!host.indexing) {
113
169
  return (0, schema_1.toolUnavailable)(name, 'Localisation index is not available in this host.', [
114
170
  'Wire an LSP index command or a thin Node index before using this tool for localisation evidence.',
115
171
  ]);
116
172
  }
173
+ const data = await host.indexing.queryLocalisation(args);
174
+ const semanticAudit = args.auditMode === true
175
+ ? await executeLspTool(host, 'cwtools.ai.queryLocalisationAudit', [{
176
+ ...(typeof args.key === 'string' ? { key: args.key } : {}),
177
+ prefix: args.prefix === true,
178
+ contains: args.contains === true,
179
+ caseSensitive: args.caseSensitive === true,
180
+ limit: Math.max(1, Math.min(Number(args.limit ?? 100) || 100, 500)),
181
+ }], 'Start the CWTools LSP process and wait for localisation validation before requesting auditMode.')
182
+ : undefined;
117
183
  return {
118
184
  ok: true,
119
185
  status: 'ready',
120
186
  source: 'cwtools-index',
121
- data: await host.indexing.queryLocalisation(args),
187
+ data: semanticAudit ? { ...data, semanticAudit } : data,
122
188
  };
123
189
  }
124
190
  case 'get_diagnostics':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cwtools-shared",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared protocol and tool contracts for the CWTools MCP server",
5
5
  "license": "MIT",
6
6
  "repository": {