@prorigo/protrak-forge 0.3.3 → 0.3.5
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/README.md +2 -0
- package/bin/protrak-forge.js +642 -220
- package/data/CLAUDE.md.template +116 -21
- package/data/patterns/notification-template-reference.md +191 -0
- 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(path17) {
|
|
3227
|
+
let input = path17;
|
|
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 [path17, query] = wsComponent.resourceName.split("?");
|
|
3427
|
+
wsComponent.path = path17 && path17 !== "/" ? path17 : 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, fs21, 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, fs21[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: path17, errorMaps, issueData } = params;
|
|
7162
|
+
const fullPath = [...path17, ...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, path17, key) {
|
|
7278
7278
|
this._cachedPath = [];
|
|
7279
7279
|
this.parent = parent;
|
|
7280
7280
|
this.data = value;
|
|
7281
|
-
this._path =
|
|
7281
|
+
this._path = path17;
|
|
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, path17) {
|
|
10928
|
+
if (!path17)
|
|
10929
10929
|
return obj;
|
|
10930
|
-
return
|
|
10930
|
+
return path17.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(path17, 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(path17);
|
|
11318
11318
|
return iss;
|
|
11319
11319
|
});
|
|
11320
11320
|
}
|
|
@@ -20869,7 +20869,8 @@ function makeWorkspace(root, namespace) {
|
|
|
20869
20869
|
formsDir: path.join(root, "Forms"),
|
|
20870
20870
|
typeWidgetsDir: path.join(root, "TypeWidgets"),
|
|
20871
20871
|
layoutTemplatesDir: path.join(root, "LayoutTemplates"),
|
|
20872
|
-
homeLayoutsDir: path.join(root, "HomeLayouts")
|
|
20872
|
+
homeLayoutsDir: path.join(root, "HomeLayouts"),
|
|
20873
|
+
notificationTemplatesDir: path.join(root, "NotificationTemplates")
|
|
20873
20874
|
};
|
|
20874
20875
|
}
|
|
20875
20876
|
function extractNamespace(csprojPath) {
|
|
@@ -21430,9 +21431,9 @@ var SearchableMap = class _SearchableMap {
|
|
|
21430
21431
|
if (!prefix.startsWith(this._prefix)) {
|
|
21431
21432
|
throw new Error("Mismatched prefix");
|
|
21432
21433
|
}
|
|
21433
|
-
const [node,
|
|
21434
|
+
const [node, path17] = trackDown(this._tree, prefix.slice(this._prefix.length));
|
|
21434
21435
|
if (node === void 0) {
|
|
21435
|
-
const [parentNode, key] = last(
|
|
21436
|
+
const [parentNode, key] = last(path17);
|
|
21436
21437
|
for (const k of parentNode.keys()) {
|
|
21437
21438
|
if (k !== LEAF && k.startsWith(key)) {
|
|
21438
21439
|
const node2 = /* @__PURE__ */ new Map();
|
|
@@ -21652,18 +21653,18 @@ var SearchableMap = class _SearchableMap {
|
|
|
21652
21653
|
return _SearchableMap.from(Object.entries(object3));
|
|
21653
21654
|
}
|
|
21654
21655
|
};
|
|
21655
|
-
var trackDown = (tree, key,
|
|
21656
|
+
var trackDown = (tree, key, path17 = []) => {
|
|
21656
21657
|
if (key.length === 0 || tree == null) {
|
|
21657
|
-
return [tree,
|
|
21658
|
+
return [tree, path17];
|
|
21658
21659
|
}
|
|
21659
21660
|
for (const k of tree.keys()) {
|
|
21660
21661
|
if (k !== LEAF && key.startsWith(k)) {
|
|
21661
|
-
|
|
21662
|
-
return trackDown(tree.get(k), key.slice(k.length),
|
|
21662
|
+
path17.push([tree, k]);
|
|
21663
|
+
return trackDown(tree.get(k), key.slice(k.length), path17);
|
|
21663
21664
|
}
|
|
21664
21665
|
}
|
|
21665
|
-
|
|
21666
|
-
return trackDown(void 0, "",
|
|
21666
|
+
path17.push([tree, key]);
|
|
21667
|
+
return trackDown(void 0, "", path17);
|
|
21667
21668
|
};
|
|
21668
21669
|
var lookup = (tree, key) => {
|
|
21669
21670
|
if (key.length === 0 || tree == null) {
|
|
@@ -21705,38 +21706,38 @@ var createPath = (node, key) => {
|
|
|
21705
21706
|
return node;
|
|
21706
21707
|
};
|
|
21707
21708
|
var remove = (tree, key) => {
|
|
21708
|
-
const [node,
|
|
21709
|
+
const [node, path17] = trackDown(tree, key);
|
|
21709
21710
|
if (node === void 0) {
|
|
21710
21711
|
return;
|
|
21711
21712
|
}
|
|
21712
21713
|
node.delete(LEAF);
|
|
21713
21714
|
if (node.size === 0) {
|
|
21714
|
-
cleanup(
|
|
21715
|
+
cleanup(path17);
|
|
21715
21716
|
} else if (node.size === 1) {
|
|
21716
21717
|
const [key2, value] = node.entries().next().value;
|
|
21717
|
-
merge2(
|
|
21718
|
+
merge2(path17, key2, value);
|
|
21718
21719
|
}
|
|
21719
21720
|
};
|
|
21720
|
-
var cleanup = (
|
|
21721
|
-
if (
|
|
21721
|
+
var cleanup = (path17) => {
|
|
21722
|
+
if (path17.length === 0) {
|
|
21722
21723
|
return;
|
|
21723
21724
|
}
|
|
21724
|
-
const [node, key] = last(
|
|
21725
|
+
const [node, key] = last(path17);
|
|
21725
21726
|
node.delete(key);
|
|
21726
21727
|
if (node.size === 0) {
|
|
21727
|
-
cleanup(
|
|
21728
|
+
cleanup(path17.slice(0, -1));
|
|
21728
21729
|
} else if (node.size === 1) {
|
|
21729
21730
|
const [key2, value] = node.entries().next().value;
|
|
21730
21731
|
if (key2 !== LEAF) {
|
|
21731
|
-
merge2(
|
|
21732
|
+
merge2(path17.slice(0, -1), key2, value);
|
|
21732
21733
|
}
|
|
21733
21734
|
}
|
|
21734
21735
|
};
|
|
21735
|
-
var merge2 = (
|
|
21736
|
-
if (
|
|
21736
|
+
var merge2 = (path17, key, value) => {
|
|
21737
|
+
if (path17.length === 0) {
|
|
21737
21738
|
return;
|
|
21738
21739
|
}
|
|
21739
|
-
const [node, nodeKey] = last(
|
|
21740
|
+
const [node, nodeKey] = last(path17);
|
|
21740
21741
|
node.set(nodeKey + key, value);
|
|
21741
21742
|
node.delete(nodeKey);
|
|
21742
21743
|
};
|
|
@@ -23318,7 +23319,7 @@ function getClient() {
|
|
|
23318
23319
|
}
|
|
23319
23320
|
|
|
23320
23321
|
// src/version.ts
|
|
23321
|
-
var PACKAGE_VERSION = "0.3.
|
|
23322
|
+
var PACKAGE_VERSION = "0.3.5";
|
|
23322
23323
|
|
|
23323
23324
|
// src/tools/_schema-write-utils.ts
|
|
23324
23325
|
var fs4 = __toESM(require("fs"));
|
|
@@ -23364,6 +23365,12 @@ function humanise(name) {
|
|
|
23364
23365
|
if (!name) return "";
|
|
23365
23366
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").trim();
|
|
23366
23367
|
}
|
|
23368
|
+
function readJsonFile(filePath) {
|
|
23369
|
+
return JSON.parse(fs4.readFileSync(filePath, "utf8"));
|
|
23370
|
+
}
|
|
23371
|
+
function writeJsonFilePath(filePath, data) {
|
|
23372
|
+
fs4.writeFileSync(filePath, JSON.stringify(data, null, JSON_INDENT), "utf8");
|
|
23373
|
+
}
|
|
23367
23374
|
function writeJsonFile(dir, fileName, data, overwrite) {
|
|
23368
23375
|
if (!fs4.existsSync(dir)) {
|
|
23369
23376
|
fs4.mkdirSync(dir, { recursive: true });
|
|
@@ -23395,7 +23402,7 @@ __export(get_program_context_exports, {
|
|
|
23395
23402
|
TOOL_DEF: () => TOOL_DEF,
|
|
23396
23403
|
handle: () => handle
|
|
23397
23404
|
});
|
|
23398
|
-
var
|
|
23405
|
+
var fs8 = __toESM(require("fs"));
|
|
23399
23406
|
|
|
23400
23407
|
// src/interface-contracts.ts
|
|
23401
23408
|
var INTERFACE_CONTRACTS = {
|
|
@@ -23808,11 +23815,67 @@ function getOrCreateIndex(workspace) {
|
|
|
23808
23815
|
return idx;
|
|
23809
23816
|
}
|
|
23810
23817
|
|
|
23818
|
+
// src/tools/_program-utils.ts
|
|
23819
|
+
var fs7 = __toESM(require("fs"));
|
|
23820
|
+
var path6 = __toESM(require("path"));
|
|
23821
|
+
function readProgramByName(provider, programName) {
|
|
23822
|
+
const programsDir = provider.ws.programsDir;
|
|
23823
|
+
const csPath = path6.join(programsDir, `${programName}.cs`);
|
|
23824
|
+
if (!fs7.existsSync(csPath)) {
|
|
23825
|
+
const available = listAvailablePrograms(programsDir);
|
|
23826
|
+
return {
|
|
23827
|
+
kind: "not_found",
|
|
23828
|
+
message: `Program '${programName}' not found at ${csPath}`,
|
|
23829
|
+
available_programs: available
|
|
23830
|
+
};
|
|
23831
|
+
}
|
|
23832
|
+
const parsed = parseFile(csPath);
|
|
23833
|
+
if (!parsed) {
|
|
23834
|
+
const source = fs7.readFileSync(csPath, "utf8");
|
|
23835
|
+
return {
|
|
23836
|
+
kind: "parse_failed",
|
|
23837
|
+
data: {
|
|
23838
|
+
file_path: csPath,
|
|
23839
|
+
source,
|
|
23840
|
+
program_type: "Unknown",
|
|
23841
|
+
services_used: [],
|
|
23842
|
+
patterns_detected: [],
|
|
23843
|
+
class_name: ""
|
|
23844
|
+
}
|
|
23845
|
+
};
|
|
23846
|
+
}
|
|
23847
|
+
const jsonPath = path6.join(programsDir, `${programName}.json`);
|
|
23848
|
+
let metadata = {};
|
|
23849
|
+
if (fs7.existsSync(jsonPath)) {
|
|
23850
|
+
try {
|
|
23851
|
+
metadata = JSON.parse(fs7.readFileSync(jsonPath, "utf8"));
|
|
23852
|
+
} catch {
|
|
23853
|
+
}
|
|
23854
|
+
}
|
|
23855
|
+
return {
|
|
23856
|
+
kind: "success",
|
|
23857
|
+
data: {
|
|
23858
|
+
file_path: csPath,
|
|
23859
|
+
source: parsed.fullSource,
|
|
23860
|
+
program_type: String(metadata["programType"] ?? parsed.programType ?? ""),
|
|
23861
|
+
services_used: parsed.servicesUsed,
|
|
23862
|
+
patterns_detected: parsed.patterns,
|
|
23863
|
+
class_name: parsed.className
|
|
23864
|
+
}
|
|
23865
|
+
};
|
|
23866
|
+
}
|
|
23867
|
+
function listAvailablePrograms(programsDir) {
|
|
23868
|
+
if (!fs7.existsSync(programsDir) || !fs7.statSync(programsDir).isDirectory()) {
|
|
23869
|
+
return [];
|
|
23870
|
+
}
|
|
23871
|
+
return fs7.readdirSync(programsDir).filter((f) => f.endsWith(".cs")).map((f) => path6.basename(f, ".cs")).sort();
|
|
23872
|
+
}
|
|
23873
|
+
|
|
23811
23874
|
// src/tools/get-program-context.ts
|
|
23812
23875
|
var DETAIL_MODES = ["full", "summary", "schema_only", "patterns_only"];
|
|
23813
23876
|
var TOOL_DEF = {
|
|
23814
23877
|
name: "get_protrak_program_context",
|
|
23815
|
-
description: "REQUIRED: Call this BEFORE writing any Protrak C# program.
|
|
23878
|
+
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
23879
|
inputSchema: {
|
|
23817
23880
|
type: "object",
|
|
23818
23881
|
properties: {
|
|
@@ -23824,6 +23887,10 @@ var TOOL_DEF = {
|
|
|
23824
23887
|
type: "string",
|
|
23825
23888
|
description: "The program type to implement: PreCreate, PostCreate, PreUpdate, PostUpdate, PreDelete, PostDelete, PostConnect, PromoteAction, PromoteActionCommand, Scheduler, Common, Report"
|
|
23826
23889
|
},
|
|
23890
|
+
program_name: {
|
|
23891
|
+
type: "string",
|
|
23892
|
+
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."
|
|
23893
|
+
},
|
|
23827
23894
|
description: {
|
|
23828
23895
|
type: "string",
|
|
23829
23896
|
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 +23915,23 @@ function buildSummarySchema(schema) {
|
|
|
23848
23915
|
relation_names: schema.relations.map((r) => r.relation_type_name)
|
|
23849
23916
|
};
|
|
23850
23917
|
}
|
|
23851
|
-
function buildValidationStep(
|
|
23918
|
+
function buildValidationStep({
|
|
23919
|
+
programType,
|
|
23920
|
+
namespace,
|
|
23921
|
+
typeName,
|
|
23922
|
+
refactorMode,
|
|
23923
|
+
existingProgramName
|
|
23924
|
+
}) {
|
|
23925
|
+
if (refactorMode) {
|
|
23926
|
+
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}.`;
|
|
23927
|
+
}
|
|
23852
23928
|
const fileName = typeName && programType ? `${typeName}${programType}.cs` : "<ProgramName>.cs";
|
|
23853
|
-
return `After writing this ${programType || "program"}
|
|
23929
|
+
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
23930
|
}
|
|
23855
23931
|
function handle(provider, args) {
|
|
23856
23932
|
const typeName = args["type_name"] ?? "";
|
|
23857
23933
|
const programType = args["program_type"] ?? "";
|
|
23934
|
+
const programName = args["program_name"] ?? "";
|
|
23858
23935
|
const description = args["description"] ?? "";
|
|
23859
23936
|
const detailRaw = args["detail"] ?? "full";
|
|
23860
23937
|
if (!DETAIL_MODES.includes(detailRaw)) {
|
|
@@ -23864,6 +23941,33 @@ function handle(provider, args) {
|
|
|
23864
23941
|
}
|
|
23865
23942
|
const detail = detailRaw;
|
|
23866
23943
|
const result = { detail };
|
|
23944
|
+
let refactorMode = false;
|
|
23945
|
+
const warnings = [];
|
|
23946
|
+
if (programName) {
|
|
23947
|
+
const existing = readProgramByName(provider, programName);
|
|
23948
|
+
if (existing.kind === "not_found") {
|
|
23949
|
+
return errorResponse(existing.message, {
|
|
23950
|
+
available_programs: existing.available_programs
|
|
23951
|
+
});
|
|
23952
|
+
}
|
|
23953
|
+
refactorMode = true;
|
|
23954
|
+
result["refactor_mode"] = true;
|
|
23955
|
+
result["existing_file_path"] = existing.data.file_path;
|
|
23956
|
+
result["existing_source"] = existing.data.source;
|
|
23957
|
+
result["existing_class_name"] = existing.data.class_name;
|
|
23958
|
+
result["existing_services_used"] = existing.data.services_used;
|
|
23959
|
+
result["existing_patterns_detected"] = existing.data.patterns_detected;
|
|
23960
|
+
if (existing.kind === "success" && existing.data.program_type && programType && existing.data.program_type !== programType) {
|
|
23961
|
+
warnings.push(
|
|
23962
|
+
`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.`
|
|
23963
|
+
);
|
|
23964
|
+
}
|
|
23965
|
+
if (existing.kind === "parse_failed") {
|
|
23966
|
+
warnings.push(
|
|
23967
|
+
`Could not parse existing source for '${programName}'. existing_source is the raw file contents; services_used and patterns_detected are empty.`
|
|
23968
|
+
);
|
|
23969
|
+
}
|
|
23970
|
+
}
|
|
23867
23971
|
const schema = provider.getTypeSchema(typeName);
|
|
23868
23972
|
const wantsSchema = detail === "full" || detail === "summary" || detail === "schema_only";
|
|
23869
23973
|
const wantsInterface = detail === "full" || detail === "summary" || detail === "schema_only";
|
|
@@ -23906,7 +24010,7 @@ function handle(provider, args) {
|
|
|
23906
24010
|
}
|
|
23907
24011
|
if (wantsSimilar) {
|
|
23908
24012
|
const programsDir = provider.ws.programsDir;
|
|
23909
|
-
if (
|
|
24013
|
+
if (fs8.existsSync(programsDir) && fs8.statSync(programsDir).isDirectory()) {
|
|
23910
24014
|
try {
|
|
23911
24015
|
const index = getOrCreateIndex(provider.ws);
|
|
23912
24016
|
const similar = index.search(description, 3);
|
|
@@ -23926,7 +24030,7 @@ function handle(provider, args) {
|
|
|
23926
24030
|
}
|
|
23927
24031
|
if (wantsQueries) {
|
|
23928
24032
|
const qdDir = provider.ws.queryDefinitionsDir;
|
|
23929
|
-
if (
|
|
24033
|
+
if (fs8.existsSync(qdDir) && fs8.statSync(qdDir).isDirectory()) {
|
|
23930
24034
|
const queries = provider.getQueryDefinitionsForType(typeName);
|
|
23931
24035
|
if (detail === "summary") {
|
|
23932
24036
|
result["query_definition_names"] = queries.map((q) => q.name);
|
|
@@ -23953,7 +24057,16 @@ function handle(provider, args) {
|
|
|
23953
24057
|
"Return raw C# code only \u2014 no markdown, no code fences."
|
|
23954
24058
|
];
|
|
23955
24059
|
}
|
|
23956
|
-
result["validation_step"] = buildValidationStep(
|
|
24060
|
+
result["validation_step"] = buildValidationStep({
|
|
24061
|
+
programType,
|
|
24062
|
+
namespace: provider.ws.namespace,
|
|
24063
|
+
typeName,
|
|
24064
|
+
refactorMode,
|
|
24065
|
+
existingProgramName: programName
|
|
24066
|
+
});
|
|
24067
|
+
if (warnings.length > 0) {
|
|
24068
|
+
result["warnings"] = warnings;
|
|
24069
|
+
}
|
|
23957
24070
|
return JSON.stringify(result, null, 2);
|
|
23958
24071
|
}
|
|
23959
24072
|
|
|
@@ -23965,8 +24078,8 @@ __export(get_layout_context_exports, {
|
|
|
23965
24078
|
});
|
|
23966
24079
|
|
|
23967
24080
|
// src/layout-provider.ts
|
|
23968
|
-
var
|
|
23969
|
-
var
|
|
24081
|
+
var fs9 = __toESM(require("fs"));
|
|
24082
|
+
var path7 = __toESM(require("path"));
|
|
23970
24083
|
var LocalLayoutProvider = class {
|
|
23971
24084
|
ws;
|
|
23972
24085
|
_forms = null;
|
|
@@ -23977,17 +24090,19 @@ var LocalLayoutProvider = class {
|
|
|
23977
24090
|
_layoutTemplateMeta = null;
|
|
23978
24091
|
_homeLayouts = null;
|
|
23979
24092
|
_homeLayoutMeta = null;
|
|
24093
|
+
_notificationTemplates = null;
|
|
24094
|
+
_notificationTemplateMeta = null;
|
|
23980
24095
|
constructor(ws) {
|
|
23981
24096
|
this.ws = ws;
|
|
23982
24097
|
}
|
|
23983
24098
|
loadLayoutDir(dir, map2, meta3, keyField, buildMeta, logLabel) {
|
|
23984
|
-
if (!
|
|
23985
|
-
for (const entry of
|
|
24099
|
+
if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) return;
|
|
24100
|
+
for (const entry of fs9.readdirSync(dir).sort()) {
|
|
23986
24101
|
if (!entry.endsWith(".json")) continue;
|
|
23987
|
-
const fp =
|
|
24102
|
+
const fp = path7.join(dir, entry);
|
|
23988
24103
|
try {
|
|
23989
|
-
const data = JSON.parse(
|
|
23990
|
-
const name = data[keyField] ??
|
|
24104
|
+
const data = JSON.parse(fs9.readFileSync(fp, "utf8"));
|
|
24105
|
+
const name = data[keyField] ?? path7.basename(entry, ".json");
|
|
23991
24106
|
map2.set(name, data);
|
|
23992
24107
|
meta3.push(buildMeta(data, name, fp));
|
|
23993
24108
|
} catch (e) {
|
|
@@ -24047,6 +24162,34 @@ var LocalLayoutProvider = class {
|
|
|
24047
24162
|
"home layout"
|
|
24048
24163
|
);
|
|
24049
24164
|
}
|
|
24165
|
+
ensureNotificationTemplates() {
|
|
24166
|
+
if (this._notificationTemplates !== null) return;
|
|
24167
|
+
this._notificationTemplates = /* @__PURE__ */ new Map();
|
|
24168
|
+
this._notificationTemplateMeta = [];
|
|
24169
|
+
const dir = this.ws.notificationTemplatesDir;
|
|
24170
|
+
if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) return;
|
|
24171
|
+
for (const entry of fs9.readdirSync(dir).sort()) {
|
|
24172
|
+
if (!entry.endsWith(".json")) continue;
|
|
24173
|
+
const fp = path7.join(dir, entry);
|
|
24174
|
+
try {
|
|
24175
|
+
const data = JSON.parse(fs9.readFileSync(fp, "utf8"));
|
|
24176
|
+
const name = path7.basename(entry, ".json");
|
|
24177
|
+
const htmlPath = path7.join(dir, `${name}.html`);
|
|
24178
|
+
this._notificationTemplates.set(name, data);
|
|
24179
|
+
this._notificationTemplateMeta.push({
|
|
24180
|
+
name,
|
|
24181
|
+
title: String(data["title"] ?? name),
|
|
24182
|
+
target: String(data["target"] ?? ""),
|
|
24183
|
+
templateState: String(data["templateState"] ?? ""),
|
|
24184
|
+
hasEmailBody: fs9.existsSync(htmlPath),
|
|
24185
|
+
hasMessageText: Boolean(data["messageText"]),
|
|
24186
|
+
filePath: fp
|
|
24187
|
+
});
|
|
24188
|
+
} catch (e) {
|
|
24189
|
+
log("warn", `Failed to load notification template ${fp}: ${e}`);
|
|
24190
|
+
}
|
|
24191
|
+
}
|
|
24192
|
+
}
|
|
24050
24193
|
formsForType(typeName, mode) {
|
|
24051
24194
|
this.ensureForms();
|
|
24052
24195
|
return this._formMeta.filter(
|
|
@@ -24069,6 +24212,12 @@ var LocalLayoutProvider = class {
|
|
|
24069
24212
|
this.ensureHomeLayouts();
|
|
24070
24213
|
return this._homeLayoutMeta;
|
|
24071
24214
|
}
|
|
24215
|
+
notificationTemplatesForType(typeName) {
|
|
24216
|
+
this.ensureNotificationTemplates();
|
|
24217
|
+
if (!typeName) return this._notificationTemplateMeta;
|
|
24218
|
+
const lower = typeName.toLowerCase();
|
|
24219
|
+
return this._notificationTemplateMeta.filter((t) => t.title.toLowerCase().includes(lower));
|
|
24220
|
+
}
|
|
24072
24221
|
getLayoutJson(layoutType, name) {
|
|
24073
24222
|
switch (layoutType) {
|
|
24074
24223
|
case "form":
|
|
@@ -24119,6 +24268,10 @@ var LocalLayoutProvider = class {
|
|
|
24119
24268
|
this._homeLayouts = null;
|
|
24120
24269
|
this._homeLayoutMeta = null;
|
|
24121
24270
|
break;
|
|
24271
|
+
case "notificationTemplate":
|
|
24272
|
+
this._notificationTemplates = null;
|
|
24273
|
+
this._notificationTemplateMeta = null;
|
|
24274
|
+
break;
|
|
24122
24275
|
}
|
|
24123
24276
|
}
|
|
24124
24277
|
};
|
|
@@ -25232,8 +25385,8 @@ __export(validate_program_exports, {
|
|
|
25232
25385
|
TOOL_DEF: () => TOOL_DEF8,
|
|
25233
25386
|
handle: () => handle8
|
|
25234
25387
|
});
|
|
25235
|
-
var
|
|
25236
|
-
var
|
|
25388
|
+
var fs10 = __toESM(require("fs"));
|
|
25389
|
+
var path8 = __toESM(require("path"));
|
|
25237
25390
|
var os = __toESM(require("os"));
|
|
25238
25391
|
var crypto = __toESM(require("crypto"));
|
|
25239
25392
|
var import_child_process = require("child_process");
|
|
@@ -25377,7 +25530,7 @@ function runStaticChecks(code, programType) {
|
|
|
25377
25530
|
// src/tools/validate-program.ts
|
|
25378
25531
|
var TOOL_DEF8 = {
|
|
25379
25532
|
name: "validate_protrak_program",
|
|
25380
|
-
description: "
|
|
25533
|
+
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
25534
|
inputSchema: {
|
|
25382
25535
|
type: "object",
|
|
25383
25536
|
properties: {
|
|
@@ -25399,18 +25552,18 @@ var TOOL_DEF8 = {
|
|
|
25399
25552
|
}
|
|
25400
25553
|
};
|
|
25401
25554
|
function runDotnetBuild(workspaceRoot, code) {
|
|
25402
|
-
const csprojFiles =
|
|
25555
|
+
const csprojFiles = fs10.readdirSync(workspaceRoot).filter((e) => e.endsWith(".csproj"));
|
|
25403
25556
|
if (csprojFiles.length === 0) {
|
|
25404
25557
|
return { success: false, errors: ["No .csproj file found in workspace"] };
|
|
25405
25558
|
}
|
|
25406
|
-
const programsDir =
|
|
25559
|
+
const programsDir = path8.join(workspaceRoot, "Programs");
|
|
25407
25560
|
const tmpName = `_protrakgen_validate_${crypto.randomBytes(4).toString("hex")}.cs`;
|
|
25408
|
-
const tmpPath =
|
|
25561
|
+
const tmpPath = fs10.existsSync(programsDir) && fs10.statSync(programsDir).isDirectory() ? path8.join(programsDir, tmpName) : path8.join(os.tmpdir(), tmpName);
|
|
25409
25562
|
try {
|
|
25410
|
-
|
|
25563
|
+
fs10.writeFileSync(tmpPath, code, "utf8");
|
|
25411
25564
|
const result = (0, import_child_process.spawnSync)(
|
|
25412
25565
|
"dotnet",
|
|
25413
|
-
["build",
|
|
25566
|
+
["build", path8.join(workspaceRoot, csprojFiles[0]), "--no-restore", "-v", "quiet"],
|
|
25414
25567
|
{ cwd: workspaceRoot, encoding: "utf8", timeout: 3e4 }
|
|
25415
25568
|
);
|
|
25416
25569
|
if (result.error) {
|
|
@@ -25419,7 +25572,7 @@ function runDotnetBuild(workspaceRoot, code) {
|
|
|
25419
25572
|
}
|
|
25420
25573
|
return { success: false, errors: [result.error.message] };
|
|
25421
25574
|
}
|
|
25422
|
-
const tmpStem =
|
|
25575
|
+
const tmpStem = path8.basename(tmpPath, ".cs");
|
|
25423
25576
|
const allOutput = [result.stdout ?? "", result.stderr ?? ""].join("\n");
|
|
25424
25577
|
const errors = allOutput.split("\n").filter((line) => (line.includes(": error ") || line.includes(": warning ")) && line.includes(tmpStem)).map((line) => line.trim());
|
|
25425
25578
|
return { success: result.status === 0, errors };
|
|
@@ -25427,7 +25580,7 @@ function runDotnetBuild(workspaceRoot, code) {
|
|
|
25427
25580
|
return { success: false, errors: [String(e)] };
|
|
25428
25581
|
} finally {
|
|
25429
25582
|
try {
|
|
25430
|
-
|
|
25583
|
+
fs10.unlinkSync(tmpPath);
|
|
25431
25584
|
} catch {
|
|
25432
25585
|
}
|
|
25433
25586
|
}
|
|
@@ -25452,7 +25605,7 @@ function handle8(provider, args) {
|
|
|
25452
25605
|
`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
25606
|
"Re-run validate_protrak_program after fixing."
|
|
25454
25607
|
] : [
|
|
25455
|
-
|
|
25608
|
+
`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
25609
|
];
|
|
25457
25610
|
return JSON.stringify(response, null, 2);
|
|
25458
25611
|
}
|
|
@@ -25463,7 +25616,7 @@ __export(search_protrak_knowledge_exports, {
|
|
|
25463
25616
|
TOOL_DEF: () => TOOL_DEF9,
|
|
25464
25617
|
handle: () => handle9
|
|
25465
25618
|
});
|
|
25466
|
-
var
|
|
25619
|
+
var fs11 = __toESM(require("fs"));
|
|
25467
25620
|
var SCOPES = ["patterns", "api", "programs", "all"];
|
|
25468
25621
|
var TOOL_DEF9 = {
|
|
25469
25622
|
name: "search_protrak_knowledge",
|
|
@@ -25532,7 +25685,7 @@ function searchApi(query, programType, services, topK) {
|
|
|
25532
25685
|
}
|
|
25533
25686
|
function searchPrograms(provider, query, topK) {
|
|
25534
25687
|
const programsDir = provider.ws.programsDir;
|
|
25535
|
-
if (!
|
|
25688
|
+
if (!fs11.existsSync(programsDir) || !fs11.statSync(programsDir).isDirectory()) {
|
|
25536
25689
|
return {
|
|
25537
25690
|
results: [],
|
|
25538
25691
|
note: `No Programs directory found at '${programsDir}'.`
|
|
@@ -25847,7 +26000,7 @@ __export(list_query_definitions_exports, {
|
|
|
25847
26000
|
TOOL_DEF: () => TOOL_DEF14,
|
|
25848
26001
|
handle: () => handle14
|
|
25849
26002
|
});
|
|
25850
|
-
var
|
|
26003
|
+
var fs12 = __toESM(require("fs"));
|
|
25851
26004
|
var TOOL_DEF14 = {
|
|
25852
26005
|
name: "list_protrak_queries",
|
|
25853
26006
|
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 +26017,7 @@ var TOOL_DEF14 = {
|
|
|
25864
26017
|
function handle14(provider, args) {
|
|
25865
26018
|
const typeName = args["type_name"] || void 0;
|
|
25866
26019
|
const qdDir = provider.ws.queryDefinitionsDir;
|
|
25867
|
-
if (!
|
|
26020
|
+
if (!fs12.existsSync(qdDir) || !fs12.statSync(qdDir).isDirectory()) {
|
|
25868
26021
|
return JSON.stringify({
|
|
25869
26022
|
query_definitions: [],
|
|
25870
26023
|
note: `QueryDefinitions directory not found at '${qdDir}'.`
|
|
@@ -25913,8 +26066,8 @@ __export(list_protrak_programs_exports, {
|
|
|
25913
26066
|
TOOL_DEF: () => TOOL_DEF16,
|
|
25914
26067
|
handle: () => handle16
|
|
25915
26068
|
});
|
|
25916
|
-
var
|
|
25917
|
-
var
|
|
26069
|
+
var fs13 = __toESM(require("fs"));
|
|
26070
|
+
var path9 = __toESM(require("path"));
|
|
25918
26071
|
var TOOL_DEF16 = {
|
|
25919
26072
|
name: "list_protrak_programs",
|
|
25920
26073
|
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 +26089,7 @@ function handle16(provider, args) {
|
|
|
25936
26089
|
const typeFilter = args["type_name"] || void 0;
|
|
25937
26090
|
const programTypeFilter = args["program_type"] || void 0;
|
|
25938
26091
|
const programsDir = provider.ws.programsDir;
|
|
25939
|
-
if (!
|
|
26092
|
+
if (!fs13.existsSync(programsDir) || !fs13.statSync(programsDir).isDirectory()) {
|
|
25940
26093
|
return JSON.stringify(
|
|
25941
26094
|
{
|
|
25942
26095
|
programs: [],
|
|
@@ -25948,18 +26101,18 @@ function handle16(provider, args) {
|
|
|
25948
26101
|
}
|
|
25949
26102
|
const entries = [];
|
|
25950
26103
|
const seen = /* @__PURE__ */ new Set();
|
|
25951
|
-
for (const entry of
|
|
26104
|
+
for (const entry of fs13.readdirSync(programsDir).sort()) {
|
|
25952
26105
|
if (!entry.endsWith(".json") && !entry.endsWith(".cs")) continue;
|
|
25953
|
-
const programName =
|
|
26106
|
+
const programName = path9.basename(entry, path9.extname(entry));
|
|
25954
26107
|
if (seen.has(programName)) continue;
|
|
25955
26108
|
seen.add(programName);
|
|
25956
|
-
const csPath =
|
|
25957
|
-
const jsonPath =
|
|
26109
|
+
const csPath = path9.join(programsDir, `${programName}.cs`);
|
|
26110
|
+
const jsonPath = path9.join(programsDir, `${programName}.json`);
|
|
25958
26111
|
let programType = "";
|
|
25959
26112
|
let typeName = "";
|
|
25960
|
-
if (
|
|
26113
|
+
if (fs13.existsSync(jsonPath)) {
|
|
25961
26114
|
try {
|
|
25962
|
-
const meta3 = JSON.parse(
|
|
26115
|
+
const meta3 = JSON.parse(fs13.readFileSync(jsonPath, "utf8"));
|
|
25963
26116
|
programType = meta3["programType"] ?? "";
|
|
25964
26117
|
typeName = meta3["typeName"] ?? meta3["entityTypeName"] ?? meta3["entityType"] ?? "";
|
|
25965
26118
|
} catch {
|
|
@@ -25969,8 +26122,8 @@ function handle16(provider, args) {
|
|
|
25969
26122
|
program_name: programName,
|
|
25970
26123
|
program_type: programType,
|
|
25971
26124
|
type_name: typeName,
|
|
25972
|
-
has_source:
|
|
25973
|
-
file_path:
|
|
26125
|
+
has_source: fs13.existsSync(csPath),
|
|
26126
|
+
file_path: fs13.existsSync(csPath) ? csPath : jsonPath
|
|
25974
26127
|
});
|
|
25975
26128
|
}
|
|
25976
26129
|
let filtered = entries;
|
|
@@ -25992,11 +26145,9 @@ __export(get_program_source_exports, {
|
|
|
25992
26145
|
TOOL_DEF: () => TOOL_DEF17,
|
|
25993
26146
|
handle: () => handle17
|
|
25994
26147
|
});
|
|
25995
|
-
var fs13 = __toESM(require("fs"));
|
|
25996
|
-
var path9 = __toESM(require("path"));
|
|
25997
26148
|
var TOOL_DEF17 = {
|
|
25998
26149
|
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.",
|
|
26150
|
+
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
26151
|
inputSchema: {
|
|
26001
26152
|
type: "object",
|
|
26002
26153
|
properties: {
|
|
@@ -26010,32 +26161,15 @@ var TOOL_DEF17 = {
|
|
|
26010
26161
|
};
|
|
26011
26162
|
function handle17(provider, args) {
|
|
26012
26163
|
const programName = args["program_name"] ?? "";
|
|
26013
|
-
const
|
|
26014
|
-
|
|
26015
|
-
|
|
26016
|
-
return errorResponse(`Program '${programName}' not found at ${csPath}`);
|
|
26017
|
-
}
|
|
26018
|
-
const parsed = parseFile(csPath);
|
|
26019
|
-
if (!parsed) {
|
|
26020
|
-
const source = fs13.readFileSync(csPath, "utf8");
|
|
26021
|
-
return JSON.stringify({ file_path: csPath, source, program_type: "Unknown", services_used: [] }, null, 2);
|
|
26164
|
+
const result = readProgramByName(provider, programName);
|
|
26165
|
+
if (result.kind === "not_found") {
|
|
26166
|
+
return errorResponse(result.message, { available_programs: result.available_programs });
|
|
26022
26167
|
}
|
|
26023
|
-
|
|
26024
|
-
|
|
26025
|
-
|
|
26026
|
-
try {
|
|
26027
|
-
metadata = JSON.parse(fs13.readFileSync(jsonPath, "utf8"));
|
|
26028
|
-
} catch {
|
|
26029
|
-
}
|
|
26168
|
+
if (result.kind === "parse_failed") {
|
|
26169
|
+
const { file_path, source, program_type, services_used } = result.data;
|
|
26170
|
+
return JSON.stringify({ file_path, source, program_type, services_used }, null, JSON_INDENT);
|
|
26030
26171
|
}
|
|
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);
|
|
26172
|
+
return JSON.stringify(result.data, null, JSON_INDENT);
|
|
26039
26173
|
}
|
|
26040
26174
|
|
|
26041
26175
|
// src/tools/generate-query-definition.ts
|
|
@@ -26711,14 +26845,105 @@ function handle21(provider, args) {
|
|
|
26711
26845
|
return successResponse({ file_path: result.filePath, template_name: templateName }, { nextSteps });
|
|
26712
26846
|
}
|
|
26713
26847
|
|
|
26714
|
-
// src/tools/
|
|
26715
|
-
var
|
|
26716
|
-
__export(
|
|
26848
|
+
// src/tools/generate-program.ts
|
|
26849
|
+
var generate_program_exports = {};
|
|
26850
|
+
__export(generate_program_exports, {
|
|
26717
26851
|
TOOL_DEF: () => TOOL_DEF22,
|
|
26718
26852
|
handle: () => handle22
|
|
26719
26853
|
});
|
|
26720
26854
|
var fs15 = __toESM(require("fs"));
|
|
26721
26855
|
var path11 = __toESM(require("path"));
|
|
26856
|
+
var TOOL_DEF22 = {
|
|
26857
|
+
name: "generate_protrak_program",
|
|
26858
|
+
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.",
|
|
26859
|
+
inputSchema: {
|
|
26860
|
+
type: "object",
|
|
26861
|
+
properties: {
|
|
26862
|
+
program_name: {
|
|
26863
|
+
type: "string",
|
|
26864
|
+
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."
|
|
26865
|
+
},
|
|
26866
|
+
program_type: {
|
|
26867
|
+
type: "string",
|
|
26868
|
+
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."
|
|
26869
|
+
},
|
|
26870
|
+
source: {
|
|
26871
|
+
type: "string",
|
|
26872
|
+
description: "Full C# source code for the program. Should already have passed validate_protrak_program. Written verbatim to Programs/<name>.cs."
|
|
26873
|
+
},
|
|
26874
|
+
overwrite: {
|
|
26875
|
+
type: "boolean",
|
|
26876
|
+
description: "When true, overwrite existing files. Default: false (errors on conflict).",
|
|
26877
|
+
default: false
|
|
26878
|
+
}
|
|
26879
|
+
},
|
|
26880
|
+
required: ["program_name", "program_type", "source"]
|
|
26881
|
+
}
|
|
26882
|
+
};
|
|
26883
|
+
function buildCompanionJson(programName, programType) {
|
|
26884
|
+
return {
|
|
26885
|
+
programName,
|
|
26886
|
+
programType,
|
|
26887
|
+
programCode: null,
|
|
26888
|
+
programHashCode: "",
|
|
26889
|
+
programStage: "Published"
|
|
26890
|
+
};
|
|
26891
|
+
}
|
|
26892
|
+
function handle22(provider, args) {
|
|
26893
|
+
const programName = args["program_name"] ?? "";
|
|
26894
|
+
const programType = args["program_type"] ?? "";
|
|
26895
|
+
const source = args["source"] ?? "";
|
|
26896
|
+
const overwrite = args["overwrite"] === true;
|
|
26897
|
+
if (!programName) return errorResponse("program_name is required");
|
|
26898
|
+
if (!isPascalCase(programName)) {
|
|
26899
|
+
return errorResponse(pascalCaseError("program_name", programName));
|
|
26900
|
+
}
|
|
26901
|
+
if (!programType) return errorResponse("program_type is required");
|
|
26902
|
+
if (!source) return errorResponse("source is required");
|
|
26903
|
+
const programsDir = provider.ws.programsDir;
|
|
26904
|
+
if (!fs15.existsSync(programsDir)) {
|
|
26905
|
+
fs15.mkdirSync(programsDir, { recursive: true });
|
|
26906
|
+
}
|
|
26907
|
+
const csPath = path11.join(programsDir, `${programName}.cs`);
|
|
26908
|
+
const jsonPath = path11.join(programsDir, `${programName}.json`);
|
|
26909
|
+
if (!overwrite) {
|
|
26910
|
+
const existing = [];
|
|
26911
|
+
if (fs15.existsSync(csPath)) existing.push(csPath);
|
|
26912
|
+
if (fs15.existsSync(jsonPath)) existing.push(jsonPath);
|
|
26913
|
+
if (existing.length > 0) {
|
|
26914
|
+
return errorResponse(
|
|
26915
|
+
`Refusing to overwrite existing file(s): ${existing.join(", ")}. Pass overwrite:true to replace.`,
|
|
26916
|
+
{ existing_paths: existing }
|
|
26917
|
+
);
|
|
26918
|
+
}
|
|
26919
|
+
}
|
|
26920
|
+
fs15.writeFileSync(csPath, source, "utf8");
|
|
26921
|
+
const companion = buildCompanionJson(programName, programType);
|
|
26922
|
+
fs15.writeFileSync(jsonPath, JSON.stringify(companion, null, JSON_INDENT), "utf8");
|
|
26923
|
+
const nextSteps = [
|
|
26924
|
+
`Verify with read_protrak_program(program_name='${programName}').`,
|
|
26925
|
+
"On import, Protrak recomputes programHashCode automatically \u2014 leaving it empty here is intentional."
|
|
26926
|
+
];
|
|
26927
|
+
return successResponse(
|
|
26928
|
+
{
|
|
26929
|
+
program_name: programName,
|
|
26930
|
+
program_type: programType,
|
|
26931
|
+
cs_path: csPath,
|
|
26932
|
+
json_path: jsonPath,
|
|
26933
|
+
companion_json: companion
|
|
26934
|
+
},
|
|
26935
|
+
{ nextSteps }
|
|
26936
|
+
);
|
|
26937
|
+
}
|
|
26938
|
+
|
|
26939
|
+
// src/tools/attach-attribute-to-type.ts
|
|
26940
|
+
var attach_attribute_to_type_exports = {};
|
|
26941
|
+
__export(attach_attribute_to_type_exports, {
|
|
26942
|
+
TOOL_DEF: () => TOOL_DEF23,
|
|
26943
|
+
handle: () => handle23
|
|
26944
|
+
});
|
|
26945
|
+
var fs16 = __toESM(require("fs"));
|
|
26946
|
+
var path12 = __toESM(require("path"));
|
|
26722
26947
|
|
|
26723
26948
|
// src/tools/_visibility-utils.ts
|
|
26724
26949
|
function applyAttributeVisibility(lifecycleData, attributeName, visibleSet, opts) {
|
|
@@ -26773,7 +26998,7 @@ function applyAttributeVisibility(lifecycleData, attributeName, visibleSet, opts
|
|
|
26773
26998
|
|
|
26774
26999
|
// src/tools/attach-attribute-to-type.ts
|
|
26775
27000
|
var VISIBILITY_ALL = "all";
|
|
26776
|
-
var
|
|
27001
|
+
var TOOL_DEF23 = {
|
|
26777
27002
|
name: "attach_protrak_attribute_to_type",
|
|
26778
27003
|
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
27004
|
inputSchema: {
|
|
@@ -26809,12 +27034,12 @@ var TOOL_DEF22 = {
|
|
|
26809
27034
|
}
|
|
26810
27035
|
};
|
|
26811
27036
|
function readJson(filePath) {
|
|
26812
|
-
return JSON.parse(
|
|
27037
|
+
return JSON.parse(fs16.readFileSync(filePath, "utf8"));
|
|
26813
27038
|
}
|
|
26814
27039
|
function writeJson(filePath, data) {
|
|
26815
|
-
|
|
27040
|
+
fs16.writeFileSync(filePath, JSON.stringify(data, null, JSON_INDENT), "utf8");
|
|
26816
27041
|
}
|
|
26817
|
-
function
|
|
27042
|
+
function handle23(provider, args) {
|
|
26818
27043
|
const typeName = args["type_name"] ?? "";
|
|
26819
27044
|
const attrName = args["attribute_name"] ?? "";
|
|
26820
27045
|
const required2 = args["required"] === true;
|
|
@@ -26837,8 +27062,8 @@ function handle22(provider, args) {
|
|
|
26837
27062
|
"lifecycle_states_visibility must be 'all' or an array of state names."
|
|
26838
27063
|
);
|
|
26839
27064
|
}
|
|
26840
|
-
const typeFilePath =
|
|
26841
|
-
if (!
|
|
27065
|
+
const typeFilePath = path12.join(provider.ws.typesDir, `${typeName}.json`);
|
|
27066
|
+
if (!fs16.existsSync(typeFilePath)) {
|
|
26842
27067
|
const available = Object.keys(provider.types).sort();
|
|
26843
27068
|
return errorResponse(`Type '${typeName}' was not found in ${provider.ws.typesDir}.`, {
|
|
26844
27069
|
available_types: available
|
|
@@ -26873,8 +27098,8 @@ function handle22(provider, args) {
|
|
|
26873
27098
|
let lifecyclePath = null;
|
|
26874
27099
|
let visibilityChanges = [];
|
|
26875
27100
|
if (lifecycleName) {
|
|
26876
|
-
const lifecycleFilePath =
|
|
26877
|
-
if (!
|
|
27101
|
+
const lifecycleFilePath = path12.join(provider.ws.lifecyclesDir, `${lifecycleName}.json`);
|
|
27102
|
+
if (!fs16.existsSync(lifecycleFilePath)) {
|
|
26878
27103
|
warnings.push(
|
|
26879
27104
|
`Lifecycle '${lifecycleName}' (referenced by type '${typeName}') was not found in ${provider.ws.lifecyclesDir}. Visibility was not updated.`
|
|
26880
27105
|
);
|
|
@@ -26928,12 +27153,12 @@ function handle22(provider, args) {
|
|
|
26928
27153
|
// src/tools/attach-attribute-to-layout.ts
|
|
26929
27154
|
var attach_attribute_to_layout_exports = {};
|
|
26930
27155
|
__export(attach_attribute_to_layout_exports, {
|
|
26931
|
-
TOOL_DEF: () =>
|
|
26932
|
-
handle: () =>
|
|
27156
|
+
TOOL_DEF: () => TOOL_DEF24,
|
|
27157
|
+
handle: () => handle24
|
|
26933
27158
|
});
|
|
26934
|
-
var
|
|
26935
|
-
var
|
|
26936
|
-
var
|
|
27159
|
+
var fs17 = __toESM(require("node:fs"));
|
|
27160
|
+
var path13 = __toESM(require("node:path"));
|
|
27161
|
+
var TOOL_DEF24 = {
|
|
26937
27162
|
name: "attach_protrak_attribute_to_layout",
|
|
26938
27163
|
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
27164
|
inputSchema: {
|
|
@@ -26964,16 +27189,10 @@ var TOOL_DEF23 = {
|
|
|
26964
27189
|
required: ["layout_name", "attribute_name"]
|
|
26965
27190
|
}
|
|
26966
27191
|
};
|
|
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
27192
|
function findWidget(widgets, widgetId) {
|
|
26974
27193
|
if (widgets.length === 0) return null;
|
|
26975
27194
|
if (widgetId === null) return widgets[0];
|
|
26976
|
-
return widgets.find((w) =>
|
|
27195
|
+
return widgets.find((w) => (w["templateWidgetKey"] ?? "") === widgetId) ?? null;
|
|
26977
27196
|
}
|
|
26978
27197
|
function makeFormField(attrName) {
|
|
26979
27198
|
return {
|
|
@@ -27002,11 +27221,11 @@ function makeWidgetColumn(attrName, attrType) {
|
|
|
27002
27221
|
};
|
|
27003
27222
|
}
|
|
27004
27223
|
function attachToForm(ws, formName, attrName, containerId, replace) {
|
|
27005
|
-
const filePath =
|
|
27006
|
-
if (!
|
|
27224
|
+
const filePath = path13.join(ws.formsDir, `${formName}.json`);
|
|
27225
|
+
if (!fs17.existsSync(filePath)) {
|
|
27007
27226
|
return { error: `Form '${formName}' was not found in ${ws.formsDir}.` };
|
|
27008
27227
|
}
|
|
27009
|
-
const formData =
|
|
27228
|
+
const formData = readJsonFile(filePath);
|
|
27010
27229
|
const config2 = formData["formConfiguration"] ?? {};
|
|
27011
27230
|
const containers = Array.isArray(config2["containers"]) ? config2["containers"] : [];
|
|
27012
27231
|
if (containers.length === 0) {
|
|
@@ -27016,16 +27235,16 @@ function attachToForm(ws, formName, attrName, containerId, replace) {
|
|
|
27016
27235
|
if (containerId === void 0) {
|
|
27017
27236
|
target = containers[0];
|
|
27018
27237
|
} else {
|
|
27019
|
-
target = containers.find((c) =>
|
|
27238
|
+
target = containers.find((c) => (c["formContainerKey"] ?? "") === containerId);
|
|
27020
27239
|
if (!target) {
|
|
27021
27240
|
return {
|
|
27022
27241
|
error: `Container '${containerId}' not found in form '${formName}'.`,
|
|
27023
|
-
available_containers: containers.map((c) =>
|
|
27242
|
+
available_containers: containers.map((c) => c["formContainerKey"] ?? "")
|
|
27024
27243
|
};
|
|
27025
27244
|
}
|
|
27026
27245
|
}
|
|
27027
27246
|
const fields = Array.isArray(target["fields"]) ? target["fields"] : [];
|
|
27028
|
-
const existingIdx = fields.findIndex((f) =>
|
|
27247
|
+
const existingIdx = fields.findIndex((f) => (f["attributeName"] ?? "") === attrName);
|
|
27029
27248
|
let action;
|
|
27030
27249
|
if (existingIdx >= 0) {
|
|
27031
27250
|
if (!replace) {
|
|
@@ -27040,28 +27259,28 @@ function attachToForm(ws, formName, attrName, containerId, replace) {
|
|
|
27040
27259
|
action = "inserted";
|
|
27041
27260
|
}
|
|
27042
27261
|
target["fields"] = fields;
|
|
27043
|
-
|
|
27262
|
+
writeJsonFilePath(filePath, formData);
|
|
27044
27263
|
return {
|
|
27045
27264
|
result: {
|
|
27046
27265
|
filePath,
|
|
27047
27266
|
formName,
|
|
27048
|
-
containerKey:
|
|
27267
|
+
containerKey: target["formContainerKey"] ?? "",
|
|
27049
27268
|
action
|
|
27050
27269
|
}
|
|
27051
27270
|
};
|
|
27052
27271
|
}
|
|
27053
27272
|
function attachToTypeWidget(provider, widgetName, attrName, replace) {
|
|
27054
|
-
const filePath =
|
|
27055
|
-
if (!
|
|
27273
|
+
const filePath = path13.join(provider.ws.typeWidgetsDir, `${widgetName}.json`);
|
|
27274
|
+
if (!fs17.existsSync(filePath)) {
|
|
27056
27275
|
return { error: `TypeWidget '${widgetName}' was not found in ${provider.ws.typeWidgetsDir}.` };
|
|
27057
27276
|
}
|
|
27058
|
-
const widgetData =
|
|
27277
|
+
const widgetData = readJsonFile(filePath);
|
|
27059
27278
|
const isRelation = widgetData["widgetType"] === "Relation";
|
|
27060
27279
|
const configKey = isRelation ? "relationWidgetConfig" : "dashboardWidgetConfig";
|
|
27061
27280
|
const config2 = widgetData[configKey] ?? {};
|
|
27062
27281
|
const fields = Array.isArray(config2["fields"]) ? config2["fields"] : [];
|
|
27063
27282
|
const attrType = String(provider.attributes[attrName]?.["attributeType"] ?? "Text");
|
|
27064
|
-
const existingIdx = fields.findIndex((f) =>
|
|
27283
|
+
const existingIdx = fields.findIndex((f) => (f["attributeName"] ?? "") === attrName);
|
|
27065
27284
|
let action;
|
|
27066
27285
|
if (existingIdx >= 0) {
|
|
27067
27286
|
if (!replace) {
|
|
@@ -27082,10 +27301,57 @@ function attachToTypeWidget(provider, widgetName, attrName, replace) {
|
|
|
27082
27301
|
}
|
|
27083
27302
|
config2["fields"] = fields;
|
|
27084
27303
|
widgetData[configKey] = config2;
|
|
27085
|
-
|
|
27304
|
+
writeJsonFilePath(filePath, widgetData);
|
|
27086
27305
|
return { result: { filePath, widgetName, action } };
|
|
27087
27306
|
}
|
|
27088
|
-
function
|
|
27307
|
+
function resolveTypeWidgetTarget(provider, widget, attrName, containerId, replace, warnings) {
|
|
27308
|
+
const typeWidgetName = widget["typeWidgetName"] ?? null;
|
|
27309
|
+
const widgetKey = widget["templateWidgetKey"] ?? "";
|
|
27310
|
+
if (!typeWidgetName) {
|
|
27311
|
+
return { error: `Layout widget '${widgetKey}' has widgetType=TypeWidget but no typeWidgetName.` };
|
|
27312
|
+
}
|
|
27313
|
+
if (containerId !== void 0) {
|
|
27314
|
+
warnings.push("container_id is only meaningful for Form widgets; ignored for TypeWidget.");
|
|
27315
|
+
}
|
|
27316
|
+
const out = attachToTypeWidget(provider, typeWidgetName, attrName, replace);
|
|
27317
|
+
if (out.error) {
|
|
27318
|
+
return { error: out.error };
|
|
27319
|
+
}
|
|
27320
|
+
return {
|
|
27321
|
+
target: {
|
|
27322
|
+
kind: "typeWidget",
|
|
27323
|
+
filePath: out.result.filePath,
|
|
27324
|
+
payload: { type_widget_name: out.result.widgetName, action: out.result.action }
|
|
27325
|
+
}
|
|
27326
|
+
};
|
|
27327
|
+
}
|
|
27328
|
+
function resolveTarget(provider, widget, attrName, containerId, replace, warnings) {
|
|
27329
|
+
const widgetKind = widget["widgetType"] ?? "";
|
|
27330
|
+
const formWidgetName = widget["formWidgetName"] ?? null;
|
|
27331
|
+
const typeWidgetName = widget["typeWidgetName"] ?? null;
|
|
27332
|
+
const widgetKey = widget["templateWidgetKey"] ?? "";
|
|
27333
|
+
if (widgetKind === "Form" || formWidgetName && !typeWidgetName) {
|
|
27334
|
+
if (!formWidgetName) {
|
|
27335
|
+
return { error: `Layout widget '${widgetKey}' has widgetType=Form but no formWidgetName.` };
|
|
27336
|
+
}
|
|
27337
|
+
const out = attachToForm(provider.ws, formWidgetName, attrName, containerId, replace);
|
|
27338
|
+
if (out.error) {
|
|
27339
|
+
return { error: out.error, extra: out.available_containers ? { available_containers: out.available_containers } : void 0 };
|
|
27340
|
+
}
|
|
27341
|
+
return {
|
|
27342
|
+
target: {
|
|
27343
|
+
kind: "form",
|
|
27344
|
+
filePath: out.result.filePath,
|
|
27345
|
+
payload: { form_name: out.result.formName, container_id: out.result.containerKey, action: out.result.action }
|
|
27346
|
+
}
|
|
27347
|
+
};
|
|
27348
|
+
}
|
|
27349
|
+
if (widgetKind === "TypeWidget" || typeWidgetName && !formWidgetName) {
|
|
27350
|
+
return resolveTypeWidgetTarget(provider, widget, attrName, containerId, replace, warnings);
|
|
27351
|
+
}
|
|
27352
|
+
return { error: `Layout widget '${widgetKey}' has unsupported widgetType='${widgetKind}'.` };
|
|
27353
|
+
}
|
|
27354
|
+
function handle24(provider, args) {
|
|
27089
27355
|
const layoutName = args["layout_name"] ?? "";
|
|
27090
27356
|
const widgetIdArg = args["widget_id"];
|
|
27091
27357
|
const attrName = args["attribute_name"] ?? "";
|
|
@@ -27094,19 +27360,15 @@ function handle23(provider, args) {
|
|
|
27094
27360
|
if (!layoutName) return errorResponse("layout_name is required");
|
|
27095
27361
|
if (!attrName) return errorResponse("attribute_name is required");
|
|
27096
27362
|
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 {
|
|
27363
|
+
if (widgetIdArg !== null && widgetIdArg !== void 0 && typeof widgetIdArg !== "string") {
|
|
27103
27364
|
return errorResponse("widget_id must be a string templateWidgetKey or null.");
|
|
27104
27365
|
}
|
|
27105
|
-
const
|
|
27106
|
-
|
|
27366
|
+
const widgetId = widgetIdArg ?? null;
|
|
27367
|
+
const layoutPath = path13.join(provider.ws.layoutTemplatesDir, `${layoutName}.json`);
|
|
27368
|
+
if (!fs17.existsSync(layoutPath)) {
|
|
27107
27369
|
return errorResponse(`Layout template '${layoutName}' was not found in ${provider.ws.layoutTemplatesDir}.`);
|
|
27108
27370
|
}
|
|
27109
|
-
const layoutData =
|
|
27371
|
+
const layoutData = readJsonFile(layoutPath);
|
|
27110
27372
|
const config2 = layoutData["configuration"] ?? {};
|
|
27111
27373
|
const widgets = Array.isArray(config2["widgets"]) ? config2["widgets"] : [];
|
|
27112
27374
|
if (widgets.length === 0) {
|
|
@@ -27114,12 +27376,9 @@ function handle23(provider, args) {
|
|
|
27114
27376
|
}
|
|
27115
27377
|
const widget = findWidget(widgets, widgetId);
|
|
27116
27378
|
if (!widget) {
|
|
27117
|
-
return errorResponse(
|
|
27118
|
-
|
|
27119
|
-
|
|
27120
|
-
available_widget_ids: widgets.map((w) => String(w["templateWidgetKey"] ?? ""))
|
|
27121
|
-
}
|
|
27122
|
-
);
|
|
27379
|
+
return errorResponse(`widget_id '${widgetId}' not found in layout '${layoutName}'.`, {
|
|
27380
|
+
available_widget_ids: widgets.map((w) => w["templateWidgetKey"] ?? "")
|
|
27381
|
+
});
|
|
27123
27382
|
}
|
|
27124
27383
|
const warnings = [];
|
|
27125
27384
|
if (!provider.attributes[attrName]) {
|
|
@@ -27127,53 +27386,11 @@ function handle23(provider, args) {
|
|
|
27127
27386
|
`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
27387
|
);
|
|
27129
27388
|
}
|
|
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
|
-
);
|
|
27389
|
+
const resolved = resolveTarget(provider, widget, attrName, containerId, replace, warnings);
|
|
27390
|
+
if ("error" in resolved) {
|
|
27391
|
+
return errorResponse(resolved.error, resolved.extra);
|
|
27176
27392
|
}
|
|
27393
|
+
const { target } = resolved;
|
|
27177
27394
|
const nextSteps = [
|
|
27178
27395
|
`Verify with read_protrak_layout(layout_name='${layoutName}').`,
|
|
27179
27396
|
`If the attribute is not yet bound to the type, call attach_protrak_attribute_to_type to bind it.`
|
|
@@ -27182,7 +27399,7 @@ function handle23(provider, args) {
|
|
|
27182
27399
|
{
|
|
27183
27400
|
layout_name: layoutName,
|
|
27184
27401
|
layout_path: layoutPath,
|
|
27185
|
-
widget_id:
|
|
27402
|
+
widget_id: widget["templateWidgetKey"] ?? "",
|
|
27186
27403
|
target_kind: target.kind,
|
|
27187
27404
|
target_path: target.filePath,
|
|
27188
27405
|
...target.payload
|
|
@@ -27194,12 +27411,12 @@ function handle23(provider, args) {
|
|
|
27194
27411
|
// src/tools/set-attribute-state-visibility.ts
|
|
27195
27412
|
var set_attribute_state_visibility_exports = {};
|
|
27196
27413
|
__export(set_attribute_state_visibility_exports, {
|
|
27197
|
-
TOOL_DEF: () =>
|
|
27198
|
-
handle: () =>
|
|
27414
|
+
TOOL_DEF: () => TOOL_DEF25,
|
|
27415
|
+
handle: () => handle25
|
|
27199
27416
|
});
|
|
27200
|
-
var
|
|
27201
|
-
var
|
|
27202
|
-
var
|
|
27417
|
+
var fs18 = __toESM(require("fs"));
|
|
27418
|
+
var path14 = __toESM(require("path"));
|
|
27419
|
+
var TOOL_DEF25 = {
|
|
27203
27420
|
name: "set_protrak_attribute_state_visibility",
|
|
27204
27421
|
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
27422
|
inputSchema: {
|
|
@@ -27222,13 +27439,13 @@ var TOOL_DEF24 = {
|
|
|
27222
27439
|
required: ["type_name", "attribute_name"]
|
|
27223
27440
|
}
|
|
27224
27441
|
};
|
|
27225
|
-
function
|
|
27226
|
-
return JSON.parse(
|
|
27442
|
+
function readJson2(filePath) {
|
|
27443
|
+
return JSON.parse(fs18.readFileSync(filePath, "utf8"));
|
|
27227
27444
|
}
|
|
27228
|
-
function
|
|
27229
|
-
|
|
27445
|
+
function writeJson2(filePath, data) {
|
|
27446
|
+
fs18.writeFileSync(filePath, JSON.stringify(data, null, JSON_INDENT), "utf8");
|
|
27230
27447
|
}
|
|
27231
|
-
function
|
|
27448
|
+
function handle25(provider, args) {
|
|
27232
27449
|
const typeName = args["type_name"] ?? "";
|
|
27233
27450
|
const attrName = args["attribute_name"] ?? "";
|
|
27234
27451
|
const hasVisibleStatesArg = Object.prototype.hasOwnProperty.call(args, "visible_states");
|
|
@@ -27248,14 +27465,14 @@ function handle24(provider, args) {
|
|
|
27248
27465
|
}
|
|
27249
27466
|
visibleStates = new Set(rawVisible);
|
|
27250
27467
|
}
|
|
27251
|
-
const typeFilePath =
|
|
27252
|
-
if (!
|
|
27468
|
+
const typeFilePath = path14.join(provider.ws.typesDir, `${typeName}.json`);
|
|
27469
|
+
if (!fs18.existsSync(typeFilePath)) {
|
|
27253
27470
|
const available = Object.keys(provider.types).sort();
|
|
27254
27471
|
return errorResponse(`Type '${typeName}' was not found in ${provider.ws.typesDir}.`, {
|
|
27255
27472
|
available_types: available
|
|
27256
27473
|
});
|
|
27257
27474
|
}
|
|
27258
|
-
const typeData =
|
|
27475
|
+
const typeData = readJson2(typeFilePath);
|
|
27259
27476
|
const typeAttributes = Array.isArray(typeData["attributes"]) ? typeData["attributes"] : [];
|
|
27260
27477
|
const knownTypeAttributes = typeAttributes.map((a) => a.name);
|
|
27261
27478
|
const warnings = [];
|
|
@@ -27268,13 +27485,13 @@ function handle24(provider, args) {
|
|
|
27268
27485
|
if (!lifecycleName) {
|
|
27269
27486
|
return errorResponse(`Type '${typeName}' has no lifecycleName set; cannot adjust visibility.`);
|
|
27270
27487
|
}
|
|
27271
|
-
const lifecycleFilePath =
|
|
27272
|
-
if (!
|
|
27488
|
+
const lifecycleFilePath = path14.join(provider.ws.lifecyclesDir, `${lifecycleName}.json`);
|
|
27489
|
+
if (!fs18.existsSync(lifecycleFilePath)) {
|
|
27273
27490
|
return errorResponse(
|
|
27274
27491
|
`Lifecycle '${lifecycleName}' (referenced by type '${typeName}') was not found in ${provider.ws.lifecyclesDir}.`
|
|
27275
27492
|
);
|
|
27276
27493
|
}
|
|
27277
|
-
const lifecycleData =
|
|
27494
|
+
const lifecycleData = readJson2(lifecycleFilePath);
|
|
27278
27495
|
const stateNames = (lifecycleData["states"] ?? []).map(
|
|
27279
27496
|
(s) => String(s["name"] ?? "")
|
|
27280
27497
|
);
|
|
@@ -27291,7 +27508,7 @@ function handle24(provider, args) {
|
|
|
27291
27508
|
knownTypeAttributes
|
|
27292
27509
|
});
|
|
27293
27510
|
if (changed) {
|
|
27294
|
-
|
|
27511
|
+
writeJson2(lifecycleFilePath, lifecycleData);
|
|
27295
27512
|
}
|
|
27296
27513
|
const nextSteps = [
|
|
27297
27514
|
`Verify with read_protrak_schema_element(kind='lifecycle', name='${lifecycleName}').`,
|
|
@@ -27311,6 +27528,194 @@ function handle24(provider, args) {
|
|
|
27311
27528
|
);
|
|
27312
27529
|
}
|
|
27313
27530
|
|
|
27531
|
+
// src/tools/get-notification-context.ts
|
|
27532
|
+
var get_notification_context_exports = {};
|
|
27533
|
+
__export(get_notification_context_exports, {
|
|
27534
|
+
TOOL_DEF: () => TOOL_DEF26,
|
|
27535
|
+
VALID_TARGETS: () => VALID_TARGETS,
|
|
27536
|
+
handle: () => handle26
|
|
27537
|
+
});
|
|
27538
|
+
var fs19 = __toESM(require("fs"));
|
|
27539
|
+
var path15 = __toESM(require("path"));
|
|
27540
|
+
var DATA_DIR2 = path15.join(__dirname, "..", "..", "data");
|
|
27541
|
+
var NOTIFICATION_TEMPLATE_REFERENCE_PATH = path15.join(
|
|
27542
|
+
DATA_DIR2,
|
|
27543
|
+
"patterns",
|
|
27544
|
+
"notification-template-reference.md"
|
|
27545
|
+
);
|
|
27546
|
+
var VALID_TARGETS = [
|
|
27547
|
+
"PromoteNotification",
|
|
27548
|
+
"AccountNotification",
|
|
27549
|
+
"WorkflowNotification",
|
|
27550
|
+
"WorkflowTaskNotification"
|
|
27551
|
+
];
|
|
27552
|
+
var TARGET_DESCRIPTIONS = {
|
|
27553
|
+
PromoteNotification: "Triggered by a lifecycle promote action. Link the template in the lifecycle Send Notification command.",
|
|
27554
|
+
AccountNotification: "Triggered by user creation or password reset. Link in Tenant Settings \u2192 Notifications.",
|
|
27555
|
+
WorkflowNotification: "Triggered by a workflow Notification Activity. Link in Workflow \u2192 Notification Activity.",
|
|
27556
|
+
WorkflowTaskNotification: "Triggered by workflow Input/Checklist Task Activity creation. Link in Workflow \u2192 Input/Checklist Task Activity."
|
|
27557
|
+
};
|
|
27558
|
+
var TOOL_DEF26 = {
|
|
27559
|
+
name: "get_protrak_notification_context",
|
|
27560
|
+
description: "RECOMMENDED first call when creating a Protrak notification template. Returns existing templates from the workspace (optionally filtered by type name) and the full @Model variable reference for all target types. Templates use Razor syntax (@Model.*), NOT {{}} placeholders. Email notifications require two separate templates: one for Subject, one for Body. Missing notificationTemplates/ directory returns an empty list without error.",
|
|
27561
|
+
inputSchema: {
|
|
27562
|
+
type: "object",
|
|
27563
|
+
properties: {
|
|
27564
|
+
type_name: {
|
|
27565
|
+
type: "string",
|
|
27566
|
+
description: "Optional. Filter existing templates whose title contains this name (e.g. 'Invoice'). Omit to return all templates."
|
|
27567
|
+
},
|
|
27568
|
+
target: {
|
|
27569
|
+
type: "string",
|
|
27570
|
+
description: "Optional. Filter by target type: 'PromoteNotification', 'AccountNotification', 'WorkflowNotification', or 'WorkflowTaskNotification'. Omit to return all."
|
|
27571
|
+
}
|
|
27572
|
+
},
|
|
27573
|
+
required: []
|
|
27574
|
+
}
|
|
27575
|
+
};
|
|
27576
|
+
function handle26(provider, args) {
|
|
27577
|
+
const typeName = args["type_name"] || void 0;
|
|
27578
|
+
const targetFilter = args["target"] || void 0;
|
|
27579
|
+
const lp = getOrCreateProvider(provider.ws);
|
|
27580
|
+
let templates = lp.notificationTemplatesForType(typeName);
|
|
27581
|
+
if (targetFilter) {
|
|
27582
|
+
templates = templates.filter((t) => t.target === targetFilter);
|
|
27583
|
+
}
|
|
27584
|
+
let variableReference = "";
|
|
27585
|
+
try {
|
|
27586
|
+
variableReference = fs19.readFileSync(NOTIFICATION_TEMPLATE_REFERENCE_PATH, "utf8");
|
|
27587
|
+
} catch {
|
|
27588
|
+
variableReference = "Variable reference file not found.";
|
|
27589
|
+
}
|
|
27590
|
+
const targetTypes = VALID_TARGETS.map((t) => ({
|
|
27591
|
+
value: t,
|
|
27592
|
+
description: TARGET_DESCRIPTIONS[t]
|
|
27593
|
+
}));
|
|
27594
|
+
const nextSteps = [];
|
|
27595
|
+
if (templates.length > 0) {
|
|
27596
|
+
nextSteps.push(
|
|
27597
|
+
"Review existing_templates above for naming conventions and patterns used in this workspace."
|
|
27598
|
+
);
|
|
27599
|
+
}
|
|
27600
|
+
nextSteps.push(
|
|
27601
|
+
"Call generate_protrak_notification_template(name='<Title> Body', target='PromoteNotification', email_html='<Razor HTML>') to write both .json and .html files.",
|
|
27602
|
+
"For email notifications, also call generate_protrak_notification_template for the subject: name='<Title> Subject', email_html='<one-line subject>'."
|
|
27603
|
+
);
|
|
27604
|
+
const result = {
|
|
27605
|
+
existing_templates: templates,
|
|
27606
|
+
target_types: targetTypes,
|
|
27607
|
+
variable_reference: variableReference,
|
|
27608
|
+
next_steps: nextSteps
|
|
27609
|
+
};
|
|
27610
|
+
return JSON.stringify(result, null, 2);
|
|
27611
|
+
}
|
|
27612
|
+
|
|
27613
|
+
// src/tools/generate-notification-template.ts
|
|
27614
|
+
var generate_notification_template_exports = {};
|
|
27615
|
+
__export(generate_notification_template_exports, {
|
|
27616
|
+
TOOL_DEF: () => TOOL_DEF27,
|
|
27617
|
+
handle: () => handle27
|
|
27618
|
+
});
|
|
27619
|
+
var fs20 = __toESM(require("fs"));
|
|
27620
|
+
var path16 = __toESM(require("path"));
|
|
27621
|
+
function buildTemplateJson(name, target, messageText) {
|
|
27622
|
+
return {
|
|
27623
|
+
title: name,
|
|
27624
|
+
target,
|
|
27625
|
+
emailText: null,
|
|
27626
|
+
hashCode: "",
|
|
27627
|
+
messageText: messageText ?? null,
|
|
27628
|
+
templateState: "Published"
|
|
27629
|
+
};
|
|
27630
|
+
}
|
|
27631
|
+
var TOOL_DEF27 = {
|
|
27632
|
+
name: "generate_protrak_notification_template",
|
|
27633
|
+
description: "Write a Protrak notification template to disk. Creates NotificationTemplates/<name>.json (metadata) and, when email_html is provided, NotificationTemplates/<name>.html (Razor HTML email body). hashCode is left empty \u2014 Protrak recomputes it on import. Templates use @Model.* Razor syntax (NOT {{}} placeholders). For a full email notification, call this tool twice: once for the Subject template (name='<Title> Subject') and once for the Body template (name='<Title> Body'). Call get_protrak_notification_context first to see existing templates and variable reference.",
|
|
27634
|
+
inputSchema: {
|
|
27635
|
+
type: "object",
|
|
27636
|
+
properties: {
|
|
27637
|
+
name: {
|
|
27638
|
+
type: "string",
|
|
27639
|
+
description: "Template title and filename (e.g. 'Invoice Sent Body', 'Invoice Sent Subject'). Spaces are allowed \u2014 matches MIS.Customization naming convention. Subject templates end with 'Subject'; body templates end with 'Body'."
|
|
27640
|
+
},
|
|
27641
|
+
target: {
|
|
27642
|
+
type: "string",
|
|
27643
|
+
description: "Notification event type. One of: 'PromoteNotification' (lifecycle promote), 'AccountNotification' (user creation/password reset), 'WorkflowNotification' (workflow notification activity), 'WorkflowTaskNotification' (workflow input/checklist task)."
|
|
27644
|
+
},
|
|
27645
|
+
email_html: {
|
|
27646
|
+
type: "string",
|
|
27647
|
+
description: "Razor HTML content for the email template. Written to <name>.html. Use @Model.* variables (see get_protrak_notification_context for the full reference). Omit if this is a push-only template."
|
|
27648
|
+
},
|
|
27649
|
+
message_text: {
|
|
27650
|
+
type: "string",
|
|
27651
|
+
description: "Short plain-text push notification content. Stored in the JSON as messageText. Razor syntax is supported. Omit if this is an email-only template."
|
|
27652
|
+
},
|
|
27653
|
+
overwrite: {
|
|
27654
|
+
type: "boolean",
|
|
27655
|
+
description: "When true, overwrite existing files. Default: false (errors on conflict).",
|
|
27656
|
+
default: false
|
|
27657
|
+
}
|
|
27658
|
+
},
|
|
27659
|
+
required: ["name", "target"]
|
|
27660
|
+
}
|
|
27661
|
+
};
|
|
27662
|
+
function handle27(provider, args) {
|
|
27663
|
+
const name = args["name"] ?? "";
|
|
27664
|
+
const target = args["target"] ?? "";
|
|
27665
|
+
const emailHtml = args["email_html"] || void 0;
|
|
27666
|
+
const messageText = args["message_text"] || void 0;
|
|
27667
|
+
const overwrite = args["overwrite"] === true;
|
|
27668
|
+
if (!name) return errorResponse("name is required");
|
|
27669
|
+
if (!target) return errorResponse("target is required");
|
|
27670
|
+
if (!VALID_TARGETS.includes(target)) {
|
|
27671
|
+
return errorResponse(
|
|
27672
|
+
`Unknown target '${target}'. Valid values: ${VALID_TARGETS.join(", ")}.`,
|
|
27673
|
+
{ valid_targets: [...VALID_TARGETS] }
|
|
27674
|
+
);
|
|
27675
|
+
}
|
|
27676
|
+
if (!emailHtml && !messageText) {
|
|
27677
|
+
return errorResponse(
|
|
27678
|
+
"At least one of email_html or message_text must be provided."
|
|
27679
|
+
);
|
|
27680
|
+
}
|
|
27681
|
+
const dir = provider.ws.notificationTemplatesDir;
|
|
27682
|
+
if (!fs20.existsSync(dir)) {
|
|
27683
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
27684
|
+
}
|
|
27685
|
+
const jsonPath = path16.join(dir, `${name}.json`);
|
|
27686
|
+
const htmlPath = emailHtml ? path16.join(dir, `${name}.html`) : null;
|
|
27687
|
+
if (!overwrite) {
|
|
27688
|
+
const existing = [];
|
|
27689
|
+
if (fs20.existsSync(jsonPath)) existing.push(jsonPath);
|
|
27690
|
+
if (htmlPath && fs20.existsSync(htmlPath)) existing.push(htmlPath);
|
|
27691
|
+
if (existing.length > 0) {
|
|
27692
|
+
return errorResponse(
|
|
27693
|
+
`Refusing to overwrite existing file(s): ${existing.join(", ")}. Pass overwrite:true to replace.`,
|
|
27694
|
+
{ existing_paths: existing }
|
|
27695
|
+
);
|
|
27696
|
+
}
|
|
27697
|
+
}
|
|
27698
|
+
const templateJson = buildTemplateJson(name, target, messageText);
|
|
27699
|
+
fs20.writeFileSync(jsonPath, JSON.stringify(templateJson, null, JSON_INDENT), "utf8");
|
|
27700
|
+
if (emailHtml && htmlPath) {
|
|
27701
|
+
fs20.writeFileSync(htmlPath, emailHtml, "utf8");
|
|
27702
|
+
}
|
|
27703
|
+
const nextSteps = [
|
|
27704
|
+
`Verify by calling get_protrak_notification_context(type_name='${name.split(" ")[0]}').`,
|
|
27705
|
+
"On import, Protrak recomputes hashCode automatically \u2014 leaving it empty here is intentional."
|
|
27706
|
+
];
|
|
27707
|
+
return successResponse(
|
|
27708
|
+
{
|
|
27709
|
+
name,
|
|
27710
|
+
target,
|
|
27711
|
+
json_path: jsonPath,
|
|
27712
|
+
...htmlPath ? { html_path: htmlPath } : {},
|
|
27713
|
+
written_json: templateJson
|
|
27714
|
+
},
|
|
27715
|
+
{ nextSteps }
|
|
27716
|
+
);
|
|
27717
|
+
}
|
|
27718
|
+
|
|
27314
27719
|
// src/index.ts
|
|
27315
27720
|
function semverGte(a, b) {
|
|
27316
27721
|
const parse3 = (v) => v.split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -27324,6 +27729,7 @@ var TOOLS = [
|
|
|
27324
27729
|
// Workflow entry points (call these first)
|
|
27325
27730
|
get_program_context_exports,
|
|
27326
27731
|
get_layout_context_exports,
|
|
27732
|
+
get_notification_context_exports,
|
|
27327
27733
|
scaffold_entity_exports,
|
|
27328
27734
|
validate_program_exports,
|
|
27329
27735
|
// Knowledge search (patterns / api / programs)
|
|
@@ -27346,6 +27752,8 @@ var TOOLS = [
|
|
|
27346
27752
|
generate_form_exports,
|
|
27347
27753
|
generate_type_widget_exports,
|
|
27348
27754
|
generate_layout_template_exports,
|
|
27755
|
+
generate_program_exports,
|
|
27756
|
+
generate_notification_template_exports,
|
|
27349
27757
|
// Surgical mutators — modify existing files without regeneration/clobber
|
|
27350
27758
|
attach_attribute_to_type_exports,
|
|
27351
27759
|
attach_attribute_to_layout_exports,
|
|
@@ -27385,11 +27793,18 @@ Restart VS Code (or reload the window) so npx can fetch the latest version.
|
|
|
27385
27793
|
log("info", `Protrak Forge MCP ready \u2014 ${TOOLS.length} tools | workspace: ${workspace.namespace} | types: ${typeCount} | programs: ${programCount}`);
|
|
27386
27794
|
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
27795
|
|
|
27388
|
-
WORKFLOW for writing Protrak
|
|
27796
|
+
WORKFLOW for writing a NEW Protrak program:
|
|
27389
27797
|
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
27798
|
2. Write the C# program following the interface contract and patterns.
|
|
27391
27799
|
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
27800
|
4. ALWAYS call validate_protrak_program after writing to check for errors.
|
|
27801
|
+
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.
|
|
27802
|
+
|
|
27803
|
+
WORKFLOW for REFACTORING an existing Protrak program:
|
|
27804
|
+
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).
|
|
27805
|
+
2. Edit the C# source.
|
|
27806
|
+
3. Call validate_protrak_program(code, program_type) \u2014 same as for new programs.
|
|
27807
|
+
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
27808
|
|
|
27394
27809
|
WORKFLOW for creating a QueryDefinition (no program needed):
|
|
27395
27810
|
1. Call read_protrak_schema_element with kind='type' to get attribute names and valid state names.
|
|
@@ -27405,6 +27820,13 @@ WORKFLOW for scaffolding schema elements (Types, Lifecycles, Attributes, Relatio
|
|
|
27405
27820
|
6. All schema names must be PascalCase. Aliases are allowed for attribute_type: Number\u2192Numeric, File\u2192Attachment.
|
|
27406
27821
|
7. All generators refuse to overwrite an existing file unless overwrite:true.
|
|
27407
27822
|
|
|
27823
|
+
WORKFLOW for writing a notification template:
|
|
27824
|
+
1. Call get_protrak_notification_context first \u2014 returns existing templates and the full @Model variable reference.
|
|
27825
|
+
2. Write the Razor HTML email body using @Model.* syntax (NOT {{}} placeholders).
|
|
27826
|
+
3. Call generate_protrak_notification_template for the Body template (name="<Title> Body", target, email_html).
|
|
27827
|
+
4. Call generate_protrak_notification_template again for the Subject template (name="<Title> Subject", email_html="<one-liner subject>").
|
|
27828
|
+
5. For push-only notifications, omit email_html and provide message_text instead.
|
|
27829
|
+
|
|
27408
27830
|
WORKFLOW for designing a Design Studio layout:
|
|
27409
27831
|
1. Call get_protrak_layout_context first \u2014 returns type schema and all existing layouts for the type+mode.
|
|
27410
27832
|
2. Use list_protrak_layouts to browse all artifacts; use read_protrak_layout to read a specific one's full JSON.
|