@link-assistant/hive-mind 1.72.2 → 1.72.4
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 +12 -0
- package/package.json +2 -1
- package/src/github-repository-names.lib.mjs +63 -0
- package/src/i18n.lib.mjs +35 -32
- package/src/locales/en.lino +878 -310
- package/src/locales/hi.lino +878 -310
- package/src/locales/ru.lino +878 -310
- package/src/locales/zh.lino +878 -310
- package/src/solve.repository.lib.mjs +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.72.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 82d440c: Fix fork creation verification for dotted repository names such as GitHub Pages forks.
|
|
8
|
+
|
|
9
|
+
## 1.72.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 502e78f: Use `lino-i18n` for Hive Mind translation loading and runtime lookup.
|
|
14
|
+
|
|
3
15
|
## 1.72.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@link-assistant/hive-mind",
|
|
3
|
-
"version": "1.72.
|
|
3
|
+
"version": "1.72.4",
|
|
4
4
|
"description": "AI-powered issue solver and hive mind for collaborative problem solving",
|
|
5
5
|
"main": "src/hive.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -76,6 +76,7 @@
|
|
|
76
76
|
"dayjs": "^1.11.19",
|
|
77
77
|
"decimal.js-light": "^2.5.1",
|
|
78
78
|
"lino-arguments": "^0.3.0",
|
|
79
|
+
"lino-i18n": "^0.1.1",
|
|
79
80
|
"lino-objects-codec": "^0.3.6",
|
|
80
81
|
"secretlint": "^11.2.5",
|
|
81
82
|
"semver": "^7.7.3",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
const OWNER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
2
|
+
const REPOSITORY_NAME_PATTERN = /^[A-Za-z0-9._-]+$/;
|
|
3
|
+
const FULL_NAME_IN_TEXT_PATTERN = /(?:^|\s)([A-Za-z0-9_-]+\/[A-Za-z0-9._-]+)(?=$|\s|[),.;:])/;
|
|
4
|
+
|
|
5
|
+
function trimOutputToken(token) {
|
|
6
|
+
return token.replace(/^[<([{'"`]+/, '').replace(/[>\])}'"`.,;]+$/, '');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function stripGitSuffix(repositoryName) {
|
|
10
|
+
return repositoryName.endsWith('.git') ? repositoryName.slice(0, -4) : repositoryName;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizeRepositoryFullName(owner, repositoryName) {
|
|
14
|
+
if (!owner || !repositoryName) return null;
|
|
15
|
+
if (!OWNER_NAME_PATTERN.test(owner)) return null;
|
|
16
|
+
if (!REPOSITORY_NAME_PATTERN.test(repositoryName)) return null;
|
|
17
|
+
return `${owner}/${repositoryName}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseGitHubRepositoryUrlToken(token) {
|
|
21
|
+
const cleaned = trimOutputToken(token);
|
|
22
|
+
let pathName = null;
|
|
23
|
+
|
|
24
|
+
if (cleaned.startsWith('git@github.com:')) {
|
|
25
|
+
pathName = cleaned.slice('git@github.com:'.length);
|
|
26
|
+
} else if (cleaned.startsWith('github.com/')) {
|
|
27
|
+
pathName = cleaned.slice('github.com/'.length);
|
|
28
|
+
} else {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = new URL(cleaned);
|
|
31
|
+
if (parsed.hostname !== 'github.com') return null;
|
|
32
|
+
pathName = parsed.pathname.replace(/^\/+/, '');
|
|
33
|
+
} catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const [owner, repositoryName] = pathName.split('/');
|
|
39
|
+
return normalizeRepositoryFullName(owner, stripGitSuffix(repositoryName || ''));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Parse the repository full name returned by `gh repo fork`.
|
|
44
|
+
*
|
|
45
|
+
* GitHub repository names can contain dots, notably GitHub Pages names like
|
|
46
|
+
* `parking.github.io`. The previous inline regex only accepted letters,
|
|
47
|
+
* digits, underscores, and dashes, so it truncated dotted fork names and
|
|
48
|
+
* verified the wrong repository.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} output
|
|
51
|
+
* @returns {string|null}
|
|
52
|
+
*/
|
|
53
|
+
export function parseForkFullNameFromGhOutput(output) {
|
|
54
|
+
const text = String(output || '');
|
|
55
|
+
|
|
56
|
+
for (const token of text.match(/\S+/g) || []) {
|
|
57
|
+
const parsed = parseGitHubRepositoryUrlToken(token);
|
|
58
|
+
if (parsed) return parsed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fullNameMatch = text.match(FULL_NAME_IN_TEXT_PATTERN);
|
|
62
|
+
return fullNameMatch ? fullNameMatch[1] : null;
|
|
63
|
+
}
|
package/src/i18n.lib.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// i18n module for hive-mind.
|
|
2
2
|
// - Translation files live in src/locales/<locale>.lino and are stored
|
|
3
|
-
// in Links Notation, parsed via lino-
|
|
3
|
+
// in Links Notation, parsed and resolved via lino-i18n.
|
|
4
4
|
// - Supported locales: en (default fallback), ru, zh, hi.
|
|
5
5
|
// - Two locale tracks: ui (user-facing strings) and work (AI prompts /
|
|
6
6
|
// tool preferred language). Both default to the value of --language.
|
|
@@ -9,23 +9,31 @@
|
|
|
9
9
|
// normalizeLocale, getUserLocale, setUserLocale, clearUserLocale,
|
|
10
10
|
// resolveLocaleFromTelegramCtx.
|
|
11
11
|
|
|
12
|
-
import { promises as fs } from 'fs';
|
|
13
12
|
import path from 'path';
|
|
14
13
|
import { fileURLToPath } from 'url';
|
|
15
|
-
import {
|
|
14
|
+
import { createI18n } from 'lino-i18n';
|
|
15
|
+
import { loadLocalesFromFile } from 'lino-i18n/loaders';
|
|
16
16
|
|
|
17
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
18
|
const __dirname = path.dirname(__filename);
|
|
19
19
|
|
|
20
20
|
const DEFAULT_LOCALE = 'en';
|
|
21
21
|
const SUPPORTED_LOCALES = ['en', 'ru', 'zh', 'hi'];
|
|
22
|
+
const LINO_COMPATIBILITY_ALIASES = ['collapseTail', 'parentLabel'];
|
|
22
23
|
|
|
23
24
|
const localeCache = new Map(); // locale -> { key: string }
|
|
24
25
|
const userLocales = new Map(); // userId/chatId -> locale (in-memory)
|
|
26
|
+
const localesDir = path.join(__dirname, 'locales');
|
|
25
27
|
|
|
26
28
|
let currentUiLocale = DEFAULT_LOCALE;
|
|
27
29
|
let currentWorkLocale = DEFAULT_LOCALE;
|
|
28
30
|
let fallbackLoaded = false;
|
|
31
|
+
let i18n = createI18n({
|
|
32
|
+
locales: {},
|
|
33
|
+
defaultLocale: DEFAULT_LOCALE,
|
|
34
|
+
fallback: [DEFAULT_LOCALE],
|
|
35
|
+
compatibilityAliases: LINO_COMPATIBILITY_ALIASES,
|
|
36
|
+
});
|
|
29
37
|
|
|
30
38
|
export function getSupportedLocales() {
|
|
31
39
|
return [...SUPPORTED_LOCALES];
|
|
@@ -46,31 +54,22 @@ export function detectLocale() {
|
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
async function readLocaleFile(locale) {
|
|
49
|
-
const localesDir = path.join(__dirname, 'locales');
|
|
50
57
|
const linoFile = path.join(localesDir, `${locale}.lino`);
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
function
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const parsed = parseIndented({ text });
|
|
67
|
-
// parsed: { id: <localeName>, obj: { key: value, ... } }
|
|
68
|
-
if (!parsed || !parsed.obj) return {};
|
|
69
|
-
const out = {};
|
|
70
|
-
for (const [k, v] of Object.entries(parsed.obj)) {
|
|
71
|
-
out[k] = typeof v === 'string' ? unescapeString(v) : String(v);
|
|
72
|
-
}
|
|
73
|
-
return out;
|
|
58
|
+
const loaded = await loadLocalesFromFile(linoFile, {
|
|
59
|
+
compatibilityAliases: LINO_COMPATIBILITY_ALIASES,
|
|
60
|
+
});
|
|
61
|
+
const match = loaded.find(catalogue => catalogue.locale === locale) || loaded[0];
|
|
62
|
+
return match?.translations || {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function refreshI18nRuntime() {
|
|
66
|
+
i18n = createI18n({
|
|
67
|
+
locales: Object.fromEntries(localeCache.entries()),
|
|
68
|
+
defaultLocale: currentUiLocale,
|
|
69
|
+
fallback: [DEFAULT_LOCALE],
|
|
70
|
+
compatibilityAliases: LINO_COMPATIBILITY_ALIASES,
|
|
71
|
+
});
|
|
72
|
+
i18n.setLocale(currentUiLocale);
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
export async function loadTranslations(locale) {
|
|
@@ -83,6 +82,7 @@ export async function loadTranslations(locale) {
|
|
|
83
82
|
translations = {};
|
|
84
83
|
}
|
|
85
84
|
localeCache.set(locale, translations);
|
|
85
|
+
refreshI18nRuntime();
|
|
86
86
|
|
|
87
87
|
// Always have the fallback (English) ready
|
|
88
88
|
if (!fallbackLoaded && locale !== DEFAULT_LOCALE) {
|
|
@@ -93,6 +93,7 @@ export async function loadTranslations(locale) {
|
|
|
93
93
|
localeCache.set(DEFAULT_LOCALE, {});
|
|
94
94
|
}
|
|
95
95
|
fallbackLoaded = true;
|
|
96
|
+
refreshI18nRuntime();
|
|
96
97
|
} else if (locale === DEFAULT_LOCALE) {
|
|
97
98
|
fallbackLoaded = true;
|
|
98
99
|
}
|
|
@@ -157,10 +158,8 @@ export function t(key, params = {}, options = {}) {
|
|
|
157
158
|
} else {
|
|
158
159
|
locale = currentUiLocale;
|
|
159
160
|
}
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
const value = main[key] ?? fallback[key] ?? key;
|
|
163
|
-
return applyParams(value, params);
|
|
161
|
+
const value = i18n.t(key, params, { ...options, locale });
|
|
162
|
+
return typeof value === 'string' ? value : applyParams(String(value), params);
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
// Convenience helper for work-language strings (AI prompts).
|
|
@@ -185,12 +184,16 @@ export function setLocale(locale) {
|
|
|
185
184
|
if (normalized) {
|
|
186
185
|
currentUiLocale = normalized;
|
|
187
186
|
currentWorkLocale = normalized;
|
|
187
|
+
i18n.setLocale(normalized);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
export function setUiLocale(locale) {
|
|
192
192
|
const normalized = normalizeLocale(locale);
|
|
193
|
-
if (normalized)
|
|
193
|
+
if (normalized) {
|
|
194
|
+
currentUiLocale = normalized;
|
|
195
|
+
i18n.setLocale(normalized);
|
|
196
|
+
}
|
|
194
197
|
}
|
|
195
198
|
|
|
196
199
|
export function setWorkLocale(locale) {
|