@triflux/core 10.51.0 → 10.52.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 (55) hide show
  1. package/hub/bridge.mjs +65 -1172
  2. package/hub/index.mjs +2 -18
  3. package/hub/lib/process-utils.mjs +1 -33
  4. package/hub/team/claude-daemon-control.mjs +1 -2
  5. package/hub/team/claude-session-projection.mjs +5 -73
  6. package/hub/team/nativeProxy.mjs +759 -0
  7. package/hub/team/session-context.mjs +1 -1
  8. package/hud/constants.mjs +2 -0
  9. package/hud/hud-qos-status.mjs +21 -5
  10. package/hud/providers/gemini.mjs +20 -0
  11. package/hud/renderers.mjs +79 -46
  12. package/hud/utils.mjs +9 -5
  13. package/package.json +2 -4
  14. package/scripts/lib/agent-route-policy.mjs +0 -67
  15. package/scripts/lib/cli-claude.mjs +0 -12
  16. package/scripts/lib/env-probe.mjs +0 -70
  17. package/scripts/lib/legacy-mcp-cleanup.mjs +283 -0
  18. package/scripts/lib/mcp-guard-engine.mjs +40 -98
  19. package/scripts/lib/session-presence.mjs +1 -99
  20. package/hub/adaptive-diagnostic.mjs +0 -350
  21. package/hub/adaptive-inject.mjs +0 -224
  22. package/hub/adaptive-memory.mjs +0 -277
  23. package/hub/adaptive.mjs +0 -172
  24. package/hub/assign-callbacks.mjs +0 -170
  25. package/hub/delegator/contracts.mjs +0 -38
  26. package/hub/delegator/index.mjs +0 -14
  27. package/hub/delegator/schema/delegator-tools.schema.json +0 -250
  28. package/hub/delegator/service.mjs +0 -336
  29. package/hub/delegator/tool-definitions.mjs +0 -35
  30. package/hub/hitl.mjs +0 -202
  31. package/hub/lib/known-errors.json +0 -72
  32. package/hub/lib/memory-store.mjs +0 -1001
  33. package/hub/lib/phase-manager.mjs +0 -493
  34. package/hub/lib/state-snapshot.mjs +0 -293
  35. package/hub/lib/trace-recorder.mjs +0 -153
  36. package/hub/lib/uuidv7.mjs +0 -45
  37. package/hub/memory-doctor.mjs +0 -640
  38. package/hub/middleware/quota-middleware.mjs +0 -70
  39. package/hub/middleware/request-logger.mjs +0 -191
  40. package/hub/paths.mjs +0 -24
  41. package/hub/pipeline/index.mjs +0 -163
  42. package/hub/pipeline/state.mjs +0 -191
  43. package/hub/pipeline/transitions.mjs +0 -131
  44. package/hub/reflexion.mjs +0 -290
  45. package/hub/role-contract.mjs +0 -312
  46. package/hub/router.mjs +0 -2026
  47. package/hub/session-fingerprint.mjs +0 -418
  48. package/hub/state.mjs +0 -300
  49. package/hub/team/synapse-http.mjs +0 -311
  50. package/hub/team-bridge.mjs +0 -27
  51. package/scripts/lib/claudemd-scanner.mjs +0 -26
  52. package/scripts/lib/context.mjs +0 -51
  53. package/scripts/lib/handoff.mjs +0 -187
  54. package/scripts/lib/logger.mjs +0 -122
  55. package/scripts/lib/stealth-fetch.mjs +0 -176
