@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/bin/meo-agent.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const { parseArgs, printHelp } = require('../lib/args');
|
|
8
|
+
const Reporter = require('../lib/reporter');
|
|
9
|
+
const { download, deriveFilename } = require('../lib/downloader');
|
|
10
|
+
const { doctor } = require('../lib/doctor');
|
|
11
|
+
const { verifyFile, parseChecksumArg } = require('../lib/checksum');
|
|
12
|
+
const { mirror } = require('../lib/mirror');
|
|
13
|
+
const { loadConfig } = require('../lib/config');
|
|
14
|
+
const { PluginManager } = require('../lib/plugins');
|
|
15
|
+
|
|
16
|
+
const pkg = require('../package.json');
|
|
17
|
+
|
|
18
|
+
async function runDownload(opts, config, plugins, reporter) {
|
|
19
|
+
if (!opts.url) {
|
|
20
|
+
if (opts.json) {
|
|
21
|
+
console.log(JSON.stringify({ event: 'error', message: 'Missing URL', code: 'MISSING_URL' }));
|
|
22
|
+
} else {
|
|
23
|
+
process.stderr.write('Error: Missing URL\n');
|
|
24
|
+
printHelp();
|
|
25
|
+
}
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const timeout = (opts.timeout || config.timeout) * 1000;
|
|
30
|
+
const output = opts.output || (config.outputDir ? path.join(config.outputDir, deriveFilename(opts.url)) : deriveFilename(opts.url));
|
|
31
|
+
|
|
32
|
+
reporter.start(opts.url, output);
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
await plugins.trigger('before_download', { url: opts.url, output, opts, config });
|
|
36
|
+
|
|
37
|
+
const result = await download({ ...opts, output, timeout }, reporter);
|
|
38
|
+
|
|
39
|
+
await plugins.trigger('after_download', { ...result, opts, config });
|
|
40
|
+
|
|
41
|
+
if (opts.sha256) {
|
|
42
|
+
reporter.info(`Verifying SHA256: ${opts.sha256}`);
|
|
43
|
+
const verify = await verifyFile(output, parseChecksumArg(opts.sha256));
|
|
44
|
+
if (!verify.ok) {
|
|
45
|
+
reporter.error(`Checksum mismatch: expected ${verify.expected}, got ${verify.actual}`, 'CHECKSUM_MISMATCH');
|
|
46
|
+
process.exit(2);
|
|
47
|
+
}
|
|
48
|
+
reporter.info('✓ Checksum verified');
|
|
49
|
+
if (opts.json) {
|
|
50
|
+
console.log(JSON.stringify({ event: 'checksum_ok', expected: verify.expected, actual: verify.actual }));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
process.exit(0);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
reporter.error(err.message, err.code || 'DOWNLOAD_FAILED');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function runMirror(opts, config, plugins, reporter) {
|
|
62
|
+
if (!opts.url) {
|
|
63
|
+
reporter.error('Mirror requires a URL', 'MISSING_URL');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
const outDir = opts.output || './mirror';
|
|
67
|
+
const mirrorOpts = {
|
|
68
|
+
maxDepth: opts.mirrorDepth || config.mirrorMaxDepth,
|
|
69
|
+
limit: opts.mirrorLimit || config.mirrorLimit
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
reporter.info(`Mirroring ${opts.url} → ${outDir} (depth=${mirrorOpts.maxDepth}, limit=${mirrorOpts.limit})`);
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const results = await mirror(opts.url, outDir, mirrorOpts);
|
|
76
|
+
if (opts.json) {
|
|
77
|
+
console.log(JSON.stringify({ event: 'mirror_done', count: results.length, results }));
|
|
78
|
+
} else {
|
|
79
|
+
reporter.info(`✓ Mirrored ${results.length} pages to ${outDir}`);
|
|
80
|
+
}
|
|
81
|
+
process.exit(0);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
reporter.error(err.message, 'MIRROR_FAILED');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function runDoctor(opts, config, plugins, reporter) {
|
|
89
|
+
const results = await doctor();
|
|
90
|
+
if (opts.json) {
|
|
91
|
+
console.log(JSON.stringify({ event: 'doctor', results }, null, 2));
|
|
92
|
+
} else {
|
|
93
|
+
console.log('meo-agent doctor — environment diagnostics\n');
|
|
94
|
+
for (const r of results) {
|
|
95
|
+
const icon = r.status === 'ok' ? '✓' : '✗';
|
|
96
|
+
console.log(` ${icon} ${r.label.padEnd(36)} ${r.detail}`);
|
|
97
|
+
}
|
|
98
|
+
const failed = results.filter(r => r.status === 'fail').length;
|
|
99
|
+
console.log(`\n${results.length - failed}/${results.length} checks passed.`);
|
|
100
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function runPlugins(opts, config, plugins, reporter) {
|
|
105
|
+
const list = plugins.list();
|
|
106
|
+
if (opts.json) {
|
|
107
|
+
console.log(JSON.stringify({ event: 'plugins', plugins: list, loadedFrom: config._loadedFrom }));
|
|
108
|
+
} else {
|
|
109
|
+
if (list.length === 0) {
|
|
110
|
+
console.log('No plugins loaded.');
|
|
111
|
+
} else {
|
|
112
|
+
console.log(`Loaded ${list.length} plugin(s):\n`);
|
|
113
|
+
for (const p of list) {
|
|
114
|
+
console.log(` ${p.name} v${p.version}`);
|
|
115
|
+
if (p.description) console.log(` ${p.description}`);
|
|
116
|
+
if (p.hooks && p.hooks.length) console.log(` hooks: ${p.hooks.join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function main() {
|
|
123
|
+
const opts = parseArgs(process.argv);
|
|
124
|
+
|
|
125
|
+
if (opts.version) {
|
|
126
|
+
console.log(`meo-agent ${pkg.version}`);
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (opts.help) {
|
|
131
|
+
printHelp();
|
|
132
|
+
process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const config = loadConfig(opts.config);
|
|
136
|
+
const reporter = new Reporter({ json: opts.json, quiet: opts.quiet });
|
|
137
|
+
const plugins = new PluginManager(reporter);
|
|
138
|
+
const loaded = plugins.loadAll();
|
|
139
|
+
|
|
140
|
+
if (loaded > 0 && !opts.quiet && !opts.json) {
|
|
141
|
+
reporter.info(`Loaded ${loaded} plugin(s)`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const isMirror = opts.mirror || process.argv.includes('mirror');
|
|
145
|
+
const isDoctor = process.argv.includes('doctor');
|
|
146
|
+
const isPlugins = opts.pluginList || process.argv.includes('plugins');
|
|
147
|
+
|
|
148
|
+
if (isDoctor) return runDoctor(opts, config, plugins, reporter);
|
|
149
|
+
if (isPlugins) return runPlugins(opts, config, plugins, reporter);
|
|
150
|
+
if (isMirror) return runMirror(opts, config, plugins, reporter);
|
|
151
|
+
|
|
152
|
+
return runDownload(opts, config, plugins, reporter);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
main().catch((err) => {
|
|
156
|
+
process.stderr.write(`Fatal: ${err.stack || err.message}\n`);
|
|
157
|
+
process.exit(1);
|
|
158
|
+
});
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Developer Kit — Use Case Scaffold
|
|
2
|
+
|
|
3
|
+
Sistem untuk auto-generate folder `usecase-NNN/` berisi 3 file:
|
|
4
|
+
`requirements.md`, `tasks.md`, `tdd.md` dari brief deskriptif.
|
|
5
|
+
|
|
6
|
+
## Struktur
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
~/Developer/agent_opencode/developer-kit/
|
|
10
|
+
├── developer-kit.sh # Main script
|
|
11
|
+
├── templates/
|
|
12
|
+
│ ├── requirements.md # Template requirements
|
|
13
|
+
│ ├── tasks.md # Template tasks breakdown
|
|
14
|
+
│ └── tdd.md # Template TDD plan
|
|
15
|
+
└── README.md
|
|
16
|
+
|
|
17
|
+
~/.config/opencode/command/developer-kit.md # opencode command (/developer-kit)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Cara Pakai
|
|
21
|
+
|
|
22
|
+
### Via Script (Manual)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
S=~/Developer/agent_opencode/developer-kit/developer-kit.sh
|
|
26
|
+
|
|
27
|
+
# 1) Docs directory belum ada → akan dibuat
|
|
28
|
+
"$S" ./docs "Add login with OAuth2"
|
|
29
|
+
|
|
30
|
+
# 2) Docs directory sudah ada → auto-increment usecase-NNN
|
|
31
|
+
"$S" ./docs "Pickup report improvements"
|
|
32
|
+
|
|
33
|
+
# 3) Dengan explicit number (3 digit)
|
|
34
|
+
"$S" ./docs "Custom numbered use case" 5
|
|
35
|
+
|
|
36
|
+
# 4) Custom docs path
|
|
37
|
+
"$S" ~/Developer/office/ivosights/commercial/app-pipeline/docs "Add payment gateway"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Argumen:
|
|
41
|
+
1. `docs_dir` : target directory (akan dibuat jika belum ada)
|
|
42
|
+
2. `brief` : deskripsi singkat use case
|
|
43
|
+
3. `usecase_number` : (opsional) nomor explicit, 3 digit dengan leading zero
|
|
44
|
+
|
|
45
|
+
### Via opencode
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
/developer-kit # Tanya docs dir + brief interaktif
|
|
49
|
+
/developer-kit add login with OAuth2 # Brief dari argumen
|
|
50
|
+
/developer-kit ./docs pickup report improvements # Docs dir + brief
|
|
51
|
+
/developer-kit 5 explicit numbered use case # Brief + explicit number
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Interaktif Prompt (Native TUI Popup)
|
|
55
|
+
|
|
56
|
+
Jika argumen tidak lengkap, command akan trigger **native TUI popup** via
|
|
57
|
+
`question` tool (UX yang sama seperti `/connect` dan `/models`):
|
|
58
|
+
|
|
59
|
+
| Prompt | Trigger |
|
|
60
|
+
|--------|---------|
|
|
61
|
+
| "Describe the use case in one sentence — what should be built?" | Tidak ada brief di argumen |
|
|
62
|
+
| "Where should the docs folder live?" | Docs dir tidak ter-resolve |
|
|
63
|
+
| "usecase-005 already exists. How should I proceed?" | Nomor konflik dengan folder ada |
|
|
64
|
+
|
|
65
|
+
User bisa:
|
|
66
|
+
- Pilih dari opsi (default/sample values)
|
|
67
|
+
- Ketik jawaban sendiri (free-text)
|
|
68
|
+
- Navigate antar multiple questions sebelum submit
|
|
69
|
+
|
|
70
|
+
## Counter Logic
|
|
71
|
+
|
|
72
|
+
- Auto-detect folder `usecase-NNN` tertinggi yang ada
|
|
73
|
+
- Next folder = `usecase-(NNN+1)` (zero-padded 3 digit)
|
|
74
|
+
- Contoh: `usecase-001, 002, 005` ada → next = `usecase-006`
|
|
75
|
+
- Bisa override dengan explicit number argumen ke-3
|
|
76
|
+
|
|
77
|
+
## Generated Files
|
|
78
|
+
|
|
79
|
+
| File | Isi |
|
|
80
|
+
|------|-----|
|
|
81
|
+
| `requirements.md` | Functional & non-functional requirements, acceptance criteria, assumptions, constraints |
|
|
82
|
+
| `tasks.md` | 5-phase task breakdown (Discovery, Design, Implementation, Quality, Release) dengan checklist |
|
|
83
|
+
| `tdd.md` | Test matrix, edge cases, performance tests, security tests, CI/CD integration |
|
|
84
|
+
|
|
85
|
+
## Reference Template
|
|
86
|
+
|
|
87
|
+
`~/Developer/office/ivosights/commercial/app-pipeline/docs/usecase-001/`
|
|
88
|
+
|
|
89
|
+
## Setup
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
chmod +x ~/Developer/agent_opencode/developer-kit/developer-kit.sh
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
opencode command sudah tersedia di `~/.config/opencode/command/developer-kit.md`.
|
|
96
|
+
|
|
97
|
+
## Restart Required
|
|
98
|
+
|
|
99
|
+
Restart opencode untuk aktivasi command `/developer-kit` (config di-load saat startup).
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# developer-kit.sh
|
|
3
|
+
# Generate a new use-case folder (usecase-NNN) with requirements.md, tasks.md, tdd.md
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# ./developer-kit.sh <docs_dir> "<brief_description>" [usecase_number]
|
|
7
|
+
#
|
|
8
|
+
# docs_dir : target docs directory (e.g. docs/)
|
|
9
|
+
# if doesn't exist, will be created
|
|
10
|
+
# if it exists, will create usecase-NNN
|
|
11
|
+
# brief_description: descriptive title/description of the use case
|
|
12
|
+
# usecase_number : optional explicit 3-digit number (default: auto-increment)
|
|
13
|
+
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
die() { echo "❌ Error: $*" >&2; exit 1; }
|
|
17
|
+
|
|
18
|
+
DOCS_DIR="${1:-}"
|
|
19
|
+
BRIEF="${2:-}"
|
|
20
|
+
EXPLICIT_NUM="${3:-}"
|
|
21
|
+
|
|
22
|
+
[[ -n "$DOCS_DIR" ]] || die "Missing docs directory. Usage: $0 <docs_dir> \"<brief>\" [usecase_number]"
|
|
23
|
+
[[ -n "$BRIEF" ]] || die "Missing brief description. Usage: $0 <docs_dir> \"<brief>\" [usecase_number]"
|
|
24
|
+
|
|
25
|
+
# Resolve to absolute path
|
|
26
|
+
DOCS_DIR="$(cd "$DOCS_DIR" 2>/dev/null && pwd || echo "$DOCS_DIR")"
|
|
27
|
+
|
|
28
|
+
# If docs dir doesn't exist → create it as the first usecase
|
|
29
|
+
if [[ ! -d "$DOCS_DIR" ]]; then
|
|
30
|
+
echo "📁 Docs directory doesn't exist — creating: $DOCS_DIR"
|
|
31
|
+
mkdir -p "$DOCS_DIR"
|
|
32
|
+
NEXT_NUM="001"
|
|
33
|
+
else
|
|
34
|
+
# Auto-increment counter based on existing usecase-NNN folders
|
|
35
|
+
if [[ -n "$EXPLICIT_NUM" ]]; then
|
|
36
|
+
NEXT_NUM="$(printf '%03d' "$((10#$EXPLICIT_NUM))")"
|
|
37
|
+
else
|
|
38
|
+
LAST=0
|
|
39
|
+
while IFS= read -r d; do
|
|
40
|
+
n="$(basename "$d" | sed -nE 's/^usecase-([0-9]+)$/\1/p')"
|
|
41
|
+
[[ -z "$n" ]] && continue
|
|
42
|
+
v="$((10#$n))"
|
|
43
|
+
(( v > LAST )) && LAST="$v"
|
|
44
|
+
done < <(find "$DOCS_DIR" -maxdepth 1 -type d -name 'usecase-[0-9]*' 2>/dev/null)
|
|
45
|
+
NEXT_NUM="$(printf '%03d' "$((LAST + 1))")"
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
USECASE_DIR="$DOCS_DIR/usecase-$NEXT_NUM"
|
|
50
|
+
|
|
51
|
+
# Refuse to overwrite
|
|
52
|
+
if [[ -d "$USECASE_DIR" ]]; then
|
|
53
|
+
die "Folder already exists: $USECASE_DIR"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
mkdir -p "$USECASE_DIR"
|
|
57
|
+
|
|
58
|
+
# Meta
|
|
59
|
+
DATETIME="$(date +"%Y%m%d_%H%M%S")"
|
|
60
|
+
DATE="$(date +"%Y-%m-%d")"
|
|
61
|
+
AUTHOR="${AUTHOR:-$(git config user.name 2>/dev/null || echo "Unknown")}"
|
|
62
|
+
USECASE_NUMBER="$NEXT_NUM"
|
|
63
|
+
USECASE_TITLE="Use Case $NEXT_NUM"
|
|
64
|
+
|
|
65
|
+
# Create a slug from the brief for nicer title
|
|
66
|
+
BRIEF_SLUG="$(echo "$BRIEF" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+|-+$//g' | cut -c1-60)"
|
|
67
|
+
|
|
68
|
+
# Templates location
|
|
69
|
+
TEMPLATE_DIR="$HOME/Developer/agent_opencode/developer-kit/templates"
|
|
70
|
+
|
|
71
|
+
# Render a template: replace {{KEY}} with env var of same name
|
|
72
|
+
render() {
|
|
73
|
+
local file="$1"
|
|
74
|
+
REPO_TITLE="Use Case $NEXT_NUM" \
|
|
75
|
+
USECASE_TITLE="$USECASE_TITLE" \
|
|
76
|
+
USECASE_NUMBER="$USECASE_NUMBER" \
|
|
77
|
+
DATE="$DATE" \
|
|
78
|
+
DATETIME="$DATETIME" \
|
|
79
|
+
AUTHOR="$AUTHOR" \
|
|
80
|
+
BRIEF_DESCRIPTION="$BRIEF" \
|
|
81
|
+
BRIEF_SLUG="$BRIEF_SLUG" \
|
|
82
|
+
awk '
|
|
83
|
+
BEGIN {
|
|
84
|
+
n = split("USECASE_TITLE USECASE_NUMBER DATE DATETIME AUTHOR BRIEF_DESCRIPTION BRIEF_SLUG", keys, " ")
|
|
85
|
+
}
|
|
86
|
+
{
|
|
87
|
+
line = $0
|
|
88
|
+
for (i = 1; i <= n; i++) {
|
|
89
|
+
ph = "{{" keys[i] "}}"
|
|
90
|
+
val = ENVIRON[keys[i]]
|
|
91
|
+
while ((idx = index(line, ph)) > 0) {
|
|
92
|
+
line = substr(line, 1, idx-1) val substr(line, idx + length(ph))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
print line
|
|
96
|
+
}' "$file"
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Generate the 3 files
|
|
100
|
+
render "$TEMPLATE_DIR/requirements.md" > "$USECASE_DIR/requirements.md"
|
|
101
|
+
render "$TEMPLATE_DIR/tasks.md" > "$USECASE_DIR/tasks.md"
|
|
102
|
+
render "$TEMPLATE_DIR/tdd.md" > "$USECASE_DIR/tdd.md"
|
|
103
|
+
|
|
104
|
+
echo "✅ Use case created!"
|
|
105
|
+
echo "📁 Folder : $USECASE_DIR"
|
|
106
|
+
echo "🔢 Number : usecase-$NEXT_NUM"
|
|
107
|
+
echo "📄 Files : requirements.md · tasks.md · tdd.md"
|
|
108
|
+
echo "📝 Brief : $BRIEF"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# {{USECASE_TITLE}} - Requirements
|
|
2
|
+
|
|
3
|
+
**Use Case ID:** UC-{{USECASE_NUMBER}}
|
|
4
|
+
**Created:** {{DATE}}
|
|
5
|
+
**Author:** {{AUTHOR}}
|
|
6
|
+
**Related documentation:** [`docs/architecture.md`](../architecture.md)
|
|
7
|
+
|
|
8
|
+
## 1. Document Purpose
|
|
9
|
+
|
|
10
|
+
This document defines the implementation requirements for the use case:
|
|
11
|
+
|
|
12
|
+
> {{BRIEF_DESCRIPTION}}
|
|
13
|
+
|
|
14
|
+
The target outcome is:
|
|
15
|
+
|
|
16
|
+
- {{OUTCOME_1}}
|
|
17
|
+
- {{OUTCOME_2}}
|
|
18
|
+
- {{OUTCOME_3}}
|
|
19
|
+
|
|
20
|
+
## 2. Background & Context
|
|
21
|
+
|
|
22
|
+
### Problem Statement
|
|
23
|
+
<!-- Why this use case exists. What problem are we solving? -->
|
|
24
|
+
|
|
25
|
+
### Business Value
|
|
26
|
+
<!-- What value does this bring to users/business? -->
|
|
27
|
+
|
|
28
|
+
### Target Users / Roles
|
|
29
|
+
| Role | Description |
|
|
30
|
+
|------|-------------|
|
|
31
|
+
| | |
|
|
32
|
+
|
|
33
|
+
## 3. Functional Requirements
|
|
34
|
+
|
|
35
|
+
### FR-1: {{FR_1_TITLE}}
|
|
36
|
+
**Description:** ...
|
|
37
|
+
|
|
38
|
+
**Acceptance Criteria:**
|
|
39
|
+
- [ ] ...
|
|
40
|
+
|
|
41
|
+
**Priority:** High | Medium | Low
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
### FR-2: {{FR_2_TITLE}}
|
|
46
|
+
**Description:** ...
|
|
47
|
+
|
|
48
|
+
**Acceptance Criteria:**
|
|
49
|
+
- [ ] ...
|
|
50
|
+
|
|
51
|
+
**Priority:** High | Medium | Low
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 4. Non-Functional Requirements
|
|
56
|
+
|
|
57
|
+
| Category | Requirement |
|
|
58
|
+
|----------|-------------|
|
|
59
|
+
| Performance | |
|
|
60
|
+
| Security | |
|
|
61
|
+
| Scalability | |
|
|
62
|
+
| Maintainability | |
|
|
63
|
+
| Usability | |
|
|
64
|
+
|
|
65
|
+
## 5. Assumptions
|
|
66
|
+
|
|
67
|
+
1. ...
|
|
68
|
+
2. ...
|
|
69
|
+
|
|
70
|
+
## 6. Constraints
|
|
71
|
+
|
|
72
|
+
1. ...
|
|
73
|
+
2. ...
|
|
74
|
+
|
|
75
|
+
## 7. Dependencies
|
|
76
|
+
|
|
77
|
+
- Internal: ...
|
|
78
|
+
- External: ...
|
|
79
|
+
- Third-party services: ...
|
|
80
|
+
|
|
81
|
+
## 8. Out of Scope
|
|
82
|
+
|
|
83
|
+
- ...
|
|
84
|
+
- ...
|
|
85
|
+
|
|
86
|
+
## 9. Open Questions
|
|
87
|
+
|
|
88
|
+
- [ ] ...
|
|
89
|
+
- [ ] ...
|
|
90
|
+
|
|
91
|
+
## 10. Change Log
|
|
92
|
+
|
|
93
|
+
| Date | Author | Change |
|
|
94
|
+
|------|--------|--------|
|
|
95
|
+
| {{DATE}} | {{AUTHOR}} | Initial draft |
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# {{USECASE_TITLE}} - Task Breakdown
|
|
2
|
+
|
|
3
|
+
**Use Case ID:** UC-{{USECASE_NUMBER}}
|
|
4
|
+
**Created:** {{DATE}}
|
|
5
|
+
**Related:** [`requirements.md`](./requirements.md) · [`tdd.md`](./tdd.md)
|
|
6
|
+
|
|
7
|
+
## 1. Sprint Goal
|
|
8
|
+
|
|
9
|
+
> {{BRIEF_DESCRIPTION}}
|
|
10
|
+
|
|
11
|
+
## 2. Task Status Legend
|
|
12
|
+
|
|
13
|
+
- ⬜ Pending
|
|
14
|
+
- 🟡 In Progress
|
|
15
|
+
- ✅ Done
|
|
16
|
+
- ❌ Blocked
|
|
17
|
+
- 🚫 Cancelled
|
|
18
|
+
|
|
19
|
+
## 3. Phase 1 — Discovery & Planning
|
|
20
|
+
|
|
21
|
+
| # | Task | Owner | Status | Notes |
|
|
22
|
+
|---|------|-------|--------|-------|
|
|
23
|
+
| 1 | Validate requirements with stakeholder | | ⬜ | |
|
|
24
|
+
| 2 | Confirm acceptance criteria | | ⬜ | |
|
|
25
|
+
| 3 | Identify edge cases & non-functional needs | | ⬜ | |
|
|
26
|
+
|
|
27
|
+
## 4. Phase 2 — Design
|
|
28
|
+
|
|
29
|
+
| # | Task | Owner | Status | Notes |
|
|
30
|
+
|---|------|-------|--------|-------|
|
|
31
|
+
| 4 | Draft technical design / API contract | | ⬜ | |
|
|
32
|
+
| 5 | Review data model & migrations | | ⬜ | |
|
|
33
|
+
| 6 | Align with architecture baseline | | ⬜ | |
|
|
34
|
+
|
|
35
|
+
## 5. Phase 3 — Implementation
|
|
36
|
+
|
|
37
|
+
| # | Task | Owner | Status | Notes |
|
|
38
|
+
|---|------|-------|--------|-------|
|
|
39
|
+
| 7 | Backend: data model / migrations | | ⬜ | |
|
|
40
|
+
| 8 | Backend: service / repository layer | | ⬜ | |
|
|
41
|
+
| 9 | Backend: API / controller layer | | ⬜ | |
|
|
42
|
+
| 10 | Frontend: UI components | | ⬜ | |
|
|
43
|
+
| 11 | Frontend: state / API integration | | ⬜ | |
|
|
44
|
+
| 12 | Configuration & feature flags | | ⬜ | |
|
|
45
|
+
|
|
46
|
+
## 6. Phase 4 — Quality & Testing
|
|
47
|
+
|
|
48
|
+
| # | Task | Owner | Status | Notes |
|
|
49
|
+
|---|------|-------|--------|-------|
|
|
50
|
+
| 13 | Unit tests (per [`tdd.md`](./tdd.md)) | | ⬜ | |
|
|
51
|
+
| 14 | Integration / API tests | | ⬜ | |
|
|
52
|
+
| 15 | E2E / UI tests | | ⬜ | |
|
|
53
|
+
| 16 | Code review (peer) | | ⬜ | |
|
|
54
|
+
| 17 | HOD review (if applicable) | | ⬜ | |
|
|
55
|
+
|
|
56
|
+
## 7. Phase 5 — Release
|
|
57
|
+
|
|
58
|
+
| # | Task | Owner | Status | Notes |
|
|
59
|
+
|---|------|-------|--------|-------|
|
|
60
|
+
| 18 | Update documentation / changelog | | ⬜ | |
|
|
61
|
+
| 19 | Deploy to staging | | ⬜ | |
|
|
62
|
+
| 20 | QA / UAT on staging | | ⬜ | |
|
|
63
|
+
| 21 | Deploy to production | | ⬜ | |
|
|
64
|
+
| 22 | Post-release monitoring | | ⬜ | |
|
|
65
|
+
|
|
66
|
+
## 8. Risks & Blockers
|
|
67
|
+
|
|
68
|
+
| Risk | Impact | Mitigation | Owner |
|
|
69
|
+
|------|--------|------------|-------|
|
|
70
|
+
| | | | |
|
|
71
|
+
|
|
72
|
+
## 9. Definition of Done
|
|
73
|
+
|
|
74
|
+
- [ ] All Phase 1–5 tasks marked ✅
|
|
75
|
+
- [ ] All acceptance criteria from `requirements.md` met
|
|
76
|
+
- [ ] Tests in `tdd.md` passing in CI
|
|
77
|
+
- [ ] Code reviewed and merged
|
|
78
|
+
- [ ] Deployed and verified in production
|
|
79
|
+
- [ ] Documentation updated
|
|
80
|
+
|
|
81
|
+
## 10. Change Log
|
|
82
|
+
|
|
83
|
+
| Date | Author | Change |
|
|
84
|
+
|------|--------|--------|
|
|
85
|
+
| {{DATE}} | {{AUTHOR}} | Initial task breakdown |
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# {{USECASE_TITLE}} - Test-Driven Development
|
|
2
|
+
|
|
3
|
+
**Use Case ID:** UC-{{USECASE_NUMBER}}
|
|
4
|
+
**Created:** {{DATE}}
|
|
5
|
+
**Related:** [`requirements.md`](./requirements.md) · [`tasks.md`](./tasks.md)
|
|
6
|
+
|
|
7
|
+
## 1. TDD Strategy
|
|
8
|
+
|
|
9
|
+
> {{BRIEF_DESCRIPTION}}
|
|
10
|
+
|
|
11
|
+
We follow the **Red → Green → Refactor** cycle:
|
|
12
|
+
|
|
13
|
+
1. 🔴 **Red** — write a failing test that captures the desired behavior
|
|
14
|
+
2. 🟢 **Green** — write the minimum code to make the test pass
|
|
15
|
+
3. 🔵 **Refactor** — clean up while keeping tests green
|
|
16
|
+
|
|
17
|
+
## 2. Test Pyramid
|
|
18
|
+
|
|
19
|
+
| Layer | Coverage Target | Tooling |
|
|
20
|
+
|-------|-----------------|---------|
|
|
21
|
+
| Unit | ≥ 80% | PHPUnit / Jest |
|
|
22
|
+
| Integration | All API endpoints | PHPUnit Feature / Supertest |
|
|
23
|
+
| E2E | Critical user journeys | Cypress / Playwright |
|
|
24
|
+
|
|
25
|
+
## 3. Test Matrix
|
|
26
|
+
|
|
27
|
+
| ID | Requirement | Test Type | Description | Status |
|
|
28
|
+
|----|-------------|-----------|-------------|--------|
|
|
29
|
+
| TC-001 | FR-1 | Unit | | ⬜ |
|
|
30
|
+
| TC-002 | FR-1 | Integration | | ⬜ |
|
|
31
|
+
| TC-003 | FR-2 | Unit | | ⬜ |
|
|
32
|
+
| TC-004 | FR-2 | E2E | | ⬜ |
|
|
33
|
+
|
|
34
|
+
## 4. Test Cases
|
|
35
|
+
|
|
36
|
+
### TC-001: {{TC_001_TITLE}}
|
|
37
|
+
**Type:** Unit | Integration | E2E
|
|
38
|
+
**Maps to:** FR-1
|
|
39
|
+
|
|
40
|
+
**Given:** ...
|
|
41
|
+
**When:** ...
|
|
42
|
+
**Then:** ...
|
|
43
|
+
|
|
44
|
+
**Steps:**
|
|
45
|
+
1. ...
|
|
46
|
+
2. ...
|
|
47
|
+
|
|
48
|
+
**Expected result:** ...
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### TC-002: {{TC_002_TITLE}}
|
|
53
|
+
**Type:** Unit | Integration | E2E
|
|
54
|
+
**Maps to:** FR-2
|
|
55
|
+
|
|
56
|
+
**Given:** ...
|
|
57
|
+
**When:** ...
|
|
58
|
+
**Then:** ...
|
|
59
|
+
|
|
60
|
+
**Steps:**
|
|
61
|
+
1. ...
|
|
62
|
+
2. ...
|
|
63
|
+
|
|
64
|
+
**Expected result:** ...
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 5. Edge Cases & Negative Tests
|
|
69
|
+
|
|
70
|
+
| ID | Scenario | Expected Behavior |
|
|
71
|
+
|----|----------|-------------------|
|
|
72
|
+
| EC-001 | Empty input | |
|
|
73
|
+
| EC-002 | Invalid input | |
|
|
74
|
+
| EC-003 | Unauthorized access | 401 |
|
|
75
|
+
| EC-004 | Forbidden access | 403 |
|
|
76
|
+
| EC-005 | Resource not found | 404 |
|
|
77
|
+
| EC-006 | Rate limit exceeded | 429 |
|
|
78
|
+
| EC-007 | Downstream service down | Graceful degradation |
|
|
79
|
+
|
|
80
|
+
## 6. Performance & Load Tests
|
|
81
|
+
|
|
82
|
+
| Scenario | Target | Tool |
|
|
83
|
+
|----------|--------|------|
|
|
84
|
+
| Normal load (X RPS) | p95 < 200ms | k6 / JMeter |
|
|
85
|
+
| Peak load (Y RPS) | p95 < 500ms | k6 / JMeter |
|
|
86
|
+
| Soak (1h sustained) | No memory leak | k6 |
|
|
87
|
+
|
|
88
|
+
## 7. Security Tests
|
|
89
|
+
|
|
90
|
+
- [ ] SQL injection
|
|
91
|
+
- [ ] XSS (reflected & stored)
|
|
92
|
+
- [ ] CSRF
|
|
93
|
+
- [ ] Authentication bypass
|
|
94
|
+
- [ ] Authorization escalation
|
|
95
|
+
- [ ] Sensitive data exposure
|
|
96
|
+
- [ ] Rate limiting
|
|
97
|
+
- [ ] Input validation
|
|
98
|
+
|
|
99
|
+
## 8. CI/CD Integration
|
|
100
|
+
|
|
101
|
+
| Stage | Test Command | Gate |
|
|
102
|
+
|-------|--------------|------|
|
|
103
|
+
| Pre-commit | `phpunit --filter=Unit` | Local |
|
|
104
|
+
| PR | `phpunit` | Required |
|
|
105
|
+
| Main | `phpunit` + e2e | Required |
|
|
106
|
+
| Release | full suite + perf | Required |
|
|
107
|
+
|
|
108
|
+
## 9. Test Data Strategy
|
|
109
|
+
|
|
110
|
+
- **Fixtures:** ...
|
|
111
|
+
- **Factories:** ...
|
|
112
|
+
- **Mocks:** ...
|
|
113
|
+
- **Test DB reset:** per-suite / per-test
|
|
114
|
+
|
|
115
|
+
## 10. Coverage Requirements
|
|
116
|
+
|
|
117
|
+
| Layer | Minimum | Target |
|
|
118
|
+
|-------|---------|--------|
|
|
119
|
+
| Statements | 80% | 90% |
|
|
120
|
+
| Branches | 75% | 85% |
|
|
121
|
+
| Functions | 80% | 90% |
|
|
122
|
+
| Lines | 80% | 90% |
|
|
123
|
+
|
|
124
|
+
## 11. Change Log
|
|
125
|
+
|
|
126
|
+
| Date | Author | Change |
|
|
127
|
+
|------|--------|--------|
|
|
128
|
+
| {{DATE}} | {{AUTHOR}} | Initial TDD plan |
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
name: 'meo-agent-logger',
|
|
5
|
+
version: '1.0.0',
|
|
6
|
+
description: 'Logs every download event to stderr',
|
|
7
|
+
hooks: [
|
|
8
|
+
{
|
|
9
|
+
name: 'before_download',
|
|
10
|
+
handler: async (ctx) => {
|
|
11
|
+
process.stderr.write(`[logger] starting: ${ctx.url} → ${ctx.output}\n`);
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
name: 'after_download',
|
|
16
|
+
handler: async (ctx) => {
|
|
17
|
+
process.stderr.write(`[logger] finished: ${ctx.output} (${ctx.bytes} bytes)\n`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
};
|
package/index.js
ADDED