-
Notifications
You must be signed in to change notification settings - Fork 0
feat(intelligence): GitHub 連携スキル推論基盤 Phase1+2(ADR-0016) #408
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
0ebb475
feat(intelligence): GitHub 連携スキル推論基盤の Phase1+2 を実装(ADR-0016)
yusuke0610 d63c73b
fix(intelligence): linguist マスタを resources/ へ移動し gitignore 除外を回避
yusuke0610 de1b639
review(intelligence): CodeRabbit 指摘対応(孤児行防止・永続化順序)
yusuke0610 5543f98
review(intelligence): CodeRabbit 指摘対応(URL誤抽出・ADR索引・テスト堅牢化)
yusuke0610 198b58e
review(intelligence): pypi package 名を PEP 503 正規化して重複を防ぐ
yusuke0610 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
128 changes: 128 additions & 0 deletions
128
backend/alembic_migrations/versions/0045_add_github_skill_tables.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,128 @@ | ||
| """GitHub 連携スキル推論の 3 層テーブルを追加する(ADR-0016 D1) | ||
|
|
||
| - github_skills : Layer 1 / 正規化スキル(language / package) | ||
| - github_skill_evidence : Layer 2 / 技術×リポの根拠(signal_source・量的シグナル) | ||
| - github_skill_proficiency: Layer 3 / 習熟度・文脈(本フェーズ未投入) | ||
|
|
||
| いずれも新規テーブル作成のみ(op.create_table)で、既存テーブルの再作成は伴わない。 | ||
| FK は users / github_skills を親に CASCADE 削除。 | ||
|
|
||
| Revision ID: 0045_add_github_skill_tables | ||
| Revises: 0044_add_credit_billing | ||
| Create Date: 2026-06-25 00:00:00.000000 | ||
| """ | ||
|
|
||
| from typing import Sequence, Union | ||
|
|
||
| import sqlalchemy as sa | ||
| from alembic import op | ||
|
|
||
| revision: str = "0045_add_github_skill_tables" | ||
| down_revision: Union[str, None] = "0044_add_credit_billing" | ||
| branch_labels: Union[str, Sequence[str], None] = None | ||
| depends_on: Union[str, Sequence[str], None] = None | ||
|
|
||
|
|
||
| def upgrade() -> None: | ||
| op.create_table( | ||
| "github_skills", | ||
| sa.Column("id", sa.String(length=36), primary_key=True), | ||
| sa.Column( | ||
| "user_id", | ||
| sa.String(length=36), | ||
| sa.ForeignKey("users.id", ondelete="CASCADE"), | ||
| nullable=False, | ||
| ), | ||
| sa.Column("kind", sa.String(length=20), nullable=False), | ||
| sa.Column("canonical_name", sa.String(length=255), nullable=False), | ||
| sa.Column("ecosystem", sa.String(length=20), nullable=False, server_default=""), | ||
| sa.Column("parent", sa.String(length=255), nullable=True), | ||
| sa.Column("display_name", sa.String(length=255), nullable=True), | ||
| sa.Column( | ||
| "created_at", | ||
| sa.DateTime(timezone=True), | ||
| server_default=sa.func.now(), | ||
| nullable=False, | ||
| ), | ||
| sa.Column( | ||
| "updated_at", | ||
| sa.DateTime(timezone=True), | ||
| server_default=sa.func.now(), | ||
| nullable=False, | ||
| ), | ||
| sa.UniqueConstraint( | ||
| "user_id", "kind", "ecosystem", "canonical_name", | ||
| name="uq_github_skills_identity", | ||
| ), | ||
| ) | ||
| op.create_index("ix_github_skills_user_id", "github_skills", ["user_id"]) | ||
|
|
||
| op.create_table( | ||
| "github_skill_evidence", | ||
| sa.Column("id", sa.String(length=36), primary_key=True), | ||
| sa.Column( | ||
| "skill_id", | ||
| sa.String(length=36), | ||
| sa.ForeignKey("github_skills.id", ondelete="CASCADE"), | ||
| nullable=False, | ||
| ), | ||
| sa.Column("repo_full_name", sa.String(length=255), nullable=False), | ||
| sa.Column("repo_url", sa.String(length=255), nullable=False, server_default=""), | ||
| sa.Column("signal_source", sa.String(length=30), nullable=False), | ||
| sa.Column("confidence", sa.Float(), nullable=False, server_default="0"), | ||
| sa.Column("language_bytes", sa.Integer(), nullable=True), | ||
| sa.Column("dependency_kind", sa.String(length=20), nullable=True), | ||
| sa.Column( | ||
| "created_at", | ||
| sa.DateTime(timezone=True), | ||
| server_default=sa.func.now(), | ||
| nullable=False, | ||
| ), | ||
| sa.UniqueConstraint( | ||
| "skill_id", "repo_full_name", "signal_source", | ||
| name="uq_github_skill_evidence_identity", | ||
| ), | ||
| ) | ||
| op.create_index( | ||
| "ix_github_skill_evidence_skill_id", "github_skill_evidence", ["skill_id"] | ||
| ) | ||
|
|
||
| op.create_table( | ||
| "github_skill_proficiency", | ||
| sa.Column("id", sa.String(length=36), primary_key=True), | ||
| sa.Column( | ||
| "skill_id", | ||
| sa.String(length=36), | ||
| sa.ForeignKey("github_skills.id", ondelete="CASCADE"), | ||
| nullable=False, | ||
| unique=True, | ||
| ), | ||
| sa.Column("self_assessed_level", sa.String(length=20), nullable=True), | ||
| sa.Column("narrative", sa.Text(), nullable=True), | ||
| sa.Column("duration_months", sa.Integer(), nullable=True), | ||
| sa.Column("scale", sa.String(length=100), nullable=True), | ||
| sa.Column("source", sa.String(length=20), nullable=True), | ||
| sa.Column("reviewed", sa.Boolean(), nullable=False, server_default="0"), | ||
| sa.Column( | ||
| "created_at", | ||
| sa.DateTime(timezone=True), | ||
| server_default=sa.func.now(), | ||
| nullable=False, | ||
| ), | ||
| sa.Column( | ||
| "updated_at", | ||
| sa.DateTime(timezone=True), | ||
| server_default=sa.func.now(), | ||
| nullable=False, | ||
| ), | ||
| ) | ||
|
|
||
|
|
||
| def downgrade() -> None: | ||
| op.drop_table("github_skill_proficiency") | ||
| op.drop_index( | ||
| "ix_github_skill_evidence_skill_id", table_name="github_skill_evidence" | ||
| ) | ||
| op.drop_table("github_skill_evidence") | ||
| op.drop_index("ix_github_skills_user_id", table_name="github_skills") | ||
| op.drop_table("github_skills") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| """GitHub 連携スキル推論の 3 層モデル(ADR-0016 D1)。 | ||
|
|
||
| 機械は「幅」(Layer 1-2)、人間は「深さ」(Layer 3)を埋める責務分離。 | ||
|
|
||
| - ``github_skills`` : Layer 1 / 正規化エンティティ(language / package) | ||
| - ``github_skill_evidence`` : Layer 2 / 技術×リポの根拠(signal_source・量的シグナル) | ||
| - ``github_skill_proficiency``: Layer 3 / 習熟度・文脈(本フェーズでは未投入) | ||
|
|
||
| 連携の再実行ごとに Layer 1-2 はユーザー単位で洗い替える(CASCADE で evidence も削除)。 | ||
| Layer 3 は人間/agent が後追いで埋める想定で、洗い替え時の保全は後続フェーズの課題。 | ||
| """ | ||
|
|
||
| import uuid | ||
| from datetime import datetime | ||
|
|
||
| from sqlalchemy import ( | ||
| Boolean, | ||
| DateTime, | ||
| Float, | ||
| ForeignKey, | ||
| Integer, | ||
| String, | ||
| Text, | ||
| UniqueConstraint, | ||
| func, | ||
| ) | ||
| from sqlalchemy.orm import Mapped, mapped_column, relationship | ||
|
|
||
| from ..db import Base | ||
|
|
||
|
|
||
| class GitHubSkill(Base): | ||
| """Layer 1: 正規化されたスキルエンティティ(D1/D2)。 | ||
|
|
||
| ``kind`` で language / package を区別する(検出方法と信頼度の出方が異なるため / D2)。 | ||
| language の ``ecosystem`` は N/A として空文字で持つ(NULL だと一意制約が効かないため)。 | ||
| package ID はエコシステム内で一意 = canonical なので辞書正規化はしない(D3)。 | ||
| """ | ||
|
|
||
| __tablename__ = "github_skills" | ||
| __table_args__ = ( | ||
| UniqueConstraint( | ||
| "user_id", "kind", "ecosystem", "canonical_name", | ||
| name="uq_github_skills_identity", | ||
| ), | ||
| ) | ||
|
|
||
| id: Mapped[str] = mapped_column( | ||
| String(36), primary_key=True, default=lambda: str(uuid.uuid4()) | ||
| ) | ||
| user_id: Mapped[str] = mapped_column( | ||
| String(36), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True | ||
| ) | ||
| # "language" / "package" | ||
| kind: Mapped[str] = mapped_column(String(20), nullable=False) | ||
| # 言語=Linguist 正規名 / package=エコシステム内で一意な package ID | ||
| canonical_name: Mapped[str] = mapped_column(String(255), nullable=False) | ||
| # package のエコシステム(npm/pypi/go/cargo)。language は "" (N/A) | ||
| ecosystem: Mapped[str] = mapped_column( | ||
| String(20), nullable=False, default="", server_default="" | ||
| ) | ||
| # Linguist の group(言語の親)。無ければ NULL | ||
| parent: Mapped[str | None] = mapped_column(String(255), nullable=True, default=None) | ||
| # 表示名・粒度畳みの確定値(agent 提案→人間確定 / D3)。未確定は NULL | ||
| display_name: Mapped[str | None] = mapped_column( | ||
| String(255), nullable=True, default=None | ||
| ) | ||
| created_at: Mapped[datetime] = mapped_column( | ||
| DateTime(timezone=True), default=func.now(), server_default=func.now(), nullable=False | ||
| ) | ||
| updated_at: Mapped[datetime] = mapped_column( | ||
| DateTime(timezone=True), | ||
| default=func.now(), | ||
| server_default=func.now(), | ||
| onupdate=func.now(), | ||
| nullable=False, | ||
| ) | ||
|
|
||
| evidence: Mapped[list["GitHubSkillEvidence"]] = relationship( | ||
| back_populates="skill", cascade="all, delete-orphan" | ||
| ) | ||
| proficiency: Mapped["GitHubSkillProficiency | None"] = relationship( | ||
| back_populates="skill", cascade="all, delete-orphan", uselist=False | ||
| ) | ||
|
|
||
|
|
||
| class GitHubSkillEvidence(Base): | ||
| """Layer 2: 技術×根拠リポの N:N(機械が埋める / D1)。 | ||
|
|
||
| ``signal_source`` で根拠の出所を区別する: | ||
| - ``language_bytes`` : Linguist のバイト比率(言語) | ||
| - ``manifest_declared`` : manifest の宣言(package / declare ステージ) | ||
| - ``actual_import`` : import 解析で実使用へ昇格(verify ステージ / 後続) | ||
| """ | ||
|
|
||
| __tablename__ = "github_skill_evidence" | ||
| __table_args__ = ( | ||
| UniqueConstraint( | ||
| "skill_id", "repo_full_name", "signal_source", | ||
| name="uq_github_skill_evidence_identity", | ||
| ), | ||
| ) | ||
|
|
||
| id: Mapped[str] = mapped_column( | ||
| String(36), primary_key=True, default=lambda: str(uuid.uuid4()) | ||
| ) | ||
| skill_id: Mapped[str] = mapped_column( | ||
| String(36), | ||
| ForeignKey("github_skills.id", ondelete="CASCADE"), | ||
| nullable=False, | ||
| index=True, | ||
| ) | ||
| # 根拠リポジトリ(owner/name)と URL(経歴書の証跡用) | ||
| repo_full_name: Mapped[str] = mapped_column(String(255), nullable=False) | ||
| repo_url: Mapped[str] = mapped_column(String(255), nullable=False, default="", server_default="") | ||
| signal_source: Mapped[str] = mapped_column(String(30), nullable=False) | ||
| # 0.0–1.0 の信頼度 | ||
| confidence: Mapped[float] = mapped_column(Float, nullable=False, default=0.0) | ||
| # 言語シグナル: このリポでのバイト数 | ||
| language_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True, default=None) | ||
| # package シグナル: direct/dev/indirect/peer/build(D7) | ||
| dependency_kind: Mapped[str | None] = mapped_column( | ||
| String(20), nullable=True, default=None | ||
| ) | ||
| created_at: Mapped[datetime] = mapped_column( | ||
| DateTime(timezone=True), default=func.now(), server_default=func.now(), nullable=False | ||
| ) | ||
|
|
||
| skill: Mapped["GitHubSkill"] = relationship(back_populates="evidence") | ||
|
|
||
|
|
||
| class GitHubSkillProficiency(Base): | ||
| """Layer 3: 習熟度・文脈(人間/agent が後追いで埋める / D1)。 | ||
|
|
||
| 本フェーズ(基盤+declare)では投入しない。スキーマだけ用意する。 | ||
| """ | ||
|
|
||
| __tablename__ = "github_skill_proficiency" | ||
|
|
||
| id: Mapped[str] = mapped_column( | ||
| String(36), primary_key=True, default=lambda: str(uuid.uuid4()) | ||
| ) | ||
| skill_id: Mapped[str] = mapped_column( | ||
| String(36), | ||
| ForeignKey("github_skills.id", ondelete="CASCADE"), | ||
| nullable=False, | ||
| unique=True, | ||
| ) | ||
| # 自己評価レベル(例: beginner/intermediate/advanced)。確定まで NULL | ||
| self_assessed_level: Mapped[str | None] = mapped_column( | ||
| String(20), nullable=True, default=None | ||
| ) | ||
| narrative: Mapped[str | None] = mapped_column(Text, nullable=True, default=None) | ||
| duration_months: Mapped[int | None] = mapped_column(Integer, nullable=True, default=None) | ||
| scale: Mapped[str | None] = mapped_column(String(100), nullable=True, default=None) | ||
| # 出所: "agent"(生成)/ "human"(手入力) | ||
| source: Mapped[str | None] = mapped_column(String(20), nullable=True, default=None) | ||
| # 人間レビュー済みか | ||
| reviewed: Mapped[bool] = mapped_column( | ||
| Boolean, nullable=False, default=False, server_default="0" | ||
| ) | ||
| created_at: Mapped[datetime] = mapped_column( | ||
| DateTime(timezone=True), default=func.now(), server_default=func.now(), nullable=False | ||
| ) | ||
| updated_at: Mapped[datetime] = mapped_column( | ||
| DateTime(timezone=True), | ||
| default=func.now(), | ||
| server_default=func.now(), | ||
| onupdate=func.now(), | ||
| nullable=False, | ||
| ) | ||
|
|
||
| skill: Mapped["GitHubSkill"] = relationship(back_populates="proficiency") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.