cdsa-harness 0.13.0 → 0.14.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdsa-harness",
3
- "version": "0.13.0",
3
+ "version": "0.14.2",
4
4
  "description": "AI 에이전트의 내부 동작을 단계별로 드러내는 교육용 터미널 하네스. 실시간 스트리밍 + OpenAI/Claude/OpenRouter + MCP + npm 플러그인·크로스포맷 스킬.",
5
5
  "type": "module",
6
6
  "bin": {
package/skills/answer.md CHANGED
@@ -1,6 +1,11 @@
1
1
  ---
2
+ argument-hint: "<민원 내용 또는 파일경로>"
2
3
  description: 민원 답변서(회신문) 초안 작성 [공공]
3
4
  ---
4
- 다음 민원에 대한 답변서(회신문) 초안을 작성해줘($ARGUMENTS 가 파일명이면 read_file 로 읽어).
5
+ 아래 [민원]에 대한 답변서(회신문) 초안을 작성해줘.
6
+ ※ [민원]이 명백한 파일 경로면 read_file 로 읽고, 아니면 그 문장 자체를 민원으로 본다(파일을 찾지 마라).
5
7
  구성: 인사말 / 민원요지 요약 / 검토결과(근거·관련규정[ ]) / 조치사항 / 맺음말.
6
- 정중하고 공감하는 공공 민원 답변 문체로. 단정이 어려운 부분은 '확인 후 안내' 로 표시.
8
+ 정중하고 공감하는 공공 민원 답변 문체로. 단정이 어려운 부분은 '확인 후 안내'로 표시.
9
+
10
+ [민원]
11
+ $ARGUMENTS
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<보도협조 주제>"
2
3
  description: 보도협조(브리핑) 요청문·요약 작성 [공공]
3
4
  ---
4
5
  "$ARGUMENTS" 를 주제로 언론 보도협조 요청문을 작성해줘.
package/skills/budget.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<예산 내용 또는 파일경로>"
2
3
  description: 예산(안) 항목을 검토·요약하고 점검 포인트 제시 [공공]
3
4
  ---
4
5
  $ARGUMENTS (파일명이면 read_file 로 읽어)의 예산 내용을 한국어로 정리해줘.
package/skills/eli5.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<파일명 또는 개념>"
2
3
  description: 지정한 파일/개념을 5살도 알게 아주 쉽게 설명
3
4
  ---
4
5
  $ARGUMENTS 을(를) 다섯 살 아이도 이해할 수 있게 아주 쉬운 비유로 설명해줘.
package/skills/explain.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<파일명 또는 대상>"
2
3
  description: 파일/코드를 읽고 비개발자도 알게 쉽게 설명
3
4
  ---
4
5
  $ARGUMENTS 을(를) read_file 도구로 읽고, 무엇을 하는 코드/문서인지
package/skills/gongmun.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<공문 주제·내용>"
2
3
  description: 공문서(기안문) 초안 작성 — 제목/수신/본문/붙임 [공공]
3
4
  ---
4
5
  "$ARGUMENTS" 내용을 바탕으로 대한민국 행정 공문서(기안문) 초안을 작성해줘.
package/skills/haiku.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<주제 또는 파일명>"
2
3
  description: 파일이나 주제로 하이쿠(짧은 3줄 시) 짓기 — 재미용
3
4
  ---
4
5
  $ARGUMENTS (파일명이면 read_file 로 그 내용을 읽어)를 주제로,
package/skills/hwpx.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<파일.hwpx>"
2
3
  description: 한컴 HWPX(.hwpx) 문서를 읽어 핵심을 개조식으로 요약 [공공]
3
4
  ---
4
5
  $ARGUMENTS 파일을 hwpx_read 도구로 읽어 본문 텍스트를 추출한 뒤,
package/skills/insa.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<인사발령 내용>"
2
3
  description: 인사발령 공지문 초안 작성 [공공]
3
4
  ---
4
5
  "$ARGUMENTS" 내용을 바탕으로 인사발령 공지문 초안을 작성해줘.
package/skills/loop.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<요청>"
2
3
  description: 이 요청을 '에이전트 루프' 관점에서 어떻게 처리할지 메타 해설 (CDSA 특별)
3
4
  ---
4
5
  다음 요청을 실제로 수행하기 전에, CDSA Harness 의 에이전트 루프
package/skills/minutes.md CHANGED
@@ -1,7 +1,10 @@
1
1
  ---
2
+ argument-hint: "<회의 내용 또는 파일경로>"
2
3
  description: 회의 내용을 표준 회의록으로 정리(안건/논의/결정/조치) [공공]
3
4
  ---
4
- $ARGUMENTS (파일명이면 read_file 로 읽어)를 회의록으로 정리해줘.
5
- 머리말: 일시[ ] / 장소[ ] / 참석[ ].
6
- 본문: 안건별로 [논의내용] · [결정사항] · [조치사항(담당/기한)] 개조식으로.
7
- 한국어로 깔끔하게. 파일은 수정하지 마.
5
+ 아래 [회의내용]을 회의록으로 정리해줘.
6
+ [회의내용] 명백한 파일 경로면 read_file 로 읽고, 아니면 그 문장 자체를 대상으로 본다.
7
+ 머리말: 일시[ ]/장소[ ]/참석[ ]. 본문: 안건별 [논의내용]·[결정사항]·[조치(담당/기한)]. 개조식. 파일은 수정하지 마.
8
+
9
+ [회의내용]
10
+ $ARGUMENTS
package/skills/minwon.md CHANGED
@@ -1,7 +1,11 @@
1
1
  ---
2
+ argument-hint: "<민원 내용 또는 파일경로>"
2
3
  description: 민원 내용을 분류(분야·담당부서·긴급도)하고 처리 방향 제안 [공공]
3
4
  ---
4
- 다음 민원을 분석해줘($ARGUMENTS 가 파일명이면 read_file 로 읽어).
5
- 형태로 한국어로 깔끔하게 정리해줘:
6
- 1) 분야 분류 2) 담당 부서(추정) 3) 긴급도(상/중/하) 4) 핵심 요지 1줄 5) 처리 방향 제안
7
- 개인정보(연락처·주민번호 등)가 보이면 마스킹해서 표시해줘. 파일은 수정하지 마.
5
+ 아래 [민원]을 분석해줘.
6
+ [민원]이 `notes.txt`·`민원.hwpx` 같은 **파일 경로 형식**이면 먼저 read_file(.hwpx 는 hwpx_read)로 읽어 그 내용을 민원으로 삼고, 그 외 일반 문장이면 **그 문장 자체가 민원**이다(파일을 찾지 마라).
7
+ 표로 한국어 정리: 1) 분야 분류 2) 담당 부서(추정) 3) 긴급도(상/중/하) 4) 핵심 요지 1줄 5) 처리 방향 제안.
8
+ 개인정보가 보이면 마스킹. 파일은 수정하지 마.
9
+
10
+ [민원]
11
+ $ARGUMENTS
package/skills/notice.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<공고 주제·내용>"
2
3
  description: 공고문(모집·입찰·안내) 초안 작성 [공공]
