@triflux/core 10.92.0 → 10.93.1

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.
@@ -1,7 +1,7 @@
1
1
  // hub/lib/jev-addon.mjs
2
2
  // Opt-in Jev (System One) 판단 애드온.
3
3
  // 환경변수, 명시 CLI 또는 host 설정으로 활성화하며,
4
- // 설정이 없거나 호출이 실패하면 항상 null을 반환해 기존 결정론적 로직을 그대로 유지한다.
4
+ // 설정이 없거나 호출이 실패하면 기존 결정론적 로직을 유지한다.
5
5
 
6
6
  import { execFile } from "node:child_process";
7
7
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
@@ -1889,3 +1889,196 @@ export async function judgeRoutingBranch(
1889
1889
  confidence: res.confidence,
1890
1890
  };
1891
1891
  }
1892
+
1893
+ const WORKER_MODE_PRIVATE_PATTERNS = [
1894
+ /[^\s@]+@[^\s@]+/u,
1895
+ /\b01[016789][ .)\p{Pd}]{0,2}\d{3,4}[ .)\p{Pd}]{0,2}\d{4}\b/u,
1896
+ /\+\d{1,3}(?:[ .()\p{Pd}]*\d){7,14}\b/u,
1897
+ /\b\d{6}(?:\s*[)\p{Pd}]\s*| )?[1-8]\d{6}\b/u,
1898
+ /\b(?=(?:\d[ .-]?){15}\d\b)(?=\d*[ .-])(?:\d[ .-]?){15}\d\b/u,
1899
+ /(?<![\w.-])\/(?:Users|home)\/[^/\s]+/u,
1900
+ /(?:\/(?:(?:mnt|cygdrive)[\\/]+)?[a-z]|\b[a-z]:)[\\/]+Users[\\/]+[^\\/\s]+/iu,
1901
+ /\b[a-z][a-z\d+.-]*:\/\/[^\s/@]+:[^\s/@]*@/i,
1902
+ /(?=[A-Za-z0-9_-]*[A-Za-z])(?=[A-Za-z0-9_-]*\d)[A-Za-z0-9_-]{20,}/u,
1903
+ /(?:비밀번호|패스워드|암호|토큰|시크릿|비번|키|(?<![A-Za-z])(?:password|passwd|token|secret|pw|pwd|key|api[\s_-]*(?:key|키))(?![A-Za-z]))\s*(?:값\s*)?(?:으로|로|[은는이가을를])?\s*(?:is\b\s*)?(?:[:=]\s*\S+|[^\s:=]*\d[^\s]*)/iu,
1904
+ ];
1905
+ const WORKER_MODE_EXCLUDED_SENTENCE =
1906
+ /금지|않|하지\s*마|말고|말\s*것|말아|마라|안\s*(?:함|합|한다|해|됨|됩|돼|된|되)|불가(?!피)|제외|없이|없음|없다|(?<!잘)못|(?:은|는)\s*(?:리드|사용자)가|리드에게|리드\s*몫|아니다|범위\s*밖|CI가|\b(?:not|never|without|avoid|don't|don’t|cannot)\b|n['’]t\b|(?<!-)\bno\b(?!-)/iu;
1907
+ const WORKER_MODE_WRITE_SIGNALS = [
1908
+ ["git push", /\b(?:git\s+)?push(?:es|ed|ing)?\b/iu],
1909
+ ["푸시", /푸시/u],
1910
+ [
1911
+ "PR 생성",
1912
+ /\bPR\s*(?:을|를)?\s*(?:생성|만[들든드듭]|열기|올[린리려])|\b(?:open|create)\s+(?:a\s+)?(?:PR\b|pull\s+request\b)/iu,
1913
+ ],
1914
+ ["머지", /머지/u],
1915
+ ["merge", /\bmerg(?:e[sd]?|ing)\b/iu],
1916
+ ["릴리스", /릴리스/u],
1917
+ ["release", /\breleas(?:e[sd]?|ing)\b/iu],
1918
+ ["배포", /배포/u],
1919
+ ["deploy", /\bdeploy(?:s|ed|ing)?\b/iu],
1920
+ ["publish", /\bpublish(?:es|ed|ing)?\b/iu],
1921
+ ["발행", /발행/u],
1922
+ ["삭제", /삭제/u],
1923
+ ["delete", /\bdelet(?:e[sd]?|ing)\b/iu],
1924
+ ["DROP", /\bDROP\s+(?:TABLE|DATABASE)\b/u],
1925
+ ["PATCH", /\bPATCH\b/u],
1926
+ ["PUT", /\bPUT\b/u],
1927
+ ["POST", /\bPOST\b/u],
1928
+ ["DELETE", /\bDELETE\b/u],
1929
+ ["메일 발송", /메일\s*(?:발송|보내)|\bsend\s+(?:mail|email)\b/iu],
1930
+ ["전송", /전송/u],
1931
+ ["kubectl apply", /\bkubectl\s+apply\b/iu],
1932
+ ["terraform apply", /\bterraform\s+apply\b/iu],
1933
+ ["npm publish", /\bnpm\s+(?:publish\b|게시)/iu],
1934
+ ["gh pr", /\bgh\s+pr\b/iu],
1935
+ ["gh release", /\bgh\s+release\b/iu],
1936
+ ["원격 쓰기", /원격.*?(?:쓰기|수정|변경|갱신|올[린리려]|반영|생성)/u],
1937
+ [
1938
+ "외부 문서 쓰기",
1939
+ /(?:\bNotion\b|노션|\bGoogle\s+Docs\b|구글\s*독스|\bConfluence\b|\bJira\b|\bSlack\b|슬랙).*?(?:쓰기|작성|수정|업데이트|생성|갱신|게시|공지|만들기|올[린리려])/iu,
1940
+ ],
1941
+ ];
1942
+ const WORKER_MODE_READ_SIGNALS = [
1943
+ ["조사", /조사/u],
1944
+ ["읽기", /읽(?:기|어|고)/u],
1945
+ ["분석", /분석/u],
1946
+ ["리뷰", /리뷰/u],
1947
+ ["검토", /검토/u],
1948
+ ["확인", /확인/u],
1949
+ ["조회", /조회/u],
1950
+ ["검색", /검색/u],
1951
+ ["탐색", /탐색/u],
1952
+ ["read-only", /\bread-only\b/iu],
1953
+ ["inspect", /\binspect(?:s|ed|ing|ion)?\b/iu],
1954
+ ["investigate", /\binvestigat(?:e[sd]?|ing|ion)\b/iu],
1955
+ ["review", /\breview(?:s|ed|ing)?\b/iu],
1956
+ ["analyze", /\banalyz(?:e[sd]?|ing)\b/iu],
1957
+ ["search", /\bsearch(?:es|ed|ing)?\b/iu],
1958
+ ["audit", /\baudit(?:s|ed|ing)?\b/iu],
1959
+ ];
1960
+
1961
+ const WORKER_MODE_ARTIFACT_SIGNALS = [
1962
+ ..."코드 함수 테스트 스크립트 로직 파일 버그 핸들러 버튼 UI 구현 리팩터 초안 노트 로컬 워크트리 브랜치 스킬 알고리즘 충돌 문서 어댑터 클라이언트 파서"
1963
+ .split(" ")
1964
+ .map((signal) => [signal, new RegExp(`(?<![가-힣])${signal}`, "u")]),
1965
+ ..."code function test script logic file bug handler implement refactor draft notes local worktree branch conflict README CHANGELOG"
1966
+ .split(" ")
1967
+ .map((signal) => [
1968
+ signal,
1969
+ new RegExp(`\\b${signal}(?:s|ed|ing)?\\b`, "iu"),
1970
+ ]),
1971
+ ["파일 확장자", /\.[a-z][a-z0-9]{0,9}\b/iu],
1972
+ ];
1973
+
1974
+ /**
1975
+ * 15. 워커 권한 모드를 권고하고, Jev를 쓰지 못할 때도 규칙 판정을 반환한다.
1976
+ */
1977
+ export async function judgeWorkerMode(input = {}, options = {}) {
1978
+ const task =
1979
+ typeof input?.task === "string" ? input.task.normalize("NFKC") : "";
1980
+ const rule = classifyWorkerMode(task);
1981
+ const fallback = {
1982
+ ...rule,
1983
+ source: "rule",
1984
+ fallbackReason: "jev_disabled",
1985
+ ruleMode: rule.mode,
1986
+ confidence: null,
1987
+ };
1988
+ if (!isJevEnabled(options)) return fallback;
1989
+
1990
+ const summary = task.replace(/\s+/gu, " ").trim();
1991
+ // 마스킹하면 판정 단어가 사라질 수 있으므로 개인정보가 있으면 통째로 보내지 않는다.
1992
+ const excerpt =
1993
+ summary.length > 1000 ||
1994
+ WORKER_MODE_PRIVATE_PATTERNS.some((pattern) => pattern.test(summary))
1995
+ ? null
1996
+ : semanticExcerpt(summary, 1000);
1997
+ const context = excerpt === null ? null : semanticContext({ task: excerpt });
1998
+ if (context === null) return { ...fallback, fallbackReason: "unsafe_input" };
1999
+
2000
+ const res = await queryJev(
2001
+ {
2002
+ hook: "worker_mode",
2003
+ kind: "choice",
2004
+ options: ["bypass", "auto", "unclear"],
2005
+ criteria: {
2006
+ bypass:
2007
+ "The worker changes resources outside its own worktree: git push, PR creation or merge, releases, deployment, remote API writes (PATCH, PUT, POST, DELETE), remote or shared resource deletion, Docs or Notion publishing, or sending mail.",
2008
+ auto: "The worker only reads, researches, reviews, edits files inside its own worktree, or runs local tests.",
2009
+ unclear:
2010
+ "The summary does not establish whether the worker changes external resources.",
2011
+ },
2012
+ query:
2013
+ "Recommend the worker permission mode from the task summary. Actions described as prohibited or forbidden are not actions the worker will perform. Choose unclear rather than forcing a decision when external changes are uncertain.",
2014
+ context,
2015
+ },
2016
+ options,
2017
+ );
2018
+ if (!res) return { ...fallback, fallbackReason: "jev_failed" };
2019
+ if (res.confidence < minConfidence(options))
2020
+ return { ...fallback, fallbackReason: "low_confidence" };
2021
+ if (!["bypass", "auto", "unclear"].includes(res.choice))
2022
+ return { ...fallback, fallbackReason: "jev_failed" };
2023
+ if (res.choice === "unclear")
2024
+ return { ...fallback, fallbackReason: "jev_unclear" };
2025
+ return {
2026
+ mode: res.choice,
2027
+ reason:
2028
+ res.choice === "bypass"
2029
+ ? "외부 자원을 변경하는 작업으로 판단해 bypass를 권고합니다."
2030
+ : "읽기, 워크트리 파일 수정과 로컬 테스트 범위로 판단해 auto를 권고합니다.",
2031
+ source: "jev",
2032
+ fallbackReason: null,
2033
+ ruleMode: rule.mode,
2034
+ confidence: res.confidence,
2035
+ };
2036
+ }
2037
+
2038
+ export function classifyWorkerMode(task = "") {
2039
+ const summary = (typeof task === "string" ? task : "")
2040
+ .slice(0, 2000)
2041
+ .replace(/[^\S\r\n]+/gu, " ")
2042
+ .trim();
2043
+ const sentences = summary.split(/[.;!?](?=\s|$)|[\r\n]+/u);
2044
+ // 실제 할 일과 금지가 섞인 문장도 제외해 불필요한 bypass보다 auto를 우선한다.
2045
+ const activeSentences = sentences.filter(
2046
+ (sentence) => !WORKER_MODE_EXCLUDED_SENTENCE.test(sentence),
2047
+ );
2048
+ const writes = WORKER_MODE_WRITE_SIGNALS.filter(([, pattern]) =>
2049
+ activeSentences.some((sentence) => pattern.test(sentence)),
2050
+ ).map(([signal]) => signal);
2051
+ const excludedWrites = WORKER_MODE_WRITE_SIGNALS.filter(([, pattern]) =>
2052
+ sentences.some(
2053
+ (sentence) =>
2054
+ WORKER_MODE_EXCLUDED_SENTENCE.test(sentence) && pattern.test(sentence),
2055
+ ),
2056
+ ).map(([signal]) => signal);
2057
+ const excludedReason = excludedWrites.length
2058
+ ? `제외된 외부 변경 신호(${excludedWrites.join(", ")})가 ${writes.length ? "있습니다." : "있어 모호합니다."}`
2059
+ : "";
2060
+ const localSignals = [
2061
+ ...WORKER_MODE_READ_SIGNALS,
2062
+ ...WORKER_MODE_ARTIFACT_SIGNALS,
2063
+ ]
2064
+ .filter(([, pattern]) => pattern.test(summary))
2065
+ .map(([signal]) => signal);
2066
+ if (writes.length && localSignals.length)
2067
+ return {
2068
+ mode: "auto",
2069
+ reason: `외부 변경 신호(${writes.join(", ")})와 읽기 또는 코드 산출물 신호(${localSignals.join(", ")})가 섞여 모호하므로 auto를 권고합니다.${excludedReason ? ` ${excludedReason}` : ""}`,
2070
+ };
2071
+ if (writes.length)
2072
+ return {
2073
+ mode: "bypass",
2074
+ reason: `외부 변경 신호(${writes.join(", ")})가 있어 bypass를 권고합니다.${excludedReason ? ` ${excludedReason}` : ""}`,
2075
+ };
2076
+ return {
2077
+ mode: "auto",
2078
+ reason:
2079
+ (excludedReason && `${excludedReason} auto를 권고합니다.`) ||
2080
+ (localSignals.length
2081
+ ? `읽기 또는 코드 산출물 신호(${localSignals.join(", ")})가 있어 auto를 권고합니다.`
2082
+ : "외부 변경 신호가 없어 auto를 권고합니다."),
2083
+ };
2084
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@triflux/core",
3
- "version": "10.92.0",
3
+ "version": "10.93.1",
4
4
  "description": "triflux core — CLI routing, adapters. Zero native dependencies.",
5
5
  "type": "module",
6
6
  "main": "hub/index.mjs",