@ankhorage/rules 0.2.0

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 (72) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +63 -0
  4. package/dist/cli/commands/config/validate.d.ts +6 -0
  5. package/dist/cli/commands/config/validate.d.ts.map +1 -0
  6. package/dist/cli/commands/config/validate.js +25 -0
  7. package/dist/cli/commands/config/validate.js.map +1 -0
  8. package/dist/cli/createRulesRuntimeProvider.d.ts +4 -0
  9. package/dist/cli/createRulesRuntimeProvider.d.ts.map +1 -0
  10. package/dist/cli/createRulesRuntimeProvider.js +29 -0
  11. package/dist/cli/createRulesRuntimeProvider.js.map +1 -0
  12. package/dist/cli/index.d.ts +4 -0
  13. package/dist/cli/index.d.ts.map +1 -0
  14. package/dist/cli/index.js +4 -0
  15. package/dist/cli/index.js.map +1 -0
  16. package/dist/cli/standalone.d.ts +6 -0
  17. package/dist/cli/standalone.d.ts.map +1 -0
  18. package/dist/cli/standalone.js +43 -0
  19. package/dist/cli/standalone.js.map +1 -0
  20. package/dist/constants/rules.d.ts +5 -0
  21. package/dist/constants/rules.d.ts.map +1 -0
  22. package/dist/constants/rules.js +6 -0
  23. package/dist/constants/rules.js.map +1 -0
  24. package/dist/features/configuration/adapters/filesystem/readRulesConfigSourceAsync.d.ts +3 -0
  25. package/dist/features/configuration/adapters/filesystem/readRulesConfigSourceAsync.d.ts.map +1 -0
  26. package/dist/features/configuration/adapters/filesystem/readRulesConfigSourceAsync.js +6 -0
  27. package/dist/features/configuration/adapters/filesystem/readRulesConfigSourceAsync.js.map +1 -0
  28. package/dist/features/configuration/adapters/filesystem/writeRulesConfigAsync.d.ts +4 -0
  29. package/dist/features/configuration/adapters/filesystem/writeRulesConfigAsync.d.ts.map +1 -0
  30. package/dist/features/configuration/adapters/filesystem/writeRulesConfigAsync.js +19 -0
  31. package/dist/features/configuration/adapters/filesystem/writeRulesConfigAsync.js.map +1 -0
  32. package/dist/features/configuration/application/validateRulesConfigFileAsync.d.ts +4 -0
  33. package/dist/features/configuration/application/validateRulesConfigFileAsync.d.ts.map +1 -0
  34. package/dist/features/configuration/application/validateRulesConfigFileAsync.js +27 -0
  35. package/dist/features/configuration/application/validateRulesConfigFileAsync.js.map +1 -0
  36. package/dist/features/configuration/composition/readRulesConfigAsync.d.ts +4 -0
  37. package/dist/features/configuration/composition/readRulesConfigAsync.d.ts.map +1 -0
  38. package/dist/features/configuration/composition/readRulesConfigAsync.js +7 -0
  39. package/dist/features/configuration/composition/readRulesConfigAsync.js.map +1 -0
  40. package/dist/features/configuration/domain/validateRulesConfig.d.ts +4 -0
  41. package/dist/features/configuration/domain/validateRulesConfig.d.ts.map +1 -0
  42. package/dist/features/configuration/domain/validateRulesConfig.js +159 -0
  43. package/dist/features/configuration/domain/validateRulesConfig.js.map +1 -0
  44. package/dist/features/evaluation/domain/createRuleRegistry.d.ts +4 -0
  45. package/dist/features/evaluation/domain/createRuleRegistry.d.ts.map +1 -0
  46. package/dist/features/evaluation/domain/createRuleRegistry.js +36 -0
  47. package/dist/features/evaluation/domain/createRuleRegistry.js.map +1 -0
  48. package/dist/features/evaluation/domain/evaluateConfiguredRules.d.ts +4 -0
  49. package/dist/features/evaluation/domain/evaluateConfiguredRules.d.ts.map +1 -0
  50. package/dist/features/evaluation/domain/evaluateConfiguredRules.js +52 -0
  51. package/dist/features/evaluation/domain/evaluateConfiguredRules.js.map +1 -0
  52. package/dist/features/evaluation/domain/evaluateRules.d.ts +4 -0
  53. package/dist/features/evaluation/domain/evaluateRules.d.ts.map +1 -0
  54. package/dist/features/evaluation/domain/evaluateRules.js +53 -0
  55. package/dist/features/evaluation/domain/evaluateRules.js.map +1 -0
  56. package/dist/features/evaluation/domain/resolveRulesStatus.d.ts +4 -0
  57. package/dist/features/evaluation/domain/resolveRulesStatus.d.ts.map +1 -0
  58. package/dist/features/evaluation/domain/resolveRulesStatus.js +12 -0
  59. package/dist/features/evaluation/domain/resolveRulesStatus.js.map +1 -0
  60. package/dist/index.d.ts +9 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +8 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/types/rules.d.ts +121 -0
  65. package/dist/types/rules.d.ts.map +1 -0
  66. package/dist/types/rules.js +2 -0
  67. package/dist/types/rules.js.map +1 -0
  68. package/dist/utils/compareRuleEntityIds.d.ts +7 -0
  69. package/dist/utils/compareRuleEntityIds.d.ts.map +1 -0
  70. package/dist/utils/compareRuleEntityIds.js +9 -0
  71. package/dist/utils/compareRuleEntityIds.js.map +1 -0
  72. package/package.json +83 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @ankhorage/rules
