@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,93 @@
1
+ #!/usr/bin/env node
2
+ // 정규 카탈로그 → .codex/ 로 투영.
3
+ // Codex CLI 형식: config.toml + agents/*.toml.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import YAML from 'yaml';
9
+ import { buildRoots } from './core/build-roots.js';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const { sourceRoot: ROOT, targetRoot: TARGET_ROOT } = buildRoots(path.resolve(__dirname, '..'));
13
+ const OUT = path.join(TARGET_ROOT, '.codex');
14
+
15
+ function ensure(dir) { fs.mkdirSync(dir, { recursive: true }); }
16
+
17
+ console.log('=> build-codex');
18
+ ensure(OUT);
19
+ ensure(path.join(OUT, 'agents'));
20
+
21
+ // agents/<name>.md → .codex/agents/<name>.toml (Codex provider 만)
22
+ const agentsDir = path.join(ROOT, 'agents');
23
+ let n = 0;
24
+ if (fs.existsSync(agentsDir)) {
25
+ for (const f of fs.readdirSync(agentsDir)) {
26
+ if (!f.endsWith('.md')) continue;
27
+ const content = fs.readFileSync(path.join(agentsDir, f), 'utf8');
28
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
29
+ if (!fmMatch) continue;
30
+ const fm = YAML.parse(fmMatch[1]);
31
+ if (fm.provider !== 'codex') continue;
32
+ const body = content.slice(fmMatch[0].length).trim();
33
+ const tomlEsc = (s) => '"""' + String(s).replace(/"""/g, '\\"\\"\\"') + '"""';
34
+ const toml = `# 자동 생성. agents/${f} 가 원본.
35
+ name = "${fm.name}"
36
+ description = ${tomlEsc(fm.description)}
37
+ model = "${fm.model}"
38
+
39
+ [sandbox]
40
+ mode = "${fm.sandbox || 'read-only'}"
41
+ network_access = ${fm.network_access === false ? 'false' : 'true'}
42
+
43
+ [prompt]
44
+ body = ${tomlEsc(body)}
45
+ `;
46
+ const out = path.join(OUT, 'agents', fm.name + '.toml');
47
+ fs.writeFileSync(out, toml);
48
+ n++;
49
+ }
50
+ }
51
+ console.log(` agents : ${n} (codex provider 만)`);
52
+
53
+ // .codex/config.toml — MCP 서버 + profiles
54
+ const manifest = YAML.parse(fs.readFileSync(path.join(ROOT, 'agent.yaml'), 'utf8'));
55
+ const lines = [];
56
+ lines.push('# 자동 생성. agent.yaml 이 원본.\n');
57
+ lines.push('multi_agent = true\n');
58
+
59
+ for (const s of (manifest.mcp?.external_servers || [])) {
60
+ lines.push(`[mcp_servers.${s.name}]`);
61
+ if (s.pin) {
62
+ lines.push('command = "npx"');
63
+ lines.push(`args = ["-y", "${s.pin}"]`);
64
+ lines.push('startup_timeout_sec = 30');
65
+ } else if (s.url) {
66
+ lines.push(`url = "${s.url}"`);
67
+ lines.push('type = "http"');
68
+ }
69
+ lines.push('');
70
+ }
71
+
72
+ lines.push('[profiles.review]');
73
+ lines.push('sandbox_mode = "read-only"');
74
+ lines.push('network_access = false');
75
+ lines.push('');
76
+ lines.push('[profiles.strict]');
77
+ lines.push('sandbox_mode = "read-only"');
78
+ lines.push('network_access = false');
79
+ lines.push('');
80
+ lines.push('[profiles.workspace]');
81
+ lines.push('sandbox_mode = "workspace-write"');
82
+ lines.push('network_access = true');
83
+
84
+ fs.writeFileSync(path.join(OUT, 'config.toml'), lines.join('\n') + '\n');
85
+ console.log(' config.toml: OK');
86
+
87
+ // AGENTS.md 만 그대로 (Codex 표준)
88
+ if (fs.existsSync(path.join(ROOT, 'AGENTS.md'))) {
89
+ fs.copyFileSync(path.join(ROOT, 'AGENTS.md'), path.join(OUT, 'AGENTS.md'));
90
+ console.log(' AGENTS.md : OK');
91
+ }
92
+
93
+ console.log('=> .codex 빌드 완료');
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ // 정규 카탈로그 → .cursor/ 로 투영.
3
+ // Cursor 형식: .cursor/rules/*.mdc (공식), .cursorrules (legacy 공유 룰).
4
+ // 이벤트 어댑터: hook 의 PreToolUse/PostToolUse 등 PascalCase → before/after camelCase.
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import YAML from 'yaml';
10
+ import { buildRoots } from './core/build-roots.js';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const { sourceRoot: ROOT, targetRoot: TARGET_ROOT } = buildRoots(path.resolve(__dirname, '..'));
14
+ const OUT = path.join(TARGET_ROOT, '.cursor');
15
+
16
+ function ensure(dir) { fs.mkdirSync(dir, { recursive: true }); }
17
+
18
+ console.log('=> build-cursor');
19
+ ensure(OUT);
20
+ ensure(path.join(OUT, 'rules'));
21
+
22
+ // agents/<name>.md → .cursor/rules/agents/<name>.mdc (frontmatter alwaysApply: false, glob: **)
23
+ const agentsDir = path.join(ROOT, 'agents');
24
+ let agentN = 0;
25
+ ensure(path.join(OUT, 'rules', 'agents'));
26
+ if (fs.existsSync(agentsDir)) {
27
+ for (const f of fs.readdirSync(agentsDir)) {
28
+ if (!f.endsWith('.md')) continue;
29
+ const content = fs.readFileSync(path.join(agentsDir, f), 'utf8');
30
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
31
+ if (!fmMatch) continue;
32
+ const fm = YAML.parse(fmMatch[1]);
33
+ const body = content.slice(fmMatch[0].length).trim();
34
+ const mdc = `---
35
+ description: ${JSON.stringify(fm.description || fm.name)}
36
+ alwaysApply: false
37
+ globs: ["**/*"]
38
+ provider: ${fm.provider || 'unknown'}
39
+ model: ${fm.model || 'unknown'}
40
+ ---
41
+
42
+ ${body}
43
+ `;
44
+ fs.writeFileSync(path.join(OUT, 'rules', 'agents', fm.name + '.mdc'), mdc);
45
+ agentN++;
46
+ }
47
+ }
48
+ console.log(` agents : ${agentN} (rules/agents/*.mdc)`);
49
+
50
+ // skills/<name>/SKILL.md → .cursor/rules/skills/<name>.mdc (alwaysApply: true)
51
+ const skillsDir = path.join(ROOT, 'skills');
52
+ let skillN = 0;
53
+ ensure(path.join(OUT, 'rules', 'skills'));
54
+ if (fs.existsSync(skillsDir)) {
55
+ for (const e of fs.readdirSync(skillsDir, { withFileTypes: true })) {
56
+ if (!e.isDirectory()) continue;
57
+ const file = path.join(skillsDir, e.name, 'SKILL.md');
58
+ if (!fs.existsSync(file)) continue;
59
+ const content = fs.readFileSync(file, 'utf8');
60
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
61
+ const fm = fmMatch ? YAML.parse(fmMatch[1]) : { name: e.name };
62
+ const body = fmMatch ? content.slice(fmMatch[0].length).trim() : content;
63
+ const mdc = `---
64
+ description: ${JSON.stringify(fm.description || fm.name)}
65
+ alwaysApply: false
66
+ globs: ["**/*"]
67
+ ---
68
+
69
+ ${body}
70
+ `;
71
+ fs.writeFileSync(path.join(OUT, 'rules', 'skills', e.name + '.mdc'), mdc);
72
+ skillN++;
73
+ }
74
+ }
75
+ console.log(` skills : ${skillN} (rules/skills/*.mdc)`);
76
+
77
+ // hooks → .cursor/hooks.json (이벤트 이름 PascalCase → camelCase: before/after Tool)
78
+ const hooksFile = path.join(ROOT, 'hooks', 'hooks.json');
79
+ if (fs.existsSync(hooksFile)) {
80
+ const def = JSON.parse(fs.readFileSync(hooksFile, 'utf8'));
81
+ const remap = (arr) => (arr || []).map(e => ({ ...e, hook: e.hook.replace(/^scripts\//, 'hooks/') }));
82
+ const eventMap = {
83
+ PreToolUse: 'beforeTool',
84
+ PostToolUse: 'afterTool',
85
+ PreCompact: 'beforeCompact',
86
+ Stop: 'sessionStop',
87
+ SessionStart: 'sessionStart',
88
+ SessionEnd: 'sessionEnd',
89
+ UserPromptSubmit: 'userPromptSubmit',
90
+ };
91
+ const cursorHooks = { version: def.version };
92
+ for (const [src, dst] of Object.entries(eventMap)) {
93
+ const arr = remap(def[src]);
94
+ if (arr.length) cursorHooks[dst] = arr;
95
+ }
96
+ fs.writeFileSync(path.join(OUT, 'hooks.json'), JSON.stringify(cursorHooks, null, 2));
97
+ console.log(' hooks.json : OK (camelCase)');
98
+
99
+ // hooks/scripts/ → .cursor/hooks/
100
+ ensure(path.join(OUT, 'hooks'));
101
+ const hookScriptsDir = path.join(ROOT, 'hooks', 'scripts');
102
+ if (fs.existsSync(hookScriptsDir)) {
103
+ for (const f of fs.readdirSync(hookScriptsDir)) {
104
+ fs.copyFileSync(path.join(hookScriptsDir, f), path.join(OUT, 'hooks', f));
105
+ }
106
+ }
107
+ }
108
+
109
+ // .cursorrules — 단일 진입 가이드
110
+ const manifest = YAML.parse(fs.readFileSync(path.join(ROOT, 'agent.yaml'), 'utf8'));
111
+ const cursorrules = `# Auto-generated. agent.yaml + agents/*.md 가 원본.
112
+ # HARNESS v${manifest.version} — Cursor 어댑터
113
+
114
+ 이 워크스페이스는 HARNESS 카탈로그를 기반으로 한다.
115
+ .cursor/rules/agents/ : 11개 에이전트 (provider/model 메타데이터 포함)
116
+ .cursor/rules/skills/ : ${manifest.skills?.length || 0}개 스킬 (claude-led-codex-review 등)
117
+ .cursor/hooks.json : Cursor 이벤트 (beforeTool/afterTool/...) 어댑터
118
+
119
+ 직접 편집 금지. 변경은 정규 카탈로그(agents/, skills/, agent.yaml)에서 하고
120
+ \`scripts/build-cursor.js\` 를 다시 실행할 것.
121
+ `;
122
+ fs.writeFileSync(path.join(OUT, '.cursorrules'), cursorrules);
123
+ console.log(' .cursorrules: OK');
124
+
125
+ // AGENTS.md / RULES.md / SOUL.md 그대로 복사
126
+ for (const f of ['AGENTS.md', 'RULES.md', 'SOUL.md', 'WORKING-CONTEXT.md']) {
127
+ const src = path.join(ROOT, f);
128
+ if (fs.existsSync(src)) fs.copyFileSync(src, path.join(OUT, f));
129
+ }
130
+ console.log(' governance : OK');
131
+
132
+ console.log('=> .cursor 빌드 완료');
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ // 정규 카탈로그 → .gemini/ 로 투영.
3
+ // Gemini 형식: 요약 중심 (output_format: summary). 풀 본문은 정규 카탈로그를 참조.
4
+ // GEMINI.md 가 단일 진입점, 스킬은 description 만 노출 (progressive disclosure).
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import YAML from 'yaml';
10
+ import { buildRoots } from './core/build-roots.js';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const { sourceRoot: ROOT, targetRoot: TARGET_ROOT } = buildRoots(path.resolve(__dirname, '..'));
14
+ const OUT = path.join(TARGET_ROOT, '.gemini');
15
+
16
+ function ensure(dir) { fs.mkdirSync(dir, { recursive: true }); }
17
+
18
+ console.log('=> build-gemini');
19
+ ensure(OUT);
20
+
21
+ const manifest = YAML.parse(fs.readFileSync(path.join(ROOT, 'agent.yaml'), 'utf8'));
22
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
23
+
24
+ // GEMINI.md — 단일 진입점 + 스킬/에이전트 description 카탈로그 (포인터만)
25
+ const lines = [];
26
+ lines.push('# GEMINI.md');
27
+ lines.push('');
28
+ lines.push(`> 자동 생성. agent.yaml v${manifest.version} 이 원본.`);
29
+ lines.push('> Gemini provider 가 받는 컨텍스트는 요약 형태. 풀 본문은 정규 카탈로그 참조.');
30
+ lines.push('');
31
+ lines.push('## 운영 원칙 (요약)');
32
+ lines.push('');
33
+ lines.push('- 한국어 응답 기본 (외부 영어 PR 환영).');
34
+ lines.push('- 사실 조사 강제: Edit/Write 직전 importer/API/schema 확인.');
35
+ lines.push('- read-only 기본. 사이드 이펙트는 명시 승인 후.');
36
+ lines.push('- 모든 수정은 quality-gate → self-review → codex-review → human-gate 순.');
37
+ lines.push('');
38
+
39
+ lines.push('## 에이전트 (Gemini provider 전용)');
40
+ lines.push('');
41
+ lines.push('| Agent | Model | 용도 |');
42
+ lines.push('|---|---|---|');
43
+ const agentsDir = path.join(ROOT, 'agents');
44
+ let geminiAgentN = 0;
45
+ const allAgents = [];
46
+ if (fs.existsSync(agentsDir)) {
47
+ for (const f of fs.readdirSync(agentsDir)) {
48
+ if (!f.endsWith('.md')) continue;
49
+ const content = fs.readFileSync(path.join(agentsDir, f), 'utf8');
50
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
51
+ if (!fmMatch) continue;
52
+ const fm = YAML.parse(fmMatch[1]);
53
+ allAgents.push(fm);
54
+ if (fm.provider === 'gemini') {
55
+ lines.push(`| ${fm.name} | ${fm.model || '?'} | ${fm.description || ''} |`);
56
+ geminiAgentN++;
57
+ }
58
+ }
59
+ }
60
+ if (geminiAgentN === 0) lines.push('| _(없음)_ | - | - |');
61
+ lines.push('');
62
+
63
+ lines.push('## 전체 에이전트 (참조용)');
64
+ lines.push('');
65
+ lines.push('| Agent | Provider | 핸드오프 가능 |');
66
+ lines.push('|---|---|---|');
67
+ for (const a of allAgents) {
68
+ lines.push(`| ${a.name} | ${a.provider || '?'} | ${a.provider === 'gemini' ? '✓' : '읽기 전용 핸드오프 수신'} |`);
69
+ }
70
+ lines.push('');
71
+
72
+ lines.push('## 스킬 카탈로그 (description 만)');
73
+ lines.push('');
74
+ const skillsDir = path.join(ROOT, 'skills');
75
+ const skillRows = [];
76
+ if (fs.existsSync(skillsDir)) {
77
+ for (const e of fs.readdirSync(skillsDir, { withFileTypes: true })) {
78
+ if (!e.isDirectory()) continue;
79
+ const file = path.join(skillsDir, e.name, 'SKILL.md');
80
+ if (!fs.existsSync(file)) continue;
81
+ const fmMatch = fs.readFileSync(file, 'utf8').match(/^---\s*\n([\s\S]*?)\n---/);
82
+ if (!fmMatch) continue;
83
+ const fm = YAML.parse(fmMatch[1]);
84
+ skillRows.push({ name: fm.name || e.name, desc: fm.description || '' });
85
+ }
86
+ }
87
+ for (const s of skillRows) {
88
+ lines.push(`- **${s.name}** — ${s.desc}`);
89
+ }
90
+ lines.push('');
91
+ lines.push('> 풀 본문이 필요하면 정규 카탈로그 \`skills/<name>/SKILL.md\` 를 직접 읽을 것.');
92
+ lines.push('');
93
+
94
+ lines.push('## MCP 서버 (참고)');
95
+ lines.push('');
96
+ for (const s of (manifest.mcp?.external_servers || [])) {
97
+ if (s.pin) lines.push(`- ${s.name}: \`${s.pin}\``);
98
+ else if (s.url) lines.push(`- ${s.name}: ${s.url}`);
99
+ }
100
+ lines.push('');
101
+
102
+ fs.writeFileSync(path.join(OUT, 'GEMINI.md'), lines.join('\n'));
103
+ console.log(` GEMINI.md : OK (agents=${geminiAgentN}/${allAgents.length}, skills=${skillRows.length})`);
104
+
105
+ // settings.json — Gemini CLI 가 읽는 가벼운 설정
106
+ const settings = {
107
+ $schema: 'https://gemini.dev/schemas/settings.schema.json',
108
+ harness_version: pkg.version,
109
+ context_files: ['GEMINI.md'],
110
+ provider_filter: 'gemini',
111
+ fact_forcing: manifest.security?.fact_forcing_default ?? true,
112
+ outbound_network_default: manifest.security?.outbound_network_default || 'deny',
113
+ };
114
+ fs.writeFileSync(path.join(OUT, 'settings.json'), JSON.stringify(settings, null, 2));
115
+ console.log(' settings.json: OK');
116
+
117
+ console.log('=> .gemini 빌드 완료');
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ // 정규 카탈로그 → .opencode/ 로 투영.
3
+ // opencode 형식: JSON 단일 설정 (config_format: json).
4
+ // agents/skills/hooks 를 모두 JSON 배열로 합성.
5
+
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import YAML from 'yaml';
10
+ import { buildRoots } from './core/build-roots.js';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const { sourceRoot: ROOT, targetRoot: TARGET_ROOT } = buildRoots(path.resolve(__dirname, '..'));
14
+ const OUT = path.join(TARGET_ROOT, '.opencode');
15
+
16
+ function ensure(dir) { fs.mkdirSync(dir, { recursive: true }); }
17
+
18
+ console.log('=> build-opencode');
19
+ ensure(OUT);
20
+
21
+ const manifest = YAML.parse(fs.readFileSync(path.join(ROOT, 'agent.yaml'), 'utf8'));
22
+ const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8'));
23
+
24
+ // agents 모으기
25
+ const agents = [];
26
+ const agentsDir = path.join(ROOT, 'agents');
27
+ if (fs.existsSync(agentsDir)) {
28
+ for (const f of fs.readdirSync(agentsDir)) {
29
+ if (!f.endsWith('.md')) continue;
30
+ const content = fs.readFileSync(path.join(agentsDir, f), 'utf8');
31
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
32
+ if (!fmMatch) continue;
33
+ const fm = YAML.parse(fmMatch[1]);
34
+ const body = content.slice(fmMatch[0].length).trim();
35
+ agents.push({
36
+ name: fm.name,
37
+ description: fm.description || '',
38
+ provider: fm.provider || 'unknown',
39
+ model: fm.model || 'unknown',
40
+ sandbox: fm.sandbox || 'read-only',
41
+ tools_disallowed: fm.disallowedTools || [],
42
+ hand_off_to: fm.hand_off_to || [],
43
+ fact_forcing: !!fm.fact_forcing,
44
+ prompt: body,
45
+ });
46
+ }
47
+ }
48
+
49
+ // skills 모으기 (description 만 — progressive disclosure)
50
+ const skills = [];
51
+ const skillsDir = path.join(ROOT, 'skills');
52
+ if (fs.existsSync(skillsDir)) {
53
+ for (const e of fs.readdirSync(skillsDir, { withFileTypes: true })) {
54
+ if (!e.isDirectory()) continue;
55
+ const file = path.join(skillsDir, e.name, 'SKILL.md');
56
+ if (!fs.existsSync(file)) continue;
57
+ const content = fs.readFileSync(file, 'utf8');
58
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
59
+ if (!fmMatch) continue;
60
+ const fm = YAML.parse(fmMatch[1]);
61
+ skills.push({
62
+ name: fm.name || e.name,
63
+ description: fm.description || '',
64
+ path: `skills/${e.name}/SKILL.md`,
65
+ });
66
+ }
67
+ }
68
+
69
+ // hooks 그대로 변환
70
+ let hooks = null;
71
+ const hooksFile = path.join(ROOT, 'hooks', 'hooks.json');
72
+ if (fs.existsSync(hooksFile)) {
73
+ hooks = JSON.parse(fs.readFileSync(hooksFile, 'utf8'));
74
+ }
75
+
76
+ // MCP servers
77
+ const mcpServers = (manifest.mcp?.external_servers || []).map(s => ({
78
+ name: s.name,
79
+ ...(s.pin ? { command: 'npx', args: ['-y', s.pin] } : {}),
80
+ ...(s.url ? { url: s.url, type: 'http' } : {}),
81
+ }));
82
+
83
+ // 단일 config.json
84
+ const config = {
85
+ $schema: 'https://opencode.dev/schemas/config.schema.json',
86
+ name: 'harness',
87
+ version: pkg.version,
88
+ description: pkg.description,
89
+ agents,
90
+ skills,
91
+ hooks: hooks || null,
92
+ mcp: { servers: mcpServers },
93
+ profiles: manifest.profiles || null,
94
+ security: manifest.security || null,
95
+ routing: manifest.routing || null,
96
+ };
97
+
98
+ fs.writeFileSync(path.join(OUT, 'config.json'), JSON.stringify(config, null, 2));
99
+ console.log(` config.json: OK (agents=${agents.length}, skills=${skills.length}, mcp=${mcpServers.length})`);
100
+
101
+ // hooks/scripts/ → .opencode/hooks/ (실 스크립트 파일 필요)
102
+ if (fs.existsSync(path.join(ROOT, 'hooks', 'scripts'))) {
103
+ ensure(path.join(OUT, 'hooks'));
104
+ for (const f of fs.readdirSync(path.join(ROOT, 'hooks', 'scripts'))) {
105
+ fs.copyFileSync(path.join(ROOT, 'hooks', 'scripts', f), path.join(OUT, 'hooks', f));
106
+ }
107
+ console.log(` hooks/ : ${fs.readdirSync(path.join(OUT, 'hooks')).length} files`);
108
+ }
109
+
110
+ // 거버넌스 마크다운 (요약)
111
+ for (const f of ['AGENTS.md', 'RULES.md', 'SOUL.md']) {
112
+ const src = path.join(ROOT, f);
113
+ if (fs.existsSync(src)) fs.copyFileSync(src, path.join(OUT, f));
114
+ }
115
+ console.log(' governance : OK');
116
+
117
+ console.log('=> .opencode 빌드 완료');
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ // 정규 카탈로그 무결성 체크. agent.yaml 의 agents/skills/commands 가
3
+ // 실제 파일과 일치하는지, 모듈이 누락 없이 컴포넌트를 참조하는지.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import YAML from 'yaml';
9
+ import { validateProfileSafety } from '../lib/profile-safety.js';
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const ROOT = path.resolve(__dirname, '..', '..');
13
+
14
+ const errors = [];
15
+ const warnings = [];
16
+
17
+ function exists(rel) {
18
+ return fs.existsSync(path.join(ROOT, rel));
19
+ }
20
+
21
+ const manifest = YAML.parse(fs.readFileSync(path.join(ROOT, 'agent.yaml'), 'utf8'));
22
+
23
+ // 1. agents/<name>.md 가 모두 존재하는가?
24
+ for (const a of manifest.agents || []) {
25
+ if (!exists(`agents/${a}.md`)) {
26
+ warnings.push(`agent file missing: agents/${a}.md`);
27
+ }
28
+ }
29
+
30
+ // 2. skills/<name>/SKILL.md 존재?
31
+ for (const s of manifest.skills || []) {
32
+ if (!exists(`skills/${s}/SKILL.md`)) {
33
+ warnings.push(`skill file missing: skills/${s}/SKILL.md`);
34
+ }
35
+ }
36
+
37
+ // 3. commands/<name>.md 존재?
38
+ for (const c of manifest.commands || []) {
39
+ if (!exists(`commands/${c}.md`)) {
40
+ warnings.push(`command file missing: commands/${c}.md`);
41
+ }
42
+ }
43
+
44
+ // 4. hooks/hooks.json 존재?
45
+ if (manifest.hooks?.file && !exists(manifest.hooks.file)) {
46
+ warnings.push(`hooks file missing: ${manifest.hooks.file}`);
47
+ }
48
+
49
+ // 5. 모듈 ↔ 컴포넌트 일관성
50
+ const modules = JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests/install-modules.json'), 'utf8'));
51
+ const components = JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests/install-components.json'), 'utf8'));
52
+ const profiles = JSON.parse(fs.readFileSync(path.join(ROOT, 'manifests/install-profiles.json'), 'utf8'));
53
+
54
+ for (const [mid, m] of Object.entries(modules.modules)) {
55
+ for (const cid of m.components) {
56
+ if (!components.components[cid]) {
57
+ errors.push(`module "${mid}" references missing component: ${cid}`);
58
+ }
59
+ }
60
+ }
61
+
62
+ // 6. 프로파일 ↔ 모듈 일관성
63
+ for (const [pid, p] of Object.entries(profiles.profiles)) {
64
+ for (const mid of p.modules) {
65
+ if (!modules.modules[mid]) {
66
+ errors.push(`profile "${pid}" references missing module: ${mid}`);
67
+ }
68
+ }
69
+ }
70
+
71
+ const profileSafety = validateProfileSafety(profiles);
72
+ errors.push(...profileSafety.errors);
73
+ warnings.push(...profileSafety.warnings);
74
+
75
+ // 7. 매니페스트 modules 와 install-modules 동기화
76
+ const manifestModules = new Set(manifest.modules || []);
77
+ const definedModules = new Set(Object.keys(modules.modules));
78
+ for (const m of manifestModules) {
79
+ if (!definedModules.has(m)) errors.push(`agent.yaml lists undefined module: ${m}`);
80
+ }
81
+
82
+ // 출력
83
+ function color(s, c) {
84
+ if (!process.stdout.isTTY) return s;
85
+ const codes = { red: 31, yellow: 33, green: 32, bold: 1 };
86
+ return `\x1b[${codes[c]}m${s}\x1b[0m`;
87
+ }
88
+
89
+ console.log('');
90
+ console.log(color('HARNESS catalog integrity check', 'bold'));
91
+ console.log('');
92
+
93
+ if (warnings.length) {
94
+ console.log(color(`경고 (${warnings.length}):`, 'yellow'));
95
+ for (const w of warnings) console.log(' - ' + w);
96
+ console.log('');
97
+ }
98
+
99
+ if (errors.length) {
100
+ console.log(color(`오류 (${errors.length}):`, 'red'));
101
+ for (const e of errors) console.log(' - ' + e);
102
+ console.log('');
103
+ process.exit(1);
104
+ }
105
+
106
+ console.log(color('통과', 'green'));
107
+ console.log(` agents 선언 ${manifest.agents?.length || 0}, 파일 ${countExisting('agents', manifest.agents, '.md')}`);
108
+ console.log(` skills 선언 ${manifest.skills?.length || 0}, 파일 ${countExistingDir('skills', manifest.skills, 'SKILL.md')}`);
109
+ console.log(` commands 선언 ${manifest.commands?.length || 0}, 파일 ${countExisting('commands', manifest.commands, '.md')}`);
110
+ console.log(` modules ${Object.keys(modules.modules).length}`);
111
+ console.log(` components ${Object.keys(components.components).length}`);
112
+ console.log(` profiles ${Object.keys(profiles.profiles).length}`);
113
+ console.log('');
114
+
115
+ function countExisting(dir, list, ext) {
116
+ return (list || []).filter(n => exists(`${dir}/${n}${ext}`)).length;
117
+ }
118
+ function countExistingDir(dir, list, file) {
119
+ return (list || []).filter(n => exists(`${dir}/${n}/${file}`)).length;
120
+ }
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ // HARNESS:START / HARNESS:END 마커 무결성 검증.
3
+ // 사용자 작성 영역과 자동 갱신 영역 사이가 짝지어져 있는지.
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const ROOT = path.resolve(__dirname, '..', '..');
11
+
12
+ // 마커 사용 옵트인 파일 (자동 갱신 영역을 갖는 문서만 등록).
13
+ // AGENTS.md 는 정전 풀 수동 문서이므로 미포함.
14
+ const FILES = ['CLAUDE.md'];
15
+ const START = /<!--\s*HARNESS:START(?:\s+version=\S+)?\s*-->/;
16
+ const END = /<!--\s*HARNESS:END\s*-->/;
17
+
18
+ let ok = true;
19
+
20
+ for (const f of FILES) {
21
+ const p = path.join(ROOT, f);
22
+ if (!fs.existsSync(p)) {
23
+ console.error(`[SKIP] ${f} 없음`);
24
+ continue;
25
+ }
26
+ const content = fs.readFileSync(p, 'utf8');
27
+ const startIdx = content.search(START);
28
+ const endIdx = content.search(END);
29
+
30
+ if (startIdx === -1 && endIdx === -1) {
31
+ console.error(`[WARN] ${f}: 마커 없음 (자동 갱신 영역 없음)`);
32
+ continue;
33
+ }
34
+ if (startIdx === -1 || endIdx === -1) {
35
+ console.error(`[FAIL] ${f}: 마커가 짝이 안 맞음`);
36
+ ok = false;
37
+ continue;
38
+ }
39
+ if (startIdx > endIdx) {
40
+ console.error(`[FAIL] ${f}: HARNESS:END 가 HARNESS:START 앞에 있음`);
41
+ ok = false;
42
+ continue;
43
+ }
44
+ const body = content.substring(startIdx, endIdx);
45
+ console.error(`[OK] ${f}: 자동 영역 ${body.length} bytes`);
46
+ }
47
+
48
+ if (!ok) process.exit(1);