@prorigo/protrak-forge 0.3.3 → 0.3.4
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/bin/protrak-forge.js +403 -219
- package/data/CLAUDE.md.template +89 -21
- package/package.json +1 -1
- package/data/patterns/aggregate-related-instances.md +0 -140
package/bin/protrak-forge.js
CHANGED
|
@@ -3223,8 +3223,8 @@ var require_utils = __commonJS({
|
|
|
3223
3223
|
}
|
|
3224
3224
|
return ind;
|
|
3225
3225
|
}
|
|
3226
|
-
function removeDotSegments(
|
|
3227
|
-
let input =
|
|
3226
|
+
function removeDotSegments(path15) {
|
|
3227
|
+
let input = path15;
|
|
3228
3228
|
const output = [];
|
|
3229
3229
|
let nextSlash = -1;
|
|
3230
3230
|
let len = 0;
|
|
@@ -3423,8 +3423,8 @@ var require_schemes = __commonJS({
|
|
|
3423
3423
|
wsComponent.secure = void 0;
|
|
3424
3424
|
}
|
|
3425
3425
|
if (wsComponent.resourceName) {
|
|
3426
|
-
const [
|
|
3427
|
-
wsComponent.path =
|
|
3426
|
+
const [path15, query] = wsComponent.resourceName.split("?");
|
|
3427
|
+
wsComponent.path = path15 && path15 !== "/" ? path15 : void 0;
|
|
3428
3428
|
wsComponent.query = query;
|
|
3429
3429
|
wsComponent.resourceName = void 0;
|
|
3430
3430
|
}
|
|
@@ -6786,12 +6786,12 @@ var require_dist = __commonJS({
|
|
|
6786
6786
|
throw new Error(`Unknown format "${name}"`);
|
|
6787
6787
|
return f;
|
|
6788
6788
|
};
|
|
6789
|
-
function addFormats(ajv, list,
|
|
6789
|
+
function addFormats(ajv, list, fs19, exportName) {
|
|
6790
6790
|
var _a2;
|
|
6791
6791
|
var _b;
|
|
6792
6792
|
(_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6793
6793
|
for (const f of list)
|
|
6794
|
-
ajv.addFormat(f,
|
|
6794
|
+
ajv.addFormat(f, fs19[f]);
|
|
6795
6795
|
}
|
|
6796
6796
|
module2.exports = exports2 = formatsPlugin;
|
|
6797
6797
|
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
@@ -7158,8 +7158,8 @@ function getErrorMap() {
|
|
|
7158
7158
|
|
|
7159
7159
|
// node_modules/zod/v3/helpers/parseUtil.js
|
|
7160
7160
|
var makeIssue = (params) => {
|
|
7161
|
-
const { data, path:
|
|
7162
|
-
const fullPath = [...
|
|
7161
|
+
const { data, path: path15, errorMaps, issueData } = params;
|
|
7162
|
+
const fullPath = [...path15, ...issueData.path || []];
|
|
7163
7163
|
const fullIssue = {
|
|
7164
7164
|
...issueData,
|
|
7165
7165
|
path: fullPath
|
|
@@ -7274,11 +7274,11 @@ var errorUtil;
|
|
|
7274
7274
|
|
|
7275
7275
|
// node_modules/zod/v3/types.js
|
|
7276
7276
|
var ParseInputLazyPath = class {
|
|
7277
|
-
constructor(parent, value,
|
|
7277
|
+
constructor(parent, value, path15, key) {
|
|
7278
7278
|
this._cachedPath = [];
|
|
7279
7279
|
this.parent = parent;
|
|
7280
7280
|
this.data = value;
|
|
7281
|
-
this._path =
|
|
7281
|
+
this._path = path15;
|
|
7282
7282
|
this._key = key;
|
|
7283
7283
|
}
|
|
7284
7284
|
get path() {
|
|
@@ -10924,10 +10924,10 @@ function mergeDefs(...defs) {
|
|
|
10924
10924
|
function cloneDef(schema) {
|
|
10925
10925
|
return mergeDefs(schema._zod.def);
|
|
10926
10926
|
}
|
|
10927
|
-
function getElementAtPath(obj,
|
|
10928
|
-
if (!
|
|
10927
|
+
function getElementAtPath(obj, path15) {
|
|
10928
|
+
if (!path15)
|
|
10929
10929
|
return obj;
|
|
10930
|
-
return
|
|
10930
|
+
return path15.reduce((acc, key) => acc?.[key], obj);
|
|
10931
10931
|
}
|
|
10932
10932
|
function promiseAllObject(promisesObj) {
|
|
10933
10933
|
const keys = Object.keys(promisesObj);
|
|
@@ -11310,11 +11310,11 @@ function aborted(x, startIndex = 0) {
|
|
|
11310
11310
|
}
|
|
11311
11311
|
return false;
|
|
11312
11312
|
}
|
|
11313
|
-
function prefixIssues(
|
|
11313
|
+
function prefixIssues(path15, issues) {
|
|
11314
11314
|
return issues.map((iss) => {
|
|
11315
11315
|
var _a2;
|
|
11316
11316
|
(_a2 = iss).path ?? (_a2.path = []);
|
|
11317
|
-
iss.path.unshift(
|
|
11317
|
+
iss.path.unshift(path15);
|
|
11318
11318
|
return iss;
|
|
11319
11319
|
});
|
|
11320
11320
|
}
|
|
@@ -21430,9 +21430,9 @@ var SearchableMap = class _SearchableMap {
|
|
|
21430
21430
|
if (!prefix.startsWith(this._prefix)) {
|
|
21431
21431
|
throw new Error("Mismatched prefix");
|
|
21432
21432
|
}
|
|
21433
|
-
const [node,
|
|
21433
|
+
const [node, path15] = trackDown(this._tree, prefix.slice(this._prefix.length));
|
|
21434
21434
|
if (node === void 0) {
|
|
21435
|
-
const [parentNode, key] = last(
|
|
21435
|
+
const [parentNode, key] = last(path15);
|
|
21436
21436
|
for (const k of parentNode.keys()) {
|
|
21437
21437
|
if (k !== LEAF && k.startsWith(key)) {
|
|
21438
21438
|
const node2 = /* @__PURE__ */ new Map();
|
|
@@ -21652,18 +21652,18 @@ var SearchableMap = class _SearchableMap {
|
|
|
21652
21652
|
return _SearchableMap.from(Object.entries(object3));
|
|
21653
21653
|
}
|
|
21654
21654
|
};
|
|
21655
|
-
var trackDown = (tree, key,
|
|
21655
|
+
var trackDown = (tree, key, path15 = []) => {
|
|
21656
21656
|
if (key.length === 0 || tree == null) {
|
|
21657
|
-
return [tree,
|
|
21657
|
+
return [tree, path15];
|
|
21658
21658
|
}
|
|
21659
21659
|
for (const k of tree.keys()) {
|
|
21660
21660
|
if (k !== LEAF && key.startsWith(k)) {
|
|
21661
|
-
|
|
21662
|
-
return trackDown(tree.get(k), key.slice(k.length),
|
|
21661
|
+
path15.push([tree, k]);
|
|
21662
|
+
return trackDown(tree.get(k), key.slice(k.length), path15);
|
|
21663
21663
|
}
|
|
21664
21664
|
}
|
|
21665
|
-
|
|
21666
|
-
return trackDown(void 0, "",
|
|
21665
|
+
path15.push([tree, key]);
|
|
21666
|
+
return trackDown(void 0, "", path15);
|
|
21667
21667
|
};
|
|
21668
21668
|
var lookup = (tree, key) => {
|
|
21669
21669
|
if (key.length === 0 || tree == null) {
|
|
@@ -21705,38 +21705,38 @@ var createPath = (node, key) => {
|
|
|
21705
21705
|
return node;
|
|
21706
21706
|
};
|
|
21707
21707
|
var remove = (tree, key) => {
|
|
21708
|
-
const [node,
|
|
21708
|
+
const [node, path15] = trackDown(tree, key);
|
|
21709
21709
|
if (node === void 0) {
|
|
21710
21710
|
return;
|
|
21711
21711
|
}
|
|
21712
21712
|
node.delete(LEAF);
|
|
21713
21713
|
if (node.size === 0) {
|
|
21714
|
-
cleanup(
|
|
21714
|
+
cleanup(path15);
|
|
21715
21715
|
} else if (node.size === 1) {
|
|
21716
21716
|
const [key2, value] = node.entries().next().value;
|
|
21717
|
-
merge2(
|
|
21717
|
+
merge2(path15, key2, value);
|
|
21718
21718
|
}
|
|
21719
21719
|
};
|
|
21720
|
-
var cleanup = (
|
|
21721
|
-
if (
|
|
21720
|
+
var cleanup = (path15) => {
|
|
21721
|
+
if (path15.length === 0) {
|
|
21722
21722
|
return;
|
|
21723
21723
|
}
|
|
21724
|
-
const [node, key] = last(
|
|
21724
|
+
const [node, key] = last(path15);
|
|
21725
21725
|
node.delete(key);
|
|
21726
21726
|
if (node.size === 0) {
|
|
21727
|
-
cleanup(
|
|
21727
|
+
cleanup(path15.slice(0, -1));
|
|
21728
21728
|
} else if (node.size === 1) {
|
|
21729
21729
|
const [key2, value] = node.entries().next().value;
|
|
21730
21730
|
if (key2 !== LEAF) {
|
|
21731
|
-
merge2(
|
|
21731
|
+
merge2(path15.slice(0, -1), key2, value);
|
|
21732
21732
|
}
|
|
21733
21733
|
}
|
|
21734
21734
|
};
|
|
21735
|
-
var merge2 = (
|
|
21736
|
-
if (
|
|
21735
|
+
var merge2 = (path15, key, value) => {
|
|
21736
|
+
if (path15.length === 0) {
|
|
21737
21737
|
return;
|
|
21738
21738
|
}
|
|
21739
|
-
const [node, nodeKey] = last(
|
|
21739
|
+
const [node, nodeKey] = last(path15);
|
|
21740
21740
|
node.set(nodeKey + key, value);
|
|
21741
21741
|
node.delete(nodeKey);
|
|
21742
21742
|
};
|
|
@@ -23318,7 +23318,7 @@ function getClient() {
|
|
|
23318
23318
|
}
|
|
23319
23319
|
|
|
23320
23320
|
// src/version.ts
|
|
23321
|
-
var PACKAGE_VERSION = "0.3.
|
|
23321
|
+
var PACKAGE_VERSION = "0.3.4";
|
|
23322
23322
|
|
|
23323
23323
|
// src/tools/_schema-write-utils.ts
|
|
23324
23324
|
var fs4 = __toESM(require("fs"));
|
|
@@ -23364,6 +23364,12 @@ function humanise(name) {
|
|
|
23364
23364
|
if (!name) return "";
|
|
23365
23365
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").trim();
|
|
23366
23366
|
}
|
|
23367
|
+
function readJsonFile(filePath) {
|
|
23368
|
+
return JSON.parse(fs4.readFileSync(filePath, "utf8"));
|
|
23369
|
+
}
|
|
23370
|
+
function writeJsonFilePath(filePath, data) {
|
|
23371
|
+
fs4.writeFileSync(filePath, JSON.stringify(data, null, JSON_INDENT), "utf8");
|
|
23372
|
+
}
|
|
23367
23373
|
function writeJsonFile(dir, fileName, data, overwrite) {
|
|
23368
23374
|
if (!fs4.existsSync(dir)) {
|
|
23369
23375
|
fs4.mkdirSync(dir, { recursive: true });
|
|
@@ -23395,7 +23401,7 @@ __export(get_program_context_exports, {
|
|
|
23395
23401
|
TOOL_DEF: () => TOOL_DEF,
|
|
23396
23402
|
handle: () => handle
|
|
23397
23403
|
});
|
|
23398
|
-
var
|
|
23404
|
+
var fs8 = __toESM(require("fs"));
|
|
23399
23405
|
|
|
23400
23406
|
// src/interface-contracts.ts
|
|
23401
23407
|
var INTERFACE_CONTRACTS = {
|
|
@@ -23808,11 +23814,67 @@ function getOrCreateIndex(workspace) {
|
|
|
23808
23814
|
return idx;
|
|
23809
23815
|
}
|
|
23810
23816
|
|
|
23817
|
+
// src/tools/_program-utils.ts
|
|
23818
|
+
var fs7 = __toESM(require("fs"));
|
|
23819
|
+
var path6 = __toESM(require("path"));
|
|
23820
|
+
function readProgramByName(provider, programName) {
|
|
23821
|
+
const programsDir = provider.ws.programsDir;
|
|
23822
|
+
const csPath = path6.join(programsDir, `${programName}.cs`);
|
|
23823
|
+
if (!fs7.existsSync(csPath)) {
|
|
23824
|
+
const available = listAvailablePrograms(programsDir);
|
|
23825
|
+
return {
|
|
23826
|
+
kind: "not_found",
|
|
23827
|
+
message: `Program '${programName}' not found at ${csPath}`,
|
|
23828
|
+
available_programs: available
|
|
23829
|
+
};
|
|
23830
|
+
}
|
|
23831
|
+
const parsed = parseFile(csPath);
|
|
23832
|
+
if (!parsed) {
|
|
23833
|
+
const source = fs7.readFileSync(csPath, "utf8");
|
|
23834
|
+
return {
|
|
23835
|
+
kind: "parse_failed",
|
|
23836
|
+
data: {
|
|
23837
|
+
file_path: csPath,
|
|
23838
|
+
source,
|
|
23839
|
+
program_type: "Unknown",
|
|
23840
|
+
services_used: [],
|
|
23841
|
+
patterns_detected: [],
|
|
23842
|
+
class_name: ""
|
|
23843
|
+
}
|
|
23844
|
+
};
|
|
23845
|
+
}
|
|
23846
|
+
const jsonPath = path6.join(programsDir, `${programName}.json`);
|
|
23847
|
+
let metadata = {};
|
|
23848
|
+
if (fs7.existsSync(jsonPath)) {
|
|
23849
|
+
try {
|
|
23850
|
+
metadata = JSON.parse(fs7.readFileSync(jsonPath, "utf8"));
|
|
23851
|
+
} catch {
|
|
23852
|
+
}
|
|
23853
|
+
}
|
|
23854
|
+
return {
|
|
23855
|
+
kind: "success",
|
|
23856
|
+
data: {
|
|
23857
|
+
file_path: csPath,
|
|
23858
|
+
source: parsed.fullSource,
|
|
23859
|
+
program_type: String(metadata["programType"] ?? parsed.programType ?? ""),
|
|
23860
|
+
services_used: parsed.servicesUsed,
|
|
23861
|
+
patterns_detected: parsed.patterns,
|
|
23862
|
+
class_name: parsed.className
|
|
23863
|
+
}
|
|
23864
|
+
};
|
|
23865
|
+
}
|
|
23866
|
+
function listAvailablePrograms(programsDir) {
|
|
23867
|
+
if (!fs7.existsSync(programsDir) || !fs7.statSync(programsDir).isDirectory()) {
|
|
23868
|
+
return [];
|
|
23869
|
+
}
|
|
23870
|
+
return fs7.readdirSync(programsDir).filter((f) => f.endsWith(".cs")).map((f) => path6.basename(f, ".cs")).sort();
|
|
23871
|
+
}
|
|
23872
|
+
|
|
23811
23873
|
// src/tools/get-program-context.ts
|
|
23812
23874
|
var DETAIL_MODES = ["full", "summary", "schema_only", "patterns_only"];
|
|
23813
23875
|
var TOOL_DEF = {
|
|
23814
23876
|
name: "get_protrak_program_context",
|
|
23815
|
-
description: "REQUIRED: Call this BEFORE writing any Protrak C# program.
|
|
23877
|
+
description: "REQUIRED: Call this BEFORE writing OR refactoring any Protrak C# program. NEW PROGRAM: omit program_name. Returns type schema, C# interface, patterns, similar programs, and query definitions in one call. REFACTOR: pass program_name to load the existing program's source, services, and detected patterns alongside the same context \u2014 no need to chain read_protrak_program separately. Use detail=summary on follow-up calls when you only need attribute/state names. This is the primary tool for Protrak program work.",
|
|
23816
23878
|
inputSchema: {
|
|
23817
23879
|
type: "object",
|
|
23818
23880
|
properties: {
|
|
@@ -23824,6 +23886,10 @@ var TOOL_DEF = {
|
|
|
23824
23886
|
type: "string",
|
|
23825
23887
|
description: "The program type to implement: PreCreate, PostCreate, PreUpdate, PostUpdate, PreDelete, PostDelete, PostConnect, PromoteAction, PromoteActionCommand, Scheduler, Common, Report"
|
|
23826
23888
|
},
|
|
23889
|
+
program_name: {
|
|
23890
|
+
type: "string",
|
|
23891
|
+
description: "OMIT for new-program creation. Provide an existing program name (e.g. 'CalculateNextInvoiceDate') to switch to refactor mode \u2014 response will include existing_source, existing_services_used, existing_patterns_detected, existing_class_name, existing_file_path, and refactor_mode:true. The validation_step will still reference validate_protrak_program with the program_type you passed."
|
|
23892
|
+
},
|
|
23827
23893
|
description: {
|
|
23828
23894
|
type: "string",
|
|
23829
23895
|
description: "What the program should do (e.g. 'validate that Title is not empty', 'send email when status changes'). Optional \u2014 patterns are still searched without it."
|
|
@@ -23848,13 +23914,23 @@ function buildSummarySchema(schema) {
|
|
|
23848
23914
|
relation_names: schema.relations.map((r) => r.relation_type_name)
|
|
23849
23915
|
};
|
|
23850
23916
|
}
|
|
23851
|
-
function buildValidationStep(
|
|
23917
|
+
function buildValidationStep({
|
|
23918
|
+
programType,
|
|
23919
|
+
namespace,
|
|
23920
|
+
typeName,
|
|
23921
|
+
refactorMode,
|
|
23922
|
+
existingProgramName
|
|
23923
|
+
}) {
|
|
23924
|
+
if (refactorMode) {
|
|
23925
|
+
return `After editing this ${programType || "program"}, call validate_protrak_program(code='<your edited source>', program_type='${programType}') to check for Protrak-specific gotchas (deadlocks, async void, DI shape, interface compliance). Then write the edited source back to Programs/${existingProgramName}.cs. Use namespace ${namespace}.`;
|
|
23926
|
+
}
|
|
23852
23927
|
const fileName = typeName && programType ? `${typeName}${programType}.cs` : "<ProgramName>.cs";
|
|
23853
|
-
return `After writing this ${programType || "program"}
|
|
23928
|
+
return `After writing this ${programType || "program"}, call validate_protrak_program(code='<your source>', program_type='${programType}') to check for errors. Once it passes, call generate_protrak_program(program_name='${typeName}${programType}', program_type='${programType}', source='<your source>') to write Programs/${fileName} and its companion JSON. Use namespace ${namespace}.`;
|
|
23854
23929
|
}
|
|
23855
23930
|
function handle(provider, args) {
|
|
23856
23931
|
const typeName = args["type_name"] ?? "";
|
|
23857
23932
|
const programType = args["program_type"] ?? "";
|
|
23933
|
+
const programName = args["program_name"] ?? "";
|
|
23858
23934
|
const description = args["description"] ?? "";
|
|
23859
23935
|
const detailRaw = args["detail"] ?? "full";
|
|
23860
23936
|
if (!DETAIL_MODES.includes(detailRaw)) {
|
|
@@ -23864,6 +23940,33 @@ function handle(provider, args) {
|
|
|
23864
23940
|
}
|
|
23865
23941
|
const detail = detailRaw;
|
|
23866
23942
|
const result = { detail };
|
|
23943
|
+
let refactorMode = false;
|
|
23944
|
+
const warnings = [];
|
|
23945
|
+
if (programName) {
|
|
23946
|
+
const existing = readProgramByName(provider, programName);
|
|
23947
|
+
if (existing.kind === "not_found") {
|
|
23948
|
+
return errorResponse(existing.message, {
|
|
23949
|
+
available_programs: existing.available_programs
|
|
23950
|
+
});
|
|
23951
|
+
}
|
|
23952
|
+
refactorMode = true;
|
|
23953
|
+
result["refactor_mode"] = true;
|
|
23954
|
+
result["existing_file_path"] = existing.data.file_path;
|
|
23955
|
+
result["existing_source"] = existing.data.source;
|
|
23956
|
+
result["existing_class_name"] = existing.data.class_name;
|
|
23957
|
+
result["existing_services_used"] = existing.data.services_used;
|
|
23958
|
+
result["existing_patterns_detected"] = existing.data.patterns_detected;
|
|
23959
|
+
if (existing.kind === "success" && existing.data.program_type && programType && existing.data.program_type !== programType) {
|
|
23960
|
+
warnings.push(
|
|
23961
|
+
`program_type mismatch \u2014 existing program implements '${existing.data.program_type}' but you passed '${programType}'. Refactor proceeds with the program_type you passed; verify the interface in existing_source matches before editing.`
|
|
23962
|
+
);
|
|
23963
|
+
}
|
|
23964
|
+
if (existing.kind === "parse_failed") {
|
|
23965
|
+
warnings.push(
|
|
23966
|
+
`Could not parse existing source for '${programName}'. existing_source is the raw file contents; services_used and patterns_detected are empty.`
|
|
23967
|
+
);
|
|
23968
|
+
}
|
|
23969
|
+
}
|
|
23867
23970
|
const schema = provider.getTypeSchema(typeName);
|
|
23868
23971
|
const wantsSchema = detail === "full" || detail === "summary" || detail === "schema_only";
|
|
23869
23972
|
const wantsInterface = detail === "full" || detail === "summary" || detail === "schema_only";
|
|
@@ -23906,7 +24009,7 @@ function handle(provider, args) {
|
|
|
23906
24009
|
}
|
|
23907
24010
|
if (wantsSimilar) {
|
|
23908
24011
|
const programsDir = provider.ws.programsDir;
|
|
23909
|
-
if (
|
|
24012
|
+
if (fs8.existsSync(programsDir) && fs8.statSync(programsDir).isDirectory()) {
|
|
23910
24013
|
try {
|
|
23911
24014
|
const index = getOrCreateIndex(provider.ws);
|
|
23912
24015
|
const similar = index.search(description, 3);
|
|
@@ -23926,7 +24029,7 @@ function handle(provider, args) {
|
|
|
23926
24029
|
}
|
|
23927
24030
|
if (wantsQueries) {
|
|
23928
24031
|
const qdDir = provider.ws.queryDefinitionsDir;
|
|
23929
|
-
if (
|
|
24032
|
+
if (fs8.existsSync(qdDir) && fs8.statSync(qdDir).isDirectory()) {
|
|
23930
24033
|
const queries = provider.getQueryDefinitionsForType(typeName);
|
|
23931
24034
|
if (detail === "summary") {
|
|
23932
24035
|
result["query_definition_names"] = queries.map((q) => q.name);
|
|
@@ -23953,7 +24056,16 @@ function handle(provider, args) {
|
|
|
23953
24056
|
"Return raw C# code only \u2014 no markdown, no code fences."
|
|
23954
24057
|
];
|
|
23955
24058
|
}
|
|
23956
|
-
result["validation_step"] = buildValidationStep(
|
|
24059
|
+
result["validation_step"] = buildValidationStep({
|
|
24060
|
+
programType,
|
|
24061
|
+
namespace: provider.ws.namespace,
|
|
24062
|
+
typeName,
|
|
24063
|
+
refactorMode,
|
|
24064
|
+
existingProgramName: programName
|
|
24065
|
+
});
|
|
24066
|
+
if (warnings.length > 0) {
|
|
24067
|
+
result["warnings"] = warnings;
|
|
24068
|
+
}
|
|
23957
24069
|
return JSON.stringify(result, null, 2);
|
|
23958
24070
|
}
|
|
23959
24071
|
|
|
@@ -23965,8 +24077,8 @@ __export(get_layout_context_exports, {
|
|
|
23965
24077
|
});
|
|
23966
24078
|
|
|
23967
24079
|
// src/layout-provider.ts
|
|
23968
|
-
var
|
|
23969
|
-
var
|
|
24080
|
+
var fs9 = __toESM(require("fs"));
|
|
24081
|
+
var path7 = __toESM(require("path"));
|
|
23970
24082
|
var LocalLayoutProvider = class {
|
|
23971
24083
|
ws;
|
|
23972
24084
|
_forms = null;
|
|
@@ -23981,13 +24093,13 @@ var LocalLayoutProvider = class {
|
|
|
23981
24093
|
this.ws = ws;
|
|
23982
24094
|
}
|
|
23983
24095
|
loadLayoutDir(dir, map2, meta3, keyField, buildMeta, logLabel) {
|
|
23984
|
-
if (!
|
|
23985
|
-
for (const entry of
|
|
24096
|
+
if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) return;
|
|
24097
|
+
for (const entry of fs9.readdirSync(dir).sort()) {
|
|
23986
24098
|
if (!entry.endsWith(".json")) continue;
|
|
23987
|
-
const fp =
|
|
24099
|
+
const fp = path7.join(dir, entry);
|
|
23988
24100
|
try {
|
|
23989
|
-
const data = JSON.parse(
|
|
23990
|
-
const name = data[keyField] ??
|
|
24101
|
+
const data = JSON.parse(fs9.readFileSync(fp, "utf8"));
|
|
24102
|
+
const name = data[keyField] ?? path7.basename(entry, ".json");
|
|
23991
24103
|
map2.set(name, data);
|
|
23992
24104
|
meta3.push(buildMeta(data, name, fp));
|
|
23993
24105
|
} catch (e) {
|
|
@@ -25232,8 +25344,8 @@ __export(validate_program_exports, {
|
|
|
25232
25344
|
TOOL_DEF: () => TOOL_DEF8,
|
|
25233
25345
|
handle: () => handle8
|
|
25234
25346
|
});
|
|
25235
|
-
var
|
|
25236
|
-
var
|
|
25347
|
+
var fs10 = __toESM(require("fs"));
|
|
25348
|
+
var path8 = __toESM(require("path"));
|
|
25237
25349
|
var os = __toESM(require("os"));
|
|
25238
25350
|
var crypto = __toESM(require("crypto"));
|
|
25239
25351
|
var import_child_process = require("child_process");
|
|
@@ -25377,7 +25489,7 @@ function runStaticChecks(code, programType) {
|
|
|
25377
25489
|
// src/tools/validate-program.ts
|
|
25378
25490
|
var TOOL_DEF8 = {
|
|
25379
25491
|
name: "validate_protrak_program",
|
|
25380
|
-
description: "
|
|
25492
|
+
description: "Static analysis for Protrak C# programs. Catches Protrak-specific gotchas that look fine to a generic reader but break in production: (1) `.Result` / `.Wait()` on tasks \u2014 deadlocks under Protrak's async context. (2) `async void` methods \u2014 exceptions escape silently. (3) Services declared as private/readonly fields \u2014 Protrak DI requires PUBLIC PROPERTIES. (4) Missing interface implementation \u2014 e.g. PreCreate must implement IPreCreateTriggerProgramAsync<T>. (5) `*Async()` calls without `await` or `return` \u2014 silent fire-and-forget. (6) Service usage where the service isn't declared as a public property. (7) Missing namespace declaration. ALWAYS call this after writing OR editing a Protrak program \u2014 these failures are not visible to a syntactic read of the file. Optionally runs 'dotnet build' for full compilation check.",
|
|
25381
25493
|
inputSchema: {
|
|
25382
25494
|
type: "object",
|
|
25383
25495
|
properties: {
|
|
@@ -25399,18 +25511,18 @@ var TOOL_DEF8 = {
|
|
|
25399
25511
|
}
|
|
25400
25512
|
};
|
|
25401
25513
|
function runDotnetBuild(workspaceRoot, code) {
|
|
25402
|
-
const csprojFiles =
|
|
25514
|
+
const csprojFiles = fs10.readdirSync(workspaceRoot).filter((e) => e.endsWith(".csproj"));
|
|
25403
25515
|
if (csprojFiles.length === 0) {
|
|
25404
25516
|
return { success: false, errors: ["No .csproj file found in workspace"] };
|
|
25405
25517
|
}
|
|
25406
|
-
const programsDir =
|
|
25518
|
+
const programsDir = path8.join(workspaceRoot, "Programs");
|
|
25407
25519
|
const tmpName = `_protrakgen_validate_${crypto.randomBytes(4).toString("hex")}.cs`;
|
|
25408
|
-
const tmpPath =
|
|
25520
|
+
const tmpPath = fs10.existsSync(programsDir) && fs10.statSync(programsDir).isDirectory() ? path8.join(programsDir, tmpName) : path8.join(os.tmpdir(), tmpName);
|
|
25409
25521
|
try {
|
|
25410
|
-
|
|
25522
|
+
fs10.writeFileSync(tmpPath, code, "utf8");
|
|
25411
25523
|
const result = (0, import_child_process.spawnSync)(
|
|
25412
25524
|
"dotnet",
|
|
25413
|
-
["build",
|
|
25525
|
+
["build", path8.join(workspaceRoot, csprojFiles[0]), "--no-restore", "-v", "quiet"],
|
|
25414
25526
|
{ cwd: workspaceRoot, encoding: "utf8", timeout: 3e4 }
|
|
25415
25527
|
);
|
|
25416
25528
|
if (result.error) {
|
|
@@ -25419,7 +25531,7 @@ function runDotnetBuild(workspaceRoot, code) {
|
|
|
25419
25531
|
}
|
|
25420
25532
|
return { success: false, errors: [result.error.message] };
|
|
25421
25533
|
}
|
|
25422
|
-
const tmpStem =
|
|
25534
|
+
const tmpStem = path8.basename(tmpPath, ".cs");
|
|
25423
25535
|
const allOutput = [result.stdout ?? "", result.stderr ?? ""].join("\n");
|
|
25424
25536
|
const errors = allOutput.split("\n").filter((line) => (line.includes(": error ") || line.includes(": warning ")) && line.includes(tmpStem)).map((line) => line.trim());
|
|
25425
25537
|
return { success: result.status === 0, errors };
|
|
@@ -25427,7 +25539,7 @@ function runDotnetBuild(workspaceRoot, code) {
|
|
|
25427
25539
|
return { success: false, errors: [String(e)] };
|
|
25428
25540
|
} finally {
|
|
25429
25541
|
try {
|
|
25430
|
-
|
|
25542
|
+
fs10.unlinkSync(tmpPath);
|
|
25431
25543
|
} catch {
|
|
25432
25544
|
}
|
|
25433
25545
|
}
|
|
@@ -25452,7 +25564,7 @@ function handle8(provider, args) {
|
|
|
25452
25564
|
`Fix the listed errors. For interface/coding-rule violations, re-read the contract via get_protrak_program_context(type_name='<TypeName>', program_type='${programType}', detail='schema_only') and adjust the program.`,
|
|
25453
25565
|
"Re-run validate_protrak_program after fixing."
|
|
25454
25566
|
] : [
|
|
25455
|
-
|
|
25567
|
+
`Validation clean. For a NEW program: call generate_protrak_program(program_name='<TypeName><ProgramType>', program_type='${programType}', source='<your code>') to write Programs/<name>.cs and its companion JSON. For a REFACTOR: write the edited source back to the existing Programs/<name>.cs file.`
|
|
25456
25568
|
];
|
|
25457
25569
|
return JSON.stringify(response, null, 2);
|
|
25458
25570
|
}
|
|
@@ -25463,7 +25575,7 @@ __export(search_protrak_knowledge_exports, {
|
|
|
25463
25575
|
TOOL_DEF: () => TOOL_DEF9,
|
|
25464
25576
|
handle: () => handle9
|
|
25465
25577
|
});
|
|
25466
|
-
var
|
|
25578
|
+
var fs11 = __toESM(require("fs"));
|
|
25467
25579
|
var SCOPES = ["patterns", "api", "programs", "all"];
|
|
25468
25580
|
var TOOL_DEF9 = {
|
|
25469
25581
|
name: "search_protrak_knowledge",
|
|
@@ -25532,7 +25644,7 @@ function searchApi(query, programType, services, topK) {
|
|
|
25532
25644
|
}
|
|
25533
25645
|
function searchPrograms(provider, query, topK) {
|
|
25534
25646
|
const programsDir = provider.ws.programsDir;
|
|
25535
|
-
if (!
|
|
25647
|
+
if (!fs11.existsSync(programsDir) || !fs11.statSync(programsDir).isDirectory()) {
|
|
25536
25648
|
return {
|
|
25537
25649
|
results: [],
|
|
25538
25650
|
note: `No Programs directory found at '${programsDir}'.`
|
|
@@ -25847,7 +25959,7 @@ __export(list_query_definitions_exports, {
|
|
|
25847
25959
|
TOOL_DEF: () => TOOL_DEF14,
|
|
25848
25960
|
handle: () => handle14
|
|
25849
25961
|
});
|
|
25850
|
-
var
|
|
25962
|
+
var fs12 = __toESM(require("fs"));
|
|
25851
25963
|
var TOOL_DEF14 = {
|
|
25852
25964
|
name: "list_protrak_queries",
|
|
25853
25965
|
description: "List Protrak query definitions available in the workspace. Call this when writing a program that uses IQueryBuilderService to see what query definitions exist and can be passed to GetQueryDefinitionResultByNameAsync. Filter by type_name to get only queries for the relevant entity type.",
|
|
@@ -25864,7 +25976,7 @@ var TOOL_DEF14 = {
|
|
|
25864
25976
|
function handle14(provider, args) {
|
|
25865
25977
|
const typeName = args["type_name"] || void 0;
|
|
25866
25978
|
const qdDir = provider.ws.queryDefinitionsDir;
|
|
25867
|
-
if (!
|
|
25979
|
+
if (!fs12.existsSync(qdDir) || !fs12.statSync(qdDir).isDirectory()) {
|
|
25868
25980
|
return JSON.stringify({
|
|
25869
25981
|
query_definitions: [],
|
|
25870
25982
|
note: `QueryDefinitions directory not found at '${qdDir}'.`
|
|
@@ -25913,8 +26025,8 @@ __export(list_protrak_programs_exports, {
|
|
|
25913
26025
|
TOOL_DEF: () => TOOL_DEF16,
|
|
25914
26026
|
handle: () => handle16
|
|
25915
26027
|
});
|
|
25916
|
-
var
|
|
25917
|
-
var
|
|
26028
|
+
var fs13 = __toESM(require("fs"));
|
|
26029
|
+
var path9 = __toESM(require("path"));
|
|
25918
26030
|
var TOOL_DEF16 = {
|
|
25919
26031
|
name: "list_protrak_programs",
|
|
25920
26032
|
description: "List Protrak C# programs in the workspace. Each entry includes the program name, the entity type it targets (when available from JSON metadata), and the program type (PreCreate, PostCreate, etc.). Filter by type_name and/or program_type to narrow. For relevance-ranked search by description use search_protrak_knowledge with scope='programs' instead.",
|
|
@@ -25936,7 +26048,7 @@ function handle16(provider, args) {
|
|
|
25936
26048
|
const typeFilter = args["type_name"] || void 0;
|
|
25937
26049
|
const programTypeFilter = args["program_type"] || void 0;
|
|
25938
26050
|
const programsDir = provider.ws.programsDir;
|
|
25939
|
-
if (!
|
|
26051
|
+
if (!fs13.existsSync(programsDir) || !fs13.statSync(programsDir).isDirectory()) {
|
|
25940
26052
|
return JSON.stringify(
|
|
25941
26053
|
{
|
|
25942
26054
|
programs: [],
|
|
@@ -25948,18 +26060,18 @@ function handle16(provider, args) {
|
|
|
25948
26060
|
}
|
|
25949
26061
|
const entries = [];
|
|
25950
26062
|
const seen = /* @__PURE__ */ new Set();
|
|
25951
|
-
for (const entry of
|
|
26063
|
+
for (const entry of fs13.readdirSync(programsDir).sort()) {
|
|
25952
26064
|
if (!entry.endsWith(".json") && !entry.endsWith(".cs")) continue;
|
|
25953
|
-
const programName =
|
|
26065
|
+
const programName = path9.basename(entry, path9.extname(entry));
|
|
25954
26066
|
if (seen.has(programName)) continue;
|
|
25955
26067
|
seen.add(programName);
|
|
25956
|
-
const csPath =
|
|
25957
|
-
const jsonPath =
|
|
26068
|
+
const csPath = path9.join(programsDir, `${programName}.cs`);
|
|
26069
|
+
const jsonPath = path9.join(programsDir, `${programName}.json`);
|
|
25958
26070
|
let programType = "";
|
|
25959
26071
|
let typeName = "";
|
|
25960
|
-
if (
|
|
26072
|
+
if (fs13.existsSync(jsonPath)) {
|
|
25961
26073
|
try {
|
|
25962
|
-
const meta3 = JSON.parse(
|
|
26074
|
+
const meta3 = JSON.parse(fs13.readFileSync(jsonPath, "utf8"));
|
|
25963
26075
|
programType = meta3["programType"] ?? "";
|
|
25964
26076
|
typeName = meta3["typeName"] ?? meta3["entityTypeName"] ?? meta3["entityType"] ?? "";
|
|
25965
26077
|
} catch {
|
|
@@ -25969,8 +26081,8 @@ function handle16(provider, args) {
|
|
|
25969
26081
|
program_name: programName,
|
|
25970
26082
|
program_type: programType,
|
|
25971
26083
|
type_name: typeName,
|
|
25972
|
-
has_source:
|
|
25973
|
-
file_path:
|
|
26084
|
+
has_source: fs13.existsSync(csPath),
|
|
26085
|
+
file_path: fs13.existsSync(csPath) ? csPath : jsonPath
|
|
25974
26086
|
});
|
|
25975
26087
|
}
|
|
25976
26088
|
let filtered = entries;
|
|
@@ -25992,11 +26104,9 @@ __export(get_program_source_exports, {
|
|
|
25992
26104
|
TOOL_DEF: () => TOOL_DEF17,
|
|
25993
26105
|
handle: () => handle17
|
|
25994
26106
|
});
|
|
25995
|
-
var fs13 = __toESM(require("fs"));
|
|
25996
|
-
var path9 = __toESM(require("path"));
|
|
25997
26107
|
var TOOL_DEF17 = {
|
|
25998
26108
|
name: "read_protrak_program",
|
|
25999
|
-
description: "Read the source code of a specific Protrak C# program from the workspace. Use this to inspect existing programs for reference, understand implementation patterns, or check what services a program uses.",
|
|
26109
|
+
description: "Read the source code of a specific Protrak C# program from the workspace. Use this to inspect existing programs for reference, understand implementation patterns, or check what services a program uses. For refactoring, prefer get_protrak_program_context with program_name set \u2014 it returns the source AND the schema/interface/patterns context in one call.",
|
|
26000
26110
|
inputSchema: {
|
|
26001
26111
|
type: "object",
|
|
26002
26112
|
properties: {
|
|
@@ -26010,32 +26120,15 @@ var TOOL_DEF17 = {
|
|
|
26010
26120
|
};
|
|
26011
26121
|
function handle17(provider, args) {
|
|
26012
26122
|
const programName = args["program_name"] ?? "";
|
|
26013
|
-
const
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
return errorResponse(`Program '${programName}' not found at ${csPath}`);
|
|
26123
|
+
const result = readProgramByName(provider, programName);
|
|
26124
|
+
if (result.kind === "not_found") {
|
|
26125
|
+
return errorResponse(result.message, { available_programs: result.available_programs });
|
|
26017
26126
|
}
|
|
26018
|
-
|
|
26019
|
-
|
|
26020
|
-
|
|
26021
|
-
return JSON.stringify({ file_path: csPath, source, program_type: "Unknown", services_used: [] }, null, 2);
|
|
26022
|
-
}
|
|
26023
|
-
const jsonPath = path9.join(programsDir, `${programName}.json`);
|
|
26024
|
-
let metadata = {};
|
|
26025
|
-
if (fs13.existsSync(jsonPath)) {
|
|
26026
|
-
try {
|
|
26027
|
-
metadata = JSON.parse(fs13.readFileSync(jsonPath, "utf8"));
|
|
26028
|
-
} catch {
|
|
26029
|
-
}
|
|
26127
|
+
if (result.kind === "parse_failed") {
|
|
26128
|
+
const { file_path, source, program_type, services_used } = result.data;
|
|
26129
|
+
return JSON.stringify({ file_path, source, program_type, services_used }, null, JSON_INDENT);
|
|
26030
26130
|
}
|
|
26031
|
-
return JSON.stringify(
|
|
26032
|
-
file_path: csPath,
|
|
26033
|
-
source: parsed.fullSource,
|
|
26034
|
-
program_type: metadata["programType"] ?? parsed.programType,
|
|
26035
|
-
services_used: parsed.servicesUsed,
|
|
26036
|
-
patterns_detected: parsed.patterns,
|
|
26037
|
-
class_name: parsed.className
|
|
26038
|
-
}, null, 2);
|
|
26131
|
+
return JSON.stringify(result.data, null, JSON_INDENT);
|
|
26039
26132
|
}
|
|
26040
26133
|
|
|
26041
26134
|
// src/tools/generate-query-definition.ts
|
|
@@ -26711,14 +26804,105 @@ function handle21(provider, args) {
|
|
|
26711
26804
|
return successResponse({ file_path: result.filePath, template_name: templateName }, { nextSteps });
|
|
26712
26805
|
}
|
|
26713
26806
|
|
|
26714
|
-
// src/tools/
|
|
26715
|
-
var
|
|
26716
|
-
__export(
|
|
26807
|
+
// src/tools/generate-program.ts
|
|
26808
|
+
var generate_program_exports = {};
|
|
26809
|
+
__export(generate_program_exports, {
|
|
26717
26810
|
TOOL_DEF: () => TOOL_DEF22,
|
|
26718
26811
|
handle: () => handle22
|
|
26719
26812
|
});
|
|
26720
26813
|
var fs15 = __toESM(require("fs"));
|
|
26721
26814
|
var path11 = __toESM(require("path"));
|
|
26815
|
+
var TOOL_DEF22 = {
|
|
26816
|
+
name: "generate_protrak_program",
|
|
26817
|
+
description: "Write a Protrak C# program to disk. Creates BOTH Programs/<name>.cs (your source) AND Programs/<name>.json (companion metadata required by Protrak's import flow). The programHashCode is intentionally empty \u2014 Protrak recomputes it on import. ALWAYS call validate_protrak_program on the source FIRST before invoking this tool. Refuses to overwrite existing files unless overwrite:true.",
|
|
26818
|
+
inputSchema: {
|
|
26819
|
+
type: "object",
|
|
26820
|
+
properties: {
|
|
26821
|
+
program_name: {
|
|
26822
|
+
type: "string",
|
|
26823
|
+
description: "PascalCase basename of the file (e.g. 'CalculateNextInvoiceDate'). Used as the file name for both .cs and .json. Must match the class name inside the source."
|
|
26824
|
+
},
|
|
26825
|
+
program_type: {
|
|
26826
|
+
type: "string",
|
|
26827
|
+
description: "Protrak's canonical programType value, written verbatim into the .json file. Common values seen in production workspaces: 'PreCreateTrigger', 'PostCreateTrigger', 'PreUpdateTrigger', 'PostUpdateTrigger', 'PreConnectTrigger', 'PostConnectTrigger', 'PromoteActionCommand', 'Scheduler', 'Common', 'Report', 'Workflow'. Inspect existing programs via list_protrak_programs / read_protrak_program to confirm the value Protrak expects in this workspace."
|
|
26828
|
+
},
|
|
26829
|
+
source: {
|
|
26830
|
+
type: "string",
|
|
26831
|
+
description: "Full C# source code for the program. Should already have passed validate_protrak_program. Written verbatim to Programs/<name>.cs."
|
|
26832
|
+
},
|
|
26833
|
+
overwrite: {
|
|
26834
|
+
type: "boolean",
|
|
26835
|
+
description: "When true, overwrite existing files. Default: false (errors on conflict).",
|
|
26836
|
+
default: false
|
|
26837
|
+
}
|
|
26838
|
+
},
|
|
26839
|
+
required: ["program_name", "program_type", "source"]
|
|
26840
|
+
}
|
|
26841
|
+
};
|
|
26842
|
+
function buildCompanionJson(programName, programType) {
|
|
26843
|
+
return {
|
|
26844
|
+
programName,
|
|
26845
|
+
programType,
|
|
26846
|
+
programCode: null,
|
|
26847
|
+
programHashCode: "",
|
|
26848
|
+
programStage: "Published"
|
|
26849
|
+
};
|
|
26850
|
+
}
|
|
26851
|
+
function handle22(provider, args) {
|
|
26852
|
+
const programName = args["program_name"] ?? "";
|
|
26853
|
+
const programType = args["program_type"] ?? "";
|
|
26854
|
+
const source = args["source"] ?? "";
|
|
26855
|
+
const overwrite = args["overwrite"] === true;
|
|
26856
|
+
if (!programName) return errorResponse("program_name is required");
|
|
26857
|
+
if (!isPascalCase(programName)) {
|
|
26858
|
+
return errorResponse(pascalCaseError("program_name", programName));
|
|
26859
|
+
}
|
|
26860
|
+
if (!programType) return errorResponse("program_type is required");
|
|
26861
|
+
if (!source) return errorResponse("source is required");
|
|
26862
|
+
const programsDir = provider.ws.programsDir;
|
|
26863
|
+
if (!fs15.existsSync(programsDir)) {
|
|
26864
|
+
fs15.mkdirSync(programsDir, { recursive: true });
|
|
26865
|
+
}
|
|
26866
|
+
const csPath = path11.join(programsDir, `${programName}.cs`);
|
|
26867
|
+
const jsonPath = path11.join(programsDir, `${programName}.json`);
|
|
26868
|
+
if (!overwrite) {
|
|
26869
|
+
const existing = [];
|
|
26870
|
+
if (fs15.existsSync(csPath)) existing.push(csPath);
|
|
26871
|
+
if (fs15.existsSync(jsonPath)) existing.push(jsonPath);
|
|
26872
|
+
if (existing.length > 0) {
|
|
26873
|
+
return errorResponse(
|
|
26874
|
+
`Refusing to overwrite existing file(s): ${existing.join(", ")}. Pass overwrite:true to replace.`,
|
|
26875
|
+
{ existing_paths: existing }
|
|
26876
|
+
);
|
|
26877
|
+
}
|
|
26878
|
+
}
|
|
26879
|
+
fs15.writeFileSync(csPath, source, "utf8");
|
|
26880
|
+
const companion = buildCompanionJson(programName, programType);
|
|
26881
|
+
fs15.writeFileSync(jsonPath, JSON.stringify(companion, null, JSON_INDENT), "utf8");
|
|
26882
|
+
const nextSteps = [
|
|
26883
|
+
`Verify with read_protrak_program(program_name='${programName}').`,
|
|
26884
|
+
"On import, Protrak recomputes programHashCode automatically \u2014 leaving it empty here is intentional."
|
|
26885
|
+
];
|
|
26886
|
+
return successResponse(
|
|
26887
|
+
{
|
|
26888
|
+
program_name: programName,
|
|
26889
|
+
program_type: programType,
|
|
26890
|
+
cs_path: csPath,
|
|
26891
|
+
json_path: jsonPath,
|
|
26892
|
+
companion_json: companion
|
|
26893
|
+
},
|
|
26894
|
+
{ nextSteps }
|
|
26895
|
+
);
|
|
26896
|
+
}
|
|
26897
|
+
|
|
26898
|
+
// src/tools/attach-attribute-to-type.ts
|
|
26899
|
+
var attach_attribute_to_type_exports = {};
|
|
26900
|
+
__export(attach_attribute_to_type_exports, {
|
|
26901
|
+
TOOL_DEF: () => TOOL_DEF23,
|
|
26902
|
+
handle: () => handle23
|
|
26903
|
+
});
|
|
26904
|
+
var fs16 = __toESM(require("fs"));
|
|
26905
|
+
var path12 = __toESM(require("path"));
|
|
26722
26906
|
|
|
26723
26907
|
// src/tools/_visibility-utils.ts
|
|
26724
26908
|
function applyAttributeVisibility(lifecycleData, attributeName, visibleSet, opts) {
|
|
@@ -26773,7 +26957,7 @@ function applyAttributeVisibility(lifecycleData, attributeName, visibleSet, opts
|
|
|
26773
26957
|
|
|
26774
26958
|
// src/tools/attach-attribute-to-type.ts
|
|
26775
26959
|
var VISIBILITY_ALL = "all";
|
|
26776
|
-
var
|
|
26960
|
+
var TOOL_DEF23 = {
|
|
26777
26961
|
name: "attach_protrak_attribute_to_type",
|
|
26778
26962
|
description: "Bind an existing Attribute to an existing Type without regenerating either file. Appends { name, index, isRequired } to the Type's attributes array with correct auto-indexing. Also ensures lifecycle visibility \u2014 by default the attribute is made visible in ALL lifecycle states (any state with viewableAttributesType=Specific has the attribute added to its viewableAttributes/editableAttributes lists). Pass replace:true to update an existing binding's isRequired flag in place. Use this instead of generate_protrak_type with overwrite:true when adding attributes to an existing Type \u2014 overwrite clobbers unrelated edits.",
|
|
26779
26963
|
inputSchema: {
|
|
@@ -26809,12 +26993,12 @@ var TOOL_DEF22 = {
|
|
|
26809
26993
|
}
|
|
26810
26994
|
};
|
|
26811
26995
|
function readJson(filePath) {
|
|
26812
|
-
return JSON.parse(
|
|
26996
|
+
return JSON.parse(fs16.readFileSync(filePath, "utf8"));
|
|
26813
26997
|
}
|
|
26814
26998
|
function writeJson(filePath, data) {
|
|
26815
|
-
|
|
26999
|
+
fs16.writeFileSync(filePath, JSON.stringify(data, null, JSON_INDENT), "utf8");
|
|
26816
27000
|
}
|
|
26817
|
-
function
|
|
27001
|
+
function handle23(provider, args) {
|
|
26818
27002
|
const typeName = args["type_name"] ?? "";
|
|
26819
27003
|
const attrName = args["attribute_name"] ?? "";
|
|
26820
27004
|
const required2 = args["required"] === true;
|
|
@@ -26837,8 +27021,8 @@ function handle22(provider, args) {
|
|
|
26837
27021
|
"lifecycle_states_visibility must be 'all' or an array of state names."
|
|
26838
27022
|
);
|
|
26839
27023
|
}
|
|
26840
|
-
const typeFilePath =
|
|
26841
|
-
if (!
|
|
27024
|
+
const typeFilePath = path12.join(provider.ws.typesDir, `${typeName}.json`);
|
|
27025
|
+
if (!fs16.existsSync(typeFilePath)) {
|
|
26842
27026
|
const available = Object.keys(provider.types).sort();
|
|
26843
27027
|
return errorResponse(`Type '${typeName}' was not found in ${provider.ws.typesDir}.`, {
|
|
26844
27028
|
available_types: available
|
|
@@ -26873,8 +27057,8 @@ function handle22(provider, args) {
|
|
|
26873
27057
|
let lifecyclePath = null;
|
|
26874
27058
|
let visibilityChanges = [];
|
|
26875
27059
|
if (lifecycleName) {
|
|
26876
|
-
const lifecycleFilePath =
|
|
26877
|
-
if (!
|
|
27060
|
+
const lifecycleFilePath = path12.join(provider.ws.lifecyclesDir, `${lifecycleName}.json`);
|
|
27061
|
+
if (!fs16.existsSync(lifecycleFilePath)) {
|
|
26878
27062
|
warnings.push(
|
|
26879
27063
|
`Lifecycle '${lifecycleName}' (referenced by type '${typeName}') was not found in ${provider.ws.lifecyclesDir}. Visibility was not updated.`
|
|
26880
27064
|
);
|
|
@@ -26928,12 +27112,12 @@ function handle22(provider, args) {
|
|
|
26928
27112
|
// src/tools/attach-attribute-to-layout.ts
|
|
26929
27113
|
var attach_attribute_to_layout_exports = {};
|
|
26930
27114
|
__export(attach_attribute_to_layout_exports, {
|
|
26931
|
-
TOOL_DEF: () =>
|
|
26932
|
-
handle: () =>
|
|
27115
|
+
TOOL_DEF: () => TOOL_DEF24,
|
|
27116
|
+
handle: () => handle24
|
|
26933
27117
|
});
|
|
26934
|
-
var
|
|
26935
|
-
var
|
|
26936
|
-
var
|
|
27118
|
+
var fs17 = __toESM(require("node:fs"));
|
|
27119
|
+
var path13 = __toESM(require("node:path"));
|
|
27120
|
+
var TOOL_DEF24 = {
|
|
26937
27121
|
name: "attach_protrak_attribute_to_layout",
|
|
26938
27122
|
description: "Add an attribute reference to a Layout Template's underlying form or type widget without regenerating either file. Resolves the layout's widget by templateWidgetKey (or null = first widget) and follows it to the underlying Form (formWidgetName) or TypeWidget (typeWidgetName) JSON, then inserts the attribute reference there with the canonical shape. For Form widgets, you may pass a container_id (formContainerKey) to pick a specific container; otherwise the attribute is added to the first container. Pass replace:true to overwrite an existing field with the same attributeName. Use this instead of regenerating the form/widget when adding a single attribute to an existing layout.",
|
|
26939
27123
|
inputSchema: {
|
|
@@ -26964,16 +27148,10 @@ var TOOL_DEF23 = {
|
|
|
26964
27148
|
required: ["layout_name", "attribute_name"]
|
|
26965
27149
|
}
|
|
26966
27150
|
};
|
|
26967
|
-
function readJson2(filePath) {
|
|
26968
|
-
return JSON.parse(fs16.readFileSync(filePath, "utf8"));
|
|
26969
|
-
}
|
|
26970
|
-
function writeJson2(filePath, data) {
|
|
26971
|
-
fs16.writeFileSync(filePath, JSON.stringify(data, null, JSON_INDENT), "utf8");
|
|
26972
|
-
}
|
|
26973
27151
|
function findWidget(widgets, widgetId) {
|
|
26974
27152
|
if (widgets.length === 0) return null;
|
|
26975
27153
|
if (widgetId === null) return widgets[0];
|
|
26976
|
-
return widgets.find((w) =>
|
|
27154
|
+
return widgets.find((w) => (w["templateWidgetKey"] ?? "") === widgetId) ?? null;
|
|
26977
27155
|
}
|
|
26978
27156
|
function makeFormField(attrName) {
|
|
26979
27157
|
return {
|
|
@@ -27002,11 +27180,11 @@ function makeWidgetColumn(attrName, attrType) {
|
|
|
27002
27180
|
};
|
|
27003
27181
|
}
|
|
27004
27182
|
function attachToForm(ws, formName, attrName, containerId, replace) {
|
|
27005
|
-
const filePath =
|
|
27006
|
-
if (!
|
|
27183
|
+
const filePath = path13.join(ws.formsDir, `${formName}.json`);
|
|
27184
|
+
if (!fs17.existsSync(filePath)) {
|
|
27007
27185
|
return { error: `Form '${formName}' was not found in ${ws.formsDir}.` };
|
|
27008
27186
|
}
|
|
27009
|
-
const formData =
|
|
27187
|
+
const formData = readJsonFile(filePath);
|
|
27010
27188
|
const config2 = formData["formConfiguration"] ?? {};
|
|
27011
27189
|
const containers = Array.isArray(config2["containers"]) ? config2["containers"] : [];
|
|
27012
27190
|
if (containers.length === 0) {
|
|
@@ -27016,16 +27194,16 @@ function attachToForm(ws, formName, attrName, containerId, replace) {
|
|
|
27016
27194
|
if (containerId === void 0) {
|
|
27017
27195
|
target = containers[0];
|
|
27018
27196
|
} else {
|
|
27019
|
-
target = containers.find((c) =>
|
|
27197
|
+
target = containers.find((c) => (c["formContainerKey"] ?? "") === containerId);
|
|
27020
27198
|
if (!target) {
|
|
27021
27199
|
return {
|
|
27022
27200
|
error: `Container '${containerId}' not found in form '${formName}'.`,
|
|
27023
|
-
available_containers: containers.map((c) =>
|
|
27201
|
+
available_containers: containers.map((c) => c["formContainerKey"] ?? "")
|
|
27024
27202
|
};
|
|
27025
27203
|
}
|
|
27026
27204
|
}
|
|
27027
27205
|
const fields = Array.isArray(target["fields"]) ? target["fields"] : [];
|
|
27028
|
-
const existingIdx = fields.findIndex((f) =>
|
|
27206
|
+
const existingIdx = fields.findIndex((f) => (f["attributeName"] ?? "") === attrName);
|
|
27029
27207
|
let action;
|
|
27030
27208
|
if (existingIdx >= 0) {
|
|
27031
27209
|
if (!replace) {
|
|
@@ -27040,28 +27218,28 @@ function attachToForm(ws, formName, attrName, containerId, replace) {
|
|
|
27040
27218
|
action = "inserted";
|
|
27041
27219
|
}
|
|
27042
27220
|
target["fields"] = fields;
|
|
27043
|
-
|
|
27221
|
+
writeJsonFilePath(filePath, formData);
|
|
27044
27222
|
return {
|
|
27045
27223
|
result: {
|
|
27046
27224
|
filePath,
|
|
27047
27225
|
formName,
|
|
27048
|
-
containerKey:
|
|
27226
|
+
containerKey: target["formContainerKey"] ?? "",
|
|
27049
27227
|
action
|
|
27050
27228
|
}
|
|
27051
27229
|
};
|
|
27052
27230
|
}
|
|
27053
27231
|
function attachToTypeWidget(provider, widgetName, attrName, replace) {
|
|
27054
|
-
const filePath =
|
|
27055
|
-
if (!
|
|
27232
|
+
const filePath = path13.join(provider.ws.typeWidgetsDir, `${widgetName}.json`);
|
|
27233
|
+
if (!fs17.existsSync(filePath)) {
|
|
27056
27234
|
return { error: `TypeWidget '${widgetName}' was not found in ${provider.ws.typeWidgetsDir}.` };
|
|
27057
27235
|
}
|
|
27058
|
-
const widgetData =
|
|
27236
|
+
const widgetData = readJsonFile(filePath);
|
|
27059
27237
|
const isRelation = widgetData["widgetType"] === "Relation";
|
|
27060
27238
|
const configKey = isRelation ? "relationWidgetConfig" : "dashboardWidgetConfig";
|
|
27061
27239
|
const config2 = widgetData[configKey] ?? {};
|
|
27062
27240
|
const fields = Array.isArray(config2["fields"]) ? config2["fields"] : [];
|
|
27063
27241
|
const attrType = String(provider.attributes[attrName]?.["attributeType"] ?? "Text");
|
|
27064
|
-
const existingIdx = fields.findIndex((f) =>
|
|
27242
|
+
const existingIdx = fields.findIndex((f) => (f["attributeName"] ?? "") === attrName);
|
|
27065
27243
|
let action;
|
|
27066
27244
|
if (existingIdx >= 0) {
|
|
27067
27245
|
if (!replace) {
|
|
@@ -27082,10 +27260,57 @@ function attachToTypeWidget(provider, widgetName, attrName, replace) {
|
|
|
27082
27260
|
}
|
|
27083
27261
|
config2["fields"] = fields;
|
|
27084
27262
|
widgetData[configKey] = config2;
|
|
27085
|
-
|
|
27263
|
+
writeJsonFilePath(filePath, widgetData);
|
|
27086
27264
|
return { result: { filePath, widgetName, action } };
|
|
27087
27265
|
}
|
|
27088
|
-
function
|
|
27266
|
+
function resolveTypeWidgetTarget(provider, widget, attrName, containerId, replace, warnings) {
|
|
27267
|
+
const typeWidgetName = widget["typeWidgetName"] ?? null;
|
|
27268
|
+
const widgetKey = widget["templateWidgetKey"] ?? "";
|
|
27269
|
+
if (!typeWidgetName) {
|
|
27270
|
+
return { error: `Layout widget '${widgetKey}' has widgetType=TypeWidget but no typeWidgetName.` };
|
|
27271
|
+
}
|
|
27272
|
+
if (containerId !== void 0) {
|
|
27273
|
+
warnings.push("container_id is only meaningful for Form widgets; ignored for TypeWidget.");
|
|
27274
|
+
}
|
|
27275
|
+
const out = attachToTypeWidget(provider, typeWidgetName, attrName, replace);
|
|
27276
|
+
if (out.error) {
|
|
27277
|
+
return { error: out.error };
|
|
27278
|
+
}
|
|
27279
|
+
return {
|
|
27280
|
+
target: {
|
|
27281
|
+
kind: "typeWidget",
|
|
27282
|
+
filePath: out.result.filePath,
|
|
27283
|
+
payload: { type_widget_name: out.result.widgetName, action: out.result.action }
|
|
27284
|
+
}
|
|
27285
|
+
};
|
|
27286
|
+
}
|
|
27287
|
+
function resolveTarget(provider, widget, attrName, containerId, replace, warnings) {
|
|
27288
|
+
const widgetKind = widget["widgetType"] ?? "";
|
|
27289
|
+
const formWidgetName = widget["formWidgetName"] ?? null;
|
|
27290
|
+
const typeWidgetName = widget["typeWidgetName"] ?? null;
|
|
27291
|
+
const widgetKey = widget["templateWidgetKey"] ?? "";
|
|
27292
|
+
if (widgetKind === "Form" || formWidgetName && !typeWidgetName) {
|
|
27293
|
+
if (!formWidgetName) {
|
|
27294
|
+
return { error: `Layout widget '${widgetKey}' has widgetType=Form but no formWidgetName.` };
|
|
27295
|
+
}
|
|
27296
|
+
const out = attachToForm(provider.ws, formWidgetName, attrName, containerId, replace);
|
|
27297
|
+
if (out.error) {
|
|
27298
|
+
return { error: out.error, extra: out.available_containers ? { available_containers: out.available_containers } : void 0 };
|
|
27299
|
+
}
|
|
27300
|
+
return {
|
|
27301
|
+
target: {
|
|
27302
|
+
kind: "form",
|
|
27303
|
+
filePath: out.result.filePath,
|
|
27304
|
+
payload: { form_name: out.result.formName, container_id: out.result.containerKey, action: out.result.action }
|
|
27305
|
+
}
|
|
27306
|
+
};
|
|
27307
|
+
}
|
|
27308
|
+
if (widgetKind === "TypeWidget" || typeWidgetName && !formWidgetName) {
|
|
27309
|
+
return resolveTypeWidgetTarget(provider, widget, attrName, containerId, replace, warnings);
|
|
27310
|
+
}
|
|
27311
|
+
return { error: `Layout widget '${widgetKey}' has unsupported widgetType='${widgetKind}'.` };
|
|
27312
|
+
}
|
|
27313
|
+
function handle24(provider, args) {
|
|
27089
27314
|
const layoutName = args["layout_name"] ?? "";
|
|
27090
27315
|
const widgetIdArg = args["widget_id"];
|
|
27091
27316
|
const attrName = args["attribute_name"] ?? "";
|
|
@@ -27094,19 +27319,15 @@ function handle23(provider, args) {
|
|
|
27094
27319
|
if (!layoutName) return errorResponse("layout_name is required");
|
|
27095
27320
|
if (!attrName) return errorResponse("attribute_name is required");
|
|
27096
27321
|
if (!isPascalCase(attrName)) return errorResponse(pascalCaseError("attribute_name", attrName));
|
|
27097
|
-
|
|
27098
|
-
if (widgetIdArg === null || widgetIdArg === void 0) {
|
|
27099
|
-
widgetId = null;
|
|
27100
|
-
} else if (typeof widgetIdArg === "string") {
|
|
27101
|
-
widgetId = widgetIdArg;
|
|
27102
|
-
} else {
|
|
27322
|
+
if (widgetIdArg !== null && widgetIdArg !== void 0 && typeof widgetIdArg !== "string") {
|
|
27103
27323
|
return errorResponse("widget_id must be a string templateWidgetKey or null.");
|
|
27104
27324
|
}
|
|
27105
|
-
const
|
|
27106
|
-
|
|
27325
|
+
const widgetId = widgetIdArg ?? null;
|
|
27326
|
+
const layoutPath = path13.join(provider.ws.layoutTemplatesDir, `${layoutName}.json`);
|
|
27327
|
+
if (!fs17.existsSync(layoutPath)) {
|
|
27107
27328
|
return errorResponse(`Layout template '${layoutName}' was not found in ${provider.ws.layoutTemplatesDir}.`);
|
|
27108
27329
|
}
|
|
27109
|
-
const layoutData =
|
|
27330
|
+
const layoutData = readJsonFile(layoutPath);
|
|
27110
27331
|
const config2 = layoutData["configuration"] ?? {};
|
|
27111
27332
|
const widgets = Array.isArray(config2["widgets"]) ? config2["widgets"] : [];
|
|
27112
27333
|
if (widgets.length === 0) {
|
|
@@ -27114,12 +27335,9 @@ function handle23(provider, args) {
|
|
|
27114
27335
|
}
|
|
27115
27336
|
const widget = findWidget(widgets, widgetId);
|
|
27116
27337
|
if (!widget) {
|
|
27117
|
-
return errorResponse(
|
|
27118
|
-
|
|
27119
|
-
|
|
27120
|
-
available_widget_ids: widgets.map((w) => String(w["templateWidgetKey"] ?? ""))
|
|
27121
|
-
}
|
|
27122
|
-
);
|
|
27338
|
+
return errorResponse(`widget_id '${widgetId}' not found in layout '${layoutName}'.`, {
|
|
27339
|
+
available_widget_ids: widgets.map((w) => w["templateWidgetKey"] ?? "")
|
|
27340
|
+
});
|
|
27123
27341
|
}
|
|
27124
27342
|
const warnings = [];
|
|
27125
27343
|
if (!provider.attributes[attrName]) {
|
|
@@ -27127,53 +27345,11 @@ function handle23(provider, args) {
|
|
|
27127
27345
|
`Attribute '${attrName}' is not defined in ${provider.ws.attributesDir}. The reference will still be inserted; create the attribute or fix the name to resolve.`
|
|
27128
27346
|
);
|
|
27129
27347
|
}
|
|
27130
|
-
const
|
|
27131
|
-
|
|
27132
|
-
|
|
27133
|
-
let target;
|
|
27134
|
-
if (widgetKind === "Form" || formWidgetName && !typeWidgetName) {
|
|
27135
|
-
if (!formWidgetName) {
|
|
27136
|
-
return errorResponse(`Layout widget '${String(widget["templateWidgetKey"])}' has widgetType=Form but no formWidgetName.`);
|
|
27137
|
-
}
|
|
27138
|
-
if (containerId !== void 0) {
|
|
27139
|
-
}
|
|
27140
|
-
const out = attachToForm(provider.ws, formWidgetName, attrName, containerId, replace);
|
|
27141
|
-
if (out.error) {
|
|
27142
|
-
return errorResponse(out.error, out.available_containers ? { available_containers: out.available_containers } : void 0);
|
|
27143
|
-
}
|
|
27144
|
-
target = {
|
|
27145
|
-
kind: "form",
|
|
27146
|
-
filePath: out.result.filePath,
|
|
27147
|
-
payload: {
|
|
27148
|
-
form_name: out.result.formName,
|
|
27149
|
-
container_id: out.result.containerKey,
|
|
27150
|
-
action: out.result.action
|
|
27151
|
-
}
|
|
27152
|
-
};
|
|
27153
|
-
} else if (widgetKind === "TypeWidget" || typeWidgetName && !formWidgetName) {
|
|
27154
|
-
if (!typeWidgetName) {
|
|
27155
|
-
return errorResponse(`Layout widget '${String(widget["templateWidgetKey"])}' has widgetType=TypeWidget but no typeWidgetName.`);
|
|
27156
|
-
}
|
|
27157
|
-
if (containerId !== void 0) {
|
|
27158
|
-
warnings.push("container_id is only meaningful for Form widgets; ignored for TypeWidget.");
|
|
27159
|
-
}
|
|
27160
|
-
const out = attachToTypeWidget(provider, typeWidgetName, attrName, replace);
|
|
27161
|
-
if (out.error) {
|
|
27162
|
-
return errorResponse(out.error);
|
|
27163
|
-
}
|
|
27164
|
-
target = {
|
|
27165
|
-
kind: "typeWidget",
|
|
27166
|
-
filePath: out.result.filePath,
|
|
27167
|
-
payload: {
|
|
27168
|
-
type_widget_name: out.result.widgetName,
|
|
27169
|
-
action: out.result.action
|
|
27170
|
-
}
|
|
27171
|
-
};
|
|
27172
|
-
} else {
|
|
27173
|
-
return errorResponse(
|
|
27174
|
-
`Layout widget '${String(widget["templateWidgetKey"])}' has unsupported widgetType='${widgetKind}'.`
|
|
27175
|
-
);
|
|
27348
|
+
const resolved = resolveTarget(provider, widget, attrName, containerId, replace, warnings);
|
|
27349
|
+
if ("error" in resolved) {
|
|
27350
|
+
return errorResponse(resolved.error, resolved.extra);
|
|
27176
27351
|
}
|
|
27352
|
+
const { target } = resolved;
|
|
27177
27353
|
const nextSteps = [
|
|
27178
27354
|
`Verify with read_protrak_layout(layout_name='${layoutName}').`,
|
|
27179
27355
|
`If the attribute is not yet bound to the type, call attach_protrak_attribute_to_type to bind it.`
|
|
@@ -27182,7 +27358,7 @@ function handle23(provider, args) {
|
|
|
27182
27358
|
{
|
|
27183
27359
|
layout_name: layoutName,
|
|
27184
27360
|
layout_path: layoutPath,
|
|
27185
|
-
widget_id:
|
|
27361
|
+
widget_id: widget["templateWidgetKey"] ?? "",
|
|
27186
27362
|
target_kind: target.kind,
|
|
27187
27363
|
target_path: target.filePath,
|
|
27188
27364
|
...target.payload
|
|
@@ -27194,12 +27370,12 @@ function handle23(provider, args) {
|
|
|
27194
27370
|
// src/tools/set-attribute-state-visibility.ts
|
|
27195
27371
|
var set_attribute_state_visibility_exports = {};
|
|
27196
27372
|
__export(set_attribute_state_visibility_exports, {
|
|
27197
|
-
TOOL_DEF: () =>
|
|
27198
|
-
handle: () =>
|
|
27373
|
+
TOOL_DEF: () => TOOL_DEF25,
|
|
27374
|
+
handle: () => handle25
|
|
27199
27375
|
});
|
|
27200
|
-
var
|
|
27201
|
-
var
|
|
27202
|
-
var
|
|
27376
|
+
var fs18 = __toESM(require("fs"));
|
|
27377
|
+
var path14 = __toESM(require("path"));
|
|
27378
|
+
var TOOL_DEF25 = {
|
|
27203
27379
|
name: "set_protrak_attribute_state_visibility",
|
|
27204
27380
|
description: "Set the per-state lifecycle visibility of a single attribute on a Type. Pass visible_states to specify exactly which states the attribute is visible in; omit visible_states to default to 'visible in every state'. Adds the attribute to viewableAttributes/editableAttributes of any Specific-mode state in the visible list, removes it from Specific-mode states not in the list, and converts All-mode states to Specific to enforce hiding when needed (preserving every other type attribute's visibility). Use this to retune visibility on an already-bound attribute; use attach_protrak_attribute_to_type to add a brand-new attribute binding.",
|
|
27205
27381
|
inputSchema: {
|
|
@@ -27222,13 +27398,13 @@ var TOOL_DEF24 = {
|
|
|
27222
27398
|
required: ["type_name", "attribute_name"]
|
|
27223
27399
|
}
|
|
27224
27400
|
};
|
|
27225
|
-
function
|
|
27226
|
-
return JSON.parse(
|
|
27401
|
+
function readJson2(filePath) {
|
|
27402
|
+
return JSON.parse(fs18.readFileSync(filePath, "utf8"));
|
|
27227
27403
|
}
|
|
27228
|
-
function
|
|
27229
|
-
|
|
27404
|
+
function writeJson2(filePath, data) {
|
|
27405
|
+
fs18.writeFileSync(filePath, JSON.stringify(data, null, JSON_INDENT), "utf8");
|
|
27230
27406
|
}
|
|
27231
|
-
function
|
|
27407
|
+
function handle25(provider, args) {
|
|
27232
27408
|
const typeName = args["type_name"] ?? "";
|
|
27233
27409
|
const attrName = args["attribute_name"] ?? "";
|
|
27234
27410
|
const hasVisibleStatesArg = Object.prototype.hasOwnProperty.call(args, "visible_states");
|
|
@@ -27248,14 +27424,14 @@ function handle24(provider, args) {
|
|
|
27248
27424
|
}
|
|
27249
27425
|
visibleStates = new Set(rawVisible);
|
|
27250
27426
|
}
|
|
27251
|
-
const typeFilePath =
|
|
27252
|
-
if (!
|
|
27427
|
+
const typeFilePath = path14.join(provider.ws.typesDir, `${typeName}.json`);
|
|
27428
|
+
if (!fs18.existsSync(typeFilePath)) {
|
|
27253
27429
|
const available = Object.keys(provider.types).sort();
|
|
27254
27430
|
return errorResponse(`Type '${typeName}' was not found in ${provider.ws.typesDir}.`, {
|
|
27255
27431
|
available_types: available
|
|
27256
27432
|
});
|
|
27257
27433
|
}
|
|
27258
|
-
const typeData =
|
|
27434
|
+
const typeData = readJson2(typeFilePath);
|
|
27259
27435
|
const typeAttributes = Array.isArray(typeData["attributes"]) ? typeData["attributes"] : [];
|
|
27260
27436
|
const knownTypeAttributes = typeAttributes.map((a) => a.name);
|
|
27261
27437
|
const warnings = [];
|
|
@@ -27268,13 +27444,13 @@ function handle24(provider, args) {
|
|
|
27268
27444
|
if (!lifecycleName) {
|
|
27269
27445
|
return errorResponse(`Type '${typeName}' has no lifecycleName set; cannot adjust visibility.`);
|
|
27270
27446
|
}
|
|
27271
|
-
const lifecycleFilePath =
|
|
27272
|
-
if (!
|
|
27447
|
+
const lifecycleFilePath = path14.join(provider.ws.lifecyclesDir, `${lifecycleName}.json`);
|
|
27448
|
+
if (!fs18.existsSync(lifecycleFilePath)) {
|
|
27273
27449
|
return errorResponse(
|
|
27274
27450
|
`Lifecycle '${lifecycleName}' (referenced by type '${typeName}') was not found in ${provider.ws.lifecyclesDir}.`
|
|
27275
27451
|
);
|
|
27276
27452
|
}
|
|
27277
|
-
const lifecycleData =
|
|
27453
|
+
const lifecycleData = readJson2(lifecycleFilePath);
|
|
27278
27454
|
const stateNames = (lifecycleData["states"] ?? []).map(
|
|
27279
27455
|
(s) => String(s["name"] ?? "")
|
|
27280
27456
|
);
|
|
@@ -27291,7 +27467,7 @@ function handle24(provider, args) {
|
|
|
27291
27467
|
knownTypeAttributes
|
|
27292
27468
|
});
|
|
27293
27469
|
if (changed) {
|
|
27294
|
-
|
|
27470
|
+
writeJson2(lifecycleFilePath, lifecycleData);
|
|
27295
27471
|
}
|
|
27296
27472
|
const nextSteps = [
|
|
27297
27473
|
`Verify with read_protrak_schema_element(kind='lifecycle', name='${lifecycleName}').`,
|
|
@@ -27346,6 +27522,7 @@ var TOOLS = [
|
|
|
27346
27522
|
generate_form_exports,
|
|
27347
27523
|
generate_type_widget_exports,
|
|
27348
27524
|
generate_layout_template_exports,
|
|
27525
|
+
generate_program_exports,
|
|
27349
27526
|
// Surgical mutators — modify existing files without regeneration/clobber
|
|
27350
27527
|
attach_attribute_to_type_exports,
|
|
27351
27528
|
attach_attribute_to_layout_exports,
|
|
@@ -27385,11 +27562,18 @@ Restart VS Code (or reload the window) so npx can fetch the latest version.
|
|
|
27385
27562
|
log("info", `Protrak Forge MCP ready \u2014 ${TOOLS.length} tools | workspace: ${workspace.namespace} | types: ${typeCount} | programs: ${programCount}`);
|
|
27386
27563
|
const SERVER_INSTRUCTIONS = `You are working in a Protrak customization workspace. Protrak programs are C# classes that implement trigger interfaces (PreCreate, PostCreate, PreUpdate, PromoteActionCommand, Scheduler, etc.).
|
|
27387
27564
|
|
|
27388
|
-
WORKFLOW for writing Protrak
|
|
27565
|
+
WORKFLOW for writing a NEW Protrak program:
|
|
27389
27566
|
1. ALWAYS call get_protrak_program_context first \u2014 it returns the type schema, interface contract, coding patterns, similar programs, and available query definitions (for IQueryBuilderService) in one call.
|
|
27390
27567
|
2. Write the C# program following the interface contract and patterns.
|
|
27391
27568
|
3. If the program uses IQueryBuilderService: pick a query name from the query_definitions returned by get_protrak_program_context, or call generate_protrak_query_definition to create a new one before writing the program.
|
|
27392
27569
|
4. ALWAYS call validate_protrak_program after writing to check for errors.
|
|
27570
|
+
5. Once validation is clean, call generate_protrak_program(program_name, program_type, source) \u2014 this writes BOTH Programs/<name>.cs AND Programs/<name>.json (the companion metadata Protrak's import flow needs). Do NOT use the filesystem Write tool for new programs \u2014 you will skip the .json companion and the program will not import.
|
|
27571
|
+
|
|
27572
|
+
WORKFLOW for REFACTORING an existing Protrak program:
|
|
27573
|
+
1. Call get_protrak_program_context with program_name set to the existing program name. This returns the current source AND the schema/interface/patterns context in ONE call (no need to chain read_protrak_program separately).
|
|
27574
|
+
2. Edit the C# source.
|
|
27575
|
+
3. Call validate_protrak_program(code, program_type) \u2014 same as for new programs.
|
|
27576
|
+
4. Write the edited source back to the existing Programs/<name>.cs file. Do NOT call generate_protrak_program for refactors \u2014 the companion .json already exists.
|
|
27393
27577
|
|
|
27394
27578
|
WORKFLOW for creating a QueryDefinition (no program needed):
|
|
27395
27579
|
1. Call read_protrak_schema_element with kind='type' to get attribute names and valid state names.
|