2
+
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 94f1dfb: Restore feature-first Rules ownership, canonical CLI command layout, standalone runtime independence, precise configuration diagnostics, and complete evaluation status semantics.
8
+
9
+ ## 0.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 67e49ab: Bootstrap the generic rule registry, JSON configuration, deterministic evaluator, status model, and config validation CLI provider.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ankhorage
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ <!-- markdownlint-disable MD013 MD033 -->
2
+ <!-- This file is generated by Paradox. Do not edit manually. -->
3
+
4
+ # @ankhorage/rules
5
+
6
+ ![license: MIT](././paradox/badges/license.svg) ![npm: v0.2.0](././paradox/badges/npm.svg) ![runtime: bun](././paradox/badges/runtime.svg) ![typescript: strict](././paradox/badges/typescript.svg) ![eslint: checked](././paradox/badges/eslint.svg) ![prettier: checked](././paradox/badges/prettier.svg) ![build: checked](././paradox/badges/build.svg) ![tests: checked](././paradox/badges/tests.svg) ![paradox: canonical](././paradox/badges/docs.svg)
7
+
8
+ Domain-agnostic rule engine and JSON configuration for Ankhorage.
9
+
10
+ ## Usage
11
+
12
+ ### CLI
13
+
14
+ Ankhorage packages expose their command-line interface through `ankh`. Use `ankh --help` to discover available package commands, or run a package command with `--help` for package-specific usage.
15
+
16
+ ```zsh
17
+ # Install the Ankhorage CLI
18
+ bun add --global @ankhorage/ankh
19
+
20
+ # Show usage information for rules
21
+ ankh rules --help
22
+ ```
23
+
24
+ ### Validate Rules Configuration
25
+
26
+ Validate a repository rules.json file through the public generic Rules API.
27
+
28
+ ```ts
29
+ import { readRulesConfigAsync } from '@ankhorage/rules';
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ ### Example
35
+
36
+ ```ts
37
+ import { defineParadoxConfig } from '@ankhorage/paradox';
38
+
39
+ export default defineParadoxConfig({
40
+ mode: 'write',
41
+ docs: {
42
+ title: '@ankhorage/rules',
43
+ description: 'Domain-agnostic rule engine and JSON configuration for Ankhorage.',
44
+ },
45
+ package: {
46
+ root: '.',
47
+ entrypoints: ['src/index.ts'],
48
+ },
49
+ output: { dir: './paradox' },
50
+ });
51
+ ```
52
+
53
+ ## Generated documentation
54
+
55
+ - [Interactive documentation app](././paradox/index.html)
56
+ - [Public API reference](././paradox/exports.md)
57
+ - [Component registry](././paradox/components.md)
58
+ - [Architecture overview](././paradox/diagrams/architecture-overview.mmd)
59
+ - [Module relationships](././paradox/diagrams/module-relationships.mmd)
60
+ - [Export graph](././paradox/diagrams/export-graph.mmd)
61
+ - [ankhorage-rules sequence](././paradox/diagrams/sequences/ankhorage-rules.mmd)
62
+ - [createRuleRegistry sequence](././paradox/diagrams/sequences/create-rule-registry.mmd)
63
+ - [validateRulesConfig sequence](././paradox/diagrams/sequences/validate-rules-config.mmd)
@@ -0,0 +1,6 @@
1
+ /*** Execute the public `ankh rules config validate` command through the configuration feature. */
2
+ export declare function validate(argv: readonly string[], cwd: string): Promise<{
3
+ readonly exitCode: number;
4
+ readonly stdout: string;
5
+ }>;
6
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/config/validate.ts"],"names":[],"mappings":"AAIA,kGAAkG;AAClG,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAUjE"}
@@ -0,0 +1,25 @@
1
+ import { resolve } from 'node:path';
2
+ import { readRulesConfigAsync } from '../../../features/configuration/composition/readRulesConfigAsync.js';
3
+ /*** Execute the public `ankh rules config validate` command through the configuration feature. */
4
+ export async function validate(argv, cwd) {
5
+ if (argv.length > 1) {
6
+ throw new Error('ankh rules config validate accepts at most one rules.json path.');
7
+ }
8
+ const result = await readRulesConfigAsync(resolve(cwd, argv.at(0) ?? 'rules.json'));
9
+ return {
10
+ exitCode: result.diagnostics.length === 0 ? 0 : 1,
11
+ stdout: renderConfigValidation(result),
12
+ };
13
+ }
14
+ /*** Render the command result without coupling the configuration feature to a transport. */
15
+ function renderConfigValidation(result) {
16
+ if (result.diagnostics.length === 0)
17
+ return `rules config validate\nvalid: ${result.path}\n`;
18
+ return [
19
+ 'rules config validate',
20
+ `invalid: ${result.path}`,
21
+ ...result.diagnostics.map((diagnostic) => `- ${diagnostic.code}: ${diagnostic.message}`),
22
+ '',
23
+ ].join('\n');
24
+ }
25
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../../src/cli/commands/config/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,qEAAqE,CAAC;AAE3G,kGAAkG;AAClG,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAuB,EACvB,GAAW;IAEX,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC;IACpF,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,MAAM,EAAE,sBAAsB,CAAC,MAAM,CAAC;KACvC,CAAC;AACJ,CAAC;AAED,4FAA4F;AAC5F,SAAS,sBAAsB,CAAC,MAAwD;IACtF,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,iCAAiC,MAAM,CAAC,IAAI,IAAI,CAAC;IAC7F,OAAO;QACL,uBAAuB;QACvB,YAAY,MAAM,CAAC,IAAI,EAAE;QACzB,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,KAAK,UAAU,CAAC,IAAI,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC;QACxF,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC","sourcesContent":["import { resolve } from 'node:path';\n\nimport { readRulesConfigAsync } from '../../../features/configuration/composition/readRulesConfigAsync.js';\n\n/*** Execute the public `ankh rules config validate` command through the configuration feature. */\nexport async function validate(\n argv: readonly string[],\n cwd: string,\n): Promise<{ readonly exitCode: number; readonly stdout: string }> {\n if (argv.length > 1) {\n throw new Error('ankh rules config validate accepts at most one rules.json path.');\n }\n\n const result = await readRulesConfigAsync(resolve(cwd, argv.at(0) ?? 'rules.json'));\n return {\n exitCode: result.diagnostics.length === 0 ? 0 : 1,\n stdout: renderConfigValidation(result),\n };\n}\n\n/*** Render the command result without coupling the configuration feature to a transport. */\nfunction renderConfigValidation(result: Awaited<ReturnType<typeof readRulesConfigAsync>>): string {\n if (result.diagnostics.length === 0) return `rules config validate\\nvalid: ${result.path}\\n`;\n return [\n 'rules config validate',\n `invalid: ${result.path}`,\n ...result.diagnostics.map((diagnostic) => `- ${diagnostic.code}: ${diagnostic.message}`),\n '',\n ].join('\\n');\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { AnkhRuntimeCommandProvider } from '@ankhorage/ankh';
2
+ /*** Create the Ankh provider that registers the generic Rules configuration command. */
3
+ export declare function createRulesRuntimeProvider(): AnkhRuntimeCommandProvider;
4
+ //# sourceMappingURL=createRulesRuntimeProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createRulesRuntimeProvider.d.ts","sourceRoot":"","sources":["../../src/cli/createRulesRuntimeProvider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AASlE,wFAAwF;AACxF,wBAAgB,0BAA0B,IAAI,0BAA0B,CAwBvE"}
@@ -0,0 +1,29 @@
1
+ import { RULES_CAPABILITIES, RULES_COMMAND_CATEGORY, RULES_PACKAGE_VERSION, } from '../constants/rules.js';
2
+ import { validate } from './commands/config/validate.js';
3
+ /*** Create the Ankh provider that registers the generic Rules configuration command. */
4
+ export function createRulesRuntimeProvider() {
5
+ return {
6
+ id: '@ankhorage/rules',
7
+ category: RULES_COMMAND_CATEGORY,
8
+ version: RULES_PACKAGE_VERSION,
9
+ capabilities: [...RULES_CAPABILITIES],
10
+ commands: [
11
+ {
12
+ path: ['config', 'validate'],
13
+ capability: RULES_CAPABILITIES[0],
14
+ summary: 'Validate the generic JSON shape of a repository rules.json file.',
15
+ },
16
+ ],
17
+ handlers: [
18
+ {
19
+ path: ['config', 'validate'],
20
+ handler: async ({ argv, context }) => {
21
+ const result = await validate(argv, context.cwd);
22
+ context.writeStdout(result.stdout);
23
+ return { exitCode: result.exitCode };
24
+ },
25
+ },
26
+ ],
27
+ };
28
+ }
29
+ //# sourceMappingURL=createRulesRuntimeProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createRulesRuntimeProvider.js","sourceRoot":"","sources":["../../src/cli/createRulesRuntimeProvider.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEzD,wFAAwF;AACxF,MAAM,UAAU,0BAA0B;IACxC,OAAO;QACL,EAAE,EAAE,kBAAkB;QACtB,QAAQ,EAAE,sBAAsB;QAChC,OAAO,EAAE,qBAAqB;QAC9B,YAAY,EAAE,CAAC,GAAG,kBAAkB,CAAC;QACrC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;gBAC5B,UAAU,EAAE,kBAAkB,CAAC,CAAC,CAAC;gBACjC,OAAO,EAAE,kEAAkE;aAC5E;SACF;QACD,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,CAAC,QAAQ,EAAE,UAAU,CAAC;gBAC5B,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;oBACnC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;oBACjD,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;oBACnC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;gBACvC,CAAC;aACF;SACF;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { AnkhRuntimeCommandProvider } from '@ankhorage/ankh';\n\nimport {\n RULES_CAPABILITIES,\n RULES_COMMAND_CATEGORY,\n RULES_PACKAGE_VERSION,\n} from '../constants/rules.js';\nimport { validate } from './commands/config/validate.js';\n\n/*** Create the Ankh provider that registers the generic Rules configuration command. */\nexport function createRulesRuntimeProvider(): AnkhRuntimeCommandProvider {\n return {\n id: '@ankhorage/rules',\n category: RULES_COMMAND_CATEGORY,\n version: RULES_PACKAGE_VERSION,\n capabilities: [...RULES_CAPABILITIES],\n commands: [\n {\n path: ['config', 'validate'],\n capability: RULES_CAPABILITIES[0],\n summary: 'Validate the generic JSON shape of a repository rules.json file.',\n },\n ],\n handlers: [\n {\n path: ['config', 'validate'],\n handler: async ({ argv, context }) => {\n const result = await validate(argv, context.cwd);\n context.writeStdout(result.stdout);\n return { exitCode: result.exitCode };\n },\n },\n ],\n };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export { createRulesRuntimeProvider } from './createRulesRuntimeProvider.js';
2
+ declare const _default: import("@ankhorage/ankh").AnkhRuntimeCommandProvider;
3
+ export default _default;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;;AAE7E,wBAA4C"}
@@ -0,0 +1,4 @@
1
+ import { createRulesRuntimeProvider } from './createRulesRuntimeProvider.js';
2
+ export { createRulesRuntimeProvider } from './createRulesRuntimeProvider.js';
3
+ export default createRulesRuntimeProvider();
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,OAAO,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAE7E,eAAe,0BAA0B,EAAE,CAAC","sourcesContent":["import { createRulesRuntimeProvider } from './createRulesRuntimeProvider.js';\n\nexport { createRulesRuntimeProvider } from './createRulesRuntimeProvider.js';\n\nexport default createRulesRuntimeProvider();\n"]}
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bun
2
+ /*** Run the standalone Rules CLI without depending on the Ankh runtime package. */
3
+ export declare function runCli(argv: readonly string[]): Promise<{
4
+ readonly exitCode: number;
5
+ }>;
6
+ //# sourceMappingURL=standalone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone.d.ts","sourceRoot":"","sources":["../../src/cli/standalone.ts"],"names":[],"mappings":";AAIA,mFAAmF;AACnF,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC;IAAE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAmB5F"}
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bun
2
+ import { validate } from './commands/config/validate.js';
3
+ /*** Run the standalone Rules CLI without depending on the Ankh runtime package. */
4
+ export async function runCli(argv) {
5
+ const [firstToken, ...restTokens] = argv;
6
+ if (firstToken === undefined || isHelpToken(firstToken)) {
7
+ process.stdout.write(renderHelp());
8
+ return { exitCode: 0 };
9
+ }
10
+ if (firstToken !== 'config' || restTokens.at(0) !== 'validate') {
11
+ process.stderr.write('Unknown Rules command. Run ankhorage-rules --help.\n');
12
+ return { exitCode: 1 };
13
+ }
14
+ try {
15
+ const result = await validate(restTokens.slice(1), process.cwd());
16
+ process.stdout.write(result.stdout);
17
+ return { exitCode: result.exitCode };
18
+ }
19
+ catch (error) {
20
+ process.stderr.write(`${error instanceof Error ? error.message : 'Rules command failed.'}\n`);
21
+ return { exitCode: 1 };
22
+ }
23
+ }
24
+ /*** Identify help tokens accepted by the standalone delivery adapter. */
25
+ function isHelpToken(value) {
26
+ return value === '--help' || value === '-h' || value === 'help';
27
+ }
28
+ /*** Render standalone help without introducing a second command implementation. */
29
+ function renderHelp() {
30
+ return [
31
+ '@ankhorage/rules',
32
+ '',
33
+ 'Usage:',
34
+ ' ankhorage-rules config validate [rules.json]',
35
+ ' ankh rules config validate [rules.json]',
36
+ '',
37
+ ].join('\n');
38
+ }
39
+ if (import.meta.main) {
40
+ const result = await runCli(process.argv.slice(2));
41
+ process.exit(result.exitCode);
42
+ }
43
+ //# sourceMappingURL=standalone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"standalone.js","sourceRoot":"","sources":["../../src/cli/standalone.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AAEzD,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,IAAuB;IAClD,MAAM,CAAC,UAAU,EAAE,GAAG,UAAU,CAAC,GAAG,IAAI,CAAC;IACzC,IAAI,UAAU,KAAK,SAAS,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;QACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;QACnC,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,UAAU,KAAK,QAAQ,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC;QAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC7E,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAClE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,uBAAuB,IAAI,CAAC,CAAC;QAC9F,OAAO,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;IACzB,CAAC;AACH,CAAC;AAED,yEAAyE;AACzE,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,MAAM,CAAC;AAClE,CAAC;AAED,mFAAmF;AACnF,SAAS,UAAU;IACjB,OAAO;QACL,kBAAkB;QAClB,EAAE;QACF,QAAQ;QACR,gDAAgD;QAChD,2CAA2C;QAC3C,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC","sourcesContent":["#!/usr/bin/env bun\n\nimport { validate } from './commands/config/validate.js';\n\n/*** Run the standalone Rules CLI without depending on the Ankh runtime package. */\nexport async function runCli(argv: readonly string[]): Promise<{ readonly exitCode: number }> {\n const [firstToken, ...restTokens] = argv;\n if (firstToken === undefined || isHelpToken(firstToken)) {\n process.stdout.write(renderHelp());\n return { exitCode: 0 };\n }\n if (firstToken !== 'config' || restTokens.at(0) !== 'validate') {\n process.stderr.write('Unknown Rules command. Run ankhorage-rules --help.\\n');\n return { exitCode: 1 };\n }\n\n try {\n const result = await validate(restTokens.slice(1), process.cwd());\n process.stdout.write(result.stdout);\n return { exitCode: result.exitCode };\n } catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : 'Rules command failed.'}\\n`);\n return { exitCode: 1 };\n }\n}\n\n/*** Identify help tokens accepted by the standalone delivery adapter. */\nfunction isHelpToken(value: string): boolean {\n return value === '--help' || value === '-h' || value === 'help';\n}\n\n/*** Render standalone help without introducing a second command implementation. */\nfunction renderHelp(): string {\n return [\n '@ankhorage/rules',\n '',\n 'Usage:',\n ' ankhorage-rules config validate [rules.json]',\n ' ankh rules config validate [rules.json]',\n '',\n ].join('\\n');\n}\n\nif (import.meta.main) {\n const result = await runCli(process.argv.slice(2));\n process.exit(result.exitCode);\n}\n"]}
@@ -0,0 +1,5 @@
1
+ /*** Package-wide Rules metadata used by CLI delivery edges. */
2
+ export declare const RULES_COMMAND_CATEGORY = "rules";
3
+ export declare const RULES_CAPABILITIES: readonly ["rules.config.validate"];
4
+ export declare const RULES_PACKAGE_VERSION: string;
5
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/constants/rules.ts"],"names":[],"mappings":"AAEA,+DAA+D;AAC/D,eAAO,MAAM,sBAAsB,UAAU,CAAC;AAC9C,eAAO,MAAM,kBAAkB,oCAAqC,CAAC;AACrE,eAAO,MAAM,qBAAqB,QAAsB,CAAC"}
@@ -0,0 +1,6 @@
1
+ import packageJson from '../../package.json';
2
+ /*** Package-wide Rules metadata used by CLI delivery edges. */
3
+ export const RULES_COMMAND_CATEGORY = 'rules';
4
+ export const RULES_CAPABILITIES = ['rules.config.validate'];
5
+ export const RULES_PACKAGE_VERSION = packageJson.version;
6
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/constants/rules.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,oBAAoB,CAAC;AAE7C,+DAA+D;AAC/D,MAAM,CAAC,MAAM,sBAAsB,GAAG,OAAO,CAAC;AAC9C,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,uBAAuB,CAAU,CAAC;AACrE,MAAM,CAAC,MAAM,qBAAqB,GAAG,WAAW,CAAC,OAAO,CAAC","sourcesContent":["import packageJson from '../../package.json';\n\n/*** Package-wide Rules metadata used by CLI delivery edges. */\nexport const RULES_COMMAND_CATEGORY = 'rules';\nexport const RULES_CAPABILITIES = ['rules.config.validate'] as const;\nexport const RULES_PACKAGE_VERSION = packageJson.version;\n"]}
@@ -0,0 +1,3 @@
1
+ /*** Read serialized Rules configuration at the filesystem edge. */
2
+ export declare function readRulesConfigSourceAsync(path: string): Promise<string>;
3
+ //# sourceMappingURL=readRulesConfigSourceAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readRulesConfigSourceAsync.d.ts","sourceRoot":"","sources":["../../../../../src/features/configuration/adapters/filesystem/readRulesConfigSourceAsync.ts"],"names":[],"mappings":"AAEA,mEAAmE;AACnE,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE9E"}
@@ -0,0 +1,6 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ /*** Read serialized Rules configuration at the filesystem edge. */
3
+ export async function readRulesConfigSourceAsync(path) {
4
+ return await readFile(path, 'utf8');
5
+ }
6
+ //# sourceMappingURL=readRulesConfigSourceAsync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readRulesConfigSourceAsync.js","sourceRoot":"","sources":["../../../../../src/features/configuration/adapters/filesystem/readRulesConfigSourceAsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,mEAAmE;AACnE,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,IAAY;IAC3D,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;AACtC,CAAC","sourcesContent":["import { readFile } from 'node:fs/promises';\n\n/*** Read serialized Rules configuration at the filesystem edge. */\nexport async function readRulesConfigSourceAsync(path: string): Promise<string> {\n return await readFile(path, 'utf8');\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { RulesConfig } from '../../../../types/rules.js';
2
+ /*** Validate and atomically write canonical rules.json configuration through the filesystem adapter. */
3
+ export declare function writeRulesConfigAsync(path: string, config: RulesConfig): Promise<void>;
4
+ //# sourceMappingURL=writeRulesConfigAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeRulesConfigAsync.d.ts","sourceRoot":"","sources":["../../../../../src/features/configuration/adapters/filesystem/writeRulesConfigAsync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAI9D,wGAAwG;AACxG,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5F"}
@@ -0,0 +1,19 @@
1
+ import { writeJsonFileAtomic } from '@ankhorage/utility/node/fs';
2
+ import { compareRuleEntityIds } from '../../../../utils/compareRuleEntityIds.js';
3
+ import { validateRulesConfig } from '../../domain/validateRulesConfig.js';
4
+ /*** Validate and atomically write canonical rules.json configuration through the filesystem adapter. */
5
+ export async function writeRulesConfigAsync(path, config) {
6
+ const validation = validateRulesConfig(config);
7
+ if (validation.config === null) {
8
+ throw new Error(validation.diagnostics.map((diagnostic) => diagnostic.message).join('\n'));
9
+ }
10
+ await writeJsonFileAtomic(path, canonicalizeRulesConfig(validation.config));
11
+ }
12
+ /*** Canonicalize rule selection order so writing preserves deterministic configuration composition. */
13
+ function canonicalizeRulesConfig(config) {
14
+ return {
15
+ version: 1,
16
+ rules: [...config.rules].sort(compareRuleEntityIds),
17
+ };
18
+ }
19
+ //# sourceMappingURL=writeRulesConfigAsync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writeRulesConfigAsync.js","sourceRoot":"","sources":["../../../../../src/features/configuration/adapters/filesystem/writeRulesConfigAsync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qCAAqC,CAAC;AAE1E,wGAAwG;AACxG,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,IAAY,EAAE,MAAmB;IAC3E,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC/C,IAAI,UAAU,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7F,CAAC;IAED,MAAM,mBAAmB,CAAC,IAAI,EAAE,uBAAuB,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AAC9E,CAAC;AAED,uGAAuG;AACvG,SAAS,uBAAuB,CAAC,MAAmB;IAClD,OAAO;QACL,OAAO,EAAE,CAAC;QACV,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC;KACpD,CAAC;AACJ,CAAC","sourcesContent":["import { writeJsonFileAtomic } from '@ankhorage/utility/node/fs';\n\nimport type { RulesConfig } from '../../../../types/rules.js';\nimport { compareRuleEntityIds } from '../../../../utils/compareRuleEntityIds.js';\nimport { validateRulesConfig } from '../../domain/validateRulesConfig.js';\n\n/*** Validate and atomically write canonical rules.json configuration through the filesystem adapter. */\nexport async function writeRulesConfigAsync(path: string, config: RulesConfig): Promise<void> {\n const validation = validateRulesConfig(config);\n if (validation.config === null) {\n throw new Error(validation.diagnostics.map((diagnostic) => diagnostic.message).join('\\n'));\n }\n\n await writeJsonFileAtomic(path, canonicalizeRulesConfig(validation.config));\n}\n\n/*** Canonicalize rule selection order so writing preserves deterministic configuration composition. */\nfunction canonicalizeRulesConfig(config: RulesConfig): RulesConfig {\n return {\n version: 1,\n rules: [...config.rules].sort(compareRuleEntityIds),\n };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { RulesConfigReadResult } from '../../../types/rules.js';
2
+ /*** Read and validate one Rules configuration through a caller supplied source reader. */
3
+ export declare function validateRulesConfigFileAsync(path: string, readSourceAsync: (path: string) => Promise<string>): Promise<RulesConfigReadResult>;
4
+ //# sourceMappingURL=validateRulesConfigFileAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateRulesConfigFileAsync.d.ts","sourceRoot":"","sources":["../../../../src/features/configuration/application/validateRulesConfigFileAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAG5F,0FAA0F;AAC1F,wBAAsB,4BAA4B,CAChD,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GACjD,OAAO,CAAC,qBAAqB,CAAC,CAWhC"}
@@ -0,0 +1,27 @@
1
+ import { validateRulesConfig } from '../domain/validateRulesConfig.js';
2
+ /*** Read and validate one Rules configuration through a caller supplied source reader. */
3
+ export async function validateRulesConfigFileAsync(path, readSourceAsync) {
4
+ try {
5
+ const source = await readSourceAsync(path);
6
+ return { path, ...validateRulesConfig(parseJson(source)) };
7
+ }
8
+ catch (error) {
9
+ return {
10
+ path,
11
+ config: null,
12
+ diagnostics: [createReadDiagnostic(error)],
13
+ };
14
+ }
15
+ }
16
+ /*** Parse JSON without conflating a syntax failure with a valid null configuration. */
17
+ function parseJson(source) {
18
+ return JSON.parse(source);
19
+ }
20
+ /*** Convert source and JSON failures into a portable configuration diagnostic. */
21
+ function createReadDiagnostic(error) {
22
+ return {
23
+ code: 'invalid-config',
24
+ message: error instanceof Error ? error.message : 'Unable to read rules.json.',
25
+ };
26
+ }
27
+ //# sourceMappingURL=validateRulesConfigFileAsync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateRulesConfigFileAsync.js","sourceRoot":"","sources":["../../../../src/features/configuration/application/validateRulesConfigFileAsync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAEvE,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,IAAY,EACZ,eAAkD;IAElD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO,EAAE,IAAI,EAAE,GAAG,mBAAmB,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;IAC7D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,IAAI;YACJ,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;SAC3C,CAAC;IACJ,CAAC;AACH,CAAC;AAED,uFAAuF;AACvF,SAAS,SAAS,CAAC,MAAc;IAC/B,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAY,CAAC;AACvC,CAAC;AAED,kFAAkF;AAClF,SAAS,oBAAoB,CAAC,KAAc;IAC1C,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,4BAA4B;KAC/E,CAAC;AACJ,CAAC","sourcesContent":["import type { RulesConfigDiagnostic, RulesConfigReadResult } from '../../../types/rules.js';\nimport { validateRulesConfig } from '../domain/validateRulesConfig.js';\n\n/*** Read and validate one Rules configuration through a caller supplied source reader. */\nexport async function validateRulesConfigFileAsync(\n path: string,\n readSourceAsync: (path: string) => Promise<string>,\n): Promise<RulesConfigReadResult> {\n try {\n const source = await readSourceAsync(path);\n return { path, ...validateRulesConfig(parseJson(source)) };\n } catch (error) {\n return {\n path,\n config: null,\n diagnostics: [createReadDiagnostic(error)],\n };\n }\n}\n\n/*** Parse JSON without conflating a syntax failure with a valid null configuration. */\nfunction parseJson(source: string): unknown {\n return JSON.parse(source) as unknown;\n}\n\n/*** Convert source and JSON failures into a portable configuration diagnostic. */\nfunction createReadDiagnostic(error: unknown): RulesConfigDiagnostic {\n return {\n code: 'invalid-config',\n message: error instanceof Error ? error.message : 'Unable to read rules.json.',\n };\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { RulesConfigReadResult } from '../../../types/rules.js';
2
+ /*** Wire the configuration source reader into the public file-validation operation. */
3
+ export declare function readRulesConfigAsync(path: string): Promise<RulesConfigReadResult>;
4
+ //# sourceMappingURL=readRulesConfigAsync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readRulesConfigAsync.d.ts","sourceRoot":"","sources":["../../../../src/features/configuration/composition/readRulesConfigAsync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAIrE,uFAAuF;AACvF,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAEvF"}
@@ -0,0 +1,7 @@
1
+ import { readRulesConfigSourceAsync } from '../adapters/filesystem/readRulesConfigSourceAsync.js';
2
+ import { validateRulesConfigFileAsync } from '../application/validateRulesConfigFileAsync.js';
3
+ /*** Wire the configuration source reader into the public file-validation operation. */
4
+ export async function readRulesConfigAsync(path) {
5
+ return await validateRulesConfigFileAsync(path, readRulesConfigSourceAsync);
6
+ }
7
+ //# sourceMappingURL=readRulesConfigAsync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readRulesConfigAsync.js","sourceRoot":"","sources":["../../../../src/features/configuration/composition/readRulesConfigAsync.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,0BAA0B,EAAE,MAAM,sDAAsD,CAAC;AAClG,OAAO,EAAE,4BAA4B,EAAE,MAAM,gDAAgD,CAAC;AAE9F,uFAAuF;AACvF,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,IAAY;IACrD,OAAO,MAAM,4BAA4B,CAAC,IAAI,EAAE,0BAA0B,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["import type { RulesConfigReadResult } from '../../../types/rules.js';\nimport { readRulesConfigSourceAsync } from '../adapters/filesystem/readRulesConfigSourceAsync.js';\nimport { validateRulesConfigFileAsync } from '../application/validateRulesConfigFileAsync.js';\n\n/*** Wire the configuration source reader into the public file-validation operation. */\nexport async function readRulesConfigAsync(path: string): Promise<RulesConfigReadResult> {\n return await validateRulesConfigFileAsync(path, readRulesConfigSourceAsync);\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import type { RulesConfigValidationOptions, RulesConfigValidationResult } from '../../../types/rules.js';
2
+ /*** Validate canonical rules.json data, optionally against a concrete rule registry. */
3
+ export declare function validateRulesConfig<TContext>(value: unknown, options?: RulesConfigValidationOptions<TContext>): RulesConfigValidationResult;
4
+ //# sourceMappingURL=validateRulesConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateRulesConfig.d.ts","sourceRoot":"","sources":["../../../../src/features/configuration/domain/validateRulesConfig.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAKV,4BAA4B,EAC5B,2BAA2B,EAE5B,MAAM,yBAAyB,CAAC;AAEjC,wFAAwF;AACxF,wBAAgB,mBAAmB,CAAC,QAAQ,EAC1C,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,4BAA4B,CAAC,QAAQ,CAAM,GACnD,2BAA2B,CAS7B"}
@@ -0,0 +1,159 @@
1
+ import { isRecord } from '@ankhorage/utility/object';
2
+ /*** Validate canonical rules.json data, optionally against a concrete rule registry. */
3
+ export function validateRulesConfig(value, options = {}) {
4
+ const structuralResult = validateRulesConfigStructure(value);
5
+ if (structuralResult.config === null)
6
+ return structuralResult;
7
+ const diagnostics = validateConfiguredRules(structuralResult.config, options);
8
+ return {
9
+ config: diagnostics.length === 0 ? structuralResult.config : null,
10
+ diagnostics,
11
+ };
12
+ }
13
+ /*** Validate the JSON-only shape before any provider-specific interpretation occurs. */
14
+ function validateRulesConfigStructure(value) {
15
+ if (!isRecord(value))
16
+ return invalidConfig('rules.json must contain an object.');
17
+ if (value.version !== 1)
18
+ return invalidConfig('rules.json "version" must be 1.', 'version');
19
+ if (!Array.isArray(value.rules)) {
20
+ return invalidConfig('rules.json "rules" must be an array.', 'rules');
21
+ }
22
+ const ruleResults = value.rules.map((rule, index) => validateRulesConfigRule(rule, index));
23
+ const diagnostics = ruleResults.flatMap((result) => result.diagnostics);
24
+ if (diagnostics.length > 0)
25
+ return { config: null, diagnostics };
26
+ const rules = ruleResults.flatMap((result) => (result.rule === null ? [] : [result.rule]));
27
+ const duplicateIds = findDuplicateRuleIds(rules);
28
+ if (duplicateIds.length > 0) {
29
+ return {
30
+ config: null,
31
+ diagnostics: duplicateIds.map((id) => ({
32
+ code: 'invalid-rule-id',
33
+ message: `rules.json configures rule "${id}" more than once.`,
34
+ path: 'rules',
35
+ ruleId: id,
36
+ })),
37
+ };
38
+ }
39
+ return { config: { version: 1, rules }, diagnostics: [] };
40
+ }
41
+ /*** Validate one serialized rule selection while retaining its original options exactly. */
42
+ function validateRulesConfigRule(value, index) {
43
+ const path = `rules[${index}]`;
44
+ if (!isRecord(value)) {
45
+ return invalidRuleField('invalid-config', path, 'must be an object.');
46
+ }
47
+ if (!isNonEmptyString(value.id)) {
48
+ return invalidRuleField('invalid-rule-id', `${path}.id`, 'must define a non-empty "id".');
49
+ }
50
+ if (typeof value.enabled !== 'boolean') {
51
+ return invalidRuleField('invalid-config', `${path}.enabled`, 'must define boolean "enabled".', value.id);
52
+ }
53
+ if (value.severity !== undefined && !isRuleSeverity(value.severity)) {
54
+ return invalidRuleField('invalid-severity', `${path}.severity`, 'has an invalid "severity".', value.id);
55
+ }
56
+ if (value.options !== undefined && !isJsonValue(value.options)) {
57
+ return invalidRuleField('invalid-options', `${path}.options`, 'has non-serializable "options".', value.id);
58
+ }
59
+ return {
60
+ diagnostics: [],
61
+ rule: {
62
+ id: value.id,
63
+ enabled: value.enabled,
64
+ ...(value.severity === undefined ? {} : { severity: value.severity }),
65
+ ...(value.options === undefined ? {} : { options: value.options }),
66
+ },
67
+ };
68
+ }
69
+ /*** Apply optional registry and capability knowledge without creating provider-specific engine paths. */
70
+ function validateConfiguredRules(config, options) {
71
+ const { registry } = options;
72
+ if (registry === undefined)
73
+ return [];
74
+ const availableCapabilities = new Set(options.capabilities ?? []);
75
+ return config.rules.reduce((diagnostics, configuredRule, index) => {
76
+ if (!configuredRule.enabled)
77
+ return diagnostics;
78
+ const rule = registry.ruleById.get(configuredRule.id);
79
+ if (rule === undefined) {
80
+ return [
81
+ ...diagnostics,
82
+ {
83
+ code: 'unknown-rule',
84
+ message: `rules.json configures unknown rule "${configuredRule.id}".`,
85
+ path: `rules[${index}].id`,
86
+ ruleId: configuredRule.id,
87
+ },
88
+ ];
89
+ }
90
+ const missingCapabilities = options.capabilities === undefined
91
+ ? []
92
+ : (rule.requiredCapabilities ?? []).filter((capability) => !availableCapabilities.has(capability));
93
+ const capabilityDiagnostics = missingCapabilities.map((capability) => ({
94
+ code: 'missing-capability',
95
+ message: `Rule "${rule.id}" requires unavailable capability "${capability}".`,
96
+ path: `rules[${index}]`,
97
+ ruleId: rule.id,
98
+ }));
99
+ const optionDiagnostics = (rule.validateOptions?.(configuredRule.options) ?? []).map((diagnostic) => ({
100
+ code: 'invalid-options',
101
+ message: `Rule "${rule.id}": ${diagnostic.message}`,
102
+ ruleId: rule.id,
103
+ ...(diagnostic.path === undefined
104
+ ? {}
105
+ : { path: `rules[${index}].options.${diagnostic.path}` }),
106
+ }));
107
+ return [...diagnostics, ...capabilityDiagnostics, ...optionDiagnostics];
108
+ }, []);
109
+ }
110
+ /*** Create a consistent structural configuration diagnostic. */
111
+ function invalidConfig(message, path) {
112
+ return {
113
+ config: null,
114
+ diagnostics: [{ code: 'invalid-config', message, ...(path === undefined ? {} : { path }) }],
115
+ };
116
+ }
117
+ /*** Create a field-specific diagnostic for one invalid configured rule. */
118
+ function invalidRuleField(code, path, detail, ruleId) {
119
+ return {
120
+ rule: null,
121
+ diagnostics: [
122
+ {
123
+ code,
124
+ message: `rules.json ${path} ${detail}`,
125
+ path,
126
+ ...(ruleId === undefined ? {} : { ruleId }),
127
+ },
128
+ ],
129
+ };
130
+ }
131
+ /*** Find duplicate explicit rule selections without relying on object-key ordering. */
132
+ function findDuplicateRuleIds(rules) {
133
+ const seen = new Set();
134
+ return rules.flatMap((rule) => {
135
+ if (seen.has(rule.id))
136
+ return [rule.id];
137
+ seen.add(rule.id);
138
+ return [];
139
+ });
140
+ }
141
+ /*** Determine whether a value is a supported generic severity. */
142
+ function isRuleSeverity(value) {
143
+ return value === 'error' || value === 'info' || value === 'warning';
144
+ }
145
+ /*** Determine whether a value can be preserved losslessly in JSON configuration. */
146
+ function isJsonValue(value) {
147
+ if (value === null || typeof value === 'boolean' || typeof value === 'string')
148
+ return true;
149
+ if (typeof value === 'number')
150
+ return Number.isFinite(value);
151
+ if (Array.isArray(value))
152
+ return value.every(isJsonValue);
153
+ return isRecord(value) && Object.values(value).every(isJsonValue);
154
+ }
155
+ /*** Determine whether a raw configuration field is a non-empty identifier. */
156
+ function isNonEmptyString(value) {
157
+ return typeof value === 'string' && value.trim().length > 0;
158
+ }
159
+ //# sourceMappingURL=validateRulesConfig.js.map