@hupan56/wlkj 2.0.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 (87) hide show
  1. package/bin/cli.js +213 -0
  2. package/package.json +11 -0
  3. package/templates/cli.js +198 -0
  4. package/templates/qoder/commands/wl-code.md +43 -0
  5. package/templates/qoder/commands/wl-commit.md +30 -0
  6. package/templates/qoder/commands/wl-init.md +80 -0
  7. package/templates/qoder/commands/wl-insight.md +51 -0
  8. package/templates/qoder/commands/wl-prd.md +199 -0
  9. package/templates/qoder/commands/wl-report.md +166 -0
  10. package/templates/qoder/commands/wl-search.md +52 -0
  11. package/templates/qoder/commands/wl-spec.md +18 -0
  12. package/templates/qoder/commands/wl-status.md +51 -0
  13. package/templates/qoder/commands/wl-task.md +71 -0
  14. package/templates/qoder/commands/wl-test.md +42 -0
  15. package/templates/qoder/config.toml +5 -0
  16. package/templates/qoder/config.yaml +141 -0
  17. package/templates/qoder/hooks/inject-workflow-state.py +117 -0
  18. package/templates/qoder/hooks/session-start.py +204 -0
  19. package/templates/qoder/rules/wl-pipeline.md +105 -0
  20. package/templates/qoder/scripts/add_session.py +245 -0
  21. package/templates/qoder/scripts/benchmark.py +209 -0
  22. package/templates/qoder/scripts/build_style_index.py +268 -0
  23. package/templates/qoder/scripts/code_index.py +41 -0
  24. package/templates/qoder/scripts/collect_prds.py +31 -0
  25. package/templates/qoder/scripts/common/__init__.py +0 -0
  26. package/templates/qoder/scripts/common/active_task.py +230 -0
  27. package/templates/qoder/scripts/common/atomicio.py +172 -0
  28. package/templates/qoder/scripts/common/developer.py +161 -0
  29. package/templates/qoder/scripts/common/eval_api.py +144 -0
  30. package/templates/qoder/scripts/common/feishu.py +278 -0
  31. package/templates/qoder/scripts/common/filelock.py +211 -0
  32. package/templates/qoder/scripts/common/identity.py +285 -0
  33. package/templates/qoder/scripts/common/mentions.py +134 -0
  34. package/templates/qoder/scripts/common/paths.py +311 -0
  35. package/templates/qoder/scripts/common/reqid.py +218 -0
  36. package/templates/qoder/scripts/common/search_engine.py +205 -0
  37. package/templates/qoder/scripts/common/task_utils.py +342 -0
  38. package/templates/qoder/scripts/common/terms.py +234 -0
  39. package/templates/qoder/scripts/common/utf8.py +38 -0
  40. package/templates/qoder/scripts/context_pack.py +196 -0
  41. package/templates/qoder/scripts/eval_prd.py +225 -0
  42. package/templates/qoder/scripts/export.py +487 -0
  43. package/templates/qoder/scripts/git_sync.py +1087 -0
  44. package/templates/qoder/scripts/handoff.py +22 -0
  45. package/templates/qoder/scripts/init_developer.py +76 -0
  46. package/templates/qoder/scripts/init_doctor.py +527 -0
  47. package/templates/qoder/scripts/install_qoderwork.py +339 -0
  48. package/templates/qoder/scripts/learn.py +67 -0
  49. package/templates/qoder/scripts/notify.py +5 -0
  50. package/templates/qoder/scripts/parse_prds.py +33 -0
  51. package/templates/qoder/scripts/report.py +281 -0
  52. package/templates/qoder/scripts/role.py +39 -0
  53. package/templates/qoder/scripts/run_weekly_update.bat +17 -0
  54. package/templates/qoder/scripts/run_weekly_update.sh +20 -0
  55. package/templates/qoder/scripts/search_index.py +352 -0
  56. package/templates/qoder/scripts/setup.py +453 -0
  57. package/templates/qoder/scripts/setup_weekly_cron.bat +22 -0
  58. package/templates/qoder/scripts/setup_weekly_cron.sh +19 -0
  59. package/templates/qoder/scripts/status.py +389 -0
  60. package/templates/qoder/scripts/syncgate.py +330 -0
  61. package/templates/qoder/scripts/task.py +954 -0
  62. package/templates/qoder/scripts/team.py +29 -0
  63. package/templates/qoder/scripts/team_sync.py +419 -0
  64. package/templates/qoder/scripts/workspace_init.py +102 -0
  65. package/templates/qoder/settings.json +53 -0
  66. package/templates/qoder/skills/design-review/SKILL.md +25 -0
  67. package/templates/qoder/skills/prd-generator/SKILL.md +180 -0
  68. package/templates/qoder/skills/prd-review/SKILL.md +36 -0
  69. package/templates/qoder/skills/prototype-generator/SKILL.md +141 -0
  70. package/templates/qoder/skills/spec-coder/SKILL.md +69 -0
  71. package/templates/qoder/skills/spec-generator/SKILL.md +67 -0
  72. package/templates/qoder/skills/test-generator/SKILL.md +72 -0
  73. package/templates/qoder/skills/wl-commit/SKILL.md +76 -0
  74. package/templates/qoder/skills/wl-init/SKILL.md +67 -0
  75. package/templates/qoder/skills/wl-insight/SKILL.md +81 -0
  76. package/templates/qoder/skills/wl-report/SKILL.md +87 -0
  77. package/templates/qoder/skills/wl-search/SKILL.md +75 -0
  78. package/templates/qoder/skills/wl-status/SKILL.md +61 -0
  79. package/templates/qoder/skills/wl-task/SKILL.md +58 -0
  80. package/templates/qoder/templates/prd-full-template.md +103 -0
  81. package/templates/qoder/templates/prd-quick-template.md +69 -0
  82. package/templates/qoder/templates/prototype-app.html +344 -0
  83. package/templates/qoder/templates/prototype-web.html +310 -0
  84. package/templates/root/AGENTS.md +182 -0
  85. package/templates/root/README-pipeline.md +56 -0
  86. package/templates/root/ROLES.md +85 -0
  87. package/templates/root//346/226/260/346/211/213/346/214/207/345/215/227.md +186 -0
