@prorigo/protrak-forge 0.4.0 → 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.
- package/bin/protrak-forge.js +187 -48
- package/data/CLAUDE.md.template +3 -0
- package/package.json +1 -1
package/bin/protrak-forge.js
CHANGED
|
@@ -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.
|
|
17959
|
+
var PACKAGE_VERSION = "0.4.2";
|
|
17952
17960
|
|
|
17953
17961
|
// src/tools/_schema-write-utils.ts
|
|
17954
17962
|
var fs4 = __toESM(require("fs"));
|
|
@@ -22486,9 +22494,6 @@ function serializeStyle(style) {
|
|
|
22486
22494
|
if (!FONT_STYLE_OPTIONS.includes(v)) {
|
|
22487
22495
|
return { error: `font_style entry '${v}' must be one of: ${FONT_STYLE_OPTIONS.join(", ")}.` };
|
|
22488
22496
|
}
|
|
22489
|
-
if (seen.has(v)) {
|
|
22490
|
-
return { error: `font_style contains duplicate entry '${v}'.` };
|
|
22491
|
-
}
|
|
22492
22497
|
seen.add(v);
|
|
22493
22498
|
}
|
|
22494
22499
|
if (seen.has("Bold")) tokens.push("fontWeight:bold");
|
|
@@ -22599,6 +22604,50 @@ var EXAMPLES = [
|
|
|
22599
22604
|
}
|
|
22600
22605
|
]
|
|
22601
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
|
+
}
|
|
22602
22651
|
}
|
|
22603
22652
|
];
|
|
22604
22653
|
var DYNAMIC_VALUE_RULES = {
|
|
@@ -22712,7 +22761,7 @@ var CONDITION_INPUT_SCHEMA = {
|
|
|
22712
22761
|
},
|
|
22713
22762
|
attribute_name: {
|
|
22714
22763
|
type: "string",
|
|
22715
|
-
description: "PascalCase attribute name. Required for Instance and User contexts.
|
|
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."
|
|
22716
22765
|
},
|
|
22717
22766
|
condition: {
|
|
22718
22767
|
type: "string",
|
|
@@ -22720,11 +22769,11 @@ var CONDITION_INPUT_SCHEMA = {
|
|
|
22720
22769
|
},
|
|
22721
22770
|
first_value: {
|
|
22722
22771
|
...VALUE_INPUT_SCHEMA,
|
|
22723
|
-
description: "First value
|
|
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."
|
|
22724
22773
|
},
|
|
22725
22774
|
second_value: {
|
|
22726
22775
|
...VALUE_INPUT_SCHEMA,
|
|
22727
|
-
description: "Second value.
|
|
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."
|
|
22728
22777
|
},
|
|
22729
22778
|
operator: {
|
|
22730
22779
|
type: "string",
|
|
@@ -22753,7 +22802,7 @@ var GROUP_INPUT_SCHEMA = {
|
|
|
22753
22802
|
};
|
|
22754
22803
|
var TOOL_DEF29 = {
|
|
22755
22804
|
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(\
|
|
22805
|
+
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
22806
|
inputSchema: {
|
|
22758
22807
|
type: "object",
|
|
22759
22808
|
properties: {
|
|
@@ -22780,6 +22829,49 @@ var TOOL_DEF29 = {
|
|
|
22780
22829
|
required: ["name", "attribute_filter_groups"]
|
|
22781
22830
|
}
|
|
22782
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
|
+
}
|
|
22783
22875
|
function validateValueShape(value, label) {
|
|
22784
22876
|
if (value === void 0) return null;
|
|
22785
22877
|
if (typeof value !== "object" || value === null) {
|
|
@@ -22808,35 +22900,42 @@ function validateDynamicValue(provider, value, label) {
|
|
|
22808
22900
|
}
|
|
22809
22901
|
function validateCondition(provider, cond, prefix) {
|
|
22810
22902
|
if (!ATTRIBUTE_CONTEXTS.includes(cond.attribute_context)) {
|
|
22811
|
-
return `${prefix}.attribute_context must be one of: ${ATTRIBUTE_CONTEXTS.join(", ")}
|
|
22903
|
+
return { error: `${prefix}.attribute_context must be one of: ${ATTRIBUTE_CONTEXTS.join(", ")}.` };
|
|
22812
22904
|
}
|
|
22813
22905
|
if (!GROUP_OPERATORS.includes(cond.operator)) {
|
|
22814
|
-
return `${prefix}.operator must be one of: ${GROUP_OPERATORS.join(", ")}
|
|
22906
|
+
return { error: `${prefix}.operator must be one of: ${GROUP_OPERATORS.join(", ")}.` };
|
|
22815
22907
|
}
|
|
22816
22908
|
if (typeof cond.condition !== "string" || !cond.condition) {
|
|
22817
|
-
return `${prefix}.condition is required
|
|
22909
|
+
return { error: `${prefix}.condition is required.` };
|
|
22818
22910
|
}
|
|
22819
|
-
let validConditions;
|
|
22820
22911
|
if (cond.attribute_context === "UserRoles") {
|
|
22821
22912
|
if (cond.attribute_name) {
|
|
22822
|
-
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'.` };
|
|
22914
|
+
}
|
|
22915
|
+
if (!USER_ROLES_CONDITIONS.includes(cond.condition)) {
|
|
22916
|
+
return {
|
|
22917
|
+
error: `${prefix}.condition '${cond.condition}' is not valid for UserRoles context. Valid: ${USER_ROLES_CONDITIONS.join(", ")}.`
|
|
22918
|
+
};
|
|
22823
22919
|
}
|
|
22824
|
-
validConditions = USER_ROLES_CONDITIONS;
|
|
22825
22920
|
} else {
|
|
22826
22921
|
if (!cond.attribute_name) {
|
|
22827
|
-
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}'.` };
|
|
22828
22923
|
}
|
|
22829
22924
|
if (!isPascalCase(cond.attribute_name)) {
|
|
22830
|
-
return `${prefix}.${pascalCaseError("attribute_name", cond.attribute_name)}
|
|
22925
|
+
return { error: `${prefix}.${pascalCaseError("attribute_name", cond.attribute_name)}` };
|
|
22831
22926
|
}
|
|
22832
22927
|
if (cond.attribute_context === "Instance") {
|
|
22833
22928
|
const attrType = resolveInstanceAttributeType(provider, cond.attribute_name);
|
|
22834
22929
|
if (!attrType) {
|
|
22835
|
-
return
|
|
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
|
+
};
|
|
22836
22933
|
}
|
|
22837
|
-
validConditions = CONDITIONS_BY_ATTR_TYPE[attrType] ?? CONDITIONS_BY_ATTR_TYPE["Text"];
|
|
22934
|
+
const validConditions = CONDITIONS_BY_ATTR_TYPE[attrType] ?? CONDITIONS_BY_ATTR_TYPE["Text"];
|
|
22838
22935
|
if (!validConditions.includes(cond.condition)) {
|
|
22839
|
-
return
|
|
22936
|
+
return {
|
|
22937
|
+
error: `${prefix}.condition '${cond.condition}' is not valid for Instance attribute '${cond.attribute_name}' (type=${attrType}). Valid conditions: ${validConditions.join(", ")}.`
|
|
22938
|
+
};
|
|
22840
22939
|
}
|
|
22841
22940
|
} else {
|
|
22842
22941
|
const acceptable = /* @__PURE__ */ new Set([
|
|
@@ -22847,32 +22946,42 @@ function validateCondition(provider, cond, prefix) {
|
|
|
22847
22946
|
...CONDITIONS_BY_ATTR_TYPE["Boolean"]
|
|
22848
22947
|
]);
|
|
22849
22948
|
if (!acceptable.has(cond.condition)) {
|
|
22850
|
-
return
|
|
22949
|
+
return {
|
|
22950
|
+
error: `${prefix}.condition '${cond.condition}' is not recognised for User-context conditions.`
|
|
22951
|
+
};
|
|
22851
22952
|
}
|
|
22852
|
-
validConditions = Array.from(acceptable);
|
|
22853
22953
|
}
|
|
22854
22954
|
}
|
|
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
22955
|
const valueless = VALUE_LESS_CONDITIONS.has(cond.condition);
|
|
22859
22956
|
const ranged = RANGE_CONDITIONS.has(cond.condition);
|
|
22860
22957
|
const hasFirst = cond.first_value !== void 0;
|
|
22861
22958
|
const hasSecond = cond.second_value !== void 0;
|
|
22862
22959
|
if (valueless) {
|
|
22863
22960
|
if (hasFirst || hasSecond) {
|
|
22864
|
-
return
|
|
22961
|
+
return {
|
|
22962
|
+
error: `${prefix}.condition '${cond.condition}' takes no value(s); omit first_value and second_value entirely.`,
|
|
22963
|
+
extras: conditionShapeHints(cond)
|
|
22964
|
+
};
|
|
22865
22965
|
}
|
|
22866
22966
|
} else if (ranged) {
|
|
22867
22967
|
if (!hasFirst || !hasSecond) {
|
|
22868
|
-
return
|
|
22968
|
+
return {
|
|
22969
|
+
error: `${prefix}.condition '${cond.condition}' requires BOTH first_value and second_value.`,
|
|
22970
|
+
extras: conditionShapeHints(cond)
|
|
22971
|
+
};
|
|
22869
22972
|
}
|
|
22870
22973
|
} else {
|
|
22871
22974
|
if (!hasFirst) {
|
|
22872
|
-
return
|
|
22975
|
+
return {
|
|
22976
|
+
error: `${prefix}.condition '${cond.condition}' requires first_value.`,
|
|
22977
|
+
extras: conditionShapeHints(cond)
|
|
22978
|
+
};
|
|
22873
22979
|
}
|
|
22874
22980
|
if (hasSecond) {
|
|
22875
|
-
return
|
|
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
|
+
};
|
|
22876
22985
|
}
|
|
22877
22986
|
}
|
|
22878
22987
|
for (const [v, lbl] of [
|
|
@@ -22880,10 +22989,10 @@ function validateCondition(provider, cond, prefix) {
|
|
|
22880
22989
|
[cond.second_value, `${prefix}.second_value`]
|
|
22881
22990
|
]) {
|
|
22882
22991
|
const shapeErr = validateValueShape(v, lbl);
|
|
22883
|
-
if (shapeErr) return shapeErr;
|
|
22992
|
+
if (shapeErr) return { error: shapeErr };
|
|
22884
22993
|
if (v) {
|
|
22885
22994
|
const dynErr = validateDynamicValue(provider, v, lbl);
|
|
22886
|
-
if (dynErr) return dynErr;
|
|
22995
|
+
if (dynErr) return { error: dynErr };
|
|
22887
22996
|
}
|
|
22888
22997
|
}
|
|
22889
22998
|
return null;
|
|
@@ -22942,16 +23051,9 @@ function handle29(provider, args) {
|
|
|
22942
23051
|
if (typeof rc !== "object" || rc === null) {
|
|
22943
23052
|
return errorResponse(`attribute_filter_groups[${gi}].conditions[${ci}] must be an object.`);
|
|
22944
23053
|
}
|
|
22945
|
-
const cond =
|
|
22946
|
-
attribute_context: rc["attribute_context"],
|
|
22947
|
-
attribute_name: rc["attribute_name"],
|
|
22948
|
-
condition: rc["condition"],
|
|
22949
|
-
first_value: rc["first_value"],
|
|
22950
|
-
second_value: rc["second_value"],
|
|
22951
|
-
operator: rc["operator"]
|
|
22952
|
-
};
|
|
23054
|
+
const cond = normalizeCondition(rc);
|
|
22953
23055
|
const err = validateCondition(provider, cond, `attribute_filter_groups[${gi}].conditions[${ci}]`);
|
|
22954
|
-
if (err) return errorResponse(err);
|
|
23056
|
+
if (err) return errorResponse(err.error, err.extras);
|
|
22955
23057
|
conditions.push(cond);
|
|
22956
23058
|
}
|
|
22957
23059
|
groups.push({ operator: raw["operator"], conditions });
|
|
@@ -22995,7 +23097,7 @@ var DISPLAY_TARGET_KINDS = ["layout_template", "form"];
|
|
|
22995
23097
|
var FORMAT_TARGET_KINDS = ["type_widget", "form"];
|
|
22996
23098
|
var TOOL_DEF30 = {
|
|
22997
23099
|
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.",
|
|
23100
|
+
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
23101
|
inputSchema: {
|
|
23000
23102
|
type: "object",
|
|
23001
23103
|
properties: {
|
|
@@ -23028,7 +23130,7 @@ var TOOL_DEF30 = {
|
|
|
23028
23130
|
},
|
|
23029
23131
|
container_id: {
|
|
23030
23132
|
type: "string",
|
|
23031
|
-
description: "Form target only: formContainerKey
|
|
23133
|
+
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
23134
|
},
|
|
23033
23135
|
section_name: {
|
|
23034
23136
|
type: "string",
|
|
@@ -23064,6 +23166,8 @@ var TOOL_DEF30 = {
|
|
|
23064
23166
|
}
|
|
23065
23167
|
};
|
|
23066
23168
|
function ruleExists(provider, ruleName) {
|
|
23169
|
+
if (provider.rules[ruleName]) return true;
|
|
23170
|
+
provider.invalidateRules();
|
|
23067
23171
|
return !!provider.rules[ruleName];
|
|
23068
23172
|
}
|
|
23069
23173
|
function findFormField(formData, attrName, containerId) {
|
|
@@ -23084,7 +23188,6 @@ function findFormField(formData, attrName, containerId) {
|
|
|
23084
23188
|
return {
|
|
23085
23189
|
result: {
|
|
23086
23190
|
field: fields[idx],
|
|
23087
|
-
index: idx,
|
|
23088
23191
|
parent: fields,
|
|
23089
23192
|
containerKey: container["formContainerKey"] ?? ""
|
|
23090
23193
|
}
|
|
@@ -23158,16 +23261,50 @@ function attachDisplayCondition(provider, args) {
|
|
|
23158
23261
|
);
|
|
23159
23262
|
}
|
|
23160
23263
|
const attrName = args["attribute_name"];
|
|
23161
|
-
|
|
23162
|
-
|
|
23264
|
+
const containerId = args["container_id"] ?? void 0;
|
|
23265
|
+
if (!attrName && !containerId) {
|
|
23266
|
+
return errorResponse(
|
|
23267
|
+
"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."
|
|
23268
|
+
);
|
|
23163
23269
|
}
|
|
23164
|
-
if (!isPascalCase(attrName)) return errorResponse(pascalCaseError("attribute_name", attrName));
|
|
23165
23270
|
const filePath = path17.join(provider.ws.formsDir, `${targetName}.json`);
|
|
23166
23271
|
if (!fs21.existsSync(filePath)) {
|
|
23167
23272
|
return errorResponse(`Form '${targetName}' not found at ${filePath}.`);
|
|
23168
23273
|
}
|
|
23169
23274
|
const data = readJsonFile(filePath);
|
|
23170
|
-
|
|
23275
|
+
if (!attrName) {
|
|
23276
|
+
const config2 = data["formConfiguration"] ?? {};
|
|
23277
|
+
const containers = Array.isArray(config2["containers"]) ? config2["containers"] : [];
|
|
23278
|
+
const container = containers.find((c) => c["formContainerKey"] === containerId);
|
|
23279
|
+
if (!container) {
|
|
23280
|
+
return errorResponse(`Container '${containerId}' not found in form '${targetName}'.`, {
|
|
23281
|
+
available_containers: containers.map((c) => c["formContainerKey"] ?? "")
|
|
23282
|
+
});
|
|
23283
|
+
}
|
|
23284
|
+
const existing2 = Array.isArray(container["displayConditions"]) ? container["displayConditions"] : [];
|
|
23285
|
+
if (existing2.includes(ruleName)) {
|
|
23286
|
+
if (!replace) {
|
|
23287
|
+
return errorResponse(
|
|
23288
|
+
`Rule '${ruleName}' is already in displayConditions for container '${containerId}'. Pass replace:true to no-op silently.`
|
|
23289
|
+
);
|
|
23290
|
+
}
|
|
23291
|
+
} else {
|
|
23292
|
+
existing2.push(ruleName);
|
|
23293
|
+
}
|
|
23294
|
+
container["displayConditions"] = existing2;
|
|
23295
|
+
writeJsonFilePath(filePath, data);
|
|
23296
|
+
return successResponse(
|
|
23297
|
+
{
|
|
23298
|
+
usage: "display_condition",
|
|
23299
|
+
target_kind: "form",
|
|
23300
|
+
target_path: filePath,
|
|
23301
|
+
container_id: containerId,
|
|
23302
|
+
display_conditions: existing2
|
|
23303
|
+
},
|
|
23304
|
+
{ nextSteps: [`Verify by reading Forms/${targetName}.json.`] }
|
|
23305
|
+
);
|
|
23306
|
+
}
|
|
23307
|
+
if (!isPascalCase(attrName)) return errorResponse(pascalCaseError("attribute_name", attrName));
|
|
23171
23308
|
const lookup2 = findFormField(data, attrName, containerId);
|
|
23172
23309
|
if (lookup2.error) return errorResponse(lookup2.error, lookup2.available_containers ? { available_containers: lookup2.available_containers } : void 0);
|
|
23173
23310
|
const field = lookup2.result.field;
|
|
@@ -23279,7 +23416,8 @@ function attachFormatCondition(provider, args) {
|
|
|
23279
23416
|
usage: "format_condition",
|
|
23280
23417
|
target_path: filePath,
|
|
23281
23418
|
attribute_name: attrName,
|
|
23282
|
-
|
|
23419
|
+
entry,
|
|
23420
|
+
total_format_conditions: fc.length
|
|
23283
23421
|
},
|
|
23284
23422
|
{ nextSteps: [`Verify by reading ViewLayouts/${targetName}.json.`] }
|
|
23285
23423
|
);
|
|
@@ -23371,7 +23509,8 @@ function attachConditionalFormatting(provider, args) {
|
|
|
23371
23509
|
target_kind: targetKind,
|
|
23372
23510
|
target_path: filePath,
|
|
23373
23511
|
attribute_name: attrName,
|
|
23374
|
-
|
|
23512
|
+
entry,
|
|
23513
|
+
total_conditional_formatting: cf.length
|
|
23375
23514
|
},
|
|
23376
23515
|
{ nextSteps: [`Verify by reading ${path17.basename(filePath)}.`] }
|
|
23377
23516
|
);
|
package/data/CLAUDE.md.template
CHANGED
|
@@ -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