3
4
  ---
4
5
  "$ARGUMENTS" 를 주제로 공공 공고문 초안을 작성해줘.
package/skills/plan.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<할 작업>"
2
3
  description: 시킨 작업을 '실행하지 말고' 단계별 계획만 세우기
3
4
  ---
4
5
  다음 작업을 실제로 수행하지 말고, 어떻게 처리할지 단계별 계획만 세워줘: $ARGUMENTS
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<질문>"
2
3
  description: 정책·제도 질문에 근거를 들어 설명(모르면 모른다고) [공공]
3
4
  ---
4
5
  다음 질문에 공공 정책·행정 관점에서 한국어로 답해줘: $ARGUMENTS
package/skills/press.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<보도 주제>"
2
3
  description: 보도자료 초안 작성(제목/부제/리드/본문/문의처) [공공]
3
4
  ---
4
5
  "$ARGUMENTS" 를 주제로 공공기관 보도자료 초안을 작성해줘.
package/skills/privacy.md CHANGED
@@ -1,7 +1,10 @@
1
1
  ---
2
+ argument-hint: "<점검할 텍스트 또는 파일경로>"
2
3
  description: 개인정보(주민번호·연락처·이메일·계좌 등) 점검 및 마스킹 제안 [공공]
3
4
  ---
4
- $ARGUMENTS (파일명이면 read_file 로 읽어)에서 개인정보로 보이는 항목
5
- (주민등록번호, 휴대전화, 이메일, 주소, 계좌·카드번호, 여권번호 ) 찾아
6
- '유형 — 발견 위치 — 마스킹 예시(예: 010-****-1234)' 표로 정리해줘.
7
- 하나도 없으면 없다고 명확히 말해줘. 파일은 절대 수정하지 말고 점검 보고만 해줘.
5
+ 아래 [대상]에서 개인정보로 보이는 항목(주민등록번호·휴대전화·이메일·주소·계좌/카드번호·여권번호 등)을 찾아줘.
6
+ [대상]이 명백한 파일 경로면 read_file(.hwpx 는 hwpx_read) 읽고, 아니면 그 문장 자체를 점검 대상으로 본다.
7
+ '유형 — 발견 위치 — 마스킹 예시(예: 010-****-1234)' 표로 정리. 없으면 없다고 명확히. 파일은 수정하지 말고 점검 보고만.
8
+
9
+ [대상]
10
+ $ARGUMENTS
package/skills/quiz.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "[주제(선택)]"
2
3
  description: 작업 폴더 코드/문서로 학습 퀴즈 만들기
3
4
  ---
4
5
  작업 폴더를 list_dir / read_file 로 살펴보고, 이 코드·문서를 공부할 수 있는
package/skills/report.md CHANGED
@@ -1,6 +1,10 @@
1
1
  ---
2
+ argument-hint: "<요약할 내용 또는 파일경로>"
2
3
  description: 개조식 1쪽 보고서로 요약(추진배경/현황/문제점/개선방안) [공공]
3
4
  ---
