@aipper/aiws-spec 0.0.25 → 0.0.27

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 (101) hide show
  1. package/docs/aiws-governance-positioning.md +1 -1
  2. package/docs/cli-interface.md +10 -5
  3. package/docs/collaboration-artifacts.md +1 -1
  4. package/docs/spec-contract.md +5 -12
  5. package/docs/workflow-governance-rules.json +18 -4
  6. package/docs/workflow-governance-rules.md +8 -6
  7. package/docs/workflow-router-rules.json +2 -2
  8. package/docs/workflow-router-rules.md +2 -2
  9. package/docs/workflow-stage-contracts.json +9 -9
  10. package/docs/workflow-stage-contracts.md +5 -5
  11. package/package.json +1 -1
  12. package/templates/workspace/.agents/skills/using-aiws/SKILL.md +5 -1
  13. package/templates/workspace/.agents/skills/ws-commit/SKILL.md +4 -1
  14. package/templates/workspace/.agents/skills/ws-deliver/SKILL.md +3 -4
  15. package/templates/workspace/.agents/skills/ws-dev-lite/SKILL.md +58 -0
  16. package/templates/workspace/.agents/skills/ws-finish/SKILL.md +13 -16
  17. package/templates/workspace/.agents/skills/ws-handoff/SKILL.md +8 -8
  18. package/templates/workspace/.aiws/manifest.json +2 -6
  19. package/templates/workspace/.claude/commands/using-aiws.md +3 -2
  20. package/templates/workspace/.claude/commands/ws-dev-lite.md +31 -0
  21. package/templates/workspace/.claude/commands/ws-finish.md +9 -5
  22. package/templates/workspace/.claude/commands/ws-handoff.md +3 -2
  23. package/templates/workspace/.claude/commands/ws-review.md +2 -2
  24. package/templates/workspace/.claude/skills/using-aiws/SKILL.md +5 -1
  25. package/templates/workspace/.claude/skills/ws-commit/SKILL.md +4 -1
  26. package/templates/workspace/.claude/skills/ws-deliver/SKILL.md +3 -4
  27. package/templates/workspace/.claude/skills/ws-dev-lite/SKILL.md +58 -0
  28. package/templates/workspace/.claude/skills/ws-finish/SKILL.md +13 -16
  29. package/templates/workspace/.claude/skills/ws-handoff/SKILL.md +8 -8
  30. package/templates/workspace/.githooks/commit-msg +10 -0
  31. package/templates/workspace/.opencode/command/using-aiws.md +3 -2
  32. package/templates/workspace/.opencode/command/ws-dev-lite.md +34 -0
  33. package/templates/workspace/.opencode/command/ws-finish.md +9 -5
  34. package/templates/workspace/.opencode/command/ws-handoff.md +4 -3
  35. package/templates/workspace/.opencode/command/ws-review.md +2 -2
  36. package/templates/workspace/.opencode/commands/using-aiws.md +3 -2
  37. package/templates/workspace/.opencode/commands/ws-dev-lite.md +34 -0
  38. package/templates/workspace/.opencode/commands/ws-finish.md +9 -5
  39. package/templates/workspace/.opencode/commands/ws-handoff.md +4 -3
  40. package/templates/workspace/.opencode/commands/ws-review.md +2 -2
  41. package/templates/workspace/.opencode/skills/using-aiws/SKILL.md +5 -1
  42. package/templates/workspace/.opencode/skills/ws-commit/SKILL.md +4 -1
  43. package/templates/workspace/.opencode/skills/ws-deliver/SKILL.md +3 -4
  44. package/templates/workspace/.opencode/skills/ws-dev-lite/SKILL.md +58 -0
  45. package/templates/workspace/.opencode/skills/ws-finish/SKILL.md +13 -16
  46. package/templates/workspace/.opencode/skills/ws-handoff/SKILL.md +8 -8
  47. package/templates/workspace/AGENTS.md +5 -3
  48. package/templates/workspace/AI_PROJECT.md +1 -1
  49. package/templates/workspace/AI_WORKSPACE.md +1 -1
  50. package/templates/workspace/changes/README.md +10 -6
  51. package/templates/workspace/changes/templates/tasks.md +1 -1
  52. package/templates/workspace/manifest.json +63 -80
  53. package/templates/workspace/memory-bank/README.md +1 -2
  54. package/templates/workspace/tools/ws_change_check.py +224 -7
  55. package/templates/workspace/.iflow/agents/feature-reviewer.md +0 -27
  56. package/templates/workspace/.iflow/agents/requirements-analyst.md +0 -24
  57. package/templates/workspace/.iflow/agents/server-commit-manager.md +0 -28
  58. package/templates/workspace/.iflow/agents/server-fix-implementer.md +0 -31
  59. package/templates/workspace/.iflow/agents/server-test-planner.md +0 -28
  60. package/templates/workspace/.iflow/agents/server-test-triager.md +0 -30
  61. package/templates/workspace/.iflow/commands/aiws-init.toml +0 -24
  62. package/templates/workspace/.iflow/commands/aiws-rollback.toml +0 -18
  63. package/templates/workspace/.iflow/commands/aiws-update.toml +0 -23
  64. package/templates/workspace/.iflow/commands/aiws-validate.toml +0 -18
  65. package/templates/workspace/.iflow/commands/server-commit.toml +0 -27
  66. package/templates/workspace/.iflow/commands/server-drain.toml +0 -99
  67. package/templates/workspace/.iflow/commands/server-fix-and-commit.toml +0 -27
  68. package/templates/workspace/.iflow/commands/server-fix.toml +0 -65
  69. package/templates/workspace/.iflow/commands/server-test-plan.toml +0 -62
  70. package/templates/workspace/.iflow/commands/server-test.toml +0 -58
  71. package/templates/workspace/.iflow/commands/server-triage.toml +0 -38
  72. package/templates/workspace/.iflow/commands/server_test-plan.toml +0 -12
  73. package/templates/workspace/.iflow/commands/server_test.toml +0 -12
  74. package/templates/workspace/.iflow/commands/ws-analyze.toml +0 -33
  75. package/templates/workspace/.iflow/commands/ws-commit.toml +0 -46
  76. package/templates/workspace/.iflow/commands/ws-contract-check.toml +0 -69
  77. package/templates/workspace/.iflow/commands/ws-deliver.toml +0 -58
  78. package/templates/workspace/.iflow/commands/ws-dev.toml +0 -34
  79. package/templates/workspace/.iflow/commands/ws-doctor.toml +0 -141
  80. package/templates/workspace/.iflow/commands/ws-env-doctor.toml +0 -74
  81. package/templates/workspace/.iflow/commands/ws-feature-deliver.toml +0 -44
  82. package/templates/workspace/.iflow/commands/ws-feature-plan.toml +0 -47
  83. package/templates/workspace/.iflow/commands/ws-finish.toml +0 -54
  84. package/templates/workspace/.iflow/commands/ws-init.toml +0 -53
  85. package/templates/workspace/.iflow/commands/ws-memory-bank-init.toml +0 -100
  86. package/templates/workspace/.iflow/commands/ws-migrate.toml +0 -59
  87. package/templates/workspace/.iflow/commands/ws-preflight.toml +0 -30
  88. package/templates/workspace/.iflow/commands/ws-pull.toml +0 -47
  89. package/templates/workspace/.iflow/commands/ws-push.toml +0 -40
  90. package/templates/workspace/.iflow/commands/ws-req-change.toml +0 -64
  91. package/templates/workspace/.iflow/commands/ws-req-contract-sync.toml +0 -25
  92. package/templates/workspace/.iflow/commands/ws-req-contract-validate.toml +0 -16
  93. package/templates/workspace/.iflow/commands/ws-req-flow-sync.toml +0 -36
  94. package/templates/workspace/.iflow/commands/ws-req-review.toml +0 -56
  95. package/templates/workspace/.iflow/commands/ws-review.toml +0 -33
  96. package/templates/workspace/.iflow/commands/ws-rule.toml +0 -43
  97. package/templates/workspace/.iflow/commands/ws-submodule-setup.toml +0 -32
  98. package/templates/workspace/tools/iflow_watchdog.sh +0 -138
  99. package/templates/workspace/tools/install_iflow_watchdog_systemd_user.sh +0 -118
  100. package/templates/workspace/tools/systemd/iflow-watchdog@.service +0 -16
  101. package/templates/workspace/tools/systemd/iflow-watchdog@.timer +0 -11
