@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.
@@ -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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('./bin/meo-agent.js');