@nhtio/adk 1.20260607.0 → 1.20260607.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 (44) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/common-BUIjZ6LV.mjs +119 -0
  3. package/common-BUIjZ6LV.mjs.map +1 -0
  4. package/common-DeNipTQI.js +148 -0
  5. package/common-DeNipTQI.js.map +1 -0
  6. package/eslint/index.d.ts +106 -0
  7. package/eslint/rules/artifact_tool_forbids_artifact_constructor.cjs +57 -0
  8. package/eslint/rules/artifact_tool_forbids_artifact_constructor.cjs.map +1 -0
  9. package/eslint/rules/artifact_tool_forbids_artifact_constructor.d.ts +21 -0
  10. package/eslint/rules/artifact_tool_forbids_artifact_constructor.mjs +52 -0
  11. package/eslint/rules/artifact_tool_forbids_artifact_constructor.mjs.map +1 -0
  12. package/eslint/rules/common.d.ts +41 -0
  13. package/eslint/rules/index.d.ts +12 -0
  14. package/eslint/rules/no_model_in_tool_handler.cjs +64 -0
  15. package/eslint/rules/no_model_in_tool_handler.cjs.map +1 -0
  16. package/eslint/rules/no_model_in_tool_handler.d.ts +26 -0
  17. package/eslint/rules/no_model_in_tool_handler.mjs +59 -0
  18. package/eslint/rules/no_model_in_tool_handler.mjs.map +1 -0
  19. package/eslint/rules/require_validator_any_required.cjs +100 -0
  20. package/eslint/rules/require_validator_any_required.cjs.map +1 -0
  21. package/eslint/rules/require_validator_any_required.d.ts +31 -0
  22. package/eslint/rules/require_validator_any_required.mjs +95 -0
  23. package/eslint/rules/require_validator_any_required.mjs.map +1 -0
  24. package/eslint/rules/thought_payload_requires_replay_tag.cjs +71 -0
  25. package/eslint/rules/thought_payload_requires_replay_tag.cjs.map +1 -0
  26. package/eslint/rules/thought_payload_requires_replay_tag.d.ts +26 -0
  27. package/eslint/rules/thought_payload_requires_replay_tag.mjs +66 -0
  28. package/eslint/rules/thought_payload_requires_replay_tag.mjs.map +1 -0
  29. package/eslint/rules/token_encoding_requires_context_window.cjs +70 -0
  30. package/eslint/rules/token_encoding_requires_context_window.cjs.map +1 -0
  31. package/eslint/rules/token_encoding_requires_context_window.d.ts +24 -0
  32. package/eslint/rules/token_encoding_requires_context_window.mjs +65 -0
  33. package/eslint/rules/token_encoding_requires_context_window.mjs.map +1 -0
  34. package/eslint/rules.cjs +12 -0
  35. package/eslint/rules.mjs +6 -0
  36. package/eslint.cjs +82 -0
  37. package/eslint.cjs.map +1 -0
  38. package/eslint.mjs +74 -0
  39. package/eslint.mjs.map +1 -0
  40. package/index.cjs +1 -1
  41. package/index.mjs +1 -1
  42. package/mcp/adk-docs-corpus.json +1 -1
  43. package/package.json +230 -187
  44. package/skills/adk-assembly/SKILL.md +2 -2