@@ -0,0 +1,29 @@
1
+ # team.py - Team member registration
2
+ import os, json, sys
3
+ NL = chr(10)
4
+ BASE = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
5
+
6
+ def add_member(name, role):
7
+ members_dir = os.path.join(BASE, 'workspace', 'members')
8
+ member_dir = os.path.join(members_dir, name)
9
+ os.makedirs(os.path.join(member_dir, 'journal'), exist_ok=True)
10
+ os.makedirs(os.path.join(member_dir, 'drafts'), exist_ok=True)
11
+ os.makedirs(os.path.join(member_dir, 'inbox'), exist_ok=True)
12
+ info = {'name': name, 'role': role}
13
+ with open(os.path.join(member_dir, 'member.json'), 'w') as f:
14
+ json.dump(info, f, indent=2)
15
+ print('OK: member ' + name + ' (' + role + ') added')
16
+
17
+ def list_members():
18
+ md = os.path.join(BASE, 'workspace', 'members')
19
+ if not os.path.isdir(md): print('No members'); return
20
+ for d in sorted(os.listdir(md)):
21
+ info_f = os.path.join(md, d, 'member.json')
22
+ if os.path.isfile(info_f):
23
+ info = json.load(open(info_f))
24
+ print(d + ' [' + info.get('role', '?') + ']')
25
+
26
+ if __name__ == '__main__':
27
+ if len(sys.argv) < 2: list_members()
28
+ elif sys.argv[1] == 'add' and len(sys.argv) >= 4: add_member(sys.argv[2], sys.argv[3])
29
+ else: list_members()
@@ -0,0 +1,419 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ QODER Team Sync - 无感 git 同步引擎
5
+
6
+ 产品经理完全不需要懂 git。所有 /wl- 命令在产出文件后自动调用本脚本,
7
+ 把"我的产出"同步到团队仓库,并拉取别人的最新产出。
8
+
9
+ Usage:
10
+ python team_sync.py pull # 拉取团队最新 (会话开始/init 时)
11
+ python team_sync.py push [-m "msg"] # 提交并推送我的产出 (发布动作后)
12
+ python team_sync.py status # 查看同步状态 (落后/领先多少)
13
+
14
+ 设计原则 (避免冲突 > 解决冲突):
15
+ - 只 stage 协作产出区: workspace/ + data/docs/ (+ data/index/ 周五更新时)
16
+ - 个人产出天然隔离: 每人只写 workspace/members/{自己}/
17
+ - REQ 编号唯一 -> specs/prd/ 文件名不冲突
18
+ - push 前 pull --rebase --autostash, 失败自动重试 3 次
19
+ - rebase 冲突时: 中止 rebase 保留本地提交, 输出 SYNC_CONFLICT 标记
20
+ (AI 看到标记后负责解决冲突, 人类用户永远不需要碰 git)
21
+
22
+ Exit codes: 0 = ok, 1 = error, 3 = conflict needs AI resolution
23
+ """
24
+
25
+ import os
26
+ import sys
27
+ import subprocess
28
+ from datetime import datetime
29
+
30
+ if sys.platform == 'win32':
31
+ try:
32
+ sys.stdout.reconfigure(encoding='utf-8')
33
+ except (AttributeError, IOError):
34
+ pass
35
+
36
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
37
+ from common.paths import get_repo_root, get_developer
38
+ from common.filelock import FileLock, LockTimeoutError
39
+ from syncgate import run_gates, SAFE_EXTENSIONS
40
+
41
+ BASE = str(get_repo_root())
42
+
43
+ # 协作产出区: 自动同步只碰这些路径, 永远不动 .qoder/ 引擎和源码区
44
+ SYNC_SCOPES = ['workspace', 'data/docs', 'data/index']
45
+
46
+ PULL_MARKER = os.path.join(BASE, '.qoder', '.runtime', 'last-pull')
47
+ PUSH_LOCK = os.path.join(BASE, '.qoder', '.runtime', 'team-sync.lock')
48
+
49
+ MAX_PUSH_RETRY = 3
50
+
51
+
52
+ def git(*args, check=False):
53
+ """git 命令包装。git 未安装时返回 rc=127 的伪结果, 不崩溃。"""
54
+ try:
55
+ r = subprocess.run(['git'] + list(args), cwd=BASE, capture_output=True,
56
+ text=True, encoding='utf-8', errors='replace')
57
+ except FileNotFoundError:
58
+ # git 未安装 — 返回伪 CompletedProcess, 让上层把"无 git"当"无远端/无仓库"处理
59
+ import types
60
+ r = types.SimpleNamespace(returncode=127, stdout='', stderr='git not installed',
61
+ encoding='utf-8')
62
+ if check and r.returncode != 0:
63
+ print('git {} failed: {}'.format(' '.join(args), r.stderr.strip()[:300]))
64
+ return r
65
+
66
+
67
+ def current_branch():
68
+ r = git('rev-parse', '--abbrev-ref', 'HEAD')
69
+ return r.stdout.strip() if r.returncode == 0 else None
70
+
71
+
72
+ def has_remote():
73
+ r = git('remote')
74
+ return 'origin' in r.stdout.split()
75
+
76
+
77
+ def rebase_in_progress():
78
+ return os.path.isdir(os.path.join(BASE, '.git', 'rebase-merge')) or \
79
+ os.path.isdir(os.path.join(BASE, '.git', 'rebase-apply'))
80
+
81
+
82
+ def touch_pull_marker():
83
+ os.makedirs(os.path.dirname(PULL_MARKER), exist_ok=True)
84
+ with open(PULL_MARKER, 'w', encoding='utf-8') as f:
85
+ f.write(datetime.now().isoformat())
86
+
87
+
88
+ def _notify_prd_publications(staged_files):
89
+ """D2: 检测 staged 里的 PRD 发布, 推飞书通知。
90
+
91
+ PRD 路径模式: data/docs/prd/REQ-*.md 或 workspace/specs/prd/REQ-*.md
92
+ 只对"新增"的 PRD 推送 (修改已有 PRD 不通知, 避免噪音)。
93
+ """
94
+ import re as _re
95
+ try:
96
+ from common.feishu import notify_prd_published, is_enabled
97
+ except ImportError:
98
+ return
99
+ if not is_enabled('prd_published'):
100
+ return
101
+
102
+ req_pattern = _re.compile(r'REQ-(\d{4})-(\d{3,4})', _re.IGNORECASE)
103
+ published = []
104
+ for f in staged_files:
105
+ norm = f.replace('\\', '/').lower()
106
+ if '/prd/' not in norm:
107
+ continue
108
+ if not f.lower().endswith('.md'):
109
+ continue
110
+ base = os.path.basename(f)
111
+ m = req_pattern.search(base)
112
+ if not m:
113
+ continue
114
+ req_id = 'REQ-{}-{:03d}'.format(m.group(1), int(m.group(2)))
115
+ # 提取标题 (从文件第一行)
116
+ title = base
117
+ try:
118
+ full = os.path.join(BASE, f) if not os.path.isabs(f) else f
119
+ if os.path.isfile(full):
120
+ with open(full, encoding='utf-8', errors='replace') as fh:
121
+ for line in fh:
122
+ line = line.strip().lstrip('#').strip()
123
+ if line:
124
+ title = line[:60]
125
+ break
126
+ except Exception:
127
+ pass
128
+ published.append((req_id, title))
129
+
130
+ for req_id, title in published:
131
+ try:
132
+ notify_prd_published(req_id, title, eval_pct=None)
133
+ except Exception:
134
+ pass
135
+
136
+
137
+ def report_conflict(stderr):
138
+ """rebase 冲突: 中止并输出结构化标记, 由会话中的 AI 接手解决"""
139
+ git('rebase', '--abort')
140
+ print('SYNC_CONFLICT: 自动同步遇到冲突, 已安全回退 (本地产出未丢失)。')
141
+ print('AI 请执行: git pull --rebase origin {} 并解决冲突后 git push。'.format(current_branch()))
142
+ print('冲突详情: ' + stderr.strip()[:300])
143
+ return 3
144
+
145
+
146
+ def do_pull(quiet=False):
147
+ """拉取团队最新。安全: autostash 保护未提交改动。"""
148
+ if rebase_in_progress():
149
+ print('SYNC_CONFLICT: 仓库处于未完成的 rebase 状态, AI 请先处理 (git rebase --abort 或 --continue)。')
150
+ return 3
151
+ if not has_remote():
152
+ if not quiet:
153
+ print('No remote configured - local-only mode, skip pull.')
154
+ touch_pull_marker()
155
+ return 0
156
+
157
+ branch = current_branch()
158
+ if not branch or branch == 'HEAD':
159
+ print('ERROR: detached HEAD or no branch - AI please check.')
160
+ return 1
161
+
162
+ r = git('pull', '--rebase', '--autostash', 'origin', branch)
163
+ if r.returncode != 0:
164
+ if 'CONFLICT' in (r.stdout + r.stderr) or rebase_in_progress():
165
+ return report_conflict(r.stderr or r.stdout)
166
+ print('Pull failed (network/remote?): ' + (r.stderr or r.stdout).strip()[:200])
167
+ print('继续离线工作, 产出不会丢失, 下次同步会自动补推。')
168
+ return 1
169
+
170
+ touch_pull_marker()
171
+ out = (r.stdout or '').strip()
172
+ if not quiet:
173
+ if 'up to date' in out.lower() or 'up-to-date' in out.lower():
174
+ print('Already up to date.')
175
+ else:
176
+ print('Pulled latest from team.')
177
+ return 0
178
+
179
+
180
+ def summarize_changes():
181
+ """生成人类可读的提交摘要 (基于 staged 文件)"""
182
+ r = git('diff', '--cached', '--name-only')
183
+ files = [f for f in r.stdout.strip().splitlines() if f.strip()]
184
+ if not files:
185
+ return None, 0
186
+ cats = {'prd': 0, 'prototype': 0, 'task': 0, 'journal': 0, 'index': 0, 'other': 0}
187
+ for f in files:
188
+ fl = f.lower()
189
+ if 'prototype' in fl and fl.endswith('.html'):
190
+ cats['prototype'] += 1
191
+ elif '/prd/' in fl or fl.split('/')[-1].startswith(('req-', 'prd-')):
192
+ cats['prd'] += 1
193
+ elif '/tasks/' in fl:
194
+ cats['task'] += 1
195
+ elif '/journal/' in fl:
196
+ cats['journal'] += 1
197
+ elif 'data/index/' in fl:
198
+ cats['index'] += 1
199
+ else:
200
+ cats['other'] += 1
201
+ parts = []
202
+ label = {'prd': 'PRD', 'prototype': '原型', 'task': '任务', 'journal': '日志',
203
+ 'index': '索引', 'other': '其他'}
204
+ for k, v in cats.items():
205
+ if v:
206
+ parts.append('{}x{}'.format(label[k], v))
207
+ return ' '.join(parts), len(files)
208
+
209
+
210
+ def _stage_scopes_safely(skip_secret=False):
211
+ """用 allowlist 替代危险的 git add -A。
212
+
213
+ 只 stage SYNC_SCOPES 下、扩展名在 SAFE_EXTENSIONS 里的文件。
214
+ 其他文件 (如误放的 .env/.zip/.docx) 被跳过并打印警告。
215
+
216
+ Returns:
217
+ (staged_count, skipped_files)
218
+ """
219
+ staged = 0
220
+ skipped = []
221
+ for scope in SYNC_SCOPES:
222
+ scope_path = os.path.join(BASE, scope)
223
+ if not os.path.isdir(scope_path):
224
+ continue
225
+ # 用 git ls-files 拿已跟踪的 + git status 拿未跟踪的, 逐个判断扩展名
226
+ # 已跟踪文件: 直接 add (它们已经在版本控制, 不可能是新秘密)
227
+ r = git('ls-files', '--', scope)
228
+ tracked = [f for f in r.stdout.strip().splitlines() if f.strip()]
229
+ for f in tracked:
230
+ git('add', '--', f)
231
+ staged += 1
232
+ # 未跟踪的新文件: 按扩展名过滤
233
+ r = git('status', '--porcelain', '--', scope)
234
+ for line in r.stdout.strip().splitlines():
235
+ if not line.strip():
236
+ continue
237
+ # porcelain 格式: "XY filepath" (XY 是 2 字符状态)
238
+ status = line[:2]
239
+ fpath = line[3:]
240
+ if status[0] == '?' or status[1] == '?': # 未跟踪
241
+ ext = os.path.splitext(fpath)[1].lower()
242
+ if ext in SAFE_EXTENSIONS:
243
+ git('add', '--', fpath)
244
+ staged += 1
245
+ else:
246
+ skipped.append(fpath)
247
+ elif 'D' not in status:
248
+ # 修改/重命名等 (非删除) —— 已跟踪, 直接 add
249
+ # 重命名格式 "R old -> new", 取 new
250
+ if '->' in fpath:
251
+ fpath = fpath.split('->')[-1].strip().strip('"')
252
+ git('add', '--', fpath)
253
+ staged += 1
254
+ if skipped:
255
+ print('[gate] 跳过 {} 个非白名单文件 (可能含二进制/秘密, 手动处理):'.format(len(skipped)))
256
+ for s in skipped[:10]:
257
+ print(' ' + s)
258
+ return staged, skipped
259
+
260
+
261
+ def do_push(message=None, skip_eval=False, skip_secret=False):
262
+ """提交我的产出并推送。零信任门禁 + 文件锁 + rebase 重试。
263
+
264
+ 门禁 (任一失败则拒绝 commit/push):
265
+ 1. 身份强制 (已注册 + 有本地密钥)
266
+ 2. git 作者与注册身份一致
267
+ 3. 秘密扫描 (AWS/GitHub/私钥/密码)
268
+ 4. EVA PRD 质量门禁 (>=80%)
269
+ """
270
+ if rebase_in_progress():
271
+ print('SYNC_CONFLICT: 仓库处于未完成的 rebase 状态, AI 请先处理。')
272
+ return 3
273
+
274
+ dev = get_developer()
275
+
276
+ # === 文件锁: 串行化 push, 避免并发 stage/commit 交叉 ===
277
+ os.makedirs(os.path.dirname(PUSH_LOCK), exist_ok=True)
278
+ try:
279
+ lock_ctx = FileLock(PUSH_LOCK, timeout=60, stale_seconds=300)
280
+ lock_ctx.acquire()
281
+ except LockTimeoutError as e:
282
+ print('SYNC_BUSY: 另一个同步正在进行, 等待超时。稍后重试。')
283
+ print(' ' + str(e))
284
+ return 2 # lock contention
285
+
286
+ try:
287
+ return _do_push_locked(message, dev, skip_eval, skip_secret)
288
+ finally:
289
+ lock_ctx.release()
290
+
291
+
292
+ def _do_push_locked(message, dev, skip_eval, skip_secret):
293
+ """锁内的实际 push 逻辑。"""
294
+ # 1. allowlist staging
295
+ staged_count, skipped = _stage_scopes_safely(skip_secret)
296
+
297
+ # 2. 取 staged 文件清单 (给门禁用)
298
+ r = git('diff', '--cached', '--name-only')
299
+ staged_files = [f for f in r.stdout.strip().splitlines() if f.strip()]
300
+
301
+ summary, count = summarize_changes()
302
+
303
+ # 3. 零信任门禁 (只在有东西要提交时才跑, 避免空提交报错)
304
+ if staged_files:
305
+ passed, reason = run_gates(
306
+ staged_files, dev, BASE,
307
+ skip_eval=skip_eval, skip_secret=skip_secret,
308
+ )
309
+ if not passed:
310
+ print(reason)
311
+ # 撤销 staging (用户修完再来)
312
+ git('reset', 'HEAD', '--')
313
+ return 1
314
+
315
+ # 4. commit
316
+ if summary:
317
+ msg = message or '[wl-sync] {}: {}'.format(dev or 'unknown', summary)
318
+ r = git('commit', '-m', msg)
319
+ if r.returncode != 0:
320
+ print('Commit failed: ' + (r.stderr or r.stdout).strip()[:200])
321
+ return 1
322
+ print('Committed {} files: {}'.format(count, msg))
323
+ else:
324
+ print('Nothing new to commit.')
325
+
326
+ if not has_remote():
327
+ print('No remote configured - committed locally only.')
328
+ return 0
329
+
330
+ branch = current_branch()
331
+ if not branch or branch == 'HEAD':
332
+ print('ERROR: detached HEAD - AI please check.')
333
+ return 1
334
+
335
+ # 5. 检查是否有待推送的提交
336
+ git('fetch', 'origin', branch)
337
+ r = git('rev-list', '--count', 'origin/{}..HEAD'.format(branch))
338
+ ahead = int(r.stdout.strip() or 0) if r.returncode == 0 else 1
339
+ if ahead == 0:
340
+ print('Already in sync with team.')
341
+ touch_pull_marker()
342
+ return 0
343
+
344
+ # 6. pull --rebase + push, 失败重试
345
+ for attempt in range(1, MAX_PUSH_RETRY + 1):
346
+ r = git('pull', '--rebase', '--autostash', 'origin', branch)
347
+ if r.returncode != 0:
348
+ if 'CONFLICT' in (r.stdout + r.stderr) or rebase_in_progress():
349
+ return report_conflict(r.stderr or r.stdout)
350
+ print('Pull failed (attempt {}): {}'.format(attempt, (r.stderr or r.stdout).strip()[:150]))
351
+
352
+ r = git('push', 'origin', branch)
353
+ if r.returncode == 0:
354
+ touch_pull_marker()
355
+ print('Synced to team. (attempt {})'.format(attempt))
356
+ # D2: 检测 PRD 发布并推飞书通知
357
+ _notify_prd_publications(staged_files)
358
+ return 0
359
+ if attempt < MAX_PUSH_RETRY:
360
+ print('Push rejected (someone pushed first?), retrying...')
361
+
362
+ print('Push failed after {} attempts: 产出已本地提交不会丢失, 下次同步自动补推。'.format(MAX_PUSH_RETRY))
363
+ return 1
364
+
365
+
366
+ def do_status():
367
+ branch = current_branch()
368
+ print('Branch: {}'.format(branch))
369
+ print('Developer: {}'.format(get_developer() or 'NOT SET'))
370
+
371
+ if not has_remote():
372
+ print('Remote: none (local-only mode)')
373
+ return 0
374
+
375
+ git('fetch', 'origin', branch)
376
+ r = git('rev-list', '--left-right', '--count', 'origin/{}...HEAD'.format(branch))
377
+ if r.returncode == 0 and r.stdout.strip():
378
+ behind, ahead = r.stdout.split()
379
+ print('Ahead (待推送): {}, Behind (待拉取): {}'.format(ahead, behind))
380
+
381
+ r = git('status', '--short', '--', *SYNC_SCOPES)
382
+ dirty = [l for l in r.stdout.strip().splitlines() if l.strip()]
383
+ print('未同步的本地产出: {} 个文件'.format(len(dirty)))
384
+ for l in dirty[:10]:
385
+ print(' ' + l)
386
+
387
+ if os.path.isfile(PULL_MARKER):
388
+ with open(PULL_MARKER, encoding='utf-8') as f:
389
+ print('Last pull: ' + f.read().strip())
390
+ return 0
391
+
392
+
393
+ def main():
394
+ args = sys.argv[1:]
395
+ cmd = args[0] if args else 'status'
396
+ message = None
397
+ if '-m' in args:
398
+ i = args.index('-m')
399
+ if i + 1 < len(args):
400
+ message = args[i + 1]
401
+ skip_eval = '--skip-eval' in args
402
+ skip_secret = '--skip-secret' in args
403
+
404
+ if not os.path.isdir(os.path.join(BASE, '.git')):
405
+ print('Not a git repository - skip sync.')
406
+ return 0
407
+
408
+ if cmd == 'pull':
409
+ return do_pull()
410
+ if cmd == 'push':
411
+ return do_push(message, skip_eval=skip_eval, skip_secret=skip_secret)
412
+ if cmd == 'status':
413
+ return do_status()
414
+ print('Usage: team_sync.py pull|push|status [-m "message"] [--skip-eval] [--skip-secret]')
415
+ return 1
416
+
417
+
418
+ if __name__ == '__main__':
419
+ sys.exit(main())
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ QODER Pipeline - Workspace Init
5
+
6
+ Creates personal workspace under team workspace:
7
+ workspace/members/{developer}/
8
+ journal/
9
+ drafts/
10
+ inbox/
11
+
12
+ Also syncs latest from git (git pull).
13
+
14
+ Usage:
15
+ python .qoder/scripts/workspace_init.py <developer-name>
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import os
21
+ import subprocess
22
+ import sys
23
+ from datetime import datetime
24
+ from pathlib import Path
25
+
26
+ # Add scripts to path
27
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
28
+
29
+ from common.paths import get_repo_root, get_developer, FILE_DEVELOPER
30
+
31
+
32
+ def init_workspace(name: str) -> bool:
33
+ """Initialize personal workspace under team workspace."""
34
+ if not name:
35
+ print("Error: developer name required", file=sys.stderr)
36
+ return False
37
+
38
+ repo_root = get_repo_root()
39
+
40
+ # 1. Create .developer file in .qoder/ (engine)
41
+ dev_file = repo_root / ".qoder" / FILE_DEVELOPER
42
+ if dev_file.is_file():
43
+ existing = get_developer(repo_root)
44
+ if existing:
45
+ print(f"Developer already: {existing}")
46
+ print(f"Workspace: workspace/members/{existing}/")
47
+ return True
48
+
49
+ dev_file.write_text(
50
+ f"name={name}\ninitialized_at={datetime.now().isoformat()}\n",
51
+ encoding="utf-8"
52
+ )
53
+
54
+ # 2. Create personal workspace under team workspace
55
+ personal = repo_root / "workspace" / "members" / name
56
+ for sub in ["journal", "drafts", "inbox"]:
57
+ (personal / sub).mkdir(parents=True, exist_ok=True)
58
+
59
+ # 3. Create initial journal
60
+ today = datetime.now().strftime("%Y-%m-%d")
61
+ journal = personal / "journal" / f"journal-1.md"
62
+ if not journal.exists():
63
+ journal.write_text(
64
+ f"# Journal - {name}\n\n> Started: {today}\n\n---\n\n",
65
+ encoding="utf-8"
66
+ )
67
+
68
+ # 4. Git pull (sync latest)
69
+ try:
70
+ result = subprocess.run(
71
+ ["git", "pull"],
72
+ capture_output=True, text=True, cwd=str(repo_root), timeout=30
73
+ )
74
+ pull_msg = result.stdout.strip() if result.returncode == 0 else "already up to date"
75
+ except Exception:
76
+ pull_msg = "skipped (no remote)"
77
+
78
+ print(f"Developer: {name}")
79
+ print(f"Personal workspace: workspace/members/{name}/")
80
+ print(f" journal/ session logs")
81
+ print(f" drafts/ personal drafts")
82
+ print(f" inbox/ pending items")
83
+ print(f"Git sync: {pull_msg}")
84
+ return True
85
+
86
+
87
+ def main():
88
+ if len(sys.argv) < 2:
89
+ print("Usage: python .qoder/scripts/workspace_init.py <your-name>")
90
+ print("Example: python .qoder/scripts/workspace_init.py zhangsan")
91
+ sys.exit(1)
92
+
93
+ name = sys.argv[1]
94
+ if init_workspace(name):
95
+ print("\nNext: say 'help me generate PRD' in Codex to start pipeline")
96
+ sys.exit(0)
97
+ else:
98
+ sys.exit(1)
99
+
100
+
101
+ if __name__ == "__main__":
102
+ main()
@@ -0,0 +1,53 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(*)",
5
+ "Read(*)",
6
+ "Write(*)",
7
+ "Edit(*)",
8
+ "Glob(*)",
9
+ "Grep(*)",
10
+ "WebFetch(*)",
11
+ "WebSearch(*)",
12
+ "mcp__*"
13
+ ],
14
+ "deny": []
15
+ },
16
+ "hooks": {
17
+ "SessionStart": [
18
+ {
19
+ "matcher": "startup",
20
+ "hooks": [
21
+ {
22
+ "command": "python .qoder/hooks/session-start.py || python3 .qoder/hooks/session-start.py"
23
+ }
24
+ ]
25
+ },
26
+ {
27
+ "matcher": "clear",
28
+ "hooks": [
29
+ {
30
+ "command": "python .qoder/hooks/session-start.py || python3 .qoder/hooks/session-start.py"
31
+ }
32
+ ]
33
+ },
34
+ {
35
+ "matcher": "compact",
36
+ "hooks": [
37
+ {
38
+ "command": "python .qoder/hooks/session-start.py || python3 .qoder/hooks/session-start.py"
39
+ }
40
+ ]
41
+ }
42
+ ],
43
+ "UserPromptSubmit": [
44
+ {
45
+ "hooks": [
46
+ {
47
+ "command": "python .qoder/hooks/inject-workflow-state.py || python3 .qoder/hooks/inject-workflow-state.py"
48
+ }
49
+ ]
50
+ }
51
+ ]
52
+ }
53
+ }
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: design-review
3
+ description: "评审设计交付物的完整性。Review design artifacts for completeness. 用户说'评审设计''设计稿看一下''检查交互稿'时触发。"
4
+ trigger: "user invokes /review design, asks to check design, or designer submits design artifacts"
5
+ ---
6
+
7
+ # Design Review Skill
8
+
9
+ ## ⚙️ 自取上下文(Quest / QoderWork 无 hook 注入,必须自读)
10
+
11
+ - `.qoder/.developer` — 当前评审人
12
+ - 设计文件路径由用户指定,或扫 `workspace/members/{dev}/drafts/prototype-*.html`
13
+
14
+ ## 检查项 (Checklist)
15
+ - [ ] 组件规格完整(components.json)
16
+ - [ ] 交互流程覆盖所有状态(interaction-flow.md)
17
+ - [ ] 技术约束已标注
18
+ - [ ] 响应式/设计 Token 已定义
19
+ - [ ] 符合 PRD 需求
20
+ - [ ] **图标来自真源(data/index/icon-reference.json),无 emoji**(硬性)
21
+ - [ ] 颜色来自真源(Web: vben-style-reference.json;APP: Vant 变量)
22
+
23
+ ## 输出
24
+
25
+ 报告:PASS 或 列出需修复的问题清单