@torus-engineering/tas-kit 1.13.0 → 2.1.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 (100) hide show
  1. package/.tas/_platform/claude-code/settings.json +58 -46
  2. package/.tas/_platform/hooks/code-quality.js +127 -127
  3. package/.tas/_platform/hooks/session-end.js +111 -111
  4. package/.tas/agents/architect.md +53 -53
  5. package/.tas/agents/aws-reviewer.md +71 -71
  6. package/.tas/agents/build-resolver.md +89 -59
  7. package/.tas/agents/code-explorer.md +63 -63
  8. package/.tas/agents/csharp-reviewer.md +62 -62
  9. package/.tas/agents/database-reviewer.md +73 -73
  10. package/.tas/agents/doc-updater.md +68 -66
  11. package/.tas/agents/python-reviewer.md +67 -67
  12. package/.tas/agents/security-reviewer.md +79 -79
  13. package/.tas/agents/software-engineer.md +53 -0
  14. package/.tas/agents/typescript-reviewer.md +65 -65
  15. package/.tas/commands/ado-create.md +33 -28
  16. package/.tas/commands/ado-delete.md +26 -22
  17. package/.tas/commands/ado-get.md +24 -20
  18. package/.tas/commands/ado-status.md +22 -18
  19. package/.tas/commands/ado-update.md +31 -27
  20. package/.tas/commands/tas-adr.md +37 -33
  21. package/.tas/commands/tas-apitest-plan.md +177 -173
  22. package/.tas/commands/tas-apitest.md +147 -143
  23. package/.tas/commands/tas-brainstorm.md +23 -19
  24. package/.tas/commands/tas-brd.md +50 -0
  25. package/.tas/commands/tas-bug.md +127 -113
  26. package/.tas/commands/tas-checklist.md +180 -0
  27. package/.tas/commands/tas-debug.md +103 -0
  28. package/.tas/commands/tas-design.md +41 -37
  29. package/.tas/commands/tas-dev.md +225 -125
  30. package/.tas/commands/tas-e2e-mobile.md +146 -155
  31. package/.tas/commands/tas-e2e-web.md +150 -163
  32. package/.tas/commands/tas-e2e.md +289 -102
  33. package/.tas/commands/tas-feature.md +181 -47
  34. package/.tas/commands/tas-fix.md +72 -51
  35. package/.tas/commands/tas-functest-mobile.md +138 -144
  36. package/.tas/commands/tas-functest-web.md +176 -192
  37. package/.tas/commands/tas-functest.md +225 -76
  38. package/.tas/commands/tas-init.md +22 -17
  39. package/.tas/commands/tas-master-plan.md +300 -0
  40. package/.tas/commands/tas-orchestrate.md +159 -0
  41. package/.tas/commands/tas-plan.md +152 -117
  42. package/.tas/commands/tas-prd.md +57 -37
  43. package/.tas/commands/tas-review-pr.md +174 -0
  44. package/.tas/commands/tas-review.md +115 -113
  45. package/.tas/commands/tas-sad.md +47 -43
  46. package/.tas/commands/tas-security.md +91 -87
  47. package/.tas/commands/tas-spec.md +54 -50
  48. package/.tas/commands/tas-status.md +25 -16
  49. package/.tas/project-status-example.yaml +3 -1
  50. package/.tas/rules/ado-integration.md +67 -65
  51. package/.tas/rules/common/api-design.md +517 -517
  52. package/.tas/rules/common/build-debug-loop.md +233 -0
  53. package/.tas/rules/common/code-review.md +4 -0
  54. package/.tas/rules/common/feature-done.md +42 -0
  55. package/.tas/rules/common/post-implementation-review.md +4 -0
  56. package/.tas/rules/common/project-status.md +33 -16
  57. package/.tas/rules/common/sad-impact.md +81 -0
  58. package/.tas/rules/common/tdd.md +104 -89
  59. package/.tas/rules/csharp/api-testing.md +2 -2
  60. package/.tas/rules/csharp/torus-core-framework.md +128 -0
  61. package/.tas/tas-example.yaml +9 -32
  62. package/.tas/templates/AGENTS.md +13 -0
  63. package/.tas/templates/API-Test-Spec.md +5 -4
  64. package/.tas/templates/BRD.md +133 -0
  65. package/.tas/templates/Bug.md +15 -0
  66. package/.tas/templates/E2E-Execution-Report.md +8 -8
  67. package/.tas/templates/E2E-Mobile-Spec.md +6 -8
  68. package/.tas/templates/E2E-Report.md +2 -2
  69. package/.tas/templates/E2E-Scenario.md +22 -22
  70. package/.tas/templates/E2E-Test-Spec.md +274 -0
  71. package/.tas/templates/E2E-Web-Spec.md +4 -4
  72. package/.tas/templates/Feature-Technical-Part.md +69 -0
  73. package/.tas/templates/Feature-Technical-Stack.md +74 -0
  74. package/.tas/templates/Feature-Technical.md +329 -0
  75. package/.tas/templates/Feature.md +50 -26
  76. package/.tas/templates/Func-Test-Script.md +29 -56
  77. package/.tas/templates/Func-Test-Spec.md +144 -142
  78. package/.tas/templates/PRD.md +173 -142
  79. package/.tas/templates/TestChecklist.md +96 -0
  80. package/.tas/templates/torus-dotnet-bootstrap.md +223 -0
  81. package/.tas/tools/tas-ado-readme.md +24 -27
  82. package/.tas/tools/tas-ado.py +328 -25
  83. package/.tas/tools/tas-github.py +339 -0
  84. package/README.md +142 -57
  85. package/bin/cli.js +90 -90
  86. package/lib/adapters/antigravity.js +131 -131
  87. package/lib/adapters/claude-code.js +71 -35
  88. package/lib/adapters/codex.js +157 -157
  89. package/lib/adapters/cursor.js +80 -80
  90. package/lib/adapters/index.js +20 -20
  91. package/lib/adapters/utils.js +81 -81
  92. package/lib/deleted-files.json +7 -0
  93. package/lib/install.js +546 -543
  94. package/package.json +2 -2
  95. package/.tas/README.md +0 -334
  96. package/.tas/commands/tas-epic.md +0 -35
  97. package/.tas/commands/tas-story.md +0 -91
  98. package/.tas/rules/common/story-done.md +0 -30
  99. package/.tas/templates/Epic.md +0 -46
  100. package/.tas/templates/Story.md +0 -90
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ TAS GitHub Integration Script
4
+ GitHub PR operations for tas-review-pr command.
5
+
6
+ Usage: python tools/tas-github.py <command> [arguments]
7
+
8
+ Commands:
9
+ pr-get <pr-id>
10
+ pr-diff <pr-id>
11
+ pr-comment <pr-id> --comment <text>
12
+ pr-inline <pr-id> --file <path> --line <n> --comment <text>
13
+ pr-vote <pr-id> --vote <approve|reject|wait-for-author|reset>
14
+
15
+ Prerequisites:
16
+ - GitHub CLI (gh) installed and authenticated: gh auth login
17
+ - Python 3.8+
18
+ """
19
+
20
+ import argparse
21
+ import json
22
+ import os
23
+ import re
24
+ import subprocess
25
+ import sys
26
+ import tempfile
27
+ import urllib.error
28
+ import urllib.request
29
+ from pathlib import Path
30
+
31
+
32
+ # --- Helpers ---
33
+
34
+ def find_repo_root():
35
+ path = Path.cwd()
36
+ while path != path.parent:
37
+ if (path / ".git").exists():
38
+ return path
39
+ path = path.parent
40
+ print("ERROR: Not inside a git repository.")
41
+ sys.exit(1)
42
+
43
+
44
+ def get_remote_url(root):
45
+ result = subprocess.run(
46
+ ["git", "remote", "get-url", "origin"],
47
+ capture_output=True,
48
+ cwd=str(root),
49
+ )
50
+ if result.returncode != 0:
51
+ print("ERROR: Cannot get git remote URL. Run inside a git repo with 'origin' remote.")
52
+ sys.exit(1)
53
+ return result.stdout.decode("utf-8").strip()
54
+
55
+
56
+ def parse_owner_repo(remote_url):
57
+ """Extract owner/repo from GitHub remote URL (https or ssh)."""
58
+ # ssh: git@github.com:owner/repo.git
59
+ m = re.search(r"github\.com[:/](.+?)(?:\.git)?$", remote_url)
60
+ if m:
61
+ return m.group(1).rstrip("/")
62
+ print(f"ERROR: Cannot parse owner/repo from remote URL: {remote_url}")
63
+ sys.exit(1)
64
+
65
+
66
+ def get_github_token():
67
+ result = subprocess.run(
68
+ ["gh", "auth", "token"],
69
+ capture_output=True,
70
+ )
71
+ if result.returncode != 0:
72
+ print("ERROR: gh CLI not authenticated. Run: gh auth login")
73
+ sys.exit(1)
74
+ return result.stdout.decode("utf-8").strip()
75
+
76
+
77
+ def gh_cmd(args):
78
+ """Run gh CLI command and return parsed JSON output."""
79
+ cmd = ["gh"] + args + ["--output", "json"] if "--output" not in args else ["gh"] + args
80
+ result = subprocess.run(cmd, capture_output=True)
81
+
82
+ def _decode(b):
83
+ try:
84
+ return (b or b"").decode("utf-8")
85
+ except UnicodeDecodeError:
86
+ return (b or b"").decode("cp1252", errors="replace")
87
+
88
+ if result.returncode != 0:
89
+ print(f"ERROR: gh command failed:\n{_decode(result.stderr)}")
90
+ sys.exit(1)
91
+ stdout = _decode(result.stdout).strip()
92
+ if not stdout:
93
+ return {}
94
+ json_start = re.search(r"[{\[]", stdout)
95
+ if json_start:
96
+ try:
97
+ return json.loads(stdout[json_start.start():])
98
+ except json.JSONDecodeError:
99
+ return {}
100
+ return {}
101
+
102
+
103
+ def github_rest(method, path, data, token):
104
+ """Call GitHub REST API using token auth."""
105
+ import base64
106
+ url = f"https://api.github.com{path}"
107
+ bearer = f"Bearer {token}"
108
+ body = json.dumps(data).encode("utf-8")
109
+ req = urllib.request.Request(url, data=body, method=method)
110
+ req.add_header("Authorization", bearer)
111
+ req.add_header("Content-Type", "application/json")
112
+ req.add_header("Accept", "application/vnd.github+json")
113
+ req.add_header("X-GitHub-Api-Version", "2022-11-28")
114
+ try:
115
+ with urllib.request.urlopen(req) as resp:
116
+ return json.loads(resp.read().decode("utf-8"))
117
+ except urllib.error.HTTPError as e:
118
+ err_body = e.read().decode("utf-8")
119
+ print(f"ERROR: GitHub REST API {method} {path} failed ({e.code}): {err_body}")
120
+ sys.exit(1)
121
+
122
+
123
+ # --- PR Commands ---
124
+
125
+ def cmd_pr_get(args):
126
+ root = find_repo_root()
127
+ remote_url = get_remote_url(root)
128
+ owner_repo = parse_owner_repo(remote_url)
129
+
130
+ result = gh_cmd([
131
+ "pr", "view", str(args.pr_id),
132
+ "--repo", owner_repo,
133
+ "--json", "number,title,body,headRefName,baseRefName,author,state,headRefOid,url",
134
+ ])
135
+
136
+ print(f"PR_ID: {result.get('number', args.pr_id)}")
137
+ print(f"TITLE: {result.get('title', '')}")
138
+ print(f"DESCRIPTION: {result.get('body', '')}")
139
+ print(f"SOURCE_BRANCH: {result.get('headRefName', '')}")
140
+ print(f"TARGET_BRANCH: {result.get('baseRefName', '')}")
141
+ author = result.get("author", {})
142
+ print(f"CREATOR: {author.get('login', '') if isinstance(author, dict) else str(author)}")
143
+ print(f"STATUS: {result.get('state', '')}")
144
+ print(f"REPO_ID: {owner_repo}")
145
+ print(f"REPO_NAME: {owner_repo.split('/')[-1]}")
146
+ print(f"HEAD_COMMIT: {result.get('headRefOid', '')}")
147
+ print(f"WORK_ITEMS: ") # GitHub has no native work item link (use PR body parsing if needed)
148
+
149
+
150
+ def cmd_pr_diff(args):
151
+ root = find_repo_root()
152
+ remote_url = get_remote_url(root)
153
+ owner_repo = parse_owner_repo(remote_url)
154
+
155
+ result = gh_cmd([
156
+ "pr", "view", str(args.pr_id),
157
+ "--repo", owner_repo,
158
+ "--json", "headRefName,baseRefName,headRefOid",
159
+ ])
160
+
161
+ source_branch = result.get("headRefName", "")
162
+ target_branch = result.get("baseRefName", "")
163
+ fetch_head = result.get("headRefOid", "")
164
+
165
+ print(f"SOURCE_BRANCH: {source_branch}")
166
+ print(f"TARGET_BRANCH: {target_branch}")
167
+
168
+ fetch_result = subprocess.run(
169
+ ["git", "fetch", "origin", source_branch],
170
+ capture_output=True,
171
+ cwd=str(root),
172
+ )
173
+ if fetch_result.returncode != 0:
174
+ warn = (fetch_result.stderr or b"").decode("utf-8", errors="replace")
175
+ print(f"WARNING: git fetch failed: {warn}")
176
+
177
+ if not fetch_head:
178
+ head_result = subprocess.run(
179
+ ["git", "rev-parse", "FETCH_HEAD"],
180
+ capture_output=True,
181
+ cwd=str(root),
182
+ )
183
+ fetch_head = head_result.stdout.decode("utf-8").strip() if head_result.returncode == 0 else ""
184
+
185
+ print(f"FETCH_HEAD: {fetch_head}")
186
+
187
+ diff_result = subprocess.run(
188
+ ["git", "diff", "--name-status", f"origin/{target_branch}...FETCH_HEAD"],
189
+ capture_output=True,
190
+ cwd=str(root),
191
+ )
192
+ if diff_result.returncode == 0:
193
+ print("CHANGED_FILES:")
194
+ print(diff_result.stdout.decode("utf-8", errors="replace").strip())
195
+ else:
196
+ warn = (diff_result.stderr or b"").decode("utf-8", errors="replace")
197
+ print(f"WARNING: git diff failed: {warn}")
198
+
199
+
200
+ def cmd_pr_comment(args):
201
+ root = find_repo_root()
202
+ remote_url = get_remote_url(root)
203
+ owner_repo = parse_owner_repo(remote_url)
204
+
205
+ tmp = tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False, encoding="utf-8")
206
+ tmp.write(args.comment)
207
+ tmp.close()
208
+ try:
209
+ result = subprocess.run(
210
+ ["gh", "pr", "comment", str(args.pr_id),
211
+ "--repo", owner_repo,
212
+ "--body-file", tmp.name],
213
+ capture_output=True,
214
+ )
215
+ if result.returncode != 0:
216
+ err = (result.stderr or b"").decode("utf-8", errors="replace")
217
+ print(f"ERROR: gh pr comment failed: {err}")
218
+ sys.exit(1)
219
+ print(f"Posted comment on PR #{args.pr_id}")
220
+ finally:
221
+ try:
222
+ os.unlink(tmp.name)
223
+ except OSError:
224
+ pass
225
+
226
+
227
+ def cmd_pr_inline(args):
228
+ root = find_repo_root()
229
+ remote_url = get_remote_url(root)
230
+ owner_repo = parse_owner_repo(remote_url)
231
+ token = get_github_token()
232
+
233
+ # Get HEAD commit for the PR (required by GitHub inline comment API)
234
+ result = gh_cmd([
235
+ "pr", "view", str(args.pr_id),
236
+ "--repo", owner_repo,
237
+ "--json", "headRefOid",
238
+ ])
239
+ commit_id = result.get("headRefOid", "")
240
+ if not commit_id:
241
+ print("ERROR: Could not get PR head commit ID")
242
+ sys.exit(1)
243
+
244
+ file_path = args.file_path.replace("\\", "/").lstrip("/")
245
+
246
+ data = {
247
+ "body": args.comment,
248
+ "commit_id": commit_id,
249
+ "path": file_path,
250
+ "line": args.line,
251
+ "side": "RIGHT",
252
+ }
253
+ resp = github_rest("POST", f"/repos/{owner_repo}/pulls/{args.pr_id}/comments", data, token)
254
+ comment_id = resp.get("id", "")
255
+ print(f"Posted inline comment on {file_path}:{args.line} (comment #{comment_id})")
256
+
257
+
258
+ def cmd_pr_vote(args):
259
+ root = find_repo_root()
260
+ remote_url = get_remote_url(root)
261
+ owner_repo = parse_owner_repo(remote_url)
262
+
263
+ vote = args.vote.lower()
264
+ if vote == "approve":
265
+ gh_args = ["pr", "review", str(args.pr_id), "--repo", owner_repo, "--approve"]
266
+ gh_cmd(gh_args)
267
+ print(f"Approved PR #{args.pr_id}")
268
+ elif vote == "reject":
269
+ gh_args = [
270
+ "pr", "review", str(args.pr_id), "--repo", owner_repo,
271
+ "--request-changes",
272
+ "--body", "AI review found Critical or High severity issues. See inline comments for details.",
273
+ ]
274
+ gh_cmd(gh_args)
275
+ print(f"Requested changes on PR #{args.pr_id}")
276
+ elif vote in ("wait-for-author", "wait"):
277
+ gh_args = [
278
+ "pr", "review", str(args.pr_id), "--repo", owner_repo,
279
+ "--comment",
280
+ "--body", "AI review: waiting for author to address review comments.",
281
+ ]
282
+ gh_cmd(gh_args)
283
+ print(f"Posted 'waiting for author' comment on PR #{args.pr_id}")
284
+ elif vote == "reset":
285
+ print(f"INFO: GitHub does not support vote reset. No action taken for PR #{args.pr_id}")
286
+ else:
287
+ print(f"ERROR: Unknown vote '{vote}'. Use: approve|reject|wait-for-author|reset")
288
+ sys.exit(1)
289
+
290
+
291
+ # --- Main ---
292
+
293
+ def main():
294
+ parser = argparse.ArgumentParser(description="TAS GitHub PR Integration")
295
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
296
+
297
+ p = subparsers.add_parser("pr-get", help="Get PR metadata")
298
+ p.add_argument("pr_id", type=int, help="Pull Request number")
299
+
300
+ p = subparsers.add_parser("pr-diff", help="Fetch PR branch and list changed files")
301
+ p.add_argument("pr_id", type=int, help="Pull Request number")
302
+
303
+ p = subparsers.add_parser("pr-comment", help="Post summary comment on PR")
304
+ p.add_argument("pr_id", type=int, help="Pull Request number")
305
+ p.add_argument("--comment", required=True, help="Comment text (markdown supported)")
306
+
307
+ p = subparsers.add_parser("pr-inline", help="Post inline comment on PR diff")
308
+ p.add_argument("pr_id", type=int, help="Pull Request number")
309
+ p.add_argument("--file", dest="file_path", required=True, help="File path (e.g. src/api/users.ts)")
310
+ p.add_argument("--line", type=int, required=True, help="Line number")
311
+ p.add_argument("--comment", required=True, help="Inline comment text")
312
+
313
+ p = subparsers.add_parser("pr-vote", help="Set vote on PR")
314
+ p.add_argument("pr_id", type=int, help="Pull Request number")
315
+ p.add_argument("--vote", required=True,
316
+ choices=["approve", "reject", "wait-for-author", "reset"],
317
+ help="Vote to cast")
318
+
319
+ args = parser.parse_args()
320
+ if not args.command:
321
+ parser.print_help()
322
+ sys.exit(1)
323
+
324
+ if args.command == "pr-get":
325
+ cmd_pr_get(args)
326
+ elif args.command == "pr-diff":
327
+ cmd_pr_diff(args)
328
+ elif args.command == "pr-comment":
329
+ cmd_pr_comment(args)
330
+ elif args.command == "pr-inline":
331
+ cmd_pr_inline(args)
332
+ elif args.command == "pr-vote":
333
+ cmd_pr_vote(args)
334
+ else:
335
+ parser.print_help()
336
+
337
+
338
+ if __name__ == "__main__":
339
+ main()