lagora-cli 1.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 (52) hide show
  1. package/README.md +138 -0
  2. package/dist/help.txt +70 -0
  3. package/dist/lagora.js +342 -0
  4. package/dist/report-help.txt +5 -0
  5. package/dist/scripts/agora_playground_harness.py +263 -0
  6. package/dist/scripts/announce.js +41 -0
  7. package/dist/scripts/check-kernel-submission.py +90 -0
  8. package/dist/scripts/chunk-2EAJVB5D.js +100 -0
  9. package/dist/scripts/chunk-2KTLCUFI.js +29 -0
  10. package/dist/scripts/chunk-AZ3EEBVD.js +137 -0
  11. package/dist/scripts/chunk-NBJMYAOA.js +2128 -0
  12. package/dist/scripts/chunk-NCJMUBTG.js +125 -0
  13. package/dist/scripts/chunk-QJPQHKIO.js +23 -0
  14. package/dist/scripts/chunk-RIR5KGHC.js +33 -0
  15. package/dist/scripts/chunk-TJZVQYBL.js +8 -0
  16. package/dist/scripts/chunk-UHJXD4TG.js +18 -0
  17. package/dist/scripts/chunk-UQ6I6VTY.js +117 -0
  18. package/dist/scripts/cli-auth.js +348 -0
  19. package/dist/scripts/cli-config-IA7EOSYD.js +7 -0
  20. package/dist/scripts/install-skill.js +199 -0
  21. package/dist/scripts/issue-local-client-DZUXZOKY.js +22 -0
  22. package/dist/scripts/issue-search.js +1823 -0
  23. package/dist/scripts/issue.js +386 -0
  24. package/dist/scripts/keycloak-provision.js +986 -0
  25. package/dist/scripts/legato-fsim-runner.py +126 -0
  26. package/dist/scripts/legato-lowering-runner.py +156 -0
  27. package/dist/scripts/legato_runner_annotations.py +235 -0
  28. package/dist/scripts/legato_runner_env.py +91 -0
  29. package/dist/scripts/legato_runner_launchers.py +287 -0
  30. package/dist/scripts/legato_runner_script_wrapper.py +193 -0
  31. package/dist/scripts/notifications-EU43SIEV.js +624 -0
  32. package/dist/scripts/playground.js +408 -0
  33. package/dist/scripts/report-bundle-sync-3U7QTP4Z.js +215 -0
  34. package/dist/scripts/report.js +104 -0
  35. package/dist/scripts/resolve-sdk-package-version.py +151 -0
  36. package/dist/scripts/sdk-runtime-JE6H2PB2.js +992 -0
  37. package/dist/scripts/sdk-runtime-kubernetes-job-KOWL4ITV.js +479 -0
  38. package/dist/scripts/sdk-runtime-smoke.py +168 -0
  39. package/dist/scripts/sdk.js +256 -0
  40. package/dist/scripts/site-feedback-CAPE5MPX.js +136 -0
  41. package/dist/scripts/site-feedback-rate-limit-5BU2WSFE.js +86 -0
  42. package/dist/scripts/site-feedback.js +117 -0
  43. package/dist/scripts/storage-234FBH54.js +67 -0
  44. package/dist/scripts/submit-issue.sh +489 -0
  45. package/dist/scripts/verification-3QCY66QW.js +772 -0
  46. package/dist/scripts/verify-issue.js +144 -0
  47. package/dist/skills/legato-agora-cli/SKILL.md +556 -0
  48. package/dist/skills/legato-agora-cli/agents/openai.yaml +7 -0
  49. package/dist/skills/legato-agora-cli/reference/kernel-with-golden.py +84 -0
  50. package/dist/skills/legato-site-feedback/SKILL.md +49 -0
  51. package/dist/skills/legato-site-feedback/agents/openai.yaml +7 -0
  52. package/package.json +16 -0
