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
18 changes: 16 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ work:
```

7. Only after approval may AI create a branch and PR.
8. The MVP implementation creates a PR scaffold only. It must not change service
code automatically.
8. The GitHub Actions implementation creates a PR scaffold only.
9. A human may then comment `/ai implement` to allow the local mini PC runner to
continue that PR branch with Codex CLI.

## Hard Rules

Expand All @@ -54,10 +55,23 @@ work:
## Approval Rules

- The approval signal is a GitHub Issue comment containing `/ai approve`.
- The implementation signal is a GitHub Issue comment containing `/ai implement`.
- The workflow should only act on Issues labeled for DevLoop proposals.
- Optional repository variable `DEVLOOP_APPROVERS` can restrict who may approve.
Use a comma-separated list of GitHub usernames.

## Continuing An Approved DevLoop PR

When asked to continue an approved DevLoop PR:

1. Confirm the current branch starts with `ai/issue-`.
2. Read `docs/ai-ideas/issue-N.md`.
3. Read `AI_POLICY.md`.
4. Implement only the approved scope.
5. Run relevant tests.
6. Commit to the same branch.
7. Push the branch so the existing PR updates.

## Suggested Agent Behavior

- Prefer documentation, tests, and narrow workflow improvements before product
Expand Down
15 changes: 15 additions & 0 deletions AI_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Allowed:
- Create an `ai/` branch after approval.
- Open a PR scaffold for the approved idea.
- Add documentation-only planning artifacts for the approved idea.
- Detect `/ai implement` through a local mini PC runner.
- Run Codex CLI locally on an approved `ai/issue-*` PR branch.
- Use manual proposal mode when a human wants to provide a Codex-drafted idea
instead of spending API credits.

Expand All @@ -33,6 +35,8 @@ Not allowed:
- Secret creation, rotation, or disclosure.
- OpenAI API calls from the approved implementation workflow.
- More than one AI-generated idea per proposal workflow run.
- Local runner commits without a human `/ai implement` command.
- Local runner pushes to `main`.

## Required GitHub Configuration

Expand Down Expand Up @@ -78,6 +82,17 @@ The implementation workflow only responds to Issue comments whose trimmed body i

The Issue must also carry the `devloop` and `ai-proposed` labels.

## Implementation Signal

The local mini PC runner only continues implementation after a human comments:

```text
/ai implement
```

The runner should work on the existing `ai/issue-*` PR branch and must not merge
the PR.

## Human Review Expectations

Before approving an Issue, verify that:
Expand Down
49 changes: 49 additions & 0 deletions docs/ai-ideas/issue-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# DevLoop 아이디어 #2: [DevLoop] 검색 결과 카드에 하이라이트된 키워드 표시 개선

이 파일은 GitHub Issue에서 사람이 승인한 뒤 생성된 PR scaffold입니다.
의도적으로 실제 서비스 코드는 수정하지 않습니다.

## 승인된 이슈

- 이슈: #2
- 제목: [DevLoop] 검색 결과 카드에 하이라이트된 키워드 표시 개선

## 원본 제안

## 문제

현재 검색 결과 카드에서 사용자가 왜 해당 글이 검색되었는지 이해하기 어려운 경우가 있어, 검색 결과의 신뢰도와 탐색 효율성이 떨어질 수 있습니다.

## 지금 필요한 이유

TechCase의 핵심 가치는 실제 기업 적용 사례를 빠르게 이해하는 데 있으므로, 검색 결과 카드에 검색어와 매칭된 키워드를 명확히 하이라이트하여 사용자가 결과의 맥락을 빠르게 파악할 수 있도록 하는 것이 중요합니다. 이는 검색 품질 평가에도 긍정적인 영향을 줄 수 있습니다.

## 제안 범위

프론트엔드 `apps/web` 내 검색 결과 카드 컴포넌트에 하이라이트된 키워드 표시 UI 개선 및 관련 문서(`docs/search-design.md`)에 하이라이트 정책과 구현 방식을 명확히 문서화하는 작업으로 제한합니다. 서비스 코드나 백엔드 검색 로직 변경은 포함하지 않습니다.

## 하지 않을 일

검색 알고리즘 변경, 백엔드 Elasticsearch 쿼리 수정, 키워드 추출 로직 개선, 인증 및 권한 관련 변경, 인프라나 배포 설정 변경은 포함하지 않습니다.

## 위험 요소 / 가드레일

UI 변경에 따른 사용자 혼란 가능성은 낮으며, 문서화 중심 작업이므로 서비스 안정성에 미치는 영향은 거의 없습니다. 다만 하이라이트 표현이 과도하거나 부족하지 않도록 적절한 밸런스 조정이 필요합니다.

## 검증 방법

개선된 검색 결과 카드에서 검색어와 매칭된 키워드가 명확히 하이라이트되어 표시되는지 확인합니다. 여러 검색어 조합에 대해 하이라이트가 일관되게 동작하는지 UI 테스트를 수행하고, 기존 사용자 피드백과 비교해 탐색 편의성이 향상되었는지 검토합니다.

## 승인 방법

이 아이디어를 진행하려면 이슈 댓글에 정확히 `/ai approve`를 남겨주세요.
승인 후 DevLoop가 `ai/` 브랜치와 PR scaffold를 생성합니다.

DevLoop fingerprint: `4cb0bf0e9a61`

## 구현 메모

- 승인된 범위 안에서만 변경합니다.
- 인증, 결제, 데이터베이스 마이그레이션, 인프라는 건드리지 않습니다.
- 자동 머지는 활성화하지 않습니다.
- 사람 리뷰를 요청하기 전에 검증 메모를 남깁니다.
90 changes: 90 additions & 0 deletions docs/devloop-runner.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# DevLoop Local Runner

This document describes the mini PC runner that continues an approved DevLoop PR
with Codex CLI.

## Flow

1. DevLoop creates an idea Issue.
2. A human comments `/ai approve`.
3. GitHub Actions creates an `ai/issue-*` scaffold PR.
4. A human comments `/ai implement` on the Issue.
5. The mini PC runner detects the command.
6. The runner checks out the matching PR branch locally.
7. The runner invokes `codex exec` with `--sandbox workspace-write`.
8. The runner can commit and push changes back to the same PR branch.

## Required Local Setup

Install and authenticate these on the mini PC:

- `git`
- `python3`
- `codex`
- GitHub credentials that can read Issues, comment on Issues, fetch branches,
and push to the repository

Required environment variables:

- `GITHUB_REPOSITORY`: for example `SmileJune/techcase`
- `GITHUB_TOKEN` or `GH_TOKEN`: token for the local runner

Optional environment variables:

- `DEVLOOP_WORKSPACE`: path to the local clone. Defaults to the current
directory.
- `DEVLOOP_RUNNER_INTERVAL`: polling interval in seconds. Defaults to `300`.
- `DEVLOOP_APPROVERS`: comma-separated GitHub usernames allowed to use
`/ai implement`.
- `DEVLOOP_CODEX_MODEL`: optional Codex model override.
- `CODEX_BIN`: optional full path to the Codex CLI executable. Useful for
systemd, cron, or SSH non-interactive shells where `~/.local/bin` is not on
`PATH`.

Use a dedicated clone for the runner. The runner refuses to start work when the
workspace is dirty.

## Dry Run

Check what the runner would do without running Codex:

```bash
python3 scripts/devloop/runner.py \
--repo SmileJune/techcase \
--once
```

## Execute Without Commit

Run Codex locally, but leave changes uncommitted for manual inspection:

```bash
python3 scripts/devloop/runner.py \
--repo SmileJune/techcase \
--once \
--execute-codex
```

## Execute And Push

Run Codex, commit the resulting allowed changes, and push to the existing PR
branch:

```bash
python3 scripts/devloop/runner.py \
--repo SmileJune/techcase \
--once \
--execute-codex \
--commit \
--push
```

## Safety Notes

- The runner only responds to `/ai implement`, not `/ai approve`.
- The runner uses `codex exec --sandbox workspace-write`.
- The runner refuses to commit changes under `.github/workflows/`, `infra/`,
and `apps/backend/alembic/`.
- The runner does not merge PRs.
- The runner does not push to `main`; it pushes only to the existing
`ai/issue-*` PR branch.
2 changes: 1 addition & 1 deletion scripts/ai/implement_idea.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ def create_pr(repo: str, token: str, base: str, branch: str, issue: dict[str, An
f"""
## 승인된 아이디어

Closes #{number}
Refs #{number}

## 변경 사항

Expand Down
4 changes: 4 additions & 0 deletions scripts/ai/prompts/unified_employee.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ Idea selection rules:
- Prefer ideas that can be validated without production access.
- Prefer ideas that are easy to review and easy to revert.
- Avoid vague platform rewrites, broad redesigns, or multi-week projects.
- Do not repeat or closely paraphrase recent DevLoop proposals. If recent
proposals focus on result cards, keyword highlights, snippet display, or
contributor guidelines, choose a materially different area such as evaluation,
source metadata, onboarding, failure handling, or maintainer workflow.
64 changes: 56 additions & 8 deletions scripts/ai/propose_idea.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def github_request(
path: str,
token: str,
payload: dict[str, Any] | None = None,
) -> dict[str, Any]:
) -> dict[str, Any] | list[Any]:
data = None if payload is None else json.dumps(payload).encode("utf-8")
request = urllib.request.Request(
f"{GITHUB_API}{path}",
Expand Down Expand Up @@ -186,7 +186,46 @@ def manual_idea_from_env() -> dict[str, str]:
return {field: require_env(env_name) for field, env_name in env_names.items()}


def api_idea() -> dict[str, str]:
def list_recent_devloop_issues(repo: str | None, token: str | None) -> list[dict[str, Any]]:
if not repo or not token:
return []

limit = int(env("DEVLOOP_RECENT_ISSUE_LIMIT", "10"))
query = urllib.parse.urlencode(
{
"state": "all",
"labels": "devloop,ai-proposed",
"per_page": str(limit),
"sort": "created",
"direction": "desc",
}
)
result = github_request("GET", f"/repos/{repo}/issues?{query}", token)
if not isinstance(result, list):
raise RuntimeError("Unexpected GitHub issues response.")
return [issue for issue in result if "pull_request" not in issue]


def recent_issue_context(issues: list[dict[str, Any]]) -> str:
if not issues:
return "No previous DevLoop proposals were available."

lines = []
for issue in issues:
body = str(issue.get("body") or "")
excerpt = " ".join(body.split())[:500]
lines.append(
"\n".join(
[
f"- #{issue.get('number')} ({issue.get('state')}): {issue.get('title')}",
f" excerpt: {excerpt}",
]
)
)
return "\n".join(lines)


def api_idea(recent_context: str) -> dict[str, str]:
prompt = PROMPT_PATH.read_text(encoding="utf-8")
context = read_repo_context()
model = env("OPENAI_MODEL", "gpt-4.1-mini")
Expand All @@ -213,6 +252,10 @@ def api_idea() -> dict[str, str]:

{context}

Recent DevLoop proposals that must not be repeated or closely paraphrased:

{recent_context}

Return a JSON object with exactly these string fields:
title, problem, rationale, scope, non_goals, risks, validation.
All field values must be written in Korean. Keep the title concise.
Expand Down Expand Up @@ -243,12 +286,12 @@ def validate_idea(idea: dict[str, Any]) -> dict[str, str]:
return normalized


def load_idea() -> dict[str, str]:
def load_idea(recent_context: str) -> dict[str, str]:
mode = env("DEVLOOP_PROPOSAL_MODE", "api").lower()
if mode == "manual":
return validate_idea(manual_idea_from_env())
if mode == "api":
return api_idea()
return api_idea(recent_context)
raise RuntimeError("DEVLOOP_PROPOSAL_MODE must be 'manual' or 'api'.")


Expand Down Expand Up @@ -285,7 +328,7 @@ def build_issue_body(idea: dict[str, str]) -> str:
def search_existing_issue(repo: str, token: str, fingerprint: str) -> str | None:
query = urllib.parse.urlencode(
{
"q": f"repo:{repo} is:issue is:open {fingerprint}",
"q": f"repo:{repo} is:issue {fingerprint}",
"per_page": "1",
}
)
Expand Down Expand Up @@ -333,7 +376,10 @@ def ensure_label(repo: str, token: str, name: str, color: str, description: str)

def main() -> int:
dry_run = env("DEVLOOP_DRY_RUN", "false").lower() == "true"
idea = load_idea()
repo = env("GITHUB_REPOSITORY")
token = env("GITHUB_TOKEN") or env("GH_TOKEN")
recent_context = recent_issue_context(list_recent_devloop_issues(repo, token))
idea = load_idea(recent_context)
title = f"[DevLoop] {idea['title']}"
body = build_issue_body(idea)
fingerprint = body.rsplit("`", 2)[1]
Expand All @@ -343,11 +389,13 @@ def main() -> int:
return 0

repo = require_env("GITHUB_REPOSITORY")
token = require_env("GITHUB_TOKEN")
token = env("GITHUB_TOKEN") or env("GH_TOKEN")
if not token:
raise RuntimeError("GITHUB_TOKEN or GH_TOKEN is required.")

existing = search_existing_issue(repo, token, fingerprint)
if existing:
print(f"Matching open DevLoop issue already exists: {existing}")
print(f"Matching DevLoop issue already exists: {existing}")
return 0

ensure_label(repo, token, "devloop", "5319e7", "Managed by the DevLoop MVP workflow.")
Expand Down
Loading