@starci/eslint-grammar-offset-pop 0.12.13 → 0.13.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.
- package/CHANGELOG.md +10 -0
- package/README.md +4 -4
- package/config.mjs +2 -2
- package/index.d.ts +4 -4
- package/index.mjs +1 -1
- package/package.json +23 -15
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +461 -0
- package/runtime/engine/invalid-config.mjs +15 -0
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +29 -0
- package/runtime/engine/release-config.mjs +28 -0
- package/runtime/engine/removed-vocabulary.mjs +26 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/secrets.mjs +134 -0
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +19 -20
- package/runtime/modules/kernel/removed-vocabulary.yaml +458 -0
- package/runtime/modules/models/registry.yaml +221 -0
- package/runtime/modules/models/runtimes.yaml +556 -0
- package/runtime/modules/models/tiers.yaml +66 -0
- package/runtime/scripts/hfs/declaration-shape.mjs +157 -0
- package/runtime/scripts/hfs/declaration-slots.mjs +86 -0
- package/runtime/scripts/hfs/edition-slots.mjs +77 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +435 -0
- package/runtime/scripts/hfs/param-names.mjs +18 -0
- package/runtime/scripts/hfs/rule-catalog.mjs +362 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +80 -0
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +91 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +89 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +149 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/lib/i18n.mjs +85 -0
- package/runtime/scripts/lib/json-clone.mjs +61 -0
- package/runtime/scripts/lib/list.mjs +31 -0
- package/runtime/scripts/lib/regex.mjs +4 -0
- package/runtime/scripts/lib/yaml-cached.mjs +14 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// i18n.mjs - the ONE mechanism for text the owner must read in Vietnamese. Runtime source, comments and messages are English;
|
|
2
|
+
// a message the owner reads (a Telegram notice, a why line, a progress report) is written once in English at its call site and
|
|
3
|
+
// translated through a DECLARED catalog keyed from that English source: modules/i18n/messages/<area>.yaml, a list of
|
|
4
|
+
// `{en, vi}` entries. Each area belongs to a scope: `runtime` for Node callers or `ui` for the generated browser catalog.
|
|
5
|
+
// The `vi` field is declared in scripts/lib/language.mjs, so it is the only Vietnamese source. There is no per-module
|
|
6
|
+
// `TEXT.vi` table and no second catalog mechanism.
|
|
7
|
+
//
|
|
8
|
+
// const tr = translator(config.language); // 'vi' translates, anything else returns the English source
|
|
9
|
+
// tr('Workflow {id} needs your decision', { id })
|
|
10
|
+
//
|
|
11
|
+
// A placeholder is `{name}`; the English and the Vietnamese of one entry carry exactly the same placeholders
|
|
12
|
+
// (scripts/checks/check-i18n-catalog.mjs). An English source with no entry is returned as is: a missing translation degrades
|
|
13
|
+
// to English, never to a blank.
|
|
14
|
+
import { byCodeUnit } from './list.mjs';
|
|
15
|
+
import { escapeRegExp } from './regex.mjs';
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { skillRoot } from '../../engine/runtime-root.mjs';
|
|
19
|
+
import { parseYaml } from '../../engine/yaml.mjs';
|
|
20
|
+
import { DEFAULT_OWNER_LANGUAGE, loadConfig } from '../../engine/config.mjs';
|
|
21
|
+
|
|
22
|
+
export const CATALOG_DIR = 'modules/i18n/messages';
|
|
23
|
+
export const PLACEHOLDER = /\{([A-Za-z_]\w*)\}/g;
|
|
24
|
+
|
|
25
|
+
const cache = new Map();
|
|
26
|
+
|
|
27
|
+
/** The catalog files of a runtime root: repository-relative paths, sorted. */
|
|
28
|
+
export function catalogFiles(root = skillRoot) {
|
|
29
|
+
const dir = path.join(root, ...CATALOG_DIR.split('/'));
|
|
30
|
+
try { return fs.readdirSync(dir).filter((name) => /\.ya?ml$/.test(name)).sort().map((name) => `${CATALOG_DIR}/${name}`); } catch { return []; }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Every entry in one catalog scope: `[{ en, vi, area, scope, file }]`. */
|
|
34
|
+
export function catalogEntries(root = skillRoot, { scope = 'runtime' } = {}) {
|
|
35
|
+
return catalogFiles(root).flatMap((rel) => {
|
|
36
|
+
const doc = parseYaml(fs.readFileSync(path.join(root, ...rel.split('/')), 'utf8'));
|
|
37
|
+
const documentScope = typeof doc?.scope === 'string' ? doc.scope : 'runtime';
|
|
38
|
+
if (documentScope !== scope) return [];
|
|
39
|
+
return (Array.isArray(doc?.messages) ? doc.messages : []).map((entry) => ({
|
|
40
|
+
en: entry?.en, vi: entry?.vi, area: doc?.area, scope: documentScope, file: rel,
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One scoped catalog as a Map from the English source to the Vietnamese text (cached per root and scope). */
|
|
46
|
+
export function loadCatalog(root = skillRoot, { scope = 'runtime' } = {}) {
|
|
47
|
+
const key = `${root}\0${scope}`;
|
|
48
|
+
if (!cache.has(key)) cache.set(key, new Map(catalogEntries(root, { scope }).filter((e) => typeof e.en === 'string' && typeof e.vi === 'string').map((e) => [e.en, e.vi])));
|
|
49
|
+
return cache.get(key);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
/** The names each `pattern` match captures (group 1) in `text`, in match order (`sort` sorts them). */
|
|
54
|
+
export const captureNames = (text, pattern, { sort = false } = {}) => {
|
|
55
|
+
const names = [...String(text).matchAll(pattern)].map((m) => m[1]);
|
|
56
|
+
return sort ? names.sort(byCodeUnit) : names;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** The names of the placeholders of a message. */
|
|
60
|
+
export const placeholdersOf = (text) => captureNames(text, PLACEHOLDER, { sort: true });
|
|
61
|
+
|
|
62
|
+
/** `text` with each `{name}` replaced by vars[name] (an unknown name is left as written). */
|
|
63
|
+
export const fill = (text, vars = {}) => String(text).replace(PLACEHOLDER, (whole, name) => (Object.hasOwn(vars, name) ? String(vars[name]) : whole));
|
|
64
|
+
|
|
65
|
+
/** The message `en` in `language` (`vi` translates through the catalog; any other language is the English source). */
|
|
66
|
+
export function translate(en, vars = {}, { language = DEFAULT_OWNER_LANGUAGE, root = skillRoot, scope = 'runtime' } = {}) {
|
|
67
|
+
const vi = language === 'vi' ? loadCatalog(root, { scope }).get(en) : undefined;
|
|
68
|
+
return fill(vi ?? en, vars);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The owner's language (config.yaml `language`); `fallback` when there is no readable config (DEFAULT_OWNER_LANGUAGE of engine/config.mjs). */
|
|
72
|
+
export function ownerLanguage(fallback = DEFAULT_OWNER_LANGUAGE) {
|
|
73
|
+
try { return loadConfig()?.language ?? fallback; } catch { return fallback; }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** A translator bound to one language: `tr(en, vars)`. */
|
|
77
|
+
export const translator = (language = DEFAULT_OWNER_LANGUAGE, { root = skillRoot, scope = 'runtime' } = {}) =>
|
|
78
|
+
(en, vars = {}) => translate(en, vars, { language, root, scope });
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A pattern source matching `en` in the owner's language or in English: each language's rendering of the English source,
|
|
82
|
+
* escaped, with the placeholder `slot` (a placeholder name of `en`) replaced by `slotPattern`. Never hardcodes a spelling.
|
|
83
|
+
*/
|
|
84
|
+
export const translatedPattern = (en, slot, slotPattern, languages = ['en', 'vi']) =>
|
|
85
|
+
`(?:${languages.map((language) => escapeRegExp(translate(en, { [slot]: '@@' }, { language })).replace('@@', slotPattern)).join('|')})`;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// json-clone.mjs — a deep copy that keeps exactly what survives a JSON text and nothing else: the plain-data
|
|
2
|
+
// copy of a value, detached from every reference, frozen state and class of the original. `structuredClone` keeps
|
|
3
|
+
// what JSON drops (undefined members, Dates, Maps, sparse holes) and refuses what JSON accepts (functions,
|
|
4
|
+
// `toJSON` objects), so it is not a substitute; this walks the value once under the JSON.stringify rules.
|
|
5
|
+
|
|
6
|
+
/** The boxed primitives JSON.stringify unwraps: recognised by the internal slot a valueOf call reads, as JSON does. */
|
|
7
|
+
const BOXED = [
|
|
8
|
+
{ slot: Number.prototype.valueOf, read: Number },
|
|
9
|
+
{ slot: String.prototype.valueOf, read: String },
|
|
10
|
+
{ slot: Boolean.prototype.valueOf, read: (box) => Boolean.prototype.valueOf.call(box) },
|
|
11
|
+
{ slot: BigInt.prototype.valueOf, read: (box) => BigInt.prototype.valueOf.call(box) },
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
/** Whether `value` carries the internal slot `slot` reads (a valueOf of that boxed type does not throw on it). */
|
|
15
|
+
const hasSlot = (slot, value) => {
|
|
16
|
+
try { slot.call(value); return true; } catch { return false; }
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/** What JSON.stringify takes of `holder[key]`: the toJSON result when there is one, a boxed primitive unwrapped. */
|
|
20
|
+
const serialized = (holder, key) => {
|
|
21
|
+
let value = holder[key];
|
|
22
|
+
if (value !== null && (typeof value === 'object' || typeof value === 'bigint') && typeof value.toJSON === 'function') value = value.toJSON(key);
|
|
23
|
+
if (value === null || typeof value !== 'object') return value;
|
|
24
|
+
const box = BOXED.find((candidate) => hasSlot(candidate.slot, value));
|
|
25
|
+
return box ? box.read(value) : value;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** A member JSON leaves out of an object (and writes as null in an array). */
|
|
29
|
+
const isAbsent = (value) => value === undefined || typeof value === 'function' || typeof value === 'symbol';
|
|
30
|
+
|
|
31
|
+
const copyValue = (value, ancestors) => {
|
|
32
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
33
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value + 0 : null;
|
|
34
|
+
if (typeof value === 'bigint') throw new TypeError('Do not know how to serialize a BigInt');
|
|
35
|
+
if (ancestors.includes(value)) throw new TypeError('Converting circular structure to JSON');
|
|
36
|
+
const inside = [...ancestors, value];
|
|
37
|
+
if (Array.isArray(value)) {
|
|
38
|
+
return Array.from({ length: value.length }, (_, index) => {
|
|
39
|
+
const item = serialized(value, String(index));
|
|
40
|
+
return isAbsent(item) ? null : copyValue(item, inside);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const entries = [];
|
|
44
|
+
for (const name of Object.keys(value)) {
|
|
45
|
+
const member = serialized(value, name);
|
|
46
|
+
if (!isAbsent(member)) entries.push([name, copyValue(member, inside)]);
|
|
47
|
+
}
|
|
48
|
+
return Object.fromEntries(entries);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* `value` copied as JSON data: members that are undefined, functions or symbols are dropped (an array item becomes
|
|
53
|
+
* null), non-finite numbers become null and -0 becomes 0, `toJSON` results replace their objects (a Date becomes its ISO string), boxed
|
|
54
|
+
* primitives unwrap, and a cycle or a BigInt throws a TypeError. A value JSON has no text for (undefined, a function,
|
|
55
|
+
* a symbol) throws a SyntaxError, as parsing the missing text does.
|
|
56
|
+
*/
|
|
57
|
+
export function jsonClone(value) {
|
|
58
|
+
const top = serialized({ '': value }, '');
|
|
59
|
+
if (isAbsent(top)) throw new SyntaxError('"undefined" is not valid JSON');
|
|
60
|
+
return copyValue(top, []);
|
|
61
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// list.mjs — the array-or-empty coercion half the scripts re-declared by hand.
|
|
2
|
+
// `v` when it already is an array, else []. Scalars are NOT wrapped: a caller that wants
|
|
3
|
+
// `[v]` for a lone value writes its own coercion (see contract-version's list).
|
|
4
|
+
export const list = (v) => (Array.isArray(v) ? v : []);
|
|
5
|
+
|
|
6
|
+
/** `v` as an array: itself when already one, [] for null/undefined, else [v]. */
|
|
7
|
+
export const asList = (v) => {
|
|
8
|
+
if (Array.isArray(v)) return v;
|
|
9
|
+
if (v == null) return [];
|
|
10
|
+
return [v];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/** The object entries of `v` (a non-array reads as []). */
|
|
14
|
+
export const objectList = (v) => (Array.isArray(v) ? v : []).filter((item) => item && typeof item === 'object');
|
|
15
|
+
|
|
16
|
+
/** Every entry of `v` a non-empty string (`blank: false` also rejects all-whitespace entries). */
|
|
17
|
+
export const stringList = (v, { blank = true } = {}) =>
|
|
18
|
+
Array.isArray(v) && v.every((s) => typeof s === 'string' && s.length > 0 && (blank || s.trim().length > 0));
|
|
19
|
+
|
|
20
|
+
/** A non-empty list of distinct values, all accepted by the supplied predicate. */
|
|
21
|
+
export const nonemptyUniqueList = (values, accepts) => Array.isArray(values) && values.length > 0
|
|
22
|
+
&& values.every((entry) => accepts(entry)) && new Set(values).size === values.length;
|
|
23
|
+
|
|
24
|
+
/** The items of a `sep`-separated list: trimmed, empties dropped; `dedupe` keeps first occurrences only. */
|
|
25
|
+
export const splitList = (v, { sep = ',', dedupe = false } = {}) => {
|
|
26
|
+
const items = String(v ?? '').split(sep).map((s) => s.trim()).filter(Boolean);
|
|
27
|
+
return dedupe ? [...new Set(items)] : items;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// The comparator of a bare `sort()` (UTF-16 code-unit order) is owned by the engine, which cannot import scripts/lib.
|
|
31
|
+
export { byCodeUnit } from '../../engine/by-code-unit.mjs';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// yaml-cached.mjs - parse a YAML text once per distinct text in a process.
|
|
2
|
+
//
|
|
3
|
+
// The HFS slot manifest and rule catalog are large documents that one process asks for again at every check, every render and every fixture. Their
|
|
4
|
+
// bytes do not change between those asks, so the parse (the dominant cost of a load) is keyed by the text itself; each caller gets its own copy of the
|
|
5
|
+
// parsed document, so nothing a caller does to it reaches the next one. A changed file is a different text and parses again.
|
|
6
|
+
import { parseYaml } from '../../engine/yaml.mjs';
|
|
7
|
+
|
|
8
|
+
const parsed = new Map();
|
|
9
|
+
|
|
10
|
+
/** The parsed document of `text`: parsed on the first ask for this text, a structured copy afterwards. Throws what parseYaml throws, every time. */
|
|
11
|
+
export function parseYamlCached(text) {
|
|
12
|
+
if (!parsed.has(text)) parsed.set(text, parseYaml(text));
|
|
13
|
+
return structuredClone(parsed.get(text));
|
|
14
|
+
}
|