@prorigo/protrak-forge 0.4.0 → 0.4.1

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.
@@ -17948,7 +17948,7 @@ function getClient() {
17948
17948
  }
17949
17949
 
17950
17950
  // src/version.ts
17951
- var PACKAGE_VERSION = "0.4.0";
17951
+ var PACKAGE_VERSION = "0.4.1";
17952
17952
 
17953
17953
  // src/tools/_schema-write-utils.ts
17954
17954
  var fs4 = __toESM(require("fs"));
@@ -22486,9 +22486,6 @@ function serializeStyle(style) {
22486
22486
  if (!FONT_STYLE_OPTIONS.includes(v)) {
22487
22487
  return { error: `font_style entry '${v}' must be one of: ${FONT_STYLE_OPTIONS.join(", ")}.` };
22488
22488
  }
22489
- if (seen.has(v)) {
22490
- return { error: `font_style contains duplicate entry '${v}'.` };
22491
- }
22492
22489
  seen.add(v);
22493
22490
  }
22494
22491
  if (seen.has("Bold")) tokens.push("fontWeight:bold");
@@ -22753,7 +22750,7 @@ var GROUP_INPUT_SCHEMA = {
22753
22750
  };
22754
22751
  var TOOL_DEF29 = {
22755
22752
  name: "generate_protrak_rule",
22756
- description: "Generate and write a Rule JSON file to <workspace>/Rules/<name>.json. Rules express attribute-filter logic used by display conditions, conditional formatting, dependent picklists, and Access Policy. Inputs are strictly structured: the agent should translate natural-language intent into attribute_filter_groups using the schema returned by get_protrak_rule_context. Validates: PascalCase name; Instance-context attribute_name exists in the workspace (or is a recognised basic attribute); the condition is valid for the attribute's type; value presence matches the condition (e.g. Between requires both, IsEmpty rejects values); dynamic values match @Instance.X, @User.X, or $StartOfToday()/$StartOfMonth(\xB1Nd) patterns; @Instance dynamic references resolve to known attributes. Pass error_message only when the rule will be used as an Access Policy. Refuses to overwrite an existing file unless overwrite:true.",
22753
+ description: "Generate and write a Rule JSON file to <workspace>/Rules/<name>.json. Rules express attribute-filter logic used by display conditions, conditional formatting, dependent picklists, and Access Policy. Inputs are strictly structured: the agent should translate natural-language intent into attribute_filter_groups using the schema returned by get_protrak_rule_context. Validates: PascalCase name; Instance-context attribute_name exists in the workspace (or is a recognised basic attribute); the condition is valid for the attribute's type; value presence matches the condition (e.g. Between requires both, IsEmpty rejects values); dynamic values match @Instance.X, @User.X, or $StartOfToday()/$StartOfMonth(\xB1N[d|w|m|y]) patterns; @Instance dynamic references resolve to known attributes. Pass error_message only when the rule will be used as an Access Policy. Refuses to overwrite an existing file unless overwrite:true.",
22757
22754
  inputSchema: {
22758
22755
  type: "object",
22759
22756
  properties: {
@@ -22816,12 +22813,13 @@ function validateCondition(provider, cond, prefix) {
22816
22813
  if (typeof cond.condition !== "string" || !cond.condition) {
22817
22814
  return `${prefix}.condition is required.`;
22818
22815
  }
22819
- let validConditions;
22820
22816
  if (cond.attribute_context === "UserRoles") {
22821
22817
  if (cond.attribute_name) {
22822
22818
  return `${prefix}.attribute_name must be omitted when attribute_context is 'UserRoles'.`;
22823
22819
  }
22824
- validConditions = USER_ROLES_CONDITIONS;
22820
+ if (!USER_ROLES_CONDITIONS.includes(cond.condition)) {
22821
+ return `${prefix}.condition '${cond.condition}' is not valid for UserRoles context. Valid: ${USER_ROLES_CONDITIONS.join(", ")}.`;
22822
+ }
22825
22823
  } else {
22826
22824
  if (!cond.attribute_name) {
22827
22825
  return `${prefix}.attribute_name is required for attribute_context='${cond.attribute_context}'.`;
@@ -22834,7 +22832,7 @@ function validateCondition(provider, cond, prefix) {
22834
22832
  if (!attrType) {
22835
22833
  return `${prefix} references Instance attribute '${cond.attribute_name}' which does not exist in the workspace. Add the attribute (or use a basic attribute name like Name/State/Created/Creator) before generating this rule.`;
22836
22834
  }
22837
- validConditions = CONDITIONS_BY_ATTR_TYPE[attrType] ?? CONDITIONS_BY_ATTR_TYPE["Text"];
22835
+ const validConditions = CONDITIONS_BY_ATTR_TYPE[attrType] ?? CONDITIONS_BY_ATTR_TYPE["Text"];
22838
22836
  if (!validConditions.includes(cond.condition)) {
22839
22837
  return `${prefix}.condition '${cond.condition}' is not valid for Instance attribute '${cond.attribute_name}' (type=${attrType}). Valid conditions: ${validConditions.join(", ")}.`;
22840
22838
  }
@@ -22849,12 +22847,8 @@ function validateCondition(provider, cond, prefix) {
22849
22847
  if (!acceptable.has(cond.condition)) {
22850
22848
  return `${prefix}.condition '${cond.condition}' is not recognised for User-context conditions.`;
22851
22849
  }
22852
- validConditions = Array.from(acceptable);
22853
22850
  }
22854
22851
  }
22855
- if (cond.attribute_context === "UserRoles" && !USER_ROLES_CONDITIONS.includes(cond.condition)) {
22856
- return `${prefix}.condition '${cond.condition}' is not valid for UserRoles context. Valid: ${USER_ROLES_CONDITIONS.join(", ")}.`;
22857
- }
22858
22852
  const valueless = VALUE_LESS_CONDITIONS.has(cond.condition);
22859
22853
  const ranged = RANGE_CONDITIONS.has(cond.condition);
22860
22854
  const hasFirst = cond.first_value !== void 0;
@@ -22995,7 +22989,7 @@ var DISPLAY_TARGET_KINDS = ["layout_template", "form"];
22995
22989
  var FORMAT_TARGET_KINDS = ["type_widget", "form"];
22996
22990
  var TOOL_DEF30 = {
22997
22991
  name: "attach_protrak_rule",
22998
- description: "Attach an existing Rule to a Protrak schema artifact. One polymorphic tool covering five consumer shapes selected by `usage`: 'display_condition' (LayoutTemplate widget displayConditions[] or Form field displayCondition); 'format_condition' (ViewLayout field formatConditions); 'conditional_formatting' (TypeWidget column or Form field conditionalFormatting); 'access_policy' (Type.accessPolicyRule); 'dependent_picklist' (Picklist option.rule). Rule must already exist in Rules/. Target file/widget/attribute/option must exist; the tool refuses to silently create them. Style is structured ({ background_color?, fore_color?, font_style? }) and serialized to the comma-terminated CSS-string the SPA evaluator expects.",
22992
+ description: "Attach an existing Rule to a Protrak schema artifact. One polymorphic tool covering five consumer shapes selected by `usage`: 'display_condition' (LayoutTemplate widget displayConditions[], Form container displayConditions[], or Form field displayCondition); 'format_condition' (ViewLayout field formatConditions); 'conditional_formatting' (TypeWidget column or Form field conditionalFormatting); 'access_policy' (Type.accessPolicyRule); 'dependent_picklist' (Picklist option.rule). Rule must already exist in Rules/. Target file/widget/attribute/option must exist; the tool refuses to silently create them. Style is structured ({ background_color?, fore_color?, font_style? }) and serialized to the comma-terminated CSS-string the SPA evaluator expects.",
22999
22993
  inputSchema: {
23000
22994
  type: "object",
23001
22995
  properties: {
@@ -23028,7 +23022,7 @@ var TOOL_DEF30 = {
23028
23022
  },
23029
23023
  container_id: {
23030
23024
  type: "string",
23031
- description: "Form target only: formContainerKey to disambiguate when multiple containers exist."
23025
+ description: "Form target only: formContainerKey. For usage='display_condition': when provided WITHOUT attribute_name, attaches the rule to the container itself (plural displayConditions[]). When attribute_name is also provided, disambiguates which container to search for that field. For usage='conditional_formatting': disambiguates which container holds the field. When omitted on a field lookup and the same attribute exists in multiple containers, the first match wins \u2014 pass container_id to disambiguate."
23032
23026
  },
23033
23027
  section_name: {
23034
23028
  type: "string",
@@ -23084,7 +23078,6 @@ function findFormField(formData, attrName, containerId) {
23084
23078
  return {
23085
23079
  result: {
23086
23080
  field: fields[idx],
23087
- index: idx,
23088
23081
  parent: fields,
23089
23082
  containerKey: container["formContainerKey"] ?? ""
23090
23083
  }
@@ -23158,16 +23151,50 @@ function attachDisplayCondition(provider, args) {
23158
23151
  );
23159
23152
  }
23160
23153
  const attrName = args["attribute_name"];
23161
- if (!attrName) {
23162
- return errorResponse("attribute_name is required for usage='display_condition' on a form.");
23154
+ const containerId = args["container_id"] ?? void 0;
23155
+ if (!attrName && !containerId) {
23156
+ return errorResponse(
23157
+ "Either attribute_name (to attach to a field) or container_id (to attach to a whole container) is required for usage='display_condition' on a form."
23158
+ );
23163
23159
  }
23164
- if (!isPascalCase(attrName)) return errorResponse(pascalCaseError("attribute_name", attrName));
23165
23160
  const filePath = path17.join(provider.ws.formsDir, `${targetName}.json`);
23166
23161
  if (!fs21.existsSync(filePath)) {
23167
23162
  return errorResponse(`Form '${targetName}' not found at ${filePath}.`);
23168
23163
  }
23169
23164
  const data = readJsonFile(filePath);
23170
- const containerId = args["container_id"] ?? void 0;
23165
+ if (!attrName) {
23166
+ const config2 = data["formConfiguration"] ?? {};
23167
+ const containers = Array.isArray(config2["containers"]) ? config2["containers"] : [];
23168
+ const container = containers.find((c) => c["formContainerKey"] === containerId);
23169
+ if (!container) {
23170
+ return errorResponse(`Container '${containerId}' not found in form '${targetName}'.`, {
23171
+ available_containers: containers.map((c) => c["formContainerKey"] ?? "")
23172
+ });
23173
+ }
23174
+ const existing2 = Array.isArray(container["displayConditions"]) ? container["displayConditions"] : [];
23175
+ if (existing2.includes(ruleName)) {
23176
+ if (!replace) {
23177
+ return errorResponse(
23178
+ `Rule '${ruleName}' is already in displayConditions for container '${containerId}'. Pass replace:true to no-op silently.`
23179
+ );
23180
+ }
23181
+ } else {
23182
+ existing2.push(ruleName);
23183
+ }
23184
+ container["displayConditions"] = existing2;
23185
+ writeJsonFilePath(filePath, data);
23186
+ return successResponse(
23187
+ {
23188
+ usage: "display_condition",
23189
+ target_kind: "form",
23190
+ target_path: filePath,
23191
+ container_id: containerId,
23192
+ display_conditions: existing2
23193
+ },
23194
+ { nextSteps: [`Verify by reading Forms/${targetName}.json.`] }
23195
+ );
23196
+ }
23197
+ if (!isPascalCase(attrName)) return errorResponse(pascalCaseError("attribute_name", attrName));
23171
23198
  const lookup2 = findFormField(data, attrName, containerId);
23172
23199
  if (lookup2.error) return errorResponse(lookup2.error, lookup2.available_containers ? { available_containers: lookup2.available_containers } : void 0);
23173
23200
  const field = lookup2.result.field;
@@ -23279,7 +23306,8 @@ function attachFormatCondition(provider, args) {
23279
23306
  usage: "format_condition",
23280
23307
  target_path: filePath,
23281
23308
  attribute_name: attrName,
23282
- format_conditions: fc
23309
+ entry,
23310
+ total_format_conditions: fc.length
23283
23311
  },
23284
23312
  { nextSteps: [`Verify by reading ViewLayouts/${targetName}.json.`] }
23285
23313
  );
@@ -23371,7 +23399,8 @@ function attachConditionalFormatting(provider, args) {
23371
23399
  target_kind: targetKind,
23372
23400
  target_path: filePath,
23373
23401
  attribute_name: attrName,
23374
- conditional_formatting: cf
23402
+ entry,
23403
+ total_conditional_formatting: cf.length
23375
23404
  },
23376
23405
  { nextSteps: [`Verify by reading ${path17.basename(filePath)}.`] }
23377
23406
  );
@@ -178,6 +178,9 @@ Call `attach_protrak_rule(usage, rule_name, ...)`. One polymorphic tool covers f
178
178
  — appends to the widget's `displayConditions: string[]`.
179
179
  - `usage='display_condition'`, `target_kind='form'`, `target_name`, `attribute_name`,
180
180
  `container_id?` — sets the **singular** `displayCondition` on the form field.
181
+ - `usage='display_condition'`, `target_kind='form'`, `target_name`, `container_id`
182
+ (no `attribute_name`) — appends to the container's **plural** `displayConditions: string[]`
183
+ (same shape as a LayoutTemplate widget).
181
184
  - `usage='format_condition'`, `target_name=<ViewLayout>`, `attribute_name`, `section_name?`,
182
185
  `widget_name?`, `style`, `order_index?` — appends `{ rule:{name}, orderIndex, style }`
183
186
  to the ViewLayout field's `formatConditions`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prorigo/protrak-forge",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Protrak domain context for coding agents — MCP server for GitHub Copilot, Claude, and Cursor",
5
5
  "bin": {
6
6
  "protrak-forge": "./bin/protrak-forge.js"