@prorigo/protrak-forge 0.4.1 → 0.4.2

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.
Files changed (2) hide show
  1. package/bin/protrak-forge.js +139 -29
  2. package/package.json +1 -1
@@ -15672,6 +15672,14 @@ var LocalSchemaProvider = class {
15672
15672
  invalidateRules() {
15673
15673
  this._rules = null;
15674
15674
  }
15675
+ /** Force a re-read of types on next access. */
15676
+ invalidateTypes() {
15677
+ this._types = null;
15678
+ }
15679
+ /** Force a re-read of attributes on next access. */
15680
+ invalidateAttributes() {
15681
+ this._attributes = null;
15682
+ }
15675
15683
  // ── Public API ────────────────────────────────────────────────────────────
15676
15684
  listTypes() {
15677
15685
  return Object.entries(this.types).map(([name, typeData]) => ({
@@ -17948,7 +17956,7 @@ function getClient() {
17948
17956
  }
17949
17957
 
17950
17958
  // src/version.ts
17951
- var PACKAGE_VERSION = "0.4.1";
17959
+ var PACKAGE_VERSION = "0.4.2";
17952
17960
 
17953
17961
  // src/tools/_schema-write-utils.ts
17954
17962
  var fs4 = __toESM(require("fs"));
@@ -22596,6 +22604,50 @@ var EXAMPLES = [
22596
22604
  }
22597
22605
  ]
22598
22606
  }
22607
+ },
22608
+ {
22609
+ intent: "Current user's roles include 'Manager'. UserRoles context: attributeName is omitted entirely; Contains takes only firstValue.",
22610
+ rule: {
22611
+ name: "CurrentUserIsManager",
22612
+ attributeFilterGroup: [
22613
+ {
22614
+ operator: "None",
22615
+ attributeFilterConditions: [
22616
+ {
22617
+ attributeContext: "UserRoles",
22618
+ condition: "Contains",
22619
+ firstValue: { valueType: "Static", value: "Manager" },
22620
+ operator: "None"
22621
+ }
22622
+ ]
22623
+ }
22624
+ ]
22625
+ }
22626
+ },
22627
+ {
22628
+ intent: "Visible when current user is the Creator OR has the 'Manager' role. Mixes UserRoles + Instance contexts in a single group joined by Or.",
22629
+ rule: {
22630
+ name: "VisibleToManagerOrCreator",
22631
+ attributeFilterGroup: [
22632
+ {
22633
+ operator: "None",
22634
+ attributeFilterConditions: [
22635
+ {
22636
+ attributeContext: "UserRoles",
22637
+ condition: "Contains",
22638
+ firstValue: { valueType: "Static", value: "Manager" },
22639
+ operator: "Or"
22640
+ },
22641
+ {
22642
+ attributeName: "Creator",
22643
+ attributeContext: "Instance",
22644
+ condition: "ContextUser",
22645
+ operator: "None"
22646
+ }
22647
+ ]
22648
+ }
22649
+ ]
22650
+ }
22599
22651
  }
22600
22652
  ];
22601
22653
  var DYNAMIC_VALUE_RULES = {
@@ -22709,7 +22761,7 @@ var CONDITION_INPUT_SCHEMA = {
22709
22761
  },
22710
22762
  attribute_name: {
22711
22763
  type: "string",
22712
- description: "PascalCase attribute name. Required for Instance and User contexts. Omit for UserRoles context (it evaluates the current user's roles)."
22764
+ description: "PascalCase attribute name. Required for Instance and User contexts. OMIT this key entirely for UserRoles context (it evaluates the current user's roles). Do NOT send empty string '' \u2014 leave the key out."
22713
22765
  },
22714
22766
  condition: {
22715
22767
  type: "string",
@@ -22717,11 +22769,11 @@ var CONDITION_INPUT_SCHEMA = {
22717
22769
  },
22718
22770
  first_value: {
22719
22771
  ...VALUE_INPUT_SCHEMA,
22720
- description: "First value. Required when the condition takes a value; omit for IsEmpty/Today/ContextUser/etc."
22772
+ description: "First value of the condition. Required for value-bearing conditions (Equals, Contains, GreaterThan, Between, In, etc.). OMIT this key entirely for valueless conditions: IsEmpty, IsNotEmpty, Today, LastOneWeek, LastOneMonth, LastOneYear, NextWeek, NextOneMonth, NextOneYear, ContextUser. Do NOT send `null` or `{value: ''}` \u2014 leave the key out of the JSON object."
22721
22773
  },
22722
22774
  second_value: {
22723
22775
  ...VALUE_INPUT_SCHEMA,
22724
- description: "Second value. Required only for 'Between' conditions."
22776
+ description: "Second value. Only valid for 'Between'. OMIT this key entirely for every other condition (including Contains, Equals, In, IsEmpty, ContextUser, etc.). Do NOT send `null` or `{value: ''}` \u2014 leave the key out of the JSON object."
22725
22777
  },
22726
22778
  operator: {
22727
22779
  type: "string",
@@ -22777,6 +22829,49 @@ var TOOL_DEF29 = {
22777
22829
  required: ["name", "attribute_filter_groups"]
22778
22830
  }
22779
22831
  };
22832
+ function normalizeValue(v) {
22833
+ if (v === void 0 || v === null) return void 0;
22834
+ if (typeof v !== "object") return v;
22835
+ const obj = v;
22836
+ if (typeof obj["value"] === "string" && obj["value"].length === 0) return void 0;
22837
+ return obj;
22838
+ }
22839
+ function normalizeCondition(rc) {
22840
+ const attrName = rc["attribute_name"];
22841
+ return {
22842
+ attribute_context: rc["attribute_context"],
22843
+ attribute_name: typeof attrName === "string" && attrName.length > 0 ? attrName : void 0,
22844
+ condition: rc["condition"],
22845
+ first_value: normalizeValue(rc["first_value"]),
22846
+ second_value: normalizeValue(rc["second_value"]),
22847
+ operator: rc["operator"]
22848
+ };
22849
+ }
22850
+ function expectedConditionShape(cond) {
22851
+ const shape = {
22852
+ attribute_context: cond.attribute_context
22853
+ };
22854
+ if (cond.attribute_context !== "UserRoles") {
22855
+ shape["attribute_name"] = cond.attribute_name ?? "<AttributeName>";
22856
+ }
22857
+ shape["condition"] = cond.condition;
22858
+ if (RANGE_CONDITIONS.has(cond.condition)) {
22859
+ shape["first_value"] = { value_type: "Static", value: "<from>" };
22860
+ shape["second_value"] = { value_type: "Static", value: "<to>" };
22861
+ } else if (!VALUE_LESS_CONDITIONS.has(cond.condition)) {
22862
+ shape["first_value"] = { value_type: "Static", value: "<value>" };
22863
+ }
22864
+ shape["operator"] = cond.operator;
22865
+ return shape;
22866
+ }
22867
+ function conditionShapeHints(cond) {
22868
+ return {
22869
+ expected_shape: expectedConditionShape(cond),
22870
+ valueless_conditions: [...VALUE_LESS_CONDITIONS].sort(),
22871
+ range_conditions: [...RANGE_CONDITIONS].sort(),
22872
+ hint: 'Send the value-bearing keys ONLY when needed. To "omit" a key, leave it out of the JSON entirely \u2014 do not send null or { value: "" }.'
22873
+ };
22874
+ }
22780
22875
  function validateValueShape(value, label) {
22781
22876
  if (value === void 0) return null;
22782
22877
  if (typeof value !== "object" || value === null) {
@@ -22805,36 +22900,42 @@ function validateDynamicValue(provider, value, label) {
22805
22900
  }
22806
22901
  function validateCondition(provider, cond, prefix) {
22807
22902
  if (!ATTRIBUTE_CONTEXTS.includes(cond.attribute_context)) {
22808
- return `${prefix}.attribute_context must be one of: ${ATTRIBUTE_CONTEXTS.join(", ")}.`;
22903
+ return { error: `${prefix}.attribute_context must be one of: ${ATTRIBUTE_CONTEXTS.join(", ")}.` };
22809
22904
  }
22810
22905
  if (!GROUP_OPERATORS.includes(cond.operator)) {
22811
- return `${prefix}.operator must be one of: ${GROUP_OPERATORS.join(", ")}.`;
22906
+ return { error: `${prefix}.operator must be one of: ${GROUP_OPERATORS.join(", ")}.` };
22812
22907
  }
22813
22908
  if (typeof cond.condition !== "string" || !cond.condition) {
22814
- return `${prefix}.condition is required.`;
22909
+ return { error: `${prefix}.condition is required.` };
22815
22910
  }
22816
22911
  if (cond.attribute_context === "UserRoles") {
22817
22912
  if (cond.attribute_name) {
22818
- return `${prefix}.attribute_name must be omitted when attribute_context is 'UserRoles'.`;
22913
+ return { error: `${prefix}.attribute_name must be omitted when attribute_context is 'UserRoles'.` };
22819
22914
  }
22820
22915
  if (!USER_ROLES_CONDITIONS.includes(cond.condition)) {
22821
- return `${prefix}.condition '${cond.condition}' is not valid for UserRoles context. Valid: ${USER_ROLES_CONDITIONS.join(", ")}.`;
22916
+ return {
22917
+ error: `${prefix}.condition '${cond.condition}' is not valid for UserRoles context. Valid: ${USER_ROLES_CONDITIONS.join(", ")}.`
22918
+ };
22822
22919
  }
22823
22920
  } else {
22824
22921
  if (!cond.attribute_name) {
22825
- return `${prefix}.attribute_name is required for attribute_context='${cond.attribute_context}'.`;
22922
+ return { error: `${prefix}.attribute_name is required for attribute_context='${cond.attribute_context}'.` };
22826
22923
  }
22827
22924
  if (!isPascalCase(cond.attribute_name)) {
22828
- return `${prefix}.${pascalCaseError("attribute_name", cond.attribute_name)}`;
22925
+ return { error: `${prefix}.${pascalCaseError("attribute_name", cond.attribute_name)}` };
22829
22926
  }
22830
22927
  if (cond.attribute_context === "Instance") {
22831
22928
  const attrType = resolveInstanceAttributeType(provider, cond.attribute_name);
22832
22929
  if (!attrType) {
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.`;
22930
+ return {
22931
+ error: `${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.`
22932
+ };
22834
22933
  }
22835
22934
  const validConditions = CONDITIONS_BY_ATTR_TYPE[attrType] ?? CONDITIONS_BY_ATTR_TYPE["Text"];
22836
22935
  if (!validConditions.includes(cond.condition)) {
22837
- return `${prefix}.condition '${cond.condition}' is not valid for Instance attribute '${cond.attribute_name}' (type=${attrType}). Valid conditions: ${validConditions.join(", ")}.`;
22936
+ return {
22937
+ error: `${prefix}.condition '${cond.condition}' is not valid for Instance attribute '${cond.attribute_name}' (type=${attrType}). Valid conditions: ${validConditions.join(", ")}.`
22938
+ };
22838
22939
  }
22839
22940
  } else {
22840
22941
  const acceptable = /* @__PURE__ */ new Set([
@@ -22845,7 +22946,9 @@ function validateCondition(provider, cond, prefix) {
22845
22946
  ...CONDITIONS_BY_ATTR_TYPE["Boolean"]
22846
22947
  ]);
22847
22948
  if (!acceptable.has(cond.condition)) {
22848
- return `${prefix}.condition '${cond.condition}' is not recognised for User-context conditions.`;
22949
+ return {
22950
+ error: `${prefix}.condition '${cond.condition}' is not recognised for User-context conditions.`
22951
+ };
22849
22952
  }
22850
22953
  }
22851
22954
  }
@@ -22855,18 +22958,30 @@ function validateCondition(provider, cond, prefix) {
22855
22958
  const hasSecond = cond.second_value !== void 0;
22856
22959
  if (valueless) {
22857
22960
  if (hasFirst || hasSecond) {
22858
- return `${prefix}.condition '${cond.condition}' takes no value(s); remove first_value/second_value.`;
22961
+ return {
22962
+ error: `${prefix}.condition '${cond.condition}' takes no value(s); omit first_value and second_value entirely.`,
22963
+ extras: conditionShapeHints(cond)
22964
+ };
22859
22965
  }
22860
22966
  } else if (ranged) {
22861
22967
  if (!hasFirst || !hasSecond) {
22862
- return `${prefix}.condition '${cond.condition}' requires BOTH first_value and second_value.`;
22968
+ return {
22969
+ error: `${prefix}.condition '${cond.condition}' requires BOTH first_value and second_value.`,
22970
+ extras: conditionShapeHints(cond)
22971
+ };
22863
22972
  }
22864
22973
  } else {
22865
22974
  if (!hasFirst) {
22866
- return `${prefix}.condition '${cond.condition}' requires first_value.`;
22975
+ return {
22976
+ error: `${prefix}.condition '${cond.condition}' requires first_value.`,
22977
+ extras: conditionShapeHints(cond)
22978
+ };
22867
22979
  }
22868
22980
  if (hasSecond) {
22869
- return `${prefix}.condition '${cond.condition}' takes only first_value; remove second_value.`;
22981
+ return {
22982
+ error: `${prefix}.condition '${cond.condition}' takes only first_value; omit second_value entirely (do not send null or empty value).`,
22983
+ extras: conditionShapeHints(cond)
22984
+ };
22870
22985
  }
22871
22986
  }
22872
22987
  for (const [v, lbl] of [
@@ -22874,10 +22989,10 @@ function validateCondition(provider, cond, prefix) {
22874
22989
  [cond.second_value, `${prefix}.second_value`]
22875
22990
  ]) {
22876
22991
  const shapeErr = validateValueShape(v, lbl);
22877
- if (shapeErr) return shapeErr;
22992
+ if (shapeErr) return { error: shapeErr };
22878
22993
  if (v) {
22879
22994
  const dynErr = validateDynamicValue(provider, v, lbl);
22880
- if (dynErr) return dynErr;
22995
+ if (dynErr) return { error: dynErr };
22881
22996
  }
22882
22997
  }
22883
22998
  return null;
@@ -22936,16 +23051,9 @@ function handle29(provider, args) {
22936
23051
  if (typeof rc !== "object" || rc === null) {
22937
23052
  return errorResponse(`attribute_filter_groups[${gi}].conditions[${ci}] must be an object.`);
22938
23053
  }
22939
- const cond = {
22940
- attribute_context: rc["attribute_context"],
22941
- attribute_name: rc["attribute_name"],
22942
- condition: rc["condition"],
22943
- first_value: rc["first_value"],
22944
- second_value: rc["second_value"],
22945
- operator: rc["operator"]
22946
- };
23054
+ const cond = normalizeCondition(rc);
22947
23055
  const err = validateCondition(provider, cond, `attribute_filter_groups[${gi}].conditions[${ci}]`);
22948
- if (err) return errorResponse(err);
23056
+ if (err) return errorResponse(err.error, err.extras);
22949
23057
  conditions.push(cond);
22950
23058
  }
22951
23059
  groups.push({ operator: raw["operator"], conditions });
@@ -23058,6 +23166,8 @@ var TOOL_DEF30 = {
23058
23166
  }
23059
23167
  };
23060
23168
  function ruleExists(provider, ruleName) {
23169
+ if (provider.rules[ruleName]) return true;
23170
+ provider.invalidateRules();
23061
23171
  return !!provider.rules[ruleName];
23062
23172
  }
23063
23173
  function findFormField(formData, attrName, containerId) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prorigo/protrak-forge",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
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"