@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,76 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>NEKOWORK Trading Dashboard Mock</title>
7
+ <link rel="stylesheet" href="src/styles.css">
8
+ </head>
9
+ <body>
10
+ <main class="shell">
11
+ <header class="topbar">
12
+ <div>
13
+ <p class="eyebrow">Mock-only workspace</p>
14
+ <h1>Trading Dashboard</h1>
15
+ </div>
16
+ <div class="status" aria-label="Connection status">
17
+ <span class="status-dot"></span>
18
+ No broker connected
19
+ </div>
20
+ </header>
21
+
22
+ <section class="notice" aria-label="Safety notice">
23
+ Demo data only. Buy, sell, payment, broker, and account actions are disabled in this mock project.
24
+ </section>
25
+
26
+ <section class="grid">
27
+ <article class="panel market-panel">
28
+ <div class="panel-header">
29
+ <div>
30
+ <p class="eyebrow">Portfolio</p>
31
+ <h2>$124,820.44</h2>
32
+ </div>
33
+ <span class="gain">+2.8%</span>
34
+ </div>
35
+ <canvas id="portfolioChart" width="720" height="320" aria-label="Portfolio performance chart"></canvas>
36
+ </article>
37
+
38
+ <article class="panel">
39
+ <div class="panel-header compact">
40
+ <div>
41
+ <p class="eyebrow">Watchlist</p>
42
+ <h2>Mock Symbols</h2>
43
+ </div>
44
+ </div>
45
+ <div id="watchlist" class="watchlist"></div>
46
+ </article>
47
+
48
+ <article class="panel">
49
+ <div class="panel-header compact">
50
+ <div>
51
+ <p class="eyebrow">Order Ticket</p>
52
+ <h2>Disabled</h2>
53
+ </div>
54
+ </div>
55
+ <form class="ticket" aria-label="Disabled mock order ticket">
56
+ <label>
57
+ Symbol
58
+ <input value="NKO" disabled>
59
+ </label>
60
+ <label>
61
+ Quantity
62
+ <input value="25" disabled>
63
+ </label>
64
+ <div class="segmented" aria-label="Disabled side selector">
65
+ <button type="button" disabled>Buy</button>
66
+ <button type="button" disabled>Sell</button>
67
+ </div>
68
+ <button class="primary" type="button" disabled>Mock only</button>
69
+ </form>
70
+ </article>
71
+ </section>
72
+ </main>
73
+
74
+ <script type="module" src="src/app.js"></script>
75
+ </body>
76
+ </html>
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "nekowork-trading-dashboard-mock",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "test": "node scripts/check.mjs"
8
+ }
9
+ }
@@ -0,0 +1,54 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+
5
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
6
+ const files = [
7
+ 'index.html',
8
+ 'src/app.js',
9
+ 'src/styles.css',
10
+ 'fixtures/market.json',
11
+ ];
12
+
13
+ for (const file of files) {
14
+ const full = path.join(root, file);
15
+ if (!fs.existsSync(full)) throw new Error(`missing required file: ${file}`);
16
+ }
17
+
18
+ const combined = files
19
+ .map(file => fs.readFileSync(path.join(root, file), 'utf8'))
20
+ .join('\n')
21
+ .toLowerCase();
22
+
23
+ const forbidden = [
24
+ 'fetch(',
25
+ 'xmlhttprequest',
26
+ 'websocket',
27
+ 'broker-sdk',
28
+ 'stripe',
29
+ 'paypal',
30
+ 'alpaca',
31
+ 'interactivebrokers',
32
+ 'binance',
33
+ 'coinbase',
34
+ ];
35
+
36
+ for (const token of forbidden) {
37
+ if (combined.includes(token)) {
38
+ throw new Error(`mock project must not include outbound or real-money wiring: ${token}`);
39
+ }
40
+ }
41
+
42
+ const html = fs.readFileSync(path.join(root, 'index.html'), 'utf8');
43
+ if (!/disabled/.test(html)) throw new Error('order ticket must keep controls disabled');
44
+ if (!/Demo data only/.test(html)) throw new Error('safety notice must be visible');
45
+
46
+ const fixture = JSON.parse(fs.readFileSync(path.join(root, 'fixtures/market.json'), 'utf8'));
47
+ if (!Array.isArray(fixture.portfolio) || fixture.portfolio.length < 6) {
48
+ throw new Error('portfolio fixture must include chart data');
49
+ }
50
+ if (!Array.isArray(fixture.symbols) || fixture.symbols.length < 3) {
51
+ throw new Error('symbol fixture must include watchlist data');
52
+ }
53
+
54
+ console.log('trading-dashboard-mock checks passed');
@@ -0,0 +1,83 @@
1
+ const market = {
2
+ portfolio: [118.2, 119.4, 121.1, 120.3, 122.6, 124.0, 123.2, 125.7, 127.4, 126.9, 129.1, 131.3],
3
+ symbols: [
4
+ { symbol: 'NKO', name: 'Neko Systems', price: 142.35, change: 2.41 },
5
+ { symbol: 'VRFY', name: 'Verify Labs', price: 88.12, change: 1.26 },
6
+ { symbol: 'GATE', name: 'Gatehold Inc.', price: 54.67, change: 0.74 },
7
+ { symbol: 'MOCK', name: 'Mock Market ETF', price: 203.19, change: 3.08 },
8
+ ],
9
+ };
10
+
11
+ const chart = document.querySelector('#portfolioChart');
12
+ const watchlist = document.querySelector('#watchlist');
13
+
14
+ renderWatchlist(market.symbols);
15
+ drawChart(chart, market.portfolio);
16
+
17
+ function renderWatchlist(symbols) {
18
+ watchlist.replaceChildren(...symbols.map((item) => {
19
+ const row = document.createElement('div');
20
+ row.className = 'symbol';
21
+ row.innerHTML = `
22
+ <strong>${item.symbol}</strong>
23
+ <span class="price">$${item.price.toFixed(2)}</span>
24
+ <span>${item.name}</span>
25
+ <span class="delta">+${item.change.toFixed(2)}%</span>
26
+ `;
27
+ return row;
28
+ }));
29
+ }
30
+
31
+ function drawChart(canvas, values) {
32
+ const ctx = canvas.getContext('2d');
33
+ const width = canvas.width;
34
+ const height = canvas.height;
35
+ const pad = 34;
36
+ const min = Math.min(...values);
37
+ const max = Math.max(...values);
38
+ const xStep = (width - pad * 2) / (values.length - 1);
39
+ const yScale = (height - pad * 2) / (max - min);
40
+
41
+ ctx.clearRect(0, 0, width, height);
42
+ ctx.strokeStyle = '#d9e0ea';
43
+ ctx.lineWidth = 1;
44
+ for (let i = 0; i < 5; i += 1) {
45
+ const y = pad + ((height - pad * 2) / 4) * i;
46
+ ctx.beginPath();
47
+ ctx.moveTo(pad, y);
48
+ ctx.lineTo(width - pad, y);
49
+ ctx.stroke();
50
+ }
51
+
52
+ const points = values.map((value, index) => ({
53
+ x: pad + xStep * index,
54
+ y: height - pad - (value - min) * yScale,
55
+ }));
56
+
57
+ const gradient = ctx.createLinearGradient(0, pad, 0, height - pad);
58
+ gradient.addColorStop(0, 'rgba(21, 127, 116, 0.28)');
59
+ gradient.addColorStop(1, 'rgba(21, 127, 116, 0.02)');
60
+
61
+ ctx.beginPath();
62
+ ctx.moveTo(points[0].x, height - pad);
63
+ for (const point of points) ctx.lineTo(point.x, point.y);
64
+ ctx.lineTo(points.at(-1).x, height - pad);
65
+ ctx.closePath();
66
+ ctx.fillStyle = gradient;
67
+ ctx.fill();
68
+
69
+ ctx.beginPath();
70
+ points.forEach((point, index) => {
71
+ if (index === 0) ctx.moveTo(point.x, point.y);
72
+ else ctx.lineTo(point.x, point.y);
73
+ });
74
+ ctx.strokeStyle = '#157f74';
75
+ ctx.lineWidth = 4;
76
+ ctx.lineJoin = 'round';
77
+ ctx.lineCap = 'round';
78
+ ctx.stroke();
79
+
80
+ ctx.fillStyle = '#17202a';
81
+ ctx.font = '700 18px system-ui';
82
+ ctx.fillText('Mock portfolio trend', pad, pad - 10);
83
+ }
@@ -0,0 +1,227 @@
1
+ :root {
2
+ color-scheme: light;
3
+ --bg: #f6f8fb;
4
+ --text: #17202a;
5
+ --muted: #657386;
6
+ --panel: #ffffff;
7
+ --line: #d9e0ea;
8
+ --accent: #157f74;
9
+ --accent-2: #3867d6;
10
+ --warn: #a85f00;
11
+ --shadow: 0 16px 40px rgba(27, 39, 51, 0.08);
12
+ }
13
+
14
+ * {
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ margin: 0;
20
+ min-height: 100vh;
21
+ background: var(--bg);
22
+ color: var(--text);
23
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
24
+ }
25
+
26
+ .shell {
27
+ width: min(1180px, calc(100% - 32px));
28
+ margin: 0 auto;
29
+ padding: 28px 0;
30
+ }
31
+
32
+ .topbar {
33
+ display: flex;
34
+ align-items: center;
35
+ justify-content: space-between;
36
+ gap: 18px;
37
+ margin-bottom: 18px;
38
+ }
39
+
40
+ .eyebrow {
41
+ margin: 0 0 6px;
42
+ color: var(--muted);
43
+ font-size: 0.78rem;
44
+ font-weight: 700;
45
+ text-transform: uppercase;
46
+ }
47
+
48
+ h1,
49
+ h2 {
50
+ margin: 0;
51
+ letter-spacing: 0;
52
+ }
53
+
54
+ h1 {
55
+ font-size: clamp(2rem, 4vw, 3.4rem);
56
+ }
57
+
58
+ h2 {
59
+ font-size: 1.25rem;
60
+ }
61
+
62
+ .status,
63
+ .notice,
64
+ .gain {
65
+ border: 1px solid var(--line);
66
+ border-radius: 8px;
67
+ background: var(--panel);
68
+ }
69
+
70
+ .status {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ gap: 9px;
74
+ padding: 10px 12px;
75
+ color: var(--muted);
76
+ font-weight: 700;
77
+ white-space: nowrap;
78
+ }
79
+
80
+ .status-dot {
81
+ width: 10px;
82
+ height: 10px;
83
+ border-radius: 999px;
84
+ background: var(--warn);
85
+ }
86
+
87
+ .notice {
88
+ margin-bottom: 18px;
89
+ padding: 13px 15px;
90
+ color: #5d3a00;
91
+ background: #fff8ea;
92
+ }
93
+
94
+ .grid {
95
+ display: grid;
96
+ grid-template-columns: minmax(0, 1.6fr) minmax(260px, 0.8fr);
97
+ gap: 16px;
98
+ }
99
+
100
+ .panel {
101
+ border: 1px solid var(--line);
102
+ border-radius: 8px;
103
+ padding: 18px;
104
+ background: var(--panel);
105
+ box-shadow: var(--shadow);
106
+ }
107
+
108
+ .market-panel {
109
+ grid-row: span 2;
110
+ }
111
+
112
+ .panel-header {
113
+ display: flex;
114
+ align-items: start;
115
+ justify-content: space-between;
116
+ gap: 14px;
117
+ margin-bottom: 18px;
118
+ }
119
+
120
+ .panel-header.compact {
121
+ margin-bottom: 14px;
122
+ }
123
+
124
+ .gain {
125
+ padding: 8px 10px;
126
+ color: var(--accent);
127
+ font-weight: 800;
128
+ background: #ebfaf5;
129
+ }
130
+
131
+ canvas {
132
+ display: block;
133
+ width: 100%;
134
+ height: auto;
135
+ aspect-ratio: 16 / 7;
136
+ border-radius: 8px;
137
+ background: #f9fbfe;
138
+ }
139
+
140
+ .watchlist {
141
+ display: grid;
142
+ gap: 10px;
143
+ }
144
+
145
+ .symbol {
146
+ display: grid;
147
+ grid-template-columns: 1fr auto;
148
+ gap: 4px 10px;
149
+ padding: 12px;
150
+ border: 1px solid var(--line);
151
+ border-radius: 8px;
152
+ }
153
+
154
+ .symbol strong,
155
+ .price {
156
+ font-size: 0.95rem;
157
+ }
158
+
159
+ .symbol span {
160
+ color: var(--muted);
161
+ font-size: 0.82rem;
162
+ }
163
+
164
+ .delta {
165
+ color: var(--accent);
166
+ font-weight: 800;
167
+ }
168
+
169
+ .ticket {
170
+ display: grid;
171
+ gap: 12px;
172
+ }
173
+
174
+ label {
175
+ display: grid;
176
+ gap: 6px;
177
+ color: var(--muted);
178
+ font-weight: 700;
179
+ }
180
+
181
+ input,
182
+ button {
183
+ min-height: 42px;
184
+ border: 1px solid var(--line);
185
+ border-radius: 8px;
186
+ font: inherit;
187
+ }
188
+
189
+ input {
190
+ padding: 0 12px;
191
+ color: var(--muted);
192
+ background: #f4f6f9;
193
+ }
194
+
195
+ .segmented {
196
+ display: grid;
197
+ grid-template-columns: 1fr 1fr;
198
+ gap: 8px;
199
+ }
200
+
201
+ button {
202
+ color: var(--muted);
203
+ background: #eef2f7;
204
+ font-weight: 800;
205
+ }
206
+
207
+ .primary {
208
+ color: #ffffff;
209
+ background: var(--accent-2);
210
+ opacity: 0.55;
211
+ }
212
+
213
+ @media (max-width: 780px) {
214
+ .topbar,
215
+ .panel-header {
216
+ align-items: stretch;
217
+ flex-direction: column;
218
+ }
219
+
220
+ .grid {
221
+ grid-template-columns: 1fr;
222
+ }
223
+
224
+ .market-panel {
225
+ grid-row: auto;
226
+ }
227
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "../schemas/hooks.schema.json",
3
+ "version": "0.0.1",
4
+ "PreToolUse": [
5
+ {
6
+ "matcher": "Bash",
7
+ "hook": "scripts/pre-bash-dispatcher.js",
8
+ "timeout_ms": 5000,
9
+ "env_toggle": "HARNESS_HOOK_PRE_BASH",
10
+ "comment": "block-no-verify, dev-server-block, commit-quality, push-reminder"
11
+ },
12
+ {
13
+ "matcher": "Edit|Write",
14
+ "hook": "scripts/gateguard-fact-force.js",
15
+ "timeout_ms": 8000,
16
+ "env_toggle": "HARNESS_HOOK_GATEGUARD",
17
+ "comment": "Edit/Write 직전 importer/API/schema 사실 조사 강제"
18
+ },
19
+ {
20
+ "matcher": "Edit|Write",
21
+ "hook": "scripts/config-protection.js",
22
+ "timeout_ms": 1000,
23
+ "env_toggle": "HARNESS_HOOK_CONFIG_PROTECTION",
24
+ "comment": ".env / *.pem / secret 파일 접근 차단"
25
+ }
26
+ ],
27
+ "PostToolUse": [
28
+ {
29
+ "matcher": "Edit|Write",
30
+ "hook": "scripts/quality-gate.js",
31
+ "timeout_ms": 30000,
32
+ "env_toggle": "HARNESS_HOOK_QUALITY_GATE",
33
+ "comment": "포맷·린트·타입체크. 실패 시 다음 도구 호출 차단"
34
+ }
35
+ ],
36
+ "Stop": [
37
+ {
38
+ "hook": "scripts/persistent-mode.mjs",
39
+ "timeout_ms": 5000,
40
+ "env_toggle": "HARNESS_HOOK_PERSISTENT_MODE",
41
+ "comment": "active 모드 검사, 영속 재개 신호 drop"
42
+ }
43
+ ]
44
+ }
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse(Edit|Write) config-protection.
3
+ // .env / *.pem / *.key / credentials 등 시크릿 파일 직접 편집 차단.
4
+
5
+ import fs from 'node:fs';
6
+
7
+ if (process.env.HARNESS_HOOK_CONFIG_PROTECTION === '0') process.exit(0);
8
+
9
+ let input = '';
10
+ try { input = fs.readFileSync(0, 'utf8'); } catch { /* TTY */ }
11
+ let payload;
12
+ try { payload = JSON.parse(input); } catch { payload = {}; }
13
+
14
+ const targetPath = String(payload?.tool_input?.file_path ?? payload?.tool_input?.path ?? '');
15
+ if (!targetPath) process.exit(0);
16
+
17
+ const PROTECTED = [
18
+ /(^|[\\\/])\.env(\..+)?$/,
19
+ /\.(pem|key|crt|p12|pfx)$/i,
20
+ /(^|[\\\/])credentials([._-]|$)/i,
21
+ /id_rsa(\.pub)?$/,
22
+ /\.aws[\\\/]credentials/i,
23
+ /\.kube[\\\/]config/i,
24
+ ];
25
+
26
+ for (const re of PROTECTED) {
27
+ if (re.test(targetPath)) {
28
+ process.stderr.write(`[config-protection] 차단: ${targetPath}\n`);
29
+ process.stderr.write(' 사용자 룰 위반: 시크릿 / 인증 파일 직접 편집 금지.\n');
30
+ process.exit(2);
31
+ }
32
+ }
33
+
34
+ process.exit(0);
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ // PreToolUse(Edit|Write) gateguard-fact-force.
3
+ // "Are you sure?" 자기평가는 무력. importer / public API / schema 사실을 강제로 디스크에 남긴다.
4
+ //
5
+ // 동작:
6
+ // 1. tool_input.file_path 가 코드 파일인가? 아니면 통과.
7
+ // 2. 사실 노트 파일 .harness/state/sessions/<id>/facts/<encoded>.md 존재?
8
+ // - 존재 + 답변 영역 비어있지 않음 → 통과 (이 세션에서 이미 조사함).
9
+ // - 존재 + 답변 영역 비어있음 → 차단 (질문에 답한 후 다시 시도).
10
+ // - 미존재 → 사실 노트 새로 생성 + 차단 (답한 후 재시도).
11
+ // 3. importer / public API 후보를 정적으로 추출해 노트에 미리 박는다 (TS/JS 만).
12
+
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+
16
+ if (process.env.HARNESS_HOOK_GATEGUARD === '0') process.exit(0);
17
+
18
+ let input = '';
19
+ try { input = fs.readFileSync(0, 'utf8'); } catch { /* TTY */ }
20
+ let payload;
21
+ try { payload = JSON.parse(input); } catch { payload = {}; }
22
+
23
+ const targetPath = String(payload?.tool_input?.file_path ?? payload?.tool_input?.path ?? '').trim();
24
+ if (!targetPath) process.exit(0);
25
+
26
+ const ext = path.extname(targetPath);
27
+ const isCode = ['.ts', '.tsx', '.js', '.mjs', '.cjs', '.py', '.go', '.rs', '.java', '.kt'].includes(ext);
28
+ if (!isCode) process.exit(0);
29
+
30
+ const sessionId = process.env.HARNESS_SESSION_ID || 'default';
31
+ const root = process.env.HARNESS_ROOT || process.cwd();
32
+ const factsDir = path.join(root, '.harness', 'state', 'sessions', sessionId, 'facts');
33
+ fs.mkdirSync(factsDir, { recursive: true });
34
+
35
+ const safeFileName = targetPath.replace(/[\\\/:*?"<>|]/g, '_');
36
+ const factFile = path.join(factsDir, safeFileName + '.md');
37
+
38
+ // 이미 답변된 사실 노트가 있는가?
39
+ if (fs.existsSync(factFile)) {
40
+ const existing = fs.readFileSync(factFile, 'utf8');
41
+ const answered = /## 답변\s*\n[^\n#]*\S/.test(existing);
42
+ if (answered) process.exit(0);
43
+ process.stderr.write(`[gateguard] 차단: 사실 노트가 비어있음. ${factFile}\n`);
44
+ process.stderr.write(`[gateguard] '## 답변' 섹션을 채운 후 다시 시도.\n`);
45
+ process.exit(2);
46
+ }
47
+
48
+ // importer / API / schema 후보 추출 (TS/JS 만, 정적 패턴 매칭)
49
+ const facts = inspectFile(targetPath, root);
50
+
51
+ const note = `# Facts: ${targetPath}
52
+
53
+ > gateguard-fact-force 가 자동 생성. self-evaluation ("are you sure?") 은 무력하다. 다음 3개 질문에 답한 후 Edit / Write 를 진행하라.
54
+
55
+ ## 1. Importers (이 파일을 참조하는 다른 파일)
56
+
57
+ 추출된 후보 (정적 검색):
58
+ ${facts.importers.length ? facts.importers.map(i => `- ${i}`).join('\n') : '- (없음)'}
59
+
60
+ ## 2. Public API (export 시그니처)
61
+
62
+ 추출된 후보:
63
+ ${facts.exports.length ? facts.exports.map(e => `- \`${e}\``).join('\n') : '- (없음)'}
64
+
65
+ ## 3. Schema / 데이터 (DB / API / config 영향)
66
+
67
+ 추출된 후보:
68
+ ${facts.schemas.length ? facts.schemas.map(s => `- ${s}`).join('\n') : '- (자동 추출 항목 없음 — 직접 확인 필요)'}
69
+
70
+ ## 답변
71
+
72
+ (이 섹션을 비워두면 다음 Edit / Write 호출이 차단된다. 위 사실을 검토한 후 변경 의도와 영향을 한 단락으로 적는다.)
73
+
74
+
75
+ `;
76
+
77
+ fs.writeFileSync(factFile, note);
78
+ process.stderr.write(`[gateguard] 사실 노트 생성: ${factFile}\n`);
79
+ process.stderr.write(`[gateguard] 차단: '## 답변' 섹션을 채운 후 다시 시도.\n`);
80
+ process.exit(2);
81
+
82
+ // ============================================================
83
+
84
+ function inspectFile(target, root) {
85
+ const result = { importers: [], exports: [], schemas: [] };
86
+
87
+ // public exports 추출 (TS/JS)
88
+ if (fs.existsSync(target)) {
89
+ const code = fs.readFileSync(target, 'utf8');
90
+ if (/\.(ts|tsx|js|mjs|cjs)$/.test(target)) {
91
+ const exportRe = /^\s*export\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+([A-Za-z_$][\w$]*)/gm;
92
+ let m;
93
+ while ((m = exportRe.exec(code))) result.exports.push(m[1]);
94
+ // export { a, b } 형식
95
+ const reexport = /^\s*export\s*\{\s*([^}]+)\}/gm;
96
+ while ((m = reexport.exec(code))) {
97
+ for (const id of m[1].split(',')) result.exports.push(id.trim().split(/\s+as\s+/)[0]);
98
+ }
99
+ }
100
+ if (/\.py$/.test(target)) {
101
+ const py = /^(?:def|class)\s+([A-Za-z_]\w*)/gm;
102
+ let m;
103
+ while ((m = py.exec(code))) result.exports.push(m[1]);
104
+ }
105
+ if (/schema|model|migration|sql/i.test(target)) {
106
+ result.schemas.push('파일 이름이 schema/model/migration 을 시사 — DB 영향 가능');
107
+ }
108
+ }
109
+
110
+ // importers 추출 — 같은 패키지의 src/, scripts/, agents/, hooks/, bridge/ 안에서 grep.
111
+ const baseName = path.basename(target).replace(/\.[^.]+$/, '');
112
+ const searchDirs = ['scripts', 'bridge', 'hooks', 'agents', 'skills'];
113
+ const seen = new Set();
114
+ for (const dir of searchDirs) {
115
+ const full = path.join(root, dir);
116
+ if (!fs.existsSync(full)) continue;
117
+ walk(full, (f) => {
118
+ if (seen.has(f) || f === target) return;
119
+ if (!/\.(ts|tsx|js|mjs|cjs|py)$/.test(f)) return;
120
+ const c = fs.readFileSync(f, 'utf8');
121
+ // import / require / from 형식 + 파일 베이스 이름 매칭
122
+ const re = new RegExp(`(?:import\\s+[^;]+from\\s+|require\\(\\s*|from\\s+)['"][^'"]*${escape(baseName)}[^'"]*['"]`);
123
+ if (re.test(c)) {
124
+ result.importers.push(path.relative(root, f).replace(/\\/g, '/'));
125
+ seen.add(f);
126
+ }
127
+ });
128
+ }
129
+
130
+ // 너무 많으면 자르기
131
+ if (result.importers.length > 20) result.importers = result.importers.slice(0, 20).concat(['... (+more)']);
132
+ if (result.exports.length > 30) result.exports = result.exports.slice(0, 30).concat(['... (+more)']);
133
+
134
+ return result;
135
+ }
136
+
137
+ function escape(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
138
+
139
+ function walk(dir, fn) {
140
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
141
+ if (e.name === 'node_modules' || e.name === '.git' || e.name.startsWith('.')) continue;
142
+ const p = path.join(dir, e.name);
143
+ if (e.isDirectory()) walk(p, fn);
144
+ else fn(p);
145
+ }
146
+ }