From 7cea023dced5a43d46916e574f4f340ae2c04e99 Mon Sep 17 00:00:00 2001 From: thanhnv Date: Fri, 10 Jul 2026 16:26:30 +0900 Subject: [PATCH] feat: chat + optmz control panel --- .gitignore | 1 + .specify/logs/audit/security.jsonl | 10 + .specify/logs/cost/metrics.jsonl | 8 + .specify/logs/level5/fallback.jsonl | 1 + .specify/logs/level5/provider-usage.jsonl | 11 + AGENTS.md | 517 +++++++++++++++++ Dockerfile.control-panel-api | 5 +- docker-compose.control-panel.local.yml | 13 + docs/guides/CASAN_LOCAL_FULL_STACK.md | 203 +++++++ infra/local-prod/casan.local.env.example | 8 + .../backend/src/chat/chat.controller.ts | 9 + .../backend/src/chat/chat.service.ts | 11 + .../backend/test/chat.test.ts | 17 + .../src/components/layout/AppLayout.tsx | 8 +- .../frontend/src/components/layout/Header.tsx | 40 +- .../src/components/layout/Sidebar.tsx | 96 ++- .../frontend/src/components/ui/Card.tsx | 28 +- .../frontend/src/index.css | 26 + .../frontend/src/lib/api.ts | 34 ++ .../frontend/src/pages/Chat.tsx | 545 ++++++++---------- .../frontend/src/pages/Overview.tsx | 87 ++- .../casan-harness/config/model-providers.yaml | 7 + .../scripts/bash/chat-readonly.py | 105 +++- .../casan-harness/scripts/bash/chat-turn.py | 30 +- .../casan-harness/scripts/bash/local-full.sh | 104 ++++ .../casan-harness/scripts/bash/model-call.py | 101 +++- .../tests/phase-chat-model-synthesis-tests.sh | 33 +- .../tests/phase3-model-router-tests.sh | 46 +- 28 files changed, 1702 insertions(+), 402 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/guides/CASAN_LOCAL_FULL_STACK.md create mode 100644 infra/local-prod/casan.local.env.example create mode 100755 packages/casan-harness/scripts/bash/local-full.sh diff --git a/.gitignore b/.gitignore index 03b7188..da29450 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ frontend/node_modules/.vite/ .env .env.* !.env.example +infra/local-prod/casan.local.env backend/.env frontend/.env **/.env diff --git a/.specify/logs/audit/security.jsonl b/.specify/logs/audit/security.jsonl index ab92746..8234e22 100644 --- a/.specify/logs/audit/security.jsonl +++ b/.specify/logs/audit/security.jsonl @@ -82,3 +82,13 @@ {"timestamp":"2026-07-08T09:53:30Z","trace_id":"412d7566-4200-42a8-8772-b88bf395c1f1","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"427aa6ae245aa0f05cc75c0aea61de8129aae1e0e589d5727873d4d7f9baf872","output_hash":"427aa6ae245aa0f05cc75c0aea61de8129aae1e0e589d5727873d4d7f9baf872"} {"timestamp":"2026-07-08T09:53:32Z","trace_id":"506f4e4f-88f0-4a76-9561-b93acd65b026","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"427aa6ae245aa0f05cc75c0aea61de8129aae1e0e589d5727873d4d7f9baf872","output_hash":"427aa6ae245aa0f05cc75c0aea61de8129aae1e0e589d5727873d4d7f9baf872"} {"timestamp":"2026-07-08T09:53:33Z","trace_id":"4c96ed73-034e-4d15-b99b-12ef890372b0","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"427aa6ae245aa0f05cc75c0aea61de8129aae1e0e589d5727873d4d7f9baf872","output_hash":"427aa6ae245aa0f05cc75c0aea61de8129aae1e0e589d5727873d4d7f9baf872"} +{"timestamp":"2026-07-10T06:11:24Z","trace_id":"trace-1783663884-37","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab","output_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab"} +{"timestamp":"2026-07-10T06:12:28Z","trace_id":"trace-1783663948-37","harness":"H4-security","mode":"input","status":"blocked","action":"block","risk_level":"high","input_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab","output_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab"} +{"timestamp":"2026-07-10T06:13:42Z","trace_id":"trace-1783664022-37","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab","output_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab"} +{"timestamp":"2026-07-10T06:14:39Z","trace_id":"trace-1783664079-613","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"e756b35ca2e4c3326cada62e0a6d4b3d714c5afb0caab2268c86c63207228ed2","output_hash":"e756b35ca2e4c3326cada62e0a6d4b3d714c5afb0caab2268c86c63207228ed2"} +{"timestamp":"2026-07-10T06:18:36Z","trace_id":"trace-1783664316-37","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab","output_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab"} +{"timestamp":"2026-07-10T06:19:34Z","trace_id":"trace-1783664374-636","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"142404dfd1bd2e31f6bef54bb495685e059a9b5c4b2e3369c8b79f8c7bec67a9","output_hash":"142404dfd1bd2e31f6bef54bb495685e059a9b5c4b2e3369c8b79f8c7bec67a9"} +{"timestamp":"2026-07-10T06:19:37Z","trace_id":"trace-1783664377-747","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab","output_hash":"5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab"} +{"timestamp":"2026-07-10T06:20:20Z","trace_id":"trace-1783664420-1347","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"d1c3c3a7ee70994d948443ba05bb0a9c2dd1f1852a994ab80d722730d282ae44","output_hash":"d1c3c3a7ee70994d948443ba05bb0a9c2dd1f1852a994ab80d722730d282ae44"} +{"timestamp":"2026-07-10T07:20:30Z","trace_id":"trace-1783668030-52","harness":"H4-security","mode":"input","status":"pass","action":"allow","risk_level":"low","input_hash":"8aaecd06d85c7f743d3545e7b865f3053c647591f95919c1c0b003046d4757aa","output_hash":"8aaecd06d85c7f743d3545e7b865f3053c647591f95919c1c0b003046d4757aa"} +{"timestamp":"2026-07-10T07:21:21Z","trace_id":"trace-1783668081-716","harness":"H4-security","mode":"output","status":"pass","action":"allow","risk_level":"low","input_hash":"280cadd0be725e6496a8614b12a98836bedb5c8a38fc6b2a8afe5f44b237158e","output_hash":"280cadd0be725e6496a8614b12a98836bedb5c8a38fc6b2a8afe5f44b237158e"} diff --git a/.specify/logs/cost/metrics.jsonl b/.specify/logs/cost/metrics.jsonl index 120324a..5be67a8 100644 --- a/.specify/logs/cost/metrics.jsonl +++ b/.specify/logs/cost/metrics.jsonl @@ -15,3 +15,11 @@ {"timestamp":"2026-07-08T09:28:30Z","trace_id":"7622fc43-9b84-4216-a704-96384d9b9375","harness":"H6-agentops","agent":"unknown-agent","step":"agent_step","status":"success","exit_code":0,"latency_ms":224,"retry_count":0,"input_tokens":3,"output_tokens":3,"total_tokens":6,"cost_estimate":1.2e-05,"cost_source":"word_count_estimate","hallucination_signals":0,"alerts":[],"input_hash":"7df5d60319af2916ff4d99c086c43b3989be3420fffff872e1077a650ca528f4","output_hash":"7df5d60319af2916ff4d99c086c43b3989be3420fffff872e1077a650ca528f4"} {"timestamp":"2026-07-08T09:53:28Z","trace_id":"65ede58f-6400-49c1-b09e-e723035f7251","harness":"H6-agentops","agent":"unknown-agent","step":"agent_step","status":"success","exit_code":0,"latency_ms":219,"retry_count":0,"input_tokens":3,"output_tokens":3,"total_tokens":6,"cost_estimate":1.2e-05,"cost_source":"word_count_estimate","hallucination_signals":0,"alerts":[],"input_hash":"7df5d60319af2916ff4d99c086c43b3989be3420fffff872e1077a650ca528f4","output_hash":"7df5d60319af2916ff4d99c086c43b3989be3420fffff872e1077a650ca528f4"} {"timestamp":"2026-07-08T09:53:32Z","trace_id":"06a26538-e7c3-42c2-a202-fc90cf24924d","harness":"H6-agentops","agent":"unknown-agent","step":"agent_step","status":"success","exit_code":0,"latency_ms":243,"retry_count":0,"input_tokens":3,"output_tokens":3,"total_tokens":6,"cost_estimate":1.2e-05,"cost_source":"word_count_estimate","hallucination_signals":0,"alerts":[],"input_hash":"7df5d60319af2916ff4d99c086c43b3989be3420fffff872e1077a650ca528f4","output_hash":"7df5d60319af2916ff4d99c086c43b3989be3420fffff872e1077a650ca528f4"} +{"timestamp": "2026-07-10T05:42:31Z", "trace_id": "ce743020-b73a-474a-871f-c465fb6eb472", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "failed", "exit_code": 2, "latency_ms": 159, "retry_count": 0, "input_tokens": 5, "output_tokens": 5, "total_tokens": 10, "cost_estimate": 0.0, "cost_source": "readonly_word_count", "synthesis_mode": "deterministic", "hallucination_signals": 0, "alerts": [], "input_hash": "5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab", "output_hash": "4f16989d1b5e09312bbd78611adcdae1219a4b19ec40252a986369155f049f96"} +{"timestamp": "2026-07-10T06:10:20Z", "trace_id": "96b4c003-ecd4-4f2e-b861-8d48f085d645", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "failed", "exit_code": 2, "latency_ms": 141, "retry_count": 0, "input_tokens": 5, "output_tokens": 5, "total_tokens": 10, "cost_estimate": 0.0, "cost_source": "readonly_word_count", "synthesis_mode": "deterministic", "hallucination_signals": 0, "alerts": [], "input_hash": "5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab", "output_hash": "4f16989d1b5e09312bbd78611adcdae1219a4b19ec40252a986369155f049f96"} +{"timestamp": "2026-07-10T06:11:24Z", "trace_id": "c924568f-2816-4ce0-b718-645b0c42733e", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "failed", "exit_code": 2, "latency_ms": 219, "retry_count": 0, "input_tokens": 5, "output_tokens": 5, "total_tokens": 10, "cost_estimate": 0.0, "cost_source": "readonly_word_count", "synthesis_mode": "deterministic", "hallucination_signals": 0, "alerts": [], "input_hash": "5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab", "output_hash": "4f16989d1b5e09312bbd78611adcdae1219a4b19ec40252a986369155f049f96"} +{"timestamp": "2026-07-10T06:12:28Z", "trace_id": "10331f30-ce27-47c6-bc5a-9f9841ff8415", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "failed", "exit_code": 2, "latency_ms": 342, "retry_count": 0, "input_tokens": 5, "output_tokens": 5, "total_tokens": 10, "cost_estimate": 0.0, "cost_source": "readonly_word_count", "synthesis_mode": "deterministic", "hallucination_signals": 0, "alerts": [], "input_hash": "5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab", "output_hash": "4f16989d1b5e09312bbd78611adcdae1219a4b19ec40252a986369155f049f96"} +{"timestamp": "2026-07-10T06:14:39Z", "trace_id": "2e9b93cb-f3a7-4ceb-accd-4e8001562d0a", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "success", "exit_code": 0, "latency_ms": 57161, "retry_count": 0, "input_tokens": 5, "output_tokens": 52, "total_tokens": 57, "cost_estimate": 0.0, "cost_source": "readonly_word_count", "synthesis_mode": "deterministic", "hallucination_signals": 0, "alerts": [], "input_hash": "5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab", "output_hash": "e756b35ca2e4c3326cada62e0a6d4b3d714c5afb0caab2268c86c63207228ed2"} +{"timestamp": "2026-07-10T06:19:34Z", "trace_id": "d8796e51-f1bf-4950-b27d-5327ed69ee12", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "success", "exit_code": 0, "latency_ms": 57864, "retry_count": 0, "input_tokens": 427, "output_tokens": 406, "total_tokens": 833, "cost_estimate": 0.0, "cost_source": "ollama_local_real_tokens", "synthesis_mode": "model", "hallucination_signals": 0, "alerts": [], "input_hash": "5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab", "output_hash": "142404dfd1bd2e31f6bef54bb495685e059a9b5c4b2e3369c8b79f8c7bec67a9"} +{"timestamp": "2026-07-10T06:20:20Z", "trace_id": "b5ad414a-4662-49c5-a024-3f2bb51ada64", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "success", "exit_code": 0, "latency_ms": 43174, "retry_count": 0, "input_tokens": 511, "output_tokens": 288, "total_tokens": 799, "cost_estimate": 0.0, "cost_source": "ollama_local_real_tokens", "synthesis_mode": "model", "hallucination_signals": 0, "alerts": [], "input_hash": "5da5739dfab4b282139a20d7272d0e676331eb09f86f562cc5e6d1f3b668a5ab", "output_hash": "d1c3c3a7ee70994d948443ba05bb0a9c2dd1f1852a994ab80d722730d282ae44"} +{"timestamp": "2026-07-10T07:21:22Z", "trace_id": "1fd5d347-f21c-4370-a60d-30b11fd8c53e", "harness": "H6-agentops", "agent": "chat.ask-casan", "step": "ask-casan-readonly", "status": "success", "exit_code": 0, "latency_ms": 51212, "retry_count": 0, "input_tokens": 362, "output_tokens": 59, "total_tokens": 421, "cost_estimate": 0.0, "cost_source": "ollama_local_real_tokens", "synthesis_mode": "model", "hallucination_signals": 0, "alerts": [], "input_hash": "8aaecd06d85c7f743d3545e7b865f3053c647591f95919c1c0b003046d4757aa", "output_hash": "280cadd0be725e6496a8614b12a98836bedb5c8a38fc6b2a8afe5f44b237158e"} diff --git a/.specify/logs/level5/fallback.jsonl b/.specify/logs/level5/fallback.jsonl index 6ddab32..b9d4615 100644 --- a/.specify/logs/level5/fallback.jsonl +++ b/.specify/logs/level5/fallback.jsonl @@ -1,2 +1,3 @@ {"timestamp":"2026-07-08T09:20:52Z","trace_id":"42b46ba8-3399-440e-aaee-da88dc06bc1c","harness":"L5-model-fallback","primary_exit":9,"route":"fallback","final_exit":0,"output":"/Users/thanhnguyen/Documents/AI/HarnessHkt/Harness_Hakathon/Output_CASAN5_REFINED/docs/output/casan/level5-evidence/10-fallback-output.txt"} {"timestamp":"2026-07-08T09:21:54Z","trace_id":"bc29e2dd-0930-4ded-9cd3-a011b5d24c4d","harness":"L5-model-fallback","primary_exit":1,"route":"fallback","final_exit":0,"output":"/var/folders/zn/qn8sqwzn18g34ddftsxgyz6r0000gn/T/tmp.2HkWTSLEMD/fb.out"} +{"timestamp":"2026-07-10T06:09:41Z","trace_id":"ed6b7bc1-3030-4656-9097-69ba16f47e5e","harness":"L5-model-fallback","primary_exit":2,"route":"fallback","final_exit":0,"output":"/var/folders/zn/qn8sqwzn18g34ddftsxgyz6r0000gn/T/tmp.jBxa5B3jDQ/fb.out"} diff --git a/.specify/logs/level5/provider-usage.jsonl b/.specify/logs/level5/provider-usage.jsonl index d3d0d28..0b56a8e 100644 --- a/.specify/logs/level5/provider-usage.jsonl +++ b/.specify/logs/level5/provider-usage.jsonl @@ -2,3 +2,14 @@ {"timestamp": "2026-07-08T09:20:56Z", "harness": "L5-provider-telemetry", "provider": "sample-provider", "model": "sample-model-large", "run_id": "provider-run-001", "step": "speckit.implement", "input_tokens": 1842, "output_tokens": 936, "total_tokens": 2778, "cost_usd": 0.08334, "latency_ms": 4210, "status": "success"} {"timestamp": "2026-07-08T09:22:30Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "judge", "role": "judge", "input_tokens": 168, "output_tokens": 3, "total_tokens": 171, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 1515, "status": "success"} {"timestamp": "2026-07-08T09:22:36Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "t4-telemetry-test", "role": "classify", "input_tokens": 208, "output_tokens": 2, "total_tokens": 210, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 1417, "status": "success"} +{"timestamp": "2026-07-10T06:09:37Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 72, "output_tokens": 27, "total_tokens": 99, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 9972, "status": "success"} +{"timestamp": "2026-07-10T06:09:40Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 218, "output_tokens": 3, "total_tokens": 221, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 2660, "status": "success"} +{"timestamp": "2026-07-10T06:09:46Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 72, "output_tokens": 29, "total_tokens": 101, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 4034, "status": "success"} +{"timestamp": "2026-07-10T06:13:44Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 2476, "status": "success"} +{"timestamp": "2026-07-10T06:15:04Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 73, "output_tokens": 39, "total_tokens": 112, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 4967, "status": "success"} +{"timestamp": "2026-07-10T06:18:39Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 2503, "status": "success"} +{"timestamp": "2026-07-10T06:19:34Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 427, "output_tokens": 406, "total_tokens": 833, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 41620, "status": "success"} +{"timestamp": "2026-07-10T06:19:38Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 216, "output_tokens": 2, "total_tokens": 218, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 626, "status": "success"} +{"timestamp": "2026-07-10T06:20:20Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 511, "output_tokens": 288, "total_tokens": 799, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 31437, "status": "success"} +{"timestamp": "2026-07-10T07:20:36Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "classify", "role": "classify", "input_tokens": 207, "output_tokens": 2, "total_tokens": 209, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 5868, "status": "success"} +{"timestamp": "2026-07-10T07:21:21Z", "harness": "L5-provider-telemetry", "provider": "ollama", "model": "ornith:9b", "run_id": "adhoc", "step": "generate", "role": "generate", "input_tokens": 362, "output_tokens": 59, "total_tokens": 421, "cost_usd": 0.0, "cost_source": "ollama_local_real_tokens", "latency_ms": 4829, "status": "success"} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..0eed0a0 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,517 @@ +# Codex Instructions for "OKR Web Application" Project + +You are an expert full-stack developer specializing in NestJS and a **master of React with Vite and Tailwind CSS**. Your primary goal is to generate code that is secure, efficient, and **strictly follows this project's architecture and design documents**. Do not invent features or logic. Every piece of code must be traceable to a design specification. + +## 1. The Supreme Rule: Adhere to Architecture & Design + +**Before generating any code, you must understand the project's structure.** + +- **Architecture:** Refer to `docs/technical_architecture.md` for the overall system design, module responsibilities, and technology stack. + +- **Requirements:** Refer to `apps/okr/domain/input/okr-requirement.md` for functional requirements, use cases, and UI mockups. + +- **Your Task:** Your role is to translate these designs into code, not to be creative. + +**Example Check:** + +- **CORRECT:** Logic for key result progress updates belongs in the `key-results` module. + +- **INCORRECT:** Placing key result logic directly within the `objectives` service. + +- **Typescript Types:** Use TypeScript types and interfaces as defined in the design documents. Do not create new types unless explicitly required by the design. + +## 2. Key Technologies & Libraries to Prioritize + +⚠️ **ABSOLUTE LIBRARY RESTRICTION - ZERO TOLERANCE POLICY:** +- **FORBIDDEN:** Installing ANY new libraries beyond those already listed in package.json +- **MANDATORY:** Use ONLY existing libraries from architecture.md dependencies +- **EXCEPTION PROCESS:** If absolutely critical to add a new library, must: + 1. Stop all code generation + 2. Ask explicit permission from user + 3. Provide detailed justification of purpose and necessity + 4. Explain why existing libraries cannot fulfill the requirement + 5. Wait for user approval before proceeding +- **FOCUS:** Maximize capabilities of existing libraries rather than seeking new ones + +**Use only libraries from architecture.md, do not install new libraries.** + +### Frontend Stack (React + Vite SPA): + +| Library | Role | +|---------|------| +| **React 18.x** | UI framework | +| **Vite 5.x** | Build tool & dev server with HMR | +| **React Router DOM 6.x** | Client-side routing (``, ``, ``) | +| **TanStack Query 5.x** | Server state management, caching (`useQuery`, `useMutation`) | +| **Axios 1.x** | HTTP client — all API calls via `src/lib/api.ts` | +| **React Hook Form 7.x** | Form state and submission | +| **Zod 3.x** | Schema validation — schemas in `src/schemas/`, shared with backend DTOs | +| **Tailwind CSS 3.x** | Utility-first styling — **only** styling tool, no CSS-in-JS | + +### Backend Stack (NestJS): + +| Library | Role | +|---------|------| +| **NestJS 10.x** | Application framework (modules, controllers, services, decorators) | +| **Prisma 5.x** | ORM — `schema.prisma` is single source of truth, use Prisma client for all DB ops | +| **@nestjs/jwt** | JWT sign/verify (no Passport — Username/Password only, no SSO) | +| **bcrypt** | Password hashing (cost factor 12) | +| **class-validator + class-transformer** | DTO validation with `ValidationPipe` | +| **@nestjs/swagger** | OpenAPI/Swagger UI at `/api/docs` (dev only) | + +## 2.1. Design Style Guidelines + +**Design Style: Clean Modern Dashboard (Pure Tailwind CSS)** + +This project uses **pure Tailwind CSS** — no external component library (no MUI, no Ant Design). The design is clean, professional, and matches the OKR dashboard wireframes defined in `apps/okr/domain/input/okr-requirement.md`. + +### Layout Structure (matches OKR wireframes): + +``` ++------------------+----------------------------------------------+ +| Sidebar (fixed) | Top Header (fixed) | +| - Year nav +----------------------------------------------+ +| - My OKRs | Main Content Area (scrollable) | +| - Members | | +| - OKR - all | | ++------------------+----------------------------------------------+ +``` + +### Color Palette: + +| Color Name | Tailwind Class | Usage | +|------------|----------------|-------| +| **Background** | `bg-gray-50` | Page background | +| **White** | `bg-white` | Cards, sidebar, content areas | +| **Primary Text** | `text-gray-800` | Titles, important content | +| **Secondary Text** | `text-gray-500` | Labels, descriptions, metadata | +| **Primary Blue** | `bg-blue-600` / `text-blue-600` | Primary actions, active nav links | +| **Success Green** | `text-green-600` / `bg-green-100` | Success status (100% / Completed) | +| **Warning Orange** | `text-orange-500` / `bg-orange-100` | In-progress / pending status | +| **Border** | `border-gray-200` | Card borders, dividers | +| **Sidebar** | `bg-white border-r border-gray-200` | Left navigation panel | + +### Component Style Rules: + +1. **No CSS-in-JS** — no `sx` prop, no `styled()` — all styling via Tailwind utility classes. +2. **Cards:** `bg-white rounded-xl shadow-sm border border-gray-200 p-6` +3. **Primary button:** `bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg font-medium transition-colors` +4. **Secondary button:** `border border-gray-300 text-gray-700 hover:bg-gray-50 px-4 py-2 rounded-lg transition-colors` +5. **Input fields:** `w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500` +6. **Select/Dropdown:** `border border-gray-300 rounded-lg px-3 py-2 text-sm bg-white focus:outline-none focus:ring-2 focus:ring-blue-500` +7. **Status badges:** + - Not Started: `bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-xs font-medium` + - In Progress: `bg-orange-100 text-orange-700 px-2 py-1 rounded-full text-xs font-medium` + - Completed: `bg-green-100 text-green-700 px-2 py-1 rounded-full text-xs font-medium` +8. **Progress bar:** + ```tsx +
+
+
+ ``` +9. **Sidebar nav item (active):** `bg-blue-50 text-blue-600 font-medium` +10. **Sidebar nav item (inactive):** `text-gray-600 hover:bg-gray-50 hover:text-gray-800` + +### Consistent Visual Rules: + +- All interactive elements must have hover effects (`hover:shadow-md`, `hover:-translate-y-px`, `transition-colors`). +- Use consistent spacing: multiples of 4px (`p-4`, `gap-4`, `mt-6`). +- Text must always use `gray` (not `grey`) — `text-gray-700`, `border-gray-200`. + +## 2.2. Tailwind CSS Configuration Rules (Tailwind v3) + +⚠️ **TAILWIND CSS v3 SPECIFIC REQUIREMENTS:** + +### CSS Import Rules (v3): +```css +✅ CORRECT: +@tailwind base; +@tailwind components; +@tailwind utilities; + +❌ WRONG: @import "tailwindcss"; +❌ WRONG: @import "tailwindcss/base"; +``` + +### Configuration File: +```javascript +// tailwind.config.ts +import type { Config } from 'tailwindcss'; + +const config: Config = { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; + +export default config; +``` + +### PostCSS Config: +```javascript +// postcss.config.js +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; +``` + +### Color Class Rules: +```css +✅ CORRECT: text-gray-700, bg-gray-50, border-gray-200 +❌ WRONG: text-grey-700, bg-grey-50, border-grey-200 +``` + +### Layout Rules: +```tsx +✅ CORRECT: Use semantic HTML + Tailwind flex/grid +
+

