@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,107 @@
1
+ // Mock runner: LLM 호출 없이 결정론적 응답 생성. 오케스트레이터 단위 테스트와
2
+ // API 키 / CLI 미설치 환경에서의 dry-run 디폴트.
3
+ //
4
+ // 단계별로 의도된 시나리오를 흉내낸다:
5
+ // - planner: AC 3개의 PRD 시드
6
+ // - executor: 작업 완료 보고
7
+ // - code-reviewer: round 1 에서 high 1 발견 (fix loop 유도), round 2 에서 approve
8
+ // - codex-reviewer: medium 1 추가 발견 후 approve_with_fixes
9
+ // - codex-challenger: info 1 발견 후 approve
10
+ // - doc-writer: ship 보고
11
+
12
+ export async function runMock({ agent, stage, task, context }) {
13
+ const round = context.round || 1;
14
+
15
+ switch (stage) {
16
+ case 'ideate':
17
+ return {
18
+ decided: `"${task}" 의 후보 접근 비교. 라이브러리 X 채택.`,
19
+ rejected: '자체 구현(유지보수 부담)',
20
+ risks: '엣지 케이스 미식별',
21
+ files: [],
22
+ remaining: 'planner 에 PRD 시드 핸드오프',
23
+ };
24
+
25
+ case 'plan':
26
+ return {
27
+ decided: `${task} 를 AC 3개로 분해. TDD 강제.`,
28
+ rejected: '범위 확장(키 회전 / 리프레시 토큰 = non-goal)',
29
+ risks: '의존성 라이브러리 호환',
30
+ files: ['prd.json'],
31
+ remaining: 'executor 에 핸드오프',
32
+ prdSeed: {
33
+ task,
34
+ acceptance: [
35
+ { id: 'AC-001', desc: '핵심 기능 happy path', passes: false },
36
+ { id: 'AC-002', desc: '실패 케이스 거절', passes: false },
37
+ { id: 'AC-003', desc: '경계 / 잘못된 입력 거절', passes: false },
38
+ ],
39
+ non_goals: ['키 회전', '리프레시 토큰'],
40
+ },
41
+ };
42
+
43
+ case 'implement':
44
+ return {
45
+ decided: `TDD ${context.acCount || 3} 사이클. AC 모두 GREEN. quality-gate 통과.`,
46
+ files: ['src/<area>/<module>.ts', 'tests/unit/<module>.test.ts'],
47
+ remaining: 'self-review',
48
+ };
49
+
50
+ case 'self-review':
51
+ if (round === 1) {
52
+ return {
53
+ decided: 'high 1, medium 1 발견. fix loop 진입.',
54
+ files: ['src/<area>/<module>.ts'],
55
+ remaining: 'fix loop',
56
+ issues: [
57
+ { severity: 'high', category: 'security', file: 'src/<area>/<module>.ts', line: 23,
58
+ summary: '입력 검증 누락', why: '경계 케이스를 거절하지 않음' },
59
+ { severity: 'medium', category: 'test', file: 'tests/unit/<module>.test.ts',
60
+ summary: 'happy path 외 커버리지 부족', why: '실패 케이스 단언 없음' },
61
+ ],
62
+ verdict: 'approve_with_fixes',
63
+ confidence: 0.85,
64
+ };
65
+ }
66
+ return {
67
+ decided: '이전 high 해결. 추가 발견 0건.',
68
+ files: ['src/<area>/<module>.ts'],
69
+ remaining: 'codex-review',
70
+ verdict: 'approve',
71
+ confidence: 0.92,
72
+ };
73
+
74
+ case 'codex-review':
75
+ return {
76
+ decided: 'self-review 와 일치. 추가 medium 1 (timeout 미설정).',
77
+ files: ['src/<area>/<module>.ts'],
78
+ remaining: '--secure 시 단계 6, 아니면 단계 7',
79
+ issues: [{ severity: 'medium', category: 'correctness',
80
+ file: 'src/<area>/<module>.ts',
81
+ summary: 'fetch timeout 미설정', why: '무한 대기 가능' }],
82
+ verdict: 'approve_with_fixes',
83
+ confidence: 0.88,
84
+ };
85
+
86
+ case 'codex-challenge':
87
+ return {
88
+ decided: '적대적 시나리오 5건 검토. 신규 critical 0, high 0, info 1.',
89
+ files: [],
90
+ remaining: 'ship',
91
+ issues: [{ severity: 'info', category: 'security',
92
+ summary: 'replay 방어 권장 (현 PRD non-goal)', why: 'jti / replay cache 향후 고려' }],
93
+ verdict: 'approve',
94
+ confidence: 0.95,
95
+ };
96
+
97
+ case 'ship':
98
+ return {
99
+ decided: 'CHANGELOG 갱신, PR 본문 한국어 초안 작성. 자동 push 안 함 (사용자 룰).',
100
+ files: ['docs/CHANGELOG.md', 'WORKING-CONTEXT.md'],
101
+ remaining: '사용자 검토 후 gh pr create 또는 git push 수동',
102
+ };
103
+
104
+ default:
105
+ throw new Error(`unknown stage: ${stage}`);
106
+ }
107
+ }
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ // Import the already-authenticated GitHub CLI OAuth token into the HARNESS vault.
3
+ // This is an explicit local-session bridge, not a static API-key path.
4
+
5
+ import { spawnSync } from 'node:child_process';
6
+ import { save, audit, redact } from '../lib/token-vault.js';
7
+
8
+ function runGh(args) {
9
+ const r = spawnSync('gh', args, {
10
+ encoding: 'utf8',
11
+ windowsHide: true,
12
+ });
13
+ if (r.status !== 0) {
14
+ throw new Error(`gh ${args.join(' ')} failed\n${r.stderr || r.stdout}`);
15
+ }
16
+ return r.stdout.trim();
17
+ }
18
+
19
+ function parseScopes(statusText) {
20
+ const line = statusText.split(/\r?\n/).find((l) => /Token scopes:/i.test(l));
21
+ if (!line) return 'unknown';
22
+ return line
23
+ .replace(/^.*Token scopes:\s*/i, '')
24
+ .split(',')
25
+ .map((s) => s.replace(/['"]/g, '').trim())
26
+ .filter(Boolean)
27
+ .join(' ');
28
+ }
29
+
30
+ (async () => {
31
+ try {
32
+ audit('auth.github.import_gh.requested', { provider: 'github' });
33
+ const status = runGh(['auth', 'status']);
34
+ const token = runGh(['auth', 'token']);
35
+ const scope = parseScopes(status);
36
+ const location = await save('github', {
37
+ access_token: token,
38
+ token_type: 'token',
39
+ scope,
40
+ source: 'gh-cli',
41
+ });
42
+ audit('auth.github.import_gh', { provider: 'github', scope, source: 'gh-cli' });
43
+ process.stdout.write('GitHub gh session imported into HARNESS vault.\n');
44
+ process.stdout.write(` backend/path : ${location}\n`);
45
+ process.stdout.write(` scope : ${scope}\n`);
46
+ process.stdout.write(` token : ${redact(token)}\n`);
47
+ } catch (e) {
48
+ audit('auth.github.import_gh.failed', { provider: 'github', error: String(e.message || e) });
49
+ process.stderr.write(`${e.message || e}\n`);
50
+ process.exit(1);
51
+ }
52
+ })();
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ // GitHub OAuth Device Flow.
3
+ // 사전 조건: HARNESS_GITHUB_CLIENT_ID 환경변수 (사용자가 자기 OAuth App 등록 후 받은 client_id).
4
+ // 자세한 절차는 docs/AUTH-MIGRATION.md §5.3.
5
+
6
+ import { save, audit } from '../lib/token-vault.js';
7
+
8
+ const CLIENT_ID = process.env.HARNESS_GITHUB_CLIENT_ID;
9
+ const SCOPES = (process.env.HARNESS_GITHUB_SCOPES || 'repo workflow').replace(/,/g, ' ');
10
+
11
+ if (!CLIENT_ID) {
12
+ process.stderr.write('환경변수 HARNESS_GITHUB_CLIENT_ID 가 필요합니다.\n');
13
+ process.stderr.write(' 1) https://github.com/settings/developers → New OAuth App\n');
14
+ process.stderr.write(' 2) "Enable Device Flow" 체크\n');
15
+ process.stderr.write(' 3) export HARNESS_GITHUB_CLIENT_ID=<your_client_id>\n');
16
+ process.exit(2);
17
+ }
18
+
19
+ async function startDevice() {
20
+ const r = await fetch('https://github.com/login/device/code', {
21
+ method: 'POST',
22
+ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
23
+ body: JSON.stringify({ client_id: CLIENT_ID, scope: SCOPES }),
24
+ });
25
+ if (!r.ok) throw new Error(`device/code 실패 ${r.status}: ${await r.text()}`);
26
+ return r.json();
27
+ }
28
+
29
+ async function poll(deviceCode, intervalSec) {
30
+ let interval = intervalSec || 5;
31
+ while (true) {
32
+ await new Promise((res) => setTimeout(res, interval * 1000));
33
+ const r = await fetch('https://github.com/login/oauth/access_token', {
34
+ method: 'POST',
35
+ headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({
37
+ client_id: CLIENT_ID,
38
+ device_code: deviceCode,
39
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
40
+ }),
41
+ });
42
+ const data = await r.json();
43
+ if (data.access_token) return data;
44
+ if (data.error === 'authorization_pending') continue;
45
+ if (data.error === 'slow_down') { interval += 5; continue; }
46
+ if (data.error === 'expired_token' || data.error === 'access_denied') {
47
+ throw new Error(`인증 실패: ${data.error}`);
48
+ }
49
+ throw new Error(`알 수 없는 응답: ${JSON.stringify(data)}`);
50
+ }
51
+ }
52
+
53
+ (async () => {
54
+ try {
55
+ audit('auth.token_issued.requested', { provider: 'github', scopes: SCOPES });
56
+ const dev = await startDevice();
57
+ process.stdout.write('\n=== GitHub OAuth Device Flow ===\n');
58
+ process.stdout.write(`URL : ${dev.verification_uri}\n`);
59
+ process.stdout.write(`코드 : ${dev.user_code}\n`);
60
+ process.stdout.write(`만료 : ${Math.floor((dev.expires_in || 900) / 60)}분\n`);
61
+ process.stdout.write('\n브라우저에서 위 URL 을 열고 코드를 입력하세요. 완료될 때까지 폴링합니다...\n\n');
62
+
63
+ const tok = await poll(dev.device_code, dev.interval || 5);
64
+ const file = await save('github', {
65
+ access_token: tok.access_token,
66
+ token_type: tok.token_type || 'bearer',
67
+ scope: tok.scope,
68
+ });
69
+ audit('auth.token_issued', { provider: 'github', scope: tok.scope, token_type: tok.token_type });
70
+
71
+ process.stdout.write(`✓ 저장됨: ${file}\n`);
72
+ process.stdout.write(` scope: ${tok.scope}\n`);
73
+ process.exit(0);
74
+ } catch (e) {
75
+ audit('auth.token_issued.failed', { provider: 'github', error: String(e.message || e) });
76
+ process.stderr.write(`✗ ${e.message || e}\n`);
77
+ process.exit(1);
78
+ }
79
+ })();
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env node
2
+ // GitHub OAuth 로그아웃. 로컬 vault 만 삭제.
3
+ // 주의: device flow 는 client secret 이 없으므로 GitHub 측 revoke API 호출 불가.
4
+ // GitHub 측에서도 폐기하려면 사용자가 https://github.com/settings/applications 에서 직접 처리.
5
+
6
+ import { remove, audit } from '../lib/token-vault.js';
7
+
8
+ (async () => {
9
+ const ok = await remove('github');
10
+ audit('auth.token_revoked', { provider: 'github', local_only: true });
11
+
12
+ if (ok) {
13
+ process.stdout.write('✓ 로컬 vault 에서 GitHub token 삭제됨.\n');
14
+ process.stdout.write(' GitHub 측에서도 폐기하려면:\n');
15
+ process.stdout.write(' https://github.com/settings/applications → 해당 OAuth App → Revoke\n');
16
+ process.exit(0);
17
+ } else {
18
+ process.stdout.write('GitHub token 이 vault 에 없습니다.\n');
19
+ process.exit(1);
20
+ }
21
+ })();
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ // GitHub OAuth 상태 점검. vault 에 토큰이 있고 GitHub API 가 응답하는지 확인.
3
+
4
+ import { load, redact, backend } from '../lib/token-vault.js';
5
+
6
+ async function verify(tok) {
7
+ try {
8
+ const r = await fetch('https://api.github.com/user', {
9
+ headers: {
10
+ 'Authorization': `${tok.token_type || 'token'} ${tok.access_token}`,
11
+ 'Accept': 'application/vnd.github+json',
12
+ 'User-Agent': 'harness-cli',
13
+ },
14
+ });
15
+ if (!r.ok) return { ok: false, status: r.status };
16
+ const u = await r.json();
17
+ return { ok: true, login: u.login };
18
+ } catch (e) {
19
+ return { ok: false, error: String(e.message || e) };
20
+ }
21
+ }
22
+
23
+ (async () => {
24
+ const tok = await load('github');
25
+ if (!tok) {
26
+ process.stdout.write('GitHub: 미인증 (`npm run auth:github:login` 필요).\n');
27
+ process.exitCode = 1;
28
+ return;
29
+ }
30
+
31
+ process.stdout.write('GitHub 인증 상태:\n');
32
+ process.stdout.write(` backend : ${await backend()}\n`);
33
+ process.stdout.write(` scope : ${tok.scope}\n`);
34
+ process.stdout.write(` saved_at : ${tok.saved_at}\n`);
35
+ process.stdout.write(` token : ${redact(tok.access_token)}\n`);
36
+
37
+ const v = await verify(tok);
38
+ if (v.ok) {
39
+ process.stdout.write(` user : ${v.login}\n`);
40
+ process.stdout.write(` 유효 : ✓\n`);
41
+ process.exitCode = 0;
42
+ } else {
43
+ process.stdout.write(` 유효 : ✗ (${v.status || v.error})\n`);
44
+ process.exitCode = 2;
45
+ }
46
+ })();
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ // 정규 카탈로그 (agents/, skills/, commands/, hooks/) → .claude/ 로 투영.
3
+ // Claude Code 가 인식하는 디렉터리 레이아웃 + .claude-plugin/plugin.json.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { buildRoots } from './core/build-roots.js';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const { sourceRoot: ROOT, targetRoot: TARGET_ROOT } = buildRoots(path.resolve(__dirname, '..'));
12
+ const OUT = path.join(TARGET_ROOT, '.claude');
13
+
14
+ function ensure(dir) { fs.mkdirSync(dir, { recursive: true }); }
15
+ function copy(src, dst) { fs.mkdirSync(path.dirname(dst), { recursive: true }); fs.copyFileSync(src, dst); }
16
+ function copyDir(src, dst) {
17
+ if (!fs.existsSync(src)) return 0;
18
+ let n = 0;
19
+ for (const e of fs.readdirSync(src, { withFileTypes: true })) {
20
+ const s = path.join(src, e.name), d = path.join(dst, e.name);
21
+ if (e.isDirectory()) n += copyDir(s, d);
22
+ else { copy(s, d); n++; }
23
+ }
24
+ return n;
25
+ }
26
+
27
+ console.log('=> build-claude');
28
+ ensure(OUT);
29
+
30
+ // agents/ → .claude/agents/
31
+ const agents = copyDir(path.join(ROOT, 'agents'), path.join(OUT, 'agents'));
32
+ console.log(` agents : ${agents}`);
33
+
34
+ // skills/ → .claude/skills/
35
+ const skills = copyDir(path.join(ROOT, 'skills'), path.join(OUT, 'skills'));
36
+ console.log(` skills : ${skills}`);
37
+
38
+ // commands/ → .claude/commands/
39
+ const cmds = copyDir(path.join(ROOT, 'commands'), path.join(OUT, 'commands'));
40
+ console.log(` commands : ${cmds}`);
41
+
42
+ // hooks/scripts/ → .claude/hooks/
43
+ const hookScripts = copyDir(path.join(ROOT, 'hooks', 'scripts'), path.join(OUT, 'hooks'));
44
+ console.log(` hooks : ${hookScripts}`);
45
+
46
+ // hooks/hooks.json → .claude/hooks.json (Claude Code 형식: top-level)
47
+ const hooksJson = path.join(ROOT, 'hooks', 'hooks.json');
48
+ if (fs.existsSync(hooksJson)) {
49
+ const def = JSON.parse(fs.readFileSync(hooksJson, 'utf8'));
50
+ // 경로를 .claude/ 기준으로 재작성: scripts/ → hooks/ (.claude 안에선 hooks/ 디렉터리에 복사돼 있음)
51
+ const remap = (entries) => (entries || []).map(e => ({
52
+ ...e,
53
+ hook: e.hook.replace(/^scripts\//, 'hooks/'),
54
+ }));
55
+ const claudeHooks = {
56
+ version: def.version,
57
+ PreToolUse: remap(def.PreToolUse),
58
+ PostToolUse: remap(def.PostToolUse),
59
+ PreCompact: remap(def.PreCompact),
60
+ Stop: remap(def.Stop),
61
+ SessionStart: remap(def.SessionStart),
62
+ SessionEnd: remap(def.SessionEnd),
63
+ UserPromptSubmit: remap(def.UserPromptSubmit),
64
+ };
65
+ // 빈 키 제거
66
+ for (const k of Object.keys(claudeHooks)) if (!claudeHooks[k] || claudeHooks[k].length === 0) delete claudeHooks[k];
67
+ fs.writeFileSync(path.join(OUT, 'hooks.json'), JSON.stringify(claudeHooks, null, 2));
68
+ console.log(' hooks.json : OK');
69
+ }
70
+
71
+ // 거버넌스 마크다운 → .claude/ 루트에 그대로
72
+ for (const f of ['CLAUDE.md', 'AGENTS.md', 'RULES.md', 'SOUL.md', 'WORKING-CONTEXT.md', 'REVIEW.md']) {
73
+ if (fs.existsSync(path.join(ROOT, f))) {
74
+ copy(path.join(ROOT, f), path.join(OUT, f));
75
+ }
76
+ }
77
+ console.log(' governance : 6 files');
78
+
79
+ // .claude-plugin/plugin.json
80
+ const pluginDir = path.join(TARGET_ROOT, '.claude-plugin');
81
+ ensure(pluginDir);
82
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
83
+ const plugin = {
84
+ $schema: 'https://raw.githubusercontent.com/Ps-Neko/NEKOWORK/main/schemas/plugin.schema.json',
85
+ name: 'harness',
86
+ version: pkg.version,
87
+ description: pkg.description,
88
+ components: {
89
+ agents: agents,
90
+ skills: skills,
91
+ commands: cmds,
92
+ hooks: hookScripts,
93
+ },
94
+ };
95
+ // homepage / authors 는 publish 전에 외부 레포 정보로 채울 것 (env 변수 또는 package.json 활용 권장)
96
+ if (process.env.HARNESS_HOMEPAGE) plugin.homepage = process.env.HARNESS_HOMEPAGE;
97
+ if (pkg.author) plugin.authors = Array.isArray(pkg.author) ? pkg.author : [pkg.author];
98
+ fs.writeFileSync(path.join(pluginDir, 'plugin.json'), JSON.stringify(plugin, null, 2));
99
+ console.log(' plugin.json: OK');
100
+
101
+ console.log('=> .claude 빌드 완료');
@@ -0,0 +1,286 @@
1
+ #!/usr/bin/env node
2
+ // Generate docs/CODEMAPS/<area>.md from repository directories.
3
+ // The maps include a shallow directory tree plus exported JS symbols.
4
+ // They intentionally omit code bodies and are safe to regenerate.
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const ROOT = path.resolve(__dirname, '..');
12
+ const OUT = path.join(ROOT, 'docs', 'CODEMAPS');
13
+
14
+ const AREAS = [
15
+ { area: 'scripts', dir: 'scripts', depth: 3 },
16
+ { area: 'agents', dir: 'agents', depth: 1 },
17
+ { area: 'skills', dir: 'skills', depth: 2 },
18
+ { area: 'hooks', dir: 'hooks', depth: 2 },
19
+ { area: 'manifests', dir: 'manifests', depth: 1 },
20
+ { area: 'schemas', dir: 'schemas', depth: 1 },
21
+ { area: 'bridge', dir: 'bridge', depth: 1 },
22
+ { area: 'rules', dir: 'rules', depth: 2 },
23
+ { area: 'tests', dir: 'tests', depth: 2 },
24
+ ];
25
+
26
+ const SKIP = new Set([
27
+ 'node_modules',
28
+ '.git',
29
+ '.harness',
30
+ '.claude',
31
+ '.codex',
32
+ '.cursor',
33
+ '.gemini',
34
+ '.opencode',
35
+ ]);
36
+
37
+ function parseArgs(argv) {
38
+ const args = { check: false, verbose: false };
39
+ for (let i = 2; i < argv.length; i++) {
40
+ const arg = argv[i];
41
+ if (arg === '--check') args.check = true;
42
+ else if (arg === '--verbose' || arg === '-v') args.verbose = true;
43
+ else if (arg === '--help' || arg === '-h') {
44
+ console.log('Usage: node scripts/build-codemaps.js [--check] [--verbose]');
45
+ process.exit(0);
46
+ } else {
47
+ console.error(`unknown argument: ${arg}`);
48
+ process.exit(2);
49
+ }
50
+ }
51
+ return args;
52
+ }
53
+
54
+ function toSlash(value) {
55
+ return value.split(path.sep).join('/');
56
+ }
57
+
58
+ function directoryTree(dir, maxDepth, prefix = '', depth = 0) {
59
+ if (depth > maxDepth) return [];
60
+
61
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
62
+ .filter(entry => !SKIP.has(entry.name) && !entry.name.startsWith('.'))
63
+ .sort((a, b) => {
64
+ if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
65
+ return a.name.localeCompare(b.name);
66
+ });
67
+
68
+ const lines = [];
69
+ for (let i = 0; i < entries.length; i++) {
70
+ const entry = entries[i];
71
+ const last = i === entries.length - 1;
72
+ const branch = last ? '`-- ' : '|-- ';
73
+ const childPrefix = last ? ' ' : '| ';
74
+ const label = entry.isDirectory() ? `${entry.name}/` : entry.name;
75
+ lines.push(prefix + branch + label);
76
+ if (entry.isDirectory()) {
77
+ lines.push(...directoryTree(path.join(dir, entry.name), maxDepth, prefix + childPrefix, depth + 1));
78
+ }
79
+ }
80
+ return lines;
81
+ }
82
+
83
+ function extractExports(code) {
84
+ const exports = new Set();
85
+ const patterns = [
86
+ /^export\s+(?:async\s+)?function\s+([a-zA-Z_$][\w$]*)/gm,
87
+ /^export\s+class\s+([a-zA-Z_$][\w$]*)/gm,
88
+ /^export\s+const\s+([a-zA-Z_$][\w$]*)/gm,
89
+ /^export\s+let\s+([a-zA-Z_$][\w$]*)/gm,
90
+ /^export\s+\{([^}]+)\}/gm,
91
+ /^export\s+default\s+(?:async\s+)?function\s+([a-zA-Z_$][\w$]*)/gm,
92
+ ];
93
+
94
+ for (const pattern of patterns) {
95
+ let match;
96
+ while ((match = pattern.exec(code)) !== null) {
97
+ const raw = match[1];
98
+ if (raw.includes(',') || raw.includes(' as ')) {
99
+ for (const part of raw.split(',')) {
100
+ const name = part.trim().split(/\s+as\s+/)[0].trim();
101
+ if (name) exports.add(name);
102
+ }
103
+ } else if (raw) {
104
+ exports.add(raw);
105
+ }
106
+ }
107
+ }
108
+
109
+ if (/^export\s+default\b/m.test(code) && !exports.has('default')) {
110
+ exports.add('default');
111
+ }
112
+
113
+ return [...exports].sort();
114
+ }
115
+
116
+ function leadingComment(code) {
117
+ const lines = code.split('\n');
118
+ const collected = [];
119
+ let started = false;
120
+
121
+ for (const line of lines) {
122
+ const trimmed = line.trim();
123
+ if (!started && (trimmed === '' || trimmed.startsWith('#!'))) continue;
124
+ if (trimmed.startsWith('//')) {
125
+ collected.push(trimmed.replace(/^\/\/\s?/, ''));
126
+ started = true;
127
+ } else if (started) {
128
+ break;
129
+ } else {
130
+ break;
131
+ }
132
+ }
133
+
134
+ return collected.join(' ');
135
+ }
136
+
137
+ function cleanCell(value) {
138
+ return String(value || '')
139
+ .normalize('NFKD')
140
+ .replace(/[^\x20-\x7E]/g, ' ')
141
+ .replace(/[|]/g, '\\|')
142
+ .replace(/\s+/g, ' ')
143
+ .trim()
144
+ .slice(0, 140);
145
+ }
146
+
147
+ function collectJsFiles(absDir) {
148
+ const files = [];
149
+
150
+ function walk(dir, rel = '') {
151
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
152
+ if (SKIP.has(entry.name) || entry.name.startsWith('.')) continue;
153
+
154
+ const abs = path.join(dir, entry.name);
155
+ const nextRel = rel ? `${rel}/${entry.name}` : entry.name;
156
+ if (entry.isDirectory()) {
157
+ walk(abs, nextRel);
158
+ } else if (/\.(m?js|cjs)$/.test(entry.name)) {
159
+ const code = fs.readFileSync(abs, 'utf8');
160
+ files.push({
161
+ rel: nextRel,
162
+ exports: extractExports(code),
163
+ comment: cleanCell(leadingComment(code)),
164
+ });
165
+ }
166
+ }
167
+ }
168
+
169
+ walk(absDir);
170
+ return files.sort((a, b) => a.rel.localeCompare(b.rel));
171
+ }
172
+
173
+ function buildArea({ area, dir, depth }) {
174
+ const absDir = path.join(ROOT, dir);
175
+ if (!fs.existsSync(absDir)) return null;
176
+
177
+ const treeLines = directoryTree(absDir, depth);
178
+ const jsFiles = collectJsFiles(absDir);
179
+
180
+ const lines = [
181
+ `# CODEMAP: ${area}`,
182
+ '',
183
+ `> Generated by \`scripts/build-codemaps.js\` from \`${dir}/\`. Do not edit directly.`,
184
+ '> Directory shape and exported JS symbols only. Code bodies are intentionally omitted.',
185
+ '',
186
+ '## Directory Tree',
187
+ '',
188
+ '```text',
189
+ `${dir}/`,
190
+ ...treeLines,
191
+ '```',
192
+ '',
193
+ ];
194
+
195
+ if (jsFiles.length) {
196
+ lines.push('## JS Exports');
197
+ lines.push('');
198
+ lines.push('| File | Exports | Description |');
199
+ lines.push('|---|---|---|');
200
+ for (const file of jsFiles) {
201
+ const exports = file.exports.length
202
+ ? file.exports.map(name => `\`${name}\``).join(', ')
203
+ : '_(none)_';
204
+ lines.push(`| \`${file.rel}\` | ${exports} | ${file.comment} |`);
205
+ }
206
+ lines.push('');
207
+ }
208
+
209
+ return {
210
+ area,
211
+ content: lines.join('\n') + '\n',
212
+ fileCount: jsFiles.length,
213
+ };
214
+ }
215
+
216
+ function buildIndex() {
217
+ const lines = [
218
+ '# CODEMAPS: index',
219
+ '',
220
+ '> Generated by `scripts/build-codemaps.js`. Do not edit directly.',
221
+ '',
222
+ '| Area | File |',
223
+ '|---|---|',
224
+ ];
225
+
226
+ for (const area of AREAS) {
227
+ if (fs.existsSync(path.join(OUT, `${area.area}.md`))) {
228
+ lines.push(`| ${area.area} | [${area.area}.md](./${area.area}.md) |`);
229
+ }
230
+ }
231
+
232
+ return lines.join('\n') + '\n';
233
+ }
234
+
235
+ function writeOrCheck(file, content, args) {
236
+ const before = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
237
+ if (before === content) return false;
238
+ if (!args.check) fs.writeFileSync(file, content);
239
+ return true;
240
+ }
241
+
242
+ function main() {
243
+ const args = parseArgs(process.argv);
244
+ fs.mkdirSync(OUT, { recursive: true });
245
+
246
+ let changed = 0;
247
+ let total = 0;
248
+
249
+ for (const area of AREAS) {
250
+ const built = buildArea(area);
251
+ if (!built) {
252
+ if (args.verbose) console.log(`[SKIP] ${area.area}: ${area.dir}/ not found`);
253
+ continue;
254
+ }
255
+
256
+ total++;
257
+ const outFile = path.join(OUT, `${built.area}.md`);
258
+ if (writeOrCheck(outFile, built.content, args)) {
259
+ changed++;
260
+ console.log(`[${args.check ? 'DIFF' : 'WRITE'}] ${toSlash(path.relative(ROOT, outFile))} (${built.fileCount} files)`);
261
+ } else if (args.verbose) {
262
+ console.log(`[ OK ] ${toSlash(path.relative(ROOT, outFile))}`);
263
+ }
264
+ }
265
+
266
+ const indexFile = path.join(OUT, 'README.md');
267
+ if (writeOrCheck(indexFile, buildIndex(), args)) {
268
+ changed++;
269
+ console.log(`[${args.check ? 'DIFF' : 'WRITE'}] ${toSlash(path.relative(ROOT, indexFile))}`);
270
+ } else if (args.verbose) {
271
+ console.log(`[ OK ] ${toSlash(path.relative(ROOT, indexFile))}`);
272
+ }
273
+
274
+ if (args.check) {
275
+ if (changed > 0) {
276
+ console.error(`${changed} codemap file(s) are outdated. Run: node scripts/build-codemaps.js`);
277
+ process.exit(1);
278
+ }
279
+ console.log('All codemaps are up to date.');
280
+ return;
281
+ }
282
+
283
+ console.log(`\nprocessed=${total}, changed=${changed}`);
284
+ }
285
+
286
+ main();