@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.
- package/AGENTS.md +112 -0
- package/CLAUDE.md +81 -0
- package/LICENSE +21 -0
- package/README.md +283 -0
- package/REVIEW.md +96 -0
- package/RULES.md +51 -0
- package/SOUL.md +21 -0
- package/WORKING-CONTEXT.md +52 -0
- package/agent.yaml +219 -0
- package/agents/architect.md +57 -0
- package/agents/code-reviewer.md +60 -0
- package/agents/codex-challenger.md +53 -0
- package/agents/codex-reviewer.md +56 -0
- package/agents/debugger.md +33 -0
- package/agents/doc-writer.md +51 -0
- package/agents/executor.md +41 -0
- package/agents/planner.md +49 -0
- package/agents/research.md +50 -0
- package/agents/security-reviewer.md +47 -0
- package/agents/test-engineer.md +41 -0
- package/bridge/mcp-server.js +301 -0
- package/commands/claude-led-codex-review.md +29 -0
- package/docs/ADVANCED.md +321 -0
- package/docs/AI-DEVELOPMENT-LIFECYCLE.md +105 -0
- package/docs/ARCHITECTURE.md +205 -0
- package/docs/AUDIT.md +114 -0
- package/docs/AUTH-MIGRATION.md +282 -0
- package/docs/CHANGELOG.md +97 -0
- package/docs/CLI-STAGES.md +89 -0
- package/docs/CODEMAPS/README.md +15 -0
- package/docs/CODEMAPS/agents.md +22 -0
- package/docs/CODEMAPS/bridge.md +18 -0
- package/docs/CODEMAPS/hooks.md +28 -0
- package/docs/CODEMAPS/manifests.md +14 -0
- package/docs/CODEMAPS/rules.md +22 -0
- package/docs/CODEMAPS/schemas.md +21 -0
- package/docs/CODEMAPS/scripts.md +158 -0
- package/docs/CODEMAPS/skills.md +29 -0
- package/docs/CODEMAPS/tests.md +98 -0
- package/docs/CORE-INVARIANTS.md +38 -0
- package/docs/DEMO.md +110 -0
- package/docs/EXAMPLE-PROJECT.md +92 -0
- package/docs/PORTING.md +154 -0
- package/docs/PRODUCT-PRINCIPLES.md +303 -0
- package/docs/PUBLISH-ALPHA.md +106 -0
- package/docs/QUICKSTART.md +344 -0
- package/docs/RELEASE-READINESS.md +140 -0
- package/docs/RISK-CLASSIFIER.md +50 -0
- package/docs/RUNBOOK.md +146 -0
- package/docs/SECURITY.md +79 -0
- package/docs/SETUP.md +142 -0
- package/docs/WHY-NEKOWORK.md +64 -0
- package/docs/case-studies/README.md +16 -0
- package/docs/case-studies/SINDRESORHUS-IS-PLAIN-OBJ.md +141 -0
- package/docs/dev-log/2026-04-29-p1-recovery.md +142 -0
- package/docs/dev-log/2026-04-29-week1-4.md +81 -0
- package/docs/examples/GITHUB-ACTIONS-HARDENING.md +86 -0
- package/docs/examples/QUALITY-LIFECYCLE-SMOKE.md +32 -0
- package/docs/examples/TRADING-DASHBOARD-MOCK.md +65 -0
- package/docs/workflows-stash/README.md +32 -0
- package/docs/workflows-stash/harness-review.yml +166 -0
- package/docs/workflows-stash/harness-validate.yml +48 -0
- package/examples/github-actions-hardening/.github/workflows/hardened-validate.yml +38 -0
- package/examples/github-actions-hardening/README.md +31 -0
- package/examples/github-actions-hardening/case-study/ASK.md +26 -0
- package/examples/github-actions-hardening/case-study/GATE_STATUS.md +28 -0
- package/examples/github-actions-hardening/case-study/PLAN.md +25 -0
- package/examples/github-actions-hardening/case-study/SHIP_READY.md +21 -0
- package/examples/github-actions-hardening/case-study/TASK.md +30 -0
- package/examples/github-actions-hardening/case-study/TEAM_HANDOFFS.md +37 -0
- package/examples/github-actions-hardening/case-study/VERIFY_SUMMARY.md +35 -0
- package/examples/github-actions-hardening/case-study/WORK_SUMMARY.md +24 -0
- package/examples/github-actions-hardening/package.json +12 -0
- package/examples/github-actions-hardening/scripts/check.mjs +43 -0
- package/examples/quality-lifecycle-smoke/README.md +30 -0
- package/examples/quality-lifecycle-smoke/case-study/ASK.md +24 -0
- package/examples/quality-lifecycle-smoke/case-study/GATE_STATUS.md +10 -0
- package/examples/quality-lifecycle-smoke/case-study/PLAN.md +19 -0
- package/examples/quality-lifecycle-smoke/case-study/SHIP_READY.md +11 -0
- package/examples/quality-lifecycle-smoke/case-study/TASK.md +19 -0
- package/examples/quality-lifecycle-smoke/case-study/TEAM_HANDOFFS.md +21 -0
- package/examples/quality-lifecycle-smoke/case-study/VERIFY_SUMMARY.md +44 -0
- package/examples/quality-lifecycle-smoke/case-study/WORK_SUMMARY.md +19 -0
- package/examples/quality-lifecycle-smoke/package.json +8 -0
- package/examples/quality-lifecycle-smoke/scripts/check.mjs +44 -0
- package/examples/trading-dashboard-mock/README.md +33 -0
- package/examples/trading-dashboard-mock/case-study/ASK.md +24 -0
- package/examples/trading-dashboard-mock/case-study/GATE_STATUS.md +28 -0
- package/examples/trading-dashboard-mock/case-study/PLAN.md +23 -0
- package/examples/trading-dashboard-mock/case-study/SHIP_READY.md +21 -0
- package/examples/trading-dashboard-mock/case-study/TASK.md +29 -0
- package/examples/trading-dashboard-mock/case-study/TEAM_HANDOFFS.md +49 -0
- package/examples/trading-dashboard-mock/case-study/VERIFY_SUMMARY.md +35 -0
- package/examples/trading-dashboard-mock/case-study/WORK_SUMMARY.md +27 -0
- package/examples/trading-dashboard-mock/fixtures/market.json +9 -0
- package/examples/trading-dashboard-mock/index.html +76 -0
- package/examples/trading-dashboard-mock/package.json +9 -0
- package/examples/trading-dashboard-mock/scripts/check.mjs +54 -0
- package/examples/trading-dashboard-mock/src/app.js +83 -0
- package/examples/trading-dashboard-mock/src/styles.css +227 -0
- package/hooks/hooks.json +44 -0
- package/hooks/scripts/config-protection.js +34 -0
- package/hooks/scripts/gateguard-fact-force.js +146 -0
- package/hooks/scripts/persistent-mode.mjs +27 -0
- package/hooks/scripts/pre-bash-dispatcher.js +63 -0
- package/hooks/scripts/quality-gate.js +106 -0
- package/manifests/install-components.json +195 -0
- package/manifests/install-modules.json +101 -0
- package/manifests/install-profiles.json +134 -0
- package/package.json +96 -0
- package/rules/common/coding-style.md +71 -0
- package/rules/common/security.md +69 -0
- package/rules/common/testing.md +58 -0
- package/rules/python/coding-style.md +80 -0
- package/rules/python/testing.md +86 -0
- package/rules/typescript/coding-style.md +97 -0
- package/rules/typescript/security.md +67 -0
- package/rules/typescript/testing.md +78 -0
- package/schemas/agent-yaml.schema.json +168 -0
- package/schemas/agent.schema.json +32 -0
- package/schemas/handoff.schema.json +105 -0
- package/schemas/hooks.schema.json +35 -0
- package/schemas/install-components.schema.json +46 -0
- package/schemas/install-modules.schema.json +39 -0
- package/schemas/install-profiles.schema.json +32 -0
- package/schemas/install-state.schema.json +42 -0
- package/schemas/routing.schema.json +42 -0
- package/schemas/skill.schema.json +19 -0
- package/scripts/agents/dispatch.js +144 -0
- package/scripts/agents/runners/claude.js +214 -0
- package/scripts/agents/runners/codex.js +233 -0
- package/scripts/agents/runners/gemini.js +92 -0
- package/scripts/agents/runners/mock.js +107 -0
- package/scripts/auth/github-import-gh.js +52 -0
- package/scripts/auth/github-login.js +79 -0
- package/scripts/auth/github-logout.js +21 -0
- package/scripts/auth/github-status.js +46 -0
- package/scripts/build-claude.js +101 -0
- package/scripts/build-codemaps.js +286 -0
- package/scripts/build-codex.js +93 -0
- package/scripts/build-cursor.js +132 -0
- package/scripts/build-gemini.js +117 -0
- package/scripts/build-opencode.js +117 -0
- package/scripts/ci/catalog.js +120 -0
- package/scripts/ci/check-markers.js +48 -0
- package/scripts/ci/security-hardening.js +270 -0
- package/scripts/ci/validate-agents.js +88 -0
- package/scripts/ci/validate-hooks.js +99 -0
- package/scripts/ci/validate-manifests.js +128 -0
- package/scripts/ci/validate-skills.js +93 -0
- package/scripts/cli.js +1134 -0
- package/scripts/core/auth-guard.js +22 -0
- package/scripts/core/build-roots.js +11 -0
- package/scripts/core/cli-resolver.js +64 -0
- package/scripts/core/execution-workspace.js +84 -0
- package/scripts/core/git-mutation-guard.js +79 -0
- package/scripts/core/install-state.js +125 -0
- package/scripts/core/json-extractor.js +32 -0
- package/scripts/core/subprocess.js +74 -0
- package/scripts/daemon/wait.js +278 -0
- package/scripts/demo-external-project.js +222 -0
- package/scripts/demo-quick-run.js +193 -0
- package/scripts/demo-review.js +204 -0
- package/scripts/doctor.js +296 -0
- package/scripts/install-apply.js +185 -0
- package/scripts/install-plan.js +411 -0
- package/scripts/lib/acceptance-criteria.js +105 -0
- package/scripts/lib/costs.js +82 -0
- package/scripts/lib/instincts.js +194 -0
- package/scripts/lib/keychain.js +85 -0
- package/scripts/lib/profile-policy.js +134 -0
- package/scripts/lib/profile-safety.js +81 -0
- package/scripts/lib/risk-classifier.js +145 -0
- package/scripts/lib/router.js +138 -0
- package/scripts/lib/severity.js +99 -0
- package/scripts/lib/token-vault.js +136 -0
- package/scripts/orchestrators/apply.js +225 -0
- package/scripts/orchestrators/ask.js +143 -0
- package/scripts/orchestrators/gate.js +179 -0
- package/scripts/orchestrators/ralph.js +179 -0
- package/scripts/orchestrators/review.js +452 -0
- package/scripts/orchestrators/run.js +151 -0
- package/scripts/orchestrators/ship.js +339 -0
- package/scripts/orchestrators/team-lite.js +270 -0
- package/scripts/orchestrators/team.js +244 -0
- package/scripts/orchestrators/verify.js +306 -0
- package/scripts/orchestrators/work.js +207 -0
- package/scripts/portability/simulate-port.js +220 -0
- package/scripts/repair.js +184 -0
- package/scripts/sync-claude-md.js +220 -0
- package/scripts/verify/claude-live.js +30 -0
- package/scripts/verify/codex-live.js +60 -0
- package/scripts/verify/gemini-live.js +48 -0
- package/scripts/verify/runtime.js +105 -0
- package/skills/claude-led-codex-review/SKILL.md +133 -0
- package/skills/plan-eng-review/SKILL.md +51 -0
- package/skills/porting/SKILL.md +69 -0
- package/skills/ralph/SKILL.md +48 -0
- package/skills/release-readiness/SKILL.md +62 -0
- package/skills/review/SKILL.md +42 -0
- package/skills/security-hardening/SKILL.md +59 -0
- package/skills/ship/SKILL.md +44 -0
- 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` 참조.
|