@ps-neko/nekowork 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/AGENTS.md +112 -0
  2. package/CLAUDE.md +81 -0
  3. package/LICENSE +21 -0
  4. package/README.md +283 -0
  5. package/REVIEW.md +96 -0
  6. package/RULES.md +51 -0
  7. package/SOUL.md +21 -0
  8. package/WORKING-CONTEXT.md +52 -0
  9. package/agent.yaml +219 -0
  10. package/agents/architect.md +57 -0
  11. package/agents/code-reviewer.md +60 -0
  12. package/agents/codex-challenger.md +53 -0
  13. package/agents/codex-reviewer.md +56 -0
  14. package/agents/debugger.md +33 -0
  15. package/agents/doc-writer.md +51 -0
  16. package/agents/executor.md +41 -0
  17. package/agents/planner.md +49 -0
  18. package/agents/research.md +50 -0
  19. package/agents/security-reviewer.md +47 -0
  20. package/agents/test-engineer.md +41 -0
  21. package/bridge/mcp-server.js +301 -0
  22. package/commands/claude-led-codex-review.md +29 -0
  23. package/docs/ADVANCED.md +321 -0
  24. package/docs/AI-DEVELOPMENT-LIFECYCLE.md +105 -0
  25. package/docs/ARCHITECTURE.md +205 -0
  26. package/docs/AUDIT.md +114 -0
  27. package/docs/AUTH-MIGRATION.md +282 -0
  28. package/docs/CHANGELOG.md +97 -0
  29. package/docs/CLI-STAGES.md +89 -0
  30. package/docs/CODEMAPS/README.md +15 -0
  31. package/docs/CODEMAPS/agents.md +22 -0
  32. package/docs/CODEMAPS/bridge.md +18 -0
  33. package/docs/CODEMAPS/hooks.md +28 -0
  34. package/docs/CODEMAPS/manifests.md +14 -0
  35. package/docs/CODEMAPS/rules.md +22 -0
  36. package/docs/CODEMAPS/schemas.md +21 -0
  37. package/docs/CODEMAPS/scripts.md +158 -0
  38. package/docs/CODEMAPS/skills.md +29 -0
  39. package/docs/CODEMAPS/tests.md +98 -0
  40. package/docs/CORE-INVARIANTS.md +38 -0
  41. package/docs/DEMO.md +110 -0
  42. package/docs/EXAMPLE-PROJECT.md +92 -0
  43. package/docs/PORTING.md +154 -0
  44. package/docs/PRODUCT-PRINCIPLES.md +303 -0
  45. package/docs/PUBLISH-ALPHA.md +106 -0
  46. package/docs/QUICKSTART.md +344 -0
  47. package/docs/RELEASE-READINESS.md +140 -0
  48. package/docs/RISK-CLASSIFIER.md +50 -0
  49. package/docs/RUNBOOK.md +146 -0
  50. package/docs/SECURITY.md +79 -0
  51. package/docs/SETUP.md +142 -0
  52. package/docs/WHY-NEKOWORK.md +64 -0
  53. package/docs/case-studies/README.md +16 -0
  54. package/docs/case-studies/SINDRESORHUS-IS-PLAIN-OBJ.md +141 -0
  55. package/docs/dev-log/2026-04-29-p1-recovery.md +142 -0
  56. package/docs/dev-log/2026-04-29-week1-4.md +81 -0
  57. package/docs/examples/GITHUB-ACTIONS-HARDENING.md +86 -0
  58. package/docs/examples/QUALITY-LIFECYCLE-SMOKE.md +32 -0
  59. package/docs/examples/TRADING-DASHBOARD-MOCK.md +65 -0
  60. package/docs/workflows-stash/README.md +32 -0
  61. package/docs/workflows-stash/harness-review.yml +166 -0
  62. package/docs/workflows-stash/harness-validate.yml +48 -0
  63. package/examples/github-actions-hardening/.github/workflows/hardened-validate.yml +38 -0
  64. package/examples/github-actions-hardening/README.md +31 -0
  65. package/examples/github-actions-hardening/case-study/ASK.md +26 -0
  66. package/examples/github-actions-hardening/case-study/GATE_STATUS.md +28 -0
  67. package/examples/github-actions-hardening/case-study/PLAN.md +25 -0
  68. package/examples/github-actions-hardening/case-study/SHIP_READY.md +21 -0
  69. package/examples/github-actions-hardening/case-study/TASK.md +30 -0
  70. package/examples/github-actions-hardening/case-study/TEAM_HANDOFFS.md +37 -0
  71. package/examples/github-actions-hardening/case-study/VERIFY_SUMMARY.md +35 -0
  72. package/examples/github-actions-hardening/case-study/WORK_SUMMARY.md +24 -0
  73. package/examples/github-actions-hardening/package.json +12 -0
  74. package/examples/github-actions-hardening/scripts/check.mjs +43 -0
  75. package/examples/quality-lifecycle-smoke/README.md +30 -0
  76. package/examples/quality-lifecycle-smoke/case-study/ASK.md +24 -0
  77. package/examples/quality-lifecycle-smoke/case-study/GATE_STATUS.md +10 -0
  78. package/examples/quality-lifecycle-smoke/case-study/PLAN.md +19 -0
  79. package/examples/quality-lifecycle-smoke/case-study/SHIP_READY.md +11 -0
  80. package/examples/quality-lifecycle-smoke/case-study/TASK.md +19 -0
  81. package/examples/quality-lifecycle-smoke/case-study/TEAM_HANDOFFS.md +21 -0
  82. package/examples/quality-lifecycle-smoke/case-study/VERIFY_SUMMARY.md +44 -0
  83. package/examples/quality-lifecycle-smoke/case-study/WORK_SUMMARY.md +19 -0
  84. package/examples/quality-lifecycle-smoke/package.json +8 -0
  85. package/examples/quality-lifecycle-smoke/scripts/check.mjs +44 -0
  86. package/examples/trading-dashboard-mock/README.md +33 -0
  87. package/examples/trading-dashboard-mock/case-study/ASK.md +24 -0
  88. package/examples/trading-dashboard-mock/case-study/GATE_STATUS.md +28 -0
  89. package/examples/trading-dashboard-mock/case-study/PLAN.md +23 -0
  90. package/examples/trading-dashboard-mock/case-study/SHIP_READY.md +21 -0
  91. package/examples/trading-dashboard-mock/case-study/TASK.md +29 -0
  92. package/examples/trading-dashboard-mock/case-study/TEAM_HANDOFFS.md +49 -0
  93. package/examples/trading-dashboard-mock/case-study/VERIFY_SUMMARY.md +35 -0
  94. package/examples/trading-dashboard-mock/case-study/WORK_SUMMARY.md +27 -0
  95. package/examples/trading-dashboard-mock/fixtures/market.json +9 -0
  96. package/examples/trading-dashboard-mock/index.html +76 -0
  97. package/examples/trading-dashboard-mock/package.json +9 -0
  98. package/examples/trading-dashboard-mock/scripts/check.mjs +54 -0
  99. package/examples/trading-dashboard-mock/src/app.js +83 -0
  100. package/examples/trading-dashboard-mock/src/styles.css +227 -0
  101. package/hooks/hooks.json +44 -0
  102. package/hooks/scripts/config-protection.js +34 -0
  103. package/hooks/scripts/gateguard-fact-force.js +146 -0
  104. package/hooks/scripts/persistent-mode.mjs +27 -0
  105. package/hooks/scripts/pre-bash-dispatcher.js +63 -0
  106. package/hooks/scripts/quality-gate.js +106 -0
  107. package/manifests/install-components.json +195 -0
  108. package/manifests/install-modules.json +101 -0
  109. package/manifests/install-profiles.json +134 -0
  110. package/package.json +96 -0
  111. package/rules/common/coding-style.md +71 -0
  112. package/rules/common/security.md +69 -0
  113. package/rules/common/testing.md +58 -0
  114. package/rules/python/coding-style.md +80 -0
  115. package/rules/python/testing.md +86 -0
  116. package/rules/typescript/coding-style.md +97 -0
  117. package/rules/typescript/security.md +67 -0
  118. package/rules/typescript/testing.md +78 -0
  119. package/schemas/agent-yaml.schema.json +168 -0
  120. package/schemas/agent.schema.json +32 -0
  121. package/schemas/handoff.schema.json +105 -0
  122. package/schemas/hooks.schema.json +35 -0
  123. package/schemas/install-components.schema.json +46 -0
  124. package/schemas/install-modules.schema.json +39 -0
  125. package/schemas/install-profiles.schema.json +32 -0
  126. package/schemas/install-state.schema.json +42 -0
  127. package/schemas/routing.schema.json +42 -0
  128. package/schemas/skill.schema.json +19 -0
  129. package/scripts/agents/dispatch.js +144 -0
  130. package/scripts/agents/runners/claude.js +214 -0
  131. package/scripts/agents/runners/codex.js +233 -0
  132. package/scripts/agents/runners/gemini.js +92 -0
  133. package/scripts/agents/runners/mock.js +107 -0
  134. package/scripts/auth/github-import-gh.js +52 -0
  135. package/scripts/auth/github-login.js +79 -0
  136. package/scripts/auth/github-logout.js +21 -0
  137. package/scripts/auth/github-status.js +46 -0
  138. package/scripts/build-claude.js +101 -0
  139. package/scripts/build-codemaps.js +286 -0
  140. package/scripts/build-codex.js +93 -0
  141. package/scripts/build-cursor.js +132 -0
  142. package/scripts/build-gemini.js +117 -0
  143. package/scripts/build-opencode.js +117 -0
  144. package/scripts/ci/catalog.js +120 -0
  145. package/scripts/ci/check-markers.js +48 -0
  146. package/scripts/ci/security-hardening.js +270 -0
  147. package/scripts/ci/validate-agents.js +88 -0
  148. package/scripts/ci/validate-hooks.js +99 -0
  149. package/scripts/ci/validate-manifests.js +128 -0
  150. package/scripts/ci/validate-skills.js +93 -0
  151. package/scripts/cli.js +1134 -0
  152. package/scripts/core/auth-guard.js +22 -0
  153. package/scripts/core/build-roots.js +11 -0
  154. package/scripts/core/cli-resolver.js +64 -0
  155. package/scripts/core/execution-workspace.js +84 -0
  156. package/scripts/core/git-mutation-guard.js +79 -0
  157. package/scripts/core/install-state.js +125 -0
  158. package/scripts/core/json-extractor.js +32 -0
  159. package/scripts/core/subprocess.js +74 -0
  160. package/scripts/daemon/wait.js +278 -0
  161. package/scripts/demo-external-project.js +222 -0
  162. package/scripts/demo-quick-run.js +193 -0
  163. package/scripts/demo-review.js +204 -0
  164. package/scripts/doctor.js +296 -0
  165. package/scripts/install-apply.js +185 -0
  166. package/scripts/install-plan.js +411 -0
  167. package/scripts/lib/acceptance-criteria.js +105 -0
  168. package/scripts/lib/costs.js +82 -0
  169. package/scripts/lib/instincts.js +194 -0
  170. package/scripts/lib/keychain.js +85 -0
  171. package/scripts/lib/profile-policy.js +134 -0
  172. package/scripts/lib/profile-safety.js +81 -0
  173. package/scripts/lib/risk-classifier.js +145 -0
  174. package/scripts/lib/router.js +138 -0
  175. package/scripts/lib/severity.js +99 -0
  176. package/scripts/lib/token-vault.js +136 -0
  177. package/scripts/orchestrators/apply.js +225 -0
  178. package/scripts/orchestrators/ask.js +143 -0
  179. package/scripts/orchestrators/gate.js +179 -0
  180. package/scripts/orchestrators/ralph.js +179 -0
  181. package/scripts/orchestrators/review.js +452 -0
  182. package/scripts/orchestrators/run.js +151 -0
  183. package/scripts/orchestrators/ship.js +339 -0
  184. package/scripts/orchestrators/team-lite.js +270 -0
  185. package/scripts/orchestrators/team.js +244 -0
  186. package/scripts/orchestrators/verify.js +306 -0
  187. package/scripts/orchestrators/work.js +207 -0
  188. package/scripts/portability/simulate-port.js +220 -0
  189. package/scripts/repair.js +184 -0
  190. package/scripts/sync-claude-md.js +220 -0
  191. package/scripts/verify/claude-live.js +30 -0
  192. package/scripts/verify/codex-live.js +60 -0
  193. package/scripts/verify/gemini-live.js +48 -0
  194. package/scripts/verify/runtime.js +105 -0
  195. package/skills/claude-led-codex-review/SKILL.md +133 -0
  196. package/skills/plan-eng-review/SKILL.md +51 -0
  197. package/skills/porting/SKILL.md +69 -0
  198. package/skills/ralph/SKILL.md +48 -0
  199. package/skills/release-readiness/SKILL.md +62 -0
  200. package/skills/review/SKILL.md +42 -0
  201. package/skills/security-hardening/SKILL.md +59 -0
  202. package/skills/ship/SKILL.md +44 -0
  203. package/skills/tdd-workflow/SKILL.md +42 -0
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: research
3
+ description: "외부 정보 수집. Context7 / Exa / GitHub 검색. read-only."
4
+ provider: gemini
5
+ model: gemini-2.5-pro
6
+ level: 1
7
+ disallowedTools: [Write, Edit, Bash]
8
+ trigger: ["research", "찾아봐", "검색", "조사"]
9
+ hand_off_to: [planner, architect]
10
+ sandbox: read-only
11
+ ---
12
+
13
+ # Research
14
+
15
+ 장컨텍스트 / 다중 출처 통합이 필요한 리서치를 Gemini CLI 워커로 위임. Anthropic 토큰 0.
16
+
17
+ ## 사용 우선순위 (사용자 룰)
18
+
19
+ 1. **GitHub code search 먼저** — 기존 구현 / 템플릿
20
+ 2. **라이브러리 공식 문서** — Context7 MCP
21
+ 3. **Exa** — 위 둘이 부족할 때만
22
+
23
+ ## 출력
24
+
25
+ ```markdown
26
+ ## 발견
27
+ - [출처 1] (URL · 신뢰도)
28
+ - [출처 2]
29
+
30
+ ## 채택 후보
31
+ - 라이브러리 X (왜): ...
32
+ - 패턴 Y (왜): ...
33
+
34
+ ## 거절안
35
+ - ...
36
+
37
+ ## 미해결 / 모호
38
+ - ...
39
+ ```
40
+
41
+ ## 금지
42
+
43
+ - 출처 없는 주장 금지. 모든 사실 주장은 URL 또는 file:line 인용.
44
+ - "GPT 가 그렇게 말했다" 금지.
45
+
46
+ ## CLI 위임
47
+
48
+ ```bash
49
+ gemini --no-browser --quiet < prompt.md > research-output.md
50
+ ```
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: security-reviewer
3
+ description: "보안 게이트. auth / crypto / payment / 외부 API 변경 시 강제."
4
+ provider: claude
5
+ model: opus
6
+ level: 3
7
+ disallowedTools: [Write, Edit, Bash]
8
+ trigger: ["security review", "보안 검토"]
9
+ hand_off_to: []
10
+ fact_forcing: true
11
+ sandbox: read-only
12
+ ---
13
+
14
+ # Security Reviewer
15
+
16
+ 보안 12-item Minimum Bar 기반 게이트. AGENTS.md 의 checklist 참조.
17
+
18
+ ## 자동 활성
19
+
20
+ - 변경에 다음 디렉터리/파일 포함:
21
+ - `auth/`, `crypto/`, `payment/`, `session/`, `permission/`, `oauth/`, `jwt`, `password`, `secret`
22
+ - 새 외부 API 호출
23
+ - DB 스키마 변경
24
+ - `.env*` 또는 시크릿 관련 파일 변경
25
+
26
+ ## 검토 항목 (12-item Minimum Bar)
27
+
28
+ 1. agent ID / 개인 계정 분리
29
+ 2. short-lived scoped credentials (OIDC)
30
+ 3. untrusted work — sandbox / devcontainer / VM
31
+ 4. outbound network deny by default
32
+ 5. secret-bearing path 읽기 차단
33
+ 6. input sanitization (파일·HTML·스크린샷·링크)
34
+ 7. unsandboxed shell / egress / deploy / off-repo write — approval
35
+ 8. tool calls / approvals / network attempts 로깅
36
+ 9. process-group kill + heartbeat dead-man switch
37
+ 10. persistent memory narrow & disposable
38
+ 11. 카탈로그(skills, hooks, MCP) 도 supply chain 스캔
39
+ 12. MCP 서버 SemVer 핀
40
+
41
+ ## 출력
42
+
43
+ 표준 핸드오프 JSON. severity = critical 1건이라도 → 즉시 human gate.
44
+
45
+ ## fact_forcing 강제
46
+
47
+ 이 에이전트는 항상 importer·public API·schema 조사를 강제한다. "Are you sure?" 자체 질문은 무력하다.
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: test-engineer
3
+ description: "테스트 작성 · 커버리지 · TDD 강제. 80% 게이트."
4
+ provider: claude
5
+ model: sonnet
6
+ level: 2
7
+ disallowedTools: []
8
+ trigger: ["test", "테스트", "TDD", "커버리지"]
9
+ hand_off_to: [executor]
10
+ fact_forcing: false
11
+ sandbox: workspace-write
12
+ ---
13
+
14
+ # Test Engineer
15
+
16
+ 테스트 우선 작성. 기능 / 엣지 / 회귀 / 통합 / e2e 다층 커버.
17
+
18
+ ## 책임
19
+
20
+ - TDD RED 단계의 실패 테스트 작성.
21
+ - 회귀 테스트 (debugger 가 재현한 시나리오).
22
+ - 모킹은 시스템 경계만 (DB·외부 API·시계).
23
+ - 커버리지 80% 미달 시 게이트 차단.
24
+
25
+ ## 테스트 분류
26
+
27
+ | 종류 | 위치 | 도구 |
28
+ |---|---|---|
29
+ | Unit | `tests/unit/` | node:test |
30
+ | Integration | `tests/integration/` | node:test + 실 DB(컨테이너) |
31
+ | E2E | `tests/e2e/` | playwright |
32
+
33
+ ## 금지
34
+
35
+ - 모킹된 통합 테스트 (사용자 룰: "DB 모킹 금지"는 없음. 단 PoC 환경에서 실 DB 컨테이너 권장).
36
+ - assert 없는 테스트.
37
+ - 항상 통과하는(true == true) 테스트.
38
+
39
+ ## 핸드오프
40
+
41
+ 테스트 파일 경로 목록 + 커버리지 % + RED → GREEN 전환 카운트.
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env node
2
+ // HARNESS MCP 단일 게이트웨이.
3
+ // 4개 도구: state_read, state_write, notepad_append, handoff_write.
4
+ // .mcp.json 에서 단일 서버로 등록되어 외부 MCP (github, context7, exa, memory) 는 별도 namespace 로 proxy 가능.
5
+ // 현재 4 도구만. namespace 프록시는 향후 확장.
6
+
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
10
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
11
+ import {
12
+ CallToolRequestSchema,
13
+ ListToolsRequestSchema,
14
+ } from '@modelcontextprotocol/sdk/types.js';
15
+
16
+ import { classifyCategory, classifySeverity, severityCounts, deriveVerdict, riskLevel } from '../scripts/lib/severity.js';
17
+ import { decide as routeDecide } from '../scripts/lib/router.js';
18
+ import { record as costRecord, list as costList, summarize as costSummarize } from '../scripts/lib/costs.js';
19
+
20
+ const ROOT = process.env.HARNESS_ROOT || process.cwd();
21
+ const SESSION_ID = process.env.HARNESS_SESSION_ID || 'default';
22
+ const SESSION_DIR = path.join(ROOT, '.harness', 'state', 'sessions', SESSION_ID);
23
+
24
+ function ensureSession() {
25
+ fs.mkdirSync(path.join(SESSION_DIR, 'handoffs'), { recursive: true });
26
+ fs.mkdirSync(path.join(SESSION_DIR, 'facts'), { recursive: true });
27
+ }
28
+
29
+ function audit(event, details) {
30
+ const auditDir = path.join(ROOT, '.harness', 'audit');
31
+ fs.mkdirSync(auditDir, { recursive: true });
32
+ const today = new Date().toISOString().slice(0, 10);
33
+ const f = path.join(auditDir, `${today}.jsonl`);
34
+ fs.appendFileSync(f, JSON.stringify({
35
+ ts: new Date().toISOString(),
36
+ session: SESSION_ID,
37
+ event,
38
+ ...details,
39
+ }) + '\n');
40
+ }
41
+
42
+ const TOOLS = [
43
+ {
44
+ name: 'state_read',
45
+ description: '세션 상태 파일 읽기. notepad / prd / progress / round / 임의 키 지원.',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ key: { type: 'string', description: 'notepad | prd | progress | round | <상대경로>' },
50
+ },
51
+ required: ['key'],
52
+ },
53
+ },
54
+ {
55
+ name: 'state_write',
56
+ description: '세션 상태 파일 쓰기 (덮어씀). prd / round / 임의 JSON 키.',
57
+ inputSchema: {
58
+ type: 'object',
59
+ properties: {
60
+ key: { type: 'string' },
61
+ value: { description: '문자열 또는 JSON 직렬화 가능 객체' },
62
+ },
63
+ required: ['key', 'value'],
64
+ },
65
+ },
66
+ {
67
+ name: 'notepad_append',
68
+ description: '세션 notepad.md 에 라인 추가 (append).',
69
+ inputSchema: {
70
+ type: 'object',
71
+ properties: {
72
+ line: { type: 'string' },
73
+ },
74
+ required: ['line'],
75
+ },
76
+ },
77
+ {
78
+ name: 'handoff_write',
79
+ description: '단계별 핸드오프 작성. handoffs/<NN>-<stage>.md. 5필드 강제.',
80
+ inputSchema: {
81
+ type: 'object',
82
+ properties: {
83
+ stage: { enum: ['ideate', 'plan', 'implement', 'self-review', 'codex-review', 'codex-challenge', 'ship'] },
84
+ agent: { type: 'string' },
85
+ round: { type: 'integer', minimum: 1, default: 1 },
86
+ decided: { type: 'string' },
87
+ rejected: { type: 'string' },
88
+ risks: { type: 'string' },
89
+ files: { type: 'array', items: { type: 'string' } },
90
+ remaining: { type: 'string' },
91
+ issues: { type: 'array' },
92
+ verdict: { enum: ['block', 'approve_with_fixes', 'approve'] },
93
+ confidence: { type: 'number', minimum: 0, maximum: 1 },
94
+ },
95
+ required: ['stage', 'agent', 'decided', 'files'],
96
+ },
97
+ },
98
+ {
99
+ name: 'severity_classify',
100
+ description: '이슈 한 건 또는 이슈 배열의 severity / category 자동 분류. 명시값이 있으면 그대로, 없으면 휴리스틱.',
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ issues: { type: 'array', description: 'issue 객체 배열' },
105
+ files: { type: 'array', items: { type: 'string' } },
106
+ task: { type: 'string' },
107
+ },
108
+ required: ['issues'],
109
+ },
110
+ },
111
+ {
112
+ name: 'route_decide',
113
+ description: '단계 + task + files + eco_mode 입력 → 라우팅 결정 (agent/model/provider) 출력. routing.jsonl 에 트레이스 가능.',
114
+ inputSchema: {
115
+ type: 'object',
116
+ properties: {
117
+ stage: { type: 'string' },
118
+ task: { type: 'string' },
119
+ files: { type: 'array', items: { type: 'string' } },
120
+ eco_mode: { type: 'boolean' },
121
+ risk_level: { enum: ['low', 'medium', 'high', 'critical'] },
122
+ trace: { type: 'boolean', description: '결정을 routing.jsonl 에 기록' },
123
+ },
124
+ required: ['stage'],
125
+ },
126
+ },
127
+ {
128
+ name: 'cost_record',
129
+ description: '도구 호출 1건의 비용을 ~/.harness/costs.jsonl 에 기록. agent / stage / model / tokens / duration / 추정 USD.',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {
133
+ agent: { type: 'string' },
134
+ stage: { type: 'string' },
135
+ provider: { type: 'string' },
136
+ model: { type: 'string' },
137
+ input_tokens: { type: 'integer' },
138
+ output_tokens: { type: 'integer' },
139
+ duration_ms: { type: 'integer' },
140
+ },
141
+ required: ['agent', 'stage', 'model'],
142
+ },
143
+ },
144
+ ];
145
+
146
+ const server = new Server(
147
+ { name: 'harness', version: '0.0.2' },
148
+ { capabilities: { tools: {} } },
149
+ );
150
+
151
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
152
+
153
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
154
+ ensureSession();
155
+ const { name, arguments: args } = req.params;
156
+
157
+ try {
158
+ if (name === 'state_read') {
159
+ const file = mapKeyToPath(args.key);
160
+ if (!fs.existsSync(file)) {
161
+ audit('state_read.miss', { key: args.key, file });
162
+ return { content: [{ type: 'text', text: '' }], isError: false };
163
+ }
164
+ const data = fs.readFileSync(file, 'utf8');
165
+ audit('state_read', { key: args.key, file, bytes: data.length });
166
+ return { content: [{ type: 'text', text: data }] };
167
+ }
168
+
169
+ if (name === 'state_write') {
170
+ const file = mapKeyToPath(args.key);
171
+ fs.mkdirSync(path.dirname(file), { recursive: true });
172
+ const text = typeof args.value === 'string' ? args.value : JSON.stringify(args.value, null, 2);
173
+ fs.writeFileSync(file, text);
174
+ audit('state_write', { key: args.key, file, bytes: text.length });
175
+ return { content: [{ type: 'text', text: `OK ${file} (${text.length}B)` }] };
176
+ }
177
+
178
+ if (name === 'notepad_append') {
179
+ const file = path.join(SESSION_DIR, 'notepad.md');
180
+ fs.mkdirSync(path.dirname(file), { recursive: true });
181
+ const line = String(args.line).replace(/\r?\n$/, '') + '\n';
182
+ fs.appendFileSync(file, line);
183
+ audit('notepad_append', { file, bytes: line.length });
184
+ return { content: [{ type: 'text', text: 'OK' }] };
185
+ }
186
+
187
+ if (name === 'severity_classify') {
188
+ const enriched = (args.issues || []).map((i) => ({
189
+ ...i,
190
+ category: classifyCategory(i),
191
+ severity: classifySeverity(i),
192
+ }));
193
+ const counts = severityCounts(enriched);
194
+ const verdict = deriveVerdict(enriched);
195
+ const risk = riskLevel(args.files || [], args.task || '');
196
+ audit('severity_classify', { count: enriched.length, verdict, risk });
197
+ return { content: [{ type: 'text', text: JSON.stringify({ issues: enriched, counts, verdict, risk_level: risk }, null, 2) }] };
198
+ }
199
+
200
+ if (name === 'route_decide') {
201
+ const decision = routeDecide({
202
+ stage: args.stage,
203
+ task: args.task,
204
+ files: args.files,
205
+ ecoMode: args.eco_mode,
206
+ riskLevel: args.risk_level,
207
+ harnessRoot: ROOT,
208
+ });
209
+ if (args.trace) {
210
+ const { trace } = await import('../scripts/lib/router.js');
211
+ trace(SESSION_DIR, decision, { stage: args.stage, task: args.task });
212
+ }
213
+ audit('route_decide', { stage: args.stage, agent: decision.agent, model: decision.model });
214
+ return { content: [{ type: 'text', text: JSON.stringify(decision, null, 2) }] };
215
+ }
216
+
217
+ if (name === 'cost_record') {
218
+ const row = costRecord({
219
+ ts: new Date().toISOString(),
220
+ session: SESSION_ID,
221
+ stage: args.stage,
222
+ agent: args.agent,
223
+ provider: args.provider,
224
+ model: args.model,
225
+ input_tokens: args.input_tokens,
226
+ output_tokens: args.output_tokens,
227
+ duration_ms: args.duration_ms,
228
+ });
229
+ audit('cost_record', { model: row.model, usd: row.estimate_usd });
230
+ return { content: [{ type: 'text', text: JSON.stringify(row, null, 2) }] };
231
+ }
232
+
233
+ if (name === 'handoff_write') {
234
+ const stageOrder = ['ideate', 'plan', 'implement', 'self-review', 'codex-review', 'codex-challenge', 'ship'];
235
+ const idx = stageOrder.indexOf(args.stage);
236
+ if (idx < 0) throw new Error(`unknown stage: ${args.stage}`);
237
+ const nn = String(idx + 1).padStart(2, '0');
238
+ const file = path.join(SESSION_DIR, 'handoffs', `${nn}-${args.stage}.md`);
239
+
240
+ const md = renderHandoff(args);
241
+ fs.writeFileSync(file, md);
242
+
243
+ // JSON 부속도 같이 저장
244
+ const jsonFile = file.replace(/\.md$/, '.json');
245
+ fs.writeFileSync(jsonFile, JSON.stringify({
246
+ stage: args.stage,
247
+ agent: args.agent,
248
+ round: args.round || 1,
249
+ timestamp: new Date().toISOString(),
250
+ ...args,
251
+ }, null, 2));
252
+
253
+ audit('handoff_write', { stage: args.stage, agent: args.agent, file, verdict: args.verdict });
254
+ return { content: [{ type: 'text', text: `OK ${file} + ${path.basename(jsonFile)}` }] };
255
+ }
256
+
257
+ throw new Error(`unknown tool: ${name}`);
258
+ } catch (e) {
259
+ audit('error', { tool: name, message: e.message });
260
+ return {
261
+ content: [{ type: 'text', text: `ERROR: ${e.message}` }],
262
+ isError: true,
263
+ };
264
+ }
265
+ });
266
+
267
+ function mapKeyToPath(key) {
268
+ switch (key) {
269
+ case 'notepad': return path.join(SESSION_DIR, 'notepad.md');
270
+ case 'prd': return path.join(SESSION_DIR, 'prd.json');
271
+ case 'progress': return path.join(SESSION_DIR, 'progress.txt');
272
+ case 'round': return path.join(SESSION_DIR, 'round.json');
273
+ default:
274
+ if (key.includes('..')) throw new Error('상위 디렉터리 접근 금지');
275
+ return path.join(SESSION_DIR, key);
276
+ }
277
+ }
278
+
279
+ function renderHandoff(a) {
280
+ const lines = [];
281
+ lines.push(`# Handoff: ${String(a.stage)} (round ${a.round || 1}, agent: ${a.agent})`);
282
+ lines.push('');
283
+ lines.push(`**Decided**: ${a.decided}`);
284
+ if (a.rejected) lines.push(`**Rejected**: ${a.rejected}`);
285
+ if (a.risks) lines.push(`**Risks**: ${a.risks}`);
286
+ lines.push(`**Files**: ${(a.files || []).join(', ')}`);
287
+ if (a.remaining) lines.push(`**Remaining**: ${a.remaining}`);
288
+ if (a.verdict) lines.push(`**Verdict**: ${a.verdict}${a.confidence != null ? ` (confidence ${a.confidence})` : ''}`);
289
+ if (a.issues && a.issues.length) {
290
+ lines.push('');
291
+ lines.push('## Issues');
292
+ for (const i of a.issues) {
293
+ lines.push(`- [${i.severity || '?'}/${i.category || '?'}] ${i.file || ''}${i.line ? ':' + i.line : ''} — ${i.summary || ''}`);
294
+ }
295
+ }
296
+ return lines.join('\n') + '\n';
297
+ }
298
+
299
+ const transport = new StdioServerTransport();
300
+ await server.connect(transport);
301
+ process.stderr.write(`[harness mcp] gateway up. session=${SESSION_ID} root=${ROOT}\n`);
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: "Claude 주도 + Codex 위임 7단계 풀사이클. claude-led-codex-review 스킬 호출."
3
+ ---
4
+
5
+ # /claude-led-codex-review
6
+
7
+ 이 슬래시 명령은 `claude-led-codex-review` 스킬의 legacy compat 진입점이다. 신규 워크플로우는 스킬에서 정의되지만 슬래시 호출 호환성을 위해 보존.
8
+
9
+ ## 동작
10
+
11
+ `Skill` 도구로 `claude-led-codex-review` 를 즉시 호출. 인자가 있으면 작업 요약으로 전달, 없으면 사용자에게 한 줄 요약을 요청.
12
+
13
+ ## 인자
14
+
15
+ - `$ARGUMENTS` — 작업 요약 한 줄
16
+ - `--fast` — 단계 1·6 스킵
17
+ - `--secure` — 단계 6 강제
18
+ - `--no-ship` — 단계 7 생략
19
+
20
+ ## 예시
21
+
22
+ ```
23
+ /claude-led-codex-review JWT 검증 미들웨어 추가 --secure
24
+ /claude-led-codex-review 결제 환불 로직 버그 수정
25
+ /claude-led-codex-review --fast 사소한 리팩토링
26
+ /claude-led-codex-review 새 API 엔드포인트 --no-ship
27
+ ```
28
+
29
+ 전체 명세는 `skills/claude-led-codex-review/SKILL.md` 참조.