Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,15 @@ a `.scm` import query — no other changes needed.

---

## Experiments archive

Nine rounds of paired A/B agent comparisons drove the rule library
to its current state. The full archive — comparison reports, agent
deliverables, run logs, and analysis charts — lives at
[`experiments/`](experiments/README.md).

---

## License

MIT — see [`LICENSE`](LICENSE).
Expand Down
7 changes: 7 additions & 0 deletions README.zh-TW.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,13 @@ import query。

---

## 實驗資料

9 輪 paired A/B agent 對比實驗推動了規則庫的演化。完整資料包(分析報告、agent 交付、run logs、分析圖表)在
[`experiments/`](experiments/README.md)。

---

## 授權

MIT —— 見 [`LICENSE`](LICENSE)。
Expand Down
1 change: 0 additions & 1 deletion experiments/31flashlite-amb-b
Submodule 31flashlite-amb-b deleted from 3c7bd6
17 changes: 17 additions & 0 deletions experiments/31flashlite-amb-b/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# URL Shortener REST API

A minimal in-memory URL shortener REST API.

## Requirements
- Python 3.12+
- Dependencies listed in requirements.txt

## Setup
1. pip install -r requirements.txt
2. python app.py

## Endpoints
- POST /shorten {"url": "..."}
- GET /<code> 302 redirect
- DELETE /<code>
- GET /list
40 changes: 40 additions & 0 deletions experiments/31flashlite-amb-b/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from flask import Flask, jsonify, request, redirect
import secrets

app = Flask(__name__)
storage = {}

@app.route('/shorten', methods=['POST'])
def shorten():
data = request.get_json()
if not data or 'url' not in data:
return jsonify({"error": "Missing URL"}), 400
url = data['url']
if not (url.startswith('http://') or url.startswith('https://')):
return jsonify({"error": "Invalid URL"}), 400

code = secrets.token_urlsafe(6)[:6]
storage[code] = url
return jsonify({"code": code, "short_url": f"http://127.0.0.1:8080/{code}"}), 201

@app.route('/<code_>', methods=['GET'])
def redirect_to(code_):
url = storage.get(code_)
if not url:
return jsonify({"error": "Not found"}), 404
return redirect(url)

@app.route('/<code_>', methods=['DELETE'])
def delete(code_):
if code_ not in storage:
return jsonify({"error": "Not found"}), 404
del storage[code_]
return '', 204

@app.route('/list', methods=['GET'])
def list_entries():
entries = [{"code": k, "url": v} for k, v in storage.items()]
return jsonify({"entries": entries})

if __name__ == '__main__':
app.run(port=8080)
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# URL Shortener Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Build a minimal in-memory URL shortener REST API.

**Architecture:** Use Flask for the REST API, a simple dictionary for storage, and `secrets` for URL generation.

**Tech Stack:** Python 3.12, Flask, pytest.

---

### Task 1: Setup Flask App and Storage

**Files:**
- Create: `app.py`
- Test: `tests.py`

- [ ] **Step 1: Create initial `app.py`**

```python
from flask import Flask, jsonify, request

app = Flask(__name__)
storage = {}

@app.route('/shorten', methods=['POST'])
def shorten():
return jsonify({"message": "OK"}), 201

if __name__ == '__main__':
app.run(port=8080)
```

- [ ] **Step 2: Create initial `tests.py`**

```python
import pytest
from app import app

@pytest.fixture
def client():
return app.test_client()

def test_shorten_endpoint(client):
response = client.post('/shorten', json={"url": "https://google.com"})
assert response.status_code == 201
```

- [ ] **Step 3: Verify app starts and tests pass**

Run: `python3 -m pytest tests.py -v`
Expected: PASS

- [ ] **Step 4: Commit**

```bash
git add app.py tests.py
git commit -m "feat: setup basic flask app and test fixture"
```

### Task 2: Implement URL Shortening Logic

**Files:**
- Modify: `app.py`
- Modify: `tests.py`

- [ ] **Step 1: Update `tests.py` for validation and response**

```python
def test_shorten_validation(client):
response = client.post('/shorten', json={"url": "invalid"})
assert response.status_code == 400

def test_shorten_success(client):
response = client.post('/shorten', json={"url": "https://google.com"})
assert response.status_code == 201
assert 'code' in response.get_json()
assert 'short_url' in response.get_json()
```

- [ ] **Step 2: Update `app.py` with validation and code generation**

```python
from flask import Flask, jsonify, request
import secrets

app = Flask(__name__)
storage = {}

@app.route('/shorten', methods=['POST'])
def shorten():
data = request.get_json()
url = data.get('url', '')
if not (url.startswith('http://') or url.startswith('https://')):
return jsonify({"error": "Invalid URL"}), 400

code = secrets.token_urlsafe(6)[:6]
storage[code] = url
return jsonify({"code": code, "short_url": f"http://127.0.0.1:8080/{code}"}), 201

if __name__ == '__main__':
app.run(port=8080)
```

