cwtools-shared 0.1.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.
- package/dist/generated/mcpTools.d.ts +1112 -0
- package/dist/generated/mcpTools.js +1247 -0
- package/dist/host/diagnostics.d.ts +41 -0
- package/dist/host/diagnostics.js +18 -0
- package/dist/host/filesystem.d.ts +18 -0
- package/dist/host/filesystem.js +2 -0
- package/dist/host/hostServices.d.ts +49 -0
- package/dist/host/hostServices.js +2 -0
- package/dist/host/indexing.d.ts +54 -0
- package/dist/host/indexing.js +2 -0
- package/dist/host/lsp.d.ts +8 -0
- package/dist/host/lsp.js +27 -0
- package/dist/host/readiness.d.ts +11 -0
- package/dist/host/readiness.js +71 -0
- package/dist/host/vanillaCache.d.ts +12 -0
- package/dist/host/vanillaCache.js +56 -0
- package/dist/host/vsCodeHostServices.d.ts +27 -0
- package/dist/host/vsCodeHostServices.js +30 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +41 -0
- package/dist/knowledge/diagnosticRouting.d.ts +13 -0
- package/dist/knowledge/diagnosticRouting.js +57 -0
- package/dist/knowledge/gameKnowledge.d.ts +20 -0
- package/dist/knowledge/gameKnowledge.js +52 -0
- package/dist/knowledge/rules.d.ts +162 -0
- package/dist/knowledge/rules.js +1383 -0
- package/dist/knowledge/workflowHints.d.ts +11 -0
- package/dist/knowledge/workflowHints.js +31 -0
- package/dist/project/knowledge.d.ts +15 -0
- package/dist/project/knowledge.js +209 -0
- package/dist/project/profile.d.ts +45 -0
- package/dist/project/profile.js +177 -0
- package/dist/safety/localisation.d.ts +33 -0
- package/dist/safety/localisation.js +105 -0
- package/dist/safety/paths.d.ts +21 -0
- package/dist/safety/paths.js +122 -0
- package/dist/safety/writes.d.ts +3 -0
- package/dist/safety/writes.js +19 -0
- package/dist/tools/mcpSchema.d.ts +5 -0
- package/dist/tools/mcpSchema.js +19 -0
- package/dist/tools/names.d.ts +6 -0
- package/dist/tools/names.js +51 -0
- package/dist/tools/pdxBlock.d.ts +17 -0
- package/dist/tools/pdxBlock.js +140 -0
- package/dist/tools/registry.d.ts +10 -0
- package/dist/tools/registry.js +2 -0
- package/dist/tools/schema.d.ts +36 -0
- package/dist/tools/schema.js +21 -0
- package/dist/tools/symbols.d.ts +60 -0
- package/dist/tools/symbols.js +625 -0
- package/dist/tools/toolHandlers.d.ts +4 -0
- package/dist/tools/toolHandlers.js +291 -0
- package/package.json +24 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queryWorkflowHints = queryWorkflowHints;
|
|
4
|
+
function queryWorkflowHints() {
|
|
5
|
+
return {
|
|
6
|
+
status: 'ready',
|
|
7
|
+
hints: [
|
|
8
|
+
{
|
|
9
|
+
id: 'diagnostic-fix',
|
|
10
|
+
title: 'Fix CWTools diagnostics',
|
|
11
|
+
triggers: ['diagnostic', 'error', 'warning'],
|
|
12
|
+
recommendedTools: ['get_diagnostics', 'analyze_diagnostic_error', 'get_pdx_block', 'query_cwt_schema', 'query_rules'],
|
|
13
|
+
guardrails: ['Verify after writes.', 'Do not suppress diagnostics instead of fixing them.'],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'loc-generation',
|
|
17
|
+
title: 'Generate or update localisation',
|
|
18
|
+
triggers: ['localisation', 'localization', 'translation'],
|
|
19
|
+
recommendedTools: ['query_localisation_index', 'write_localisation'],
|
|
20
|
+
guardrails: ['Do not write .yml files through generic file tools.', 'Preserve language header and BOM.'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'entity-lookup',
|
|
24
|
+
title: 'Verify game identifiers',
|
|
25
|
+
triggers: ['identifier', 'reference', 'definition', 'entity'],
|
|
26
|
+
recommendedTools: ['query_project_knowledge', 'explore_pdx_project', 'query_cwt_schema', 'search_rule_capabilities', 'query_rules', 'query_types', 'query_workspace_index', 'query_definition_by_name'],
|
|
27
|
+
guardrails: ['Treat empty text search results as inconclusive without indexed verification.', 'Treat semanticHints as retrieval hints, not legality proof.'],
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { HostServices } from '../host/hostServices';
|
|
2
|
+
import type { SharedToolResult } from '../tools/schema';
|
|
3
|
+
export interface QueryProjectKnowledgeArgs {
|
|
4
|
+
intent?: string;
|
|
5
|
+
domains?: string[];
|
|
6
|
+
identifiers?: string[];
|
|
7
|
+
entityTypes?: string[];
|
|
8
|
+
includeProjectPatterns?: boolean;
|
|
9
|
+
includeVanillaArchetypes?: boolean;
|
|
10
|
+
includeTopology?: boolean;
|
|
11
|
+
includeUnresolved?: boolean;
|
|
12
|
+
includeEventGraph?: boolean;
|
|
13
|
+
limit?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function queryProjectKnowledgeWithHost(host: HostServices, args?: QueryProjectKnowledgeArgs): Promise<SharedToolResult>;
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.queryProjectKnowledgeWithHost = queryProjectKnowledgeWithHost;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const KNOWLEDGE_DIR = path.join('.cwtools', 'project', 'knowledge');
|
|
39
|
+
function asRecord(value) {
|
|
40
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
41
|
+
? value
|
|
42
|
+
: {};
|
|
43
|
+
}
|
|
44
|
+
function stringArray(value) {
|
|
45
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
46
|
+
}
|
|
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
|
+
async function readJson(host, filePath) {
|
|
61
|
+
const read = await host.filesystem.readTextFile(filePath);
|
|
62
|
+
if (!read.exists)
|
|
63
|
+
return undefined;
|
|
64
|
+
try {
|
|
65
|
+
return asRecord(JSON.parse(read.content));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
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
|
+
}
|
|
85
|
+
if (!manifest) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
status: 'unavailable',
|
|
89
|
+
source: 'cwtools-project-knowledge',
|
|
90
|
+
error: { code: 'knowledge_missing', message: 'Project knowledge pack is missing.' },
|
|
91
|
+
data: {
|
|
92
|
+
status: 'missing',
|
|
93
|
+
manifestPath,
|
|
94
|
+
domains: [],
|
|
95
|
+
evidence: [],
|
|
96
|
+
unresolved: [],
|
|
97
|
+
_hint: 'Run /init in the VS Code extension and wait for the deep semantic phase to complete.',
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (Number(manifest.schemaVersion) >= 2) {
|
|
102
|
+
const database = asRecord(manifest.database);
|
|
103
|
+
const relativeDatabasePath = typeof database.path === 'string' && database.path.trim()
|
|
104
|
+
? database.path
|
|
105
|
+
: 'knowledge.sqlite';
|
|
106
|
+
const databasePath = path.resolve(root, relativeDatabasePath);
|
|
107
|
+
const relative = path.relative(root, databasePath);
|
|
108
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
109
|
+
return {
|
|
110
|
+
ok: false,
|
|
111
|
+
status: 'error',
|
|
112
|
+
source: 'cwtools-project-knowledge-sqlite',
|
|
113
|
+
error: { code: 'invalid_database_path', message: 'Project knowledge database path escapes the knowledge directory.' },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const result = asRecord(await host.lsp.executeCommand('cwtools.ai.queryProjectKnowledgeDb', [{ databasePath, ...args, includeEventGraph: args.includeEventGraph !== false }], { timeoutMs: 30000 }));
|
|
117
|
+
if (result.ok !== true) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
status: 'error',
|
|
121
|
+
source: 'cwtools-project-knowledge-sqlite',
|
|
122
|
+
error: {
|
|
123
|
+
code: 'knowledge_query_failed',
|
|
124
|
+
message: typeof result.error === 'string' ? result.error : 'Project knowledge SQLite query failed.',
|
|
125
|
+
},
|
|
126
|
+
data: { manifestPath, databasePath },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const manifestStatus = String(manifest.status ?? 'stale');
|
|
130
|
+
const staleReasons = stringArray(manifest.staleReasons);
|
|
131
|
+
const ready = String(result.status ?? manifestStatus) === 'ready' && manifestStatus === 'ready' && staleReasons.length === 0;
|
|
132
|
+
const partial = staleReasons.length === 0
|
|
133
|
+
&& (String(result.status ?? manifestStatus) === 'partial' || manifestStatus === 'partial');
|
|
134
|
+
return {
|
|
135
|
+
ok: true,
|
|
136
|
+
status: ready ? 'ready' : partial ? 'partial' : 'stale',
|
|
137
|
+
source: 'cwtools-project-knowledge-sqlite',
|
|
138
|
+
data: {
|
|
139
|
+
...result,
|
|
140
|
+
status: ready ? 'ready' : partial ? 'partial' : 'stale',
|
|
141
|
+
manifestPath,
|
|
142
|
+
staleReasons,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
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
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { HostServices } from '../host/hostServices';
|
|
2
|
+
import type { SharedToolResult } from '../tools/schema';
|
|
3
|
+
export type AgentMode = 'build' | 'plan' | 'explore' | 'general' | 'utility' | 'review' | 'gui_expert' | 'script_reviewer' | 'loc_translator' | 'loc_writer' | 'orchestrator' | 'script';
|
|
4
|
+
export interface QueryProjectProfileArgs {
|
|
5
|
+
section?: 'summary' | 'routing' | 'directories' | 'localisation' | 'identifiers' | 'validation' | 'promptCards' | 'all';
|
|
6
|
+
mode?: AgentMode | 'asset';
|
|
7
|
+
}
|
|
8
|
+
export interface ProjectProfile {
|
|
9
|
+
schemaVersion: 1;
|
|
10
|
+
generatedAt: string;
|
|
11
|
+
workspaceRoot: string;
|
|
12
|
+
workspaceKind: string;
|
|
13
|
+
projectName: string;
|
|
14
|
+
game: {
|
|
15
|
+
id: string;
|
|
16
|
+
displayName: string;
|
|
17
|
+
confidence: 'high' | 'medium' | 'low';
|
|
18
|
+
evidence: string[];
|
|
19
|
+
};
|
|
20
|
+
keyDirectories: Array<{
|
|
21
|
+
key: string;
|
|
22
|
+
path: string;
|
|
23
|
+
exists: boolean;
|
|
24
|
+
fileCount?: number;
|
|
25
|
+
}>;
|
|
26
|
+
localisation: {
|
|
27
|
+
roots: string[];
|
|
28
|
+
languages: string[];
|
|
29
|
+
encoding: string;
|
|
30
|
+
sampleFiles: string[];
|
|
31
|
+
};
|
|
32
|
+
identifiers: Record<string, unknown>;
|
|
33
|
+
routing: Record<string, unknown>;
|
|
34
|
+
validation: Record<string, unknown>;
|
|
35
|
+
promptCards: Partial<Record<AgentMode | 'asset', string>>;
|
|
36
|
+
efficiencyHints: string[];
|
|
37
|
+
[key: string]: unknown;
|
|
38
|
+
}
|
|
39
|
+
export declare const PROJECT_PROFILE_RELATIVE_PATH: string;
|
|
40
|
+
export declare function getProjectProfilePath(workspaceRoot: string): string;
|
|
41
|
+
export declare function isProjectProfile(value: unknown): value is ProjectProfile;
|
|
42
|
+
export declare function buildProfileSummary(profile: ProjectProfile): string;
|
|
43
|
+
export declare function getPromptCardForMode(profile: ProjectProfile, mode?: AgentMode | 'asset'): string | undefined;
|
|
44
|
+
export declare function selectProfileSection(profile: ProjectProfile, section: NonNullable<QueryProjectProfileArgs['section']>): unknown;
|
|
45
|
+
export declare function queryProjectProfileWithHost(host: HostServices, args?: QueryProjectProfileArgs): Promise<SharedToolResult>;
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.PROJECT_PROFILE_RELATIVE_PATH = void 0;
|
|
37
|
+
exports.getProjectProfilePath = getProjectProfilePath;
|
|
38
|
+
exports.isProjectProfile = isProjectProfile;
|
|
39
|
+
exports.buildProfileSummary = buildProfileSummary;
|
|
40
|
+
exports.getPromptCardForMode = getPromptCardForMode;
|
|
41
|
+
exports.selectProfileSection = selectProfileSection;
|
|
42
|
+
exports.queryProjectProfileWithHost = queryProjectProfileWithHost;
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
exports.PROJECT_PROFILE_RELATIVE_PATH = path.join('.cwtools', 'project', 'profile.json');
|
|
45
|
+
function getProjectProfilePath(workspaceRoot) {
|
|
46
|
+
try {
|
|
47
|
+
const fs = require('fs');
|
|
48
|
+
const primary = path.join(workspaceRoot, '.cwtools', 'project', 'profile.json');
|
|
49
|
+
if (fs.existsSync(primary))
|
|
50
|
+
return primary;
|
|
51
|
+
const legacy = path.join(workspaceRoot, '.cwtools-ai', 'project', 'profile.json');
|
|
52
|
+
if (fs.existsSync(legacy))
|
|
53
|
+
return legacy;
|
|
54
|
+
return primary;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return path.join(workspaceRoot, exports.PROJECT_PROFILE_RELATIVE_PATH);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function isProjectProfile(value) {
|
|
61
|
+
return !!value
|
|
62
|
+
&& typeof value === 'object'
|
|
63
|
+
&& value.schemaVersion === 1
|
|
64
|
+
&& typeof value.projectName === 'string';
|
|
65
|
+
}
|
|
66
|
+
function buildProfileSummary(profile) {
|
|
67
|
+
const dirs = profile.keyDirectories.filter(dir => dir.exists).map(dir => dir.path).slice(0, 8).join(', ') || 'none';
|
|
68
|
+
const namespaces = Array.isArray(profile.identifiers.namespaces)
|
|
69
|
+
? profile.identifiers.namespaces.slice(0, 8).join(', ') || 'none'
|
|
70
|
+
: 'none';
|
|
71
|
+
const languages = profile.localisation.languages.join(', ') || 'unknown';
|
|
72
|
+
return [
|
|
73
|
+
`Project: ${profile.projectName}`,
|
|
74
|
+
`Kind: ${profile.workspaceKind}`,
|
|
75
|
+
`Game: ${profile.game.displayName}`,
|
|
76
|
+
`Key dirs: ${dirs}`,
|
|
77
|
+
`Namespaces: ${namespaces}`,
|
|
78
|
+
`Localisation: ${languages} (${profile.localisation.encoding})`,
|
|
79
|
+
].join('\n');
|
|
80
|
+
}
|
|
81
|
+
function getPromptCardForMode(profile, mode) {
|
|
82
|
+
if (!mode)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (mode === 'loc_translator' || mode === 'loc_writer')
|
|
85
|
+
return profile.promptCards.loc_writer ?? profile.promptCards.build;
|
|
86
|
+
if (mode === 'gui_expert')
|
|
87
|
+
return profile.promptCards.asset ?? profile.promptCards.build;
|
|
88
|
+
if (mode === 'script_reviewer')
|
|
89
|
+
return profile.promptCards.review;
|
|
90
|
+
return profile.promptCards[mode] ?? profile.promptCards.build;
|
|
91
|
+
}
|
|
92
|
+
function selectProfileSection(profile, section) {
|
|
93
|
+
switch (section) {
|
|
94
|
+
case 'routing': return profile.routing;
|
|
95
|
+
case 'directories': return profile.keyDirectories;
|
|
96
|
+
case 'localisation': return profile.localisation;
|
|
97
|
+
case 'identifiers': return profile.identifiers;
|
|
98
|
+
case 'validation': return profile.validation;
|
|
99
|
+
case 'promptCards': return profile.promptCards;
|
|
100
|
+
case 'all': return profile;
|
|
101
|
+
case 'summary':
|
|
102
|
+
default:
|
|
103
|
+
return {
|
|
104
|
+
workspaceKind: profile.workspaceKind,
|
|
105
|
+
projectName: profile.projectName,
|
|
106
|
+
game: profile.game,
|
|
107
|
+
generatedAt: profile.generatedAt,
|
|
108
|
+
efficiencyHints: profile.efficiencyHints,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function queryProjectProfileWithHost(host, args = {}) {
|
|
113
|
+
const profilePath = getProjectProfilePath(host.workspaceRoot);
|
|
114
|
+
try {
|
|
115
|
+
const read = await host.filesystem.readTextFile(profilePath);
|
|
116
|
+
if (!read.exists) {
|
|
117
|
+
return {
|
|
118
|
+
ok: false,
|
|
119
|
+
status: 'unavailable',
|
|
120
|
+
source: 'cwtools-shared',
|
|
121
|
+
error: {
|
|
122
|
+
code: 'profile_missing',
|
|
123
|
+
message: 'Project profile is missing.',
|
|
124
|
+
},
|
|
125
|
+
data: {
|
|
126
|
+
status: 'missing',
|
|
127
|
+
profilePath,
|
|
128
|
+
_hint: 'Run /init in the VS Code extension or create .cwtools/project/profile.json, then retry.',
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const parsed = JSON.parse(read.content);
|
|
133
|
+
if (!isProjectProfile(parsed)) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
status: 'error',
|
|
137
|
+
source: 'cwtools-shared',
|
|
138
|
+
error: {
|
|
139
|
+
code: 'invalid_profile',
|
|
140
|
+
message: 'Project profile exists but is not schemaVersion 1.',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
const section = args.section ?? 'summary';
|
|
145
|
+
return {
|
|
146
|
+
ok: true,
|
|
147
|
+
status: 'ready',
|
|
148
|
+
source: 'cwtools-shared',
|
|
149
|
+
data: {
|
|
150
|
+
status: 'ready',
|
|
151
|
+
profilePath,
|
|
152
|
+
generatedAt: parsed.generatedAt,
|
|
153
|
+
section,
|
|
154
|
+
profile: section === 'all' ? parsed : undefined,
|
|
155
|
+
summary: buildProfileSummary(parsed),
|
|
156
|
+
data: selectProfileSection(parsed, section),
|
|
157
|
+
promptCard: getPromptCardForMode(parsed, args.mode),
|
|
158
|
+
_hint: 'Use section="routing", "localisation", "identifiers", or a mode-specific promptCard for targeted context.',
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
status: 'error',
|
|
166
|
+
source: 'cwtools-shared',
|
|
167
|
+
error: {
|
|
168
|
+
code: 'profile_error',
|
|
169
|
+
message: error instanceof Error ? error.message : String(error),
|
|
170
|
+
},
|
|
171
|
+
data: {
|
|
172
|
+
status: 'error',
|
|
173
|
+
profilePath,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { HostServices } from '../host/hostServices';
|
|
2
|
+
import type { SharedToolResult } from '../tools/schema';
|
|
3
|
+
export interface LocalisationEntry {
|
|
4
|
+
key: string;
|
|
5
|
+
value: string;
|
|
6
|
+
number?: number;
|
|
7
|
+
comment?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface UpsertLocalisationResult {
|
|
10
|
+
content: string;
|
|
11
|
+
added: number;
|
|
12
|
+
updated: number;
|
|
13
|
+
hasBom: boolean;
|
|
14
|
+
language: string;
|
|
15
|
+
keys: string[];
|
|
16
|
+
}
|
|
17
|
+
export interface WriteLocalisationArgs {
|
|
18
|
+
filePath: string;
|
|
19
|
+
language?: string;
|
|
20
|
+
entries: LocalisationEntry[];
|
|
21
|
+
}
|
|
22
|
+
export interface WriteLocalisationResult {
|
|
23
|
+
success: boolean;
|
|
24
|
+
message: string;
|
|
25
|
+
filePath?: string;
|
|
26
|
+
relativePath?: string;
|
|
27
|
+
added?: number;
|
|
28
|
+
updated?: number;
|
|
29
|
+
keys?: string[];
|
|
30
|
+
}
|
|
31
|
+
export declare function sanitizeLocalisationValue(value: string): string;
|
|
32
|
+
export declare function upsertLocalisationText(existingContent: string | null | undefined, language: string | undefined, entries: LocalisationEntry[]): UpsertLocalisationResult;
|
|
33
|
+
export declare function writeLocalisationWithHost(host: HostServices, args: WriteLocalisationArgs): Promise<SharedToolResult<WriteLocalisationResult>>;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sanitizeLocalisationValue = sanitizeLocalisationValue;
|
|
4
|
+
exports.upsertLocalisationText = upsertLocalisationText;
|
|
5
|
+
exports.writeLocalisationWithHost = writeLocalisationWithHost;
|
|
6
|
+
const paths_1 = require("./paths");
|
|
7
|
+
const writes_1 = require("./writes");
|
|
8
|
+
const schema_1 = require("../tools/schema");
|
|
9
|
+
const BOM = '\uFEFF';
|
|
10
|
+
function sanitizeLocalisationValue(value) {
|
|
11
|
+
return value
|
|
12
|
+
.replace(/\r\n/g, String.raw `\n`)
|
|
13
|
+
.replace(/\n/g, String.raw `\n`)
|
|
14
|
+
.replace(/\r/g, '')
|
|
15
|
+
.replace(/\t/g, String.raw `\t`)
|
|
16
|
+
.replace(/\u201C|\u201D/g, '"')
|
|
17
|
+
.replace(/\u2018|\u2019/g, "'");
|
|
18
|
+
}
|
|
19
|
+
function upsertLocalisationText(existingContent, language = 'l_english', entries) {
|
|
20
|
+
const raw = existingContent ?? '';
|
|
21
|
+
const existingHasBom = raw.charCodeAt(0) === 0xfeff;
|
|
22
|
+
const isNewFile = raw.length === 0;
|
|
23
|
+
const clean = existingHasBom ? raw.slice(1) : raw;
|
|
24
|
+
const lines = clean.length > 0 ? clean.split(/\r?\n/) : [];
|
|
25
|
+
const header = `${language}:`;
|
|
26
|
+
const firstNonEmpty = lines.findIndex(line => line.trim().length > 0);
|
|
27
|
+
if (firstNonEmpty === -1) {
|
|
28
|
+
lines.splice(0, lines.length, header);
|
|
29
|
+
}
|
|
30
|
+
else if (!/^l_[a-z_]+:\s*$/i.test(lines[firstNonEmpty].trim())) {
|
|
31
|
+
lines.splice(firstNonEmpty, 0, header);
|
|
32
|
+
}
|
|
33
|
+
const keyLineMap = new Map();
|
|
34
|
+
const keyRegex = /^\s*([\w.-]+):\d*\s*"/;
|
|
35
|
+
for (let index = 0; index < lines.length; index++) {
|
|
36
|
+
const match = lines[index].match(keyRegex);
|
|
37
|
+
if (match?.[1])
|
|
38
|
+
keyLineMap.set(match[1], index);
|
|
39
|
+
}
|
|
40
|
+
const appendLines = [];
|
|
41
|
+
let added = 0;
|
|
42
|
+
let updated = 0;
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const number = entry.number ?? 0;
|
|
45
|
+
const formattedLine = ` ${entry.key}:${number} "${sanitizeLocalisationValue(entry.value)}"`;
|
|
46
|
+
const existingIndex = keyLineMap.get(entry.key);
|
|
47
|
+
if (existingIndex !== undefined) {
|
|
48
|
+
lines[existingIndex] = formattedLine;
|
|
49
|
+
updated++;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
if (entry.comment)
|
|
53
|
+
appendLines.push(` ${entry.comment}`);
|
|
54
|
+
appendLines.push(formattedLine);
|
|
55
|
+
added++;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (appendLines.length > 0) {
|
|
59
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === '')
|
|
60
|
+
lines.pop();
|
|
61
|
+
lines.push(...appendLines);
|
|
62
|
+
}
|
|
63
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === '')
|
|
64
|
+
lines.pop();
|
|
65
|
+
const hasBom = existingHasBom || isNewFile;
|
|
66
|
+
const body = `${lines.join('\n')}\n`;
|
|
67
|
+
return {
|
|
68
|
+
content: `${hasBom ? BOM : ''}${body}`,
|
|
69
|
+
added,
|
|
70
|
+
updated,
|
|
71
|
+
hasBom,
|
|
72
|
+
language,
|
|
73
|
+
keys: entries.map(entry => entry.key),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function writeLocalisationWithHost(host, args) {
|
|
77
|
+
const writeDenied = (0, writes_1.ensureToolWriteAllowed)(host, 'write_localisation');
|
|
78
|
+
if (writeDenied)
|
|
79
|
+
return writeDenied;
|
|
80
|
+
if (!Array.isArray(args.entries) || args.entries.length === 0) {
|
|
81
|
+
return (0, schema_1.toolDenied)('invalid_arguments', 'write_localisation requires at least one entry.');
|
|
82
|
+
}
|
|
83
|
+
const pathValidation = (0, paths_1.validateLocalisationPath)(host.workspaceRoot, args.filePath);
|
|
84
|
+
if (!pathValidation.ok || !pathValidation.resolvedPath) {
|
|
85
|
+
return (0, schema_1.toolDenied)(pathValidation.reason ?? 'invalid_path', pathValidation.message ?? 'Invalid localisation path.');
|
|
86
|
+
}
|
|
87
|
+
const existing = await host.filesystem.readTextFile(pathValidation.resolvedPath);
|
|
88
|
+
const upsert = upsertLocalisationText(existing.exists ? existing.content : null, args.language ?? 'l_english', args.entries);
|
|
89
|
+
await host.filesystem.writeTextFile(pathValidation.resolvedPath, upsert.content);
|
|
90
|
+
await host.indexing?.invalidate?.(pathValidation.resolvedPath);
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
status: 'success',
|
|
94
|
+
source: 'cwtools-shared',
|
|
95
|
+
data: {
|
|
96
|
+
success: true,
|
|
97
|
+
message: `Localisation updated: ${upsert.added} added, ${upsert.updated} updated.`,
|
|
98
|
+
filePath: pathValidation.resolvedPath,
|
|
99
|
+
relativePath: pathValidation.relativePath,
|
|
100
|
+
added: upsert.added,
|
|
101
|
+
updated: upsert.updated,
|
|
102
|
+
keys: upsert.keys,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface WorkspacePathResolution {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
input: string;
|
|
4
|
+
workspaceRoot: string;
|
|
5
|
+
resolvedPath?: string;
|
|
6
|
+
relativePath?: string;
|
|
7
|
+
reason?: 'empty_path' | 'outside_workspace';
|
|
8
|
+
}
|
|
9
|
+
export interface LocalisationPathValidation {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
resolvedPath?: string;
|
|
12
|
+
relativePath?: string;
|
|
13
|
+
reason?: 'outside_workspace' | 'not_yml' | 'not_localisation_directory' | 'scratch_path';
|
|
14
|
+
message?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function normalizeWorkspaceRoot(workspaceRoot: string): string;
|
|
17
|
+
export declare function isPathInsideOrEqual(parent: string, child: string): boolean;
|
|
18
|
+
export declare function resolveWorkspacePath(workspaceRoot: string, inputPath: string): WorkspacePathResolution;
|
|
19
|
+
export declare function isLocalisationRelativePath(relativePath: string): boolean;
|
|
20
|
+
export declare function isScratchRelativePath(relativePath: string): boolean;
|
|
21
|
+
export declare function validateLocalisationPath(workspaceRoot: string, inputPath: string): LocalisationPathValidation;
|