triflux 9.8.3 → 9.8.5
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/bin/triflux.mjs +139 -1
- package/hooks/hook-adaptive-collector.mjs +86 -0
- package/hooks/hook-registry.json +4 -4
- package/hooks/keyword-rules.json +189 -1
- package/hooks/safety-guard.mjs +16 -0
- package/hub/adaptive-diagnostic.mjs +319 -0
- package/hub/adaptive-inject.mjs +186 -0
- package/hub/adaptive-memory.mjs +322 -0
- package/hub/adaptive.mjs +143 -0
- package/hub/cli-adapter-base.mjs +192 -0
- package/hub/codex-adapter.mjs +190 -0
- package/hub/codex-preflight.mjs +147 -0
- package/hub/gemini-adapter.mjs +179 -0
- package/hub/lib/known-errors.json +72 -0
- package/hub/lib/memory-store.mjs +748 -0
- package/hub/lib/ssh-command.mjs +150 -0
- package/hub/lib/uuidv7.mjs +44 -0
- package/hub/middleware/request-logger.mjs +80 -0
- package/hub/pipe.mjs +1 -1
- package/hub/platform.mjs +58 -19
- package/hub/reflexion.mjs +303 -38
- package/hub/router.mjs +1 -1
- package/hub/schema.sql +2 -0
- package/hub/server.mjs +1218 -1112
- package/hub/session-fingerprint.mjs +352 -0
- package/hub/store-adapter.mjs +88 -584
- package/hub/store.mjs +857 -820
- package/hub/team/backend.mjs +3 -5
- package/hub/team/cli/services/hub-client.mjs +38 -19
- package/hub/team/cli/services/native-control.mjs +4 -5
- package/hub/team/conductor.mjs +602 -0
- package/hub/team/event-log.mjs +76 -0
- package/hub/team/headless.mjs +89 -30
- package/hub/team/health-probe.mjs +272 -0
- package/hub/team/launcher-template.mjs +94 -0
- package/hub/team/lead-control.mjs +104 -0
- package/hub/team/notify.mjs +293 -0
- package/hub/team/pane.mjs +4 -5
- package/hub/team/process-cleanup.mjs +342 -0
- package/hub/team/remote-probe.mjs +276 -0
- package/hub/team/remote-watcher.mjs +478 -0
- package/hub/team/session-sync.mjs +169 -0
- package/hub/team/tui-remote-adapter.mjs +393 -0
- package/hub/team/tui.mjs +206 -2
- package/hub/team-bridge.mjs +25 -0
- package/hub/tools.mjs +1 -1
- package/hud/constants.mjs +7 -0
- package/hud/context-monitor.mjs +397 -0
- package/hud/hud-qos-status.mjs +8 -4
- package/hud/providers/claude.mjs +5 -0
- package/hud/renderers.mjs +13 -9
- package/package.json +15 -5
- package/scripts/__tests__/gen-skill-docs.test.mjs +87 -0
- package/scripts/__tests__/skill-template.test.mjs +104 -0
- package/scripts/cache-warmup.mjs +3 -3
- package/scripts/gen-skill-docs.mjs +110 -0
- package/scripts/lib/claudemd-manager.mjs +325 -0
- package/scripts/lib/claudemd-scanner.mjs +218 -0
- package/scripts/lib/env-probe.mjs +95 -14
- package/scripts/lib/handoff.mjs +171 -0
- package/scripts/lib/skill-template.mjs +222 -0
- package/scripts/notion-read.mjs +5 -3
- package/scripts/pack.mjs +205 -0
- package/scripts/preflight-cache.mjs +6 -5
- package/scripts/remote-spawn.mjs +5 -5
- package/scripts/setup.mjs +260 -0
- package/scripts/templates/claudemd-tfx-section.md +54 -0
- package/scripts/test-lock.mjs +71 -0
- package/skills/_templates/base.md +9 -0
- package/skills/_templates/deep.md +6 -0
- package/skills/tfx-codex-swarm/SKILL.md +55 -11
- /package/hub/{team/codex-compat.mjs → codex-compat.mjs} +0 -0
package/bin/triflux.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// triflux CLI — setup, doctor, version
|
|
3
3
|
import { copyFileSync, existsSync, readFileSync, readSync, writeFileSync, mkdirSync, chmodSync, readdirSync, unlinkSync, statSync, openSync, closeSync } from "fs";
|
|
4
|
-
import { join, dirname, basename } from "path";
|
|
4
|
+
import { join, dirname, basename, resolve } from "path";
|
|
5
5
|
import { homedir, tmpdir } from "os";
|
|
6
6
|
import { execSync, execFileSync, spawn } from "child_process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
@@ -11,6 +11,7 @@ import { detectMultiplexer, getSessionAttachedCount, killSession, listSessions,
|
|
|
11
11
|
import { forceCleanupTeam } from "../hub/team/nativeProxy.mjs";
|
|
12
12
|
import { cleanupStaleOmcTeams, inspectStaleOmcTeams } from "../hub/team/staleState.mjs";
|
|
13
13
|
import { getPipelineStateDbPath } from "../hub/pipeline/state.mjs";
|
|
14
|
+
import { serializeHandoff } from "../scripts/lib/handoff.mjs";
|
|
14
15
|
import { ensureGeminiProfiles } from "../scripts/lib/gemini-profiles.mjs";
|
|
15
16
|
import { probePsmuxSupport, formatPsmuxInstallGuidance, formatPsmuxUpdateGuidance } from "../scripts/lib/psmux-info.mjs";
|
|
16
17
|
import {
|
|
@@ -98,6 +99,17 @@ const CLI_COMMAND_SCHEMAS = Object.freeze({
|
|
|
98
99
|
{ name: "--json", type: "boolean", description: "버전 정보를 JSON으로 출력" },
|
|
99
100
|
],
|
|
100
101
|
},
|
|
102
|
+
handoff: {
|
|
103
|
+
usage: "tfx handoff [--target local|remote] [--decision <text>] [--decision-file <path>] [--output <path>] [--json]",
|
|
104
|
+
description: "현재 작업 컨텍스트를 세션 핸드오프 프롬프트로 직렬화",
|
|
105
|
+
options: [
|
|
106
|
+
{ name: "--target", type: "string", description: "주입 대상 (local|remote, 기본값 remote)" },
|
|
107
|
+
{ name: "--decision", type: "string", description: "핸드오프 결정사항 (반복 지정 가능)" },
|
|
108
|
+
{ name: "--decision-file", type: "string", description: "결정사항 파일 (라인/불릿 단위)" },
|
|
109
|
+
{ name: "--output", type: "string", description: "생성한 핸드오프 프롬프트 저장 경로" },
|
|
110
|
+
{ name: "--json", type: "boolean", description: "핸드오프 결과를 JSON으로 출력" },
|
|
111
|
+
],
|
|
112
|
+
},
|
|
101
113
|
list: {
|
|
102
114
|
usage: "tfx list [--json]",
|
|
103
115
|
description: "패키지 스킬과 사용자 스킬 목록 표시",
|
|
@@ -2783,6 +2795,128 @@ function cmdVersion(options = {}) {
|
|
|
2783
2795
|
console.log("");
|
|
2784
2796
|
}
|
|
2785
2797
|
|
|
2798
|
+
function cmdHandoff(args = [], options = {}) {
|
|
2799
|
+
const { json = false } = options;
|
|
2800
|
+
const parsed = {
|
|
2801
|
+
target: "remote",
|
|
2802
|
+
decisions: [],
|
|
2803
|
+
decisionFile: null,
|
|
2804
|
+
output: null,
|
|
2805
|
+
cwd: process.cwd(),
|
|
2806
|
+
};
|
|
2807
|
+
|
|
2808
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
2809
|
+
const arg = args[index];
|
|
2810
|
+
const next = args[index + 1];
|
|
2811
|
+
|
|
2812
|
+
if (arg === "--target") {
|
|
2813
|
+
if (!next || next.startsWith("-")) {
|
|
2814
|
+
throw createCliError("--target 값이 필요합니다 (local|remote)", {
|
|
2815
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2816
|
+
reason: "argError",
|
|
2817
|
+
fix: "tfx handoff --target remote",
|
|
2818
|
+
});
|
|
2819
|
+
}
|
|
2820
|
+
if (!["local", "remote"].includes(next)) {
|
|
2821
|
+
throw createCliError(`지원하지 않는 --target 값: ${next}`, {
|
|
2822
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2823
|
+
reason: "argError",
|
|
2824
|
+
fix: "tfx handoff --target local|remote",
|
|
2825
|
+
});
|
|
2826
|
+
}
|
|
2827
|
+
parsed.target = next;
|
|
2828
|
+
index += 1;
|
|
2829
|
+
continue;
|
|
2830
|
+
}
|
|
2831
|
+
|
|
2832
|
+
if (arg === "--decision") {
|
|
2833
|
+
if (!next || next.startsWith("-")) {
|
|
2834
|
+
throw createCliError("--decision 값이 필요합니다", {
|
|
2835
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2836
|
+
reason: "argError",
|
|
2837
|
+
fix: "tfx handoff --decision \"결정사항\"",
|
|
2838
|
+
});
|
|
2839
|
+
}
|
|
2840
|
+
parsed.decisions.push(next);
|
|
2841
|
+
index += 1;
|
|
2842
|
+
continue;
|
|
2843
|
+
}
|
|
2844
|
+
|
|
2845
|
+
if (arg === "--decision-file") {
|
|
2846
|
+
if (!next || next.startsWith("-")) {
|
|
2847
|
+
throw createCliError("--decision-file 경로가 필요합니다", {
|
|
2848
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2849
|
+
reason: "argError",
|
|
2850
|
+
fix: "tfx handoff --decision-file .omx/notepad.md",
|
|
2851
|
+
});
|
|
2852
|
+
}
|
|
2853
|
+
parsed.decisionFile = resolve(next);
|
|
2854
|
+
index += 1;
|
|
2855
|
+
continue;
|
|
2856
|
+
}
|
|
2857
|
+
|
|
2858
|
+
if (arg === "--output" || arg === "--out") {
|
|
2859
|
+
if (!next || next.startsWith("-")) {
|
|
2860
|
+
throw createCliError(`${arg} 경로가 필요합니다`, {
|
|
2861
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2862
|
+
reason: "argError",
|
|
2863
|
+
fix: "tfx handoff --output .omx/handoff.md",
|
|
2864
|
+
});
|
|
2865
|
+
}
|
|
2866
|
+
parsed.output = resolve(next);
|
|
2867
|
+
index += 1;
|
|
2868
|
+
continue;
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
if (arg === "--cwd") {
|
|
2872
|
+
if (!next || next.startsWith("-")) {
|
|
2873
|
+
throw createCliError("--cwd 경로가 필요합니다", {
|
|
2874
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2875
|
+
reason: "argError",
|
|
2876
|
+
fix: "tfx handoff --cwd <project-path>",
|
|
2877
|
+
});
|
|
2878
|
+
}
|
|
2879
|
+
parsed.cwd = resolve(next);
|
|
2880
|
+
index += 1;
|
|
2881
|
+
continue;
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
throw createCliError(`알 수 없는 handoff 옵션: ${arg}`, {
|
|
2885
|
+
exitCode: EXIT_ARG_ERROR,
|
|
2886
|
+
reason: "argError",
|
|
2887
|
+
fix: "tfx handoff --target remote --output .omx/handoff.md",
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
|
|
2891
|
+
const result = serializeHandoff({
|
|
2892
|
+
target: parsed.target,
|
|
2893
|
+
decisions: parsed.decisions,
|
|
2894
|
+
decisionFile: parsed.decisionFile,
|
|
2895
|
+
cwd: parsed.cwd,
|
|
2896
|
+
});
|
|
2897
|
+
|
|
2898
|
+
if (parsed.output) {
|
|
2899
|
+
const outputDir = dirname(parsed.output);
|
|
2900
|
+
if (!existsSync(outputDir)) mkdirSync(outputDir, { recursive: true });
|
|
2901
|
+
writeFileSync(parsed.output, `${result.prompt}\n`, "utf8");
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
if (json) {
|
|
2905
|
+
printJson({
|
|
2906
|
+
handoff: {
|
|
2907
|
+
...result,
|
|
2908
|
+
...(parsed.output ? { output: parsed.output } : {}),
|
|
2909
|
+
},
|
|
2910
|
+
});
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
console.log(result.prompt);
|
|
2915
|
+
if (parsed.output) {
|
|
2916
|
+
console.log(`\n${DIM}saved:${RESET} ${parsed.output}`);
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2786
2920
|
function cmdSchema(args = []) {
|
|
2787
2921
|
const bundle = loadDelegatorSchemaBundle();
|
|
2788
2922
|
const selector = String(args[0] || "").trim();
|
|
@@ -3054,6 +3188,7 @@ ${updateNotice}
|
|
|
3054
3188
|
${WHITE_BRIGHT}tfx update${RESET} ${GRAY}최신 안정 버전으로 업데이트${RESET}
|
|
3055
3189
|
${DIM} --dev / dev${RESET} ${GRAY}dev 태그로 업데이트${RESET}
|
|
3056
3190
|
${WHITE_BRIGHT}tfx list${RESET} ${GRAY}설치된 스킬 목록${RESET}
|
|
3191
|
+
${WHITE_BRIGHT}tfx handoff${RESET} ${GRAY}현재 컨텍스트를 원격/로컬 핸드오프 프롬프트로 생성${RESET}
|
|
3057
3192
|
${WHITE_BRIGHT}tfx schema${RESET} ${GRAY}CLI/Hub schema JSON 출력${RESET}
|
|
3058
3193
|
${WHITE_BRIGHT}tfx hub${RESET} ${GRAY}MCP 메시지 버스 관리 (start/stop/status)${RESET}
|
|
3059
3194
|
${WHITE_BRIGHT}tfx tray${RESET} ${GRAY}Windows 시스템 트레이 실행${RESET}
|
|
@@ -3559,6 +3694,9 @@ async function main() {
|
|
|
3559
3694
|
case "ls":
|
|
3560
3695
|
cmdList({ json: JSON_OUTPUT });
|
|
3561
3696
|
return;
|
|
3697
|
+
case "handoff":
|
|
3698
|
+
cmdHandoff(cmdArgs, { json: JSON_OUTPUT });
|
|
3699
|
+
return;
|
|
3562
3700
|
case "hub":
|
|
3563
3701
|
await cmdHub(cmdArgs, { json: JSON_OUTPUT && (cmdArgs[0] || "status") === "status" });
|
|
3564
3702
|
return;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { basename, join } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
import { createAdaptiveEngine } from '../hub/adaptive.mjs';
|
|
8
|
+
|
|
9
|
+
let engine = null;
|
|
10
|
+
let createEngine = createAdaptiveEngine;
|
|
11
|
+
|
|
12
|
+
function readStdin() {
|
|
13
|
+
try {
|
|
14
|
+
return readFileSync(0, 'utf8');
|
|
15
|
+
} catch {
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function inferProjectSlug(cwd = process.cwd()) {
|
|
21
|
+
const packagePath = join(cwd, 'package.json');
|
|
22
|
+
if (existsSync(packagePath)) {
|
|
23
|
+
try {
|
|
24
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
25
|
+
if (typeof pkg.name === 'string' && pkg.name.trim()) return pkg.name.trim();
|
|
26
|
+
} catch {}
|
|
27
|
+
}
|
|
28
|
+
return basename(cwd) || 'default';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getEngine() {
|
|
32
|
+
if (engine) return engine;
|
|
33
|
+
engine = createEngine({
|
|
34
|
+
projectSlug: inferProjectSlug(),
|
|
35
|
+
repoRoot: process.cwd(),
|
|
36
|
+
});
|
|
37
|
+
engine.startSession?.();
|
|
38
|
+
return engine;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function buildErrorContext(event = {}) {
|
|
42
|
+
return {
|
|
43
|
+
exitCode: event.exitCode,
|
|
44
|
+
stderr: String(event.stderr || '').slice(0, 500),
|
|
45
|
+
tool: event.tool,
|
|
46
|
+
command: String(event.command || '').slice(0, 200),
|
|
47
|
+
timestamp: new Date().toISOString(),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default function hookAdaptiveCollector(event = {}) {
|
|
52
|
+
if (Number(event.exitCode) === 0) return null;
|
|
53
|
+
if (!event.tool || event.tool === 'Read') return null;
|
|
54
|
+
|
|
55
|
+
const result = getEngine().handleError(buildErrorContext(event));
|
|
56
|
+
if (result?.diagnosed) {
|
|
57
|
+
console.error(`[adaptive] 에러 패턴 감지: ${result.rule?.id || 'unknown'}`);
|
|
58
|
+
if (result.promoted) {
|
|
59
|
+
console.error(`[adaptive] 규칙 승격 → Tier ${result.rule?.tier ?? '?'}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function __setAdaptiveCollectorFactoryForTests(factory) {
|
|
66
|
+
createEngine = factory;
|
|
67
|
+
engine = null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function __resetAdaptiveCollectorForTests() {
|
|
71
|
+
createEngine = createAdaptiveEngine;
|
|
72
|
+
engine = null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function main() {
|
|
76
|
+
const raw = readStdin();
|
|
77
|
+
if (!raw.trim()) return;
|
|
78
|
+
try {
|
|
79
|
+
hookAdaptiveCollector(JSON.parse(raw));
|
|
80
|
+
} catch {}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const isEntrypoint = process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url;
|
|
84
|
+
if (isEntrypoint) {
|
|
85
|
+
main();
|
|
86
|
+
}
|
package/hooks/hook-registry.json
CHANGED
|
@@ -153,9 +153,9 @@
|
|
|
153
153
|
"id": "ext-session-vault-start",
|
|
154
154
|
"source": "session-vault",
|
|
155
155
|
"matcher": "*",
|
|
156
|
-
"command": "${HOME}/Desktop/Projects/
|
|
156
|
+
"command": "bash \"${HOME}/Desktop/Projects/tools/session-vault/scripts/start_hook.sh\"",
|
|
157
157
|
"priority": 100,
|
|
158
|
-
"enabled":
|
|
158
|
+
"enabled": true,
|
|
159
159
|
"timeout": 10,
|
|
160
160
|
"blocking": false,
|
|
161
161
|
"description": "세션 볼트 로깅 시작"
|
|
@@ -177,9 +177,9 @@
|
|
|
177
177
|
"id": "ext-session-vault-export",
|
|
178
178
|
"source": "session-vault",
|
|
179
179
|
"matcher": "*",
|
|
180
|
-
"command": "${HOME}/Desktop/Projects/
|
|
180
|
+
"command": "bash \"${HOME}/Desktop/Projects/tools/session-vault/scripts/export_hook.sh\"",
|
|
181
181
|
"priority": 100,
|
|
182
|
-
"enabled":
|
|
182
|
+
"enabled": true,
|
|
183
183
|
"timeout": 30,
|
|
184
184
|
"blocking": false,
|
|
185
185
|
"description": "세션 트랜스크립트 내보내기"
|
package/hooks/keyword-rules.json
CHANGED
|
@@ -37,6 +37,46 @@
|
|
|
37
37
|
"state": null,
|
|
38
38
|
"mcp_route": null
|
|
39
39
|
},
|
|
40
|
+
{
|
|
41
|
+
"id": "tfx-codex-swarm",
|
|
42
|
+
"patterns": [
|
|
43
|
+
{
|
|
44
|
+
"source": "\\btfx[\\s-]?codex[\\s-]?swarm\\b",
|
|
45
|
+
"flags": "i"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"source": "\\bcodex[\\s-]?swarm\\b",
|
|
49
|
+
"flags": "i"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"skill": "tfx-codex-swarm",
|
|
53
|
+
"priority": 1,
|
|
54
|
+
"supersedes": [
|
|
55
|
+
"tfx-codex"
|
|
56
|
+
],
|
|
57
|
+
"exclusive": false,
|
|
58
|
+
"state": null,
|
|
59
|
+
"mcp_route": null
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "tfx-remote-spawn",
|
|
63
|
+
"patterns": [
|
|
64
|
+
{
|
|
65
|
+
"source": "\\btfx[\\s-]?remote[\\s-]?spawn\\b",
|
|
66
|
+
"flags": "i"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"source": "\\bremote[\\s-]?spawn\\b",
|
|
70
|
+
"flags": "i"
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"skill": "tfx-remote-spawn",
|
|
74
|
+
"priority": 1,
|
|
75
|
+
"supersedes": [],
|
|
76
|
+
"exclusive": false,
|
|
77
|
+
"state": null,
|
|
78
|
+
"mcp_route": null
|
|
79
|
+
},
|
|
40
80
|
{
|
|
41
81
|
"id": "tfx-auto-codex",
|
|
42
82
|
"patterns": [
|
|
@@ -74,7 +114,7 @@
|
|
|
74
114
|
"id": "tfx-codex",
|
|
75
115
|
"patterns": [
|
|
76
116
|
{
|
|
77
|
-
"source": "\\btfx[\\s-]?codex\\b",
|
|
117
|
+
"source": "\\btfx[\\s-]?codex\\b(?![\\s-]?swarm)",
|
|
78
118
|
"flags": "i"
|
|
79
119
|
}
|
|
80
120
|
],
|
|
@@ -373,6 +413,154 @@
|
|
|
373
413
|
"state": null,
|
|
374
414
|
"mcp_route": "codex"
|
|
375
415
|
},
|
|
416
|
+
{
|
|
417
|
+
"id": "gstack-checkpoint",
|
|
418
|
+
"patterns": [
|
|
419
|
+
{
|
|
420
|
+
"source": "(?:어디까지\\s*했|뭐\\s*하고\\s*있었|뭘\\s*해야\\s*하|까먹|기억이\\s*안|이어서\\s*하|중단\\s*지점)",
|
|
421
|
+
"flags": "i"
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
"source": "\\b(?:checkpoint|resume|pick\\s*up|where\\s*was\\s*I)\\b",
|
|
425
|
+
"flags": "i"
|
|
426
|
+
}
|
|
427
|
+
],
|
|
428
|
+
"skill": "checkpoint",
|
|
429
|
+
"priority": 5,
|
|
430
|
+
"supersedes": [],
|
|
431
|
+
"exclusive": false,
|
|
432
|
+
"state": null,
|
|
433
|
+
"mcp_route": null
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
"id": "gstack-office-hours",
|
|
437
|
+
"patterns": [
|
|
438
|
+
{
|
|
439
|
+
"source": "(?:아이디어\\s*(?:있|정리|브레인)|뭘\\s*만들|제품\\s*구상)",
|
|
440
|
+
"flags": "i"
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"source": "\\boffice[\\s-]?hours\\b",
|
|
444
|
+
"flags": "i"
|
|
445
|
+
}
|
|
446
|
+
],
|
|
447
|
+
"skill": "office-hours",
|
|
448
|
+
"priority": 5,
|
|
449
|
+
"supersedes": [],
|
|
450
|
+
"exclusive": false,
|
|
451
|
+
"state": null,
|
|
452
|
+
"mcp_route": null
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
"id": "gstack-ship",
|
|
456
|
+
"patterns": [
|
|
457
|
+
{
|
|
458
|
+
"source": "(?:배포해|PR\\s*만들|릴리스\\s*해|머지하고\\s*배포)",
|
|
459
|
+
"flags": "i"
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
"source": "\\bship\\b(?!.*tfx)",
|
|
463
|
+
"flags": "i"
|
|
464
|
+
}
|
|
465
|
+
],
|
|
466
|
+
"skill": "ship",
|
|
467
|
+
"priority": 5,
|
|
468
|
+
"supersedes": [],
|
|
469
|
+
"exclusive": false,
|
|
470
|
+
"state": null,
|
|
471
|
+
"mcp_route": null
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
"id": "gstack-investigate",
|
|
475
|
+
"patterns": [
|
|
476
|
+
{
|
|
477
|
+
"source": "(?:왜\\s*(?:안\\s*돼|터져|에러)|원인\\s*(?:찾아|분석)|root\\s*cause)",
|
|
478
|
+
"flags": "i"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"source": "\\binvestigate\\b",
|
|
482
|
+
"flags": "i"
|
|
483
|
+
}
|
|
484
|
+
],
|
|
485
|
+
"skill": "investigate",
|
|
486
|
+
"priority": 5,
|
|
487
|
+
"supersedes": [],
|
|
488
|
+
"exclusive": false,
|
|
489
|
+
"state": null,
|
|
490
|
+
"mcp_route": null
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
"id": "gstack-cso",
|
|
494
|
+
"patterns": [
|
|
495
|
+
{
|
|
496
|
+
"source": "(?:보안\\s*(?:감사|점검|리뷰|스캔)|\\bcso\\b|\\bOWASP\\b|\\bSTRIDE\\b)",
|
|
497
|
+
"flags": "i"
|
|
498
|
+
}
|
|
499
|
+
],
|
|
500
|
+
"skill": "cso",
|
|
501
|
+
"priority": 5,
|
|
502
|
+
"supersedes": [],
|
|
503
|
+
"exclusive": false,
|
|
504
|
+
"state": null,
|
|
505
|
+
"mcp_route": null
|
|
506
|
+
},
|
|
507
|
+
{
|
|
508
|
+
"id": "gstack-qa-browser",
|
|
509
|
+
"patterns": [
|
|
510
|
+
{
|
|
511
|
+
"source": "(?:사이트|브라우저|웹)\\s*(?:QA|테스트|확인|점검)",
|
|
512
|
+
"flags": "i"
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
"source": "(?:클릭|접속).*(?:테스트|확인)",
|
|
516
|
+
"flags": "i"
|
|
517
|
+
}
|
|
518
|
+
],
|
|
519
|
+
"skill": "qa",
|
|
520
|
+
"priority": 5,
|
|
521
|
+
"supersedes": [],
|
|
522
|
+
"exclusive": false,
|
|
523
|
+
"state": null,
|
|
524
|
+
"mcp_route": null
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"id": "gstack-retro",
|
|
528
|
+
"patterns": [
|
|
529
|
+
{
|
|
530
|
+
"source": "(?:회고|이번\\s*주\\s*뭐\\s*했|주간\\s*리뷰|뭘\\s*했지)",
|
|
531
|
+
"flags": "i"
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
"source": "\\bretro\\b",
|
|
535
|
+
"flags": "i"
|
|
536
|
+
}
|
|
537
|
+
],
|
|
538
|
+
"skill": "retro",
|
|
539
|
+
"priority": 5,
|
|
540
|
+
"supersedes": [],
|
|
541
|
+
"exclusive": false,
|
|
542
|
+
"state": null,
|
|
543
|
+
"mcp_route": null
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
"id": "gstack-autoplan",
|
|
547
|
+
"patterns": [
|
|
548
|
+
{
|
|
549
|
+
"source": "(?:자동\\s*리뷰|리뷰\\s*파이프라인|전체\\s*리뷰)",
|
|
550
|
+
"flags": "i"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
"source": "\\bautoplan\\b",
|
|
554
|
+
"flags": "i"
|
|
555
|
+
}
|
|
556
|
+
],
|
|
557
|
+
"skill": "autoplan",
|
|
558
|
+
"priority": 5,
|
|
559
|
+
"supersedes": [],
|
|
560
|
+
"exclusive": false,
|
|
561
|
+
"state": null,
|
|
562
|
+
"mcp_route": null
|
|
563
|
+
},
|
|
376
564
|
{
|
|
377
565
|
"id": "suppress-omc-team",
|
|
378
566
|
"patterns": [
|
package/hooks/safety-guard.mjs
CHANGED
|
@@ -22,6 +22,8 @@ const BLOCK_RULES = [
|
|
|
22
22
|
{ pattern: /\bformat\s+[a-z]:/i, reason: "디스크 포맷 차단" },
|
|
23
23
|
{ pattern: /\b(del|rmdir)\s+\/[sq]\b/i, reason: "Windows 재귀 삭제 차단" },
|
|
24
24
|
{ pattern: /\bgit\s+clean\s+.*-fd/i, reason: "git clean -fd 차단 — 추적되지 않은 파일 소실 위험" },
|
|
25
|
+
{ pattern: /\bpsmux\s+kill-session\b/i, reason: "raw psmux kill-session 차단 — WT ConPTY 프리징 위험. 안전 경로: node hub/team/psmux.mjs kill --session <name>", skipIfGit: true },
|
|
26
|
+
{ pattern: /\bpsmux\s+kill-server\b/i, reason: "psmux kill-server 차단 — 모든 세션이 즉시 종료됩니다. node hub/team/psmux.mjs kill-swarm 사용", skipIfGit: true },
|
|
25
27
|
];
|
|
26
28
|
|
|
27
29
|
// ── 경고 규칙 ──────────────────────────────────────────────
|
|
@@ -60,8 +62,22 @@ function main() {
|
|
|
60
62
|
const command = (input.tool_input?.command || "").trim();
|
|
61
63
|
if (!command) process.exit(0);
|
|
62
64
|
|
|
65
|
+
// psmux 명령이 실제 CLI 호출인지 판별 (오탐 방지)
|
|
66
|
+
// git commit 메시지, echo, grep, cat, heredoc 안의 텍스트는 무시
|
|
67
|
+
function isPsmuxInvocation(cmd) {
|
|
68
|
+
// 명령을 세그먼트로 분할 (&&, ;, | 기준)
|
|
69
|
+
const segments = cmd.split(/[;&|]+/);
|
|
70
|
+
return segments.some((seg) => {
|
|
71
|
+
const trimmed = seg.trim();
|
|
72
|
+
if (trimmed.startsWith("#")) return false; // 주석
|
|
73
|
+
// 세그먼트의 첫 단어가 psmux인 경우만 실제 호출
|
|
74
|
+
return /^\s*psmux\s+kill-(session|server)\b/i.test(trimmed);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
63
78
|
// 1. BLOCK 체크 — exit 2로 차단
|
|
64
79
|
for (const rule of BLOCK_RULES) {
|
|
80
|
+
if (rule.skipIfGit && !isPsmuxInvocation(command)) continue;
|
|
65
81
|
if (rule.pattern.test(command)) {
|
|
66
82
|
process.stderr.write(
|
|
67
83
|
`[triflux safety-guard] BLOCKED: ${rule.reason}\n` +
|