@alex-dbs/coderuler 0.0.0-stage → 1.3.1
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/LICENSE +175 -0
- package/README.md +218 -2
- package/dist/adapter/cli/commands/check.d.ts +3 -0
- package/dist/adapter/cli/commands/check.js +93 -0
- package/dist/adapter/cli/commands/deps.d.ts +3 -0
- package/dist/adapter/cli/commands/deps.js +4 -0
- package/dist/adapter/cli/commands/inspect.d.ts +3 -0
- package/dist/adapter/cli/commands/inspect.js +59 -0
- package/dist/adapter/cli/commands/rules.d.ts +10 -0
- package/dist/adapter/cli/commands/rules.js +17 -0
- package/dist/adapter/cli/commands/skill.d.ts +4 -0
- package/dist/adapter/cli/commands/skill.js +7 -0
- package/dist/adapter/cli/commands/test.d.ts +3 -0
- package/dist/adapter/cli/commands/test.js +78 -0
- package/dist/adapter/cli/commands/update.d.ts +3 -0
- package/dist/adapter/cli/commands/update.js +7 -0
- package/dist/adapter/cli/commands/validate.d.ts +3 -0
- package/dist/adapter/cli/commands/validate.js +12 -0
- package/dist/adapter/cli/exit.d.ts +7 -0
- package/dist/adapter/cli/exit.js +10 -0
- package/dist/adapter/cli/inspect-json.d.ts +9 -0
- package/dist/adapter/cli/inspect-json.js +160 -0
- package/dist/adapter/cli/inspect-usage.d.ts +9 -0
- package/dist/adapter/cli/inspect-usage.js +14 -0
- package/dist/adapter/cli/program.d.ts +4 -0
- package/dist/adapter/cli/program.js +216 -0
- package/dist/adapter/cli/progress.d.ts +22 -0
- package/dist/adapter/cli/progress.js +79 -0
- package/dist/adapter/cli/reports.d.ts +47 -0
- package/dist/adapter/cli/reports.js +735 -0
- package/dist/adapter/cli/self-update.d.ts +1 -0
- package/dist/adapter/cli/self-update.js +89 -0
- package/dist/adapter/cli/services.d.ts +101 -0
- package/dist/adapter/cli/services.js +1 -0
- package/dist/adapter/cli/text.d.ts +5 -0
- package/dist/adapter/cli/text.js +36 -0
- package/dist/adapter/cli/version.d.ts +1 -0
- package/dist/adapter/cli/version.js +3 -0
- package/dist/adapter/decision/jev.d.ts +11 -0
- package/dist/adapter/decision/jev.js +138 -0
- package/dist/adapter/filesystem/dependencies.d.ts +26 -0
- package/dist/adapter/filesystem/dependencies.js +577 -0
- package/dist/adapter/filesystem/dependency-installation.d.ts +11 -0
- package/dist/adapter/filesystem/dependency-installation.js +373 -0
- package/dist/adapter/filesystem/project.d.ts +14 -0
- package/dist/adapter/filesystem/project.js +387 -0
- package/dist/adapter/filesystem/rule-resolution.d.ts +4 -0
- package/dist/adapter/filesystem/rule-resolution.js +516 -0
- package/dist/adapter/filesystem/rule-test-worker.d.ts +17 -0
- package/dist/adapter/filesystem/rule-test-worker.js +47 -0
- package/dist/adapter/filesystem/rule-tests.d.ts +3 -0
- package/dist/adapter/filesystem/rule-tests.js +264 -0
- package/dist/adapter/filesystem/rules.d.ts +13 -0
- package/dist/adapter/filesystem/rules.js +242 -0
- package/dist/adapter/git/changes.d.ts +1 -0
- package/dist/adapter/git/changes.js +54 -0
- package/dist/adapter/git/environment.d.ts +1 -0
- package/dist/adapter/git/environment.js +10 -0
- package/dist/adapter/github/api.d.ts +5 -0
- package/dist/adapter/github/api.js +40 -0
- package/dist/adapter/github/comments.d.ts +3 -0
- package/dist/adapter/github/comments.js +235 -0
- package/dist/adapter/github/pull-request.d.ts +16 -0
- package/dist/adapter/github/pull-request.js +139 -0
- package/dist/adapter/scope/astgrep-languages.d.ts +5 -0
- package/dist/adapter/scope/astgrep-languages.js +119 -0
- package/dist/adapter/scope/astgrep.d.ts +14 -0
- package/dist/adapter/scope/astgrep.js +183 -0
- package/dist/adapter/scope/csv.d.ts +4 -0
- package/dist/adapter/scope/csv.js +158 -0
- package/dist/adapter/scope/file.d.ts +2 -0
- package/dist/adapter/scope/file.js +4 -0
- package/dist/adapter/scope/json.d.ts +7 -0
- package/dist/adapter/scope/json.js +132 -0
- package/dist/adapter/scope/lines.d.ts +10 -0
- package/dist/adapter/scope/lines.js +42 -0
- package/dist/adapter/scope/protobuf-options.d.ts +29 -0
- package/dist/adapter/scope/protobuf-options.js +64 -0
- package/dist/adapter/scope/protobuf-parser.d.ts +30 -0
- package/dist/adapter/scope/protobuf-parser.js +772 -0
- package/dist/adapter/scope/protobuf.d.ts +5 -0
- package/dist/adapter/scope/protobuf.js +53 -0
- package/dist/adapter/scope/structured.d.ts +10 -0
- package/dist/adapter/scope/structured.js +84 -0
- package/dist/adapter/scope/yaml.d.ts +7 -0
- package/dist/adapter/scope/yaml.js +224 -0
- package/dist/adapter/skills/cli.d.ts +10 -0
- package/dist/adapter/skills/cli.js +77 -0
- package/dist/app/concurrency.d.ts +1 -0
- package/dist/app/concurrency.js +44 -0
- package/dist/app/evaluators.d.ts +14 -0
- package/dist/app/evaluators.js +175 -0
- package/dist/app/execute.d.ts +2 -0
- package/dist/app/execute.js +427 -0
- package/dist/app/jsonpath.d.ts +1 -0
- package/dist/app/jsonpath.js +8 -0
- package/dist/app/ports.d.ts +165 -0
- package/dist/app/ports.js +1 -0
- package/dist/app/rule-tests.d.ts +20 -0
- package/dist/app/rule-tests.js +11 -0
- package/dist/app/rule.d.ts +121 -0
- package/dist/app/rule.js +24 -0
- package/dist/app/selection.d.ts +2 -0
- package/dist/app/selection.js +23 -0
- package/dist/app/usage.d.ts +2 -0
- package/dist/app/usage.js +13 -0
- package/dist/app/validate.d.ts +11 -0
- package/dist/app/validate.js +415 -0
- package/dist/app/violation-comments.d.ts +10 -0
- package/dist/app/violation-comments.js +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +3 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +11 -0
- package/dist/infra/runtime.d.ts +3 -0
- package/dist/infra/runtime.js +168 -0
- package/dist/infra/settings.d.ts +5 -0
- package/dist/infra/settings.js +91 -0
- package/package.json +86 -4
- package/skills/coderuler-new-rule/SKILL.md +141 -0
- package/skills/coderuler-new-rule/reference.md +447 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { SourceLines } from './lines.js';
|
|
2
|
+
import { DECLARATION_LISTS, readProtoFile } from './protobuf-parser.js';
|
|
3
|
+
import { pointerSegments, query, validatePath } from './structured.js';
|
|
4
|
+
const DEFAULT_PATH = '$.rpcs[*]';
|
|
5
|
+
function resolvePath(options) {
|
|
6
|
+
const path = options?.path === undefined ? DEFAULT_PATH : options.path;
|
|
7
|
+
validatePath(path, 'protobuf scope option path');
|
|
8
|
+
return path;
|
|
9
|
+
}
|
|
10
|
+
function isDeclarationList(value) {
|
|
11
|
+
return DECLARATION_LISTS.includes(value);
|
|
12
|
+
}
|
|
13
|
+
function scopeId(document, pointer) {
|
|
14
|
+
if (pointer === '')
|
|
15
|
+
return '$';
|
|
16
|
+
const segments = pointerSegments(pointer);
|
|
17
|
+
const list = segments[0];
|
|
18
|
+
if (list === undefined || !isDeclarationList(list))
|
|
19
|
+
return pointer;
|
|
20
|
+
const index = Number(segments[1]);
|
|
21
|
+
const declaration = Number.isInteger(index) ? document[list][index] : undefined;
|
|
22
|
+
if (declaration === undefined)
|
|
23
|
+
return pointer;
|
|
24
|
+
const declarationPointer = `/${segments[0]}/${segments[1]}`;
|
|
25
|
+
return segments.length === 2 ? declaration.id : `${declaration.id}:${pointer.slice(declarationPointer.length)}`;
|
|
26
|
+
}
|
|
27
|
+
function spanAt(spans, pointer) {
|
|
28
|
+
let candidate = pointer;
|
|
29
|
+
while (true) {
|
|
30
|
+
const span = spans.get(candidate);
|
|
31
|
+
if (span !== undefined)
|
|
32
|
+
return span;
|
|
33
|
+
const slash = candidate.lastIndexOf('/');
|
|
34
|
+
candidate = slash <= 0 ? '' : candidate.slice(0, slash);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function protobuf(options) {
|
|
38
|
+
const path = resolvePath(options);
|
|
39
|
+
return (file) => {
|
|
40
|
+
const parsed = readProtoFile(file.source);
|
|
41
|
+
const matches = query(parsed.data, path);
|
|
42
|
+
const lines = new SourceLines(file.source);
|
|
43
|
+
return matches.map(({ pointer, value }) => {
|
|
44
|
+
const span = spanAt(parsed.spans, pointer);
|
|
45
|
+
return {
|
|
46
|
+
id: scopeId(parsed.data, pointer),
|
|
47
|
+
source: file.source.slice(span.start, span.end),
|
|
48
|
+
data: value,
|
|
49
|
+
lines: lines.span(span.start, span.end),
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { JsonValue } from '../../app/rule.js';
|
|
2
|
+
export interface QueryMatch {
|
|
3
|
+
readonly pointer: string;
|
|
4
|
+
readonly value: JsonValue;
|
|
5
|
+
}
|
|
6
|
+
export declare function validatePath(path: string, label: string): void;
|
|
7
|
+
export declare function query(data: JsonValue, path: string): readonly QueryMatch[];
|
|
8
|
+
export declare function pointerSegment(key: string): string;
|
|
9
|
+
export declare function pointerSegments(pointer: string): readonly string[];
|
|
10
|
+
export declare function keyOf(data: JsonValue, path: string): string;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { JSONPath, JSONPathClass } from 'jsonpath-plus';
|
|
2
|
+
import { assertNonExecutable } from '../../app/jsonpath.js';
|
|
3
|
+
class SourcePointerJSONPath extends JSONPathClass {
|
|
4
|
+
_getPreferredOutput(match) {
|
|
5
|
+
const pointer = Array.isArray(match.path) && match.parentProperty !== null ? pointerOf(match.path) : undefined;
|
|
6
|
+
super._getPreferredOutput(match);
|
|
7
|
+
if (pointer !== undefined)
|
|
8
|
+
match.pointer = pointer;
|
|
9
|
+
return match;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const PATH_PROBE = { a: { b: { c: 0 } }, items: [{ b: 0 }], list: [0] };
|
|
13
|
+
export function validatePath(path, label) {
|
|
14
|
+
if (typeof path !== 'string' || path === '') {
|
|
15
|
+
throw new Error(`The ${label} must be a non-empty string`);
|
|
16
|
+
}
|
|
17
|
+
if (!path.startsWith('$'))
|
|
18
|
+
throw new Error(`The ${label} must start with "$"`);
|
|
19
|
+
assertNonExecutable(path, label);
|
|
20
|
+
try {
|
|
21
|
+
JSONPath({ path, json: PATH_PROBE, eval: false });
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
25
|
+
throw new Error(`The ${label} "${path}" could not be evaluated: ${reason}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function query(data, path) {
|
|
29
|
+
if (path.trim() === '$')
|
|
30
|
+
return [{ pointer: '', value: data }];
|
|
31
|
+
let found;
|
|
32
|
+
try {
|
|
33
|
+
const engine = new SourcePointerJSONPath({ autostart: false, resultType: 'all', eval: false });
|
|
34
|
+
found = engine.evaluate({ path, json: data, wrap: true });
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
38
|
+
throw new Error(`The scope path "${path}" could not be evaluated: ${reason}`);
|
|
39
|
+
}
|
|
40
|
+
if (!Array.isArray(found))
|
|
41
|
+
return [];
|
|
42
|
+
return found.map((entry) => {
|
|
43
|
+
const { value, pointer } = entry;
|
|
44
|
+
if (typeof pointer !== 'string') {
|
|
45
|
+
throw new Error(`The scope path "${path}" produced a match without a JSON pointer`);
|
|
46
|
+
}
|
|
47
|
+
return { pointer, value: value };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function pointerSegment(key) {
|
|
51
|
+
return key.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
52
|
+
}
|
|
53
|
+
function pointerOf(path) {
|
|
54
|
+
let pointer = '';
|
|
55
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
56
|
+
pointer += `/${pointerSegment(String(path[index]))}`;
|
|
57
|
+
}
|
|
58
|
+
return pointer;
|
|
59
|
+
}
|
|
60
|
+
export function pointerSegments(pointer) {
|
|
61
|
+
if (pointer === '')
|
|
62
|
+
return [];
|
|
63
|
+
if (!pointer.startsWith('/'))
|
|
64
|
+
throw new Error(`Unexpected JSON pointer "${pointer}"`);
|
|
65
|
+
return pointer
|
|
66
|
+
.slice(1)
|
|
67
|
+
.split('/')
|
|
68
|
+
.map((segment) => segment.replaceAll('~1', '/').replaceAll('~0', '~'));
|
|
69
|
+
}
|
|
70
|
+
export function keyOf(data, path) {
|
|
71
|
+
let value = data;
|
|
72
|
+
if (path.trim() !== '$') {
|
|
73
|
+
const matches = query(data, path);
|
|
74
|
+
if (matches.length !== 1) {
|
|
75
|
+
throw new Error('The scope "key" selector must match exactly one value');
|
|
76
|
+
}
|
|
77
|
+
value = matches[0]?.value;
|
|
78
|
+
}
|
|
79
|
+
if (typeof value === 'string')
|
|
80
|
+
return value;
|
|
81
|
+
if (typeof value === 'number' && Number.isFinite(value))
|
|
82
|
+
return String(value);
|
|
83
|
+
throw new Error('The scope "key" selector must resolve to a string or finite number');
|
|
84
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { JsonValue, ScopeSelector } from '../../app/rule.js';
|
|
2
|
+
export interface YamlScopeOptions {
|
|
3
|
+
readonly path?: string;
|
|
4
|
+
readonly key?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function parseYaml(source: string): readonly JsonValue[];
|
|
7
|
+
export declare function yaml<T = JsonValue>(options?: YamlScopeOptions): ScopeSelector<T>;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { isAlias, isMap, isNode, isScalar, isSeq, parseAllDocuments, } from 'yaml';
|
|
2
|
+
import { SourceLines } from './lines.js';
|
|
3
|
+
import { keyOf, pointerSegments, query, validatePath } from './structured.js';
|
|
4
|
+
const MAX_ALIAS_COUNT = 100;
|
|
5
|
+
function withGuard(node, active, work) {
|
|
6
|
+
if (active.has(node))
|
|
7
|
+
throw new Error('Recursive YAML aliases or merges are not supported');
|
|
8
|
+
active.add(node);
|
|
9
|
+
try {
|
|
10
|
+
return work();
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
active.delete(node);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function resolveAlias(node, document) {
|
|
17
|
+
if (!isAlias(node))
|
|
18
|
+
return node;
|
|
19
|
+
const target = node.resolve(document);
|
|
20
|
+
if (target === undefined)
|
|
21
|
+
throw new Error(`YAML alias "*${node.source}" has no matching anchor`);
|
|
22
|
+
return target;
|
|
23
|
+
}
|
|
24
|
+
function keyString(key) {
|
|
25
|
+
if (!isScalar(key))
|
|
26
|
+
throw new Error('A YAML scope requires scalar mapping keys');
|
|
27
|
+
const value = key.value;
|
|
28
|
+
if (value === null || value === undefined)
|
|
29
|
+
return '';
|
|
30
|
+
if (typeof value === 'string')
|
|
31
|
+
return value;
|
|
32
|
+
if (typeof value === 'boolean' || typeof value === 'number')
|
|
33
|
+
return String(value);
|
|
34
|
+
throw new Error('A YAML mapping key must be a string, number, boolean or null');
|
|
35
|
+
}
|
|
36
|
+
function mergeSources(value, document) {
|
|
37
|
+
const resolved = resolveAlias(value, document);
|
|
38
|
+
if (isSeq(resolved)) {
|
|
39
|
+
return resolved.items.map(item => {
|
|
40
|
+
const source = resolveAlias(item, document);
|
|
41
|
+
if (!isMap(source))
|
|
42
|
+
throw new Error('YAML merge sequence entries must be mappings');
|
|
43
|
+
return source;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (!isMap(resolved))
|
|
47
|
+
throw new Error('A YAML merge value must be a mapping or a sequence of mappings');
|
|
48
|
+
return [resolved];
|
|
49
|
+
}
|
|
50
|
+
function effectiveEntries(map, document, active, resolved) {
|
|
51
|
+
const cached = resolved.get(map);
|
|
52
|
+
if (cached !== undefined)
|
|
53
|
+
return cached;
|
|
54
|
+
const entries = withGuard(map, active, () => {
|
|
55
|
+
const explicit = new Map();
|
|
56
|
+
const order = [];
|
|
57
|
+
const merges = [];
|
|
58
|
+
for (const pair of map.items) {
|
|
59
|
+
if (isScalar(pair.key) && typeof pair.key.value === 'symbol' && String(pair.key.value) === 'Symbol(<<)') {
|
|
60
|
+
merges.push(pair.value);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const key = pair.key === null || pair.key === undefined ? '' : keyString(pair.key);
|
|
64
|
+
if (!explicit.has(key))
|
|
65
|
+
order.push(key);
|
|
66
|
+
explicit.set(key, pair.value);
|
|
67
|
+
}
|
|
68
|
+
const resolvedEntries = order.map(key => ({ key, value: explicit.get(key) ?? null }));
|
|
69
|
+
const present = new Set(order);
|
|
70
|
+
for (const merge of merges) {
|
|
71
|
+
for (const source of mergeSources(merge, document)) {
|
|
72
|
+
for (const entry of effectiveEntries(source, document, active, resolved)) {
|
|
73
|
+
if (present.has(entry.key))
|
|
74
|
+
continue;
|
|
75
|
+
present.add(entry.key);
|
|
76
|
+
resolvedEntries.push(entry);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return resolvedEntries;
|
|
81
|
+
});
|
|
82
|
+
resolved.set(map, entries);
|
|
83
|
+
return entries;
|
|
84
|
+
}
|
|
85
|
+
function assertJsonValue(value, visiting, seen) {
|
|
86
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
87
|
+
return;
|
|
88
|
+
if (typeof value === 'number') {
|
|
89
|
+
if (!Number.isFinite(value))
|
|
90
|
+
throw new Error('YAML scope data must stay JSON-compatible: non-finite number');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (typeof value !== 'object')
|
|
94
|
+
throw new Error('YAML scope data must stay JSON-compatible: unsupported value');
|
|
95
|
+
if (seen.has(value))
|
|
96
|
+
return;
|
|
97
|
+
if (visiting.has(value))
|
|
98
|
+
throw new Error('YAML scope data must stay JSON-compatible: cyclic value');
|
|
99
|
+
visiting.add(value);
|
|
100
|
+
const prototype = Object.getPrototypeOf(value);
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
for (const item of value)
|
|
103
|
+
assertJsonValue(item, visiting, seen);
|
|
104
|
+
}
|
|
105
|
+
else if (prototype === Object.prototype || prototype === null) {
|
|
106
|
+
for (const key of Object.keys(value)) {
|
|
107
|
+
assertJsonValue(value[key], visiting, seen);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
throw new Error('YAML scope data must stay JSON-compatible: unsupported value');
|
|
112
|
+
}
|
|
113
|
+
visiting.delete(value);
|
|
114
|
+
seen.add(value);
|
|
115
|
+
}
|
|
116
|
+
function resolveData(document) {
|
|
117
|
+
let data;
|
|
118
|
+
try {
|
|
119
|
+
data = document.toJS({ maxAliasCount: MAX_ALIAS_COUNT });
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
123
|
+
throw new Error(`YAML scope data could not be resolved: ${reason}`);
|
|
124
|
+
}
|
|
125
|
+
assertJsonValue(data, new Set(), new Set());
|
|
126
|
+
return data;
|
|
127
|
+
}
|
|
128
|
+
function locate(root, segments, document, resolved) {
|
|
129
|
+
let node = root;
|
|
130
|
+
const active = new Set();
|
|
131
|
+
for (const segment of segments) {
|
|
132
|
+
node = resolveAlias(node, document);
|
|
133
|
+
if (isMap(node)) {
|
|
134
|
+
const entry = effectiveEntries(node, document, active, resolved)
|
|
135
|
+
.find(candidate => candidate.key === segment);
|
|
136
|
+
if (entry === undefined)
|
|
137
|
+
return null;
|
|
138
|
+
node = entry.value;
|
|
139
|
+
}
|
|
140
|
+
else if (isSeq(node)) {
|
|
141
|
+
const index = Number(segment);
|
|
142
|
+
if (!Number.isInteger(index) || index < 0 || index >= node.items.length)
|
|
143
|
+
return null;
|
|
144
|
+
node = node.items[index] ?? null;
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return node;
|
|
151
|
+
}
|
|
152
|
+
function isEmptyDocument(document) {
|
|
153
|
+
const root = document.contents;
|
|
154
|
+
if (root === null)
|
|
155
|
+
return true;
|
|
156
|
+
if (!isNode(root))
|
|
157
|
+
return false;
|
|
158
|
+
const range = root.range;
|
|
159
|
+
if (range === null || range === undefined || range[0] !== range[1])
|
|
160
|
+
return false;
|
|
161
|
+
return !(isScalar(root) && root.tag !== undefined);
|
|
162
|
+
}
|
|
163
|
+
function parseDocuments(source) {
|
|
164
|
+
if (source === '')
|
|
165
|
+
return [];
|
|
166
|
+
const documents = parseAllDocuments(source, { merge: true });
|
|
167
|
+
const streamError = 'empty' in documents ? documents.errors[0] : undefined;
|
|
168
|
+
if (streamError !== undefined) {
|
|
169
|
+
throw new Error(`YAML source could not be parsed: ${streamError.message}`);
|
|
170
|
+
}
|
|
171
|
+
const parsed = [];
|
|
172
|
+
documents.forEach((document, index) => {
|
|
173
|
+
const firstError = document.errors[0];
|
|
174
|
+
if (firstError !== undefined) {
|
|
175
|
+
throw new Error(`YAML source could not be parsed: ${firstError.message}`);
|
|
176
|
+
}
|
|
177
|
+
if (isEmptyDocument(document))
|
|
178
|
+
return;
|
|
179
|
+
parsed.push({ index, document, data: resolveData(document) });
|
|
180
|
+
});
|
|
181
|
+
return parsed;
|
|
182
|
+
}
|
|
183
|
+
export function parseYaml(source) {
|
|
184
|
+
return parseDocuments(source).map(document => document.data);
|
|
185
|
+
}
|
|
186
|
+
function locationOf(node, source, lines) {
|
|
187
|
+
const range = isNode(node) ? node.range : undefined;
|
|
188
|
+
if (!range)
|
|
189
|
+
throw new Error('The selected YAML node has no source range');
|
|
190
|
+
return { source: source.slice(range[0], range[1]), lines: lines.span(range[0], range[1]) };
|
|
191
|
+
}
|
|
192
|
+
function selectScopes(source, selector, keySelector) {
|
|
193
|
+
const lines = new SourceLines(source);
|
|
194
|
+
const rootOnly = selector.trim() === '$';
|
|
195
|
+
const scopes = [];
|
|
196
|
+
for (const { index, document, data } of parseDocuments(source)) {
|
|
197
|
+
const resolved = new Map();
|
|
198
|
+
const matches = rootOnly ? [{ pointer: '', value: data }] : query(data, selector);
|
|
199
|
+
for (const match of matches) {
|
|
200
|
+
const node = locate(document.contents, pointerSegments(match.pointer), document, resolved);
|
|
201
|
+
const location = locationOf(node, source, lines);
|
|
202
|
+
scopes.push({
|
|
203
|
+
id: keySelector === undefined ? `${index}:${match.pointer}` : `${index}:${keyOf(match.value, keySelector)}`,
|
|
204
|
+
source: location.source,
|
|
205
|
+
data: match.value,
|
|
206
|
+
lines: location.lines,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return scopes;
|
|
211
|
+
}
|
|
212
|
+
export function yaml(options) {
|
|
213
|
+
const selector = options?.path === undefined ? '$' : options.path;
|
|
214
|
+
if (typeof selector !== 'string')
|
|
215
|
+
throw new Error('The YAML scope "path" must be a non-empty string');
|
|
216
|
+
validatePath(selector, 'YAML scope path');
|
|
217
|
+
const keySelector = options?.key;
|
|
218
|
+
if (keySelector !== undefined && typeof keySelector !== 'string') {
|
|
219
|
+
throw new Error('The YAML scope "key" must be a non-empty string');
|
|
220
|
+
}
|
|
221
|
+
if (keySelector !== undefined)
|
|
222
|
+
validatePath(keySelector, 'YAML scope key');
|
|
223
|
+
return ({ source }) => selectScopes(source, selector, keySelector);
|
|
224
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const SKILL_NAME = "coderuler-new-rule";
|
|
2
|
+
export declare const SKILL_SOURCE: string;
|
|
3
|
+
export interface SkillOptions {
|
|
4
|
+
readonly global?: boolean;
|
|
5
|
+
readonly project?: boolean;
|
|
6
|
+
readonly agent?: readonly string[];
|
|
7
|
+
readonly yes?: boolean;
|
|
8
|
+
readonly copy?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function createSkillManager(cwd: string, env: NodeJS.ProcessEnv, executable?: string): (operation: 'install' | 'update', options: SkillOptions) => Promise<number>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { constants, homedir } from 'node:os';
|
|
5
|
+
import { dirname, resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
export const SKILL_NAME = 'coderuler-new-rule';
|
|
8
|
+
export const SKILL_SOURCE = fileURLToPath(new URL(`../../../skills/${SKILL_NAME}`, import.meta.url));
|
|
9
|
+
function skillsExecutable() {
|
|
10
|
+
const manifestPath = createRequire(import.meta.url).resolve('skills/package.json');
|
|
11
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
12
|
+
return resolve(dirname(manifestPath), manifest.bin.skills);
|
|
13
|
+
}
|
|
14
|
+
function runSkills(executable, args, cwd, env, capture) {
|
|
15
|
+
const { promise, resolve: resolveResult, reject } = Promise.withResolvers();
|
|
16
|
+
const child = spawn(process.execPath, [executable, ...args], {
|
|
17
|
+
cwd,
|
|
18
|
+
env,
|
|
19
|
+
stdio: capture ? ['inherit', 'pipe', 'inherit'] : 'inherit',
|
|
20
|
+
});
|
|
21
|
+
let stdout = '';
|
|
22
|
+
child.stdout?.setEncoding('utf8');
|
|
23
|
+
child.stdout?.on('data', (chunk) => { stdout += chunk; });
|
|
24
|
+
child.once('error', reject);
|
|
25
|
+
child.once('close', (code, signal) => {
|
|
26
|
+
resolveResult({ status: code ?? 128 + (signal === null ? 0 : constants.signals[signal]), stdout });
|
|
27
|
+
});
|
|
28
|
+
return promise;
|
|
29
|
+
}
|
|
30
|
+
export function createSkillManager(cwd, env, executable) {
|
|
31
|
+
return async (operation, options) => {
|
|
32
|
+
const cli = executable ?? skillsExecutable();
|
|
33
|
+
const scope = options.global === true ? ['--global'] : [];
|
|
34
|
+
// Never let environment detection choose a different destination during a refresh.
|
|
35
|
+
const targets = options.agent ?? ['universal'];
|
|
36
|
+
const agents = ['--agent', ...targets];
|
|
37
|
+
if (operation === 'update') {
|
|
38
|
+
const allAgents = targets.includes('*');
|
|
39
|
+
const filter = allAgents ? [] : agents;
|
|
40
|
+
const listed = await runSkills(cli, ['list', '--json', ...scope, ...filter], cwd, env, true);
|
|
41
|
+
if (listed.status !== 0)
|
|
42
|
+
return listed.status;
|
|
43
|
+
const installed = JSON.parse(listed.stdout);
|
|
44
|
+
const selectedScope = options.global === true ? 'global' : 'project';
|
|
45
|
+
const matching = installed.filter(skill => skill.name === SKILL_NAME && skill.scope === selectedScope);
|
|
46
|
+
if (matching.length === 0) {
|
|
47
|
+
throw new Error(`${SKILL_NAME} is not installed in this scope; run coderuler skill install first`);
|
|
48
|
+
}
|
|
49
|
+
// List filters also scan other agents. Universal has no agent label in JSON,
|
|
50
|
+
// so verify its canonical path; ordinary targets must match display labels.
|
|
51
|
+
// An explicit '*' deliberately opts into installing for every upstream agent.
|
|
52
|
+
const home = env[process.platform === 'win32' ? 'USERPROFILE' : 'HOME'] ?? homedir();
|
|
53
|
+
const universalPath = targets.includes('universal')
|
|
54
|
+
? resolve(options.global === true ? home : cwd, '.agents', 'skills', SKILL_NAME)
|
|
55
|
+
: undefined;
|
|
56
|
+
if (!allAgents) {
|
|
57
|
+
for (const target of targets) {
|
|
58
|
+
const normalized = target.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
59
|
+
const verified = target === 'universal'
|
|
60
|
+
? matching.some(skill => skill.path === universalPath)
|
|
61
|
+
: normalized !== '' && matching.some(skill => Array.isArray(skill.agents)
|
|
62
|
+
&& skill.agents.some((agent) => typeof agent === 'string' && agent.toLowerCase().replace(/[^a-z0-9]/g, '') === normalized));
|
|
63
|
+
if (!verified) {
|
|
64
|
+
throw new Error(`${SKILL_NAME} is not installed for agent "${target}" in this scope, or its installation cannot be verified; `
|
|
65
|
+
+ 'use a supported installed --agent target or universal, or run coderuler skill install first');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const args = ['add', SKILL_SOURCE, '--skill', SKILL_NAME, ...scope, ...agents];
|
|
71
|
+
if (operation === 'update' || options.yes === true)
|
|
72
|
+
args.push('--yes');
|
|
73
|
+
if (options.copy === true)
|
|
74
|
+
args.push('--copy');
|
|
75
|
+
return (await runSkills(cli, args, cwd, env, false)).status;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mapConcurrent<T, R>(items: readonly T[], concurrency: number, mapper: (item: T, index: number, reportFailure: (error: unknown) => void) => Promise<R>): Promise<R[]>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function invalidConcurrency(received) {
|
|
2
|
+
return new RangeError(`--concurrency must be a positive safe integer, received ${received}`);
|
|
3
|
+
}
|
|
4
|
+
function requirePositiveSafeInteger(concurrency) {
|
|
5
|
+
if (!Number.isSafeInteger(concurrency) || concurrency < 1) {
|
|
6
|
+
throw invalidConcurrency(String(concurrency));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export async function mapConcurrent(items, concurrency, mapper) {
|
|
10
|
+
requirePositiveSafeInteger(concurrency);
|
|
11
|
+
const results = new Array(items.length);
|
|
12
|
+
if (items.length === 0)
|
|
13
|
+
return results;
|
|
14
|
+
const workerCount = Math.min(items.length, concurrency);
|
|
15
|
+
let nextIndex = 0;
|
|
16
|
+
let failureObserved = false;
|
|
17
|
+
let failure;
|
|
18
|
+
// A mapper may need to drain started work after it has already failed.
|
|
19
|
+
const reportFailure = (error) => {
|
|
20
|
+
if (!failureObserved) {
|
|
21
|
+
failureObserved = true;
|
|
22
|
+
failure = error;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const runWorker = async () => {
|
|
26
|
+
while (!failureObserved) {
|
|
27
|
+
const index = nextIndex;
|
|
28
|
+
nextIndex += 1;
|
|
29
|
+
if (index >= items.length)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
results[index] = await mapper(items[index], index, reportFailure);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
reportFailure(error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
const workers = Array.from({ length: workerCount }, () => runWorker());
|
|
40
|
+
await Promise.all(workers);
|
|
41
|
+
if (failureObserved)
|
|
42
|
+
throw failure;
|
|
43
|
+
return results;
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AiOptions, Awaitable, CheckResult, Evaluator, JsonValue, ProjectContext, ScopeSubject, Subject, ViolationLocation } from './rule.js';
|
|
2
|
+
import type { DecisionObserver, DecisionRuntime } from './ports.js';
|
|
3
|
+
export declare function pass(): CheckResult;
|
|
4
|
+
export declare function violation(message: string, locations?: readonly ViolationLocation[]): CheckResult;
|
|
5
|
+
export declare function ai<T = JsonValue>(options: AiOptions<T>): Evaluator<T>;
|
|
6
|
+
export declare function evaluateAi<T = JsonValue>(subject: Subject<T>, options: AiOptions<T>, context: ProjectContext & {
|
|
7
|
+
readonly ruleName: string;
|
|
8
|
+
}, runtime: DecisionRuntime, observer?: DecisionObserver): Promise<CheckResult>;
|
|
9
|
+
export interface RegexOptions<T = JsonValue> {
|
|
10
|
+
readonly pattern: RegExp;
|
|
11
|
+
readonly violationMessage?: string;
|
|
12
|
+
readonly input?: (subject: ScopeSubject<T>, context: ProjectContext) => Awaitable<string>;
|
|
13
|
+
}
|
|
14
|
+
export declare function regex<T = JsonValue>(options: RegexOptions<T>): Evaluator<T>;
|