4
- $ARGUMENTS (파일명이면 read_file 로 읽어)를 대한민국 공공 보고서 양식의 개조식으로
5
- 1쪽 분량으로 요약해줘. 항목: 추진배경 / 현황 / 문제점 / 개선방안 / □ 기대효과.
6
- 항목은 'ㅇ' 글머리로 간결하게. 파일은 수정하지 마.
5
+ 아래 [내용]을 대한민국 공공 보고서 양식의 개조식으로 1쪽 요약해줘.
6
+ [내용]이 명백한 파일 경로면 read_file(.hwpx hwpx_read)로 읽고, 아니면 문장 자체를 대상으로 본다.
7
+ 항목: □ 추진배경 / □ 현황 / □ 문제점 / □ 개선방안 / □ 기대효과. 항목 'ㅇ' 글머리. 파일은 수정하지 마.
8
+
9
+ [내용]
10
+ $ARGUMENTS
package/skills/review.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<파일명>"
2
3
  description: 파일을 읽고 버그 위험·개선점을 리뷰
3
4
  ---
4
5
  $ARGUMENTS 파일을 read_file 도구로 읽고, 버그 위험·가독성·개선점을 한국어로 짚어줘.
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<고민·문제>"
2
3
  description: 러버덕 디버깅 — 답 대신 질문으로 스스로 찾게 돕기
3
4
  ---
4
5
  당신은 '러버덕(고무오리)' 디버깅 파트너입니다. 사용자의 고민: $ARGUMENTS
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "<파일명>"
2
3
  description: 지정한 파일을 읽고 한국어 3줄로 요약
3
4
  ---
4
5
  $ARGUMENTS 파일을 read_file 도구로 읽은 다음, 핵심 내용을 한국어 3줄로 요약해줘.
package/skills/todo.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "[대상(선택)]"
2
3
  description: 작업 폴더에서 TODO/FIXME 주석을 모아 목록화
3
4
  ---
4
5
  작업 폴더의 파일들을 list_dir / read_file 로 훑어 TODO, FIXME, XXX, HACK 같은
package/skills/tour.md CHANGED
@@ -1,4 +1,5 @@
1
1
  ---
2
+ argument-hint: "[주제(선택)]"
2
3
  description: 작업 폴더를 둘러보고 이 프로젝트가 무엇인지 브리핑
3
4
  ---
4
5
  작업 폴더를 list_dir 로 살펴보고, 핵심으로 보이는 파일 2~4개를 read_file 로 읽어
package/src/builtins.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // 스킬/플러그인/버전을 바꾼 뒤 `node scripts/gen-builtins.mjs` 로 재생성하세요.
3
3
  import p0 from "../plugins/hwpx_read.mjs";
4
4
 
5
- export const VERSION = "0.13.0";
5
+ export const VERSION = "0.14.2";
6
6
 
7
7
  export const BUILTIN_PLUGINS = [p0];
8
8
 