@@ -0,0 +1,12 @@
1
+ /**
2
+ * @module @nhtio/adk/eslint/rules
3
+ *
4
+ * Barrel of the individual `@nhtio/adk` ESLint rule objects. Most consumers want the assembled
5
+ * plugin from `@nhtio/adk/eslint` instead; import individual rules from here only when composing a
6
+ * bespoke plugin or testing a single rule.
7
+ */
8
+ export { default as requireValidatorAnyRequired } from "./require_validator_any_required";
9
+ export { default as noModelInToolHandler } from "./no_model_in_tool_handler";
10
+ export { default as thoughtPayloadRequiresReplayTag } from "./thought_payload_requires_replay_tag";
11
+ export { default as tokenEncodingRequiresContextWindow } from "./token_encoding_requires_context_window";
12
+ export { default as artifactToolForbidsArtifactConstructor } from "./artifact_tool_forbids_artifact_constructor";
@@ -0,0 +1,64 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ require("../../chunk-Ble4zEEl.js");
6
+ const require_common = require("../../common-DeNipTQI.js");
7
+ //#region src/eslint/rules/no_model_in_tool_handler.ts
8
+ /**
9
+ * @module @nhtio/adk/eslint/rules/no_model_in_tool_handler
10
+ *
11
+ * Flags an LLM/model call inside a `Tool` / `ArtifactTool` `handler`.
12
+ *
13
+ * Why: the executor owns the dispatch loop. A tool handler runs as one step the model proposed; it
14
+ * must do work and return a result, not start its own reasoning loop. Calling a model from inside a
15
+ * handler hides a nested, unmanaged dispatch from the harness — no token accounting, no event
16
+ * surfacing, no abort propagation. The one legitimate exception is a tool that is explicitly a
17
+ * sub-agent running its own scoped dispatch; this rule allows a handler that constructs a
18
+ * `new TurnRunner(...)` or calls the lower-level `DispatchRunner.dispatch(...)`.
19
+ *
20
+ * Detects provider SDK constructors (`new OpenAI()`, `new Anthropic()`) and chat/completion calls
21
+ * (a `.create()` whose receiver chain passes through `messages`/`completions`/`responses`, or a
22
+ * `generateContent()` call) within the `handler` function passed to `new Tool({ handler })` /
23
+ * `new ArtifactTool({ handler })`, ignoring inner closures (so a sub-agent's own callbacks don't
24
+ * false-positive).
25
+ *
26
+ * Opt-out:
27
+ * // eslint-disable-next-line adk/no-model-in-tool-handler -- <reason>
28
+ */
29
+ var literalKey = (prop) => {
30
+ if (prop.computed) return void 0;
31
+ if (prop.key.type === "Identifier") return prop.key.name;
32
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
33
+ };
34
+ var noModelInToolHandlerRule = require_common.createRule({
35
+ name: "no-model-in-tool-handler",
36
+ meta: {
37
+ type: "problem",
38
+ docs: { description: "A Tool/ArtifactTool handler must not invoke a model — the executor owns the dispatch loop. The only exception is a sub-agent tool that runs its own scoped dispatch via `new TurnRunner(...)` or `DispatchRunner.dispatch(...)`." },
39
+ schema: [],
40
+ messages: { noModelInHandler: "Do not call a model inside a tool handler — the executor owns the dispatch loop, and a model call here hides an unmanaged nested dispatch (no token accounting, events, or abort). If this tool is a sub-agent, run a scoped `new TurnRunner(...)` or `DispatchRunner.dispatch(...)` instead. Opt out with an eslint-disable-next-line adk/no-model-in-tool-handler comment + reason." }
41
+ },
42
+ defaultOptions: [],
43
+ create(context) {
44
+ return { NewExpression(node) {
45
+ if (node.callee.type !== "Identifier" || node.callee.name !== "Tool" && node.callee.name !== "ArtifactTool") return;
46
+ const arg = node.arguments[0];
47
+ if (!arg || arg.type !== "ObjectExpression") return;
48
+ const handlerProp = arg.properties.find((p) => p.type === "Property" && literalKey(p) === "handler");
49
+ if (!handlerProp || !require_common.isFunctionNode(handlerProp.value)) return;
50
+ const handler = handlerProp.value;
51
+ if (require_common.runsSubAgent(handler.body)) return;
52
+ require_common.walkBodySkippingNestedFunctions(handler.body, (n) => {
53
+ if (require_common.isLlmCall(n)) context.report({
54
+ node: n,
55
+ messageId: "noModelInHandler"
56
+ });
57
+ });
58
+ } };
59
+ }
60
+ });
61
+ //#endregion
62
+ exports.default = noModelInToolHandlerRule;
63
+
64
+ //# sourceMappingURL=no_model_in_tool_handler.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no_model_in_tool_handler.cjs","names":[],"sources":["../../../src/eslint/rules/no_model_in_tool_handler.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/no_model_in_tool_handler\n *\n * Flags an LLM/model call inside a `Tool` / `ArtifactTool` `handler`.\n *\n * Why: the executor owns the dispatch loop. A tool handler runs as one step the model proposed; it\n * must do work and return a result, not start its own reasoning loop. Calling a model from inside a\n * handler hides a nested, unmanaged dispatch from the harness — no token accounting, no event\n * surfacing, no abort propagation. The one legitimate exception is a tool that is explicitly a\n * sub-agent running its own scoped dispatch; this rule allows a handler that constructs a\n * `new TurnRunner(...)` or calls the lower-level `DispatchRunner.dispatch(...)`.\n *\n * Detects provider SDK constructors (`new OpenAI()`, `new Anthropic()`) and chat/completion calls\n * (a `.create()` whose receiver chain passes through `messages`/`completions`/`responses`, or a\n * `generateContent()` call) within the `handler` function passed to `new Tool({ handler })` /\n * `new ArtifactTool({ handler })`, ignoring inner closures (so a sub-agent's own callbacks don't\n * false-positive).\n *\n * Opt-out:\n * // eslint-disable-next-line adk/no-model-in-tool-handler -- <reason>\n */\n\nimport {\n createRule,\n isFunctionNode,\n isLlmCall,\n runsSubAgent,\n walkBodySkippingNestedFunctions,\n} from './common'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\nconst literalKey = (prop: TSESTree.Property): string | undefined => {\n if (prop.computed) return undefined\n if (prop.key.type === 'Identifier') return prop.key.name\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') return prop.key.value\n return undefined\n}\n\nconst noModelInToolHandlerRule = createRule({\n name: 'no-model-in-tool-handler',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'A Tool/ArtifactTool handler must not invoke a model — the executor owns the dispatch loop. The only exception is a sub-agent tool that runs its own scoped dispatch via `new TurnRunner(...)` or `DispatchRunner.dispatch(...)`.',\n },\n schema: [],\n messages: {\n noModelInHandler:\n 'Do not call a model inside a tool handler — the executor owns the dispatch loop, and a model call here hides an unmanaged nested dispatch (no token accounting, events, or abort). If this tool is a sub-agent, run a scoped `new TurnRunner(...)` or `DispatchRunner.dispatch(...)` instead. Opt out with an eslint-disable-next-line adk/no-model-in-tool-handler comment + reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n NewExpression(node: TSESTree.NewExpression) {\n if (\n node.callee.type !== 'Identifier' ||\n (node.callee.name !== 'Tool' && node.callee.name !== 'ArtifactTool')\n ) {\n return\n }\n const arg = node.arguments[0]\n if (!arg || arg.type !== 'ObjectExpression') return\n\n const handlerProp = arg.properties.find(\n (p): p is TSESTree.Property => p.type === 'Property' && literalKey(p) === 'handler'\n )\n if (!handlerProp || !isFunctionNode(handlerProp.value)) return\n const handler = handlerProp.value\n\n // A sub-agent tool legitimately runs a scoped dispatch (new TurnRunner / DispatchRunner.dispatch)\n // — that's the documented exception.\n if (runsSubAgent(handler.body)) return\n\n walkBodySkippingNestedFunctions(handler.body, (n) => {\n if (isLlmCall(n)) {\n context.report({ node: n, messageId: 'noModelInHandler' })\n }\n })\n },\n }\n },\n})\n\nexport default noModelInToolHandlerRule\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAM,cAAc,SAAgD;CAClE,IAAI,KAAK,UAAU,OAAO,KAAA;CAC1B,IAAI,KAAK,IAAI,SAAS,cAAc,OAAO,KAAK,IAAI;CACpD,IAAI,KAAK,IAAI,SAAS,aAAa,OAAO,KAAK,IAAI,UAAU,UAAU,OAAO,KAAK,IAAI;AAEzF;AAEA,IAAM,2BAA2B,eAAA,WAAW;CAC1C,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,mOACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,kBACE,wXACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,cAAc,MAA8B;GAC1C,IACE,KAAK,OAAO,SAAS,gBACpB,KAAK,OAAO,SAAS,UAAU,KAAK,OAAO,SAAS,gBAErD;GAEF,MAAM,MAAM,KAAK,UAAU;GAC3B,IAAI,CAAC,OAAO,IAAI,SAAS,oBAAoB;GAE7C,MAAM,cAAc,IAAI,WAAW,MAChC,MAA8B,EAAE,SAAS,cAAc,WAAW,CAAC,MAAM,SAC5E;GACA,IAAI,CAAC,eAAe,CAAC,eAAA,eAAe,YAAY,KAAK,GAAG;GACxD,MAAM,UAAU,YAAY;GAI5B,IAAI,eAAA,aAAa,QAAQ,IAAI,GAAG;GAEhC,eAAA,gCAAgC,QAAQ,OAAO,MAAM;IACnD,IAAI,eAAA,UAAU,CAAC,GACb,QAAQ,OAAO;KAAE,MAAM;KAAG,WAAW;IAAmB,CAAC;GAE7D,CAAC;EACH,EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @module @nhtio/adk/eslint/rules/no_model_in_tool_handler
3
+ *
4
+ * Flags an LLM/model call inside a `Tool` / `ArtifactTool` `handler`.
5
+ *
6
+ * Why: the executor owns the dispatch loop. A tool handler runs as one step the model proposed; it
7
+ * must do work and return a result, not start its own reasoning loop. Calling a model from inside a
8
+ * handler hides a nested, unmanaged dispatch from the harness — no token accounting, no event
9
+ * surfacing, no abort propagation. The one legitimate exception is a tool that is explicitly a
10
+ * sub-agent running its own scoped dispatch; this rule allows a handler that constructs a
11
+ * `new TurnRunner(...)` or calls the lower-level `DispatchRunner.dispatch(...)`.
12
+ *
13
+ * Detects provider SDK constructors (`new OpenAI()`, `new Anthropic()`) and chat/completion calls
14
+ * (a `.create()` whose receiver chain passes through `messages`/`completions`/`responses`, or a
15
+ * `generateContent()` call) within the `handler` function passed to `new Tool({ handler })` /
16
+ * `new ArtifactTool({ handler })`, ignoring inner closures (so a sub-agent's own callbacks don't
17
+ * false-positive).
18
+ *
19
+ * Opt-out:
20
+ * // eslint-disable-next-line adk/no-model-in-tool-handler -- <reason>
21
+ */
22
+ declare const noModelInToolHandlerRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"noModelInHandler", [
23
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
24
+ name: string;
25
+ };
26
+ export default noModelInToolHandlerRule;
@@ -0,0 +1,59 @@
1
+ import { a as walkBodySkippingNestedFunctions, i as runsSubAgent, n as isFunctionNode, r as isLlmCall, t as createRule } from "../../common-BUIjZ6LV.mjs";
2
+ //#region src/eslint/rules/no_model_in_tool_handler.ts
3
+ /**
4
+ * @module @nhtio/adk/eslint/rules/no_model_in_tool_handler
5
+ *
6
+ * Flags an LLM/model call inside a `Tool` / `ArtifactTool` `handler`.
7
+ *
8
+ * Why: the executor owns the dispatch loop. A tool handler runs as one step the model proposed; it
9
+ * must do work and return a result, not start its own reasoning loop. Calling a model from inside a
10
+ * handler hides a nested, unmanaged dispatch from the harness — no token accounting, no event
11
+ * surfacing, no abort propagation. The one legitimate exception is a tool that is explicitly a
12
+ * sub-agent running its own scoped dispatch; this rule allows a handler that constructs a
13
+ * `new TurnRunner(...)` or calls the lower-level `DispatchRunner.dispatch(...)`.
14
+ *
15
+ * Detects provider SDK constructors (`new OpenAI()`, `new Anthropic()`) and chat/completion calls
16
+ * (a `.create()` whose receiver chain passes through `messages`/`completions`/`responses`, or a
17
+ * `generateContent()` call) within the `handler` function passed to `new Tool({ handler })` /
18
+ * `new ArtifactTool({ handler })`, ignoring inner closures (so a sub-agent's own callbacks don't
19
+ * false-positive).
20
+ *
21
+ * Opt-out:
22
+ * // eslint-disable-next-line adk/no-model-in-tool-handler -- <reason>
23
+ */
24
+ var literalKey = (prop) => {
25
+ if (prop.computed) return void 0;
26
+ if (prop.key.type === "Identifier") return prop.key.name;
27
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
28
+ };
29
+ var noModelInToolHandlerRule = createRule({
30
+ name: "no-model-in-tool-handler",
31
+ meta: {
32
+ type: "problem",
33
+ docs: { description: "A Tool/ArtifactTool handler must not invoke a model — the executor owns the dispatch loop. The only exception is a sub-agent tool that runs its own scoped dispatch via `new TurnRunner(...)` or `DispatchRunner.dispatch(...)`." },
34
+ schema: [],
35
+ messages: { noModelInHandler: "Do not call a model inside a tool handler — the executor owns the dispatch loop, and a model call here hides an unmanaged nested dispatch (no token accounting, events, or abort). If this tool is a sub-agent, run a scoped `new TurnRunner(...)` or `DispatchRunner.dispatch(...)` instead. Opt out with an eslint-disable-next-line adk/no-model-in-tool-handler comment + reason." }
36
+ },
37
+ defaultOptions: [],
38
+ create(context) {
39
+ return { NewExpression(node) {
40
+ if (node.callee.type !== "Identifier" || node.callee.name !== "Tool" && node.callee.name !== "ArtifactTool") return;
41
+ const arg = node.arguments[0];
42
+ if (!arg || arg.type !== "ObjectExpression") return;
43
+ const handlerProp = arg.properties.find((p) => p.type === "Property" && literalKey(p) === "handler");
44
+ if (!handlerProp || !isFunctionNode(handlerProp.value)) return;
45
+ const handler = handlerProp.value;
46
+ if (runsSubAgent(handler.body)) return;
47
+ walkBodySkippingNestedFunctions(handler.body, (n) => {
48
+ if (isLlmCall(n)) context.report({
49
+ node: n,
50
+ messageId: "noModelInHandler"
51
+ });
52
+ });
53
+ } };
54
+ }
55
+ });
56
+ //#endregion
57
+ export { noModelInToolHandlerRule as default };
58
+
59
+ //# sourceMappingURL=no_model_in_tool_handler.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no_model_in_tool_handler.mjs","names":[],"sources":["../../../src/eslint/rules/no_model_in_tool_handler.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/no_model_in_tool_handler\n *\n * Flags an LLM/model call inside a `Tool` / `ArtifactTool` `handler`.\n *\n * Why: the executor owns the dispatch loop. A tool handler runs as one step the model proposed; it\n * must do work and return a result, not start its own reasoning loop. Calling a model from inside a\n * handler hides a nested, unmanaged dispatch from the harness — no token accounting, no event\n * surfacing, no abort propagation. The one legitimate exception is a tool that is explicitly a\n * sub-agent running its own scoped dispatch; this rule allows a handler that constructs a\n * `new TurnRunner(...)` or calls the lower-level `DispatchRunner.dispatch(...)`.\n *\n * Detects provider SDK constructors (`new OpenAI()`, `new Anthropic()`) and chat/completion calls\n * (a `.create()` whose receiver chain passes through `messages`/`completions`/`responses`, or a\n * `generateContent()` call) within the `handler` function passed to `new Tool({ handler })` /\n * `new ArtifactTool({ handler })`, ignoring inner closures (so a sub-agent's own callbacks don't\n * false-positive).\n *\n * Opt-out:\n * // eslint-disable-next-line adk/no-model-in-tool-handler -- <reason>\n */\n\nimport {\n createRule,\n isFunctionNode,\n isLlmCall,\n runsSubAgent,\n walkBodySkippingNestedFunctions,\n} from './common'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\nconst literalKey = (prop: TSESTree.Property): string | undefined => {\n if (prop.computed) return undefined\n if (prop.key.type === 'Identifier') return prop.key.name\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') return prop.key.value\n return undefined\n}\n\nconst noModelInToolHandlerRule = createRule({\n name: 'no-model-in-tool-handler',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'A Tool/ArtifactTool handler must not invoke a model — the executor owns the dispatch loop. The only exception is a sub-agent tool that runs its own scoped dispatch via `new TurnRunner(...)` or `DispatchRunner.dispatch(...)`.',\n },\n schema: [],\n messages: {\n noModelInHandler:\n 'Do not call a model inside a tool handler — the executor owns the dispatch loop, and a model call here hides an unmanaged nested dispatch (no token accounting, events, or abort). If this tool is a sub-agent, run a scoped `new TurnRunner(...)` or `DispatchRunner.dispatch(...)` instead. Opt out with an eslint-disable-next-line adk/no-model-in-tool-handler comment + reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n NewExpression(node: TSESTree.NewExpression) {\n if (\n node.callee.type !== 'Identifier' ||\n (node.callee.name !== 'Tool' && node.callee.name !== 'ArtifactTool')\n ) {\n return\n }\n const arg = node.arguments[0]\n if (!arg || arg.type !== 'ObjectExpression') return\n\n const handlerProp = arg.properties.find(\n (p): p is TSESTree.Property => p.type === 'Property' && literalKey(p) === 'handler'\n )\n if (!handlerProp || !isFunctionNode(handlerProp.value)) return\n const handler = handlerProp.value\n\n // A sub-agent tool legitimately runs a scoped dispatch (new TurnRunner / DispatchRunner.dispatch)\n // — that's the documented exception.\n if (runsSubAgent(handler.body)) return\n\n walkBodySkippingNestedFunctions(handler.body, (n) => {\n if (isLlmCall(n)) {\n context.report({ node: n, messageId: 'noModelInHandler' })\n }\n })\n },\n }\n },\n})\n\nexport default noModelInToolHandlerRule\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAM,cAAc,SAAgD;CAClE,IAAI,KAAK,UAAU,OAAO,KAAA;CAC1B,IAAI,KAAK,IAAI,SAAS,cAAc,OAAO,KAAK,IAAI;CACpD,IAAI,KAAK,IAAI,SAAS,aAAa,OAAO,KAAK,IAAI,UAAU,UAAU,OAAO,KAAK,IAAI;AAEzF;AAEA,IAAM,2BAA2B,WAAW;CAC1C,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,mOACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,kBACE,wXACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,cAAc,MAA8B;GAC1C,IACE,KAAK,OAAO,SAAS,gBACpB,KAAK,OAAO,SAAS,UAAU,KAAK,OAAO,SAAS,gBAErD;GAEF,MAAM,MAAM,KAAK,UAAU;GAC3B,IAAI,CAAC,OAAO,IAAI,SAAS,oBAAoB;GAE7C,MAAM,cAAc,IAAI,WAAW,MAChC,MAA8B,EAAE,SAAS,cAAc,WAAW,CAAC,MAAM,SAC5E;GACA,IAAI,CAAC,eAAe,CAAC,eAAe,YAAY,KAAK,GAAG;GACxD,MAAM,UAAU,YAAY;GAI5B,IAAI,aAAa,QAAQ,IAAI,GAAG;GAEhC,gCAAgC,QAAQ,OAAO,MAAM;IACnD,IAAI,UAAU,CAAC,GACb,QAAQ,OAAO;KAAE,MAAM;KAAG,WAAW;IAAmB,CAAC;GAE7D,CAAC;EACH,EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,100 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ require("../../chunk-Ble4zEEl.js");
6
+ const require_common = require("../../common-DeNipTQI.js");
7
+ //#region src/eslint/rules/require_validator_any_required.ts
8
+ /**
9
+ * @module @nhtio/adk/eslint/rules/require_validator_any_required
10
+ *
11
+ * Flags every `validator.any()` schema chain that does not declare intent with `.required()`
12
+ * or `.optional()`.
13
+ *
14
+ * Why: in `@nhtio/validation`, `.any()` ADMITS `null`/`undefined` unless you make it `.required()`.
15
+ * That default is silent and easy to miss — a schema you believe rejects missing values quietly
16
+ * accepts them, and any `.custom()` refinement is skipped for an absent value (a `.custom()` guard
17
+ * is the usual way this surfaces, e.g. `implementsX(undefined) === true`). The fix is to make the
18
+ * disposition an EXPLICIT declaration with no ambiguity — every `.any()` must say which it is:
19
+ * - `.required()` → reject null/undefined
20
+ * - `.optional()` → deliberately allow null/undefined
21
+ * - `.default(x)` → allow absence, substituting a fallback value
22
+ * - `.forbidden()` → the value must be absent
23
+ * This applies whether the `.any()` is top-level or nested inside `items(...)` / `alternatives(...)`:
24
+ * the enclosing schema being `.required()` does NOT govern an inner `.any()`'s null/undefined handling.
25
+ *
26
+ * The sharpest illustration is `.valid(null)`: an author writing `validator.any().valid(null)`
27
+ * means "must be exactly null" — but because `.any()` admits `undefined`, that schema actually
28
+ * accepts BOTH `null` and `undefined` (and `undefined !== null`). The fix is
29
+ * `validator.any().required().valid(null)`.
30
+ *
31
+ * Opt-out (e.g. a bare `.any()` used purely as a type argument like `items(validator.any())`):
32
+ * // eslint-disable-next-line adk/require-validator-any-required -- <reason>
33
+ */
34
+ var baseIdentifierName = (node) => {
35
+ let cur = node;
36
+ while (cur) {
37
+ if (cur.type === "Identifier") return cur.name;
38
+ if (cur.type === "MemberExpression") {
39
+ cur = cur.object;
40
+ continue;
41
+ }
42
+ if (cur.type === "CallExpression") {
43
+ cur = cur.callee;
44
+ continue;
45
+ }
46
+ return;
47
+ }
48
+ };
49
+ var isAnyCall = (node) => node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && node.callee.property.name === "any" && baseIdentifierName(node.callee.object) === "validator";
50
+ var collectChainCalls = (anyCall) => {
51
+ const calls = [];
52
+ let cur = anyCall;
53
+ while (cur && cur.type === "CallExpression") {
54
+ calls.push(cur);
55
+ cur = cur.callee.type === "MemberExpression" ? cur.callee.object : null;
56
+ }
57
+ cur = anyCall;
58
+ while (cur) {
59
+ const p = cur.parent;
60
+ if (!p) break;
61
+ if (p.type === "MemberExpression" && p.object === cur) {
62
+ cur = p;
63
+ continue;
64
+ }
65
+ if (p.type === "CallExpression" && p.callee === cur) {
66
+ calls.push(p);
67
+ cur = p;
68
+ continue;
69
+ }
70
+ break;
71
+ }
72
+ return calls;
73
+ };
74
+ var chainCalls = (calls, name) => calls.some((c) => c.callee.type === "MemberExpression" && c.callee.property.type === "Identifier" && c.callee.property.name === name);
75
+ var requireValidatorAnyRequiredRule = require_common.createRule({
76
+ name: "require-validator-any-required",
77
+ meta: {
78
+ type: "problem",
79
+ docs: { description: "`validator.any()` admits null/undefined unless `.required()`; make the disposition explicit by ending every `.any()` in `.required()`, `.optional()`, or `.default(…)`. Opt out with a tactical disable comment + reason." },
80
+ schema: [],
81
+ messages: { declareIntent: "`validator.any()` admits null/undefined unless made `.required()`. Make the disposition explicit: end this `.any()` in `.required()` (reject null/undefined), `.optional()` (deliberately allow it), or `.default(…)` (allow it with a fallback) — applies even when nested in items()/alternatives(). Or add an eslint-disable-next-line adk/require-validator-any-required comment with a reason." }
82
+ },
83
+ defaultOptions: [],
84
+ create(context) {
85
+ return { CallExpression(node) {
86
+ if (!isAnyCall(node)) return;
87
+ const calls = collectChainCalls(node);
88
+ if (chainCalls(calls, "required") || chainCalls(calls, "optional") || chainCalls(calls, "default") || chainCalls(calls, "forbidden")) return;
89
+ const reportNode = node.callee.type === "MemberExpression" ? node.callee.property : node;
90
+ context.report({
91
+ node: reportNode,
92
+ messageId: "declareIntent"
93
+ });
94
+ } };
95
+ }
96
+ });
97
+ //#endregion
98
+ exports.default = requireValidatorAnyRequiredRule;
99
+
100
+ //# sourceMappingURL=require_validator_any_required.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require_validator_any_required.cjs","names":[],"sources":["../../../src/eslint/rules/require_validator_any_required.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/require_validator_any_required\n *\n * Flags every `validator.any()` schema chain that does not declare intent with `.required()`\n * or `.optional()`.\n *\n * Why: in `@nhtio/validation`, `.any()` ADMITS `null`/`undefined` unless you make it `.required()`.\n * That default is silent and easy to miss — a schema you believe rejects missing values quietly\n * accepts them, and any `.custom()` refinement is skipped for an absent value (a `.custom()` guard\n * is the usual way this surfaces, e.g. `implementsX(undefined) === true`). The fix is to make the\n * disposition an EXPLICIT declaration with no ambiguity — every `.any()` must say which it is:\n * - `.required()` → reject null/undefined\n * - `.optional()` → deliberately allow null/undefined\n * - `.default(x)` → allow absence, substituting a fallback value\n * - `.forbidden()` → the value must be absent\n * This applies whether the `.any()` is top-level or nested inside `items(...)` / `alternatives(...)`:\n * the enclosing schema being `.required()` does NOT govern an inner `.any()`'s null/undefined handling.\n *\n * The sharpest illustration is `.valid(null)`: an author writing `validator.any().valid(null)`\n * means \"must be exactly null\" — but because `.any()` admits `undefined`, that schema actually\n * accepts BOTH `null` and `undefined` (and `undefined !== null`). The fix is\n * `validator.any().required().valid(null)`.\n *\n * Opt-out (e.g. a bare `.any()` used purely as a type argument like `items(validator.any())`):\n * // eslint-disable-next-line adk/require-validator-any-required -- <reason>\n */\n\nimport { createRule } from './common'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\n// The base identifier a member/call chain roots at, e.g. `validator` in `validator.any()` or\n// `validator.alternatives(...).any()`. Returns undefined if the chain doesn't root at a plain name.\nconst baseIdentifierName = (node: TSESTree.Node | undefined): string | undefined => {\n let cur: TSESTree.Node | undefined = node\n while (cur) {\n if (cur.type === 'Identifier') return cur.name\n if (cur.type === 'MemberExpression') {\n cur = cur.object\n continue\n }\n if (cur.type === 'CallExpression') {\n cur = cur.callee\n continue\n }\n return undefined\n }\n return undefined\n}\n\n// `validator.any()` (or a `validator.…().any()` chain) — NOT `expect.any()`, `_.any()`, etc.\nconst isAnyCall = (node: TSESTree.CallExpression): boolean =>\n node.callee.type === 'MemberExpression' &&\n node.callee.property.type === 'Identifier' &&\n node.callee.property.name === 'any' &&\n baseIdentifierName(node.callee.object) === 'validator'\n\n// Collect every CallExpression in the method chain `anyCall` belongs to — inward through the\n// callee object (`a.b().c()` -> `a.b()`) and outward through `.parent` links\n// (`x.any()` -> `x.any().required` -> `x.any().required()`).\nconst collectChainCalls = (anyCall: TSESTree.CallExpression): TSESTree.CallExpression[] => {\n const calls: TSESTree.CallExpression[] = []\n\n let cur: TSESTree.Node | null = anyCall\n while (cur && cur.type === 'CallExpression') {\n calls.push(cur)\n cur = cur.callee.type === 'MemberExpression' ? cur.callee.object : null\n }\n\n cur = anyCall\n while (cur) {\n const p: TSESTree.Node | undefined = cur.parent\n if (!p) break\n if (p.type === 'MemberExpression' && p.object === cur) {\n cur = p\n continue\n }\n if (p.type === 'CallExpression' && p.callee === cur) {\n calls.push(p)\n cur = p\n continue\n }\n break\n }\n\n return calls\n}\n\nconst chainCalls = (calls: TSESTree.CallExpression[], name: string): boolean =>\n calls.some(\n (c) =>\n c.callee.type === 'MemberExpression' &&\n c.callee.property.type === 'Identifier' &&\n c.callee.property.name === name\n )\n\nconst requireValidatorAnyRequiredRule = createRule({\n name: 'require-validator-any-required',\n meta: {\n type: 'problem',\n docs: {\n description:\n '`validator.any()` admits null/undefined unless `.required()`; make the disposition explicit by ending every `.any()` in `.required()`, `.optional()`, or `.default(…)`. Opt out with a tactical disable comment + reason.',\n },\n schema: [],\n messages: {\n declareIntent:\n '`validator.any()` admits null/undefined unless made `.required()`. Make the disposition explicit: end this `.any()` in `.required()` (reject null/undefined), `.optional()` (deliberately allow it), or `.default(…)` (allow it with a fallback) — applies even when nested in items()/alternatives(). Or add an eslint-disable-next-line adk/require-validator-any-required comment with a reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node: TSESTree.CallExpression) {\n if (!isAnyCall(node)) return\n const calls = collectChainCalls(node)\n // An unambiguous disposition declaration clears the rule:\n // .required() — reject null/undefined\n // .optional() — deliberately allow null/undefined\n // .default(x) — allow absence, substituting a fallback\n // .forbidden() — the value must be absent\n if (\n chainCalls(calls, 'required') ||\n chainCalls(calls, 'optional') ||\n chainCalls(calls, 'default') ||\n chainCalls(calls, 'forbidden')\n ) {\n return\n }\n const reportNode = node.callee.type === 'MemberExpression' ? node.callee.property : node\n context.report({ node: reportNode, messageId: 'declareIntent' })\n },\n }\n },\n})\n\nexport default requireValidatorAnyRequiredRule\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAM,sBAAsB,SAAwD;CAClF,IAAI,MAAiC;CACrC,OAAO,KAAK;EACV,IAAI,IAAI,SAAS,cAAc,OAAO,IAAI;EAC1C,IAAI,IAAI,SAAS,oBAAoB;GACnC,MAAM,IAAI;GACV;EACF;EACA,IAAI,IAAI,SAAS,kBAAkB;GACjC,MAAM,IAAI;GACV;EACF;EACA;CACF;AAEF;AAGA,IAAM,aAAa,SACjB,KAAK,OAAO,SAAS,sBACrB,KAAK,OAAO,SAAS,SAAS,gBAC9B,KAAK,OAAO,SAAS,SAAS,SAC9B,mBAAmB,KAAK,OAAO,MAAM,MAAM;AAK7C,IAAM,qBAAqB,YAAgE;CACzF,MAAM,QAAmC,CAAC;CAE1C,IAAI,MAA4B;CAChC,OAAO,OAAO,IAAI,SAAS,kBAAkB;EAC3C,MAAM,KAAK,GAAG;EACd,MAAM,IAAI,OAAO,SAAS,qBAAqB,IAAI,OAAO,SAAS;CACrE;CAEA,MAAM;CACN,OAAO,KAAK;EACV,MAAM,IAA+B,IAAI;EACzC,IAAI,CAAC,GAAG;EACR,IAAI,EAAE,SAAS,sBAAsB,EAAE,WAAW,KAAK;GACrD,MAAM;GACN;EACF;EACA,IAAI,EAAE,SAAS,oBAAoB,EAAE,WAAW,KAAK;GACnD,MAAM,KAAK,CAAC;GACZ,MAAM;GACN;EACF;EACA;CACF;CAEA,OAAO;AACT;AAEA,IAAM,cAAc,OAAkC,SACpD,MAAM,MACH,MACC,EAAE,OAAO,SAAS,sBAClB,EAAE,OAAO,SAAS,SAAS,gBAC3B,EAAE,OAAO,SAAS,SAAS,IAC/B;AAEF,IAAM,kCAAkC,eAAA,WAAW;CACjD,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,4NACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,eACE,sYACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,eAAe,MAA+B;GAC5C,IAAI,CAAC,UAAU,IAAI,GAAG;GACtB,MAAM,QAAQ,kBAAkB,IAAI;GAMpC,IACE,WAAW,OAAO,UAAU,KAC5B,WAAW,OAAO,UAAU,KAC5B,WAAW,OAAO,SAAS,KAC3B,WAAW,OAAO,WAAW,GAE7B;GAEF,MAAM,aAAa,KAAK,OAAO,SAAS,qBAAqB,KAAK,OAAO,WAAW;GACpF,QAAQ,OAAO;IAAE,MAAM;IAAY,WAAW;GAAgB,CAAC;EACjE,EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @module @nhtio/adk/eslint/rules/require_validator_any_required
3
+ *
4
+ * Flags every `validator.any()` schema chain that does not declare intent with `.required()`
5
+ * or `.optional()`.
6
+ *
7
+ * Why: in `@nhtio/validation`, `.any()` ADMITS `null`/`undefined` unless you make it `.required()`.
8
+ * That default is silent and easy to miss — a schema you believe rejects missing values quietly
9
+ * accepts them, and any `.custom()` refinement is skipped for an absent value (a `.custom()` guard
10
+ * is the usual way this surfaces, e.g. `implementsX(undefined) === true`). The fix is to make the
11
+ * disposition an EXPLICIT declaration with no ambiguity — every `.any()` must say which it is:
12
+ * - `.required()` → reject null/undefined
13
+ * - `.optional()` → deliberately allow null/undefined
14
+ * - `.default(x)` → allow absence, substituting a fallback value
15
+ * - `.forbidden()` → the value must be absent
16
+ * This applies whether the `.any()` is top-level or nested inside `items(...)` / `alternatives(...)`:
17
+ * the enclosing schema being `.required()` does NOT govern an inner `.any()`'s null/undefined handling.
18
+ *
19
+ * The sharpest illustration is `.valid(null)`: an author writing `validator.any().valid(null)`
20
+ * means "must be exactly null" — but because `.any()` admits `undefined`, that schema actually
21
+ * accepts BOTH `null` and `undefined` (and `undefined !== null`). The fix is
22
+ * `validator.any().required().valid(null)`.
23
+ *
24
+ * Opt-out (e.g. a bare `.any()` used purely as a type argument like `items(validator.any())`):
25
+ * // eslint-disable-next-line adk/require-validator-any-required -- <reason>
26
+ */
27
+ declare const requireValidatorAnyRequiredRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"declareIntent", [
28
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
29
+ name: string;
30
+ };
31
+ export default requireValidatorAnyRequiredRule;
@@ -0,0 +1,95 @@
1
+ import { t as createRule } from "../../common-BUIjZ6LV.mjs";
2
+ //#region src/eslint/rules/require_validator_any_required.ts
3
+ /**
4
+ * @module @nhtio/adk/eslint/rules/require_validator_any_required
5
+ *
6
+ * Flags every `validator.any()` schema chain that does not declare intent with `.required()`
7
+ * or `.optional()`.
8
+ *
9
+ * Why: in `@nhtio/validation`, `.any()` ADMITS `null`/`undefined` unless you make it `.required()`.
10
+ * That default is silent and easy to miss — a schema you believe rejects missing values quietly
11
+ * accepts them, and any `.custom()` refinement is skipped for an absent value (a `.custom()` guard
12
+ * is the usual way this surfaces, e.g. `implementsX(undefined) === true`). The fix is to make the
13
+ * disposition an EXPLICIT declaration with no ambiguity — every `.any()` must say which it is:
14
+ * - `.required()` → reject null/undefined
15
+ * - `.optional()` → deliberately allow null/undefined
16
+ * - `.default(x)` → allow absence, substituting a fallback value
17
+ * - `.forbidden()` → the value must be absent
18
+ * This applies whether the `.any()` is top-level or nested inside `items(...)` / `alternatives(...)`:
19
+ * the enclosing schema being `.required()` does NOT govern an inner `.any()`'s null/undefined handling.
20
+ *
21
+ * The sharpest illustration is `.valid(null)`: an author writing `validator.any().valid(null)`
22
+ * means "must be exactly null" — but because `.any()` admits `undefined`, that schema actually
23
+ * accepts BOTH `null` and `undefined` (and `undefined !== null`). The fix is
24
+ * `validator.any().required().valid(null)`.
25
+ *
26
+ * Opt-out (e.g. a bare `.any()` used purely as a type argument like `items(validator.any())`):
27
+ * // eslint-disable-next-line adk/require-validator-any-required -- <reason>
28
+ */
29
+ var baseIdentifierName = (node) => {
30
+ let cur = node;
31
+ while (cur) {
32
+ if (cur.type === "Identifier") return cur.name;
33
+ if (cur.type === "MemberExpression") {
34
+ cur = cur.object;
35
+ continue;
36
+ }
37
+ if (cur.type === "CallExpression") {
38
+ cur = cur.callee;
39
+ continue;
40
+ }
41
+ return;
42
+ }
43
+ };
44
+ var isAnyCall = (node) => node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier" && node.callee.property.name === "any" && baseIdentifierName(node.callee.object) === "validator";
45
+ var collectChainCalls = (anyCall) => {
46
+ const calls = [];
47
+ let cur = anyCall;
48
+ while (cur && cur.type === "CallExpression") {
49
+ calls.push(cur);
50
+ cur = cur.callee.type === "MemberExpression" ? cur.callee.object : null;
51
+ }
52
+ cur = anyCall;
53
+ while (cur) {
54
+ const p = cur.parent;
55
+ if (!p) break;
56
+ if (p.type === "MemberExpression" && p.object === cur) {
57
+ cur = p;
58
+ continue;
59
+ }
60
+ if (p.type === "CallExpression" && p.callee === cur) {
61
+ calls.push(p);
62
+ cur = p;
63
+ continue;
64
+ }
65
+ break;
66
+ }
67
+ return calls;
68
+ };
69
+ var chainCalls = (calls, name) => calls.some((c) => c.callee.type === "MemberExpression" && c.callee.property.type === "Identifier" && c.callee.property.name === name);
70
+ var requireValidatorAnyRequiredRule = createRule({
71
+ name: "require-validator-any-required",
72
+ meta: {
73
+ type: "problem",
74
+ docs: { description: "`validator.any()` admits null/undefined unless `.required()`; make the disposition explicit by ending every `.any()` in `.required()`, `.optional()`, or `.default(…)`. Opt out with a tactical disable comment + reason." },
75
+ schema: [],
76
+ messages: { declareIntent: "`validator.any()` admits null/undefined unless made `.required()`. Make the disposition explicit: end this `.any()` in `.required()` (reject null/undefined), `.optional()` (deliberately allow it), or `.default(…)` (allow it with a fallback) — applies even when nested in items()/alternatives(). Or add an eslint-disable-next-line adk/require-validator-any-required comment with a reason." }
77
+ },
78
+ defaultOptions: [],
79
+ create(context) {
80
+ return { CallExpression(node) {
81
+ if (!isAnyCall(node)) return;
82
+ const calls = collectChainCalls(node);
83
+ if (chainCalls(calls, "required") || chainCalls(calls, "optional") || chainCalls(calls, "default") || chainCalls(calls, "forbidden")) return;
84
+ const reportNode = node.callee.type === "MemberExpression" ? node.callee.property : node;
85
+ context.report({
86
+ node: reportNode,
87
+ messageId: "declareIntent"
88
+ });
89
+ } };
90
+ }
91
+ });
92
+ //#endregion
93
+ export { requireValidatorAnyRequiredRule as default };
94
+
95
+ //# sourceMappingURL=require_validator_any_required.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require_validator_any_required.mjs","names":[],"sources":["../../../src/eslint/rules/require_validator_any_required.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/require_validator_any_required\n *\n * Flags every `validator.any()` schema chain that does not declare intent with `.required()`\n * or `.optional()`.\n *\n * Why: in `@nhtio/validation`, `.any()` ADMITS `null`/`undefined` unless you make it `.required()`.\n * That default is silent and easy to miss — a schema you believe rejects missing values quietly\n * accepts them, and any `.custom()` refinement is skipped for an absent value (a `.custom()` guard\n * is the usual way this surfaces, e.g. `implementsX(undefined) === true`). The fix is to make the\n * disposition an EXPLICIT declaration with no ambiguity — every `.any()` must say which it is:\n * - `.required()` → reject null/undefined\n * - `.optional()` → deliberately allow null/undefined\n * - `.default(x)` → allow absence, substituting a fallback value\n * - `.forbidden()` → the value must be absent\n * This applies whether the `.any()` is top-level or nested inside `items(...)` / `alternatives(...)`:\n * the enclosing schema being `.required()` does NOT govern an inner `.any()`'s null/undefined handling.\n *\n * The sharpest illustration is `.valid(null)`: an author writing `validator.any().valid(null)`\n * means \"must be exactly null\" — but because `.any()` admits `undefined`, that schema actually\n * accepts BOTH `null` and `undefined` (and `undefined !== null`). The fix is\n * `validator.any().required().valid(null)`.\n *\n * Opt-out (e.g. a bare `.any()` used purely as a type argument like `items(validator.any())`):\n * // eslint-disable-next-line adk/require-validator-any-required -- <reason>\n */\n\nimport { createRule } from './common'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\n// The base identifier a member/call chain roots at, e.g. `validator` in `validator.any()` or\n// `validator.alternatives(...).any()`. Returns undefined if the chain doesn't root at a plain name.\nconst baseIdentifierName = (node: TSESTree.Node | undefined): string | undefined => {\n let cur: TSESTree.Node | undefined = node\n while (cur) {\n if (cur.type === 'Identifier') return cur.name\n if (cur.type === 'MemberExpression') {\n cur = cur.object\n continue\n }\n if (cur.type === 'CallExpression') {\n cur = cur.callee\n continue\n }\n return undefined\n }\n return undefined\n}\n\n// `validator.any()` (or a `validator.…().any()` chain) — NOT `expect.any()`, `_.any()`, etc.\nconst isAnyCall = (node: TSESTree.CallExpression): boolean =>\n node.callee.type === 'MemberExpression' &&\n node.callee.property.type === 'Identifier' &&\n node.callee.property.name === 'any' &&\n baseIdentifierName(node.callee.object) === 'validator'\n\n// Collect every CallExpression in the method chain `anyCall` belongs to — inward through the\n// callee object (`a.b().c()` -> `a.b()`) and outward through `.parent` links\n// (`x.any()` -> `x.any().required` -> `x.any().required()`).\nconst collectChainCalls = (anyCall: TSESTree.CallExpression): TSESTree.CallExpression[] => {\n const calls: TSESTree.CallExpression[] = []\n\n let cur: TSESTree.Node | null = anyCall\n while (cur && cur.type === 'CallExpression') {\n calls.push(cur)\n cur = cur.callee.type === 'MemberExpression' ? cur.callee.object : null\n }\n\n cur = anyCall\n while (cur) {\n const p: TSESTree.Node | undefined = cur.parent\n if (!p) break\n if (p.type === 'MemberExpression' && p.object === cur) {\n cur = p\n continue\n }\n if (p.type === 'CallExpression' && p.callee === cur) {\n calls.push(p)\n cur = p\n continue\n }\n break\n }\n\n return calls\n}\n\nconst chainCalls = (calls: TSESTree.CallExpression[], name: string): boolean =>\n calls.some(\n (c) =>\n c.callee.type === 'MemberExpression' &&\n c.callee.property.type === 'Identifier' &&\n c.callee.property.name === name\n )\n\nconst requireValidatorAnyRequiredRule = createRule({\n name: 'require-validator-any-required',\n meta: {\n type: 'problem',\n docs: {\n description:\n '`validator.any()` admits null/undefined unless `.required()`; make the disposition explicit by ending every `.any()` in `.required()`, `.optional()`, or `.default(…)`. Opt out with a tactical disable comment + reason.',\n },\n schema: [],\n messages: {\n declareIntent:\n '`validator.any()` admits null/undefined unless made `.required()`. Make the disposition explicit: end this `.any()` in `.required()` (reject null/undefined), `.optional()` (deliberately allow it), or `.default(…)` (allow it with a fallback) — applies even when nested in items()/alternatives(). Or add an eslint-disable-next-line adk/require-validator-any-required comment with a reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n CallExpression(node: TSESTree.CallExpression) {\n if (!isAnyCall(node)) return\n const calls = collectChainCalls(node)\n // An unambiguous disposition declaration clears the rule:\n // .required() — reject null/undefined\n // .optional() — deliberately allow null/undefined\n // .default(x) — allow absence, substituting a fallback\n // .forbidden() — the value must be absent\n if (\n chainCalls(calls, 'required') ||\n chainCalls(calls, 'optional') ||\n chainCalls(calls, 'default') ||\n chainCalls(calls, 'forbidden')\n ) {\n return\n }\n const reportNode = node.callee.type === 'MemberExpression' ? node.callee.property : node\n context.report({ node: reportNode, messageId: 'declareIntent' })\n },\n }\n },\n})\n\nexport default requireValidatorAnyRequiredRule\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiCA,IAAM,sBAAsB,SAAwD;CAClF,IAAI,MAAiC;CACrC,OAAO,KAAK;EACV,IAAI,IAAI,SAAS,cAAc,OAAO,IAAI;EAC1C,IAAI,IAAI,SAAS,oBAAoB;GACnC,MAAM,IAAI;GACV;EACF;EACA,IAAI,IAAI,SAAS,kBAAkB;GACjC,MAAM,IAAI;GACV;EACF;EACA;CACF;AAEF;AAGA,IAAM,aAAa,SACjB,KAAK,OAAO,SAAS,sBACrB,KAAK,OAAO,SAAS,SAAS,gBAC9B,KAAK,OAAO,SAAS,SAAS,SAC9B,mBAAmB,KAAK,OAAO,MAAM,MAAM;AAK7C,IAAM,qBAAqB,YAAgE;CACzF,MAAM,QAAmC,CAAC;CAE1C,IAAI,MAA4B;CAChC,OAAO,OAAO,IAAI,SAAS,kBAAkB;EAC3C,MAAM,KAAK,GAAG;EACd,MAAM,IAAI,OAAO,SAAS,qBAAqB,IAAI,OAAO,SAAS;CACrE;CAEA,MAAM;CACN,OAAO,KAAK;EACV,MAAM,IAA+B,IAAI;EACzC,IAAI,CAAC,GAAG;EACR,IAAI,EAAE,SAAS,sBAAsB,EAAE,WAAW,KAAK;GACrD,MAAM;GACN;EACF;EACA,IAAI,EAAE,SAAS,oBAAoB,EAAE,WAAW,KAAK;GACnD,MAAM,KAAK,CAAC;GACZ,MAAM;GACN;EACF;EACA;CACF;CAEA,OAAO;AACT;AAEA,IAAM,cAAc,OAAkC,SACpD,MAAM,MACH,MACC,EAAE,OAAO,SAAS,sBAClB,EAAE,OAAO,SAAS,SAAS,gBAC3B,EAAE,OAAO,SAAS,SAAS,IAC/B;AAEF,IAAM,kCAAkC,WAAW;CACjD,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,4NACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,eACE,sYACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,eAAe,MAA+B;GAC5C,IAAI,CAAC,UAAU,IAAI,GAAG;GACtB,MAAM,QAAQ,kBAAkB,IAAI;GAMpC,IACE,WAAW,OAAO,UAAU,KAC5B,WAAW,OAAO,UAAU,KAC5B,WAAW,OAAO,SAAS,KAC3B,WAAW,OAAO,WAAW,GAE7B;GAEF,MAAM,aAAa,KAAK,OAAO,SAAS,qBAAqB,KAAK,OAAO,WAAW;GACpF,QAAQ,OAAO;IAAE,MAAM;IAAY,WAAW;GAAgB,CAAC;EACjE,EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,71 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ require("../../chunk-Ble4zEEl.js");
6
+ const require_common = require("../../common-DeNipTQI.js");
7
+ //#region src/eslint/rules/thought_payload_requires_replay_tag.ts
8
+ /**
9
+ * @module @nhtio/adk/eslint/rules/thought_payload_requires_replay_tag
10
+ *
11
+ * Flags `new Thought({ … })` whose options literal sets a `payload` but omits a
12
+ * `replayCompatibility` tag.
13
+ *
14
+ * Why: a `Thought` may carry a vendor-opaque `payload` that the harness cannot interpret, to be
15
+ * replayed back to a matching model wire. The ADK can only route that payload if the thought also
16
+ * declares which adapter wire-shape it is replayable into, via `replayCompatibility`. A `payload`
17
+ * with no `replayCompatibility` is a footgun: the harness has no way to know which adapter can
18
+ * consume it, so it is dropped silently. The `Thought` constructor enforces this cross-field
19
+ * invariant at runtime (`payload` present ⇒ `replayCompatibility` required); this rule surfaces it
20
+ * statically at the construction site so it fails lint, not just at runtime.
21
+ *
22
+ * Detects the common literal form: a `new Thought({ … })` options object that has a `payload`
23
+ * property but no `replayCompatibility` property. Spreads or computed keys are not analyzable here —
24
+ * those fall back to the runtime check.
25
+ *
26
+ * Opt-out:
27
+ * // eslint-disable-next-line adk/thought-payload-requires-replay-tag -- <reason>
28
+ */
29
+ var literalKey = (prop) => {
30
+ if (prop.computed) return void 0;
31
+ if (prop.key.type === "Identifier") return prop.key.name;
32
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
33
+ };
34
+ var isUndefinedValue = (node) => node.type === "Identifier" && node.name === "undefined";
35
+ var thoughtPayloadRequiresReplayTagRule = require_common.createRule({
36
+ name: "thought-payload-requires-replay-tag",
37
+ meta: {
38
+ type: "problem",
39
+ docs: { description: "A `Thought` carrying a `payload` must declare `replayCompatibility`; otherwise the harness cannot route the opaque payload to a matching adapter and drops it. Enforced at runtime by the Thought constructor; this surfaces it at the construction site." },
40
+ schema: [],
41
+ messages: { requireReplayTag: "`new Thought({ payload })` requires a `replayCompatibility` tag — without it the harness cannot route the opaque payload to an adapter and silently drops it. Add `replayCompatibility: \"<wire-shape-id>\"`, or remove the payload. Opt out with an eslint-disable-next-line adk/thought-payload-requires-replay-tag comment + reason." }
42
+ },
43
+ defaultOptions: [],
44
+ create(context) {
45
+ return { NewExpression(node) {
46
+ if (node.callee.type !== "Identifier" || node.callee.name !== "Thought") return;
47
+ const arg = node.arguments[0];
48
+ if (!arg || arg.type !== "ObjectExpression") return;
49
+ let payloadProp;
50
+ let hasReplayTag = false;
51
+ let hasSpread = false;
52
+ for (const p of arg.properties) {
53
+ if (p.type === "SpreadElement") {
54
+ hasSpread = true;
55
+ continue;
56
+ }
57
+ const key = literalKey(p);
58
+ if (key === "payload" && !isUndefinedValue(p.value)) payloadProp = p;
59
+ else if (key === "replayCompatibility" && !isUndefinedValue(p.value)) hasReplayTag = true;
60
+ }
61
+ if (payloadProp && !hasReplayTag && !hasSpread) context.report({
62
+ node: payloadProp,
63
+ messageId: "requireReplayTag"
64
+ });
65
+ } };
66
+ }
67
+ });
68
+ //#endregion
69
+ exports.default = thoughtPayloadRequiresReplayTagRule;
70
+
71
+ //# sourceMappingURL=thought_payload_requires_replay_tag.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thought_payload_requires_replay_tag.cjs","names":[],"sources":["../../../src/eslint/rules/thought_payload_requires_replay_tag.ts"],"sourcesContent":["/**\n * @module @nhtio/adk/eslint/rules/thought_payload_requires_replay_tag\n *\n * Flags `new Thought({ … })` whose options literal sets a `payload` but omits a\n * `replayCompatibility` tag.\n *\n * Why: a `Thought` may carry a vendor-opaque `payload` that the harness cannot interpret, to be\n * replayed back to a matching model wire. The ADK can only route that payload if the thought also\n * declares which adapter wire-shape it is replayable into, via `replayCompatibility`. A `payload`\n * with no `replayCompatibility` is a footgun: the harness has no way to know which adapter can\n * consume it, so it is dropped silently. The `Thought` constructor enforces this cross-field\n * invariant at runtime (`payload` present ⇒ `replayCompatibility` required); this rule surfaces it\n * statically at the construction site so it fails lint, not just at runtime.\n *\n * Detects the common literal form: a `new Thought({ … })` options object that has a `payload`\n * property but no `replayCompatibility` property. Spreads or computed keys are not analyzable here —\n * those fall back to the runtime check.\n *\n * Opt-out:\n * // eslint-disable-next-line adk/thought-payload-requires-replay-tag -- <reason>\n */\n\nimport { createRule } from './common'\n\nimport type { TSESTree } from '@typescript-eslint/utils'\n\nconst literalKey = (prop: TSESTree.Property): string | undefined => {\n if (prop.computed) return undefined\n if (prop.key.type === 'Identifier') return prop.key.name\n if (prop.key.type === 'Literal' && typeof prop.key.value === 'string') return prop.key.value\n return undefined\n}\n\n// True when the property value is statically `undefined` (the absent case) — `payload: undefined`\n// is equivalent to omitting it, so it should not trigger the rule.\nconst isUndefinedValue = (node: TSESTree.Node): boolean =>\n node.type === 'Identifier' && node.name === 'undefined'\n\nconst thoughtPayloadRequiresReplayTagRule = createRule({\n name: 'thought-payload-requires-replay-tag',\n meta: {\n type: 'problem',\n docs: {\n description:\n 'A `Thought` carrying a `payload` must declare `replayCompatibility`; otherwise the harness cannot route the opaque payload to a matching adapter and drops it. Enforced at runtime by the Thought constructor; this surfaces it at the construction site.',\n },\n schema: [],\n messages: {\n requireReplayTag:\n '`new Thought({ payload })` requires a `replayCompatibility` tag — without it the harness cannot route the opaque payload to an adapter and silently drops it. Add `replayCompatibility: \"<wire-shape-id>\"`, or remove the payload. Opt out with an eslint-disable-next-line adk/thought-payload-requires-replay-tag comment + reason.',\n },\n },\n defaultOptions: [],\n create(context) {\n return {\n NewExpression(node: TSESTree.NewExpression) {\n if (node.callee.type !== 'Identifier' || node.callee.name !== 'Thought') return\n const arg = node.arguments[0]\n if (!arg || arg.type !== 'ObjectExpression') return\n\n let payloadProp: TSESTree.Property | undefined\n let hasReplayTag = false\n let hasSpread = false\n for (const p of arg.properties) {\n if (p.type === 'SpreadElement') {\n hasSpread = true\n continue\n }\n const key = literalKey(p)\n if (key === 'payload' && !isUndefinedValue(p.value)) payloadProp = p\n else if (key === 'replayCompatibility' && !isUndefinedValue(p.value)) hasReplayTag = true\n }\n\n // A spread could supply replayCompatibility we can't see — stay conservative, don't flag.\n if (payloadProp && !hasReplayTag && !hasSpread) {\n context.report({ node: payloadProp, messageId: 'requireReplayTag' })\n }\n },\n }\n },\n})\n\nexport default thoughtPayloadRequiresReplayTagRule\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,IAAM,cAAc,SAAgD;CAClE,IAAI,KAAK,UAAU,OAAO,KAAA;CAC1B,IAAI,KAAK,IAAI,SAAS,cAAc,OAAO,KAAK,IAAI;CACpD,IAAI,KAAK,IAAI,SAAS,aAAa,OAAO,KAAK,IAAI,UAAU,UAAU,OAAO,KAAK,IAAI;AAEzF;AAIA,IAAM,oBAAoB,SACxB,KAAK,SAAS,gBAAgB,KAAK,SAAS;AAE9C,IAAM,sCAAsC,eAAA,WAAW;CACrD,MAAM;CACN,MAAM;EACJ,MAAM;EACN,MAAM,EACJ,aACE,4PACJ;EACA,QAAQ,CAAC;EACT,UAAU,EACR,kBACE,0UACJ;CACF;CACA,gBAAgB,CAAC;CACjB,OAAO,SAAS;EACd,OAAO,EACL,cAAc,MAA8B;GAC1C,IAAI,KAAK,OAAO,SAAS,gBAAgB,KAAK,OAAO,SAAS,WAAW;GACzE,MAAM,MAAM,KAAK,UAAU;GAC3B,IAAI,CAAC,OAAO,IAAI,SAAS,oBAAoB;GAE7C,IAAI;GACJ,IAAI,eAAe;GACnB,IAAI,YAAY;GAChB,KAAK,MAAM,KAAK,IAAI,YAAY;IAC9B,IAAI,EAAE,SAAS,iBAAiB;KAC9B,YAAY;KACZ;IACF;IACA,MAAM,MAAM,WAAW,CAAC;IACxB,IAAI,QAAQ,aAAa,CAAC,iBAAiB,EAAE,KAAK,GAAG,cAAc;SAC9D,IAAI,QAAQ,yBAAyB,CAAC,iBAAiB,EAAE,KAAK,GAAG,eAAe;GACvF;GAGA,IAAI,eAAe,CAAC,gBAAgB,CAAC,WACnC,QAAQ,OAAO;IAAE,MAAM;IAAa,WAAW;GAAmB,CAAC;EAEvE,EACF;CACF;AACF,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @module @nhtio/adk/eslint/rules/thought_payload_requires_replay_tag
3
+ *
4
+ * Flags `new Thought({ … })` whose options literal sets a `payload` but omits a
5
+ * `replayCompatibility` tag.
6
+ *
7
+ * Why: a `Thought` may carry a vendor-opaque `payload` that the harness cannot interpret, to be
8
+ * replayed back to a matching model wire. The ADK can only route that payload if the thought also
9
+ * declares which adapter wire-shape it is replayable into, via `replayCompatibility`. A `payload`
10
+ * with no `replayCompatibility` is a footgun: the harness has no way to know which adapter can
11
+ * consume it, so it is dropped silently. The `Thought` constructor enforces this cross-field
12
+ * invariant at runtime (`payload` present ⇒ `replayCompatibility` required); this rule surfaces it
13
+ * statically at the construction site so it fails lint, not just at runtime.
14
+ *
15
+ * Detects the common literal form: a `new Thought({ … })` options object that has a `payload`
16
+ * property but no `replayCompatibility` property. Spreads or computed keys are not analyzable here —
17
+ * those fall back to the runtime check.
18
+ *
19
+ * Opt-out:
20
+ * // eslint-disable-next-line adk/thought-payload-requires-replay-tag -- <reason>
21
+ */
22
+ declare const thoughtPayloadRequiresReplayTagRule: import("@typescript-eslint/utils/ts-eslint").RuleModule<"requireReplayTag", [
23
+ ], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
24
+ name: string;
25
+ };
26
+ export default thoughtPayloadRequiresReplayTagRule;
@@ -0,0 +1,66 @@
1
+ import { t as createRule } from "../../common-BUIjZ6LV.mjs";
2
+ //#region src/eslint/rules/thought_payload_requires_replay_tag.ts
3
+ /**
4
+ * @module @nhtio/adk/eslint/rules/thought_payload_requires_replay_tag
5
+ *
6
+ * Flags `new Thought({ … })` whose options literal sets a `payload` but omits a
7
+ * `replayCompatibility` tag.
8
+ *
9
+ * Why: a `Thought` may carry a vendor-opaque `payload` that the harness cannot interpret, to be
10
+ * replayed back to a matching model wire. The ADK can only route that payload if the thought also
11
+ * declares which adapter wire-shape it is replayable into, via `replayCompatibility`. A `payload`
12
+ * with no `replayCompatibility` is a footgun: the harness has no way to know which adapter can
13
+ * consume it, so it is dropped silently. The `Thought` constructor enforces this cross-field
14
+ * invariant at runtime (`payload` present ⇒ `replayCompatibility` required); this rule surfaces it
15
+ * statically at the construction site so it fails lint, not just at runtime.
16
+ *
17
+ * Detects the common literal form: a `new Thought({ … })` options object that has a `payload`
18
+ * property but no `replayCompatibility` property. Spreads or computed keys are not analyzable here —
19
+ * those fall back to the runtime check.
20
+ *
21
+ * Opt-out:
22
+ * // eslint-disable-next-line adk/thought-payload-requires-replay-tag -- <reason>
23
+ */
24
+ var literalKey = (prop) => {
25
+ if (prop.computed) return void 0;
26
+ if (prop.key.type === "Identifier") return prop.key.name;
27
+ if (prop.key.type === "Literal" && typeof prop.key.value === "string") return prop.key.value;
28
+ };
29
+ var isUndefinedValue = (node) => node.type === "Identifier" && node.name === "undefined";
30
+ var thoughtPayloadRequiresReplayTagRule = createRule({
31
+ name: "thought-payload-requires-replay-tag",
32
+ meta: {
33
+ type: "problem",
34
+ docs: { description: "A `Thought` carrying a `payload` must declare `replayCompatibility`; otherwise the harness cannot route the opaque payload to a matching adapter and drops it. Enforced at runtime by the Thought constructor; this surfaces it at the construction site." },
35
+ schema: [],
36
+ messages: { requireReplayTag: "`new Thought({ payload })` requires a `replayCompatibility` tag — without it the harness cannot route the opaque payload to an adapter and silently drops it. Add `replayCompatibility: \"<wire-shape-id>\"`, or remove the payload. Opt out with an eslint-disable-next-line adk/thought-payload-requires-replay-tag comment + reason." }
37
+ },
38
+ defaultOptions: [],
39
+ create(context) {
40
+ return { NewExpression(node) {
41
+ if (node.callee.type !== "Identifier" || node.callee.name !== "Thought") return;
42
+ const arg = node.arguments[0];
43
+ if (!arg || arg.type !== "ObjectExpression") return;
44
+ let payloadProp;
45
+ let hasReplayTag = false;
46
+ let hasSpread = false;
47
+ for (const p of arg.properties) {
48
+ if (p.type === "SpreadElement") {
49
+ hasSpread = true;
50
+ continue;
51
+ }
52
+ const key = literalKey(p);
53
+ if (key === "payload" && !isUndefinedValue(p.value)) payloadProp = p;
54
+ else if (key === "replayCompatibility" && !isUndefinedValue(p.value)) hasReplayTag = true;
55
+ }
56
+ if (payloadProp && !hasReplayTag && !hasSpread) context.report({
57
+ node: payloadProp,
58
+ messageId: "requireReplayTag"
59
+ });
60
+ } };
61
+ }
62
+ });
63
+ //#endregion
64
+ export { thoughtPayloadRequiresReplayTagRule as default };
65
+
66
+ //# sourceMappingURL=thought_payload_requires_replay_tag.mjs.map