triflux 10.3.4 → 10.7.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/LICENSE +21 -21
- package/bin/tfx-doctor-tui.mjs +1 -1
- package/bin/tfx-doctor.mjs +6 -1
- package/bin/tfx-profile.mjs +1 -1
- package/bin/tfx-setup-tui.mjs +1 -1
- package/bin/tfx-setup.mjs +6 -1
- package/bin/triflux.mjs +2396 -1140
- package/hooks/agent-route-guard.mjs +12 -8
- package/hooks/cross-review-tracker.mjs +21 -8
- package/hooks/error-context.mjs +19 -7
- package/hooks/hook-adaptive-collector.mjs +18 -16
- package/hooks/hook-manager.mjs +93 -32
- package/hooks/hook-orchestrator.mjs +108 -24
- package/hooks/hook-registry.json +11 -0
- package/hooks/keyword-rules.json +6 -10
- package/hooks/lib/resolve-root.mjs +1 -1
- package/hooks/mcp-config-watcher.mjs +6 -2
- package/hooks/pipeline-stop.mjs +3 -6
- package/hooks/safety-guard.mjs +99 -28
- package/hooks/session-start-fast.mjs +143 -0
- package/hooks/subagent-verifier.mjs +5 -4
- package/hub/account-broker.mjs +256 -60
- package/hub/adaptive-diagnostic.mjs +75 -48
- package/hub/adaptive-inject.mjs +95 -57
- package/hub/adaptive-memory.mjs +156 -42
- package/hub/adaptive.mjs +60 -31
- package/hub/assign-callbacks.mjs +67 -30
- package/hub/bridge.mjs +0 -1
- package/hub/cli-adapter-base.mjs +200 -48
- package/hub/codex-adapter.mjs +76 -96
- package/hub/codex-compat.mjs +3 -3
- package/hub/codex-preflight.mjs +63 -37
- package/hub/delegator/contracts.mjs +19 -23
- package/hub/delegator/index.mjs +3 -3
- package/hub/delegator/service.mjs +88 -64
- package/hub/delegator/tool-definitions.mjs +5 -5
- package/hub/fullcycle.mjs +33 -17
- package/hub/gemini-adapter.mjs +69 -94
- package/hub/hitl.mjs +89 -30
- package/hub/intent.mjs +161 -38
- package/hub/lib/cache-guard.mjs +43 -17
- package/hub/lib/mcp-response-cache.mjs +66 -32
- package/hub/lib/memory-store.mjs +285 -111
- package/hub/lib/path-utils.mjs +35 -37
- package/hub/lib/process-utils.mjs +106 -37
- package/hub/lib/spawn-trace.mjs +527 -0
- package/hub/lib/ssh-command.mjs +34 -4
- package/hub/lib/ssh-retry.mjs +5 -1
- package/hub/lib/uuidv7.mjs +4 -3
- package/hub/memory-doctor.mjs +266 -106
- package/hub/middleware/request-logger.mjs +61 -34
- package/hub/paths.mjs +9 -9
- package/hub/pipeline/gates/confidence.mjs +34 -15
- package/hub/pipeline/gates/consensus.mjs +27 -15
- package/hub/pipeline/gates/index.mjs +7 -3
- package/hub/pipeline/gates/selfcheck.mjs +57 -19
- package/hub/pipeline/index.mjs +77 -42
- package/hub/pipeline/state.mjs +10 -10
- package/hub/pipeline/transitions.mjs +40 -23
- package/hub/platform.mjs +57 -48
- package/hub/promote-penalties.mjs +25 -7
- package/hub/quality/deslop.mjs +70 -49
- package/hub/research.mjs +32 -25
- package/hub/router.mjs +240 -107
- package/hub/routing/complexity.mjs +132 -29
- package/hub/routing/index.mjs +17 -12
- package/hub/routing/q-learning.mjs +76 -28
- package/hub/server.mjs +4 -4
- package/hub/session-fingerprint.mjs +126 -60
- package/hub/state.mjs +84 -43
- package/hub/store-adapter.mjs +59 -26
- package/hub/store.mjs +356 -153
- package/hub/team/agent-map.json +22 -7
- package/hub/team/ansi.mjs +186 -122
- package/hub/team/backend.mjs +28 -10
- package/hub/team/cli/commands/attach.mjs +29 -9
- package/hub/team/cli/commands/control.mjs +29 -8
- package/hub/team/cli/commands/debug.mjs +32 -11
- package/hub/team/cli/commands/focus.mjs +38 -11
- package/hub/team/cli/commands/interrupt.mjs +18 -6
- package/hub/team/cli/commands/kill.mjs +16 -5
- package/hub/team/cli/commands/list.mjs +11 -4
- package/hub/team/cli/commands/send.mjs +19 -6
- package/hub/team/cli/commands/start/index.mjs +154 -31
- package/hub/team/cli/commands/start/parse-args.mjs +38 -11
- package/hub/team/cli/commands/start/start-headless.mjs +112 -36
- package/hub/team/cli/commands/start/start-in-process.mjs +12 -2
- package/hub/team/cli/commands/start/start-mux.mjs +70 -21
- package/hub/team/cli/commands/start/start-wt.mjs +29 -12
- package/hub/team/cli/commands/status.mjs +43 -14
- package/hub/team/cli/commands/stop.mjs +11 -4
- package/hub/team/cli/commands/task.mjs +8 -3
- package/hub/team/cli/commands/tasks.mjs +1 -1
- package/hub/team/cli/index.mjs +2 -2
- package/hub/team/cli/manifest.mjs +38 -8
- package/hub/team/cli/render.mjs +30 -8
- package/hub/team/cli/services/attach-fallback.mjs +31 -11
- package/hub/team/cli/services/hub-client.mjs +42 -14
- package/hub/team/cli/services/member-selector.mjs +11 -4
- package/hub/team/cli/services/native-control.mjs +48 -21
- package/hub/team/cli/services/runtime-mode.mjs +2 -1
- package/hub/team/cli/services/state-store.mjs +25 -8
- package/hub/team/cli/services/task-model.mjs +16 -6
- package/hub/team/conductor-mesh-bridge.mjs +24 -23
- package/hub/team/conductor.mjs +8 -4
- package/hub/team/dashboard-anchor.mjs +4 -5
- package/hub/team/dashboard-layout.mjs +3 -1
- package/hub/team/dashboard-open.mjs +41 -21
- package/hub/team/dashboard.mjs +76 -28
- package/hub/team/event-log.mjs +18 -10
- package/hub/team/handoff.mjs +31 -15
- package/hub/team/headless.mjs +2 -1
- package/hub/team/health-probe.mjs +69 -54
- package/hub/team/launcher-template.mjs +16 -13
- package/hub/team/native-supervisor.mjs +65 -21
- package/hub/team/native.mjs +74 -35
- package/hub/team/nativeProxy.mjs +184 -113
- package/hub/team/notify.mjs +119 -76
- package/hub/team/orchestrator.mjs +9 -4
- package/hub/team/pane.mjs +12 -7
- package/hub/team/process-cleanup.mjs +25 -16
- package/hub/team/psmux.mjs +491 -201
- package/hub/team/remote-probe.mjs +68 -52
- package/hub/team/remote-session.mjs +117 -59
- package/hub/team/remote-watcher.mjs +61 -33
- package/hub/team/routing.mjs +51 -25
- package/hub/team/runtime-strategy.mjs +3 -1
- package/hub/team/session.mjs +98 -34
- package/hub/team/staleState.mjs +72 -30
- package/hub/team/swarm-locks.mjs +15 -13
- package/hub/team/swarm-planner.mjs +32 -21
- package/hub/team/swarm-reconciler.mjs +48 -23
- package/hub/team/tui-lite.mjs +266 -68
- package/hub/team/tui-remote-adapter.mjs +14 -10
- package/hub/team/tui-viewer.mjs +99 -43
- package/hub/team/tui.mjs +708 -271
- package/hub/team/worktree-lifecycle.mjs +152 -58
- package/hub/team/wt-manager.mjs +24 -14
- package/hub/token-mode.mjs +71 -71
- package/hub/tray.mjs +66 -23
- package/hub/workers/claude-worker.mjs +162 -118
- package/hub/workers/codex-mcp.mjs +192 -141
- package/hub/workers/delegator-mcp.mjs +507 -333
- package/hub/workers/factory.mjs +8 -8
- package/hub/workers/gemini-worker.mjs +115 -84
- package/hub/workers/interface.mjs +6 -1
- package/hub/workers/worker-utils.mjs +21 -14
- package/hud/colors.mjs +27 -9
- package/hud/constants.mjs +162 -26
- package/hud/context-monitor.mjs +82 -41
- package/hud/hud-qos-status.mjs +129 -49
- package/hud/mission-board.mjs +6 -3
- package/hud/providers/claude.mjs +226 -115
- package/hud/providers/codex.mjs +62 -22
- package/hud/providers/gemini.mjs +168 -56
- package/hud/renderers.mjs +384 -119
- package/hud/terminal.mjs +101 -31
- package/hud/utils.mjs +78 -38
- package/mesh/index.mjs +11 -5
- package/mesh/mesh-budget.mjs +18 -9
- package/mesh/mesh-heartbeat.mjs +1 -1
- package/mesh/mesh-queue.mjs +3 -5
- package/mesh/mesh-router.mjs +5 -4
- package/package.json +2 -1
- package/scripts/__tests__/gen-skill-docs.test.mjs +36 -7
- package/scripts/__tests__/keyword-detector.test.mjs +77 -28
- package/scripts/__tests__/mcp-guard-engine.test.mjs +58 -20
- package/scripts/__tests__/remote-spawn-transfer.test.mjs +30 -19
- package/scripts/__tests__/remote-spawn.test.mjs +10 -4
- package/scripts/__tests__/session-start-fast.test.mjs +36 -0
- package/scripts/__tests__/skill-template.test.mjs +98 -50
- package/scripts/__tests__/smoke.test.mjs +1 -1
- package/scripts/__tests__/spawn-trace.test.mjs +102 -0
- package/scripts/__tests__/tfx-doctor-diagnose.test.mjs +48 -0
- package/scripts/cache-doctor.mjs +11 -4
- package/scripts/cache-warmup.mjs +96 -37
- package/scripts/claudemd-sync.mjs +27 -17
- package/scripts/codex-gateway-preflight.mjs +52 -37
- package/scripts/codex-mcp-gateway-sync.mjs +59 -39
- package/scripts/completions/tfx.bash +47 -47
- package/scripts/completions/tfx.fish +44 -44
- package/scripts/completions/tfx.zsh +83 -83
- package/scripts/config-audit.mjs +232 -0
- package/scripts/convert-to-tmpl.mjs +54 -0
- package/scripts/cross-review-gate.mjs +35 -12
- package/scripts/cross-review-tracker.mjs +21 -8
- package/scripts/demo.mjs +35 -17
- package/scripts/doctor-diagnose.mjs +284 -0
- package/scripts/gen-skill-docs.mjs +7 -2
- package/scripts/gen-skill-manifest.mjs +2 -1
- package/scripts/headless-guard.mjs +86 -48
- package/scripts/hub-ensure.mjs +45 -26
- package/scripts/keyword-detector.mjs +41 -20
- package/scripts/keyword-rules-expander.mjs +47 -30
- package/scripts/lib/claudemd-scanner.mjs +6 -1
- package/scripts/lib/context.mjs +3 -3
- package/scripts/lib/cross-review-utils.mjs +6 -3
- package/scripts/lib/env-probe.mjs +47 -28
- package/scripts/lib/gemini-profiles.mjs +44 -10
- package/scripts/lib/handoff.mjs +33 -17
- package/scripts/lib/hook-utils.mjs +8 -6
- package/scripts/lib/keyword-rules.mjs +43 -19
- package/scripts/lib/logger.mjs +24 -24
- package/scripts/lib/mcp-filter.mjs +377 -239
- package/scripts/lib/mcp-guard-engine.mjs +194 -79
- package/scripts/lib/mcp-manifest.mjs +23 -13
- package/scripts/lib/mcp-server-catalog.mjs +300 -63
- package/scripts/lib/psmux-info.mjs +11 -6
- package/scripts/lib/remote-spawn-transfer.mjs +44 -14
- package/scripts/lib/skill-template.mjs +30 -7
- package/scripts/mcp-check.mjs +58 -39
- package/scripts/mcp-gateway-config.mjs +83 -39
- package/scripts/mcp-gateway-ensure.mjs +43 -35
- package/scripts/mcp-gateway-integration-test.mjs +70 -58
- package/scripts/mcp-gateway-start.mjs +126 -60
- package/scripts/mcp-gateway-verify.mjs +24 -22
- package/scripts/mcp-safety-guard.mjs +44 -11
- package/scripts/notion-read.mjs +199 -84
- package/scripts/pack.mjs +94 -89
- package/scripts/preflight-cache.mjs +27 -10
- package/scripts/preinstall.mjs +42 -13
- package/scripts/remote-spawn.mjs +309 -94
- package/scripts/run.cjs +8 -5
- package/scripts/session-spawn-helper.mjs +130 -39
- package/scripts/session-stale-cleanup.mjs +123 -0
- package/scripts/setup.mjs +941 -492
- package/scripts/test-lock.mjs +20 -7
- package/scripts/test-tfx-route-no-claude-native.mjs +16 -12
- package/scripts/tfx-batch-stats.mjs +32 -11
- package/scripts/tfx-gate-activate.mjs +11 -4
- package/scripts/tfx-route-post.mjs +87 -20
- package/scripts/tfx-route-worker.mjs +57 -51
- package/scripts/tfx-route.sh +41 -124
- package/scripts/tmp-cleanup.mjs +21 -7
- package/scripts/token-snapshot.mjs +204 -85
- package/skills/.omc/state/agent-replay-8f0e10a9-9693-4410-96f5-a6b07e8ed995.jsonl +1 -0
- package/skills/.omc/state/idle-notif-cooldown.json +3 -0
- package/skills/.omc/state/last-tool-error.json +7 -0
- package/skills/.omc/state/subagent-tracking.json +7 -0
- package/skills/_templates/base.md +1 -6
- package/skills/merge-worktree/SKILL.md.tmpl +144 -0
- package/skills/shared/telemetry-segment.md +6 -0
- package/skills/star-prompt/SKILL.md.tmpl +222 -0
- package/skills/tfx-analysis/SKILL.md.tmpl +107 -0
- package/skills/tfx-analysis/skill.json +1 -6
- package/skills/tfx-auto/SKILL.md +1 -0
- package/skills/tfx-auto-codex/SKILL.md.tmpl +106 -0
- package/skills/tfx-auto-codex/skill.json +1 -3
- package/skills/tfx-autopilot/SKILL.md.tmpl +116 -0
- package/skills/tfx-autopilot/skill.json +1 -5
- package/skills/tfx-autoresearch/SKILL.md.tmpl +136 -0
- package/skills/tfx-autoroute/SKILL.md.tmpl +189 -0
- package/skills/tfx-autoroute/skill.json +1 -7
- package/skills/tfx-codex/SKILL.md +1 -0
- package/skills/tfx-codex/skill.json +1 -3
- package/skills/tfx-codex-swarm/SKILL.md.tmpl +16 -0
- package/skills/tfx-codex-swarm/evals/evals.json +1 -1
- package/skills/tfx-codex-swarm/skill.json +1 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/benchmark.json +54 -12
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/with_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/full-swarm-all-prds/without_skill/grading.json +35 -7
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/with_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/implicit-swarm-no-keywords/without_skill/grading.json +25 -5
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/with_skill/grading.json +20 -4
- package/skills/tfx-codex-swarm-workspace/iteration-1/selective-spawn-with-override/without_skill/grading.json +16 -4
- package/skills/tfx-consensus/SKILL.md.tmpl +146 -0
- package/skills/tfx-debate/SKILL.md.tmpl +192 -0
- package/skills/tfx-debate/skill.json +1 -7
- package/skills/tfx-deep-analysis/SKILL.md.tmpl +228 -0
- package/skills/tfx-deep-analysis/skill.json +1 -5
- package/skills/tfx-deep-interview/SKILL.md.tmpl +203 -0
- package/skills/tfx-deep-plan/SKILL.md.tmpl +282 -0
- package/skills/tfx-deep-qa/SKILL.md.tmpl +165 -0
- package/skills/tfx-deep-qa/skill.json +1 -6
- package/skills/tfx-deep-research/SKILL.md.tmpl +217 -0
- package/skills/tfx-deep-review/SKILL.md.tmpl +179 -0
- package/skills/tfx-doctor/SKILL.md +21 -0
- package/skills/tfx-doctor/SKILL.md.tmpl +172 -0
- package/skills/tfx-doctor/skill.json +1 -3
- package/skills/tfx-find/SKILL.md +1 -0
- package/skills/tfx-forge/SKILL.md.tmpl +187 -0
- package/skills/tfx-fullcycle/SKILL.md.tmpl +286 -0
- package/skills/tfx-fullcycle/skill.json +1 -6
- package/skills/tfx-gemini/SKILL.md.tmpl +91 -0
- package/skills/tfx-gemini/skill.json +1 -3
- package/skills/tfx-hooks/SKILL.md.tmpl +216 -0
- package/skills/tfx-hooks/skill.json +1 -3
- package/skills/tfx-hub/SKILL.md.tmpl +212 -0
- package/skills/tfx-hub/skill.json +1 -3
- package/skills/tfx-index/SKILL.md +1 -0
- package/skills/tfx-index/skill.json +1 -6
- package/skills/tfx-interview/SKILL.md.tmpl +285 -0
- package/skills/tfx-multi/SKILL.md.tmpl +183 -0
- package/skills/tfx-multi/skill.json +1 -3
- package/skills/tfx-panel/SKILL.md.tmpl +189 -0
- package/skills/tfx-panel/skill.json +1 -7
- package/skills/tfx-persist/SKILL.md.tmpl +270 -0
- package/skills/tfx-persist/skill.json +1 -7
- package/skills/tfx-plan/SKILL.md +1 -0
- package/skills/tfx-plan/skill.json +1 -6
- package/skills/tfx-profile/SKILL.md.tmpl +239 -0
- package/skills/tfx-profile/skill.json +1 -3
- package/skills/tfx-prune/SKILL.md.tmpl +200 -0
- package/skills/tfx-prune/skill.json +1 -7
- package/skills/tfx-psmux-rules/SKILL.md.tmpl +326 -0
- package/skills/tfx-psmux-rules/skill.json +1 -4
- package/skills/tfx-qa/SKILL.md +1 -0
- package/skills/tfx-qa/skill.json +1 -6
- package/skills/tfx-ralph/SKILL.md.tmpl +28 -0
- package/skills/tfx-ralph/skill.json +1 -4
- package/skills/tfx-remote-setup/SKILL.md.tmpl +576 -0
- package/skills/tfx-remote-setup/skill.json +1 -3
- package/skills/tfx-remote-spawn/SKILL.md.tmpl +263 -0
- package/skills/tfx-remote-spawn/references/hosts.json +16 -0
- package/skills/tfx-remote-spawn/skill.json +1 -4
- package/skills/tfx-research/SKILL.md +1 -0
- package/skills/tfx-review/SKILL.md +1 -0
- package/skills/tfx-review/skill.json +1 -6
- package/skills/tfx-setup/SKILL.md.tmpl +504 -0
- package/skills/tfx-setup/skill.json +1 -3
- package/skills/tfx-swarm/SKILL.md +22 -0
- package/skills/tfx-swarm/SKILL.md.tmpl +218 -0
- package/tui/codex-profile.mjs +88 -33
- package/tui/core.mjs +45 -15
- package/tui/doctor.mjs +75 -28
- package/tui/gemini-profile.mjs +74 -29
- package/tui/monitor-data.mjs +8 -4
- package/tui/monitor.mjs +71 -27
- package/tui/setup.mjs +133 -42
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import { dirname, resolve } from
|
|
3
|
-
import { fileURLToPath } from
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
4
|
|
|
5
|
-
import { normalizeError } from
|
|
5
|
+
import { normalizeError } from "./reflexion.mjs";
|
|
6
6
|
|
|
7
7
|
const DEFAULT_KNOWN_ERRORS_PATH = resolve(
|
|
8
8
|
dirname(fileURLToPath(import.meta.url)),
|
|
9
|
-
|
|
9
|
+
"lib/known-errors.json",
|
|
10
10
|
);
|
|
11
11
|
const DEFAULT_CONFIDENCE = 0.5;
|
|
12
12
|
const ADAPTIVE_CONFIDENCE_STEP = 0.1;
|
|
@@ -18,15 +18,17 @@ function clone(value) {
|
|
|
18
18
|
|
|
19
19
|
function pickString(...values) {
|
|
20
20
|
for (const value of values) {
|
|
21
|
-
if (typeof value ===
|
|
21
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
22
22
|
}
|
|
23
|
-
return
|
|
23
|
+
return "";
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
function pickObject(...values) {
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
return (
|
|
28
|
+
values.find(
|
|
29
|
+
(value) => value && typeof value === "object" && !Array.isArray(value),
|
|
30
|
+
) || {}
|
|
31
|
+
);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
function clampConfidence(value, fallback = DEFAULT_CONFIDENCE) {
|
|
@@ -36,25 +38,27 @@ function clampConfidence(value, fallback = DEFAULT_CONFIDENCE) {
|
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
function normalizeLookup(text) {
|
|
39
|
-
return String(text ||
|
|
41
|
+
return String(text || "")
|
|
42
|
+
.trim()
|
|
43
|
+
.toLowerCase();
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
function readPathValue(source, path) {
|
|
43
47
|
if (!source || !path) return undefined;
|
|
44
48
|
return String(path)
|
|
45
|
-
.split(
|
|
49
|
+
.split(".")
|
|
46
50
|
.filter(Boolean)
|
|
47
51
|
.reduce((current, key) => current?.[key], source);
|
|
48
52
|
}
|
|
49
53
|
|
|
50
54
|
function renderTemplate(template, observation, signature) {
|
|
51
|
-
if (!template) return
|
|
55
|
+
if (!template) return "";
|
|
52
56
|
const context = pickObject(observation.context);
|
|
53
57
|
const dna = pickObject(observation.dna);
|
|
54
58
|
const dnaValue = signature.dna_factor
|
|
55
|
-
? readPathValue(dna, signature.dna_factor) ??
|
|
59
|
+
? (readPathValue(dna, signature.dna_factor) ??
|
|
56
60
|
readPathValue(context, signature.dna_factor) ??
|
|
57
|
-
readPathValue(observation, signature.dna_factor)
|
|
61
|
+
readPathValue(observation, signature.dna_factor))
|
|
58
62
|
: undefined;
|
|
59
63
|
const variables = {
|
|
60
64
|
...context,
|
|
@@ -64,7 +68,7 @@ function renderTemplate(template, observation, signature) {
|
|
|
64
68
|
};
|
|
65
69
|
return String(template).replace(/\{([^}]+)\}/gu, (match, key) => {
|
|
66
70
|
const value = variables[key];
|
|
67
|
-
return value == null || value ===
|
|
71
|
+
return value == null || value === "" ? match : String(value);
|
|
68
72
|
});
|
|
69
73
|
}
|
|
70
74
|
|
|
@@ -76,7 +80,7 @@ function buildKnownContextText(observation) {
|
|
|
76
80
|
pickString(observation.step),
|
|
77
81
|
]
|
|
78
82
|
.filter(Boolean)
|
|
79
|
-
.join(
|
|
83
|
+
.join(" "),
|
|
80
84
|
);
|
|
81
85
|
}
|
|
82
86
|
|
|
@@ -88,7 +92,7 @@ function buildErrorText(observation = {}) {
|
|
|
88
92
|
pickString(observation.message),
|
|
89
93
|
]
|
|
90
94
|
.filter(Boolean)
|
|
91
|
-
.join(
|
|
95
|
+
.join("\n")
|
|
92
96
|
.trim();
|
|
93
97
|
}
|
|
94
98
|
|
|
@@ -114,13 +118,13 @@ function compileSignatures(raw = {}) {
|
|
|
114
118
|
return Object.entries(raw.signatures || {}).map(([id, signature]) => ({
|
|
115
119
|
id,
|
|
116
120
|
...clone(signature),
|
|
117
|
-
matcher: new RegExp(String(signature.pattern ||
|
|
121
|
+
matcher: new RegExp(String(signature.pattern || ""), "iu"),
|
|
118
122
|
}));
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
export function loadKnownErrors(filePath = DEFAULT_KNOWN_ERRORS_PATH) {
|
|
122
126
|
try {
|
|
123
|
-
const parsed = JSON.parse(readFileSync(filePath,
|
|
127
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8"));
|
|
124
128
|
return {
|
|
125
129
|
path: filePath,
|
|
126
130
|
version: parsed.version ?? 1,
|
|
@@ -132,7 +136,10 @@ export function loadKnownErrors(filePath = DEFAULT_KNOWN_ERRORS_PATH) {
|
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
function scoreKnownMatch(signature, observation) {
|
|
135
|
-
if (
|
|
139
|
+
if (
|
|
140
|
+
!observation.errorText ||
|
|
141
|
+
!signature.matcher.test(observation.errorText)
|
|
142
|
+
) {
|
|
136
143
|
return null;
|
|
137
144
|
}
|
|
138
145
|
if (
|
|
@@ -157,10 +164,10 @@ function scoreKnownMatch(signature, observation) {
|
|
|
157
164
|
);
|
|
158
165
|
}
|
|
159
166
|
|
|
160
|
-
function severityFromConfidence(confidence, fallback =
|
|
161
|
-
if (confidence >= 0.9) return
|
|
162
|
-
if (confidence >= 0.75) return
|
|
163
|
-
if (confidence >= 0.55) return
|
|
167
|
+
function severityFromConfidence(confidence, fallback = "medium") {
|
|
168
|
+
if (confidence >= 0.9) return "critical";
|
|
169
|
+
if (confidence >= 0.75) return "high";
|
|
170
|
+
if (confidence >= 0.55) return "medium";
|
|
164
171
|
return fallback;
|
|
165
172
|
}
|
|
166
173
|
|
|
@@ -178,7 +185,7 @@ export function matchKnownError(catalog, observationInput = {}) {
|
|
|
178
185
|
const { signature, confidence } = matched;
|
|
179
186
|
return {
|
|
180
187
|
matched: true,
|
|
181
|
-
source:
|
|
188
|
+
source: "known",
|
|
182
189
|
signature_id: signature.id,
|
|
183
190
|
project_slug: observation.projectSlug,
|
|
184
191
|
error_pattern: observation.errorPattern,
|
|
@@ -187,7 +194,7 @@ export function matchKnownError(catalog, observationInput = {}) {
|
|
|
187
194
|
severity: signature.severity || severityFromConfidence(confidence),
|
|
188
195
|
tool: signature.tool || observation.tool,
|
|
189
196
|
context: signature.context || observation.contextText || null,
|
|
190
|
-
root_cause: signature.root_cause ||
|
|
197
|
+
root_cause: signature.root_cause || "known failure pattern",
|
|
191
198
|
rule: renderTemplate(signature.rule_template, observation, signature),
|
|
192
199
|
fix: signature.fix || null,
|
|
193
200
|
dna_factor: signature.dna_factor || null,
|
|
@@ -199,14 +206,21 @@ function resolveRuleStore(options = {}) {
|
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
function ensureAdaptiveRule(store, observation) {
|
|
202
|
-
if (
|
|
209
|
+
if (
|
|
210
|
+
!store?.findAdaptiveRule ||
|
|
211
|
+
!store?.addAdaptiveRule ||
|
|
212
|
+
!observation.projectSlug
|
|
213
|
+
) {
|
|
203
214
|
return null;
|
|
204
215
|
}
|
|
205
216
|
const identity = {
|
|
206
217
|
project_slug: observation.projectSlug,
|
|
207
218
|
pattern: observation.errorPattern,
|
|
208
219
|
};
|
|
209
|
-
const current = store.findAdaptiveRule(
|
|
220
|
+
const current = store.findAdaptiveRule(
|
|
221
|
+
identity.project_slug,
|
|
222
|
+
identity.pattern,
|
|
223
|
+
);
|
|
210
224
|
if (!current) {
|
|
211
225
|
return store.addAdaptiveRule(identity);
|
|
212
226
|
}
|
|
@@ -214,7 +228,10 @@ function ensureAdaptiveRule(store, observation) {
|
|
|
214
228
|
return store.updateRuleConfidence(
|
|
215
229
|
identity.project_slug,
|
|
216
230
|
identity.pattern,
|
|
217
|
-
Math.min(
|
|
231
|
+
Math.min(
|
|
232
|
+
MAX_ADAPTIVE_CONFIDENCE,
|
|
233
|
+
current.confidence + ADAPTIVE_CONFIDENCE_STEP,
|
|
234
|
+
),
|
|
218
235
|
{ hit_count_increment: 1 },
|
|
219
236
|
);
|
|
220
237
|
}
|
|
@@ -223,41 +240,45 @@ function buildAdaptiveDiagnosis(rule, observation) {
|
|
|
223
240
|
if (!rule) {
|
|
224
241
|
return {
|
|
225
242
|
matched: false,
|
|
226
|
-
source:
|
|
243
|
+
source: "novel",
|
|
227
244
|
project_slug: observation.projectSlug,
|
|
228
245
|
error_pattern: observation.errorPattern,
|
|
229
246
|
error_message: observation.errorText,
|
|
230
247
|
confidence: DEFAULT_CONFIDENCE,
|
|
231
|
-
severity:
|
|
232
|
-
root_cause:
|
|
233
|
-
rule:
|
|
248
|
+
severity: "low",
|
|
249
|
+
root_cause: "새로운 실패 패턴으로 분류됨",
|
|
250
|
+
rule: "",
|
|
234
251
|
fix: null,
|
|
235
252
|
};
|
|
236
253
|
}
|
|
237
|
-
const matched =
|
|
254
|
+
const matched =
|
|
255
|
+
Number(rule.hit_count || 0) > 1 ||
|
|
256
|
+
Number(rule.confidence || 0) > DEFAULT_CONFIDENCE;
|
|
238
257
|
const confidence = clampConfidence(rule.confidence, DEFAULT_CONFIDENCE);
|
|
239
258
|
return {
|
|
240
259
|
matched,
|
|
241
|
-
source: matched ?
|
|
260
|
+
source: matched ? "adaptive" : "novel",
|
|
242
261
|
project_slug: rule.project_slug,
|
|
243
262
|
error_pattern: rule.pattern,
|
|
244
263
|
error_message: observation.errorText,
|
|
245
264
|
confidence,
|
|
246
|
-
severity: severityFromConfidence(confidence, matched ?
|
|
265
|
+
severity: severityFromConfidence(confidence, matched ? "medium" : "low"),
|
|
247
266
|
root_cause: matched
|
|
248
|
-
?
|
|
249
|
-
:
|
|
267
|
+
? "반복 관측된 adaptive rule과 일치"
|
|
268
|
+
: "adaptive memory에 첫 관측으로 저장됨",
|
|
250
269
|
rule: matched
|
|
251
270
|
? `프로젝트 ${rule.project_slug}에서 동일 패턴이 ${rule.hit_count}회 관측되었습니다.`
|
|
252
271
|
: `프로젝트 ${rule.project_slug}의 adaptive memory에 패턴을 기록했습니다.`,
|
|
253
|
-
fix: matched
|
|
272
|
+
fix: matched
|
|
273
|
+
? "최근 성공한 수정/회피 전략을 재적용하세요."
|
|
274
|
+
: "추가 관측 후 adaptive rule을 승격하세요.",
|
|
254
275
|
adaptive_rule: clone(rule),
|
|
255
276
|
};
|
|
256
277
|
}
|
|
257
278
|
|
|
258
279
|
function createHealthyState(catalog) {
|
|
259
280
|
return {
|
|
260
|
-
state:
|
|
281
|
+
state: "healthy",
|
|
261
282
|
known_errors_count: catalog.signatures.length,
|
|
262
283
|
last_error: null,
|
|
263
284
|
};
|
|
@@ -265,24 +286,28 @@ function createHealthyState(catalog) {
|
|
|
265
286
|
|
|
266
287
|
function createDegradedState(error) {
|
|
267
288
|
return {
|
|
268
|
-
state:
|
|
289
|
+
state: "degraded",
|
|
269
290
|
known_errors_count: 0,
|
|
270
291
|
last_error: {
|
|
271
|
-
name: error?.name ||
|
|
272
|
-
message: error?.message ||
|
|
292
|
+
name: error?.name || "Error",
|
|
293
|
+
message: error?.message || "unknown adaptive diagnostic error",
|
|
273
294
|
},
|
|
274
295
|
};
|
|
275
296
|
}
|
|
276
297
|
|
|
277
298
|
export function createDiagnosticPipeline(options = {}) {
|
|
278
299
|
const store = resolveRuleStore(options);
|
|
279
|
-
let catalog = options.knownErrors
|
|
300
|
+
let catalog = options.knownErrors
|
|
301
|
+
? { signatures: compileSignatures({ signatures: options.knownErrors }) }
|
|
302
|
+
: null;
|
|
280
303
|
let health = catalog ? createHealthyState(catalog) : null;
|
|
281
304
|
|
|
282
305
|
function ensureCatalog() {
|
|
283
306
|
if (catalog) return catalog;
|
|
284
307
|
try {
|
|
285
|
-
catalog = loadKnownErrors(
|
|
308
|
+
catalog = loadKnownErrors(
|
|
309
|
+
options.knownErrorsPath || DEFAULT_KNOWN_ERRORS_PATH,
|
|
310
|
+
);
|
|
286
311
|
health = createHealthyState(catalog);
|
|
287
312
|
} catch (error) {
|
|
288
313
|
catalog = { signatures: [] };
|
|
@@ -303,11 +328,13 @@ export function createDiagnosticPipeline(options = {}) {
|
|
|
303
328
|
}
|
|
304
329
|
|
|
305
330
|
function getHealth() {
|
|
306
|
-
return clone(health || ensureCatalog() && health);
|
|
331
|
+
return clone(health || (ensureCatalog() && health));
|
|
307
332
|
}
|
|
308
333
|
|
|
309
334
|
function listKnownErrors() {
|
|
310
|
-
return (ensureCatalog().signatures || []).map(({ matcher, ...signature }) =>
|
|
335
|
+
return (ensureCatalog().signatures || []).map(({ matcher, ...signature }) =>
|
|
336
|
+
clone(signature),
|
|
337
|
+
);
|
|
311
338
|
}
|
|
312
339
|
|
|
313
340
|
return Object.freeze({
|
package/hub/adaptive-inject.mjs
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from
|
|
2
|
-
import { join, resolve } from
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
3
|
|
|
4
|
-
const SECTION_HEADING =
|
|
4
|
+
const SECTION_HEADING = "## Adaptive Rules (triflux auto-generated)";
|
|
5
5
|
const DEFAULT_MAX_RULES = 10;
|
|
6
6
|
const SECTION_RE = /^## Adaptive Rules \(triflux auto-generated\)$/mu;
|
|
7
|
-
const BLOCK_RE =
|
|
7
|
+
const BLOCK_RE =
|
|
8
|
+
/<!-- tfx-adaptive:start rule_id="([^"]+)" confidence=([0-9.]+) occurrences=(\d+) first_seen=([0-9-]+) last_seen=([0-9-]+) -->\r?\n([^\r\n]*)\r?\n<!-- tfx-adaptive:end -->/gu;
|
|
8
9
|
|
|
9
10
|
function cloneRule(rule) {
|
|
10
11
|
return Object.freeze({ ...rule });
|
|
@@ -27,20 +28,23 @@ function formatConfidence(value) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
function normalizeDate(value, fallback) {
|
|
30
|
-
const text = String(value ?? fallback ??
|
|
31
|
+
const text = String(value ?? fallback ?? "").trim();
|
|
31
32
|
return text || fallback;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
function normalizeRuleInput(rule, fallback = {}) {
|
|
35
|
-
if (!rule || typeof rule !==
|
|
36
|
+
if (!rule || typeof rule !== "object") return null;
|
|
36
37
|
|
|
37
|
-
const id = String(rule.id ?? rule.rule_id ?? fallback.id ??
|
|
38
|
-
const text = String(rule.rule ?? rule.text ?? fallback.rule ??
|
|
38
|
+
const id = String(rule.id ?? rule.rule_id ?? fallback.id ?? "").trim();
|
|
39
|
+
const text = String(rule.rule ?? rule.text ?? fallback.rule ?? "").trim();
|
|
39
40
|
const firstSeen = normalizeDate(
|
|
40
41
|
rule.firstSeen ?? rule.first_seen,
|
|
41
|
-
fallback.firstSeen ?? fallback.lastSeen ??
|
|
42
|
+
fallback.firstSeen ?? fallback.lastSeen ?? "1970-01-01",
|
|
43
|
+
);
|
|
44
|
+
const lastSeen = normalizeDate(
|
|
45
|
+
rule.lastSeen ?? rule.last_seen,
|
|
46
|
+
fallback.lastSeen ?? firstSeen,
|
|
42
47
|
);
|
|
43
|
-
const lastSeen = normalizeDate(rule.lastSeen ?? rule.last_seen, fallback.lastSeen ?? firstSeen);
|
|
44
48
|
if (!id || !text || /["\r\n]/u.test(id) || /[\r\n]/u.test(text)) {
|
|
45
49
|
return null;
|
|
46
50
|
}
|
|
@@ -56,37 +60,43 @@ function normalizeRuleInput(rule, fallback = {}) {
|
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
function trimLeadingBlankLines(text) {
|
|
59
|
-
return String(text ??
|
|
63
|
+
return String(text ?? "").replace(/^(?:[ \t]*\r?\n)+/u, "");
|
|
60
64
|
}
|
|
61
65
|
|
|
62
66
|
function trimTrailingBlankLines(text) {
|
|
63
|
-
return String(text ??
|
|
67
|
+
return String(text ?? "").replace(/(?:\r?\n[ \t]*)+$/u, "");
|
|
64
68
|
}
|
|
65
69
|
|
|
66
|
-
function parseInjectedRules(sectionBody =
|
|
70
|
+
function parseInjectedRules(sectionBody = "") {
|
|
67
71
|
const matches = Array.from(String(sectionBody).matchAll(BLOCK_RE));
|
|
68
|
-
return matches.map(
|
|
69
|
-
id,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
return matches.map(
|
|
73
|
+
([, id, confidence, occurrences, firstSeen, lastSeen, text]) =>
|
|
74
|
+
cloneRule({
|
|
75
|
+
id,
|
|
76
|
+
rule: text,
|
|
77
|
+
confidence: clampConfidence(confidence),
|
|
78
|
+
occurrences: normalizeOccurrences(occurrences),
|
|
79
|
+
firstSeen,
|
|
80
|
+
lastSeen,
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
function readDocument(claudeMdPath) {
|
|
79
|
-
const raw = existsSync(claudeMdPath)
|
|
86
|
+
const raw = existsSync(claudeMdPath)
|
|
87
|
+
? readFileSync(claudeMdPath, "utf8")
|
|
88
|
+
: "";
|
|
80
89
|
const sectionStart = raw.search(SECTION_RE);
|
|
81
90
|
if (sectionStart === -1) {
|
|
82
|
-
return { before: raw, after:
|
|
91
|
+
return { before: raw, after: "", rules: [] };
|
|
83
92
|
}
|
|
84
93
|
|
|
85
|
-
const headingEnd = raw.indexOf(
|
|
94
|
+
const headingEnd = raw.indexOf("\n", sectionStart);
|
|
86
95
|
const bodyStart = headingEnd === -1 ? raw.length : headingEnd + 1;
|
|
87
96
|
const rest = raw.slice(bodyStart);
|
|
88
97
|
const nextHeadingOffset = rest.search(/^#{1,6}\s/mu);
|
|
89
|
-
const sectionEnd =
|
|
98
|
+
const sectionEnd =
|
|
99
|
+
nextHeadingOffset === -1 ? raw.length : bodyStart + nextHeadingOffset;
|
|
90
100
|
const body = raw.slice(bodyStart, sectionEnd);
|
|
91
101
|
|
|
92
102
|
return {
|
|
@@ -100,36 +110,49 @@ function serializeRule(rule) {
|
|
|
100
110
|
return [
|
|
101
111
|
`<!-- tfx-adaptive:start rule_id="${rule.id}" confidence=${formatConfidence(rule.confidence)} occurrences=${rule.occurrences} first_seen=${rule.firstSeen} last_seen=${rule.lastSeen} -->`,
|
|
102
112
|
rule.rule,
|
|
103
|
-
|
|
104
|
-
].join(
|
|
113
|
+
"<!-- tfx-adaptive:end -->",
|
|
114
|
+
].join("\n");
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
function serializeDocument(before, rules, after) {
|
|
108
|
-
const section =
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
const section =
|
|
119
|
+
rules.length > 0
|
|
120
|
+
? `${SECTION_HEADING}\n\n${rules.map(serializeRule).join("\n\n")}`
|
|
121
|
+
: "";
|
|
122
|
+
const parts = [
|
|
123
|
+
trimTrailingBlankLines(before),
|
|
124
|
+
section,
|
|
125
|
+
trimLeadingBlankLines(after),
|
|
126
|
+
].filter(Boolean);
|
|
127
|
+
return parts.length > 0 ? `${parts.join("\n\n")}\n` : "";
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
function enforceMaxRules(rules, maxRules) {
|
|
116
131
|
if (rules.length <= maxRules) return rules.map(cloneRule);
|
|
117
132
|
const ranked = rules
|
|
118
133
|
.map((rule, index) => ({ ...rule, index }))
|
|
119
|
-
.sort(
|
|
120
|
-
left
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
.sort(
|
|
135
|
+
(left, right) =>
|
|
136
|
+
left.confidence - right.confidence ||
|
|
137
|
+
left.occurrences - right.occurrences ||
|
|
138
|
+
left.lastSeen.localeCompare(right.lastSeen) ||
|
|
139
|
+
left.index - right.index ||
|
|
140
|
+
left.id.localeCompare(right.id),
|
|
141
|
+
);
|
|
142
|
+
const removedIds = new Set(
|
|
143
|
+
ranked.slice(0, rules.length - maxRules).map((rule) => rule.id),
|
|
144
|
+
);
|
|
127
145
|
return rules.filter((rule) => !removedIds.has(rule.id)).map(cloneRule);
|
|
128
146
|
}
|
|
129
147
|
|
|
130
148
|
export function createAdaptiveInjector(opts = {}) {
|
|
131
|
-
const claudeMdPath = resolve(
|
|
132
|
-
|
|
149
|
+
const claudeMdPath = resolve(
|
|
150
|
+
opts.claudeMdPath ?? join(process.cwd(), "CLAUDE.md"),
|
|
151
|
+
);
|
|
152
|
+
const maxRules =
|
|
153
|
+
Number.isInteger(opts.maxRules) && opts.maxRules > 0
|
|
154
|
+
? opts.maxRules
|
|
155
|
+
: DEFAULT_MAX_RULES;
|
|
133
156
|
|
|
134
157
|
function listInjected() {
|
|
135
158
|
return readDocument(claudeMdPath).rules.map(cloneRule);
|
|
@@ -137,40 +160,55 @@ export function createAdaptiveInjector(opts = {}) {
|
|
|
137
160
|
|
|
138
161
|
function inject(rule) {
|
|
139
162
|
const document = readDocument(claudeMdPath);
|
|
140
|
-
const targetId = String(rule?.id ?? rule?.rule_id ??
|
|
163
|
+
const targetId = String(rule?.id ?? rule?.rule_id ?? "").trim();
|
|
141
164
|
const existing = document.rules.find((item) => item.id === targetId);
|
|
142
165
|
const normalized = normalizeRuleInput(rule, existing ?? {});
|
|
143
166
|
if (!normalized) return false;
|
|
144
167
|
|
|
145
168
|
const nextRules = existing
|
|
146
|
-
? document.rules.map((item) =>
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
? document.rules.map((item) =>
|
|
170
|
+
item.id === normalized.id
|
|
171
|
+
? cloneRule({
|
|
172
|
+
...item,
|
|
173
|
+
confidence: normalized.confidence,
|
|
174
|
+
occurrences: normalized.occurrences,
|
|
175
|
+
lastSeen: normalized.lastSeen,
|
|
176
|
+
})
|
|
177
|
+
: cloneRule(item),
|
|
178
|
+
)
|
|
154
179
|
: [...document.rules.map(cloneRule), normalized];
|
|
155
180
|
const limitedRules = enforceMaxRules(nextRules, maxRules);
|
|
156
|
-
writeFileSync(
|
|
181
|
+
writeFileSync(
|
|
182
|
+
claudeMdPath,
|
|
183
|
+
serializeDocument(document.before, limitedRules, document.after),
|
|
184
|
+
"utf8",
|
|
185
|
+
);
|
|
157
186
|
return limitedRules.some((item) => item.id === normalized.id);
|
|
158
187
|
}
|
|
159
188
|
|
|
160
189
|
function remove(ruleId) {
|
|
161
|
-
const targetId = String(ruleId ??
|
|
190
|
+
const targetId = String(ruleId ?? "").trim();
|
|
162
191
|
if (!targetId || !existsSync(claudeMdPath)) return false;
|
|
163
192
|
const document = readDocument(claudeMdPath);
|
|
164
193
|
if (!document.rules.some((rule) => rule.id === targetId)) return false;
|
|
165
|
-
const nextRules = document.rules
|
|
166
|
-
|
|
194
|
+
const nextRules = document.rules
|
|
195
|
+
.filter((rule) => rule.id !== targetId)
|
|
196
|
+
.map(cloneRule);
|
|
197
|
+
writeFileSync(
|
|
198
|
+
claudeMdPath,
|
|
199
|
+
serializeDocument(document.before, nextRules, document.after),
|
|
200
|
+
"utf8",
|
|
201
|
+
);
|
|
167
202
|
return true;
|
|
168
203
|
}
|
|
169
204
|
|
|
170
205
|
function cleanup(activeRuleIds = []) {
|
|
171
|
-
const activeIds = new Set(
|
|
206
|
+
const activeIds = new Set(
|
|
207
|
+
Array.isArray(activeRuleIds) ? activeRuleIds : Array.from(activeRuleIds),
|
|
208
|
+
);
|
|
172
209
|
return listInjected().reduce(
|
|
173
|
-
(count, rule) =>
|
|
210
|
+
(count, rule) =>
|
|
211
|
+
count + (activeIds.has(rule.id) ? 0 : Number(remove(rule.id))),
|
|
174
212
|
0,
|
|
175
213
|
);
|
|
176
214
|
}
|