@warnyin/agents 0.11.0 → 0.12.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/CHANGELOG.md +120 -115
- package/README.md +148 -148
- package/package.json +38 -38
- package/src/.claude/agents/warnyin-infra.md +13 -13
- package/src/.claude/agents/warnyin-qa.md +13 -13
- package/src/.claude/agents/warnyin-sa.md +13 -13
- package/src/.claude/agents/warnyin-security.md +13 -13
- package/src/.claude/agents/warnyin-tech-lead.md +13 -13
- package/src/.claude/commands/warnyin/build.md +31 -31
- package/src/.claude/commands/warnyin/design.md +27 -27
- package/src/.claude/commands/warnyin/discovery.md +17 -17
- package/src/.claude/commands/warnyin/explore.md +14 -14
- package/src/.claude/commands/warnyin/init.md +12 -12
- package/src/.claude/commands/warnyin/install-skill.md +14 -14
- package/src/.claude/commands/warnyin/next.md +17 -17
- package/src/.claude/commands/warnyin/ship.md +28 -28
- package/src/.claude/commands/warnyin/triage.md +14 -0
- package/src/.claude/commands/warnyin/update-codemaps.md +12 -12
- package/src/.claude/commands/warnyin/verify.md +20 -20
- package/src/.claude/skills/explore/SKILL.md +8 -8
- package/src/.claude/skills/next/SKILL.md +8 -8
- package/src/.claude/skills/update-codemaps/SKILL.md +8 -8
- package/src/.warnyin/installer/templates/CLAUDE.md +30 -29
- package/src/.warnyin/template/docs/codemap/index.md +18 -18
- package/src/.warnyin/template/docs/features/[feature-name]/business.md +5 -5
- package/src/.warnyin/template/docs/features/[feature-name]/feature.md +5 -5
- package/src/.warnyin/template/docs/features/[feature-name]/spec.md +16 -16
- package/src/.warnyin/template/docs/infra.md +16 -16
- package/src/.warnyin/template/docs/project.md +18 -18
- package/src/.warnyin/template/docs/rule.md +7 -7
- package/src/.warnyin/template/docs/techstack/[component]/about.md +6 -6
- package/src/.warnyin/template/docs/techstack/[component]/rule.md +6 -6
- package/src/.warnyin/template/docs/techstack/[component]/standard.md +6 -6
- package/src/.warnyin/template/docs/techstack/[component]/structure.md +7 -7
- package/src/.warnyin/template/docs/techstack/[component]/test.md +7 -7
- package/src/.warnyin/template/docs/troubleshooting.md +32 -32
- package/src/.warnyin/template/stages/[topic]/build.md +58 -58
- package/src/.warnyin/template/stages/[topic]/business.md +21 -21
- package/src/.warnyin/template/stages/[topic]/design.md +63 -63
- package/src/.warnyin/template/stages/[topic]/discovery.md +69 -69
- package/src/.warnyin/template/stages/[topic]/proposal.md +43 -43
- package/src/.warnyin/template/stages/[topic]/research.md +49 -49
- package/src/.warnyin/template/stages/[topic]/ship.md +32 -32
- package/src/.warnyin/template/stages/[topic]/tasks/[task-name]/issue.md +19 -19
- package/src/.warnyin/template/stages/[topic]/tasks/[task-name]/rule.md +13 -13
- package/src/.warnyin/template/stages/[topic]/tasks/[task-name]/spec.md +36 -36
- package/src/.warnyin/template/stages/[topic]/tasks/[task-name]/standard.md +21 -21
- package/src/.warnyin/template/stages/[topic]/tasks/[task-name]/task.md +40 -40
- package/src/.warnyin/template/stages/[topic]/test.md +46 -46
- package/src/.warnyin/template/stages/[topic]/troubleshooting.md +34 -34
- package/src/.warnyin/template/stages/[topic]/verify.md +44 -44
- package/src/.warnyin/workflow/README.md +102 -101
- package/src/.warnyin/workflow/api-doc.md +93 -93
- package/src/.warnyin/workflow/codemap.md +91 -91
- package/src/.warnyin/workflow/contexts/README.md +51 -51
- package/src/.warnyin/workflow/contexts/build.md +25 -25
- package/src/.warnyin/workflow/contexts/research.md +25 -25
- package/src/.warnyin/workflow/contexts/review.md +25 -25
- package/src/.warnyin/workflow/explore.md +32 -32
- package/src/.warnyin/workflow/init.md +125 -125
- package/src/.warnyin/workflow/next.md +48 -48
- package/src/.warnyin/workflow/roles/README.md +47 -47
- package/src/.warnyin/workflow/roles/ba.md +25 -25
- package/src/.warnyin/workflow/roles/developer.md +31 -31
- package/src/.warnyin/workflow/roles/infra.md +24 -24
- package/src/.warnyin/workflow/roles/po.md +28 -28
- package/src/.warnyin/workflow/roles/qa.md +35 -35
- package/src/.warnyin/workflow/roles/sa.md +28 -28
- package/src/.warnyin/workflow/roles/security.md +39 -39
- package/src/.warnyin/workflow/roles/tech-lead.md +28 -28
- package/src/.warnyin/workflow/scripts/build-wave.mjs +143 -143
- package/src/.warnyin/workflow/scripts/validate-topic.mjs +378 -378
- package/src/.warnyin/workflow/stages/build.md +98 -98
- package/src/.warnyin/workflow/stages/design.md +131 -126
- package/src/.warnyin/workflow/stages/discovery.md +78 -78
- package/src/.warnyin/workflow/stages/ship.md +94 -92
- package/src/.warnyin/workflow/stages/verify.md +82 -80
- package/src/.warnyin/workflow/triage.md +74 -0
- package/src/AGENTS.md +48 -48
- package/src/bin/cli.mjs +193 -193
package/src/bin/cli.mjs
CHANGED
|
@@ -1,193 +1,193 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* warnyin-agents installer
|
|
4
|
-
* ติดตั้ง Warnyin Standard Workflow ลงโปรเจกต์ปัจจุบัน (cwd)
|
|
5
|
-
*
|
|
6
|
-
* npx @warnyin/agents ติดตั้ง (ข้ามไฟล์ที่มีอยู่แล้ว)
|
|
7
|
-
* npx @warnyin/agents --update อัปเดต playbook กลาง (เขียนทับเฉพาะไฟล์ core)
|
|
8
|
-
* npx @warnyin/agents --dry-run แสดงว่าจะทำอะไร โดยไม่เขียนไฟล์จริง
|
|
9
|
-
* (ทางสำรองไม่ผ่าน npm: npx github:warnyin/warnyin-agents)
|
|
10
|
-
*/
|
|
11
|
-
import fs from 'node:fs'
|
|
12
|
-
import path from 'node:path'
|
|
13
|
-
import { fileURLToPath } from 'node:url'
|
|
14
|
-
|
|
15
|
-
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
16
|
-
const target = process.cwd()
|
|
17
|
-
const args = new Set(process.argv.slice(2))
|
|
18
|
-
const UPDATE = args.has('--update')
|
|
19
|
-
const DRY = args.has('--dry-run')
|
|
20
|
-
|
|
21
|
-
if (args.has('--help') || args.has('-h')) {
|
|
22
|
-
console.log(`warnyin-agents — ติดตั้ง Warnyin Standard Workflow ลงโปรเจกต์ปัจจุบัน
|
|
23
|
-
|
|
24
|
-
ใช้งาน:
|
|
25
|
-
npx @warnyin/agents ติดตั้ง (ข้ามไฟล์ที่มีอยู่แล้ว ไม่เขียนทับ)
|
|
26
|
-
npx @warnyin/agents --update อัปเดต playbook กลางเป็นเวอร์ชันล่าสุด
|
|
27
|
-
(เขียนทับเฉพาะ .warnyin/workflow/, .claude/commands/warnyin/,
|
|
28
|
-
template .warnyin/template/stages/[topic] — ไม่แตะ docs/ และงานจริง)
|
|
29
|
-
npx @warnyin/agents --dry-run แสดงรายการไฟล์ที่จะสร้าง/อัปเดต โดยไม่เขียนจริง
|
|
30
|
-
|
|
31
|
-
หลังติดตั้ง: เปิด Claude Code ในโปรเจกต์ แล้วรัน /warnyin:init ให้ agent วิเคราะห์โปรเจกต์ + เติม docs/`)
|
|
32
|
-
process.exit(0)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// guard กัน self-install — เก็บไว้แบบ defensive (zero-cost)
|
|
36
|
-
// หลังย้าย source เข้า src/ → pkgRoot = src/ (sibling ของ bin/) จึงแทบไม่มีทาง === target (repo root / temp sandbox)
|
|
37
|
-
// → guard นี้เป็น no-op โดยตั้งใจในเคสปกติ/sandbox; ยังคงดักได้เฉพาะ edge case ที่ install ลงโฟลเดอร์ที่เป็น src/ เอง
|
|
38
|
-
if (path.resolve(pkgRoot) === path.resolve(target)) {
|
|
39
|
-
console.error('✖ กำลังรันอยู่ใน repo ของ warnyin-agents เอง — ให้ cd ไปที่โปรเจกต์ปลายทางก่อน')
|
|
40
|
-
process.exit(1)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// โครงเก่า (≤0.2.x): workflow/ + warnyin-stages/ ที่ root — เตือนให้ย้ายเอง ไม่แตะงานจริงของ user
|
|
44
|
-
const legacyV2 = ['workflow', 'warnyin-stages'].filter((d) => fs.existsSync(path.join(target, d)))
|
|
45
|
-
if (legacyV2.length) {
|
|
46
|
-
console.warn(`⚠ พบโครงเลย์เอาต์เก่า (≤0.2.x): ${legacyV2.join(', ')}
|
|
47
|
-
เวอร์ชันนี้ย้าย core ไปใต้ .warnyin/ และงานจริงไป docs/stages/ — แนะนำย้ายด้วยตัวเองก่อน:
|
|
48
|
-
1. mkdir -p docs/stages && git mv warnyin-stages/* docs/stages/ # งานจริงของคุณ (ปลอดภัย ไม่ถูกแตะโดย installer)
|
|
49
|
-
2. rm -rf workflow warnyin-stages # core เก่า + โฟลเดอร์ที่ย้ายของออกแล้ว
|
|
50
|
-
แล้วรันคำสั่งนี้อีกครั้ง\n`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// โครงเก่า (0.3–0.5.x): ทุกอย่างอยู่ใต้ warnyin/ ที่ root — เวอร์ชันนี้แยกเป็น .warnyin/ (core) + docs/stages (งานจริง)
|
|
54
|
-
const legacyV5 = ['workflow', 'template', 'installer', 'stages'].filter((d) =>
|
|
55
|
-
fs.existsSync(path.join(target, 'warnyin', d)),
|
|
56
|
-
)
|
|
57
|
-
if (legacyV5.length) {
|
|
58
|
-
console.warn(`⚠ พบโครงเลย์เอาต์เก่า (0.3–0.5.x): warnyin/{${legacyV5.join(', ')}}
|
|
59
|
-
เวอร์ชันนี้ย้าย core ไป .warnyin/ และงานจริงไป docs/stages/ — แนะนำย้ายด้วยตัวเองก่อน:
|
|
60
|
-
1. mkdir -p docs/stages && git mv warnyin/stages/* docs/stages/ # งานจริงของคุณ (active + achieved) — ปลอดภัย ไม่ถูกแตะ
|
|
61
|
-
2. rm -rf warnyin # core เก่าทั้งหมด (เวอร์ชันใหม่ installer จะวางที่ .warnyin/)
|
|
62
|
-
แล้วรันคำสั่งนี้อีกครั้ง — installer จะวาง .warnyin/ ชุดใหม่ให้\n`)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// core = playbook กลาง + command + agent + template — เขียนทับได้เมื่อ --update
|
|
66
|
-
const CORE = [
|
|
67
|
-
path.join('.warnyin', 'workflow'),
|
|
68
|
-
path.join('.warnyin', 'template'),
|
|
69
|
-
path.join('.claude', 'commands', 'warnyin'),
|
|
70
|
-
path.join('.claude', 'agents'),
|
|
71
|
-
path.join('.claude', 'skills'),
|
|
72
|
-
]
|
|
73
|
-
// scaffold = พื้นที่ทำงานเปล่าของโปรเจกต์ — installer "สร้างเอง" ไม่ copy tree จาก package
|
|
74
|
-
// (สำคัญ: ถ้า copy docs/stages จาก pkgRoot งานจริงของ repo ต้นทางจะรั่วไป target ทุกครั้ง — ดู verify installer-test-ci)
|
|
75
|
-
const SCAFFOLD_FILES = [
|
|
76
|
-
path.join('docs', 'stages', 'context.md'), // บริบทงานที่จดไว้ (next/discovery/explore อ่าน "ถ้ามี")
|
|
77
|
-
path.join('docs', 'stages', 'achieved', '.gitkeep'), // ให้ git track โฟลเดอร์ archive เปล่า
|
|
78
|
-
]
|
|
79
|
-
|
|
80
|
-
const stats = { created: 0, updated: 0, skipped: 0 }
|
|
81
|
-
|
|
82
|
-
function copyTree(relDir, { overwrite }) {
|
|
83
|
-
const srcDir = path.join(pkgRoot, relDir)
|
|
84
|
-
if (!fs.existsSync(srcDir)) return
|
|
85
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
86
|
-
const rel = path.join(relDir, entry.name)
|
|
87
|
-
if (entry.isDirectory()) {
|
|
88
|
-
copyTree(rel, { overwrite })
|
|
89
|
-
continue
|
|
90
|
-
}
|
|
91
|
-
const src = path.join(pkgRoot, rel)
|
|
92
|
-
const dest = path.join(target, rel)
|
|
93
|
-
const exists = fs.existsSync(dest)
|
|
94
|
-
if (exists && !overwrite) {
|
|
95
|
-
stats.skipped++
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
98
|
-
if (exists && overwrite && fs.readFileSync(src).equals(fs.readFileSync(dest))) {
|
|
99
|
-
stats.skipped++
|
|
100
|
-
continue
|
|
101
|
-
}
|
|
102
|
-
if (!DRY) {
|
|
103
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
104
|
-
fs.copyFileSync(src, dest)
|
|
105
|
-
}
|
|
106
|
-
stats[exists ? 'updated' : 'created']++
|
|
107
|
-
console.log(` ${exists ? '↻' : '+'} ${rel}`)
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/** สร้างโครง scaffold เปล่าของ docs/stages เอง (ไม่ copy จาก package — กันงานจริงของ repo ต้นทางรั่วไป target) — ไม่ทับไฟล์ที่มีอยู่ */
|
|
112
|
-
function ensureScaffold() {
|
|
113
|
-
for (const rel of SCAFFOLD_FILES) {
|
|
114
|
-
const dest = path.join(target, rel)
|
|
115
|
-
if (fs.existsSync(dest)) {
|
|
116
|
-
stats.skipped++
|
|
117
|
-
continue
|
|
118
|
-
}
|
|
119
|
-
if (!DRY) {
|
|
120
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
121
|
-
fs.writeFileSync(dest, '')
|
|
122
|
-
}
|
|
123
|
-
stats.created++
|
|
124
|
-
console.log(` + ${rel}`)
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** seed docs/ ของโปรเจกต์จาก .warnyin/template/docs — ข้ามโฟลเดอร์ template `[...]` (ไว้ให้ /warnyin:init copy เป็นชื่อจริง) และไม่ทับไฟล์ที่มีอยู่ */
|
|
129
|
-
const TEMPLATE_DOCS = path.join('.warnyin', 'template', 'docs')
|
|
130
|
-
function seedDocs(relDir = TEMPLATE_DOCS) {
|
|
131
|
-
const srcDir = path.join(pkgRoot, relDir)
|
|
132
|
-
if (!fs.existsSync(srcDir)) return
|
|
133
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
134
|
-
if (entry.name.startsWith('[')) continue
|
|
135
|
-
const rel = path.join(relDir, entry.name)
|
|
136
|
-
if (entry.isDirectory()) {
|
|
137
|
-
seedDocs(rel)
|
|
138
|
-
continue
|
|
139
|
-
}
|
|
140
|
-
const destRel = path.join('docs', path.relative(TEMPLATE_DOCS, rel))
|
|
141
|
-
const dest = path.join(target, destRel)
|
|
142
|
-
if (fs.existsSync(dest)) {
|
|
143
|
-
stats.skipped++
|
|
144
|
-
continue
|
|
145
|
-
}
|
|
146
|
-
if (!DRY) {
|
|
147
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
148
|
-
fs.copyFileSync(path.join(pkgRoot, rel), dest)
|
|
149
|
-
}
|
|
150
|
-
stats.created++
|
|
151
|
-
console.log(` + ${destRel}`)
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/** CLAUDE.md / AGENTS.md: ไม่มี → สร้างจาก template; มีอยู่แล้วแต่ยังไม่มี workflow → ต่อท้ายเป็น section */
|
|
156
|
-
function installRootDoc(name, srcPath) {
|
|
157
|
-
const dest = path.join(target, name)
|
|
158
|
-
const content = fs.readFileSync(srcPath, 'utf8')
|
|
159
|
-
if (!fs.existsSync(dest)) {
|
|
160
|
-
if (!DRY) fs.writeFileSync(dest, content)
|
|
161
|
-
stats.created++
|
|
162
|
-
console.log(` + ${name}`)
|
|
163
|
-
return
|
|
164
|
-
}
|
|
165
|
-
const existing = fs.readFileSync(dest, 'utf8')
|
|
166
|
-
if (existing.includes('warnyin/workflow/stages/')) {
|
|
167
|
-
stats.skipped++
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
const section = '\n\n' + content.replace(/^#\s[^\n]*\n/, '## Warnyin Standard Workflow\n')
|
|
171
|
-
if (!DRY) fs.appendFileSync(dest, section)
|
|
172
|
-
stats.updated++
|
|
173
|
-
console.log(` ± ${name} (ต่อท้าย section Warnyin Standard Workflow)`)
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
console.log(`Warnyin Standard Workflow → ${target}${DRY ? ' (dry-run)' : ''}\n`)
|
|
177
|
-
|
|
178
|
-
for (const dir of CORE) copyTree(dir, { overwrite: UPDATE })
|
|
179
|
-
ensureScaffold()
|
|
180
|
-
seedDocs()
|
|
181
|
-
installRootDoc('CLAUDE.md', path.join(pkgRoot, '.warnyin', 'installer', 'templates', 'CLAUDE.md'))
|
|
182
|
-
installRootDoc('AGENTS.md', path.join(pkgRoot, 'AGENTS.md'))
|
|
183
|
-
|
|
184
|
-
console.log(`\nสรุป: สร้างใหม่ ${stats.created} · อัปเดต ${stats.updated} · ข้าม (มีอยู่แล้ว) ${stats.skipped}`)
|
|
185
|
-
|
|
186
|
-
if (!UPDATE) {
|
|
187
|
-
console.log(`
|
|
188
|
-
ขั้นถัดไป:
|
|
189
|
-
1. เปิด Claude Code ในโปรเจกต์นี้ แล้วรัน /warnyin:init — ให้ agent วิเคราะห์โปรเจกต์ + เติม docs/
|
|
190
|
-
2. เริ่มงานแรก: /warnyin:discovery <topic> หรือ /warnyin:design <slug> <change>
|
|
191
|
-
|
|
192
|
-
อัปเดต playbook ภายหลัง: npx @warnyin/agents --update`)
|
|
193
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* warnyin-agents installer
|
|
4
|
+
* ติดตั้ง Warnyin Standard Workflow ลงโปรเจกต์ปัจจุบัน (cwd)
|
|
5
|
+
*
|
|
6
|
+
* npx @warnyin/agents ติดตั้ง (ข้ามไฟล์ที่มีอยู่แล้ว)
|
|
7
|
+
* npx @warnyin/agents --update อัปเดต playbook กลาง (เขียนทับเฉพาะไฟล์ core)
|
|
8
|
+
* npx @warnyin/agents --dry-run แสดงว่าจะทำอะไร โดยไม่เขียนไฟล์จริง
|
|
9
|
+
* (ทางสำรองไม่ผ่าน npm: npx github:warnyin/warnyin-agents)
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
import { fileURLToPath } from 'node:url'
|
|
14
|
+
|
|
15
|
+
const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
16
|
+
const target = process.cwd()
|
|
17
|
+
const args = new Set(process.argv.slice(2))
|
|
18
|
+
const UPDATE = args.has('--update')
|
|
19
|
+
const DRY = args.has('--dry-run')
|
|
20
|
+
|
|
21
|
+
if (args.has('--help') || args.has('-h')) {
|
|
22
|
+
console.log(`warnyin-agents — ติดตั้ง Warnyin Standard Workflow ลงโปรเจกต์ปัจจุบัน
|
|
23
|
+
|
|
24
|
+
ใช้งาน:
|
|
25
|
+
npx @warnyin/agents ติดตั้ง (ข้ามไฟล์ที่มีอยู่แล้ว ไม่เขียนทับ)
|
|
26
|
+
npx @warnyin/agents --update อัปเดต playbook กลางเป็นเวอร์ชันล่าสุด
|
|
27
|
+
(เขียนทับเฉพาะ .warnyin/workflow/, .claude/commands/warnyin/,
|
|
28
|
+
template .warnyin/template/stages/[topic] — ไม่แตะ docs/ และงานจริง)
|
|
29
|
+
npx @warnyin/agents --dry-run แสดงรายการไฟล์ที่จะสร้าง/อัปเดต โดยไม่เขียนจริง
|
|
30
|
+
|
|
31
|
+
หลังติดตั้ง: เปิด Claude Code ในโปรเจกต์ แล้วรัน /warnyin:init ให้ agent วิเคราะห์โปรเจกต์ + เติม docs/`)
|
|
32
|
+
process.exit(0)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// guard กัน self-install — เก็บไว้แบบ defensive (zero-cost)
|
|
36
|
+
// หลังย้าย source เข้า src/ → pkgRoot = src/ (sibling ของ bin/) จึงแทบไม่มีทาง === target (repo root / temp sandbox)
|
|
37
|
+
// → guard นี้เป็น no-op โดยตั้งใจในเคสปกติ/sandbox; ยังคงดักได้เฉพาะ edge case ที่ install ลงโฟลเดอร์ที่เป็น src/ เอง
|
|
38
|
+
if (path.resolve(pkgRoot) === path.resolve(target)) {
|
|
39
|
+
console.error('✖ กำลังรันอยู่ใน repo ของ warnyin-agents เอง — ให้ cd ไปที่โปรเจกต์ปลายทางก่อน')
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// โครงเก่า (≤0.2.x): workflow/ + warnyin-stages/ ที่ root — เตือนให้ย้ายเอง ไม่แตะงานจริงของ user
|
|
44
|
+
const legacyV2 = ['workflow', 'warnyin-stages'].filter((d) => fs.existsSync(path.join(target, d)))
|
|
45
|
+
if (legacyV2.length) {
|
|
46
|
+
console.warn(`⚠ พบโครงเลย์เอาต์เก่า (≤0.2.x): ${legacyV2.join(', ')}
|
|
47
|
+
เวอร์ชันนี้ย้าย core ไปใต้ .warnyin/ และงานจริงไป docs/stages/ — แนะนำย้ายด้วยตัวเองก่อน:
|
|
48
|
+
1. mkdir -p docs/stages && git mv warnyin-stages/* docs/stages/ # งานจริงของคุณ (ปลอดภัย ไม่ถูกแตะโดย installer)
|
|
49
|
+
2. rm -rf workflow warnyin-stages # core เก่า + โฟลเดอร์ที่ย้ายของออกแล้ว
|
|
50
|
+
แล้วรันคำสั่งนี้อีกครั้ง\n`)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// โครงเก่า (0.3–0.5.x): ทุกอย่างอยู่ใต้ warnyin/ ที่ root — เวอร์ชันนี้แยกเป็น .warnyin/ (core) + docs/stages (งานจริง)
|
|
54
|
+
const legacyV5 = ['workflow', 'template', 'installer', 'stages'].filter((d) =>
|
|
55
|
+
fs.existsSync(path.join(target, 'warnyin', d)),
|
|
56
|
+
)
|
|
57
|
+
if (legacyV5.length) {
|
|
58
|
+
console.warn(`⚠ พบโครงเลย์เอาต์เก่า (0.3–0.5.x): warnyin/{${legacyV5.join(', ')}}
|
|
59
|
+
เวอร์ชันนี้ย้าย core ไป .warnyin/ และงานจริงไป docs/stages/ — แนะนำย้ายด้วยตัวเองก่อน:
|
|
60
|
+
1. mkdir -p docs/stages && git mv warnyin/stages/* docs/stages/ # งานจริงของคุณ (active + achieved) — ปลอดภัย ไม่ถูกแตะ
|
|
61
|
+
2. rm -rf warnyin # core เก่าทั้งหมด (เวอร์ชันใหม่ installer จะวางที่ .warnyin/)
|
|
62
|
+
แล้วรันคำสั่งนี้อีกครั้ง — installer จะวาง .warnyin/ ชุดใหม่ให้\n`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// core = playbook กลาง + command + agent + template — เขียนทับได้เมื่อ --update
|
|
66
|
+
const CORE = [
|
|
67
|
+
path.join('.warnyin', 'workflow'),
|
|
68
|
+
path.join('.warnyin', 'template'),
|
|
69
|
+
path.join('.claude', 'commands', 'warnyin'),
|
|
70
|
+
path.join('.claude', 'agents'),
|
|
71
|
+
path.join('.claude', 'skills'),
|
|
72
|
+
]
|
|
73
|
+
// scaffold = พื้นที่ทำงานเปล่าของโปรเจกต์ — installer "สร้างเอง" ไม่ copy tree จาก package
|
|
74
|
+
// (สำคัญ: ถ้า copy docs/stages จาก pkgRoot งานจริงของ repo ต้นทางจะรั่วไป target ทุกครั้ง — ดู verify installer-test-ci)
|
|
75
|
+
const SCAFFOLD_FILES = [
|
|
76
|
+
path.join('docs', 'stages', 'context.md'), // บริบทงานที่จดไว้ (next/discovery/explore อ่าน "ถ้ามี")
|
|
77
|
+
path.join('docs', 'stages', 'achieved', '.gitkeep'), // ให้ git track โฟลเดอร์ archive เปล่า
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
const stats = { created: 0, updated: 0, skipped: 0 }
|
|
81
|
+
|
|
82
|
+
function copyTree(relDir, { overwrite }) {
|
|
83
|
+
const srcDir = path.join(pkgRoot, relDir)
|
|
84
|
+
if (!fs.existsSync(srcDir)) return
|
|
85
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
86
|
+
const rel = path.join(relDir, entry.name)
|
|
87
|
+
if (entry.isDirectory()) {
|
|
88
|
+
copyTree(rel, { overwrite })
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
const src = path.join(pkgRoot, rel)
|
|
92
|
+
const dest = path.join(target, rel)
|
|
93
|
+
const exists = fs.existsSync(dest)
|
|
94
|
+
if (exists && !overwrite) {
|
|
95
|
+
stats.skipped++
|
|
96
|
+
continue
|
|
97
|
+
}
|
|
98
|
+
if (exists && overwrite && fs.readFileSync(src).equals(fs.readFileSync(dest))) {
|
|
99
|
+
stats.skipped++
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
if (!DRY) {
|
|
103
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
104
|
+
fs.copyFileSync(src, dest)
|
|
105
|
+
}
|
|
106
|
+
stats[exists ? 'updated' : 'created']++
|
|
107
|
+
console.log(` ${exists ? '↻' : '+'} ${rel}`)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** สร้างโครง scaffold เปล่าของ docs/stages เอง (ไม่ copy จาก package — กันงานจริงของ repo ต้นทางรั่วไป target) — ไม่ทับไฟล์ที่มีอยู่ */
|
|
112
|
+
function ensureScaffold() {
|
|
113
|
+
for (const rel of SCAFFOLD_FILES) {
|
|
114
|
+
const dest = path.join(target, rel)
|
|
115
|
+
if (fs.existsSync(dest)) {
|
|
116
|
+
stats.skipped++
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (!DRY) {
|
|
120
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
121
|
+
fs.writeFileSync(dest, '')
|
|
122
|
+
}
|
|
123
|
+
stats.created++
|
|
124
|
+
console.log(` + ${rel}`)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** seed docs/ ของโปรเจกต์จาก .warnyin/template/docs — ข้ามโฟลเดอร์ template `[...]` (ไว้ให้ /warnyin:init copy เป็นชื่อจริง) และไม่ทับไฟล์ที่มีอยู่ */
|
|
129
|
+
const TEMPLATE_DOCS = path.join('.warnyin', 'template', 'docs')
|
|
130
|
+
function seedDocs(relDir = TEMPLATE_DOCS) {
|
|
131
|
+
const srcDir = path.join(pkgRoot, relDir)
|
|
132
|
+
if (!fs.existsSync(srcDir)) return
|
|
133
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
134
|
+
if (entry.name.startsWith('[')) continue
|
|
135
|
+
const rel = path.join(relDir, entry.name)
|
|
136
|
+
if (entry.isDirectory()) {
|
|
137
|
+
seedDocs(rel)
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
const destRel = path.join('docs', path.relative(TEMPLATE_DOCS, rel))
|
|
141
|
+
const dest = path.join(target, destRel)
|
|
142
|
+
if (fs.existsSync(dest)) {
|
|
143
|
+
stats.skipped++
|
|
144
|
+
continue
|
|
145
|
+
}
|
|
146
|
+
if (!DRY) {
|
|
147
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
148
|
+
fs.copyFileSync(path.join(pkgRoot, rel), dest)
|
|
149
|
+
}
|
|
150
|
+
stats.created++
|
|
151
|
+
console.log(` + ${destRel}`)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** CLAUDE.md / AGENTS.md: ไม่มี → สร้างจาก template; มีอยู่แล้วแต่ยังไม่มี workflow → ต่อท้ายเป็น section */
|
|
156
|
+
function installRootDoc(name, srcPath) {
|
|
157
|
+
const dest = path.join(target, name)
|
|
158
|
+
const content = fs.readFileSync(srcPath, 'utf8')
|
|
159
|
+
if (!fs.existsSync(dest)) {
|
|
160
|
+
if (!DRY) fs.writeFileSync(dest, content)
|
|
161
|
+
stats.created++
|
|
162
|
+
console.log(` + ${name}`)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
const existing = fs.readFileSync(dest, 'utf8')
|
|
166
|
+
if (existing.includes('warnyin/workflow/stages/')) {
|
|
167
|
+
stats.skipped++
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
const section = '\n\n' + content.replace(/^#\s[^\n]*\n/, '## Warnyin Standard Workflow\n')
|
|
171
|
+
if (!DRY) fs.appendFileSync(dest, section)
|
|
172
|
+
stats.updated++
|
|
173
|
+
console.log(` ± ${name} (ต่อท้าย section Warnyin Standard Workflow)`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log(`Warnyin Standard Workflow → ${target}${DRY ? ' (dry-run)' : ''}\n`)
|
|
177
|
+
|
|
178
|
+
for (const dir of CORE) copyTree(dir, { overwrite: UPDATE })
|
|
179
|
+
ensureScaffold()
|
|
180
|
+
seedDocs()
|
|
181
|
+
installRootDoc('CLAUDE.md', path.join(pkgRoot, '.warnyin', 'installer', 'templates', 'CLAUDE.md'))
|
|
182
|
+
installRootDoc('AGENTS.md', path.join(pkgRoot, 'AGENTS.md'))
|
|
183
|
+
|
|
184
|
+
console.log(`\nสรุป: สร้างใหม่ ${stats.created} · อัปเดต ${stats.updated} · ข้าม (มีอยู่แล้ว) ${stats.skipped}`)
|
|
185
|
+
|
|
186
|
+
if (!UPDATE) {
|
|
187
|
+
console.log(`
|
|
188
|
+
ขั้นถัดไป:
|
|
189
|
+
1. เปิด Claude Code ในโปรเจกต์นี้ แล้วรัน /warnyin:init — ให้ agent วิเคราะห์โปรเจกต์ + เติม docs/
|
|
190
|
+
2. เริ่มงานแรก: /warnyin:discovery <topic> หรือ /warnyin:design <slug> <change>
|
|
191
|
+
|
|
192
|
+
อัปเดต playbook ภายหลัง: npx @warnyin/agents --update`)
|
|
193
|
+
}
|