@@ -0,0 +1,489 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ cat <<'USAGE'
6
+ Usage: lagora report --kernel path --title title --reporter name [--log path] [--description text | --description-file path] [--api-url url | --store path] [--idempotency-key key] [--auto-lowering] [--sdk-root path] [--python python3] [--stages MLIR,CORE_IR] [--kernel-function name]
7
+
8
+ Creates a portable issue bundle under issue-store/issues/<id>/ by default.
9
+ Pass --api-url to submit to the internal Agora server.
10
+ Pass --store to write into a local or mounted issue repository.
11
+ USAGE
12
+ }
13
+
14
+ kernel=""
15
+ log_file=""
16
+ title=""
17
+ reporter=""
18
+ description=""
19
+ description_file=""
20
+ tags=""
21
+ store="issue-store"
22
+ store_seen="0"
23
+ api_url="${LAGORA_API_URL:-}"
24
+ api_url_seen="0"
25
+ idempotency_key=""
26
+ auto_lowering="0"
27
+ sdk_root=""
28
+ legato_python=""
29
+ lowering_stages=""
30
+ kernel_function=""
31
+
32
+ while [ "$#" -gt 0 ]; do
33
+ case "$1" in
34
+ --kernel) kernel="${2:-}"; shift 2 ;;
35
+ --log) log_file="${2:-}"; shift 2 ;;
36
+ --title) title="${2:-}"; shift 2 ;;
37
+ --reporter) reporter="${2:-}"; shift 2 ;;
38
+ --description) description="${2:-}"; shift 2 ;;
39
+ --description-file) description_file="${2:-}"; shift 2 ;;
40
+ --tags) tags="${2:-}"; shift 2 ;;
41
+ --store) store="${2:-}"; store_seen="1"; shift 2 ;;
42
+ --api-url)
43
+ if [ "$api_url_seen" = "1" ]; then
44
+ echo "Pass --api-url at most once" >&2
45
+ exit 1
46
+ fi
47
+ api_url_seen="1"
48
+ api_url="${2:-}"
49
+ shift 2
50
+ ;;
51
+ --idempotency-key) idempotency_key="${2:-}"; shift 2 ;;
52
+ --auto-lowering) auto_lowering="1"; shift ;;
53
+ --sdk-root) sdk_root="${2:-}"; shift 2 ;;
54
+ --python) legato_python="${2:-}"; shift 2 ;;
55
+ --stages) lowering_stages="${2:-}"; shift 2 ;;
56
+ --kernel-function) kernel_function="${2:-}"; shift 2 ;;
57
+ -h|--help) usage; exit 0 ;;
58
+ *) echo "Unknown argument: $1" >&2; usage; exit 1 ;;
59
+ esac
60
+ done
61
+
62
+ if [ "$store_seen" = "1" ] && [ "$api_url_seen" = "0" ]; then
63
+ api_url=""
64
+ fi
65
+
66
+ if [ -z "$kernel" ] || [ -z "$title" ] || [ -z "$reporter" ]; then
67
+ usage >&2
68
+ exit 1
69
+ fi
70
+
71
+ if [ ! -f "$kernel" ]; then
72
+ echo "Kernel file not found: $kernel" >&2
73
+ exit 1
74
+ fi
75
+
76
+ # Say what the site will be able to do with this kernel. Informational only: an
77
+ # arbitrary kernel is a perfectly good bug report, and the reporter is simply the
78
+ # one person who knows the right answer, so this is the moment to mention it.
79
+ if command -v python3 >/dev/null 2>&1; then
80
+ python3 "$(dirname "$0")/check-kernel-submission.py" "$kernel" || true
81
+ fi
82
+
83
+ if [ -n "$log_file" ] && [ ! -f "$log_file" ]; then
84
+ echo "Log file not found: $log_file" >&2
85
+ exit 1
86
+ fi
87
+
88
+ if [ -n "$api_url" ]; then
89
+ sha_cmd=""
90
+ if command -v sha256sum >/dev/null 2>&1; then
91
+ sha_cmd="sha256sum"
92
+ elif command -v shasum >/dev/null 2>&1; then
93
+ sha_cmd="shasum -a 256"
94
+ fi
95
+
96
+ kernel_checksum="unknown"
97
+ if [ -n "$sha_cmd" ]; then
98
+ kernel_checksum="$($sha_cmd "$kernel" | awk '{print $1}')"
99
+ fi
100
+
101
+ legato_version="legato binary not found on PATH"
102
+ if command -v legato >/dev/null 2>&1; then
103
+ legato_version="$(legato --version 2>/dev/null || true)"
104
+ fi
105
+
106
+ python_version="not found"
107
+ if command -v python3 >/dev/null 2>&1; then
108
+ python_version="$(python3 --version 2>&1)"
109
+ elif command -v python >/dev/null 2>&1; then
110
+ python_version="$(python --version 2>&1)"
111
+ fi
112
+
113
+ git_remote="$(git config --get remote.origin.url 2>/dev/null || true)"
114
+ git_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
115
+ git_commit="$(git rev-parse HEAD 2>/dev/null || true)"
116
+ git_dirty="false"
117
+ if [ -n "$(git status --porcelain 2>/dev/null || true)" ]; then
118
+ git_dirty="true"
119
+ fi
120
+
121
+ github_login=""
122
+ if command -v gh >/dev/null 2>&1; then
123
+ github_login="$(gh api user --jq .login 2>/dev/null || true)"
124
+ fi
125
+
126
+ node_version="not found"
127
+ if command -v node >/dev/null 2>&1; then
128
+ node_version="$(node --version 2>/dev/null || true)"
129
+ fi
130
+
131
+ if [ -n "$description_file" ]; then
132
+ if [ ! -f "$description_file" ]; then
133
+ echo "Description file not found: $description_file" >&2
134
+ exit 1
135
+ fi
136
+ description="$(cat "$description_file")"
137
+ elif [ -z "$description" ]; then
138
+ description="Reported from lagora with kernel $(basename "$kernel")."
139
+ fi
140
+
141
+ tmp_dir="$(mktemp -d)"
142
+ trap 'rm -rf "$tmp_dir"' EXIT
143
+ GIT_REMOTE="$git_remote" \
144
+ GIT_BRANCH="$git_branch" \
145
+ GIT_COMMIT="$git_commit" \
146
+ GIT_DIRTY="$git_dirty" \
147
+ KERNEL_PATH="$kernel" \
148
+ NODE_VERSION="$node_version" \
149
+ PYTHON_VERSION="$python_version" \
150
+ KERNEL_CHECKSUM="$kernel_checksum" \
151
+ LEGATO_VERSION="$legato_version" \
152
+ GITHUB_LOGIN="$github_login" \
153
+ python3 - "$tmp_dir/git.json" "$tmp_dir/environment.json" <<'PY'
154
+ import json, os, platform, sys
155
+
156
+ git_path, environment_path = sys.argv[1], sys.argv[2]
157
+ git_metadata = {
158
+ "remoteUrl": os.environ["GIT_REMOTE"],
159
+ "branch": os.environ["GIT_BRANCH"],
160
+ "commit": os.environ["GIT_COMMIT"],
161
+ "dirty": os.environ["GIT_DIRTY"],
162
+ "filePath": os.environ["KERNEL_PATH"],
163
+ }
164
+ environment_metadata = {
165
+ "platform": platform.system().lower(),
166
+ "arch": platform.machine(),
167
+ "node": os.environ["NODE_VERSION"],
168
+ "python": os.environ["PYTHON_VERSION"],
169
+ "kernelChecksum": os.environ["KERNEL_CHECKSUM"],
170
+ "legatoVersion": os.environ["LEGATO_VERSION"],
171
+ "cliVersion": "lagora/submit-issue.sh",
172
+ "githubLogin": os.environ["GITHUB_LOGIN"],
173
+ }
174
+ with open(git_path, "w", encoding="utf-8") as f:
175
+ json.dump(git_metadata, f)
176
+ with open(environment_path, "w", encoding="utf-8") as f:
177
+ json.dump(environment_metadata, f)
178
+ PY
179
+
180
+ api_endpoint="${api_url%/}/api/report-bundles"
181
+ if [ -z "$idempotency_key" ]; then
182
+ idempotency_key="$(date +%s)-$$-$kernel_checksum"
183
+ fi
184
+ curl_args=(
185
+ -sS
186
+ -f
187
+ -X POST "$api_endpoint"
188
+ -F "title=$title"
189
+ -F "reporter=$reporter"
190
+ -F "description=$description"
191
+ -F "tags=$tags"
192
+ -F "idempotencyKey=$idempotency_key"
193
+ -F "gitMetadata=<${tmp_dir}/git.json"
194
+ -F "environmentMetadata=<${tmp_dir}/environment.json"
195
+ -F "kernelFile=@${kernel};type=text/x-python"
196
+ )
197
+ if [ -n "$log_file" ]; then
198
+ curl_args+=(-F "errorLogFile=@${log_file};type=text/plain")
199
+ fi
200
+
201
+ auth_token="${LAGORA_AUTH_TOKEN:?Run lagora login first}"
202
+ unset LAGORA_AUTH_TOKEN
203
+ printf 'header = "Authorization: Bearer %s"\n' "$auth_token" | curl --config - "${curl_args[@]}"
204
+ auth_token=""
205
+ printf '\n'
206
+ exit 0
207
+ fi
208
+
209
+ if command -v python3 >/dev/null 2>&1; then
210
+ issue_id="$(python3 - <<'PY'
211
+ import uuid
212
+ print(uuid.uuid4())
213
+ PY
214
+ )"
215
+ elif command -v uuidgen >/dev/null 2>&1; then
216
+ issue_id="$(uuidgen | tr '[:upper:]' '[:lower:]')"
217
+ else
218
+ issue_id="$(date +%s)-$$"
219
+ fi
220
+
221
+ issue_dir="$store/issues/$issue_id"
222
+ artifact_dir="$issue_dir/artifacts"
223
+ mkdir -p "$artifact_dir"
224
+
225
+ kernel_name="$(basename "$kernel")"
226
+ log_name=""
227
+ if [ -n "$log_file" ]; then
228
+ log_name="$(basename "$log_file")"
229
+ fi
230
+
231
+ if [ -n "$log_name" ] && [ "$kernel_name" = "$log_name" ]; then
232
+ kernel_name="kernel-$kernel_name"
233
+ log_name="error-log-$log_name"
234
+ fi
235
+
236
+ cp "$kernel" "$artifact_dir/$kernel_name"
237
+ if [ -n "$log_file" ]; then
238
+ cp "$log_file" "$artifact_dir/$log_name"
239
+ fi
240
+
241
+ if [ -n "$description_file" ]; then
242
+ if [ ! -f "$description_file" ]; then
243
+ echo "Description file not found: $description_file" >&2
244
+ exit 1
245
+ fi
246
+ cp "$description_file" "$issue_dir/description.md"
247
+ elif [ -n "$description" ]; then
248
+ printf '%s\n' "$description" > "$issue_dir/description.md"
249
+ else
250
+ printf 'Reported from shell submit script with kernel `%s`.\n' "$kernel" > "$issue_dir/description.md"
251
+ fi
252
+
253
+ sha_cmd=""
254
+ if command -v sha256sum >/dev/null 2>&1; then
255
+ sha_cmd="sha256sum"
256
+ elif command -v shasum >/dev/null 2>&1; then
257
+ sha_cmd="shasum -a 256"
258
+ fi
259
+
260
+ kernel_checksum="unknown"
261
+ if [ -n "$sha_cmd" ]; then
262
+ kernel_checksum="$($sha_cmd "$kernel" | awk '{print $1}')"
263
+ fi
264
+
265
+ legato_version="legato binary not found on PATH"
266
+ if command -v legato >/dev/null 2>&1; then
267
+ legato_version="$(legato --version 2>/dev/null || true)"
268
+ fi
269
+
270
+ python_version="not found"
271
+ if command -v python3 >/dev/null 2>&1; then
272
+ python_version="$(python3 --version 2>&1)"
273
+ elif command -v python >/dev/null 2>&1; then
274
+ python_version="$(python --version 2>&1)"
275
+ fi
276
+
277
+ git_remote="$(git config --get remote.origin.url 2>/dev/null || true)"
278
+ git_branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"
279
+ git_commit="$(git rev-parse HEAD 2>/dev/null || true)"
280
+ git_dirty="false"
281
+ if [ -n "$(git status --porcelain 2>/dev/null || true)" ]; then
282
+ git_dirty="true"
283
+ fi
284
+
285
+ github_login=""
286
+ if command -v gh >/dev/null 2>&1; then
287
+ github_login="$(gh api user --jq .login 2>/dev/null || true)"
288
+ fi
289
+
290
+ node_version="not found"
291
+ if command -v node >/dev/null 2>&1; then
292
+ node_version="$(node --version 2>/dev/null || true)"
293
+ fi
294
+
295
+ created_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
296
+
297
+ ISSUE_ID="$issue_id" \
298
+ ISSUE_TITLE="$title" \
299
+ ISSUE_REPORTER="$reporter" \
300
+ ISSUE_CREATED_AT="$created_at" \
301
+ ISSUE_KERNEL_NAME="$kernel_name" \
302
+ ISSUE_KERNEL_PATH="$kernel" \
303
+ ISSUE_KERNEL_CHECKSUM="$kernel_checksum" \
304
+ ISSUE_LOG_NAME="$log_name" \
305
+ ISSUE_LOG_PATH="$log_file" \
306
+ ISSUE_LEGATO_VERSION="$legato_version" \
307
+ ISSUE_GIT_REMOTE="$git_remote" \
308
+ ISSUE_GIT_BRANCH="$git_branch" \
309
+ ISSUE_GIT_COMMIT="$git_commit" \
310
+ ISSUE_GIT_DIRTY="$git_dirty" \
311
+ ISSUE_GITHUB_LOGIN="$github_login" \
312
+ ISSUE_NODE_VERSION="$node_version" \
313
+ ISSUE_PYTHON_VERSION="$python_version" \
314
+ ISSUE_TAGS="$tags" \
315
+ ISSUE_STORE="$store" \
316
+ python3 - "$issue_dir/issue.json" <<'PY'
317
+ import hashlib, json, os, platform, re, sys, uuid
318
+ from pathlib import Path
319
+
320
+ path = sys.argv[1]
321
+ log_name = os.environ["ISSUE_LOG_NAME"]
322
+ manual_tags = [tag.strip().lower().replace(" ", "-") for tag in os.environ["ISSUE_TAGS"].split(",") if tag.strip()]
323
+ issue_id = os.environ["ISSUE_ID"]
324
+ created_at = os.environ["ISSUE_CREATED_AT"]
325
+
326
+ description_path = Path(path).with_name("description.md")
327
+ description = description_path.read_text(encoding="utf-8") if description_path.exists() else ""
328
+
329
+ def normalize_signature_text(text):
330
+ text = text.lower()
331
+ text = re.sub(r"[a-f0-9]{16,}", "<hash>", text)
332
+ text = re.sub(r"0x[a-f0-9]+", "<addr>", text)
333
+ text = re.sub(r"\d+", "<num>", text)
334
+ return re.sub(r"\s+", " ", text).strip()
335
+
336
+ def raw_fingerprint():
337
+ diagnostic = normalize_signature_text(f"{os.environ['ISSUE_TITLE']}\n{description}")[:240]
338
+ material = "\0".join(["raw", os.environ["ISSUE_KERNEL_CHECKSUM"], "no-log" if not log_name else log_name, diagnostic])
339
+ key = hashlib.sha256(material.encode("utf-8")).hexdigest()
340
+ return {
341
+ "key": key,
342
+ "basis": "raw",
343
+ "artifactHash": os.environ["ISSUE_KERNEL_CHECKSUM"],
344
+ "diagnosticSignature": diagnostic,
345
+ "createdAt": created_at,
346
+ }
347
+
348
+ fingerprint = raw_fingerprint()
349
+ occurrence = {
350
+ "id": str(uuid.uuid4()),
351
+ "issueId": issue_id,
352
+ "reporter": os.environ["ISSUE_REPORTER"],
353
+ "bundleId": f"{issue_id}-bundle",
354
+ "artifactIds": [f"{issue_id}-kernel"] + ([f"{issue_id}-error-log"] if log_name else []),
355
+ "kernelChecksum": os.environ["ISSUE_KERNEL_CHECKSUM"],
356
+ "source": "shell",
357
+ "gitMetadata": {
358
+ "remoteUrl": os.environ["ISSUE_GIT_REMOTE"] or None,
359
+ "branch": os.environ["ISSUE_GIT_BRANCH"] or None,
360
+ "commit": os.environ["ISSUE_GIT_COMMIT"] or None,
361
+ "dirty": os.environ["ISSUE_GIT_DIRTY"] == "true",
362
+ "filePath": os.environ["ISSUE_KERNEL_PATH"],
363
+ },
364
+ "environmentMetadata": {
365
+ "platform": platform.system().lower(),
366
+ "arch": platform.machine(),
367
+ "node": os.environ["ISSUE_NODE_VERSION"],
368
+ "python": os.environ["ISSUE_PYTHON_VERSION"],
369
+ "kernelChecksum": os.environ["ISSUE_KERNEL_CHECKSUM"],
370
+ "githubLogin": os.environ["ISSUE_GITHUB_LOGIN"] or None,
371
+ },
372
+ "createdAt": created_at,
373
+ }
374
+
375
+ def find_duplicate():
376
+ issues_root = Path(os.environ["ISSUE_STORE"]) / "issues"
377
+ if not issues_root.exists():
378
+ return None, None
379
+ for issue_json in sorted(issues_root.glob("*/issue.json")):
380
+ try:
381
+ existing = json.loads(issue_json.read_text(encoding="utf-8"))
382
+ except Exception:
383
+ continue
384
+ if existing.get("id") == issue_id or existing.get("duplicateOf"):
385
+ continue
386
+ fingerprints = existing.get("fingerprints") or ([existing["fingerprint"]] if existing.get("fingerprint") else [])
387
+ if any(item.get("key") == fingerprint["key"] for item in fingerprints):
388
+ return issue_json, existing
389
+ return None, None
390
+
391
+ duplicate_path, duplicate = find_duplicate()
392
+ data = {
393
+ "schemaVersion": 1,
394
+ "id": issue_id,
395
+ "title": os.environ["ISSUE_TITLE"],
396
+ "reporter": os.environ["ISSUE_REPORTER"],
397
+ "status": "open",
398
+ "createdAt": os.environ["ISSUE_CREATED_AT"],
399
+ "updatedAt": os.environ["ISSUE_CREATED_AT"],
400
+ "source": "shell",
401
+ "tags": manual_tags,
402
+ "kernel": {
403
+ "filename": os.environ["ISSUE_KERNEL_NAME"],
404
+ "originalPath": os.environ["ISSUE_KERNEL_PATH"],
405
+ "checksum": os.environ["ISSUE_KERNEL_CHECKSUM"],
406
+ },
407
+ "log": {"filename": log_name, "originalPath": os.environ["ISSUE_LOG_PATH"]} if log_name else None,
408
+ "legatoBinaryVersion": os.environ["ISSUE_LEGATO_VERSION"],
409
+ "cliVersion": "submit-issue.sh/0.1.0",
410
+ "gitMetadata": {
411
+ "remoteUrl": os.environ["ISSUE_GIT_REMOTE"] or None,
412
+ "branch": os.environ["ISSUE_GIT_BRANCH"] or None,
413
+ "commit": os.environ["ISSUE_GIT_COMMIT"] or None,
414
+ "dirty": os.environ["ISSUE_GIT_DIRTY"] == "true",
415
+ "filePath": os.environ["ISSUE_KERNEL_PATH"],
416
+ },
417
+ "environmentMetadata": {
418
+ "platform": platform.system().lower(),
419
+ "arch": platform.machine(),
420
+ "node": os.environ["ISSUE_NODE_VERSION"],
421
+ "python": os.environ["ISSUE_PYTHON_VERSION"],
422
+ "kernelChecksum": os.environ["ISSUE_KERNEL_CHECKSUM"],
423
+ "githubLogin": os.environ["ISSUE_GITHUB_LOGIN"] or None,
424
+ },
425
+ "fingerprint": fingerprint,
426
+ "fingerprints": [fingerprint],
427
+ "occurrenceCount": 1,
428
+ "occurrences": [occurrence],
429
+ "store": {
430
+ "bundlePath": f"issues/{issue_id}",
431
+ },
432
+ }
433
+
434
+ if duplicate_path and duplicate:
435
+ duplicate_id = duplicate["id"]
436
+ occurrence["issueId"] = duplicate_id
437
+ comments = duplicate.get("comments") or []
438
+ comments.append({
439
+ "id": str(uuid.uuid4()),
440
+ "issueId": duplicate_id,
441
+ "author": "Legato Agora",
442
+ "kind": "system",
443
+ "body": f"동일 raw signature report가 다시 접수되었습니다. 새 제출 {issue_id}는 occurrence로 병합했습니다.",
444
+ "createdAt": created_at,
445
+ })
446
+ duplicate["comments"] = comments
447
+ duplicate["occurrences"] = (duplicate.get("occurrences") or []) + [occurrence]
448
+ duplicate["occurrenceCount"] = max(int(duplicate.get("occurrenceCount") or 1) + 1, len(duplicate["occurrences"]))
449
+ duplicate["updatedAt"] = created_at
450
+ duplicate["fingerprints"] = duplicate.get("fingerprints") or ([duplicate["fingerprint"]] if duplicate.get("fingerprint") else [])
451
+ if not any(item.get("key") == fingerprint["key"] for item in duplicate["fingerprints"]):
452
+ duplicate["fingerprints"].insert(0, fingerprint)
453
+ if not duplicate.get("fingerprint"):
454
+ duplicate["fingerprint"] = fingerprint
455
+ duplicate_path.write_text(json.dumps(duplicate, indent=2), encoding="utf-8")
456
+ data["duplicateOf"] = duplicate_id
457
+ Path(path).with_name(".duplicate-of").write_text(duplicate_id, encoding="utf-8")
458
+
459
+ with open(path, "w", encoding="utf-8") as f:
460
+ json.dump(data, f, indent=2)
461
+ PY
462
+
463
+ public_issue_id="$issue_id"
464
+ if [ -f "$issue_dir/.duplicate-of" ]; then
465
+ public_issue_id="$(cat "$issue_dir/.duplicate-of")"
466
+ fi
467
+
468
+ if [ "$auto_lowering" = "1" ] && [ "$public_issue_id" = "$issue_id" ]; then
469
+ verify_args=(--issue "$issue_id" --store "$store" --kind lowering --mode real)
470
+ if [ -n "$sdk_root" ]; then verify_args+=(--sdk-root "$sdk_root"); fi
471
+ if [ -n "$legato_python" ]; then verify_args+=(--python "$legato_python"); fi
472
+ if [ -n "$lowering_stages" ]; then verify_args+=(--stages "$lowering_stages"); fi
473
+ if [ -n "$kernel_function" ]; then verify_args+=(--kernel-function "$kernel_function"); fi
474
+ "$LAGORA_NODE" "$LAGORA_VERIFY_ENTRY" "${verify_args[@]}"
475
+ fi
476
+
477
+ if [ "$public_issue_id" = "$issue_id" ]; then
478
+ cat <<EOF
479
+ Created portable issue $issue_id
480
+ Store: $issue_dir
481
+ Open after the board has this store mounted: /issues/$issue_id
482
+ EOF
483
+ else
484
+ cat <<EOF
485
+ Duplicate report merged into portable issue $public_issue_id
486
+ Duplicate bundle: $issue_dir
487
+ Open after the board has this store mounted: /issues/$public_issue_id
488
+ EOF
489
+ fi