Title

+ +
+ +❌ WRONG: Use third-party layout components + + Title + +``` + +### Hover State Rules: +```css +✅ CORRECT: hover:bg-gray-50, hover:bg-gray-100 +❌ WRONG: hover:bg-gray-25 (doesn't exist in Tailwind) +``` + +### Custom Styles Rules: +```css +✅ CORRECT: Use standard CSS in index.css (no @apply with @layer) +.okr-card { + background: white; + border-radius: 0.75rem; + border: 1px solid #e5e7eb; +} + +❌ WRONG: Use @apply with @layer +@layer components { + .okr-card { @apply bg-white rounded-xl border border-gray-200; } +} +``` + +## 3. Backend Generation Rules (NestJS + Prisma) + +### Controller Rules: + +- **Location:** Must be in `backend/src/[module-name]/[module-name].controller.ts`. +- **Responsibility:** Keep controllers "thin". They only receive requests, trigger guards, validate DTOs, and call a single service method. +- **Auth:** Use `@UseGuards(JwtAuthGuard)` and `@Roles()` decorator on all protected routes. + +### Service Rules: + +- **Location:** Must be in `backend/src/[module-name]/[module-name].service.ts`. +- **Responsibility:** All business logic lives here. +- **Key Logic:** + - Use Prisma client for all DB operations — **no raw SQL** in application code. + - Filter data by role: ADMIN sees all, MANAGER sees all, EMPLOYEE sees only own objectives. + - Throw specific NestJS exceptions (`NotFoundException`, `ForbiddenException`). + +### Prisma Schema Rules: + +- **Single source of truth:** `backend/prisma/schema.prisma` defines ALL tables. +- **Migrations:** Use `npx prisma migrate dev --name ` — never edit migration files manually. +- **Types:** Always use Prisma-generated types (`Prisma.ObjectiveCreateInput`, `Prisma.KeyResultUpdateInput`). + +### Module Structure (OKR Domain): + +``` +backend/src/ +├── auth/ # JWT login, refresh token endpoints +├── users/ # User CRUD (Admin/Manager only) +├── objectives/ # Objective CRUD, filtering by quarter/owner/status +├── key-results/ # KR CRUD, progress update (PATCH /:id/progress) +└── common/ # Guards, filters, interceptors, decorators +``` + +### Database Seed Management: + +- **CRITICAL:** After completing backend code with schema changes, **ALWAYS** update the seed file. +- **Seed file location:** `backend/prisma/seed.ts` +- **Execution:** `npx prisma db seed` (or automatically on container start — always seeded in workshop environment) +- **Idempotency:** Use Prisma `upsert` keyed on stable identifiers — running seed twice must produce no duplicates. + +```typescript +// backend/prisma/seed.ts +import { PrismaClient } from '@prisma/client'; +import * as bcrypt from 'bcrypt'; + +const prisma = new PrismaClient(); + +async function main() { + // Always seed — workshop environment, no env gate needed + + const passwordHash = await bcrypt.hash('Password@123', 10); + + // Users — upsert keyed on email + const admin = await prisma.user.upsert({ + where: { email: 'admin@okr.local' }, + update: {}, + create: { name: 'System Admin', email: 'admin@okr.local', password: passwordHash, role: 'ADMIN' }, + }); + + const manager = await prisma.user.upsert({ + where: { email: 'manager@okr.local' }, + update: {}, + create: { name: 'Nguyen Van Manager', email: 'manager@okr.local', password: passwordHash, role: 'MANAGER' }, + }); + + const employee = await prisma.user.upsert({ + where: { email: 'employee@okr.local' }, + update: {}, + create: { name: 'Nguyen Van A', email: 'employee@okr.local', password: passwordHash, role: 'EMPLOYEE' }, + }); + + // Objectives — upsert keyed on id + const obj1 = await prisma.objective.upsert({ + where: { id: 1 }, + update: {}, + create: { + title: 'POC AI for SQL Injection prevention', + description: 'Evaluate AI tools for automated SQL injection detection', + ownerId: employee.id, + quarter: 'Q2/2026', + status: 'IN_PROGRESS', + }, + }); + + // Key Results — upsert keyed on id + await prisma.keyResult.upsert({ + where: { id: 1 }, + update: {}, + create: { + objectiveId: obj1.id, + title: 'Complete 3 POC sessions with security team', + progress: 0, + startValue: 0, + targetValue: 3, + deadline: new Date('2026-06-30'), + }, + }); + + console.log('Seed completed successfully.'); +} + +main() + .catch((e) => { console.error(e); process.exit(1); }) + .finally(() => prisma.$disconnect()); +``` + +### Seed Data Requirements (OKR Domain): + +| Category | Records | Notes | +|----------|---------|-------| +| Users | ≥ 3 | Admin, Manager, Employee — password `Password@123` hashed with bcrypt | +| Objectives | ≥ 2 | One per owner, varied statuses (`NOT_STARTED`, `IN_PROGRESS`) | +| Key Results | ≥ 4 | Linked to objectives, with `progress`, `startValue`, `targetValue`, `deadline` | + +- **When to update seed:** After adding new Prisma model fields, after changing relations, after completing any backend module. +- **Run seed:** `docker-compose exec backend npx prisma db seed` + +## 4. Frontend Generation Rules (React + Vite) + +### Routing Rules (React Router DOM v6): + +```tsx +// frontend/src/App.tsx — route structure + + } /> + }> + }> + } /> + } /> + } /> + } /> + + + +``` + +### Component & File Location Rules: + +- **Route-level pages:** `frontend/src/pages/` (Login, Dashboard, OKRDetail, CreateObjective, KeyResultDetail) +- **Layout components:** `frontend/src/components/layout/` (Sidebar, Header, AppLayout) +- **Reusable UI components:** `frontend/src/components/ui/` (Button, ProgressBar, Badge, Table) +- **Custom hooks:** `frontend/src/hooks/` (useAuth, useObjectives, useKeyResults) +- **API client:** `frontend/src/lib/api.ts` — all Axios calls go here +- **Query client config:** `frontend/src/lib/queryClient.ts` +- **Zod schemas:** `frontend/src/schemas/` +- **TypeScript interfaces:** `frontend/src/types/` + +### Layout Construction Rules: + +- **ONLY** use semantic HTML elements (`
`, `