@@ -1,640 +0,0 @@
1
- import {
2
- copyFileSync,
3
- existsSync,
4
- lstatSync,
5
- mkdirSync,
6
- readdirSync,
7
- readFileSync,
8
- renameSync,
9
- writeFileSync,
10
- } from "node:fs";
11
- import { basename, join, resolve } from "node:path";
12
-
13
- // ── Constants ──
14
-
15
- const SEVERITY = Object.freeze({ P0: "P0", P1: "P1", P2: "P2" });
16
- const WEIGHT = Object.freeze({ P0: 2.0, P1: 0.5, P2: 0.2 });
17
-
18
- const MEMORY_LINK_RE = /\[([^\]]*)\]\(([^)]+\.md)\)/gu;
19
-
20
- const PATHS_YAML_RE = /^paths:\s*\n(\s+-\s+.+\n?)+/mu;
21
-
22
- const RULE_VIOLATION_PATTERNS = [
23
- /^\|\s*스킬\s*\|/mu,
24
- /^\|\s*디렉토리\s*\|/mu,
25
- /^```\s*(?:tree|npm ls|git log)/mu,
26
- /^\s*├──|└──/mu,
27
- ];
28
-
29
- const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---/u;
30
-
31
- const HANGUL_RE = /[\uAC00-\uD7A3]/u;
32
- const LATIN_RE = /[a-zA-Z]{3,}/u;
33
-
34
- // ── Helpers ──
35
-
36
- const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
37
-
38
- function parseFrontmatter(content) {
39
- const match = FRONTMATTER_RE.exec(String(content || ""));
40
- if (!match) return { found: false, fields: {}, body: content };
41
-
42
- const fields = {};
43
- for (const line of match[1].split("\n")) {
44
- const sep = line.indexOf(":");
45
- if (sep === -1) continue;
46
- const key = line.slice(0, sep).trim();
47
- const val = line.slice(sep + 1).trim();
48
- if (key) fields[key] = val;
49
- }
50
- return {
51
- found: true,
52
- fields,
53
- body: String(content).slice(match[0].length).trim(),
54
- };
55
- }
56
-
57
- function safeReadFile(filePath) {
58
- try {
59
- return existsSync(filePath) ? readFileSync(filePath, "utf8") : null;
60
- } catch {
61
- return null;
62
- }
63
- }
64
-
65
- function safeReadDir(dirPath) {
66
- try {
67
- return existsSync(dirPath) ? readdirSync(dirPath) : [];
68
- } catch {
69
- return [];
70
- }
71
- }
72
-
73
- function isCI() {
74
- return (
75
- process.env.CI === "true" ||
76
- process.env.DOCKER === "true" ||
77
- (process.env.TFX_POSTINSTALL_FIX !== "1" && process.env.CI != null)
78
- );
79
- }
80
-
81
- function makeCheckResult(id, name, severity, autofix, issues, error = null) {
82
- return Object.freeze({
83
- id,
84
- name,
85
- severity,
86
- autofix,
87
- issues: Object.freeze([...issues]),
88
- passed: issues.length === 0 && error == null,
89
- error,
90
- });
91
- }
92
-
93
- // ── Check Functions ──
94
-
95
- function checkOrphanFiles(memoryDir) {
96
- const indexPath = join(memoryDir, "MEMORY.md");
97
- const indexContent = safeReadFile(indexPath);
98
- if (indexContent == null) {
99
- return makeCheckResult(
100
- "orphan-files",
101
- "고아 파일 감지",
102
- SEVERITY.P0,
103
- true,
104
- [],
105
- );
106
- }
107
-
108
- const indexed = new Set();
109
- let m;
110
- const re = new RegExp(MEMORY_LINK_RE.source, MEMORY_LINK_RE.flags);
111
- while ((m = re.exec(indexContent)) !== null) {
112
- indexed.add(m[2]);
113
- }
114
-
115
- const diskFiles = safeReadDir(memoryDir).filter(
116
- (f) => f.endsWith(".md") && f !== "MEMORY.md",
117
- );
118
-
119
- const orphans = diskFiles
120
- .filter((f) => !indexed.has(f))
121
- .map((f) => ({ file: f, detail: "MEMORY.md 인덱스에 미등록" }));
122
-
123
- return makeCheckResult(
124
- "orphan-files",
125
- "고아 파일 감지",
126
- SEVERITY.P0,
127
- true,
128
- orphans,
129
- );
130
- }
131
-
132
- function checkPathsYamlBug(rulesDir) {
133
- const files = safeReadDir(rulesDir).filter((f) => f.endsWith(".md"));
134
- const issues = [];
135
-
136
- for (const file of files) {
137
- const content = safeReadFile(join(rulesDir, file));
138
- if (content == null) continue;
139
-
140
- const fm = parseFrontmatter(content);
141
- if (!fm.found) continue;
142
-
143
- const rawFm = FRONTMATTER_RE.exec(content);
144
- if (rawFm && PATHS_YAML_RE.test(rawFm[1])) {
145
- issues.push({
146
- file,
147
- detail: "paths: YAML 배열 사용 (동작 안 함 — globs: CSV로 변환 필요)",
148
- });
149
- }
150
- }
151
-
152
- return makeCheckResult(
153
- "paths-yaml-bug",
154
- "paths: YAML 배열 버그",
155
- SEVERITY.P0,
156
- true,
157
- issues,
158
- );
159
- }
160
-
161
- function checkTrifluxResidue(projectDir, claudeDir) {
162
- const issues = [];
163
-
164
- const omcState = join(projectDir, ".omc", "state");
165
- if (existsSync(omcState)) {
166
- const now = Date.now();
167
- const staleThreshold = 7 * 24 * 60 * 60 * 1000;
168
- for (const f of safeReadDir(omcState).filter((f) => f.endsWith(".json"))) {
169
- try {
170
- const stat = lstatSync(join(omcState, f));
171
- if (now - stat.mtimeMs > staleThreshold) {
172
- issues.push({
173
- file: `.omc/state/${f}`,
174
- detail: "7일 이상 된 stale state 파일",
175
- });
176
- }
177
- } catch {
178
- /* skip */
179
- }
180
- }
181
- }
182
-
183
- const scriptsDir = join(claudeDir, "scripts");
184
- for (const f of safeReadDir(scriptsDir)) {
185
- try {
186
- const fullPath = join(scriptsDir, f);
187
- const stat = lstatSync(fullPath);
188
- if (stat.isSymbolicLink() && !existsSync(fullPath)) {
189
- issues.push({
190
- file: `~/.claude/scripts/${f}`,
191
- detail: "깨진 심볼릭 링크",
192
- });
193
- }
194
- } catch {
195
- /* skip */
196
- }
197
- }
198
-
199
- return makeCheckResult(
200
- "triflux-residue",
201
- "기존 triflux 잔재",
202
- SEVERITY.P0,
203
- true,
204
- issues,
205
- );
206
- }
207
-
208
- function checkRuleViolation(memoryDir) {
209
- const files = safeReadDir(memoryDir).filter(
210
- (f) => f.endsWith(".md") && f !== "MEMORY.md",
211
- );
212
- const issues = [];
213
-
214
- for (const file of files) {
215
- const content = safeReadFile(join(memoryDir, file));
216
- if (content == null) continue;
217
-
218
- for (const pattern of RULE_VIOLATION_PATTERNS) {
219
- if (pattern.test(content)) {
220
- issues.push({ file, detail: "코드에서 도출 가능한 정보 포함" });
221
- break;
222
- }
223
- }
224
- }
225
-
226
- return makeCheckResult(
227
- "rule-violation",
228
- "저장 규칙 위반",
229
- SEVERITY.P1,
230
- false,
231
- issues,
232
- );
233
- }
234
-
235
- function checkStaleReferences(memoryDir, projectDir) {
236
- const files = safeReadDir(memoryDir).filter(
237
- (f) => f.endsWith(".md") && f !== "MEMORY.md",
238
- );
239
- const issues = [];
240
- const fileRefRe = /(?:^|\s)([a-zA-Z][\w/.%-]+\.[a-z]{1,5})(?::(\d+))?/gu;
241
-
242
- for (const file of files) {
243
- const content = safeReadFile(join(memoryDir, file));
244
- if (content == null) continue;
245
-
246
- let m;
247
- const re = new RegExp(fileRefRe.source, fileRefRe.flags);
248
- while ((m = re.exec(content)) !== null) {
249
- const refPath = m[1];
250
- if (
251
- refPath.startsWith("http") ||
252
- refPath.startsWith("node:") ||
253
- refPath.includes("node_modules")
254
- )
255
- continue;
256
- const fullPath = resolve(projectDir, refPath);
257
- if (!existsSync(fullPath) && refPath.includes("/")) {
258
- issues.push({ file, detail: `존재하지 않는 파일 참조: ${refPath}` });
259
- }
260
- }
261
- }
262
-
263
- return makeCheckResult(
264
- "stale-references",
265
- "stale 참조",
266
- SEVERITY.P1,
267
- false,
268
- issues,
269
- );
270
- }
271
-
272
- function checkLanguageInconsistency(memoryDir) {
273
- const files = safeReadDir(memoryDir).filter(
274
- (f) => f.endsWith(".md") && f !== "MEMORY.md",
275
- );
276
- const issues = [];
277
-
278
- let koCount = 0;
279
- let enCount = 0;
280
-
281
- for (const file of files) {
282
- const content = safeReadFile(join(memoryDir, file));
283
- if (content == null) continue;
284
- const fm = parseFrontmatter(content);
285
- if (!fm.found) continue;
286
-
287
- const nameVal = fm.fields.name || "";
288
- const descVal = fm.fields.description || "";
289
- const combined = `${nameVal} ${descVal}`;
290
-
291
- if (HANGUL_RE.test(combined)) koCount++;
292
- else if (LATIN_RE.test(combined)) enCount++;
293
- }
294
-
295
- const majorLang = koCount >= enCount ? "ko" : "en";
296
- const isKoMajor = majorLang === "ko";
297
-
298
- for (const file of files) {
299
- const content = safeReadFile(join(memoryDir, file));
300
- if (content == null) continue;
301
- const fm = parseFrontmatter(content);
302
- if (!fm.found || !fm.fields.name) continue;
303
-
304
- const combined = `${fm.fields.name} ${fm.fields.description || ""}`;
305
- const hasKo = HANGUL_RE.test(combined);
306
- const hasEn = LATIN_RE.test(combined);
307
-
308
- if (isKoMajor && !hasKo && hasEn) {
309
- issues.push({ file, detail: `다수 언어(한국어)와 불일치 — 영어 전용` });
310
- } else if (!isKoMajor && hasKo && !hasEn) {
311
- issues.push({ file, detail: `다수 언어(영어)와 불일치 — 한국어 전용` });
312
- }
313
- }
314
-
315
- return makeCheckResult(
316
- "language-inconsistency",
317
- "언어 혼용",
318
- SEVERITY.P2,
319
- false,
320
- issues,
321
- );
322
- }
323
-
324
- function checkOversizedFiles(memoryDir) {
325
- const files = safeReadDir(memoryDir).filter(
326
- (f) => f.endsWith(".md") && f !== "MEMORY.md",
327
- );
328
- const issues = [];
329
-
330
- for (const file of files) {
331
- const content = safeReadFile(join(memoryDir, file));
332
- if (content == null) continue;
333
- const lineCount = content.split("\n").length;
334
- if (lineCount > 50) {
335
- issues.push({ file, detail: `${lineCount}줄 (50줄 초과)` });
336
- }
337
- }
338
-
339
- return makeCheckResult(
340
- "oversized-files",
341
- "과대 파일",
342
- SEVERITY.P2,
343
- false,
344
- issues,
345
- );
346
- }
347
-
348
- function checkMissingUserMemory(memoryDir) {
349
- const files = safeReadDir(memoryDir).filter(
350
- (f) => f.endsWith(".md") && f !== "MEMORY.md",
351
- );
352
- const issues = [];
353
-
354
- const hasUserType = files.some((file) => {
355
- const content = safeReadFile(join(memoryDir, file));
356
- if (content == null) return false;
357
- const fm = parseFrontmatter(content);
358
- return fm.found && fm.fields.type === "user";
359
- });
360
-
361
- if (!hasUserType && files.length > 0) {
362
- issues.push({
363
- file: "memory/",
364
- detail: "type: user 메모리 파일 없음 — 사용자 프로필 미등록",
365
- });
366
- }
367
-
368
- return makeCheckResult(
369
- "missing-user-memory",
370
- "user 타입 메모리 부재",
371
- SEVERITY.P2,
372
- true,
373
- issues,
374
- );
375
- }
376
-
377
- // ── Fix Functions ──
378
-
379
- function createBackup(files, backupDir) {
380
- const timestamp = new Date()
381
- .toISOString()
382
- .replace(/[:.]/gu, "-")
383
- .slice(0, 19);
384
- const dir = join(backupDir, timestamp);
385
- mkdirSync(dir, { recursive: true });
386
-
387
- const backed = [];
388
- for (const src of files) {
389
- if (!existsSync(src)) continue;
390
- const dst = join(dir, basename(src));
391
- copyFileSync(src, dst);
392
- backed.push({ src, dst });
393
- }
394
-
395
- writeFileSync(
396
- join(dir, "manifest.json"),
397
- JSON.stringify(
398
- {
399
- timestamp,
400
- files: backed,
401
- restore: "tfx memory-doctor --undo",
402
- },
403
- null,
404
- 2,
405
- ),
406
- "utf8",
407
- );
408
-
409
- return { dir, files: backed };
410
- }
411
-
412
- function fixOrphanFiles(memoryDir, issues) {
413
- const indexPath = join(memoryDir, "MEMORY.md");
414
- const content = safeReadFile(indexPath);
415
- if (content == null) return { action: "skipped", fixed: [] };
416
-
417
- const additions = issues.map(({ file }) => {
418
- const fm = parseFrontmatter(safeReadFile(join(memoryDir, file)) || "");
419
- const desc = fm.fields.description || "(설명 없음)";
420
- return `- [${fm.fields.name || file}](${file}) — ${desc}`;
421
- });
422
-
423
- if (additions.length === 0) return { action: "unchanged", fixed: [] };
424
-
425
- const separator = content.endsWith("\n") ? "" : "\n";
426
- writeFileSync(
427
- indexPath,
428
- `${content}${separator}${additions.join("\n")}\n`,
429
- "utf8",
430
- );
431
-
432
- return { action: "fixed", fixed: issues.map((i) => i.file) };
433
- }
434
-
435
- function fixPathsYamlBug(rulesDir, issues) {
436
- const fixed = [];
437
-
438
- for (const { file } of issues) {
439
- const filePath = join(rulesDir, file);
440
- const content = safeReadFile(filePath);
441
- if (content == null) continue;
442
-
443
- const fmMatch = FRONTMATTER_RE.exec(content);
444
- if (!fmMatch) continue;
445
-
446
- const fmRaw = fmMatch[1];
447
- const pathsMatch = PATHS_YAML_RE.exec(fmRaw);
448
- if (!pathsMatch) continue;
449
-
450
- const values = [];
451
- for (const line of pathsMatch[0].split("\n")) {
452
- const trimmed = line
453
- .replace(/^\s*-\s*/, "")
454
- .replace(/^["']|["']$/gu, "")
455
- .trim();
456
- if (trimmed && trimmed !== "paths:") values.push(trimmed);
457
- }
458
-
459
- if (values.length === 0) continue;
460
-
461
- const globsLine = `globs: ${values.join(", ")}`;
462
- const nextFm = fmRaw.replace(pathsMatch[0], globsLine);
463
- const nextContent = `---\n${nextFm}\n---${content.slice(fmMatch[0].length)}`;
464
- writeFileSync(filePath, nextContent, "utf8");
465
- fixed.push(file);
466
- }
467
-
468
- return { action: fixed.length > 0 ? "fixed" : "unchanged", fixed };
469
- }
470
-
471
- function fixTrifluxResidue(issues) {
472
- const fixed = [];
473
-
474
- for (const { file } of issues) {
475
- try {
476
- const fullPath = resolve(
477
- file.replace(/^~\/\.claude/u, join(process.env.HOME || "", ".claude")),
478
- );
479
- if (!existsSync(fullPath)) continue;
480
-
481
- const archiveDir = join(resolve(".tfx", "archive"));
482
- mkdirSync(archiveDir, { recursive: true });
483
- renameSync(fullPath, join(archiveDir, basename(fullPath)));
484
- fixed.push(file);
485
- } catch {
486
- /* skip unresolvable */
487
- }
488
- }
489
-
490
- return { action: fixed.length > 0 ? "fixed" : "unchanged", fixed };
491
- }
492
-
493
- // ── Health Score ──
494
-
495
- function computeHealthScore(checks) {
496
- let penalty = 0;
497
- for (const check of checks) {
498
- if (check.error != null) continue;
499
- const count = check.issues.length;
500
- const w = WEIGHT[check.severity] || 0;
501
- penalty += count * w;
502
- }
503
- return clamp(Number((10 - penalty).toFixed(1)), 0, 10);
504
- }
505
-
506
- // ── Factory ──
507
-
508
- const ALL_CHECKS = [
509
- checkOrphanFiles,
510
- checkPathsYamlBug,
511
- checkTrifluxResidue,
512
- checkRuleViolation,
513
- checkStaleReferences,
514
- checkLanguageInconsistency,
515
- checkOversizedFiles,
516
- checkMissingUserMemory,
517
- ];
518
-
519
- const FIX_MAP = {
520
- "orphan-files": fixOrphanFiles,
521
- "paths-yaml-bug": fixPathsYamlBug,
522
- "triflux-residue": fixTrifluxResidue,
523
- };
524
-
525
- export function createMemoryDoctor(options = {}) {
526
- const memoryDir = resolve(options.memoryDir || "");
527
- const rulesDir = resolve(options.rulesDir || "");
528
- const projectDir = resolve(options.projectDir || process.cwd());
529
- const claudeDir = resolve(options.claudeDir || "");
530
- const backupDir = resolve(
531
- options.backupDir || join(projectDir, ".tfx", "backups"),
532
- );
533
-
534
- function scan() {
535
- const checks = ALL_CHECKS.map((fn) => {
536
- try {
537
- if (fn === checkOrphanFiles) return fn(memoryDir);
538
- if (fn === checkPathsYamlBug) return fn(rulesDir);
539
- if (fn === checkTrifluxResidue) return fn(projectDir, claudeDir);
540
- if (fn === checkRuleViolation) return fn(memoryDir);
541
- if (fn === checkStaleReferences) return fn(memoryDir, projectDir);
542
- if (fn === checkLanguageInconsistency) return fn(memoryDir);
543
- if (fn === checkOversizedFiles) return fn(memoryDir);
544
- if (fn === checkMissingUserMemory) return fn(memoryDir);
545
- return fn(memoryDir);
546
- } catch (err) {
547
- return makeCheckResult(
548
- fn.name
549
- .replace("check", "")
550
- .replace(/([A-Z])/gu, "-$1")
551
- .toLowerCase()
552
- .replace(/^-/u, ""),
553
- fn.name,
554
- SEVERITY.P2,
555
- false,
556
- [],
557
- err.message,
558
- );
559
- }
560
- });
561
-
562
- const healthScore = computeHealthScore(checks);
563
- const summary = {
564
- p0: checks.filter((c) => c.severity === SEVERITY.P0 && !c.passed).length,
565
- p1: checks.filter((c) => c.severity === SEVERITY.P1 && !c.passed).length,
566
- p2: checks.filter((c) => c.severity === SEVERITY.P2 && !c.passed).length,
567
- };
568
-
569
- return Object.freeze({ checks, healthScore, summary });
570
- }
571
-
572
- function fix(checkId, opts = {}) {
573
- if (isCI() && !opts.force) {
574
- return { action: "skipped_ci", backup: null, fixed: [] };
575
- }
576
-
577
- const { checks } = scan();
578
- const check = checks.find((c) => c.id === checkId);
579
- if (!check || check.passed)
580
- return { action: "no_issues", backup: null, fixed: [] };
581
- if (!check.autofix)
582
- return { action: "manual_only", backup: null, fixed: [] };
583
-
584
- const fixFn = FIX_MAP[checkId];
585
- if (!fixFn) return { action: "no_fixer", backup: null, fixed: [] };
586
-
587
- const affectedFiles = check.issues
588
- .map((i) => {
589
- const candidate = join(
590
- checkId === "orphan-files" ? memoryDir : rulesDir,
591
- i.file,
592
- );
593
- return existsSync(candidate) ? candidate : null;
594
- })
595
- .filter(Boolean);
596
-
597
- const indexFile =
598
- checkId === "orphan-files" ? join(memoryDir, "MEMORY.md") : null;
599
- const backupFiles = indexFile
600
- ? [indexFile, ...affectedFiles]
601
- : affectedFiles;
602
-
603
- const backup =
604
- backupFiles.length > 0 ? createBackup(backupFiles, backupDir) : null;
605
-
606
- const dir =
607
- checkId === "orphan-files"
608
- ? memoryDir
609
- : checkId === "paths-yaml-bug"
610
- ? rulesDir
611
- : projectDir;
612
- const result = fixFn(dir, check.issues);
613
-
614
- return { ...result, backup: backup?.dir || null };
615
- }
616
-
617
- function fixAll(opts = {}) {
618
- if (isCI() && !opts.force) {
619
- return { results: [], backup: null };
620
- }
621
-
622
- const { checks } = scan();
623
- const targets = checks.filter((c) => {
624
- if (opts.severity && c.severity !== opts.severity) return false;
625
- return c.autofix && !c.passed;
626
- });
627
-
628
- const results = targets.map((c) => ({ id: c.id, ...fix(c.id, opts) }));
629
- return { results };
630
- }
631
-
632
- return Object.freeze({
633
- scan,
634
- fix,
635
- fixAll,
636
- getHealthScore: computeHealthScore,
637
- });
638
- }
639
-
640
- export { computeHealthScore, isCI, parseFrontmatter };
@@ -1,70 +0,0 @@
1
- const FAILURE_STATUSES = new Set(["quota_hit", "error"]);
2
-
3
- function buildFailureRecord(result = {}) {
4
- const failure = {
5
- id: result.id ?? "unknown",
6
- status: result.status ?? "unknown",
7
- };
8
-
9
- if (Number.isFinite(result.http)) {
10
- failure.http = result.http;
11
- }
12
-
13
- if (typeof result.message === "string" && result.message.length > 0) {
14
- failure.message = result.message;
15
- }
16
-
17
- if (result.headers && Object.keys(result.headers).length > 0) {
18
- failure.headers = result.headers;
19
- }
20
-
21
- return failure;
22
- }
23
-
24
- export function summarizeQuotaResults(results = []) {
25
- const metrics = {
26
- checked: results.length,
27
- ok: 0,
28
- quotaHit: 0,
29
- error: 0,
30
- failed: 0,
31
- };
32
- const failures = [];
33
-
34
- for (const result of results) {
35
- const status = result?.status ?? "unknown";
36
-
37
- if (status === "ok") {
38
- metrics.ok += 1;
39
- } else if (status === "quota_hit") {
40
- metrics.quotaHit += 1;
41
- } else if (status === "error") {
42
- metrics.error += 1;
43
- }
44
-
45
- if (FAILURE_STATUSES.has(status)) {
46
- failures.push(buildFailureRecord(result));
47
- }
48
- }
49
-
50
- metrics.failed = failures.length;
51
- return { metrics, failures };
52
- }
53
-
54
- export function logQuotaRefreshFailures(logger, results = []) {
55
- const { metrics, failures } = summarizeQuotaResults(results);
56
- if (failures.length === 0) {
57
- return { logged: false, metrics, failures };
58
- }
59
-
60
- logger.warn(
61
- {
62
- tag: "hub-quota",
63
- metrics,
64
- failures,
65
- },
66
- "broker.quota_refresh_degraded",
67
- );
68
-
69
- return { logged: true, metrics, failures };
70
- }