@@ -10,121 +10,145 @@ export const BUILTIN_SKILLS = [
10
10
  {
11
11
  "name": "answer",
12
12
  "description": "민원 답변서(회신문) 초안 작성 [공공]",
13
- "body": "다음 민원에 대한 답변서(회신문) 초안을 작성해줘($ARGUMENTS 가 파일명이면 read_file 로 읽어).\n구성: 인사말 / 민원요지 요약 / 검토결과(근거·관련규정[ ]) / 조치사항 / 맺음말.\n정중하고 공감하는 공공 민원 답변 문체로. 단정이 어려운 부분은 '확인 후 안내' 로 표시."
13
+ "hint": "<민원 내용 또는 파일경로>",
14
+ "body": "아래 [민원]에 대한 답변서(회신문) 초안을 작성해줘.\n※ [민원]이 명백한 파일 경로면 read_file 로 읽고, 아니면 그 문장 자체를 민원으로 본다(파일을 찾지 마라).\n구성: 인사말 / 민원요지 요약 / 검토결과(근거·관련규정[ ]) / 조치사항 / 맺음말.\n정중하고 공감하는 공공 민원 답변 문체로. 단정이 어려운 부분은 '확인 후 안내'로 표시.\n\n[민원]\n$ARGUMENTS"
14
15
  },
15
16
  {
16
17
  "name": "briefing",
17
18
  "description": "보도협조(브리핑) 요청문·요약 작성 [공공]",
19
+ "hint": "<보도협조 주제>",
18
20
  "body": "\"$ARGUMENTS\" 를 주제로 언론 보도협조 요청문을 작성해줘.\n구성: 제목 / 협조요청 취지 / 핵심 메시지 3가지 / 일정·자료 / 담당자 연락처[ ].\n객관적이고 간결하게 한국어로. 과장 표현은 피해줘."
19
21
  },
20
22
  {
21
23
  "name": "budget",
22
24
  "description": "예산(안) 항목을 검토·요약하고 점검 포인트 제시 [공공]",
25
+ "hint": "<예산 내용 또는 파일경로>",
23
26
  "body": "$ARGUMENTS (파일명이면 read_file 로 읽어)의 예산 내용을 한국어로 정리해줘.\n1) 총액·주요 항목 표(항목 / 금액 / 비중) 2) 전년 대비/특이사항 3) 점검 포인트(과다·누락·근거 불명).\n숫자는 그대로 인용하고, 계산이 필요하면 근거를 함께 보여줘. 파일은 수정하지 마."
24
27
  },
25
28
  {
26
29
  "name": "eli5",
27
30
  "description": "지정한 파일/개념을 5살도 알게 아주 쉽게 설명",
31
+ "hint": "<파일명 또는 개념>",
28
32
  "body": "$ARGUMENTS 을(를) 다섯 살 아이도 이해할 수 있게 아주 쉬운 비유로 설명해줘.\n파일 이름이면 read_file 로 읽고 설명해. 전문용어는 풀어서 쓰고, 그림 그리듯 비유를 들어줘.\n파일은 수정하지 마."
29
33
  },
30
34
  {
31
35
  "name": "explain",
32
36
  "description": "파일/코드를 읽고 비개발자도 알게 쉽게 설명",
37
+ "hint": "<파일명 또는 대상>",
33
38
  "body": "$ARGUMENTS 을(를) read_file 도구로 읽고, 무엇을 하는 코드/문서인지\n비개발자도 이해할 수 있게 한국어로 차근차근 설명해줘. 실제 수정은 하지 마."
34
39
  },
35
40
  {
36
41
  "name": "gongmun",
37
42
  "description": "공문서(기안문) 초안 작성 — 제목/수신/본문/붙임 [공공]",
43
+ "hint": "<공문 주제·내용>",
38
44
  "body": "\"$ARGUMENTS\" 내용을 바탕으로 대한민국 행정 공문서(기안문) 초안을 작성해줘.\n형식: 제목 / 수신 / (경유) / 본문(개조식, 'ㅇ' 글머리) / 붙임.\n정중한 공공 문체로. 사실이 불확실한 부분은 [ ] 로 비워 '채울 항목'임을 표시해줘."
39
45
  },
40
46
  {
41
47
  "name": "haiku",
42
48
  "description": "파일이나 주제로 하이쿠(짧은 3줄 시) 짓기 — 재미용",
49
+ "hint": "<주제 또는 파일명>",
43
50
  "body": "$ARGUMENTS (파일명이면 read_file 로 그 내용을 읽어)를 주제로,\n5-7-5 운율을 살린 한국어 하이쿠를 한 편 지어줘. 짧고 운치 있게.\n파일은 절대 건드리지 말고 시만 보여줘."
44
51
  },
45
52
  {
46
53
  "name": "hwpx",
47
54
  "description": "한컴 HWPX(.hwpx) 문서를 읽어 핵심을 개조식으로 요약 [공공]",
55
+ "hint": "<파일.hwpx>",
48
56
  "body": "$ARGUMENTS 파일을 hwpx_read 도구로 읽어 본문 텍스트를 추출한 뒤,\n핵심 내용을 한국어 개조식('ㅇ' 글머리)으로 요약해줘. 파일은 수정하지 마.\n(.hwp 구버전이면 .hwpx 로 저장 후 다시 시도하라고 안내해줘.)"
49
57
  },
50
58
  {
51
59
  "name": "insa",
52
60
  "description": "인사발령 공지문 초안 작성 [공공]",
61
+ "hint": "<인사발령 내용>",
53
62
  "body": "\"$ARGUMENTS\" 내용을 바탕으로 인사발령 공지문 초안을 작성해줘.\n형식: 제목 / 발령일자[ ] / 대상자(직위·성명·소속) 표 / 발령내용(전보·승진·신규 등) / 비고.\n정중한 공공 문체로. 확실하지 않은 항목은 [ ] 로 비워둬. 파일은 수정하지 마."
54
63
  },
55
64
  {
56
65
  "name": "loop",
57
66
  "description": "이 요청을 '에이전트 루프' 관점에서 어떻게 처리할지 메타 해설 (CDSA 특별)",
67
+ "hint": "<요청>",
58
68
  "body": "다음 요청을 실제로 수행하기 전에, CDSA Harness 의 에이전트 루프\n(컨텍스트 구성 → LLM 호출 → 도구 판단 → 실행 → 결과 되먹임) 관점에서\n어떤 도구를 어떤 순서로 부를 계획인지 단계별로 한국어로 먼저 설명해줘: $ARGUMENTS\n설명만 하고, 이번에는 실제 도구 호출(특히 write_file)은 하지 마."
59
69
  },
60
70
  {
61
71
  "name": "minutes",
62
72
  "description": "회의 내용을 표준 회의록으로 정리(안건/논의/결정/조치) [공공]",
63
- "body": "$ARGUMENTS (파일명이면 read_file 로 읽어)를 회의록으로 정리해줘.\n머리말: 일시[ ] / 장소[ ] / 참석[ ].\n본문: 안건별로 [논의내용] · [결정사항] · [조치사항(담당/기한)] 을 개조식으로.\n한국어로 깔끔하게. 파일은 수정하지 마."
73
+ "hint": "<회의 내용 또는 파일경로>",
74
+ "body": "아래 [회의내용]을 회의록으로 정리해줘.\n※ [회의내용]이 명백한 파일 경로면 read_file 로 읽고, 아니면 그 문장 자체를 대상으로 본다.\n머리말: 일시[ ]/장소[ ]/참석[ ]. 본문: 안건별 [논의내용]·[결정사항]·[조치(담당/기한)]. 개조식. 파일은 수정하지 마.\n\n[회의내용]\n$ARGUMENTS"
64
75
  },
65
76
  {
66
77
  "name": "minwon",
67
78
  "description": "민원 내용을 분류(분야·담당부서·긴급도)하고 처리 방향 제안 [공공]",
68
- "body": "다음 민원을 분석해줘($ARGUMENTS 가 파일명이면 read_file 로 읽어).\n표 형태로 한국어로 깔끔하게 정리해줘:\n1) 분야 분류 2) 담당 부서(추정) 3) 긴급도(상/중/하) 4) 핵심 요지 1줄 5) 처리 방향 제안\n개인정보(연락처·주민번호 등)가 보이면 마스킹해서 표시해줘. 파일은 수정하지 마."
79
+ "hint": "<민원 내용 또는 파일경로>",
80
+ "body": "아래 [민원]을 분석해줘.\n※ [민원]이 `notes.txt`·`민원.hwpx` 같은 **파일 경로 형식**이면 먼저 read_file(.hwpx 는 hwpx_read)로 읽어 그 내용을 민원으로 삼고, 그 외 일반 문장이면 **그 문장 자체가 민원**이다(파일을 찾지 마라).\n표로 한국어 정리: 1) 분야 분류 2) 담당 부서(추정) 3) 긴급도(상/중/하) 4) 핵심 요지 1줄 5) 처리 방향 제안.\n개인정보가 보이면 마스킹. 파일은 수정하지 마.\n\n[민원]\n$ARGUMENTS"
69
81
  },
70
82
  {
71
83
  "name": "notice",
72
84
  "description": "공고문(모집·입찰·안내) 초안 작성 [공공]",
85
+ "hint": "<공고 주제·내용>",
73
86
  "body": "\"$ARGUMENTS\" 를 주제로 공공 공고문 초안을 작성해줘.\n형식: 제목 / 공고번호[ ] / 목적 / 대상·자격 / 기간·일정 / 신청방법 / 문의처[ ] / 붙임.\n간결하고 명확한 공고 문체로 한국어 작성."
74
87
  },
75
88
  {
76
89
  "name": "plan",
77
90
  "description": "시킨 작업을 '실행하지 말고' 단계별 계획만 세우기",
91
+ "hint": "<할 작업>",
78
92
  "body": "다음 작업을 실제로 수행하지 말고, 어떻게 처리할지 단계별 계획만 세워줘: $ARGUMENTS\n필요하면 read_file / list_dir 로 사실만 확인하되, write_file 은 절대 호출하지 마.\n각 단계마다 '왜 그 단계가 필요한지'도 한 줄씩 덧붙여줘."
79
93
  },
80
94
  {
81
95
  "name": "policyqa",
82
96
  "description": "정책·제도 질문에 근거를 들어 설명(모르면 모른다고) [공공]",
97
+ "hint": "<질문>",
83
98
  "body": "다음 질문에 공공 정책·행정 관점에서 한국어로 답해줘: $ARGUMENTS\n필요하면 작업 폴더의 관련 문서를 read_file 로 확인해 근거로 삼아.\n확실하지 않은 부분은 추측하지 말고 '확인 필요'로 표시하고, 어디서/누구에게 확인하면 되는지 안내해줘."
84
99
  },
85
100
  {
86
101
  "name": "press",
87
102
  "description": "보도자료 초안 작성(제목/부제/리드/본문/문의처) [공공]",
103
+ "hint": "<보도 주제>",
88
104
  "body": "\"$ARGUMENTS\" 를 주제로 공공기관 보도자료 초안을 작성해줘.\n구성: 제목 / 부제 / 리드(핵심 1문단) / 본문(육하원칙) / 기대효과 / 문의처[ ].\n객관적이고 간결한 보도 문체로 한국어로 작성해줘."
89
105
  },
90
106
  {
91
107
  "name": "privacy",
92
108
  "description": "개인정보(주민번호·연락처·이메일·계좌 등) 점검 및 마스킹 제안 [공공]",
93
- "body": "$ARGUMENTS (파일명이면 read_file 로 읽어)에서 개인정보로 보이는 항목\n(주민등록번호, 휴대전화, 이메일, 주소, 계좌·카드번호, 여권번호 등)을 찾아\n'유형 — 발견 위치 — 마스킹 예시(예: 010-****-1234)' 표로 정리해줘.\n하나도 없으면 없다고 명확히 말해줘. 파일은 절대 수정하지 말고 점검 보고만 해줘."
109
+ "hint": "<점검할 텍스트 또는 파일경로>",
110
+ "body": "아래 [대상]에서 개인정보로 보이는 항목(주민등록번호·휴대전화·이메일·주소·계좌/카드번호·여권번호 등)을 찾아줘.\n※ [대상]이 명백한 파일 경로면 read_file(.hwpx 는 hwpx_read)로 읽고, 아니면 그 문장 자체를 점검 대상으로 본다.\n'유형 — 발견 위치 — 마스킹 예시(예: 010-****-1234)' 표로 정리. 없으면 없다고 명확히. 파일은 수정하지 말고 점검 보고만.\n\n[대상]\n$ARGUMENTS"
94
111
  },
95
112
  {
96
113
  "name": "quiz",
97
114
  "description": "작업 폴더 코드/문서로 학습 퀴즈 만들기",
115
+ "hint": "[주제(선택)]",
98
116
  "body": "작업 폴더를 list_dir / read_file 로 살펴보고, 이 코드·문서를 공부할 수 있는\n객관식 퀴즈 3문제를 한국어로 만들어줘($ARGUMENTS 주제가 있으면 그쪽으로).\n각 문제에 보기 4개와 정답, 그리고 한 줄 해설을 달아줘. 파일은 수정하지 마."
99
117
  },
100
118
  {
101
119
  "name": "report",
102
120
  "description": "개조식 1쪽 보고서로 요약(추진배경/현황/문제점/개선방안) [공공]",
103
- "body": "$ARGUMENTS (파일명이면 read_file 로 읽어)를 대한민국 공공 보고서 양식의 개조식으로\n1쪽 분량으로 요약해줘. 항목: □ 추진배경 / □ 현황 / □ 문제점 / □ 개선방안 / □ 기대효과.\n각 항목은 'ㅇ' 글머리로 간결하게. 파일은 수정하지 마."
121
+ "hint": "<요약할 내용 또는 파일경로>",
122
+ "body": "아래 [내용]을 대한민국 공공 보고서 양식의 개조식으로 1쪽 요약해줘.\n※ [내용]이 명백한 파일 경로면 read_file(.hwpx 는 hwpx_read)로 읽고, 아니면 그 문장 자체를 대상으로 본다.\n항목: □ 추진배경 / □ 현황 / □ 문제점 / □ 개선방안 / □ 기대효과. 각 항목 'ㅇ' 글머리. 파일은 수정하지 마.\n\n[내용]\n$ARGUMENTS"
104
123
  },
105
124
  {
106
125
  "name": "review",
107
126
  "description": "파일을 읽고 버그 위험·개선점을 리뷰",
127
+ "hint": "<파일명>",
108
128
  "body": "$ARGUMENTS 파일을 read_file 도구로 읽고, 버그 위험·가독성·개선점을 한국어로 짚어줘.\n실제 수정은 하지 말고, 무엇을 어떻게 고치면 좋을지 제안만 해줘."
109
129
  },
110
130
  {
111
131
  "name": "rubberduck",
112
132
  "description": "러버덕 디버깅 — 답 대신 질문으로 스스로 찾게 돕기",
133
+ "hint": "<고민·문제>",
113
134
  "body": "당신은 '러버덕(고무오리)' 디버깅 파트너입니다. 사용자의 고민: $ARGUMENTS\n바로 정답을 주지 말고, 문제를 좁히는 좋은 질문 3~5개를 한국어로 던져줘.\n필요하면 read_file 로 관련 파일을 살펴봐도 되지만, 절대 수정하지 마.\n마지막에 \"이 중 어디부터 확인해볼까요?\" 라고 물어줘."
114
135
  },
115
136
  {
116
137
  "name": "summarize",
117
138
  "description": "지정한 파일을 읽고 한국어 3줄로 요약",
139
+ "hint": "<파일명>",
118
140
  "body": "$ARGUMENTS 파일을 read_file 도구로 읽은 다음, 핵심 내용을 한국어 3줄로 요약해줘.\n파일을 수정하지는 말고, 요약만 보여줘."
119
141
  },
120
142
  {
121
143
  "name": "todo",
122
144
  "description": "작업 폴더에서 TODO/FIXME 주석을 모아 목록화",
145
+ "hint": "[대상(선택)]",
123
146
  "body": "작업 폴더의 파일들을 list_dir / read_file 로 훑어 TODO, FIXME, XXX, HACK 같은\n미완성 표시가 있는 곳을 찾아 '파일 — 해당 줄 내용' 형태의 한국어 목록으로 정리해줘.\n하나도 없으면 없다고 분명히 말해줘. 파일은 수정하지 마."
124
147
  },
125
148
  {
126
149
  "name": "tour",
127
150
  "description": "작업 폴더를 둘러보고 이 프로젝트가 무엇인지 브리핑",
151
+ "hint": "[주제(선택)]",
128
152
  "body": "작업 폴더를 list_dir 로 살펴보고, 핵심으로 보이는 파일 2~4개를 read_file 로 읽어\n이 프로젝트가 무엇이고 어떤 구조인지 한국어로 친절하게 브리핑해줘.\n$ARGUMENTS 가 있으면 그 주제에 초점을 맞춰. 파일은 절대 수정하지 마."
129
153
  }
130
154
  ];
