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
6 changes: 3 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"source": "./plugins/lnpl",
"homepage": "https://github.com/choiyounggi/linkly",
"version": "0.8.0",
"version": "0.9.0",
"tags": [
"lnpl",
"linkly",
Expand All @@ -37,7 +37,7 @@
},
"source": "./plugins/lnpl-dev",
"homepage": "https://github.com/choiyounggi/linkly",
"version": "0.8.0",
"version": "0.9.0",
"tags": [
"linkly",
"contributor",
Expand All @@ -56,7 +56,7 @@
},
"source": "./plugins/lnpl-mcp",
"homepage": "https://github.com/choiyounggi/linkly",
"version": "0.8.0",
"version": "0.9.0",
"tags": [
"lnpl",
"linkly",
Expand Down
28 changes: 26 additions & 2 deletions .github/workflows/_ai-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ name: AI gate (reusable)
# t4·t5의 게이트별 워크플로가 `uses: ./.github/workflows/_ai-gate.yml`로 이것을
# 부른다(프롬프트 파일 경로만 넘긴다). 이 워크플로는 게이트 자체(프롬프트·판정
# 임계)를 만들지 않는다 — 판정 배관만 만든다: 포크 차단, SHA 핀, 시크릿 부재/
# 변경 파일 0건의 명시적 skip, exit code로만 판정(scripts/ai_gate_verdict.py).
# 변경 파일 0건의 명시적 처리(blocking이면 실패, advisory면 경고 — #244), exit
# code로만 판정(scripts/ai_gate_verdict.py).
# 산문은 exit code를 정하지 못한다.

on:
Expand Down Expand Up @@ -95,7 +96,12 @@ concurrency:

jobs:
gate:
name: ${{ inputs.gate_name }}
# #244: 이 잡에는 name:을 두지 않는다. 잡 수준 if:로 skip된 재사용 잡은
# name: 식을 평가하지 않은 채 보고된다(2026-10-22 실측: facebook/rocksdb,
# qualcomm-linux/meta-qcom, shakfu/inferna). 그러면 skip될 때만 체크 이름이
# 달라져, skip-ai-gate 라벨·포크 PR에서 required check가 영원히 대기한다.
# 이름이 없으면 돌든 skip되든 "<호출 잡> / gate"로 보고된다 — required
# check 이름은 test-quality / gate (docs/CI-GATES.md).
runs-on: ubuntu-latest
# D6: 이 레포는 PUBLIC이다. 포크 PR은 시크릿 없이 돈다 — 시크릿을 포크 PR
# 컨텍스트로 끌어오는 위험한 트리거는 이 워크플로에서 절대 쓰지 않는다
Expand Down Expand Up @@ -209,13 +215,31 @@ jobs:
printf '%s\n' "${FILES[@]}"
fi
echo "count=${#FILES[@]}" >> "$GITHUB_OUTPUT"
mapfile -t DELETED_FILES < <(git diff --diff-filter=D --name-only "$MERGE_BASE" "$HEAD_SHA")
echo "deleted=${#DELETED_FILES[@]}" >> "$GITHUB_OUTPUT"
DELIM="AI_GATE_FILES_$(openssl rand -hex 20)"
{
echo "list<<$DELIM"
printf '%s\n' "${FILES[@]}"
echo "$DELIM"
} >> "$GITHUB_OUTPUT"

# #244: 위 목록은 --diff-filter=d라 삭제된 파일이 빠진다. 변경 파일 0건(삭제만 있는
# PR, 빈 diff)이면 아래 판정 스텝이 전부 건너뛰어 잡이 판정 없이 초록이 된다 —
# 시크릿 부재와 같은 의미론으로 다룬다: blocking이면 실패, advisory면 경고.
- name: 변경 파일 없음 처리
if: steps.check_secret.outputs.skip != 'true' && steps.changed_files.outputs.count == '0'
env:
BLOCKING: ${{ inputs.blocking }}
GATE_NAME: ${{ inputs.gate_name }}
DELETED: ${{ steps.changed_files.outputs.deleted }}
run: |
if [ "$BLOCKING" = "true" ]; then
echo "::error::[$GATE_NAME] 변경 파일 0건(삭제 ${DELETED}건) — 게이트가 판정할 수 없다. blocking 게이트이므로 잡을 실패시킨다. 삭제만 있는 PR은 skip-ai-gate 라벨(사유를 PR 본문에)로 우회한다."
exit 1
fi
echo "::warning::[$GATE_NAME] 변경 파일 0건(삭제 ${DELETED}건) — 이 게이트는 판정하지 않았다. 이 잡의 초록불은 '검토하지 않음'을 뜻한다 — advisory이므로 머지는 막지 않는다."

- name: 프롬프트 조립
id: build_prompt
if: steps.check_secret.outputs.skip != 'true' && steps.changed_files.outputs.count != '0'
Expand Down
10 changes: 7 additions & 3 deletions .github/workflows/ai-gates-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ jobs:
with:
gate_name: test-quality
prompt_file: .github/ai-gates/test-quality.md
# D6: advisory로 시작한다. 승격은 docs/CI-GATES.md의 절차(약 50 PR,
# 수용률 80% 이상)를 거쳐 이 한 줄을 true로 뒤집는 일이다.
blocking: false
# D6: 2026-10-22 승격(issue #244). 승격 기준(약 50 PR, 수용률 80%)을
# 채우기 전의 예외다 — 근거와 실측은 docs/CI-GATES.md "advisory → blocking
# 승격 절차". 이 잡 키와 재사용 잡 id(gate)가 required check
# `test-quality / gate`를 이룬다 — 이 잡 이름을 바꾸거나, 이 잡에 if:를
# 달거나, pull_request: 아래에 types/branches/paths를 넣으면 보호 브랜치가
# 영영 보고되지 않는 체크를 기다린다.
blocking: true
# D5: D4가 성립시켜 준다 — 어떤 PR에서도 읽은 파일이 실리므로
# 공허한 통과 보호를 끌 이유가 없다.
expect_nonempty: true
Expand Down
41 changes: 25 additions & 16 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
name: Release

# issue #141 phase 1: tag-triggered build + GitHub Release. PyPI publishing is
# explicitly out of scope for this phase (user decision, issue #141) — the
# `publish-pypi` job below is scaffolded but disabled (`if: false`) until a
# PyPI Trusted Publisher is registered for this project (see
# https://docs.pypi.org/trusted-publishers/using-a-publisher/). When that
# happens, also add `environment: pypi` to that job (PyPA recommends gating
# publish jobs behind a protected GitHub Environment) and drop the `if: false`.
# Tag-triggered release (issue #141; PyPI publishing enabled by issue #245):
# gates -> build -> release (GitHub Release) -> publish-pypi (PyPI through
# Trusted Publishing and the `pypi` environment), plus gates -> image (GHCR).
# gates first checks that the tag equals pyproject.toml's version.
# Registration values, the `pypi` environment prerequisite and the recovery
# when publish-pypi fails after the GitHub Release exists: docs/RELEASING.md.

on:
push:
Expand All @@ -23,6 +22,15 @@ jobs:
with:
python-version: "3.12"

- name: Tag matches the pyproject version (issue #245)
run: |
version="$(python -c 'import tomllib; print(tomllib.load(open("pyproject.toml", "rb"))["project"]["version"])')"
if [ "${GITHUB_REF_NAME#v}" != "$version" ]; then
echo "::error::tag $GITHUB_REF_NAME does not match pyproject.toml version $version"
exit 1
fi
echo "tag $GITHUB_REF_NAME matches pyproject.toml version $version"

- name: Install
run: python -m pip install --upgrade pip && pip install .

Expand Down Expand Up @@ -212,15 +220,16 @@ jobs:
sbom: true
provenance: true

# Scaffolded, disabled: PyPI publishing needs a Trusted Publisher registered
# at https://pypi.org/manage/project/<name>/settings/publishing/ first
# (issue #141 explicitly excludes actual publishing from this phase). Once
# registered, flip `if: false` off and add `environment: pypi` above
# `runs-on` — PyPA recommends a protected environment for publish jobs.
# PyPI publishing through Trusted Publishing (issue #245): PyPI accepts this
# job's OIDC token for owner choiyounggi, repository linkly, workflow
# release.yml and environment pypi; the token permission below is the job's
# only one. needs: [build, release] keeps the irreversible upload last: a
# failed gates, build or release job skips this job, so PyPI never gets a
# version whose GitHub Release was not created. Recovery: docs/RELEASING.md.
publish-pypi:
name: Publish to PyPI (disabled — issue #141 phase 1)
if: false
needs: build
name: Publish to PyPI (Trusted Publishing)
needs: [build, release]
environment: pypi
runs-on: ubuntu-latest
permissions:
id-token: write # required for PyPI Trusted Publishing (OIDC)
Expand All @@ -231,4 +240,4 @@ jobs:
path: dist

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
Loading
Loading