openmoneta-dev-kit 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -0
- package/agents/qa-autonomous.md +131 -0
- package/agents/requirement-analyst.md +98 -0
- package/agents/security-auditor.md +120 -0
- package/agents/ui-tester.md +186 -0
- package/bin/openmoneta.js +11 -0
- package/hooks/check-plan-exists.sh +154 -0
- package/hooks/enforce-docs-first.sh +169 -0
- package/hooks/inject-process-context.sh +117 -0
- package/hooks/track-changes.sh +46 -0
- package/hooks/verify-completion.sh +165 -0
- package/hooks.json +30 -0
- package/opencode/AGENTS.md.tpl +38 -0
- package/opencode/agents/qa-autonomous.md +42 -0
- package/opencode/agents/requirement-analyst.md +51 -0
- package/opencode/agents/security-auditor.md +46 -0
- package/opencode/agents/ui-tester.md +43 -0
- package/opencode/plugins/openmoneta-guard.ts +389 -0
- package/package.json +41 -0
- package/scripts/debug-hooks.sh +54 -0
- package/scripts/init-project.sh +438 -0
- package/scripts/list-affected-modules.sh +74 -0
- package/skills/auth-bypass-testing/SKILL.md +236 -0
- package/skills/automated-testing/SKILL.md +162 -0
- package/skills/automated-testing/scripts/install-playwright.sh +134 -0
- package/skills/module-architect/SKILL.md +256 -0
- package/skills/plan-writer/SKILL.md +229 -0
- package/skills/requirement-analysis/SKILL.md +163 -0
- package/skills/safe-push/SKILL.md +182 -0
- package/skills/security-checklist/SKILL.md +116 -0
- package/skills/test-strategy/SKILL.md +135 -0
- package/skills/ui-test-loop/SKILL.md +161 -0
- package/src/cli.js +63 -0
- package/src/commands/check.js +30 -0
- package/src/commands/init.js +43 -0
- package/src/commands/install.js +50 -0
- package/src/commands/uninstall.js +74 -0
- package/src/commands/update.js +81 -0
- package/src/lib/paths.js +46 -0
- package/src/lib/version.js +45 -0
- package/templates/AGENTS.md.tpl +106 -0
- package/templates/docs-INDEX.md.tpl +62 -0
- package/templates/env.test.tpl +16 -0
- package/templates/karpathy-reference.md +49 -0
- package/templates/plans-INDEX.md.tpl +38 -0
- package/templates/playwright.config.ts.tpl +44 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const { execSync } = require("node:child_process")
|
|
2
|
+
const path = require("node:path")
|
|
3
|
+
const { getPkgRoot, OPENCODE_DIR } = require("../lib/paths")
|
|
4
|
+
|
|
5
|
+
async function run(args) {
|
|
6
|
+
let projectDir = process.cwd()
|
|
7
|
+
let isOpenCode = false
|
|
8
|
+
const remaining = []
|
|
9
|
+
|
|
10
|
+
for (const arg of args) {
|
|
11
|
+
if (arg === "--opencode") {
|
|
12
|
+
isOpenCode = true
|
|
13
|
+
} else if (arg.startsWith("-")) {
|
|
14
|
+
remaining.push(arg)
|
|
15
|
+
} else {
|
|
16
|
+
projectDir = arg
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
console.log(`\n 📁 Khởi tạo OpenMoneta Dev Kit cho project`)
|
|
21
|
+
console.log(` Path: ${projectDir}`)
|
|
22
|
+
|
|
23
|
+
const pkgRoot = getPkgRoot()
|
|
24
|
+
const initScript = path.join(pkgRoot, "scripts", "init-project.sh")
|
|
25
|
+
|
|
26
|
+
// If opencode, use opencode install path
|
|
27
|
+
const env = { ...process.env }
|
|
28
|
+
if (isOpenCode) {
|
|
29
|
+
env.OPENMONETA_HOME = OPENCODE_DIR
|
|
30
|
+
console.log(` Mode: OpenCode`)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const cmd = `bash "${initScript}" "${projectDir}" ${remaining.join(" ")}`
|
|
35
|
+
console.log(` Running: ${cmd}`)
|
|
36
|
+
execSync(cmd, { stdio: "inherit", env })
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(`\n ❌ Init thất bại: ${err.message}`)
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { run }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const { execSync } = require("node:child_process")
|
|
2
|
+
const path = require("node:path")
|
|
3
|
+
const { getPkgRoot, CURSOR_DIR, OPENCODE_DIR, isInstalled, REPO_DIR } = require("../lib/paths")
|
|
4
|
+
const { getLocalVersion } = require("../lib/version")
|
|
5
|
+
|
|
6
|
+
async function run(args) {
|
|
7
|
+
const isOpenCode = args.includes("--opencode")
|
|
8
|
+
const autoYes = args.includes("--yes") || args.includes("-y")
|
|
9
|
+
const target = isOpenCode ? "opencode" : "cursor"
|
|
10
|
+
|
|
11
|
+
const v = getLocalVersion() || "unknown"
|
|
12
|
+
const targetDir = isOpenCode ? OPENCODE_DIR : CURSOR_DIR
|
|
13
|
+
const targetLabel = isOpenCode ? "OpenCode" : "Cursor"
|
|
14
|
+
|
|
15
|
+
console.log(`\n 🔧 Cài OpenMoneta Dev Kit v${v} cho ${targetLabel}`)
|
|
16
|
+
console.log(` Target: ${targetDir}`)
|
|
17
|
+
|
|
18
|
+
if (isInstalled(target) && !autoYes) {
|
|
19
|
+
const readline = require("node:readline")
|
|
20
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
21
|
+
const answer = await new Promise((resolve) => {
|
|
22
|
+
rl.question(`\n ${targetLabel} đã được cài. Ghi đè? [y/N] `, resolve)
|
|
23
|
+
})
|
|
24
|
+
rl.close()
|
|
25
|
+
if (answer.toLowerCase() !== "y") {
|
|
26
|
+
console.log(" Hủy.")
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const pkgRoot = getPkgRoot()
|
|
32
|
+
|
|
33
|
+
if (isOpenCode) {
|
|
34
|
+
console.log(`\n Not yet implemented: use bash install-opencode.sh for now.`)
|
|
35
|
+
console.log(` bash ${path.join(pkgRoot, "install-opencode.sh")}${autoYes ? " --yes" : ""}`)
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Call install.sh from package root
|
|
40
|
+
try {
|
|
41
|
+
const cmd = `bash "${path.join(pkgRoot, "install.sh")}"${autoYes ? " --yes" : ""}`
|
|
42
|
+
console.log(`\n Running: ${cmd}`)
|
|
43
|
+
execSync(cmd, { stdio: "inherit", cwd: pkgRoot })
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error(`\n ❌ Cài đặt thất bại: ${err.message}`)
|
|
46
|
+
process.exit(1)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { run }
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const { unlinkSync, rmdirSync, existsSync, readdirSync, lstatSync } = require("node:fs")
|
|
2
|
+
const path = require("node:path")
|
|
3
|
+
const { execSync } = require("node:child_process")
|
|
4
|
+
const { CURSOR_DIR, OPENCODE_DIR, isInstalled, getPkgRoot } = require("../lib/paths")
|
|
5
|
+
|
|
6
|
+
function rmDir(dir) {
|
|
7
|
+
if (!existsSync(dir)) return
|
|
8
|
+
for (const entry of readdirSync(dir)) {
|
|
9
|
+
const p = path.join(dir, entry)
|
|
10
|
+
if (lstatSync(p).isDirectory()) {
|
|
11
|
+
rmDir(p)
|
|
12
|
+
} else {
|
|
13
|
+
unlinkSync(p)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
rmdirSync(dir)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function rmFile(p) {
|
|
20
|
+
if (existsSync(p)) unlinkSync(p)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function run(args) {
|
|
24
|
+
const purge = args.includes("--purge")
|
|
25
|
+
const autoYes = args.includes("--yes") || args.includes("-y")
|
|
26
|
+
|
|
27
|
+
if (!autoYes) {
|
|
28
|
+
const readline = require("node:readline")
|
|
29
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
30
|
+
const answer = await new Promise((resolve) => {
|
|
31
|
+
rl.question(`\n ⚠ Gỡ cài đặt OpenMoneta Dev Kit? [y/N] `, resolve)
|
|
32
|
+
})
|
|
33
|
+
rl.close()
|
|
34
|
+
if (answer.toLowerCase() !== "y") {
|
|
35
|
+
console.log(" Hủy.")
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
console.log(`\n 🗑 Uninstalling OpenMoneta Dev Kit...`)
|
|
41
|
+
|
|
42
|
+
// Try calling uninstall.sh first (handles backup restore)
|
|
43
|
+
const pkgRoot = getPkgRoot()
|
|
44
|
+
const uninstallScript = path.join(pkgRoot, "uninstall.sh")
|
|
45
|
+
if (existsSync(uninstallScript)) {
|
|
46
|
+
try {
|
|
47
|
+
const flags = [autoYes ? "--yes" : "", purge ? "--purge" : ""].filter(Boolean).join(" ")
|
|
48
|
+
execSync(`bash "${uninstallScript}" ${flags}`, { stdio: "inherit" })
|
|
49
|
+
return
|
|
50
|
+
} catch {
|
|
51
|
+
console.log(` Manual uninstall...`)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Manual removal
|
|
56
|
+
for (const dir of [CURSOR_DIR, OPENCODE_DIR]) {
|
|
57
|
+
if (!existsSync(dir)) continue
|
|
58
|
+
for (const item of ["templates", "skills", "agents", "hooks", "scripts"]) {
|
|
59
|
+
const p = path.join(dir, item)
|
|
60
|
+
rmDir(p)
|
|
61
|
+
}
|
|
62
|
+
rmFile(path.join(dir, "hooks.json"))
|
|
63
|
+
rmFile(path.join(dir, ".openmoneta-version"))
|
|
64
|
+
rmFile(path.join(dir, ".openmoneta-repo"))
|
|
65
|
+
rmFile(path.join(dir, ".openmoneta-installed-at"))
|
|
66
|
+
rmFile(path.join(dir, ".openmoneta-update-check"))
|
|
67
|
+
rmFile(path.join(dir, "AGENTS.md"))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(`\n ✅ Đã gỡ cài đặt.`)
|
|
71
|
+
console.log(` Restart Cursor/OpenCode để hoàn tất.`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { run }
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const { execSync } = require("node:child_process")
|
|
2
|
+
const path = require("node:path")
|
|
3
|
+
const { getPkgRoot, isInstalled } = require("../lib/paths")
|
|
4
|
+
const { getLocalVersion, getLatestVersion } = require("../lib/version")
|
|
5
|
+
|
|
6
|
+
async function run(args) {
|
|
7
|
+
const checkOnly = args.includes("--check")
|
|
8
|
+
const autoYes = args.includes("--yes") || args.includes("-y")
|
|
9
|
+
const skipProjects = args.includes("--skip-projects")
|
|
10
|
+
const force = args.includes("--force")
|
|
11
|
+
|
|
12
|
+
const local = getLocalVersion()
|
|
13
|
+
const latest = getLatestVersion()
|
|
14
|
+
|
|
15
|
+
if (checkOnly) {
|
|
16
|
+
console.log(`\n OpenMoneta Dev Kit`)
|
|
17
|
+
console.log(` Local : v${local || "unknown"}`)
|
|
18
|
+
console.log(` Latest: v${latest || "không xác định"}`)
|
|
19
|
+
if (latest && local && local !== latest) {
|
|
20
|
+
console.log(`\n 🔔 Cập nhật có sẵn: v${local} → v${latest}`)
|
|
21
|
+
console.log(` Chạy: openmoneta update`)
|
|
22
|
+
} else if (latest && local && local === latest) {
|
|
23
|
+
console.log(`\n ✅ Đã ở phiên bản mới nhất.`)
|
|
24
|
+
}
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!isInstalled("cursor") && !isInstalled("opencode")) {
|
|
29
|
+
console.log(`\n ❌ Chưa cài OpenMoneta. Chạy: openmoneta install`)
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (local && latest && local === latest && autoYes && !force) {
|
|
34
|
+
console.log(`\n ✅ Already at latest version (v${local}), skipping. Dùng --force để cài lại.`)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log(`\n 🔄 Updating OpenMoneta Dev Kit...`)
|
|
39
|
+
|
|
40
|
+
const pkgRoot = getPkgRoot()
|
|
41
|
+
|
|
42
|
+
if (isInstalled("cursor")) {
|
|
43
|
+
console.log(`\n ▶ Cursor global...`)
|
|
44
|
+
try {
|
|
45
|
+
execSync(`bash "${path.join(pkgRoot, "install.sh")}" --yes`, { stdio: "inherit", cwd: pkgRoot })
|
|
46
|
+
} catch {
|
|
47
|
+
console.error(` ⚠ Cursor update failed`)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isInstalled("opencode")) {
|
|
52
|
+
console.log(`\n ▶ OpenCode global...`)
|
|
53
|
+
try {
|
|
54
|
+
execSync(`bash "${path.join(pkgRoot, "install-opencode.sh")}" --yes`, { stdio: "inherit", cwd: pkgRoot })
|
|
55
|
+
} catch {
|
|
56
|
+
console.error(` ⚠ OpenCode update failed`)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`\n ✅ Global update done.`)
|
|
61
|
+
|
|
62
|
+
if (!skipProjects) {
|
|
63
|
+
const initScript = path.join(pkgRoot, "scripts", "init-project.sh")
|
|
64
|
+
const cwd = process.cwd()
|
|
65
|
+
const docsIndex = path.join(cwd, "docs", "INDEX.md")
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
require("node:fs").accessSync(docsIndex)
|
|
69
|
+
console.log(`\n ▶ Syncing project: ${path.basename(cwd)}`)
|
|
70
|
+
execSync(`bash "${initScript}" "${cwd}"`, { stdio: "inherit" })
|
|
71
|
+
console.log(`\n ✅ Project synced.`)
|
|
72
|
+
} catch {
|
|
73
|
+
console.log(`\n ℹ Không phát hiện project OpenMoneta ở thư mục hiện tại.`)
|
|
74
|
+
console.log(` Sync thủ công: openmoneta init`)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`\n 🎉 Hoàn tất. Restart Cursor/OpenCode để áp dụng.`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { run }
|
package/src/lib/paths.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const path = require("node:path")
|
|
2
|
+
const fs = require("node:fs")
|
|
3
|
+
|
|
4
|
+
let ROOT = ""
|
|
5
|
+
|
|
6
|
+
function getPkgRoot() {
|
|
7
|
+
return ROOT
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const HOME = process.env.HOME || process.env.USERPROFILE || ""
|
|
11
|
+
const XDG_CONFIG = process.env.XDG_CONFIG_HOME || path.join(HOME, ".config")
|
|
12
|
+
|
|
13
|
+
const CURSOR_DIR = path.join(HOME, ".cursor")
|
|
14
|
+
const OPENCODE_DIR = path.join(XDG_CONFIG, "opencode")
|
|
15
|
+
const REPO_DIR = path.join(HOME, "OpenMoneta-Dev-Kit") // legacy
|
|
16
|
+
|
|
17
|
+
function versionFilePath(target) {
|
|
18
|
+
if (target === "opencode") return path.join(OPENCODE_DIR, ".openmoneta-version")
|
|
19
|
+
return path.join(CURSOR_DIR, ".openmoneta-version")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function installedVersion(target) {
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync(versionFilePath(target), "utf8").trim()
|
|
25
|
+
} catch {
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isInstalled(target) {
|
|
31
|
+
return installedVersion(target) !== null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
setPkgRoot(r) {
|
|
36
|
+
ROOT = r
|
|
37
|
+
},
|
|
38
|
+
getPkgRoot,
|
|
39
|
+
HOME,
|
|
40
|
+
CURSOR_DIR,
|
|
41
|
+
OPENCODE_DIR,
|
|
42
|
+
REPO_DIR,
|
|
43
|
+
versionFilePath,
|
|
44
|
+
installedVersion,
|
|
45
|
+
isInstalled,
|
|
46
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { execSync } = require("node:child_process")
|
|
2
|
+
const path = require("node:path")
|
|
3
|
+
const fs = require("node:fs")
|
|
4
|
+
const { getPkgRoot } = require("./paths")
|
|
5
|
+
|
|
6
|
+
const PKG_NAME = "openmoneta-dev-kit"
|
|
7
|
+
|
|
8
|
+
function getLocalVersion() {
|
|
9
|
+
try {
|
|
10
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(getPkgRoot(), "package.json"), "utf8"))
|
|
11
|
+
return pkg.version || null
|
|
12
|
+
} catch {
|
|
13
|
+
return null
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getLatestVersion() {
|
|
18
|
+
try {
|
|
19
|
+
return execSync(`npm view ${PKG_NAME} version 2>/dev/null`, {
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
timeout: 8000,
|
|
22
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
23
|
+
}).trim()
|
|
24
|
+
} catch {
|
|
25
|
+
return null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function checkUpdate() {
|
|
30
|
+
const local = getLocalVersion()
|
|
31
|
+
const latest = getLatestVersion()
|
|
32
|
+
if (!local || !latest) return null
|
|
33
|
+
return {
|
|
34
|
+
local,
|
|
35
|
+
latest,
|
|
36
|
+
needsUpdate: local !== latest,
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
PKG_NAME,
|
|
42
|
+
getLocalVersion,
|
|
43
|
+
getLatestVersion,
|
|
44
|
+
checkUpdate,
|
|
45
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# AGENTS.md — Quy trình chuẩn (OpenMoneta Dev Kit v1.7.0+)
|
|
2
|
+
|
|
3
|
+
> File này được tạo từ `~/.cursor/templates/AGENTS.md.tpl`. Chạy `bash ~/.cursor/scripts/init-project.sh .` để re-sync (idempotent, giữ phần `<!-- BEGIN PROJECT OVERRIDE -->`).
|
|
4
|
+
|
|
5
|
+
## Nguyên tắc vàng
|
|
6
|
+
|
|
7
|
+
1. **Trả lời tiếng Việt.**
|
|
8
|
+
2. **Tuân thủ quy trình 6 bước cho mọi yêu cầu** (Bước 3 adaptive, Bước 6 chỉ chạy khi user yêu cầu push).
|
|
9
|
+
3. **Đọc `docs/INDEX.md` ĐẦU TIÊN** mỗi session — hook `enforce-docs-first` sẽ chặn `Read/Glob/Grep` source code nếu chưa đọc.
|
|
10
|
+
4. **Hỏi clarify TRƯỚC khi plan/code** — task trivial có thể skip; nếu có plan thì PHẢI ghi lý do/câu hỏi trong "## Hiểu yêu cầu".
|
|
11
|
+
5. **Surgical changes** — mỗi dòng đổi phải trace về 1 task trong plan. Không "improve" code adjacent.
|
|
12
|
+
6. **Simplicity First (Karpathy)** — không abstraction / config / error-handling không yêu cầu.
|
|
13
|
+
7. **Safe Push** — khi user yêu cầu push, PHẢI sync remote ngay trước push; KHÔNG `git push --force` lên shared branch.
|
|
14
|
+
|
|
15
|
+
## Quy trình 6 bước (B6 conditional)
|
|
16
|
+
|
|
17
|
+
### Bước 1 — Phân tích yêu cầu (TOKEN-AWARE, HOOK ENFORCED)
|
|
18
|
+
|
|
19
|
+
> Skill: `requirement-analysis`. Hook `enforce-docs-first` chặn Read source code nếu chưa đọc `docs/INDEX.md`.
|
|
20
|
+
|
|
21
|
+
1. Read `docs/INDEX.md` (Modules + Token Routing) → match keyword user → 1-3 module candidate.
|
|
22
|
+
2. Read `plans/INDEX.md` (check overlap với plan active).
|
|
23
|
+
3. Read `docs/modules/<candidate>/README.md` (CHỈ module liên quan, không scan toàn bộ).
|
|
24
|
+
4. Read source code module candidate (giờ hook unlock).
|
|
25
|
+
5. Tóm tắt yêu cầu + giả thuyết + edge case + rủi ro.
|
|
26
|
+
6. **Đặt câu hỏi critical để làm rõ yêu cầu** (`AskQuestion`) HOẶC khai báo `Lý do skip clarify` cho task trivial (typo, bump version, đổi 1 hằng số). Mục đích: làm rõ yêu cầu người dùng, phân tích tất cả trường hợp có thể xảy ra, phản biện và đề xuất phương án tối ưu hơn nếu có. Số lượng câu hỏi tùy thuộc vào độ phức tạp của task.
|
|
27
|
+
|
|
28
|
+
### Bước 2 — Thiết kế module
|
|
29
|
+
|
|
30
|
+
> Skill: `module-architect`.
|
|
31
|
+
|
|
32
|
+
- 1 module = 1 trách nhiệm (SRP), mô tả 1 câu KHÔNG có "và".
|
|
33
|
+
- **Feature mới**: áp dụng "Khi nào TẠO module mới" — feature có concept business riêng → module mới (KHÔNG nhét vào `utils/` / `common/` / `shared/`).
|
|
34
|
+
- **Dự án EXISTING**: chạy Bước 2.0–2.3 (auto-detect module từ `apps/`, `packages/`, `src/modules/`); module bị sửa mà chưa có docs → backfill `docs/modules/<slug>/README.md` (3 sections: Trách nhiệm + Public API + Dependencies).
|
|
35
|
+
- Module mới → BẮT BUỘC thêm 3-5 keyword vào bảng "Token Routing" của `docs/INDEX.md`.
|
|
36
|
+
|
|
37
|
+
### Bước 3 — Adaptive Planning Gate
|
|
38
|
+
|
|
39
|
+
> Skill: `plan-writer`. Plan KHÔNG bắt buộc cho task nhỏ. Plan BẮT BUỘC cho task lớn/rủi ro.
|
|
40
|
+
|
|
41
|
+
- Task nhỏ/rõ ràng → không cần plan, triển khai luôn nhưng vẫn giữ surgical changes.
|
|
42
|
+
- Task lớn/rủi ro/mơ hồ → tạo `plans/YYYY-MM-DD-<slug>.md` với `Status: Draft`, trình user review, chỉ triển khai sau khi user approve và đổi `Status: In Progress`.
|
|
43
|
+
- Khi đã có plan `In Progress`, hook `check-plan-exists` enforce file scope theo `## Files ảnh hưởng` / `## Files thay đổi`.
|
|
44
|
+
- Khi plan còn `Draft`, hook chặn code edit để tránh Agent tự approve plan.
|
|
45
|
+
- Plan đụng >2 module → BẮT BUỘC thêm subsection "Lý do cross-module".
|
|
46
|
+
- Update `plans/INDEX.md` (thêm vào Active).
|
|
47
|
+
|
|
48
|
+
### Bước 4 — Triển khai
|
|
49
|
+
|
|
50
|
+
- Nếu có plan active: chỉ edit file có trong `## Files ảnh hưởng` / `## Files thay đổi`.
|
|
51
|
+
- Nếu không có plan: chỉ sửa đúng phạm vi user yêu cầu; phát hiện task vượt small/risk threshold → dừng, tạo plan Draft và xin review.
|
|
52
|
+
- Mỗi sub-task xong → tick `- [x]` checkbox.
|
|
53
|
+
- Cần mở rộng scope → update plan TRƯỚC khi sửa file mới.
|
|
54
|
+
|
|
55
|
+
### Bước 5 — Update doc + close plan
|
|
56
|
+
|
|
57
|
+
> Skill: `module-architect` (đã merge `doc-maintainer`). Hook `verify-completion` Check 6 chặn nếu module README thiếu.
|
|
58
|
+
|
|
59
|
+
- Sync `docs/modules/<slug>/README.md` (Public API + Dependencies) nếu API đổi.
|
|
60
|
+
- Module mới → đảm bảo đã có trong bảng "Modules hiện có" + "Token Routing" của `docs/INDEX.md`.
|
|
61
|
+
- Plan → `Status: Done` + tick mọi checkbox.
|
|
62
|
+
- **AUTO-ARCHIVE**: `git mv plans/<file>.md plans/archive/<file>.md` + update `plans/INDEX.md`.
|
|
63
|
+
|
|
64
|
+
### Bước 6 — Pre-push Sync + Safe Push (CONDITIONAL)
|
|
65
|
+
|
|
66
|
+
> Skill: `safe-push`. Chỉ chạy khi user yêu cầu `push`, `commit + push`, `đẩy code`, `merge lên remote`.
|
|
67
|
+
|
|
68
|
+
- Trước `git push`, chạy `git fetch origin <branch>` + so sánh local/remote.
|
|
69
|
+
- Nếu remote có commit mới → `git pull --rebase origin <branch>`.
|
|
70
|
+
- Nếu conflict code logic → hỏi user, KHÔNG tự chọn ours/theirs.
|
|
71
|
+
- Sau rebase/conflict → chạy lại verify tối thiểu phù hợp repo.
|
|
72
|
+
- Push bằng `git push` thường. Nếu rejected vì remote mới → lặp lại fetch/rebase tối đa 3 vòng.
|
|
73
|
+
- KHÔNG dùng `git push --force` hoặc `git push -f` trên `main`, `master`, `develop`, `staging`, `production`.
|
|
74
|
+
|
|
75
|
+
## Sub-agents có sẵn (delegate khi cần isolation)
|
|
76
|
+
|
|
77
|
+
| Subagent | Khi nào |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `requirement-analyst` | Yêu cầu mơ hồ/lớn, cần đọc nhiều file |
|
|
80
|
+
| `security-auditor` | **ON-DEMAND** — user yêu cầu audit security, hoặc task chạm auth/payment/PII |
|
|
81
|
+
| `qa-autonomous` | **ON-DEMAND** — user yêu cầu viết unit/integration test |
|
|
82
|
+
| `ui-tester` | **ON-DEMAND** — user yêu cầu Playwright UI/E2E test |
|
|
83
|
+
|
|
84
|
+
## Skills có sẵn
|
|
85
|
+
|
|
86
|
+
**Core (luôn relevant)**: `requirement-analysis`, `module-architect`, `plan-writer`.
|
|
87
|
+
|
|
88
|
+
**Core conditional**: `safe-push` (Bước 6 — chỉ khi user yêu cầu push).
|
|
89
|
+
|
|
90
|
+
**On-demand (chỉ trigger khi user yêu cầu rõ)**: `security-checklist`, `test-strategy`, `automated-testing`, `auth-bypass-testing`, `ui-test-loop`.
|
|
91
|
+
|
|
92
|
+
## Hooks enforce
|
|
93
|
+
|
|
94
|
+
| Hook | Khi nào | Hậu quả |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `enforce-docs-first` | preToolUse Read/Glob/Grep | BLOCK source code đọc nếu chưa Read `docs/INDEX.md` |
|
|
97
|
+
| `check-plan-exists` | preToolUse Write/StrReplace/EditNotebook/Delete | ALLOW task thường không plan; BLOCK file nhạy cảm không plan, plan Draft, hoặc file ngoài scope plan |
|
|
98
|
+
| `verify-completion` | stop | BLOCK kết thúc nếu Check 5/6/9 fail (plan Done + checkbox, module README, "Hiểu yêu cầu") |
|
|
99
|
+
|
|
100
|
+
## Override cho dự án này
|
|
101
|
+
|
|
102
|
+
<!-- Thêm rule riêng của dự án bên dưới. Phần này được giữ lại khi chạy init-project.sh lần nữa. -->
|
|
103
|
+
|
|
104
|
+
<!-- BEGIN PROJECT OVERRIDE -->
|
|
105
|
+
|
|
106
|
+
<!-- END PROJECT OVERRIDE -->
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Docs Index
|
|
2
|
+
|
|
3
|
+
> **Đây là điểm vào duy nhất cho AI khi mở dự án.** Hook `enforce-docs-first` (~/.cursor/hooks/) sẽ chặn `Read/Glob/Grep` trên `apps/`, `packages/`, `src/`, ... cho đến khi AI đã Read file này. Tiết kiệm 70-90% token bằng cách dùng bảng "Token Routing" để đến thẳng module liên quan.
|
|
4
|
+
|
|
5
|
+
## Cấu trúc tài liệu
|
|
6
|
+
|
|
7
|
+
<!-- BEGIN PROJECT DOC STRUCTURE -->
|
|
8
|
+
| Đường dẫn | Mục đích |
|
|
9
|
+
|---|---|
|
|
10
|
+
| `docs/modules/<name>/README.md` | Mục đích, public API, deps của từng module |
|
|
11
|
+
<!-- END PROJECT DOC STRUCTURE -->
|
|
12
|
+
|
|
13
|
+
## Modules hiện có
|
|
14
|
+
|
|
15
|
+
<!-- BEGIN PROJECT MODULES -->
|
|
16
|
+
| Module | Source path | Trách nhiệm (1 câu, không có "và") |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| (chưa có) | | |
|
|
19
|
+
<!-- END PROJECT MODULES -->
|
|
20
|
+
|
|
21
|
+
## Feature / Keyword → Module (Token Routing)
|
|
22
|
+
|
|
23
|
+
> **MỤC ĐÍCH**: AI ở Bước 1 (requirement-analysis) dùng bảng này để map yêu cầu user → module liên quan, sau đó chỉ đọc README của module đó thay vì scan toàn bộ docs/.
|
|
24
|
+
>
|
|
25
|
+
> **MAINTAIN**: mỗi khi tạo/đổi tên module → update bảng này. Keyword là từ tiếng Việt + tiếng Anh user thường gõ. Mỗi module nên có 3-5 keyword.
|
|
26
|
+
|
|
27
|
+
<!-- BEGIN PROJECT TOKEN ROUTING -->
|
|
28
|
+
| Keyword (VN/EN) | Module liên quan | Ghi chú |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| login, đăng nhập, OAuth, Google login, JWT, session, authentication | (vd: `auth`) | |
|
|
31
|
+
| user, người dùng, profile, avatar | (vd: `user`) | |
|
|
32
|
+
| payment, thanh toán, billing, invoice, subscription | (vd: `billing`) | |
|
|
33
|
+
| email, mail, notification, thông báo, push, SMS | (vd: `notifications`) | |
|
|
34
|
+
| upload, file, ảnh, image, storage, S3 | (vd: `storage`) | |
|
|
35
|
+
| search, tìm kiếm, filter, sort | (vd: `search`) | |
|
|
36
|
+
| (thêm dòng cho mỗi domain concept của dự án) | | |
|
|
37
|
+
<!-- END PROJECT TOKEN ROUTING -->
|
|
38
|
+
|
|
39
|
+
## Cách thêm module mới
|
|
40
|
+
|
|
41
|
+
1. Tạo `docs/modules/<module-name>/README.md` với 3 sections:
|
|
42
|
+
- **Trách nhiệm**: 1 câu không có "và".
|
|
43
|
+
- **Public API**: function/class export.
|
|
44
|
+
- **Dependencies**: internal + external.
|
|
45
|
+
2. Update bảng "Modules hiện có" ở trên.
|
|
46
|
+
3. **BẮT BUỘC**: thêm 3-5 keyword vào bảng "Token Routing" (cho cả VN + EN).
|
|
47
|
+
4. Tạo source folder tương ứng (`src/modules/<name>/` hoặc `apps/<name>/` hoặc `packages/<name>/`).
|
|
48
|
+
|
|
49
|
+
## Plans hiện có
|
|
50
|
+
|
|
51
|
+
Xem `plans/INDEX.md`.
|
|
52
|
+
|
|
53
|
+
## Quy ước
|
|
54
|
+
|
|
55
|
+
- Tài liệu **tiếng Việt**.
|
|
56
|
+
- Tách module theo **SRP** (Single Responsibility), không theo số dòng. Chi tiết: skill `module-architect`.
|
|
57
|
+
- Hook `verify-completion` Check 6 sẽ chặn session nếu code đã sửa nhưng module README chưa có.
|
|
58
|
+
|
|
59
|
+
## Ghi chú riêng của dự án
|
|
60
|
+
|
|
61
|
+
<!-- BEGIN PROJECT DOCS NOTES -->
|
|
62
|
+
<!-- END PROJECT DOCS NOTES -->
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# .env.test - GITIGNORED, KHÔNG commit lên git
|
|
2
|
+
# File này được sinh tự động bởi init-project.sh. Token rotate mỗi lần bootstrap.
|
|
3
|
+
# Dùng cho test local với cơ chế Test Bypass Flag (xem skill auth-bypass-testing).
|
|
4
|
+
|
|
5
|
+
# Bật chế độ bypass auth - CHỈ HOẠT ĐỘNG khi NODE_ENV != 'production'
|
|
6
|
+
ENABLE_TEST_BYPASS=true
|
|
7
|
+
|
|
8
|
+
# Token random 64-char để authenticate test request. Phải khớp với header X-Test-Bypass-Token.
|
|
9
|
+
TEST_BYPASS_TOKEN=__GENERATED_TOKEN__
|
|
10
|
+
|
|
11
|
+
# Test user IDs hardcoded (xem tests/fixtures/test-users.json)
|
|
12
|
+
TEST_USER_ID_DEFAULT=00000000-0000-0000-0000-000000000001
|
|
13
|
+
TEST_USER_ID_ADMIN=00000000-0000-0000-0000-000000000002
|
|
14
|
+
|
|
15
|
+
# IP whitelist - chỉ accept bypass request từ các IP này
|
|
16
|
+
TEST_BYPASS_ALLOWED_IPS=127.0.0.1,::1,localhost
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Karpathy Behavioral Guidelines — Reference
|
|
2
|
+
|
|
3
|
+
> Ghi chú: file này là **reference attribution** cho 4 nguyên tắc behavioral guideline của Andrej Karpathy được tích hợp vào OpenMoneta Dev Kit từ v1.2.0. Giữ nguyên tên + text gốc tiếng Anh để attribution rõ. Phần Việt hóa + áp dụng cụ thể nằm trong các skill được map bên dưới.
|
|
4
|
+
|
|
5
|
+
## Source
|
|
6
|
+
|
|
7
|
+
- Original guidelines: Karpathy behavioral guidelines (Cursor rule format `.mdc`).
|
|
8
|
+
- Tích hợp vào OpenMoneta Dev Kit: v1.2.0.
|
|
9
|
+
- Approach: INLINE — bổ sung vào skill hiện tại thay vì tạo skill mới riêng (tránh trùng lặp, AI bắt buộc đọc khi áp dụng skill ở từng bước).
|
|
10
|
+
|
|
11
|
+
## 4 nguyên tắc gốc (English)
|
|
12
|
+
|
|
13
|
+
| # | Nguyên tắc | Tóm tắt |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| 1 | **Think Before Coding** | State assumptions explicitly. If multiple interpretations exist, present them. If simpler approach exists, push back. If unclear, stop and ask. |
|
|
16
|
+
| 2 | **Simplicity First** | Minimum code that solves the problem. No speculative features/abstractions/configurability/error-handling. "Would a senior engineer say overcomplicated?" → simplify. |
|
|
17
|
+
| 3 | **Surgical Changes** | Touch only what you must. Don't "improve" adjacent code. Match existing style. Mention dead code, don't delete. Clean up only YOUR own orphans. Every changed line traces to user request. |
|
|
18
|
+
| 4 | **Goal-Driven Execution** | Define verifiable success criteria. Bug fix: write reproducing test → fail → fix → pass. Multi-step plan: each step has a verify check. Strong criteria let agent loop independently. |
|
|
19
|
+
|
|
20
|
+
## Mapping nguyên tắc → skill OpenMoneta
|
|
21
|
+
|
|
22
|
+
| Karpathy | Skill OpenMoneta áp dụng | Section trong skill |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| #1 Think Before Coding | [`requirement-analysis`](../skills/requirement-analysis/SKILL.md) | Step 5: "Đề xuất cách đơn giản hơn" |
|
|
25
|
+
| #2 Simplicity First | [`module-architect`](../skills/module-architect/SKILL.md) | "Anti-overengineering checklist" |
|
|
26
|
+
| #3 Surgical Changes | [`plan-writer`](../skills/plan-writer/SKILL.md) | "Surgical Changes — line-level" |
|
|
27
|
+
| #4 Goal-Driven (TDD bug fix) | [`test-strategy`](../skills/test-strategy/SKILL.md) | "Bug fix workflow (TDD bắt buộc)" |
|
|
28
|
+
| #4 Goal-Driven (verify per step) | [`plan-writer`](../skills/plan-writer/SKILL.md) | "Tasks (mỗi task PHẢI có verify check)" |
|
|
29
|
+
|
|
30
|
+
## Nguyên tắc vàng cấp dự án
|
|
31
|
+
|
|
32
|
+
2 nguyên tắc rút gọn được thêm vào [`AGENTS.md.tpl`](./AGENTS.md.tpl) (inject mỗi session qua `sessionStart` hook):
|
|
33
|
+
|
|
34
|
+
- **Nguyên tắc vàng #6**: Surgical changes — mỗi dòng đổi phải trace về plan.
|
|
35
|
+
- **Nguyên tắc vàng #7**: Đơn giản tối thiểu — không abstraction/config/error-handling không yêu cầu.
|
|
36
|
+
|
|
37
|
+
## Tradeoff (theo Karpathy gốc)
|
|
38
|
+
|
|
39
|
+
> "These guidelines bias toward caution over speed. For trivial tasks, use judgment."
|
|
40
|
+
|
|
41
|
+
OpenMoneta áp dụng tương tự:
|
|
42
|
+
- Trivial task (vd: rename biến, sửa typo) → AI dùng judgment, không cần đầy đủ checklist.
|
|
43
|
+
- Task ảnh hưởng >1 file hoặc thêm logic mới → bắt buộc apply 4 nguyên tắc.
|
|
44
|
+
|
|
45
|
+
## Caveat khi áp dụng cho team Việt
|
|
46
|
+
|
|
47
|
+
- "Push back" được phrase lại là "đề xuất thay thế" để tránh nghĩa chống đối.
|
|
48
|
+
- "No error handling for impossible scenarios" KHÔNG mâu thuẫn với input validation: validate input của user là **bắt buộc** (xem `security-checklist`); chỉ tránh handle case **không thể xảy ra** trong contract code (vd: enum đã đủ cases nhưng vẫn `default: throw`).
|
|
49
|
+
- "Match existing style" áp dụng kể cả khi style cũ không tối ưu — refactor style là task riêng, không gộp vào task khác.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Plans Index
|
|
2
|
+
|
|
3
|
+
> AI cập nhật bảng này mỗi khi tạo/hoàn thành plan. Từ v1.7.0, task nhỏ có thể không cần plan; task lớn/rủi ro tạo plan `Draft`, user review, rồi mới `In Progress`. Plan Done được auto-archive sang `plans/archive/` để giảm noise khi AI scan plans/ folder.
|
|
4
|
+
|
|
5
|
+
## Active (đang triển khai)
|
|
6
|
+
|
|
7
|
+
<!-- BEGIN ACTIVE PLANS -->
|
|
8
|
+
| Plan | Mục tiêu | Trạng thái | Ngày tạo |
|
|
9
|
+
|---|---|---|---|
|
|
10
|
+
| (chưa có) | | | |
|
|
11
|
+
<!-- END ACTIVE PLANS -->
|
|
12
|
+
|
|
13
|
+
## Extra / Legacy Plan Sections
|
|
14
|
+
|
|
15
|
+
<!-- BEGIN PROJECT PLANS EXTRA -->
|
|
16
|
+
<!-- END PROJECT PLANS EXTRA -->
|
|
17
|
+
|
|
18
|
+
## Archived (đã Done, đã move sang plans/archive/)
|
|
19
|
+
|
|
20
|
+
<!-- BEGIN ARCHIVED PLANS -->
|
|
21
|
+
| Plan | Mục tiêu | Hoàn thành | Plan archive path |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| (chưa có) | | | `plans/archive/...` |
|
|
24
|
+
<!-- END ARCHIVED PLANS -->
|
|
25
|
+
|
|
26
|
+
## Quy ước
|
|
27
|
+
|
|
28
|
+
- File plan: `plans/YYYY-MM-DD-<slug-kebab-case>.md`.
|
|
29
|
+
- Slug: ngắn gọn, mô tả tính năng/fix chính (vd: `add-google-login`, `fix-cart-overflow`).
|
|
30
|
+
- Status trong file: `Draft` (chờ user review) → `In Progress` (đã approve) → `Done`.
|
|
31
|
+
- **Auto-archive**: ngay khi đổi Status → Done, chạy `git mv plans/<file>.md plans/archive/<file>.md` và update bảng Archived bên trên.
|
|
32
|
+
- Plan template: 5 sections (Hiểu yêu cầu, Mục tiêu, Modules ảnh hưởng, Files ảnh hưởng, Tasks). Plan minimal chỉ dùng khi vẫn muốn audit cho task nhỏ.
|
|
33
|
+
- Skill: `~/.cursor/skills/plan-writer/SKILL.md`.
|
|
34
|
+
|
|
35
|
+
## Ghi chú riêng của dự án
|
|
36
|
+
|
|
37
|
+
<!-- BEGIN PROJECT PLANS NOTES -->
|
|
38
|
+
<!-- END PROJECT PLANS NOTES -->
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineConfig, devices } from '@playwright/test';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
dotenv.config({ path: path.resolve(__dirname, '.env.test') });
|
|
6
|
+
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
testDir: './tests/e2e',
|
|
9
|
+
fullyParallel: true,
|
|
10
|
+
forbidOnly: !!process.env.CI,
|
|
11
|
+
retries: process.env.CI ? 2 : 0,
|
|
12
|
+
workers: process.env.CI ? 1 : undefined,
|
|
13
|
+
reporter: [
|
|
14
|
+
['list'],
|
|
15
|
+
['html', { outputFolder: 'tests/playwright-report', open: 'never' }],
|
|
16
|
+
],
|
|
17
|
+
use: {
|
|
18
|
+
baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3000',
|
|
19
|
+
trace: 'retain-on-failure',
|
|
20
|
+
screenshot: 'only-on-failure',
|
|
21
|
+
video: 'retain-on-failure',
|
|
22
|
+
extraHTTPHeaders: process.env.TEST_BYPASS_TOKEN
|
|
23
|
+
? {
|
|
24
|
+
'X-Test-Bypass-Token': process.env.TEST_BYPASS_TOKEN,
|
|
25
|
+
'X-Test-User-Id': process.env.TEST_USER_ID_DEFAULT || '',
|
|
26
|
+
}
|
|
27
|
+
: {},
|
|
28
|
+
},
|
|
29
|
+
projects: [
|
|
30
|
+
{
|
|
31
|
+
name: 'mobile',
|
|
32
|
+
use: { ...devices['iPhone 13'], viewport: { width: 375, height: 812 } },
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'tablet',
|
|
36
|
+
use: { ...devices['iPad (gen 7)'], viewport: { width: 768, height: 1024 } },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'desktop',
|
|
40
|
+
use: { ...devices['Desktop Chrome'], viewport: { width: 1440, height: 900 } },
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
outputDir: 'tests/screenshots/_playwright-output',
|
|
44
|
+
});
|