@gobing-ai/ts-rule-engine 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +328 -58
- package/dist/config/extensions.d.ts +10 -7
- package/dist/config/extensions.d.ts.map +1 -1
- package/dist/config/extensions.js +48 -23
- package/dist/config/loader.d.ts +7 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +17 -12
- package/dist/engine.d.ts +13 -2
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +107 -45
- package/dist/evaluators/agent-detection-evaluator.d.ts.map +1 -1
- package/dist/evaluators/agent-detection-evaluator.js +2 -9
- package/dist/evaluators/coverage-gate-evaluator.d.ts.map +1 -1
- package/dist/evaluators/coverage-gate-evaluator.js +4 -5
- package/dist/evaluators/exit-code-evaluator.d.ts.map +1 -1
- package/dist/evaluators/exit-code-evaluator.js +6 -23
- package/dist/evaluators/file-utils.d.ts +25 -1
- package/dist/evaluators/file-utils.d.ts.map +1 -1
- package/dist/evaluators/file-utils.js +48 -8
- package/dist/evaluators/forbidden-import-evaluator.js +2 -10
- package/dist/evaluators/path-evaluator.d.ts.map +1 -1
- package/dist/evaluators/path-evaluator.js +5 -18
- package/dist/evaluators/regex-evaluator.js +3 -11
- package/dist/evaluators/ripgrep-evaluator.d.ts +50 -0
- package/dist/evaluators/ripgrep-evaluator.d.ts.map +1 -0
- package/dist/evaluators/ripgrep-evaluator.js +145 -0
- package/dist/evaluators/schema-artifact-evaluator.d.ts.map +1 -1
- package/dist/evaluators/schema-artifact-evaluator.js +3 -7
- package/dist/evaluators/sg-evaluator.d.ts +10 -2
- package/dist/evaluators/sg-evaluator.d.ts.map +1 -1
- package/dist/evaluators/sg-evaluator.js +21 -4
- package/dist/evaluators/test-location-evaluator.d.ts +2 -2
- package/dist/evaluators/test-location-evaluator.d.ts.map +1 -1
- package/dist/evaluators/test-location-evaluator.js +2 -15
- package/dist/events.d.ts +33 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +0 -0
- package/dist/fixers/fixers.d.ts +1 -1
- package/dist/fixers/fixers.d.ts.map +1 -1
- package/dist/fixers/fixers.js +4 -5
- package/dist/fixers/test-stub-fixer.d.ts +1 -1
- package/dist/fixers/test-stub-fixer.d.ts.map +1 -1
- package/dist/fixers/test-stub-fixer.js +3 -4
- package/dist/host/builtins.d.ts.map +1 -1
- package/dist/host/builtins.js +5 -3
- package/dist/host/bundled-rules.d.ts +1 -1
- package/dist/host/bundled-rules.js +1 -1
- package/dist/host/rule-engine-host.d.ts +1 -1
- package/dist/host/rule-engine-host.d.ts.map +1 -1
- package/dist/host/rule-engine-host.js +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/package.json +4 -5
- package/rules/example.yaml +13 -0
- package/src/config/extensions.ts +58 -29
- package/src/config/loader.ts +27 -12
- package/src/engine.ts +132 -47
- package/src/evaluators/agent-detection-evaluator.ts +2 -8
- package/src/evaluators/coverage-gate-evaluator.ts +4 -5
- package/src/evaluators/exit-code-evaluator.ts +6 -23
- package/src/evaluators/file-utils.ts +70 -8
- package/src/evaluators/forbidden-import-evaluator.ts +2 -9
- package/src/evaluators/path-evaluator.ts +5 -18
- package/src/evaluators/regex-evaluator.ts +4 -11
- package/src/evaluators/ripgrep-evaluator.ts +167 -0
- package/src/evaluators/schema-artifact-evaluator.ts +3 -8
- package/src/evaluators/sg-evaluator.ts +21 -4
- package/src/evaluators/test-location-evaluator.ts +3 -16
- package/src/events.ts +13 -0
- package/src/fixers/fixers.ts +12 -6
- package/src/fixers/test-stub-fixer.ts +4 -5
- package/src/host/builtins.ts +5 -3
- package/src/host/bundled-rules.ts +1 -1
- package/src/host/rule-engine-host.ts +1 -1
- package/src/index.ts +8 -1
- package/src/types.ts +7 -0
- package/dist/host/capability-registry.d.ts +0 -10
- package/dist/host/capability-registry.d.ts.map +0 -1
- package/dist/host/capability-registry.js +0 -9
- package/rules/recommended.yaml +0 -10
- package/rules/spur-dev.yaml +0 -6
- package/src/host/capability-registry.ts +0 -9
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @module rule-engine/fixers/test-stub-fixer
|
|
12
12
|
*/
|
|
13
13
|
import { type ProcessExecutor } from '@gobing-ai/ts-runtime';
|
|
14
|
-
import type { CapabilityRegistry } from '
|
|
14
|
+
import type { CapabilityRegistry } from '@gobing-ai/ts-runtime/plugin';
|
|
15
15
|
import type { TestPathResolver } from '../resolvers/test-path-resolver';
|
|
16
16
|
import type { Fix } from '../types';
|
|
17
17
|
import type { RuleFixerInput, RuleFixerProvider } from './fixers';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"test-stub-fixer.d.ts","sourceRoot":"","sources":["../../src/fixers/test-stub-fixer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;
|
|
1
|
+
{"version":3,"file":"test-stub-fixer.d.ts","sourceRoot":"","sources":["../../src/fixers/test-stub-fixer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAA4C,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACvG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAElE,0DAA0D;AAC1D,MAAM,WAAW,kBAAkB;IAC/B,kDAAkD;IAClD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1C;AAED,6EAA6E;AAC7E,eAAO,MAAM,cAAc,EAAE,kBAAyC,CAAC;AAEvE,wDAAwD;AACxD,MAAM,WAAW,iBAAiB;IAC9B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IACzD,+DAA+D;IAC/D,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,iDAAiD;IACjD,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAC5C;AAUD;;;;;;;GAOG;AACH,qBAAa,aAAc,YAAW,iBAAiB;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAuC;IACjE,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAqB;gBAE5B,IAAI,EAAE,iBAAiB;IAOnC,wDAAwD;IAClD,WAAW,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;CAkD3D"}
|
|
@@ -10,13 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @module rule-engine/fixers/test-stub-fixer
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
14
|
-
import { NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
13
|
+
import { isAbsolutePath, joinPath, NodeFileSystem } from '@gobing-ai/ts-runtime';
|
|
15
14
|
/** Real file-system implementation backed by the runtime FileSystem seam. */
|
|
16
15
|
export const realFileSystem = new NodeFileSystem();
|
|
17
16
|
/** Normalize a finding path to a repository-relative POSIX path, or return null when invalid. */
|
|
18
17
|
function normalizeFindingPath(filePath) {
|
|
19
|
-
if (!filePath ||
|
|
18
|
+
if (!filePath || isAbsolutePath(filePath))
|
|
20
19
|
return null;
|
|
21
20
|
const normalized = filePath.replaceAll('\\', '/').replace(/^\.\//, '');
|
|
22
21
|
if (normalized === '..' || normalized.startsWith('../') || normalized.includes('/../'))
|
|
@@ -68,7 +67,7 @@ export class TestStubFixer {
|
|
|
68
67
|
catch {
|
|
69
68
|
continue;
|
|
70
69
|
}
|
|
71
|
-
const absTestPath =
|
|
70
|
+
const absTestPath = joinPath(context.workdir, testRelPath);
|
|
72
71
|
if (await this.fs.exists(absTestPath))
|
|
73
72
|
continue;
|
|
74
73
|
// Export discovery is intentionally omitted — pass empty exports array.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/host/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"builtins.d.ts","sourceRoot":"","sources":["../../src/host/builtins.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAsB7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD,4DAA4D;AAC5D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,eAAe,GAAG,IAAI,CAwBvF"}
|
package/dist/host/builtins.js
CHANGED
|
@@ -5,6 +5,7 @@ import { ForbiddenImportEvaluator } from '../evaluators/forbidden-import-evaluat
|
|
|
5
5
|
import { ImportBoundaryEvaluator } from '../evaluators/import-boundary-evaluator.js';
|
|
6
6
|
import { PathEvaluator } from '../evaluators/path-evaluator.js';
|
|
7
7
|
import { RegexEvaluator } from '../evaluators/regex-evaluator.js';
|
|
8
|
+
import { RipgrepEvaluator } from '../evaluators/ripgrep-evaluator.js';
|
|
8
9
|
import { SchemaArtifactEvaluator } from '../evaluators/schema-artifact-evaluator.js';
|
|
9
10
|
import { SecretsScannerEvaluator } from '../evaluators/secrets-scanner-evaluator.js';
|
|
10
11
|
import { SgEvaluator } from '../evaluators/sg-evaluator.js';
|
|
@@ -15,10 +16,11 @@ import { TextFormatter } from '../formatters/text.js';
|
|
|
15
16
|
import { GoTestPathResolver, PythonTestPathResolver, RustTestPathResolver, TypeScriptTestPathResolver, } from '../resolvers/test-path-resolver.js';
|
|
16
17
|
/** Register bundled evaluators and formatters on a host. */
|
|
17
18
|
export function registerBuiltins(host, executor) {
|
|
18
|
-
const regex = new RegexEvaluator();
|
|
19
19
|
const path = new PathEvaluator();
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
// `regex` = JS RegExp engine; `rg` = real ripgrep engine (ReDoS-immune, prunes during
|
|
21
|
+
// traversal). The two are honestly named distinct engines, not aliases.
|
|
22
|
+
host.evaluators.register('regex', new RegexEvaluator(), 'builtin');
|
|
23
|
+
host.evaluators.register('rg', new RipgrepEvaluator(executor), 'builtin');
|
|
22
24
|
host.evaluators.register('path', path, 'builtin');
|
|
23
25
|
host.evaluators.register('file-exist', path, 'builtin');
|
|
24
26
|
host.evaluators.register('forbidden-import', new ForbiddenImportEvaluator(), 'builtin');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Resolve the absolute path to the rule presets bundled with
|
|
3
3
|
* `@gobing-ai/ts-rule-engine`.
|
|
4
4
|
*
|
|
5
|
-
* The directory ships
|
|
5
|
+
* The directory ships a generic example preset and category
|
|
6
6
|
* folders (`typescript`, `structure`, `quality`) so a consumer gets a working
|
|
7
7
|
* default ruleset without authoring any files. Pass the returned path as the
|
|
8
8
|
* lowest-priority entry to {@link loadPreset}'s `roots`, letting project-local
|
|
@@ -12,7 +12,7 @@ const defaultFs = new NodeSyncFileSystem();
|
|
|
12
12
|
* Resolve the absolute path to the rule presets bundled with
|
|
13
13
|
* `@gobing-ai/ts-rule-engine`.
|
|
14
14
|
*
|
|
15
|
-
* The directory ships
|
|
15
|
+
* The directory ships a generic example preset and category
|
|
16
16
|
* folders (`typescript`, `structure`, `quality`) so a consumer gets a working
|
|
17
17
|
* default ruleset without authoring any files. Pass the returned path as the
|
|
18
18
|
* lowest-priority entry to {@link loadPreset}'s `roots`, letting project-local
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { CapabilityRegistry } from '@gobing-ai/ts-runtime/plugin';
|
|
1
2
|
import type { TestPathResolver } from '../resolvers/test-path-resolver';
|
|
2
3
|
import type { ResultFormatter, RuleEvaluator } from '../types';
|
|
3
|
-
import { CapabilityRegistry } from './capability-registry';
|
|
4
4
|
/** Host container for rule-engine capabilities. */
|
|
5
5
|
export declare class RuleEngineHost {
|
|
6
6
|
/** Evaluator registry keyed by evaluator type. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-engine-host.d.ts","sourceRoot":"","sources":["../../src/host/rule-engine-host.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"rule-engine-host.d.ts","sourceRoot":"","sources":["../../src/host/rule-engine-host.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAClE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE/D,mDAAmD;AACnD,qBAAa,cAAc;IACvB,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,aAAa,CAAC,CAAC;IACvD,kDAAkD;IAClD,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,eAAe,CAAC,CAAC;IACzD,0DAA0D;IAC1D,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;;CAO5D"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
export { type CapabilityEntry, type CapabilityOrigin, CapabilityRegistry } from '@gobing-ai/ts-runtime/plugin';
|
|
1
2
|
export * from './config/extensions';
|
|
2
3
|
export * from './config/loader';
|
|
3
4
|
export * from './engine';
|
|
5
|
+
export { isRipgrepCompatiblePattern } from './evaluators/ripgrep-evaluator';
|
|
6
|
+
export type { RuleEngineEvents } from './events';
|
|
4
7
|
export * from './fixers/fixers';
|
|
5
8
|
export * from './fixers/test-stub-fixer';
|
|
6
9
|
export * from './formatters/json';
|
|
7
10
|
export * from './formatters/text';
|
|
8
11
|
export * from './host/bundled-rules';
|
|
9
|
-
export * from './host/capability-registry';
|
|
10
12
|
export * from './host/rule-engine-host';
|
|
11
13
|
export * from './resolvers/test-path-resolver';
|
|
12
14
|
export * from './types';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,eAAe,EAAE,KAAK,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAC/G,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AAGzB,OAAO,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAC;AAC5E,YAAY,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACjD,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gCAAgC,CAAC;AAC/C,cAAc,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
// Public re-export of the shared plugin registry (ADR-010). Sourced directly from
|
|
2
|
+
// the shared plugin core; the former host/capability-registry.ts shim was removed
|
|
3
|
+
// once the one-release back-compat window closed.
|
|
4
|
+
export { CapabilityRegistry } from '@gobing-ai/ts-runtime/plugin';
|
|
1
5
|
export * from './config/extensions.js';
|
|
2
6
|
export * from './config/loader.js';
|
|
3
7
|
export * from './engine.js';
|
|
8
|
+
// Public dialect helper for the `rg` (ripgrep) evaluator — consumed by the downstream
|
|
9
|
+
// rule-file converter and the rg-dialect rule to keep JS-only patterns off the `rg` type.
|
|
10
|
+
export { isRipgrepCompatiblePattern } from './evaluators/ripgrep-evaluator.js';
|
|
4
11
|
export * from './fixers/fixers.js';
|
|
5
12
|
export * from './fixers/test-stub-fixer.js';
|
|
6
13
|
export * from './formatters/json.js';
|
|
7
14
|
export * from './formatters/text.js';
|
|
8
15
|
export * from './host/bundled-rules.js';
|
|
9
|
-
export * from './host/capability-registry.js';
|
|
10
16
|
export * from './host/rule-engine-host.js';
|
|
11
17
|
export * from './resolvers/test-path-resolver.js';
|
|
12
18
|
export * from './types.js';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
/** Finding severity emitted by the rule engine. */
|
|
3
3
|
export type RuleSeverity = 'error' | 'warning' | 'info';
|
|
4
|
+
/** Numeric rank for severity comparison; higher = more severe. */
|
|
5
|
+
export declare const SEVERITY_RANK: Record<RuleSeverity, number>;
|
|
4
6
|
/** Fix authority level for candidate fixes. */
|
|
5
7
|
export type FixMode = 'none' | 'suggest' | 'auto';
|
|
6
8
|
/** Structured configuration for a rule evaluator. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { assertRelativeExtensionPath } from '@gobing-ai/ts-runtime/plugin';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
/** Numeric rank for severity comparison; higher = more severe. */
|
|
4
|
+
export const SEVERITY_RANK = {
|
|
5
|
+
error: 3,
|
|
6
|
+
warning: 2,
|
|
7
|
+
info: 1,
|
|
8
|
+
};
|
|
3
9
|
/** Create a finding with inherited rule severity. */
|
|
4
10
|
export function createFinding(rule, message, filePath, extras = {}) {
|
|
5
11
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gobing-ai/ts-rule-engine",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "@gobing-ai/ts-rule-engine — Constraint rule schemas, loading, evaluation, and result formatting.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -49,10 +49,9 @@
|
|
|
49
49
|
"release": "echo 'Manual publish is disabled. Releases go through GitHub Actions via Trusted Publishing — push a tag: git tag @gobing-ai/ts-rule-engine-v<version> && git push --tags' && exit 1"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@gobing-ai/ts-ai-runner": "^0.3.
|
|
53
|
-
"@gobing-ai/ts-
|
|
54
|
-
"@gobing-ai/ts-runtime": "^0.3.
|
|
55
|
-
"@gobing-ai/ts-utils": "^0.3.1",
|
|
52
|
+
"@gobing-ai/ts-ai-runner": "^0.3.3",
|
|
53
|
+
"@gobing-ai/ts-infra": "^0.3.3",
|
|
54
|
+
"@gobing-ai/ts-runtime": "^0.3.3",
|
|
56
55
|
"yaml": "^2.7.0",
|
|
57
56
|
"zod": "^4.1.0"
|
|
58
57
|
},
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Example preset — demonstrates the extends mechanism by composing all three
|
|
2
|
+
# bundled rule categories (typescript, quality, structure).
|
|
3
|
+
#
|
|
4
|
+
# Load with: loadPreset('example', { roots: [bundledRulesRoot()] })
|
|
5
|
+
#
|
|
6
|
+
# Bundled with @gobing-ai/ts-rule-engine. A project may override individual rule
|
|
7
|
+
# files by placing same-named files under a local rules root that shadows the
|
|
8
|
+
# bundled copy.
|
|
9
|
+
name: example
|
|
10
|
+
extends:
|
|
11
|
+
- typescript
|
|
12
|
+
- quality
|
|
13
|
+
- structure
|
package/src/config/extensions.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { fileURLToPath } from 'node:url';
|
|
2
|
+
import type { Logger } from '@gobing-ai/ts-infra';
|
|
3
|
+
import { basenamePath, dirnamePath, resolvePath, SEP } from '@gobing-ai/ts-runtime';
|
|
4
|
+
import type {
|
|
5
|
+
ExtensionRef as SharedExtensionRef,
|
|
6
|
+
LoadExtensionsOptions as SharedLoadExtensionsOptions,
|
|
7
|
+
} from '@gobing-ai/ts-runtime/plugin';
|
|
8
|
+
import { loadExtensionModules } from '@gobing-ai/ts-runtime/plugin';
|
|
2
9
|
import type { RuleEngineHost } from '../host/rule-engine-host';
|
|
3
|
-
|
|
4
10
|
/** A capability kind a preset extension can contribute. */
|
|
5
11
|
export type ExtensionKind = 'resolvers' | 'evaluators' | 'fixers' | 'formatters';
|
|
6
12
|
|
|
@@ -23,7 +29,7 @@ export interface LoadExtensionsOptions {
|
|
|
23
29
|
*/
|
|
24
30
|
allowExtensions?: boolean;
|
|
25
31
|
/** Optional sink for non-fatal warnings (e.g. built-in overrides). */
|
|
26
|
-
logger?:
|
|
32
|
+
logger?: Pick<Logger, 'warn'>;
|
|
27
33
|
/** Optional module loader seam for tests or embedders with custom import policy. */
|
|
28
34
|
moduleLoader?: (absPath: string) => Promise<Record<string, unknown>>;
|
|
29
35
|
}
|
|
@@ -51,7 +57,7 @@ export function collectExtensions(
|
|
|
51
57
|
const refs: ExtensionRef[] = [];
|
|
52
58
|
for (const kind of ['resolvers', 'evaluators', 'fixers', 'formatters'] as ExtensionKind[]) {
|
|
53
59
|
for (const path of extensions[kind] ?? []) {
|
|
54
|
-
refs.push({ kind, presetName: sourceName, absPath:
|
|
60
|
+
refs.push({ kind, presetName: sourceName, absPath: resolvePath(sourceDir, path) });
|
|
55
61
|
}
|
|
56
62
|
}
|
|
57
63
|
return refs;
|
|
@@ -61,13 +67,17 @@ export function collectExtensions(
|
|
|
61
67
|
* Import each extension module and register its export on the matching host
|
|
62
68
|
* registry.
|
|
63
69
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
70
|
+
* Delegates generic loading (trust gate, path guard, module import, export-shape
|
|
71
|
+
* validation) to the shared ``loadExtensionModules`` from ts-runtime/plugin, then
|
|
72
|
+
* routes each capability to the correct host registry based on ``ref.kind``.
|
|
73
|
+
*
|
|
74
|
+
* A module must default-export (or named-export ``extension``) an object with a
|
|
75
|
+
* ``name: string`` and the capability implementation. Loading is gated by
|
|
66
76
|
* {@link LoadExtensionsOptions.allowExtensions}; when refs are present but
|
|
67
77
|
* loading is not allowed, this throws so the requirement is never silently dropped.
|
|
68
78
|
*
|
|
69
|
-
* @throws When extensions are present but
|
|
70
|
-
* a module cannot be imported or lacks a valid
|
|
79
|
+
* @throws When extensions are present but ``allowExtensions`` is not true, or when
|
|
80
|
+
* a module cannot be imported or lacks a valid ``name``.
|
|
71
81
|
*/
|
|
72
82
|
export async function loadExtensionsIntoHost(
|
|
73
83
|
host: RuleEngineHost,
|
|
@@ -75,40 +85,59 @@ export async function loadExtensionsIntoHost(
|
|
|
75
85
|
options: LoadExtensionsOptions = {},
|
|
76
86
|
): Promise<void> {
|
|
77
87
|
if (refs.length === 0) return;
|
|
78
|
-
if (options.allowExtensions !== true) {
|
|
79
|
-
const first = refs[0] as ExtensionRef;
|
|
80
|
-
throw new Error(
|
|
81
|
-
`preset "${first.presetName}" declares ${first.kind} extension "${first.absPath}", but extensions are disabled — pass allowExtensions: true to load preset extension modules`,
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
// Normalize file:// URLs to file paths so dirname/basename produce valid
|
|
90
|
+
// path components that isAbsolute() accepts (e.g. import.meta.url in tests).
|
|
91
|
+
const toFilePath = (p: string): string => (p.startsWith('file://') ? fileURLToPath(p) : p);
|
|
92
|
+
|
|
93
|
+
// Defense-in-depth: pre-validate `..` traversal in caller-supplied absPath
|
|
94
|
+
// before the basename adaptation strips it. The shared loader's
|
|
95
|
+
// assertRelativeExtensionPath also runs on the derived (basename) path.
|
|
86
96
|
for (const ref of refs) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
candidate === null ||
|
|
91
|
-
typeof candidate !== 'object' ||
|
|
92
|
-
typeof (candidate as { name?: unknown }).name !== 'string'
|
|
93
|
-
) {
|
|
97
|
+
const segments = toFilePath(ref.absPath).split(SEP);
|
|
98
|
+
if (segments.includes('..')) {
|
|
94
99
|
throw new Error(
|
|
95
|
-
`
|
|
100
|
+
`extension path "${ref.absPath}" declared by "${ref.presetName}" must not contain ".." traversal`,
|
|
96
101
|
);
|
|
97
102
|
}
|
|
98
|
-
|
|
99
|
-
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Adapt rule-engine ExtensionRef → shared ExtensionRef so the generic loader
|
|
106
|
+
// governs every import. The shared loader resolves (baseDir, path) →
|
|
107
|
+
// absPath internally; we supply dirname/basename so the resolved path
|
|
108
|
+
// reconstructs the caller's original absPath.
|
|
109
|
+
const sharedRefs: SharedExtensionRef<ExtensionKind>[] = refs.map((ref) => {
|
|
110
|
+
const fp = toFilePath(ref.absPath);
|
|
111
|
+
return {
|
|
112
|
+
kind: ref.kind,
|
|
113
|
+
path: `./${basenamePath(fp)}`,
|
|
114
|
+
baseDir: dirnamePath(fp),
|
|
115
|
+
sourceName: ref.presetName,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const moduleLoader = options.moduleLoader ?? defaultModuleLoader;
|
|
120
|
+
const sharedOptions: SharedLoadExtensionsOptions = {
|
|
121
|
+
allowExtensions: options.allowExtensions,
|
|
122
|
+
logger: options.logger,
|
|
123
|
+
moduleLoader,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
await loadExtensionModules<ExtensionKind>(sharedRefs, sharedOptions, async (sharedRef, extension) => {
|
|
127
|
+
const name = extension.name as string;
|
|
128
|
+
const registryKey = HOST_REGISTRY_BY_KIND[sharedRef.kind];
|
|
100
129
|
if (registryKey === undefined) {
|
|
101
|
-
throw new Error(`
|
|
130
|
+
throw new Error(`"${sharedRef.sourceName}" ${sharedRef.kind} extensions are not supported`);
|
|
102
131
|
}
|
|
103
132
|
const registry = host[registryKey] as unknown as {
|
|
104
133
|
register: (name: string, impl: unknown, origin: 'builtin' | 'extension') => void;
|
|
105
134
|
has?: (name: string) => boolean;
|
|
106
135
|
};
|
|
107
136
|
if (options.logger && registry.has?.(name)) {
|
|
108
|
-
options.logger.warn(`
|
|
137
|
+
options.logger.warn(`"${sharedRef.sourceName}" ${sharedRef.kind} extension overrides existing "${name}"`);
|
|
109
138
|
}
|
|
110
|
-
registry.register(name,
|
|
111
|
-
}
|
|
139
|
+
registry.register(name, extension, 'extension');
|
|
140
|
+
});
|
|
112
141
|
}
|
|
113
142
|
|
|
114
143
|
async function defaultModuleLoader(absPath: string): Promise<Record<string, unknown>> {
|
package/src/config/loader.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
basenamePath,
|
|
3
|
+
dirnamePath,
|
|
4
|
+
joinPath,
|
|
5
|
+
loadStructuredConfig,
|
|
6
|
+
NodeFileSystem,
|
|
7
|
+
relativePath,
|
|
8
|
+
resolvePath,
|
|
9
|
+
SEP,
|
|
10
|
+
} from '@gobing-ai/ts-runtime';
|
|
3
11
|
import {
|
|
4
12
|
type ConstraintRule,
|
|
5
13
|
type ConstraintRuleFile,
|
|
@@ -27,6 +35,7 @@ export interface RuleLoaderOptions {
|
|
|
27
35
|
fetch?: (input: string) => Promise<Response>;
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
/** Options for loading a single rule file directly from disk. */
|
|
30
39
|
export interface RuleFileLoadOptions {
|
|
31
40
|
/** When true, honor top-level `$schema` refs. Defaults to true. */
|
|
32
41
|
validateSchema?: boolean;
|
|
@@ -58,12 +67,12 @@ interface MergedRoots {
|
|
|
58
67
|
* the rest of a preset's categories from the lower-priority roots.
|
|
59
68
|
*/
|
|
60
69
|
export async function loadPreset(name: string, options: RuleLoaderOptions): Promise<LoadedPreset> {
|
|
61
|
-
const merged = await buildMergedRoots(options.roots.map((root) =>
|
|
70
|
+
const merged = await buildMergedRoots(options.roots.map((root) => resolvePath(root)));
|
|
62
71
|
const presetPath = findMergedPreset(merged, name);
|
|
63
72
|
if (presetPath === null) return { rules: [], extensions: [] };
|
|
64
73
|
const preset = PresetDefinitionSchema.parse(await readStructuredFile(presetPath, options)) as PresetDefinition;
|
|
65
74
|
const rules: ConstraintRule[] = [];
|
|
66
|
-
const extensions = collectExtensions(preset.name,
|
|
75
|
+
const extensions = collectExtensions(preset.name, dirnamePath(presetPath), preset.extensions);
|
|
67
76
|
for (const entry of preset.extends) {
|
|
68
77
|
const loaded = await loadPresetEntry(merged, entry, new Set([name]), options);
|
|
69
78
|
rules.push(...loaded.rules);
|
|
@@ -111,6 +120,12 @@ function assertFixModeNotPromoted(presetName: string, rule: ConstraintRule, over
|
|
|
111
120
|
}
|
|
112
121
|
}
|
|
113
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Load a preset's resolved rules (convenience wrapper).
|
|
125
|
+
*
|
|
126
|
+
* Shortcut for `(await loadPreset(name, options)).rules` — returns only the
|
|
127
|
+
* normalized rule set without the extension module refs.
|
|
128
|
+
*/
|
|
114
129
|
export async function loadPresetRules(name: string, options: RuleLoaderOptions): Promise<ConstraintRule[]> {
|
|
115
130
|
return (await loadPreset(name, options)).rules;
|
|
116
131
|
}
|
|
@@ -125,12 +140,12 @@ export async function loadPresetRules(name: string, options: RuleLoaderOptions):
|
|
|
125
140
|
* object, not a `rules:` array) cannot declare extensions and yields `extensions: []`.
|
|
126
141
|
*/
|
|
127
142
|
export async function loadRuleFile(filePath: string, options: RuleFileLoadOptions = {}): Promise<LoadedPreset> {
|
|
128
|
-
const resolved =
|
|
143
|
+
const resolved = resolvePath(filePath);
|
|
129
144
|
const raw = await readStructuredFile(resolved, options);
|
|
130
145
|
const rules = normalizeRuleFile(raw, resolved);
|
|
131
146
|
const parsed = ConstraintRuleFileSchema.safeParse(raw);
|
|
132
147
|
const extensions = parsed.success
|
|
133
|
-
? collectExtensions(
|
|
148
|
+
? collectExtensions(basenamePath(resolved), dirnamePath(resolved), parsed.data.extensions)
|
|
134
149
|
: [];
|
|
135
150
|
return { rules, extensions };
|
|
136
151
|
}
|
|
@@ -151,7 +166,7 @@ async function loadPresetEntry(
|
|
|
151
166
|
const preset = PresetDefinitionSchema.safeParse(await readStructuredFile(presetPath, options));
|
|
152
167
|
if (preset.success) {
|
|
153
168
|
const rules: ConstraintRule[] = [];
|
|
154
|
-
const extensions = collectExtensions(preset.data.name,
|
|
169
|
+
const extensions = collectExtensions(preset.data.name, dirnamePath(presetPath), preset.data.extensions);
|
|
155
170
|
for (const child of preset.data.extends) {
|
|
156
171
|
const loaded = await loadPresetEntry(merged, child, nextSeen, options);
|
|
157
172
|
rules.push(...loaded.rules);
|
|
@@ -196,7 +211,7 @@ async function buildMergedRoots(roots: readonly string[]): Promise<MergedRoots>
|
|
|
196
211
|
const categories = new Set<string>();
|
|
197
212
|
for (const root of roots) {
|
|
198
213
|
for (const absPath of await walkYamlFiles(fs, root)) {
|
|
199
|
-
const relPath =
|
|
214
|
+
const relPath = relativePath(root, absPath).split(SEP).join('/');
|
|
200
215
|
const slashIdx = relPath.indexOf('/');
|
|
201
216
|
if (slashIdx > 0) categories.add(relPath.slice(0, slashIdx));
|
|
202
217
|
if (!files.has(relPath)) files.set(relPath, absPath);
|
|
@@ -249,7 +264,7 @@ async function walkYamlFiles(fs: NodeFileSystem, dir: string, depth = 0): Promis
|
|
|
249
264
|
const acc: string[] = [];
|
|
250
265
|
for (const entry of (await fs.readDir(dir)).sort()) {
|
|
251
266
|
if (depth === 0 && entry === 'presets') continue;
|
|
252
|
-
const fullPath =
|
|
267
|
+
const fullPath = joinPath(dir, entry);
|
|
253
268
|
const entryStat = await fs.stat(fullPath);
|
|
254
269
|
if (entryStat?.isDirectory()) {
|
|
255
270
|
acc.push(...(await walkYamlFiles(fs, fullPath, depth + 1)));
|
|
@@ -267,7 +282,7 @@ async function listImmediateDirs(fs: NodeFileSystem, dir: string): Promise<strin
|
|
|
267
282
|
const dirs: string[] = [];
|
|
268
283
|
for (const entry of await fs.readDir(dir)) {
|
|
269
284
|
if (entry === 'presets') continue;
|
|
270
|
-
const entryStat = await fs.stat(
|
|
285
|
+
const entryStat = await fs.stat(joinPath(dir, entry));
|
|
271
286
|
if (entryStat?.isDirectory()) dirs.push(entry);
|
|
272
287
|
}
|
|
273
288
|
return dirs;
|
|
@@ -287,7 +302,7 @@ async function readStructuredFile(
|
|
|
287
302
|
type ParsedRule = Omit<ConstraintRule, 'severity'> & { severity?: ConstraintRule['severity'] };
|
|
288
303
|
|
|
289
304
|
function normalizeRuleFile(raw: unknown, filePath: string): ConstraintRule[] {
|
|
290
|
-
const sourceDir =
|
|
305
|
+
const sourceDir = dirnamePath(filePath);
|
|
291
306
|
const maybeFile = ConstraintRuleFileSchema.safeParse(raw);
|
|
292
307
|
if (maybeFile.success) return normalizeFileRules(maybeFile.data, sourceDir);
|
|
293
308
|
const maybeRule = ConstraintRuleSchema.safeParse(raw);
|
|
@@ -297,7 +312,7 @@ function normalizeRuleFile(raw: unknown, filePath: string): ConstraintRule[] {
|
|
|
297
312
|
// single-rule schema. Include field paths so the offending key is obvious.
|
|
298
313
|
const isRuleFileShape = typeof raw === 'object' && raw !== null && 'rules' in raw;
|
|
299
314
|
const issues = (isRuleFileShape ? maybeFile.error : maybeRule.error).issues;
|
|
300
|
-
throw new Error(`Invalid rule file "${
|
|
315
|
+
throw new Error(`Invalid rule file "${basenamePath(filePath)}": ${formatIssues(issues)}`);
|
|
301
316
|
}
|
|
302
317
|
|
|
303
318
|
/** Render Zod issues as `path: message` fragments for actionable diagnostics. */
|