vibe-coding-master 0.2.6 → 0.2.8

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 (43) hide show
  1. package/README.md +164 -9
  2. package/dist/backend/api/claude-hook-routes.js +7 -1
  3. package/dist/backend/api/gateway-routes.js +17 -0
  4. package/dist/backend/api/round-routes.js +1 -1
  5. package/dist/backend/gateway/channels/weixin-ilink-channel.js +304 -0
  6. package/dist/backend/gateway/gateway-audit-log.js +39 -0
  7. package/dist/backend/gateway/gateway-command-parser.js +77 -0
  8. package/dist/backend/gateway/gateway-service.js +848 -0
  9. package/dist/backend/gateway/gateway-settings-service.js +214 -0
  10. package/dist/backend/server.js +42 -2
  11. package/dist/backend/services/claude-hook-service.js +46 -7
  12. package/dist/backend/services/harness-service.js +37 -32
  13. package/dist/backend/services/job-guard-service.js +126 -0
  14. package/dist/backend/services/round-service.js +110 -64
  15. package/dist/backend/services/session-service.js +10 -4
  16. package/dist/backend/services/task-service.js +32 -3
  17. package/dist/backend/services/translation-service.js +15 -0
  18. package/dist/backend/templates/harness/architect-agent.js +19 -8
  19. package/dist/backend/templates/harness/claude-root.js +7 -11
  20. package/dist/backend/templates/harness/coder-agent.js +45 -17
  21. package/dist/backend/templates/harness/gitignore.js +3 -2
  22. package/dist/backend/templates/harness/project-manager-agent.js +16 -11
  23. package/dist/backend/templates/harness/reviewer-agent.js +25 -28
  24. package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +42 -31
  25. package/dist/backend/templates/harness/vcm-long-running-validation-skill.js +37 -18
  26. package/dist/shared/types/gateway.js +1 -0
  27. package/dist-frontend/assets/{index-CPXFnxAY.css → index-7lq6YPCq.css} +1 -1
  28. package/dist-frontend/assets/index-DHuS-DYr.js +92 -0
  29. package/dist-frontend/index.html +2 -2
  30. package/docs/full-harness-baseline.md +7 -5
  31. package/docs/gateway-design.md +200 -27
  32. package/docs/product-design.md +35 -14
  33. package/docs/v0.2-implementation-plan.md +22 -7
  34. package/docs/vcm-cc-best-practices.md +11 -4
  35. package/package.json +2 -1
  36. package/scripts/harness-tools/run-long-check +401 -0
  37. package/scripts/harness-tools/vcm-bash-guard +107 -0
  38. package/scripts/harness-tools/watch-job +204 -0
  39. package/scripts/install-vcm-harness.mjs +93 -387
  40. package/scripts/uninstall-vcm-harness.mjs +18 -1
  41. package/scripts/verify-package.mjs +3 -0
  42. package/dist/backend/templates/harness/known-issues-doc.js +0 -22
  43. package/dist-frontend/assets/index-D0_02lmQ.js +0 -90
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env python3
2
+ """Watch one VCM long-running validation job in the foreground.
3
+
4
+ Usage:
5
+ .ai/tools/watch-job <job-id> [--window <duration>] [--interval <duration>]
6
+
7
+ watch-job renews the job supervision lease while it runs. When the watch
8
+ window elapses and the job is still running, it exits 125 WITHOUT stopping
9
+ the job; call watch-job again immediately in the same turn. The job ceiling
10
+ itself is enforced by the run-long-check worker, not by watch-job.
11
+
12
+ Exit codes:
13
+ 0 success
14
+ 1 failed
15
+ 124 timeout (job hit its ceiling and was killed by the worker)
16
+ 125 still running; call watch-job again now
17
+ 4 orphaned or stale (job lost supervision and was killed, or its worker died)
18
+ 2 usage error or unknown job id
19
+ """
20
+ import argparse
21
+ import json
22
+ import sys
23
+ import time
24
+ from pathlib import Path
25
+
26
+ MAX_WINDOW_SECONDS = 8 * 60
27
+ DEFAULT_WINDOW = "8m"
28
+ STATUS_WAIT_SECONDS = 10
29
+ TERMINAL_EXIT_CODES = {
30
+ "success": 0,
31
+ "failed": 1,
32
+ "timeout": 124,
33
+ "orphaned": 4,
34
+ "stale": 4,
35
+ }
36
+
37
+
38
+ def root_dir() -> Path:
39
+ return Path(__file__).resolve().parents[2]
40
+
41
+
42
+ def parse_duration(value: str) -> float:
43
+ value = value.strip().lower()
44
+ if value.endswith("ms"):
45
+ return float(value[:-2]) / 1000
46
+ if value.endswith("s"):
47
+ return float(value[:-1])
48
+ if value.endswith("m"):
49
+ return float(value[:-1]) * 60
50
+ if value.endswith("h"):
51
+ return float(value[:-1]) * 3600
52
+ return float(value)
53
+
54
+
55
+ def read_optional_json(path: Path) -> dict | None:
56
+ try:
57
+ return json.loads(path.read_text())
58
+ except (OSError, ValueError):
59
+ return None
60
+
61
+
62
+ def renew_lease(lease_path: Path) -> None:
63
+ try:
64
+ lease_path.touch()
65
+ except OSError:
66
+ pass
67
+
68
+
69
+ def tail(path: Path, lines: int = 40, max_bytes: int = 65536) -> str:
70
+ try:
71
+ size = path.stat().st_size
72
+ except OSError:
73
+ return ""
74
+ try:
75
+ with path.open("rb") as handle:
76
+ handle.seek(max(0, size - max_bytes))
77
+ data = handle.read(max_bytes)
78
+ except OSError:
79
+ return ""
80
+ text = data.decode("utf-8", errors="replace")
81
+ return "\n".join(text.splitlines()[-lines:])
82
+
83
+
84
+ def elapsed_seconds(status: dict) -> float | None:
85
+ started_at = status.get("startedAt")
86
+ if not started_at:
87
+ return None
88
+ from datetime import datetime, timezone
89
+
90
+ try:
91
+ started = datetime.fromisoformat(str(started_at).replace("Z", "+00:00"))
92
+ except ValueError:
93
+ return None
94
+ return round((datetime.now(timezone.utc) - started).total_seconds(), 3)
95
+
96
+
97
+ def print_summary(job_id: str, status: dict, directory: Path) -> None:
98
+ state = status.get("status")
99
+ print(f"job: {job_id}")
100
+ print(f"status: {state}")
101
+ print(f"exitCode: {status.get('exitCode')}")
102
+ print(f"durationSeconds: {status.get('durationSeconds')}")
103
+ if state == "timeout":
104
+ print(f"timeoutSeconds: {status.get('timeoutSeconds')}")
105
+ print(f"processStopResult: {status.get('processStopResult')}")
106
+ if state == "orphaned":
107
+ print(f"orphanReason: {status.get('orphanReason')}")
108
+ print(f"processStopResult: {status.get('processStopResult')}")
109
+ if state == "stale":
110
+ print(f"staleReason: {status.get('staleReason')}")
111
+
112
+ if state in {"failed", "timeout", "orphaned", "stale"}:
113
+ stdout_tail = tail(directory / "stdout.log")
114
+ stderr_tail = tail(directory / "stderr.log")
115
+ if stdout_tail:
116
+ print("\nstdout tail:")
117
+ print(stdout_tail)
118
+ if stderr_tail:
119
+ print("\nstderr tail:")
120
+ print(stderr_tail)
121
+
122
+
123
+ def print_progress(job_id: str, status: dict, directory: Path, window: float) -> None:
124
+ print(f"job: {job_id}")
125
+ print("status: still-running")
126
+ print(f"jobStatus: {status.get('status')}")
127
+ elapsed = elapsed_seconds(status)
128
+ if elapsed is not None:
129
+ print(f"elapsedSeconds: {elapsed}")
130
+ ceiling = status.get("timeoutSeconds")
131
+ if isinstance(ceiling, (int, float)):
132
+ print(f"ceilingRemainingSeconds: {max(0, round(ceiling - elapsed))}")
133
+ print(f"watchWindowSeconds: {int(window)}")
134
+ stdout_tail = tail(directory / "stdout.log", lines=5)
135
+ if stdout_tail:
136
+ print("\nstdout tail:")
137
+ print(stdout_tail)
138
+ print("\njob still running - call .ai/tools/watch-job again now; do not end the turn.")
139
+
140
+
141
+ def main() -> int:
142
+ parser = argparse.ArgumentParser(
143
+ description="Watch a file-backed long-running validation job."
144
+ )
145
+ parser.add_argument("job_id")
146
+ parser.add_argument("--window", default=DEFAULT_WINDOW)
147
+ parser.add_argument("--interval", default="1s")
148
+ parser.add_argument("--timeout", dest="legacy_timeout", default=None, help=argparse.SUPPRESS)
149
+ args = parser.parse_args()
150
+
151
+ if args.legacy_timeout is not None:
152
+ print(
153
+ "error: watch-job no longer takes --timeout; the job ceiling is set by"
154
+ " run-long-check --timeout. Use --window (max 8m) and call watch-job"
155
+ " repeatedly until it reports a terminal result.",
156
+ file=sys.stderr,
157
+ )
158
+ return 2
159
+
160
+ try:
161
+ window = parse_duration(args.window)
162
+ interval = parse_duration(args.interval)
163
+ except ValueError as exc:
164
+ print(f"error: invalid duration: {exc}", file=sys.stderr)
165
+ return 2
166
+ if window <= 0 or interval <= 0:
167
+ print("error: window and interval must be positive", file=sys.stderr)
168
+ return 2
169
+ if window > MAX_WINDOW_SECONDS:
170
+ print("error: window exceeds maximum 8m; call watch-job repeatedly instead", file=sys.stderr)
171
+ return 2
172
+
173
+ directory = root_dir() / ".ai/vcm/jobs" / args.job_id
174
+ status_path = directory / "status.json"
175
+ lease_path = directory / "lease"
176
+
177
+ found_deadline = time.time() + STATUS_WAIT_SECONDS
178
+ while not status_path.is_file():
179
+ if time.time() >= found_deadline:
180
+ print(f"error: unknown job id: {args.job_id} (no {status_path})", file=sys.stderr)
181
+ return 2
182
+ time.sleep(0.2)
183
+
184
+ deadline = time.time() + window
185
+ last_status: dict = {}
186
+ while True:
187
+ renew_lease(lease_path)
188
+ status = read_optional_json(status_path)
189
+ if status:
190
+ last_status = status
191
+ state = status.get("status")
192
+ if state in TERMINAL_EXIT_CODES:
193
+ print_summary(args.job_id, status, directory)
194
+ return TERMINAL_EXIT_CODES[state]
195
+
196
+ if time.time() >= deadline:
197
+ print_progress(args.job_id, last_status, directory, window)
198
+ return 125
199
+
200
+ time.sleep(interval)
201
+
202
+
203
+ if __name__ == "__main__":
204
+ raise SystemExit(main())