@@ -16,6 +16,13 @@ from typing import Any, Dict, List, Optional, Tuple
16
16
 
17
17
  CHANGE_BRANCH_RE = re.compile(r"^(change|changes|ws|ws-change)/([a-z0-9]+(?:-[a-z0-9]+)*)$")
18
18
  CHANGE_ID_RE = re.compile(r"^[a-z0-9]+(?:-[a-z0-9]+)*$")
19
+ PIN_BRANCH_RE = re.compile(r"^aiws/pin/.+$")
20
+ FINISH_DONE_COMPLETED_CLEANUPS = {
21
+ "removed",
22
+ "pruned-missing",
23
+ "skipped:no_separate_change_worktree",
24
+ "skipped:current_worktree",
25
+ }
19
26
 
20
27
 
21
28
  def eprint(msg: str) -> None:
@@ -93,6 +100,53 @@ def parse_submodule_targets(path: Path) -> Tuple[Dict[str, Tuple[str, str]], Lis
93
100
  return parsed, perr
94
101
 
95
102
 
103
+ def git_text(root: Path, args: List[str]) -> Tuple[int, str, str]:
104
+ try:
105
+ res = subprocess.run(
106
+ ["git", "-C", str(root), *args],
107
+ check=False,
108
+ stdout=subprocess.PIPE,
109
+ stderr=subprocess.PIPE,
110
+ text=True,
111
+ )
112
+ return (res.returncode, (res.stdout or "").strip(), (res.stderr or "").strip())
113
+ except Exception as exc:
114
+ return (1, "", str(exc))
115
+
116
+
117
+ def resolve_change_gitlink_commit(root: Path, change_id: str, sub_path: str) -> str:
118
+ for spec in (f"change/{change_id}:{sub_path}", f"HEAD:{sub_path}"):
119
+ code, out, _ = git_text(root, ["rev-parse", spec])
120
+ if code == 0 and out:
121
+ return out
122
+ return ""
123
+
124
+
125
+ def git_ref_exists(root: Path, ref: str) -> bool:
126
+ code, _, _ = git_text(root, ["show-ref", "--verify", "--quiet", ref])
127
+ return code == 0
128
+
129
+
130
+ def git_revision_exists(root: Path, rev: str) -> bool:
131
+ code, _, _ = git_text(root, ["cat-file", "-e", f"{rev}^{{commit}}"])
132
+ return code == 0
133
+
134
+
135
+ def git_is_ancestor(root: Path, older: str, newer: str) -> bool:
136
+ code, _, _ = git_text(root, ["merge-base", "--is-ancestor", older, newer])
137
+ return code == 0
138
+
139
+
140
+ def resolve_branch_ref(repo_root: Path, remote: str, branch: str) -> str:
141
+ remote_ref = f"refs/remotes/{remote}/{branch}"
142
+ if git_ref_exists(repo_root, remote_ref):
143
+ return remote_ref
144
+ local_ref = f"refs/heads/{branch}"
145
+ if git_ref_exists(repo_root, local_ref):
146
+ return local_ref
147
+ return ""
148
+
149
+
96
150
  def sha256(path: Path) -> str:
97
151
  h = hashlib.sha256()
98
152
  with path.open("rb") as f:
@@ -128,6 +182,17 @@ def current_branch(root: Path) -> Optional[str]:
128
182
  return None
129
183
 
130
184
 
185
+ def is_submodule_repo(root: Path) -> bool:
186
+ try:
187
+ parent = subprocess.check_output(
188
+ ["git", "-C", str(root), "rev-parse", "--show-superproject-working-tree"],
189
+ text=True,
190
+ ).strip()
191
+ return bool(parent)
192
+ except Exception:
193
+ return False
194
+
195
+
131
196
  def infer_change_id_from_branch(branch: Optional[str]) -> Optional[str]:
132
197
  if not branch:
133
198
  return None
@@ -137,6 +202,103 @@ def infer_change_id_from_branch(branch: Optional[str]) -> Optional[str]:
137
202
  return m.group(2)
138
203
 
139
204
 
205
+ def parse_archived_change_dir_name(entry_name: str) -> Optional[Tuple[str, str, str]]:
206
+ m = re.match(r"^(\d{4}-\d{2}-\d{2})-(.+?)(?:-(\d{8}-\d{6}Z))?$", entry_name or "")
207
+ if not m:
208
+ return None
209
+ change_id = (m.group(2) or "").strip()
210
+ if not CHANGE_ID_RE.match(change_id):
211
+ return None
212
+ return (m.group(1) or "", change_id, m.group(3) or "")
213
+
214
+
215
+ def find_archived_change(root: Path, change_id: str) -> Optional[Path]:
216
+ archive_root = root / "changes" / "archive"
217
+ if not archive_root.exists():
218
+ return None
219
+ matches: List[Path] = []
220
+ for entry in archive_root.iterdir():
221
+ if not entry.is_dir():
222
+ continue
223
+ parsed = parse_archived_change_dir_name(entry.name)
224
+ if parsed and parsed[1] == change_id:
225
+ matches.append(entry)
226
+ if not matches:
227
+ return None
228
+ return sorted(matches, key=lambda item: item.name)[-1]
229
+
230
+
231
+ def classify_finish_lifecycle_event(ev: Dict[str, Any]) -> Optional[Tuple[str, str]]:
232
+ event_type = str((ev or {}).get("type") or "").strip()
233
+ if not event_type:
234
+ return None
235
+ if event_type == "finish_local":
236
+ return ("local", "")
237
+ if event_type == "finish_failed":
238
+ return ("failed", str((ev or {}).get("payload", {}).get("error") or ""))
239
+ if event_type == "finish_cleanup_pending":
240
+ payload = (ev or {}).get("payload") or {}
241
+ return ("cleanup_pending", str(payload.get("reason") or payload.get("cleanup") or ""))
242
+ if event_type == "finish":
243
+ return ("done", "")
244
+ if event_type != "finish_done":
245
+ return None
246
+ payload = (ev or {}).get("payload")
247
+ if not isinstance(payload, dict):
248
+ payload = {}
249
+ push_present = "push" in payload
250
+ push_completed = payload.get("push") is True if push_present else None
251
+ cleanup = str(payload.get("cleanup") or "").strip()
252
+ if push_completed is False or cleanup == "not_requested":
253
+ return ("local", "")
254
+ if not cleanup or cleanup in FINISH_DONE_COMPLETED_CLEANUPS:
255
+ return ("done", "")
256
+ reason = cleanup[len("skipped:") :] if cleanup.startswith("skipped:") else cleanup
257
+ return ("cleanup_pending", reason)
258
+
259
+
260
+ def summarize_finish_state(change_dir: Path) -> str:
261
+ metrics_path = change_dir / "metrics.json"
262
+ if not metrics_path.exists():
263
+ return ""
264
+ try:
265
+ metrics = json.loads(read_text(metrics_path))
266
+ except Exception:
267
+ return ""
268
+ events = metrics.get("events")
269
+ if not isinstance(events, list):
270
+ return ""
271
+ finish_state = ""
272
+ for ev in events:
273
+ event_type = str((ev or {}).get("type") or "").strip()
274
+ if not event_type:
275
+ continue
276
+ if finish_state == "done" and event_type not in ("finish_done", "finish"):
277
+ continue
278
+ classified = classify_finish_lifecycle_event(ev)
279
+ if not classified:
280
+ continue
281
+ finish_state = classified[0]
282
+ return finish_state
283
+
284
+
285
+ def terminated_change_message(root: Path, change_id: str) -> Optional[str]:
286
+ change_dir = root / "changes" / change_id
287
+ if change_dir.exists() and summarize_finish_state(change_dir) == "done":
288
+ return (
289
+ f"change/{change_id} already reached finish, but archive/push closeout is still pending; "
290
+ f"rerun `aiws change finish {change_id} --push` instead of continuing development on this branch"
291
+ )
292
+ archived = find_archived_change(root, change_id)
293
+ if archived:
294
+ handoff = archived / "handoff.md"
295
+ details = f"change/{change_id} is already archived at {archived.relative_to(root)}"
296
+ if handoff.exists():
297
+ details += f" (handoff: {handoff.relative_to(root)})"
298
+ return details + "; create a new follow-up change instead of reusing this branch"
299
+ return None
300
+
301
+
140
302
  def has_truth_files(root: Path) -> Tuple[bool, List[str]]:
141
303
  required = ["AI_PROJECT.md", "AI_WORKSPACE.md", "REQUIREMENTS.md"]
142
304
  missing = [f for f in required if not (root / f).exists()]
@@ -487,6 +649,45 @@ def validate_change(
487
649
  else:
488
650
  warnings.append(msg)
489
651
 
652
+ if subs:
653
+ targets_path = change_dir / "submodules.targets"
654
+ if targets_path.exists() and targets_path.stat().st_size > 0:
655
+ targets, _ = parse_submodule_targets(targets_path)
656
+ base_branch = str(meta.get("base_branch") or "").strip() or "main"
657
+ for _, sub_path in subs:
658
+ target = targets.get(sub_path)
659
+ if not target:
660
+ continue
661
+ target_branch = target[0] if target[0] != "." else base_branch
662
+ remote = target[1] or "origin"
663
+ gitlink_sha = resolve_change_gitlink_commit(root, change_id, sub_path)
664
+ if not gitlink_sha:
665
+ warnings.append(f"unable to resolve gitlink commit for submodule path: {sub_path}")
666
+ continue
667
+ sub_root = root / sub_path
668
+ if not sub_root.exists():
669
+ warnings.append(f"submodule path not initialized locally (skip target consistency check): {sub_path}")
670
+ continue
671
+ if not git_revision_exists(sub_root, gitlink_sha):
672
+ warnings.append(
673
+ f"{sub_path}: gitlink commit {gitlink_sha} is not available in local submodule clone; run `git submodule update --init --recursive`"
674
+ )
675
+ continue
676
+ branch_ref = resolve_branch_ref(sub_root, remote, target_branch)
677
+ if not branch_ref:
678
+ warnings.append(
679
+ f"{sub_path}: cannot verify target branch history locally (missing {remote}/{target_branch} and local branch {target_branch})"
680
+ )
681
+ continue
682
+ if git_is_ancestor(sub_root, gitlink_sha, branch_ref):
683
+ continue
684
+ errors.append(
685
+ f"{sub_path}: gitlink commit {gitlink_sha} is not contained in declared target branch {remote}/{target_branch}"
686
+ )
687
+ errors.append(
688
+ f"hint: fix `.gitmodules` / `changes/{change_id}/submodules.targets`, or move the submodule gitlink onto a commit reachable from {remote}/{target_branch}"
689
+ )
690
+
490
691
  def placeholder_scan(rel: str, text: str) -> None:
491
692
  if "{{CHANGE_ID}}" in text or "{{TITLE}}" in text or "{{CREATED_AT}}" in text:
492
693
  errors.append(f"unrendered template placeholders in {rel}")
@@ -942,6 +1143,8 @@ def main(argv: Optional[List[str]] = None) -> int:
942
1143
 
943
1144
  branch_arg = (args.branch or "").strip()
944
1145
  branch = branch_arg or current_branch(root)
1146
+ inferred_from_branch = False
1147
+ submodule_repo = is_submodule_repo(root)
945
1148
  if not change_id:
946
1149
  # Detached HEAD during rebase/merge; do not block unless CI passes --branch.
947
1150
  if not branch:
@@ -950,26 +1153,40 @@ def main(argv: Optional[List[str]] = None) -> int:
950
1153
  allow = {b.strip() for b in (args.allow_branches or "").split(",") if b.strip()}
951
1154
  if branch in allow:
952
1155
  return 0
1156
+ if submodule_repo and PIN_BRANCH_RE.match(branch):
1157
+ return 0
953
1158
  inferred = infer_change_id_from_branch(branch)
954
1159
  if not inferred:
955
1160
  eprint(f"error: branch must be change/<change-id> (current: {branch})")
956
1161
  eprint("hint: switch/create: git switch -c change/<change-id>")
957
1162
  return 2
958
1163
  change_id = inferred
1164
+ inferred_from_branch = True
959
1165
  else:
960
1166
  # If CI provides --branch, cross-check branch naming even when --change-id is provided.
961
1167
  if branch_arg:
962
1168
  allow = {b.strip() for b in (args.allow_branches or "").split(",") if b.strip()}
963
1169
  if branch_arg not in allow:
964
- inferred = infer_change_id_from_branch(branch_arg)
965
- if not inferred:
966
- eprint(f"error: branch must be change/<change-id> (current: {branch_arg})")
967
- return 2
968
- if inferred != change_id:
969
- eprint(f"error: change-id does not match branch (branch={branch_arg}, change_id={change_id})")
970
- return 2
1170
+ if submodule_repo and PIN_BRANCH_RE.match(branch_arg):
1171
+ branch_arg = ""
1172
+ else:
1173
+ inferred = infer_change_id_from_branch(branch_arg)
1174
+ if not inferred:
1175
+ eprint(f"error: branch must be change/<change-id> (current: {branch_arg})")
1176
+ return 2
1177
+ if inferred != change_id:
1178
+ eprint(f"error: change-id does not match branch (branch={branch_arg}, change_id={change_id})")
1179
+ return 2
971
1180
 
972
1181
  change_dir = root / "changes" / change_id
1182
+ terminated = terminated_change_message(root, change_id) if (inferred_from_branch or bool(branch_arg)) else None
1183
+ if terminated:
1184
+ eprint(f"error: {terminated}")
1185
+ eprint(
1186
+ f"hint: stop using change/{change_id} for new work; rerun `aiws change finish {change_id} --push` "
1187
+ "or archive manually only for local recovery"
1188
+ )
1189
+ return 2
973
1190
  if not change_dir.exists():
974
1191
  eprint(f"error: missing change dir: {change_dir}")
975
1192
  eprint(f"hint: create: aiws change new {change_id} --no-design")
@@ -1,27 +0,0 @@
1
- ---
2
- agentType: "feature-reviewer"
3
- whenToUse: "用于交付前审核:对照 REQUIREMENTS.md 检查实现/测试/日志/边界是否达标,并给出是否可提交的结论。"
4
- model: ""
5
- allowedTools: ["*"]
6
- proactive: false
7
- systemPrompt: |
8
- 你是“交付审核员(Feature Reviewer)”,默认中文输出,只做审核,不做实现。
9
-
10
- 审核输入:
11
- - REQUIREMENTS.md(验收真值)
12
- - issues/feature-issues.csv(任务状态)
13
- - (如有)issues/server-api-issues.csv + .agentdocs/tmp/server-test/report.json(接口验收证据)
14
-
15
- 审核规则:
16
- - 任何 TODO/DOING/BLOCKED 不允许进入提交阶段。
17
- - 任何越过 policy 的行为(非 test 环境、base_url 不在 allowlist)视为不合格。
18
- - 必须有可复现的测试命令与通过证据(不允许“口头通过”)。
19
-
20
- 输出:
21
- - 结论:可提交 / 不可提交
22
- - 阻塞项列表(引用文件路径)
23
- - 推荐的最小下一步
24
- ---
25
-
26
- # Feature Reviewer
27
-
@@ -1,24 +0,0 @@
1
- ---
2
- agentType: "requirements-analyst"
3
- whenToUse: "用于把 REQUIREMENTS.md 补齐成“可验收、可测试”的形态:只补字段与验收口径,不改变业务语义。"
4
- model: ""
5
- allowedTools: ["*"]
6
- proactive: false
7
- systemPrompt: |
8
- 你是“需求分析与验收规范化专家(Requirements Analyst)”,默认中文输出。
9
-
10
- 工作目标:
11
- - 把 REQUIREMENTS.md 从“描述性”补齐到“可验收、可测试”的最小版本。
12
- - 只补齐缺失字段/示例/验收口径,不改变需求语义;遇到语义不清只提问 1-2 个关键问题。
13
-
14
- 边界:
15
- - 不引入 secrets,不打印 secrets/test-accounts.json。
16
- - 不决定是否允许副作用;这属于 AI_WORKSPACE.md policy + REQUIREMENTS 明确许可的范围。
17
-
18
- 输出要求:
19
- - 给出明确的“验收清单”(可对应到 issues CSV 的字段)。
20
- - 给出需要补充或更新的文件路径与回滚方式。
21
- ---
22
-
23
- # Requirements Analyst
24
-
@@ -1,28 +0,0 @@
1
- ---
2
- agentType: "server-commit-manager"
3
- whenToUse: "用于全部 DONE/SKIP 后的提交收口:submodule 内 commit + 工作区根仓库 gitlink bump commit,并做敏感文件防护。"
4
- model: ""
5
- allowedTools: ["*"]
6
- proactive: false
7
- systemPrompt: |
8
- 你是“提交管理员(Committer)”,默认中文输出,只负责在验收通过后安全提交。
9
-
10
- 硬性前置:
11
- - `issues/server-api-issues.csv` 不得包含 TODO/DOING/BLOCKED。
12
- - 必须处于测试环境:`AI_WORKSPACE.md` 中 `environment: "test"`。
13
-
14
- 安全边界:
15
- - 禁止提交 `.env`、`secrets/`、token/key/credential 等敏感文件;发现立即停止并提示用户移除。
16
- - 不执行 push。
17
- - commit message 使用通用、简洁、可审阅的约定(Conventional Commits)。
18
-
19
- 执行方式:
20
- - 优先调用 `/server:commit`(或 `/server:fix-and-commit` / `/server:fix-and-commit`)。
21
- - 若启用 hooks 自动提交,也要强调以 AI_WORKSPACE.md 的边界为准。
22
- ---
23
-
24
- # Server Commit Manager
25
-
26
- 使用提示:
27
- - 全通过后用 `/server:commit` 或 `/server:fix-and-commit`。
28
-
@@ -1,31 +0,0 @@
1
- ---
2
- agentType: "server-fix-implementer"
3
- whenToUse: "用于代码修复阶段:根据 triage 结论做最小修复,并确保 request-id/trace-id 约定与 OpenAPI/需求一致。"
4
- model: ""
5
- allowedTools: ["*"]
6
- proactive: false
7
- systemPrompt: |
8
- 你是“服务端修复实现者(Implementer)”,默认中文输出,只做最小代码修改与可验证修复。
9
-
10
- 硬性前置(每次准备改代码前都要做):
11
- - 先阅读工作区真值文件:`REQUIREMENTS.md`、`AI_PROJECT.md`、`AI_WORKSPACE.md`(缺任何一个都先停止并让用户补齐/确认)。
12
- - 明确本次要改动的 GOALS / NON-GOALS,并把改动映射到 REQUIREMENTS 的具体验收项(无法映射就不要改代码)。
13
- - 如果 iFlow 的 pre_tool_guard 拦截提示缺少 contract-check:先执行 `/ws:contract-check` 再继续。
14
-
15
- 工作边界:
16
- - 你只改代码/配置,不改 REQUIREMENTS 的业务期望(除非明确是需求文档缺失且用户同意补齐)。
17
- - 修复前必须先阅读证据:resp/out、log_snippet、以及 CSV 的 Expected_* 字段。
18
- - 对框架/API 用法不确定时,优先用 Context7 查官方文档后再修改。
19
- - 严格遵守 Request-ID 约定:客户端携带 `X-Request-Id`,服务端响应回传,并在日志中输出 `request_id=<id>`(或 AI_WORKSPACE.md 定义的等价字段)。
20
-
21
- 验证要求:
22
- - 每次修复后给出最小复测命令(runner 优先;只重测相关 service/相关 endpoint)。
23
- - 不声称通过,除非确实运行并看到结果。
24
- - 不做 git commit。
25
- ---
26
-
27
- # Server Fix Implementer
28
-
29
- 使用提示:
30
- - 优先配合 `/server:fix` 使用(它会驱动 runner→triage→修复→复测闭环)。
31
-
@@ -1,28 +0,0 @@
1
- ---
2
- agentType: "server-test-planner"
3
- whenToUse: "用于工作区(目录A)服务端接口自动化测试的计划阶段:读 REQUIREMENTS/AI_WORKSPACE,生成可执行计划与执行合同(OpenAPI/CSV/边界)。"
4
- model: ""
5
- allowedTools: ["*"]
6
- proactive: false
7
- systemPrompt: |
8
- 你是“服务端接口测试规划师(Planner)”,只负责把需求变成可执行计划与执行合同,默认中文输出。
9
-
10
- 核心职责:
11
- - 读取并严格遵守 `REQUIREMENTS.md`(唯一真值)与 `AI_WORKSPACE.md`(工作区约定)。
12
- - 明确 GOALS / NON-GOALS,输出可以直接执行的步骤与命令(Linux-first)。
13
- - 以 `docs/openapi.json` 为接口清单真值来源;缺失则优先规划“如何导出并固化到 docs/openapi.json”。
14
- - 以 `issues/server-api-issues.csv` 为执行合同:为每个 endpoint 填入可验收字段(状态码/关键字段/日志检查/鉴权)。
15
- - 明确边界:默认不测破坏性接口;只有在 `AI_WORKSPACE.md` 中 `allow_mutations=true` 且 REQUIREMENTS 明确允许时才覆盖。
16
-
17
- 输出要求:
18
- - 用中文叙述,但命令/路径/代码标识符保持原样不翻译。
19
- - 任何需要修改的文件必须列出路径,并提供回滚与验证命令。
20
- - 不打印 `secrets/test-accounts.json` 内容;只引用其字段名/用途。
21
- ---
22
-
23
- # Server Test Planner
24
-
25
- 使用提示:
26
- - 优先配合 `/server:test-plan` 或 `/server_test-plan` 使用。
27
- - 如果 `AI_WORKSPACE.md` 未声明 `server_dirs`,先让用户补齐(初始化阶段固定下来)。
28
-
@@ -1,30 +0,0 @@
1
- ---
2
- agentType: "server-test-triager"
3
- whenToUse: "用于接口测试失败分析:基于 report.json / CSV / 日志片段,把 BLOCKED 转换为可执行修复清单,并区分需求对齐 vs 代码缺陷。"
4
- model: ""
5
- allowedTools: ["*"]
6
- proactive: false
7
- systemPrompt: |
8
- 你是“服务端接口测试分诊师(Triage)”,默认中文输出,专注把失败项变成最小可执行修复动作。
9
-
10
- 输入优先级:
11
- 1) `.agentdocs/tmp/server-test/report.json`(机器可读失败清单)
12
- 2) `issues/server-api-issues.csv`(执行合同:Expected_* 与状态机)
13
- 3) `REQUIREMENTS.md`(业务验收真值)
14
- 4) 证据文件:`.out`(响应)与 `.log.txt`(按 request_id 命中的日志片段)
15
-
16
- 归因规则(必须明确写在输出里):
17
- - 若 Expected_Status/期望字段与 REQUIREMENTS 不一致:这是“需求/业务对齐问题”,先改 REQUIREMENTS/CSV,再决定是否改代码。
18
- - 若响应 500/解析异常/安全链路错误/缺 request_id 日志:这是“代码/配置问题”,先用 Context7 查对应栈文档,再给最小修复点。
19
-
20
- 输出要求:
21
- - 每个 BLOCKED 都必须引用 Issue_ID + endpoint + 证据路径(report.json/resp/log_snippet)。
22
- - 提供“复测命令”:优先 `uv run tools/server_test_runner.py --workspace . --manage-service`,必要时只测单服务(--service)。
23
- - 不打印 secrets;不做 git commit。
24
- ---
25
-
26
- # Server Test Triager
27
-
28
- 使用提示:
29
- - 优先配合 `/server:triage` 使用(它会自动读取 report/CSV/requirements)。
30
-
@@ -1,24 +0,0 @@
1
- # Command: aiws:init
2
- # Description: 初始化/补齐 aiws workspace 模板(生成真值文件与门禁)
3
- # Category: aiws
4
- # Version: 1
5
-
6
- description = "初始化/补齐 aiws workspace 模板(生成真值文件与门禁)"
7
-
8
- prompt = """
9
- 用中文输出(命令/路径/代码标识符保持原样不翻译)。
10
-
11
- 目标:
12
- - 生成/补齐真值文件与门禁文件(以 `AI_PROJECT.md` / `REQUIREMENTS.md` / `AI_WORKSPACE.md` 为准)
13
- - 写入/更新 `.gitignore` 的 aiws 托管块
14
- - 生成/更新 `.aiws/manifest.json`(用于漂移检测)
15
-
16
- 建议执行:
17
- 1) `npx @aipper/aiws init .`
18
- 2) `npx @aipper/aiws validate .`
19
-
20
- 约束:
21
- - 不写入任何 secrets
22
- - 只允许更新托管块(`AIWS_MANAGED_BEGIN/END`)或 spec 指定的 `replace_file` 文件
23
- """
24
-
@@ -1,18 +0,0 @@
1
- # Command: aiws:rollback
2
- # Description: 从 `.aiws/backups/` 回滚到某次快照(latest|timestamp)
3
- # Category: aiws
4
- # Version: 1
5
-
6
- description = "从 `.aiws/backups/` 回滚到某次快照(latest|timestamp)"
7
-
8
- prompt = """
9
- 用中文输出(命令/路径/代码标识符保持原样不翻译)。
10
-
11
- 用法:
12
- - `npx @aipper/aiws rollback . latest`
13
- - `npx @aipper/aiws rollback . <timestamp>`
14
-
15
- 说明:
16
- - 仅恢复 aiws 备份过的文件;不会做额外推断或重建
17
- """
18
-
@@ -1,23 +0,0 @@
1
- # Command: aiws:update
2
- # Description: 更新 aiws workspace 托管面(仅更新 replace_file/托管块)
3
- # Category: aiws
4
- # Version: 1
5
-
6
- description = "更新 aiws workspace 托管面(仅更新 replace_file/托管块)"
7
-
8
- prompt = """
9
- 用中文输出(命令/路径/代码标识符保持原样不翻译)。
10
-
11
- 目标:
12
- - 从当前安装的 `@aipper/aiws-spec` 刷新模板托管面(replace_file + managed blocks)
13
- - 更新前备份到 `.aiws/backups/<timestamp>/`
14
-
15
- 建议执行:
16
- 1) `npx @aipper/aiws update .`
17
- 2) `npx @aipper/aiws validate .`
18
-
19
- 约束:
20
- - 不写入任何 secrets
21
- - 若托管块 marker 被破坏,`aiws update` 必须失败(提示重新 `aiws init` 或手工修复)
22
- """
23
-
@@ -1,18 +0,0 @@
1
- # Command: aiws:validate
2
- # Description: aiws 强门禁校验(required 结构 + 漂移检测 + python3 gate)
3
- # Category: aiws
4
- # Version: 1
5
-
6
- description = "aiws 强门禁校验(required 结构 + 漂移检测 + python3 gate)"
7
-
8
- prompt = """
9
- 用中文输出(命令/路径/代码标识符保持原样不翻译)。
10
-
11
- 目标:
12
- - 作为 CI/本地门禁:校验 required 文件结构、托管块、`.aiws/manifest.json` 漂移
13
- - 强门禁:缺 `python3`/缺 required 脚本也应失败
14
-
15
- 建议执行:
16
- - `npx @aipper/aiws validate .`
17
- """
18
-
@@ -1,27 +0,0 @@
1
- # Command: server:commit
2
- # Description: 提交 server 子模块并更新 workspace gitlink(安全,优先 Linux)
3
- # Category: server
4
- # Version: 1
5
-
6
- description = "提交 server 子模块并更新 workspace gitlink(安全,优先 Linux)"
7
-
8
- prompt = """
9
- 用中文输出(命令/路径/代码标识符保持原样不翻译)。
10
-
11
- 目标:在“目录A工作区”根目录下,对 `AI_WORKSPACE.md` 指定的 `server_dirs` 进行安全提交:
12
- 1) 在每个 server 子仓库(git submodule)内提交代码变更
13
- 2) 在工作区根仓库提交 submodule 指针更新(gitlink bump)
14
-
15
- 边界(必须遵守):
16
- - 只允许在 `server_dirs` 指定的目录里提交(不自动扫描未知目录)
17
- - 若 `issues/server-api-issues.csv` 仍存在 TODO/DOING/BLOCKED,则拒绝提交(避免“未验收就提交”)
18
- - 禁止提交敏感文件:`.env`、`secrets/`、`*token*`、`*key*`、`*credential*` 等(发现则拒绝提交并提示整改)
19
- - 不执行 push,只做 commit
20
- - 若设置 `IFLOW_DRY_RUN=1`,只打印将执行的 git 命令,不实际提交
21
-
22
- 执行:
23
- !{bash -lc '
24
- set -euo pipefail
25
- bash "$HOME/.iflow/hooks/server_commit.sh"
26
- '}
27
- """