@penadidik/meo-agent 1.2.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.
- package/CHANGELOG.md +84 -0
- package/LICENSE +24 -0
- package/README.md +483 -0
- package/bin/meo-agent.js +158 -0
- package/developer-kit/README.md +99 -0
- package/developer-kit/developer-kit.sh +108 -0
- package/developer-kit/templates/requirements.md +95 -0
- package/developer-kit/templates/tasks.md +85 -0
- package/developer-kit/templates/tdd.md +128 -0
- package/examples/plugins/meo-agent-logger.js +21 -0
- package/index.js +2 -0
- package/lib/args.js +134 -0
- package/lib/checksum.js +29 -0
- package/lib/config.js +52 -0
- package/lib/doctor.js +84 -0
- package/lib/downloader.js +115 -0
- package/lib/mirror.js +137 -0
- package/lib/plugins.js +93 -0
- package/lib/reporter.js +108 -0
- package/package.json +87 -0
- package/pull_request/README.md +104 -0
- package/pull_request/generate-pr.sh +190 -0
- package/pull_request/templates/basic.md +41 -0
- package/pull_request/templates/hod-review.md +101 -0
- package/pull_request/templates/lead-review.md +83 -0
package/lib/reporter.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
class Reporter {
|
|
7
|
+
constructor(opts) {
|
|
8
|
+
this.json = opts.json || false;
|
|
9
|
+
this.quiet = opts.quiet || false;
|
|
10
|
+
this.url = null;
|
|
11
|
+
this.output = null;
|
|
12
|
+
this.startedAt = 0;
|
|
13
|
+
this.lastReport = 0;
|
|
14
|
+
this.totalBytes = 0;
|
|
15
|
+
this.receivedBytes = 0;
|
|
16
|
+
this.barWidth = 30;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_emit(event, data) {
|
|
20
|
+
if (this.json) {
|
|
21
|
+
process.stdout.write(JSON.stringify({ event, ...data, ts: Date.now() }) + '\n');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
start(url, output) {
|
|
26
|
+
this.url = url;
|
|
27
|
+
this.output = output;
|
|
28
|
+
this.startedAt = Date.now();
|
|
29
|
+
this.lastReport = this.startedAt;
|
|
30
|
+
this.receivedBytes = 0;
|
|
31
|
+
this.totalBytes = 0;
|
|
32
|
+
|
|
33
|
+
if (!this.quiet && !this.json) {
|
|
34
|
+
process.stderr.write(`Downloading ${url}\n → ${output}\n`);
|
|
35
|
+
}
|
|
36
|
+
this._emit('start', { url, output });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setTotal(bytes) {
|
|
40
|
+
this.totalBytes = bytes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
progress(received) {
|
|
44
|
+
this.receivedBytes = received;
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
|
|
47
|
+
if (this.json) {
|
|
48
|
+
this._emit('progress', {
|
|
49
|
+
received,
|
|
50
|
+
total: this.totalBytes,
|
|
51
|
+
percent: this.totalBytes ? Math.round((received / this.totalBytes) * 100) : null
|
|
52
|
+
});
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (this.quiet) return;
|
|
57
|
+
|
|
58
|
+
if (now - this.lastReport < 100 && this.totalBytes) return;
|
|
59
|
+
this.lastReport = now;
|
|
60
|
+
|
|
61
|
+
const percent = this.totalBytes ? Math.min(100, Math.round((received / this.totalBytes) * 100)) : 0;
|
|
62
|
+
const filled = Math.round((percent / 100) * this.barWidth);
|
|
63
|
+
const empty = this.barWidth - filled;
|
|
64
|
+
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
65
|
+
const mbReceived = (received / 1024 / 1024).toFixed(2);
|
|
66
|
+
const mbTotal = this.totalBytes ? (this.totalBytes / 1024 / 1024).toFixed(2) : '?';
|
|
67
|
+
|
|
68
|
+
process.stderr.write(`\r [${bar}] ${percent}% ${mbReceived} / ${mbTotal} MB`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
finish({ statusCode, resumed }) {
|
|
72
|
+
const elapsed = ((Date.now() - this.startedAt) / 1000).toFixed(2);
|
|
73
|
+
const mb = (this.receivedBytes / 1024 / 1024).toFixed(2);
|
|
74
|
+
|
|
75
|
+
if (!this.json) {
|
|
76
|
+
if (!this.quiet && this.totalBytes) {
|
|
77
|
+
process.stderr.write('\n');
|
|
78
|
+
}
|
|
79
|
+
if (!this.quiet) {
|
|
80
|
+
process.stderr.write(`✓ Saved to ${this.output} (${mb} MB in ${elapsed}s${resumed ? ', resumed' : ''})\n`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this._emit('finish', {
|
|
85
|
+
output: this.output,
|
|
86
|
+
url: this.url,
|
|
87
|
+
bytes: this.receivedBytes,
|
|
88
|
+
elapsed_sec: parseFloat(elapsed),
|
|
89
|
+
status: statusCode,
|
|
90
|
+
resumed: resumed || false
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
error(message, code) {
|
|
95
|
+
if (this.json) {
|
|
96
|
+
this._emit('error', { message, code: code || 'UNKNOWN' });
|
|
97
|
+
} else {
|
|
98
|
+
process.stderr.write(`✗ Error: ${message}\n`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
info(message) {
|
|
103
|
+
if (this.quiet || this.json) return;
|
|
104
|
+
process.stderr.write(`${message}\n`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = Reporter;
|
package/package.json
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@penadidik/meo-agent",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "The unified AI agent runtime from Meo Code Labs — bundles a wget-like CLI with --json/--output/--continue/--sha256/--mirror/doctor/plugins features, developer-kit (use case scaffolder), and generate-pr (PR description generator) into one cross-platform distribution.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"meo-agent": "./bin/meo-agent.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "pkg .",
|
|
11
|
+
"test": "node test/test.js"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@yao-pkg/pkg": "^5.16.1"
|
|
15
|
+
},
|
|
16
|
+
"pkg": {
|
|
17
|
+
"targets": [
|
|
18
|
+
"node18-win-x64",
|
|
19
|
+
"node18-macos-x64",
|
|
20
|
+
"node18-linux-x64"
|
|
21
|
+
],
|
|
22
|
+
"outputPath": "dist",
|
|
23
|
+
"scripts": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"bin/**/*.js",
|
|
26
|
+
"lib/**/*.js"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/meocode-labs/meo-agent.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/meocode-labs/meo-agent/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/meocode-labs/meo-agent#readme",
|
|
37
|
+
"author": {
|
|
38
|
+
"name": "penadidik",
|
|
39
|
+
"email": "researcher@meocode.com",
|
|
40
|
+
"url": "https://github.com/penadidik"
|
|
41
|
+
},
|
|
42
|
+
"contributors": [
|
|
43
|
+
{
|
|
44
|
+
"name": "Meo Code Labs",
|
|
45
|
+
"url": "https://meocode.com"
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"funding": {
|
|
49
|
+
"type": "github",
|
|
50
|
+
"url": "https://github.com/sponsors/meocode-labs"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"keywords": [
|
|
54
|
+
"meo-agent",
|
|
55
|
+
"meo-code-labs",
|
|
56
|
+
"meocode",
|
|
57
|
+
"wget",
|
|
58
|
+
"cli",
|
|
59
|
+
"downloader",
|
|
60
|
+
"http",
|
|
61
|
+
"https",
|
|
62
|
+
"developer-tools",
|
|
63
|
+
"ai-agents",
|
|
64
|
+
"ai-tooling",
|
|
65
|
+
"developer-kit",
|
|
66
|
+
"generate-pr",
|
|
67
|
+
"terminal",
|
|
68
|
+
"progress-bar",
|
|
69
|
+
"json",
|
|
70
|
+
"resume",
|
|
71
|
+
"doctor"
|
|
72
|
+
],
|
|
73
|
+
"engines": {
|
|
74
|
+
"node": ">=18"
|
|
75
|
+
},
|
|
76
|
+
"files": [
|
|
77
|
+
"index.js",
|
|
78
|
+
"bin/",
|
|
79
|
+
"lib/",
|
|
80
|
+
"examples/",
|
|
81
|
+
"developer-kit/",
|
|
82
|
+
"pull_request/",
|
|
83
|
+
"README.md",
|
|
84
|
+
"LICENSE",
|
|
85
|
+
"CHANGELOG.md"
|
|
86
|
+
]
|
|
87
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Auto-Generate Pull Request
|
|
2
|
+
|
|
3
|
+
Sistem untuk auto-generate Pull Request description/review dari git changes.
|
|
4
|
+
|
|
5
|
+
## Cara Kerja (2 Fase)
|
|
6
|
+
|
|
7
|
+
1. **Fase Metadata (script)** — `generate-pr.sh` otomatis mengisi: repo, branch,
|
|
8
|
+
daftar file, jumlah baris (+/-), diff stat, dan commits.
|
|
9
|
+
2. **Fase Analisis (AI/manual)** — untuk type `lead`/`hod`, file hasil berisi
|
|
10
|
+
placeholder `<!-- REVIEW:* -->` dan tabel `TBD` yang diisi oleh reviewer
|
|
11
|
+
(via opencode command `/generate-pr` atau manual).
|
|
12
|
+
|
|
13
|
+
## Struktur
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
~/Developer/agent_opencode/
|
|
17
|
+
└── pull_request/
|
|
18
|
+
├── generate-pr.sh # Main script
|
|
19
|
+
├── templates/
|
|
20
|
+
│ ├── basic.md
|
|
21
|
+
│ ├── lead-review.md
|
|
22
|
+
│ └── hod-review.md
|
|
23
|
+
├── output/ # OUTPUT (per app)
|
|
24
|
+
│ └── <app_name>/
|
|
25
|
+
│ └── pull_request_<datetime>.md
|
|
26
|
+
└── README.md
|
|
27
|
+
|
|
28
|
+
~/.config/opencode/command/generate-pr.md # opencode command
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Penggunaan
|
|
32
|
+
|
|
33
|
+
### Via Script
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
S=~/Developer/agent_opencode/pull_request/generate-pr.sh
|
|
37
|
+
|
|
38
|
+
# Basic (default), current branch
|
|
39
|
+
"$S"
|
|
40
|
+
|
|
41
|
+
# Tipe + branch + PR number
|
|
42
|
+
"$S" lead improvements/pickup-report/08062026
|
|
43
|
+
"$S" hod request/SCA-Ticketing/17062026 515
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Argumen:
|
|
47
|
+
1. `type` : `basic` | `lead` | `hod` (default `basic`)
|
|
48
|
+
2. `branch` : target branch (default: current branch)
|
|
49
|
+
3. `pr_number` : nomor PR untuk URL (default: `TBD`)
|
|
50
|
+
|
|
51
|
+
### Via opencode
|
|
52
|
+
|
|
53
|
+
Gunakan keyword:
|
|
54
|
+
- "generate pr" / "make pr description" → **basic**
|
|
55
|
+
- "as lead review ..." → **lead**
|
|
56
|
+
- "as hod review ..." → **hod**
|
|
57
|
+
|
|
58
|
+
## Logika Pemilihan Diff (otomatis)
|
|
59
|
+
|
|
60
|
+
Script memilih sumber perubahan dengan prioritas:
|
|
61
|
+
1. `origin/<branch>` vs base branch (jika ada perbedaan)
|
|
62
|
+
2. `<branch>` lokal vs base branch
|
|
63
|
+
3. Working tree (uncommitted changes) vs `HEAD`
|
|
64
|
+
|
|
65
|
+
Base branch dideteksi otomatis: `origin/master` → `origin/main` → `master` → `main`.
|
|
66
|
+
|
|
67
|
+
## Output Naming
|
|
68
|
+
|
|
69
|
+
`pull_request_<datetime>.md` — jika sudah ada (detik sama), otomatis menambah
|
|
70
|
+
suffix `_1`, `_2`, dst untuk menghindari overwrite.
|
|
71
|
+
|
|
72
|
+
Output tersimpan di:
|
|
73
|
+
```
|
|
74
|
+
~/Developer/agent_opencode/pull_request/output/<app_name>/pull_request_<datetime>.md
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Variabel Template
|
|
78
|
+
|
|
79
|
+
| Variable | Keterangan |
|
|
80
|
+
|----------|------------|
|
|
81
|
+
| `{{REPO}}` | Nama repository |
|
|
82
|
+
| `{{BRANCH}}` | Branch |
|
|
83
|
+
| `{{PR_NUMBER}}` | Nomor PR (default TBD) |
|
|
84
|
+
| `{{AUTHOR}}` | git user.name |
|
|
85
|
+
| `{{DATE}}` / `{{DATETIME}}` | Tanggal / timestamp |
|
|
86
|
+
| `{{SUMMARY}}` | Ringkasan otomatis |
|
|
87
|
+
| `{{FILES_COUNT}}` | Jumlah file |
|
|
88
|
+
| `{{BACKEND_COUNT}}` | File `.php` |
|
|
89
|
+
| `{{FRONTEND_COUNT}}` | File `.js`/`.ts` |
|
|
90
|
+
| `{{BLADE_COUNT}}` | File `.blade.php` |
|
|
91
|
+
| `{{NEW_FILES}}` | File baru |
|
|
92
|
+
| `{{LINES_ADDED}}` / `{{LINES_REMOVED}}` | Total +/- baris |
|
|
93
|
+
| `{{COMMITS_COUNT}}` | Jumlah commit |
|
|
94
|
+
| `{{COMMITS}}` | Daftar commit |
|
|
95
|
+
| `{{DIFF_STAT}}` | Statistik diff |
|
|
96
|
+
| `{{FILES_MODIFIED}}` | Tabel markdown file (template basic) |
|
|
97
|
+
|
|
98
|
+
## Setup
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
chmod +x ~/Developer/agent_opencode/pull_request/generate-pr.sh
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
opencode command sudah tersedia di `~/.config/opencode/command/generate-pr.md`.
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# generate-pr.sh
|
|
3
|
+
# Auto-generate Pull Request description/review from git changes.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ./generate-pr.sh [type] [branch] [pr_number]
|
|
7
|
+
# type : basic | lead | hod (default: basic)
|
|
8
|
+
# branch : target branch (default: current branch)
|
|
9
|
+
# pr_number : PR number for the URL (default: TBD)
|
|
10
|
+
#
|
|
11
|
+
# Output:
|
|
12
|
+
# ~/Developer/office/ivosights/pull_request/<app_name>/pull_request_<datetime>.md
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
# Helpers
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
die() { echo "❌ Error: $*" >&2; exit 1; }
|
|
20
|
+
|
|
21
|
+
# Ensure we are inside a git repository
|
|
22
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "Not inside a git repository."
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------------------
|
|
25
|
+
# Parameters
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
TYPE="${1:-basic}"
|
|
28
|
+
BRANCH="${2:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "HEAD")}"
|
|
29
|
+
PR_NUMBER="${3:-TBD}"
|
|
30
|
+
|
|
31
|
+
case "$TYPE" in
|
|
32
|
+
basic) TEMPLATE_NAME="basic" ;;
|
|
33
|
+
lead) TEMPLATE_NAME="lead-review" ;;
|
|
34
|
+
hod) TEMPLATE_NAME="hod-review" ;;
|
|
35
|
+
*) die "Invalid type '$TYPE'. Use: basic | lead | hod" ;;
|
|
36
|
+
esac
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------------------
|
|
39
|
+
# Repo & meta info
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
REPO="$(basename "$(git rev-parse --show-toplevel)")"
|
|
42
|
+
APP_NAME="${REPO//-/_}"
|
|
43
|
+
DATETIME="$(date +"%Y%m%d_%H%M%S")"
|
|
44
|
+
DATE="$(date +"%Y-%m-%d")"
|
|
45
|
+
AUTHOR="$(git config user.name 2>/dev/null || echo "Unknown")"
|
|
46
|
+
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
# Determine base branch (master or main)
|
|
49
|
+
# ---------------------------------------------------------------------------
|
|
50
|
+
BASE_BRANCH=""
|
|
51
|
+
for candidate in origin/master origin/main master main; do
|
|
52
|
+
if git rev-parse --verify "$candidate" >/dev/null 2>&1; then
|
|
53
|
+
BASE_BRANCH="$candidate"
|
|
54
|
+
break
|
|
55
|
+
fi
|
|
56
|
+
done
|
|
57
|
+
[[ -n "$BASE_BRANCH" ]] || die "Could not resolve base branch (master/main)."
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# Resolve the comparison target (the "head" side of the diff)
|
|
61
|
+
# Priority: origin/<branch> -> <branch> -> working tree (uncommitted)
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
COMPARE_MODE=""
|
|
64
|
+
HEAD_REF=""
|
|
65
|
+
|
|
66
|
+
if git rev-parse --verify "origin/$BRANCH" >/dev/null 2>&1 \
|
|
67
|
+
&& [[ -n "$(git diff "$BASE_BRANCH".."origin/$BRANCH" --name-only 2>/dev/null)" ]]; then
|
|
68
|
+
HEAD_REF="origin/$BRANCH"
|
|
69
|
+
COMPARE_MODE="branch"
|
|
70
|
+
elif git rev-parse --verify "$BRANCH" >/dev/null 2>&1 \
|
|
71
|
+
&& [[ -n "$(git diff "$BASE_BRANCH".."$BRANCH" --name-only 2>/dev/null)" ]]; then
|
|
72
|
+
HEAD_REF="$BRANCH"
|
|
73
|
+
COMPARE_MODE="branch"
|
|
74
|
+
elif [[ -n "$(git status --porcelain 2>/dev/null)" ]]; then
|
|
75
|
+
# Fall back to uncommitted working tree changes
|
|
76
|
+
COMPARE_MODE="working"
|
|
77
|
+
else
|
|
78
|
+
die "No changes found for branch '$BRANCH' vs '$BASE_BRANCH' and no uncommitted changes."
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
# Gather diff data based on compare mode
|
|
83
|
+
# ---------------------------------------------------------------------------
|
|
84
|
+
if [[ "$COMPARE_MODE" == "branch" ]]; then
|
|
85
|
+
DIFF_RANGE="$BASE_BRANCH..$HEAD_REF"
|
|
86
|
+
DIFF_STAT="$(git diff "$DIFF_RANGE" --stat)"
|
|
87
|
+
NAME_ONLY="$(git diff "$DIFF_RANGE" --name-only)"
|
|
88
|
+
NEW_NAMES="$(git diff "$DIFF_RANGE" --name-only --diff-filter=A)"
|
|
89
|
+
NUMSTAT="$(git diff "$DIFF_RANGE" --numstat)"
|
|
90
|
+
COMMITS="$(git log "$DIFF_RANGE" --oneline)"
|
|
91
|
+
else
|
|
92
|
+
# working tree (staged + unstaged) vs HEAD
|
|
93
|
+
DIFF_STAT="$(git diff HEAD --stat)"
|
|
94
|
+
NAME_ONLY="$(git diff HEAD --name-only)"
|
|
95
|
+
NEW_NAMES="$(git ls-files --others --exclude-standard)"
|
|
96
|
+
NUMSTAT="$(git diff HEAD --numstat)"
|
|
97
|
+
COMMITS="(uncommitted working-tree changes)"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
# ---------------------------------------------------------------------------
|
|
101
|
+
# Counts
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
count_lines() { [[ -z "$1" ]] && echo 0 || echo "$1" | grep -c . ; }
|
|
104
|
+
|
|
105
|
+
FILES_COUNT="$(count_lines "$NAME_ONLY")"
|
|
106
|
+
BACKEND_COUNT="$(echo "$NAME_ONLY" | grep -cE '\.php$' || true)"
|
|
107
|
+
FRONTEND_COUNT="$(echo "$NAME_ONLY" | grep -cE '\.(js|ts)$' || true)"
|
|
108
|
+
BLADE_COUNT="$(echo "$NAME_ONLY" | grep -cE '\.blade\.php$' || true)"
|
|
109
|
+
NEW_FILES="$(count_lines "$NEW_NAMES")"
|
|
110
|
+
COMMITS_COUNT="$(count_lines "$COMMITS")"
|
|
111
|
+
|
|
112
|
+
LINES_ADDED="$(echo "$NUMSTAT" | awk '{sum+=$1} END {print sum+0}')"
|
|
113
|
+
LINES_REMOVED="$(echo "$NUMSTAT" | awk '{sum+=$2} END {print sum+0}')"
|
|
114
|
+
|
|
115
|
+
SUMMARY="Pull request from branch \`$BRANCH\` ($COMPARE_MODE mode) with $FILES_COUNT file(s) changed, +$LINES_ADDED/-$LINES_REMOVED lines."
|
|
116
|
+
|
|
117
|
+
# Build a markdown table of modified files
|
|
118
|
+
if [[ -n "$NAME_ONLY" ]]; then
|
|
119
|
+
FILES_MODIFIED="$(echo "$NAME_ONLY" | sed 's/.*/| `&` |/')"
|
|
120
|
+
FILES_MODIFIED="| File |"$'\n'"|------|"$'\n'"$FILES_MODIFIED"
|
|
121
|
+
else
|
|
122
|
+
FILES_MODIFIED="_No files_"
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
# ---------------------------------------------------------------------------
|
|
126
|
+
# Template resolution
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
TEMPLATE_DIR="$HOME/Developer/agent_opencode/pull_request/templates"
|
|
129
|
+
TEMPLATE_FILE="$TEMPLATE_DIR/$TEMPLATE_NAME.md"
|
|
130
|
+
[[ -f "$TEMPLATE_FILE" ]] || die "Template not found: $TEMPLATE_FILE"
|
|
131
|
+
|
|
132
|
+
# ---------------------------------------------------------------------------
|
|
133
|
+
# Output path (avoid collisions)
|
|
134
|
+
# ---------------------------------------------------------------------------
|
|
135
|
+
OUTPUT_DIR="$HOME/Developer/agent_opencode/pull_request/output/$APP_NAME"
|
|
136
|
+
mkdir -p "$OUTPUT_DIR"
|
|
137
|
+
OUTPUT_FILE="$OUTPUT_DIR/pull_request_${DATETIME}.md"
|
|
138
|
+
counter=1
|
|
139
|
+
while [[ -e "$OUTPUT_FILE" ]]; do
|
|
140
|
+
OUTPUT_FILE="$OUTPUT_DIR/pull_request_${DATETIME}_${counter}.md"
|
|
141
|
+
counter=$((counter + 1))
|
|
142
|
+
done
|
|
143
|
+
|
|
144
|
+
# ---------------------------------------------------------------------------
|
|
145
|
+
# Render template (multiline-safe via awk placeholder replacement)
|
|
146
|
+
# ---------------------------------------------------------------------------
|
|
147
|
+
render() {
|
|
148
|
+
local content
|
|
149
|
+
content="$(cat "$TEMPLATE_FILE")"
|
|
150
|
+
|
|
151
|
+
# Single-line replacements use awk gsub with literal strings.
|
|
152
|
+
# Multiline values are injected via environment + awk to stay safe.
|
|
153
|
+
REPO="$REPO" BRANCH="$BRANCH" PR_NUMBER="$PR_NUMBER" AUTHOR="$AUTHOR" \
|
|
154
|
+
DATE="$DATE" DATETIME="$DATETIME" SUMMARY="$SUMMARY" \
|
|
155
|
+
FILES_COUNT="$FILES_COUNT" BACKEND_COUNT="$BACKEND_COUNT" \
|
|
156
|
+
FRONTEND_COUNT="$FRONTEND_COUNT" BLADE_COUNT="$BLADE_COUNT" \
|
|
157
|
+
NEW_FILES="$NEW_FILES" LINES_ADDED="$LINES_ADDED" \
|
|
158
|
+
LINES_REMOVED="$LINES_REMOVED" COMMITS_COUNT="$COMMITS_COUNT" \
|
|
159
|
+
DIFF_STAT="$DIFF_STAT" COMMITS="$COMMITS" FILES_MODIFIED="$FILES_MODIFIED" \
|
|
160
|
+
awk '
|
|
161
|
+
BEGIN {
|
|
162
|
+
# Map placeholder -> env var name
|
|
163
|
+
n = split("REPO BRANCH PR_NUMBER AUTHOR DATE DATETIME SUMMARY FILES_COUNT BACKEND_COUNT FRONTEND_COUNT BLADE_COUNT NEW_FILES LINES_ADDED LINES_REMOVED COMMITS_COUNT DIFF_STAT COMMITS FILES_MODIFIED", keys, " ")
|
|
164
|
+
}
|
|
165
|
+
{
|
|
166
|
+
line = $0
|
|
167
|
+
for (i = 1; i <= n; i++) {
|
|
168
|
+
ph = "{{" keys[i] "}}"
|
|
169
|
+
val = ENVIRON[keys[i]]
|
|
170
|
+
# replace all occurrences without regex interpretation
|
|
171
|
+
while ((idx = index(line, ph)) > 0) {
|
|
172
|
+
line = substr(line, 1, idx-1) val substr(line, idx + length(ph))
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
print line
|
|
176
|
+
}' <<< "$content"
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
render > "$OUTPUT_FILE"
|
|
180
|
+
|
|
181
|
+
# ---------------------------------------------------------------------------
|
|
182
|
+
# Report
|
|
183
|
+
# ---------------------------------------------------------------------------
|
|
184
|
+
echo "✅ Pull Request generated successfully!"
|
|
185
|
+
echo "📁 Output : $OUTPUT_FILE"
|
|
186
|
+
echo "📋 Type : $TYPE ($TEMPLATE_NAME)"
|
|
187
|
+
echo "🌿 Branch : $BRANCH [base: $BASE_BRANCH, mode: $COMPARE_MODE]"
|
|
188
|
+
echo "📊 Files : $FILES_COUNT (PHP: $BACKEND_COUNT, JS/TS: $FRONTEND_COUNT, Blade: $BLADE_COUNT, New: $NEW_FILES)"
|
|
189
|
+
echo "➕ Lines : +$LINES_ADDED / -$LINES_REMOVED"
|
|
190
|
+
echo "🔢 Commits: $COMMITS_COUNT"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
## Pull Request Description
|
|
2
|
+
|
|
3
|
+
**Repository:** {{REPO}}
|
|
4
|
+
**PR URL:** https://github.com/ivosights/{{REPO}}/pull/{{PR_NUMBER}}
|
|
5
|
+
**Branch:** `{{BRANCH}}`
|
|
6
|
+
**Author:** {{AUTHOR}}
|
|
7
|
+
**Date:** {{DATE}}
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
### Summary
|
|
12
|
+
|
|
13
|
+
{{SUMMARY}}
|
|
14
|
+
|
|
15
|
+
### Scope
|
|
16
|
+
- **Files changed:** {{FILES_COUNT}} (PHP: {{BACKEND_COUNT}}, JS/TS: {{FRONTEND_COUNT}}, Blade: {{BLADE_COUNT}})
|
|
17
|
+
- **New files:** {{NEW_FILES}}
|
|
18
|
+
- **Lines:** +{{LINES_ADDED}} / -{{LINES_REMOVED}}
|
|
19
|
+
|
|
20
|
+
### Files Modified
|
|
21
|
+
|
|
22
|
+
{{FILES_MODIFIED}}
|
|
23
|
+
|
|
24
|
+
### What This PR Does
|
|
25
|
+
|
|
26
|
+
<!-- Describe the purpose / behaviour change of this PR -->
|
|
27
|
+
_..._
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
### Diff Summary
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
{{DIFF_STAT}}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Commits
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
{{COMMITS}}
|
|
41
|
+
```
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# HOD Code Review
|
|
2
|
+
|
|
3
|
+
**Repository:** {{REPO}}
|
|
4
|
+
**PR URL:** https://github.com/ivosights/{{REPO}}/pull/{{PR_NUMBER}}
|
|
5
|
+
**Branch:** `{{BRANCH}}`
|
|
6
|
+
**Reviewed By:** Head of Department (HOD)
|
|
7
|
+
**Review Date:** {{DATE}}
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Executive Summary
|
|
12
|
+
|
|
13
|
+
{{SUMMARY}}
|
|
14
|
+
|
|
15
|
+
> _HOD verdict & qualitative analysis to be completed by reviewer (opencode `/generate-pr hod` or manual)._
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Scope of Changes
|
|
20
|
+
|
|
21
|
+
| Metric | Value |
|
|
22
|
+
|--------|-------|
|
|
23
|
+
| Files Modified | {{FILES_COUNT}} files |
|
|
24
|
+
| Lines Added | +{{LINES_ADDED}} |
|
|
25
|
+
| Lines Removed | -{{LINES_REMOVED}} |
|
|
26
|
+
| New Files | {{NEW_FILES}} |
|
|
27
|
+
| Commits | {{COMMITS_COUNT}} |
|
|
28
|
+
|
|
29
|
+
### Files Breakdown
|
|
30
|
+
- **Backend (PHP):** {{BACKEND_COUNT}} files
|
|
31
|
+
- **Frontend (JS/TS):** {{FRONTEND_COUNT}} files
|
|
32
|
+
- **Blade Templates:** {{BLADE_COUNT}} files
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 🟥 Critical Issues (Must Fix)
|
|
37
|
+
|
|
38
|
+
<!-- REVIEW:CRITICAL -->
|
|
39
|
+
_List blocking issues here (security, broken logic, debug code, undefined vars)._
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 🟧 High Priority Issues
|
|
44
|
+
|
|
45
|
+
<!-- REVIEW:HIGH -->
|
|
46
|
+
_List high-priority concerns (breaking changes, missing tests, interface changes)._
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🟨 Medium / Minor Issues
|
|
51
|
+
|
|
52
|
+
<!-- REVIEW:MEDIUM -->
|
|
53
|
+
_List code-quality, duplication, and style concerns._
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Required Actions Before Approval
|
|
58
|
+
|
|
59
|
+
| Priority | Action | Owner |
|
|
60
|
+
|----------|--------|-------|
|
|
61
|
+
| 🔴 CRITICAL | _..._ | _..._ |
|
|
62
|
+
| 🟧 HIGH | _..._ | _..._ |
|
|
63
|
+
| 🟨 MEDIUM | _..._ | _..._ |
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Risk Assessment
|
|
68
|
+
|
|
69
|
+
| Area | Risk Level | Notes |
|
|
70
|
+
|------|-----------|-------|
|
|
71
|
+
| **Security** | _TBD_ | _..._ |
|
|
72
|
+
| **Performance** | _TBD_ | _..._ |
|
|
73
|
+
| **Data Integrity** | _TBD_ | _..._ |
|
|
74
|
+
| **Backward Compat** | _TBD_ | _..._ |
|
|
75
|
+
| **Testing** | _TBD_ | _..._ |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Final Verdict
|
|
80
|
+
|
|
81
|
+
### ⬜ TBD — APPROVE / REQUEST CHANGES
|
|
82
|
+
|
|
83
|
+
**Reason:** _..._
|
|
84
|
+
|
|
85
|
+
**Recommended Next Steps:**
|
|
86
|
+
1. _..._
|
|
87
|
+
2. _..._
|
|
88
|
+
3. _..._
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Files Changed Summary
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
{{DIFF_STAT}}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Commits
|
|
99
|
+
```
|
|
100
|
+
{{COMMITS}}
|
|
101
|
+
```
|