package/src/cli.js CHANGED
@@ -197,6 +197,44 @@ function makeApproval(ask) {
197
197
  };
198
198
  }
199
199
 
200
+ function isNewer(a, b) {
201
+ const pa = String(a).split(".").map((n) => parseInt(n, 10) || 0);
202
+ const pb = String(b).split(".").map((n) => parseInt(n, 10) || 0);
203
+ for (let i = 0; i < 3; i++) {
204
+ if ((pa[i] || 0) > (pb[i] || 0)) return true;
205
+ if ((pa[i] || 0) < (pb[i] || 0)) return false;
206
+ }
207
+ return false;
208
+ }
209
+
210
+ // 시작 시 새 버전 확인(하루 1회, 네트워크 실패는 조용히 무시 → 폐쇄망 안전).
211
+ async function maybeCheckUpdate(cfg) {
212
+ if (cfg.update_check === false) return null;
213
+ const stamp = path.join(configDir(), ".update_check");
214
+ try {
215
+ if (Date.now() - Number(fs.readFileSync(stamp, "utf8")) < 24 * 3600 * 1000) return null;
216
+ } catch {
217
+ /* 첫 확인 */
218
+ }
219
+ try {
220
+ fs.mkdirSync(configDir(), { recursive: true });
221
+ fs.writeFileSync(stamp, String(Date.now())); // 결과와 무관하게 하루 1회로 제한
222
+ } catch {
223
+ /* ignore */
224
+ }
225
+ try {
226
+ const ctrl = new AbortController();
227
+ const t = setTimeout(() => ctrl.abort(), 1500);
228
+ const res = await fetch("https://registry.npmjs.org/cdsa-harness/latest", { signal: ctrl.signal });
229
+ clearTimeout(t);
230
+ if (!res.ok) return null;
231
+ const latest = (await res.json()).version;
232
+ return latest && isNewer(latest, VERSION) ? latest : null;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
200
238
  // 작업 폴더 기준으로 플러그인·스킬·도구상자를 구성한다(시작 시 + /workspace 변경 시).
201
239
  async function buildExtensions(cfg, mcp) {
202
240
  const filePlugins = await loadPlugins(cfg.workspacePath());
@@ -209,7 +247,7 @@ async function buildExtensions(cfg, mcp) {
209
247
  ...mcp.errors.map((e) => ({ error: `MCP ${e}` })),
210
248
  ];
211
249
  const skills = {};
212
- for (const s of npm.skills) skills[s.name] = { name: s.name, description: s.description || "", body: s.body, source: "(npm)" };
250
+ for (const s of npm.skills) skills[s.name] = { name: s.name, description: s.description || "", hint: s.hint || "", body: s.body, source: "(npm)" };
213
251
  Object.assign(
214
252
  skills,
215
253
  loadSkills(cfg.workspacePath(), { importForeign: cfg.import_foreign_skills, extraDirs: cfg.skill_dirs || [] })
@@ -539,6 +577,16 @@ export async function main(argv = []) {
539
577
 
540
578
  printIntro(cfg);
541
579
 
580
+ // 새 버전 안내(있을 때만, 하루 1회)
581
+ const newer = await maybeCheckUpdate(cfg);
582
+ if (newer) {
583
+ console.log(
584
+ c.yellow(`⬆️ 새 버전 v${newer} 가 나왔어요!`) +
585
+ c.dim(` 업데이트: npm i -g cdsa-harness@latest · exe 는 Releases 에서 새로 받기`) +
586
+ "\n"
587
+ );
588
+ }
589
+
542
590
  // ③ MCP 서버(다른 에이전트와 공용 표준) 연결 — 1회
543
591
  if (cfg.mcpServers && Object.keys(cfg.mcpServers).length) {
544
592
  process.stdout.write(c.dim("MCP 서버 연결 중...\r"));
@@ -578,10 +626,37 @@ export async function main(argv = []) {
578
626
 
579
627
  const rule = () => console.log(c.grey("─".repeat(Math.min(80, stdout.columns || 80))));
580
628
 
581
- // 첫 실행이면 튜토리얼을 권한다(한 번만 — ~/.cdsa_harness/.welcomed 표시).
629
+ // 첫 실행 온보딩(한 번만 — ~/.cdsa_harness/.welcomed 표시): 작업 폴더 설정 + 튜토리얼
582
630
  const markerPath = path.join(configDir(), ".welcomed");
583
631
  if (stdin.isTTY && !fs.existsSync(markerPath)) {
584
- const a = await ask(c.cyan("처음 오셨네요! 👋 짧은 튜토리얼을 볼까요? [Y/n] "));
632
+ console.log(panel(
633
+ [
634
+ "AI 가 파일을 다룰 ‘작업 폴더’를 정하세요.",
635
+ c.dim("이 폴더 밖은 절대 건드리지 않아요(안전장치)."),
636
+ "",
637
+ ` ${c.bold("엔터")} 기본값 ${c.cyan("./workspace")} (하위 폴더 자동 생성)`,
638
+ ` ${c.bold(".")} 지금 이 폴더를 그대로 사용`,
639
+ ` ${c.bold("경로")} 예) ${c.cyan("./문서")} 또는 ${c.cyan("C:\\작업\\프로젝트")}`,
640
+ ],
641
+ { title: "📁 작업 폴더 설정 (처음 한 번)", color: "cyan" }
642
+ ));
643
+ const wsAns = await ask(c.cyan("작업 폴더 [엔터=기본]: "));
644
+ if (wsAns !== null && wsAns.trim()) {
645
+ cfg.workspace = wsAns.trim();
646
+ const rebuilt = await buildExtensions(cfg, mcp);
647
+ toolbox = rebuilt.toolbox;
648
+ skills = rebuilt.skills;
649
+ loop.toolbox = toolbox;
650
+ loop.reset();
651
+ }
652
+ try {
653
+ saveConfig(cfg);
654
+ } catch {
655
+ /* 저장 실패 무시 */
656
+ }
657
+ console.log(c.green(`작업 폴더: ${cfg.workspacePath()}`) + c.dim(" (나중에 /workspace 로 변경 가능)\n"));
658
+
659
+ const a = await ask(c.cyan("짧은 튜토리얼을 볼까요? [Y/n] "));
585
660
  if (a !== null && ["", "y", "yes"].includes(a.trim().toLowerCase())) await runTutorial(ask);
586
661
  try {
587
662
  fs.mkdirSync(configDir(), { recursive: true });
@@ -721,8 +796,12 @@ export async function main(argv = []) {
721
796
  return c.dim("📄 파일");
722
797
  };
723
798
  const lines = names.length
724
- ? names.map((n) => `${c.cyan("/" + n)} ${c.grey(skills[n].description || "")} ${srcTag(skills[n])}`)
799
+ ? names.map((n) => {
800
+ const h = skills[n].hint ? " " + c.dim(skills[n].hint) : "";
801
+ return `${c.cyan("/" + n)}${h} ${c.grey(skills[n].description || "")} ${srcTag(skills[n])}`;
802
+ })
725
803
  : [c.dim("등록된 스킬이 없습니다.")];
804
+ lines.unshift(c.dim("사용법: /명령 뒤에 <필수> 또는 [선택] 표기대로 입력하세요."));
726
805
  lines.push(c.dim("추가: <작업폴더>/.cdsa/skills/*.md · ~/.cdsa_harness/skills/ · config.json 의 skill_dirs"));
727
806
  lines.push(c.dim("외부(.claude/commands 등)는 import_foreign_skills 로 끌 수 있음"));
728
807
  console.log(panel(lines, { title: "🎯 스킬 (프롬프트 템플릿, /이름 으로 실행)", color: "cyan" }));
@@ -733,7 +812,14 @@ export async function main(argv = []) {
733
812
  if (user.startsWith("/")) {
734
813
  const name = low.slice(1).split(/\s+/)[0];
735
814
  if (skills[name]) {
736
- const argStr = user.split(/\s+/).slice(1).join(" ");
815
+ const argStr = user.split(/\s+/).slice(1).join(" ").trim();
816
+ const hint = skills[name].hint || "";
817
+ // 입력이 필요한 스킬(<...>)인데 비어 있으면, 실행 대신 사용법을 알려준다.
818
+ if (!argStr && hint.includes("<")) {
819
+ console.log(c.yellow(`사용법: ${c.bold("/" + name)} ${hint}`) + c.dim(" ← 명령 뒤에 내용을 입력하세요"));
820
+ if (skills[name].description) console.log(c.dim(" " + skills[name].description));
821
+ continue;
822
+ }
737
823
  console.log(c.dim(`(스킬 '/${name}' 실행)`));
738
824
  rule();
739
825
  try {
package/src/config.js CHANGED
@@ -43,6 +43,7 @@ const DEFAULTS = {
43
43
  import_foreign_skills: true, // .claude/commands 등 외부 포맷 스킬도 읽기(프로젝트+전역)
44
44
  skill_dirs: [], // 스킬을 추가로 읽어올 폴더(절대/상대 경로)
45
45
  no_color: false, // 색상 끄기(흑백)
46
+ update_check: true, // 시작 시 새 버전 확인(하루 1회, 실패 시 조용히 무시)
46
47
  plugins: [], // 추가로 불러올 npm 플러그인 패키지 이름(이름 규칙과 무관하게 강제 로드)
47
48
  mcpServers: {}, // MCP 서버 설정 (Claude Code/Cursor 와 동일한 형식)
48
49
  };
package/src/skills.js CHANGED
@@ -44,7 +44,7 @@ function parseFrontmatter(text) {
44
44
  const meta = {};
45
45
  for (const line of m[1].split("\n")) {
46
46
  const i = line.indexOf(":");
47
- if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim();
47
+ if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim().replace(/^["']|["']$/g, "");
48
48
  }
49
49
  return { meta, body: text.slice(m[0].length).trim() };
50
50
  }
@@ -54,7 +54,13 @@ function addSkill(skills, name, file) {
54
54
  try {
55
55
  const raw = fs.readFileSync(file, "utf8");
56
56
  const { meta, body } = parseFrontmatter(raw);
57
- skills[name] = { name, description: meta.description || "", body, source: file };
57
+ skills[name] = {
58
+ name,
59
+ description: meta.description || "",
60
+ hint: meta["argument-hint"] || meta.args || meta.usage || "",
61
+ body,
62
+ source: file,
63
+ };
58
64
  } catch {
59
65
  /* ignore unreadable skill */
60
66
  }
@@ -89,7 +95,7 @@ export function loadSkills(workspace, { importForeign = true, extraDirs = [] } =
89
95
  }
90
96
  // 패키지 내장 기본 스킬(임베드) — 가장 낮은 우선순위(사용자 파일이 덮어씀)
91
97
  for (const s of BUILTIN_SKILLS) {
92
- if (!skills[s.name]) skills[s.name] = { name: s.name, description: s.description || "", body: s.body, source: "(내장)" };
98
+ if (!skills[s.name]) skills[s.name] = { name: s.name, description: s.description || "", hint: s.hint || "", body: s.body, source: "(내장)" };
93
99
  }
94
100
  return skills;
95
101
  }