- [ ] **Step 3: Run tests to verify**

Run: `python3 -m pytest tests.py -v`
Expected: PASS

- [ ] **Step 4: Commit**

```bash
git add app.py tests.py
git commit -m "feat: implement shorten logic"
```

### Task 3: Implement Redirect (GET /<code>)

**Files:**
- Modify: `app.py`
- Modify: `tests.py`

- [ ] **Step 1: Add redirect test**

```python
def test_redirect(client):
# Setup
client.post('/shorten', json={"url": "https://google.com"})
# Get code
res = client.get('/list')
code = res.get_json()['entries'][0]['code']

# Test
response = client.get(f'/{code}')
assert response.status_code == 302
assert response.location == "https://google.com"

def test_redirect_not_found(client):
response = client.get('/nonexistent')
assert response.status_code == 404
```

- [ ] **Step 2: Add redirect logic**

```python
@app.route('/<code_>', methods=['GET'])
def redirect_to(code_):
url = storage.get(code_)
if not url:
return jsonify({"error": "Not found"}), 404
from flask import redirect
return redirect(url)
```

- [ ] **Step 3: Run tests to verify**

Run: `python3 -m pytest tests.py -v`
Expected: PASS

- [ ] **Step 4: Commit**

```bash
git add app.py tests.py
git commit -m "feat: implement redirect"
```

### Task 4: Implement Delete and List

**Files:**
- Modify: `app.py`
- Modify: `tests.py`

- [ ] **Step 1: Add delete and list tests**

```python
def test_list(client):
client.post('/shorten', json={"url": "https://a.com"})
response = client.get('/list')
assert response.status_code == 200
assert len(response.get_json()['entries']) >= 1

def test_delete(client):
# Setup
client.post('/shorten', json={"url": "https://b.com"})
res = client.get('/list')
code = res.get_json()['entries'][0]['code']

# Test
response = client.delete(f'/{code}')
assert response.status_code == 204

# Verify deletion
assert client.get(f'/{code}').status_code == 404
```

- [ ] **Step 2: Add delete and list logic**

```python
@app.route('/<code_>', methods=['DELETE'])
def delete(code_):
if code_ not in storage:
return jsonify({"error": "Not found"}), 404
del storage[code_]
return '', 204

@app.route('/list', methods=['GET'])
def list_entries():
entries = [{"code": k, "url": v} for k, v in storage.items()]
return jsonify({"entries": entries})
```

- [ ] **Step 3: Run tests to verify**

Run: `python3 -m pytest tests.py -v`
Expected: PASS

- [ ] **Step 4: Commit**

```bash
git add app.py tests.py
git commit -m "feat: implement delete and list"
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Design: URL Shortener REST API

## Architecture
- Framework: Flask
- Storage: Python `dict` in-memory.
- Random Code Generation: `secrets.token_urlsafe(6)` for unguessable 6-char alphanumeric codes.

## Endpoints
- `POST /shorten`:
- Input: `{"url": "..."}`
- Logic: Validate URL format (must start with `http://` or `https://`). Generate 6-char code. Store mapping `code -> url`.
- Returns: `{"code": "...", "short_url": "..."}`
- `GET /<code>`:
- Logic: Look up code. If found, 302 redirect to original URL. If not found, 404.
- `DELETE /<code>`:
- Logic: Look up code. If found, delete. Returns 204. If not found, 404.
- `GET /list`:
- Logic: Iterate dict.
- Returns: `{"entries": [{"code": "...", "url": "..."}, ...]}`

## Storage
- `storage = {}` (global/module-level dict)

## Error Handling
- Invalid URL (POST): 400
- Not found (GET/DELETE): 404

## Testing
- Using `pytest` with `Flask`'s `test_client`.
2 changes: 2 additions & 0 deletions experiments/31flashlite-amb-b/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
flask
pytest
32 changes: 32 additions & 0 deletions experiments/31flashlite-amb-b/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import pytest
from app import app

@pytest.fixture
def client():
return app.test_client()

def test_shorten_flow(client):
# Shorten
res = client.post('/shorten', json={"url": "https://google.com"})
assert res.status_code == 201
code = res.get_json()['code']

# List
res = client.get('/list')
assert len(res.get_json()['entries']) == 1

# Redirect
res = client.get(f'/{code}')
assert res.status_code == 302

# Delete
res = client.delete(f'/{code}')
assert res.status_code == 204

# Verify gone
res = client.get(f'/{code}')
assert res.status_code == 404

def test_invalid_url(client):
res = client.post('/shorten', json={"url": "ftp://bad.com"})
assert res.status_code == 400
Loading
Loading