feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加 - #445

Merged
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis
Jun 30, 2026
Merged

feat(intelligence): GitHub スキル推論に verify ステージ(import 解析)を追加#445
yusuke0610 merged 2 commits into
mainfrom
feat/skill-verify-import-analysis

Conversation

@yusuke0610

@yusuke0610yusuke0610 commented Jun 30, 2026

Copy link
Copy Markdown
Owner

概要

ADR-0016「GitHub 連携によるスキル推論基盤」の残タスク タスク1(verify ステージ / D6) を実装。direct 依存が実際に import されているかをソース解析で確認し、manifest_declared(宣言)を actual_import(実使用)証跡へ昇格させる(昇格のみ・降格なし。declare 証跡は保持 / D8「保持は細かく」)。

declare までで経歴書は出力可能、verify は精度を後追いで上げる位置づけ(D5)。

変更内容

  • github/api_client.py: fetch_manifest_pathsfetch_repo_tree(recursive Trees を 1 コールで全 blob パス取得)に分離。manifest 探索(declare)と import 解析(verify)が同一ツリーを共有し、verify のために tree を再取得しない(D9(a) 維持)
  • 新規 skills/imports/: ImportScanner plugin(go / python / js_ts / rust)+ registry。辞書を持たず(D3)、エコシステム別の保守的照合で実 import を判定:
    • go: import パス完全一致 or module/... 接頭辞一致
    • npm: specifier == package(@scope/name / subpath 対応)
    • pypi / cargo: -_ 変換後の一致
    • import 名乖離(PyYAMLyaml 等)は昇格漏れ(false negative)として受容(テストで明示固定)
  • github_collector.py: _collect_manifests_collect_repo_signals に拡張。1 ツリーから manifest 宣言と、direct 宣言のあるエコシステムの source を除外セグメント/浅い順/件数キャップ(30件)でサンプリング → import 解析。生コードは scan 後に破棄(永続化しない / D6)
  • skills/aggregator.py: RepoSkillInput.imported_symbols を追加。direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
  • テスト追加: import スキャナ(test_skill_import_scanners.py)/ aggregator verify / collector verify / api_client tree

判断事項

  • 設計分岐: import 照合は辞書レス・保守的マッチ。Python の import 名乖離は false negative として受容(昇格漏れのみで害なし)
  • 挙動変更: GET /api/github-link/skills の evidence に direct 依存で actual_import が増える(既存 manifest_declared は残存)
  • スキーマ不変: signal_source は既存値域内(actual_import は model/schema に既記載)→ migration 不要・codegen drift なし
  • コスト: verify でリポあたり最大 30 ソースファイルの追加 fetch(同一ツリー再利用で tree API 呼び出しは増やさない)

テスト

  • make ci green(backend lint+test / web / build)
  • codegen drift なし
  • E2E 不要(UI フロー不変。evidence 配列が増えるのみ)

補足(別 PR 予定)

skills/imports/ および skills/manifests/ の Protocol 属性に対する pyright 型注釈(extensions: tuple[str, ...] 等の invariance 対応)は、本 PR と分離して別 PR で対応します。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added import-scanning support for multiple ecosystems, including Python, JavaScript/TypeScript, Go, and Rust.
    • Enhanced repository analysis to capture both declared dependencies and actual imports from source files.
    • Added support for additional evidence when a declared dependency is confirmed in code.
  • Bug Fixes

    • Improved handling of repository tree traversal, including partial/truncated results and invalid repository inputs.
    • Refined dependency matching rules for common naming differences and package subpaths.

ADR-0016 D6 を実装。direct 依存が実際に import されているかをソース解析で確認し、
manifest_declared を actual_import 証跡へ昇格させる(昇格のみ・降格なし)。
- api_client: fetch_repo_tree を新設し recursive Trees を 1 コール化。
manifest 探索と import 解析が同一ツリーを共有(tree 再取得しない / D9(a))
- skills/imports/: ImportScanner plugin(go/python/js_ts/rust)。辞書レスの
保守的照合(-→_ 変換 / go 接頭辞一致 / npm 完全一致)。取りこぼしは false negative 受容
- github_collector: _collect_repo_signals で manifest 宣言 + direct エコシステムの
source を除外/浅い順/件数キャップ(30)でサンプリング→import 解析。生コードは scan 後破棄
- aggregator: direct かつ import 検出時に actual_import 証跡(confidence 0.85)を追加
- テスト追加(import スキャナ / aggregator verify / collector / api_client tree)
API スキーマ・型は不変(signal_source 値域内)。migration / codegen drift なし。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actionsgithub-actionsBot added the feature 新機能 label Jun 30, 2026
@coderabbitai

coderabbitaiBot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@yusuke0610, you've reached your PR review limit, so we couldn't start this review.

Next review available in:42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 155fd2ee-a086-4e0e-9c84-059f3d7fed15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a68f7f and 7fc60d4.

📒 Files selected for processing (4)
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_skill_import_scanners.py
📝 Walkthrough

Walkthrough

Implements the D6 verify stage for GitHub skill inference: introduces an imports/ plugin package with ImportScanner protocol and ecosystem-specific implementations (Go, Python, JS/TS, Rust), replaces fetch_manifest_paths with a general fetch_repo_tree, unifies manifest and source scanning in _collect_repo_signals, and promotes confirmed direct-dependency imports to actual_import evidence in the skill aggregator.

Changes

D6 Verify Stage: Import Scanner Plugins and actual_import Evidence

Layer / File(s)Summary
ImportScanner protocol and registry
backend/app/services/intelligence/skills/imports/base.py, backend/app/services/intelligence/skills/imports/registry.py, backend/app/services/intelligence/skills/imports/__init__.py
Defines the runtime_checkableImportScanner Protocol with extensions, ecosystem, scan, and matches contracts. Registry instantiates Tier1 scanners and exposes scanner_for_ecosystem, scanner_for_extension, and SOURCE_EXTENSIONS.
Ecosystem scanner implementations
backend/app/services/intelligence/skills/imports/go.py, backend/app/services/intelligence/skills/imports/python.py, backend/app/services/intelligence/skills/imports/js_ts.py, backend/app/services/intelligence/skills/imports/rust.py
Adds Go (exact + subpackage prefix match), Python (PEP 503 dash-to-underscore normalization), JS/TS (scoped package canonicalization, require/import extraction), and Rust (use/extern crate with reserved-segment exclusion) ImportScanner implementations.
Replace fetch_manifest_paths with fetch_repo_tree
backend/app/services/intelligence/github/api_client.py, backend/tests/test_github_api_client.py
Removes the manifest-name-filtered tree traversal and replaces it with fetch_repo_tree returning all blob paths, with ([], False) for invalid repos, (paths, True) for truncated, and ([], True) for HTTP/network errors. Tests updated accordingly.
Unified _collect_repo_signals
backend/app/services/intelligence/github_collector.py, backend/tests/test_github_collector_extended.py
Replaces _collect_manifests with _collect_repo_signals, which reuses the single fetch_repo_tree result to extract both package_declarations and imported_symbols (source files scanned only for direct-dependency ecosystems). Adds RepoData.imported_symbols, _SOURCE_MAX_COUNT/_SOURCE_MAX_DEPTH limits, and unified partial-scan flag.
actual_import evidence promotion in aggregator
backend/app/services/intelligence/skills/aggregator.py, backend/app/services/intelligence/github_link_service.py
Extends RepoSkillInput with imported_symbols, adds _is_imported helper and _SIGNAL_ACTUAL_IMPORT/_ACTUAL_IMPORT_CONFIDENCE constants. For direct declarations confirmed by the ecosystem scanner, appends an actual_import evidence record without downgrading existing manifest_declared evidence. github_link_service wires repo.imported_symbols into RepoSkillInput.
Scanner tests, aggregator verify tests, and ADR update
backend/tests/test_skill_import_scanners.py, backend/tests/test_skill_aggregator.py, docs/adr/0016-github-skill-inference.md, .claude/rules/backend/architecture.md
Adds full scanner unit tests (registry dispatch, per-ecosystem scan/matches including false-negative cases), aggregator verify tests (direct promoted, dev not promoted, Go subpackage prefix), and updates ADR-0016 with the implemented D6 design and revision history entry.

Sequence Diagram(s)

sequenceDiagram
participant collect_repos
participant _collect_repo_signals
participant fetch_repo_tree as GitHub Trees API
participant parse_manifest
participant ImportScanner
collect_repos->>_collect_repo_signals: repo metadata (owner/repo/branch)
_collect_repo_signals->>fetch_repo_tree: recursive tree request
fetch_repo_tree-->>_collect_repo_signals: (all_blob_paths, truncated)
_collect_repo_signals->>parse_manifest: manifest file contents
parse_manifest-->>_collect_repo_signals: package_declarations (with dependency_kind)
_collect_repo_signals->>ImportScanner: source files for direct-dep ecosystems only
ImportScanner-->>_collect_repo_signals: imported_symbols per ecosystem
_collect_repo_signals-->>collect_repos: (decls, imported_symbols, scan_partial)
Note over collect_repos: RepoData.imported_symbols populated
collect_repos->>aggregator: RepoSkillInput(imported_symbols=...)
aggregator->>ImportScanner: scanner_for_ecosystem + matches(canonical_name, imported)
ImportScanner-->>aggregator: bool
aggregator-->>collect_repos: EvidenceRecord(actual_import) appended if matched
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • yusuke0610/devforge#408: Introduced the ADR-0016 3-layer schema, persistence, and manifest-declare parsing that this PR extends with the D6 verify stage and actual_import promotion.
  • yusuke0610/devforge#417: Introduced the earlier fetch_manifest_paths Trees API approach in github/api_client.py that this PR replaces with the generalized fetch_repo_tree.

Poem

🐇 A rabbit hops through code so wide,
Scanning imports far and near with pride,
go, pypi, npm, cargo too—
Each blob path checked, direct or new.
actual_import earned, not merely declared,
Evidence stacked, the truth is bared! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 52.17% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main change: adding a verify stage with import analysis to GitHub skill inference.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-verify-import-analysis

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation backend バックエンド test テスト追加・修正 labels Jun 30, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/services/intelligence/github_collector.py (1)

149-168: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Optional: avoid the duplicate scanner_for_extension lookup.

scanner_for_extension(path) is computed once to build source_candidates and recomputed inside the selection loop. You can carry the scanner alongside the path to avoid the second lookup.

♻️ Possible refactor
- source_candidates = []+ source_candidates: list[tuple[str, object]] = []
for path in tree_paths:
scanner = scanner_for_extension(path)
if scanner is not None and scanner.ecosystem in direct_ecosystems:
- source_candidates.append(path)- selected_sources, source_dropped = _select_shallow(- source_candidates, _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT- )- for path in selected_sources:- scanner = scanner_for_extension(path)- if scanner is None:- continue+ source_candidates.append((path, scanner))+ selected_paths, source_dropped = _select_shallow(+ [p for p, _ in source_candidates], _SOURCE_MAX_DEPTH, _SOURCE_MAX_COUNT+ )+ scanner_by_path = dict(source_candidates)+ for path in selected_paths:+ scanner = scanner_by_path[path]
content = await fetch_repo_file(client, owner, repo, path)
if not content:
continue
imported_symbols.setdefault(scanner.ecosystem, set()).update(
scanner.scan(content)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/github_collector.py` around lines 149 -
168, Avoid recomputing scanner_for_extension(path) in the direct_ecosystems path
selection flow: in the logic that builds source_candidates and then iterates
selected_sources, carry the scanner together with each path so the same scanner
object can be reused after _select_shallow. Update the selected_sources loop in
github_collector.py to use the precomputed scanner instead of looking it up
again, while keeping the existing fetch_repo_file and imported_symbols update
behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/app/services/intelligence/skills/imports/js_ts.py`:
- Around line 12-14: The import scanner in js_ts.py is matching require/import
text inside comments, docs, and string literals, which can create false
positives in imported_symbols and incorrectly mark packages as actual imports.
Update the extraction logic around _SPEC_RE and the JS/TS import parsing path to
ignore comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.
In `@backend/app/services/intelligence/skills/imports/rust.py`:
- Around line 11-24: Update the Rust import scanner so `RustImportScanner.scan`
also detects re-exported imports like `pub use` and `pub(crate) use`, not just
plain `use` statements. Adjust `_USE_RE` in the Rust import module to allow an
optional visibility prefix before `use`, while keeping the existing
`RustImportScanner`, `_USE_RE`, and `_NON_CRATE` behavior unchanged for
filtering and external-crate detection.
---
Nitpick comments:
In `@backend/app/services/intelligence/github_collector.py`:
- Around line 149-168: Avoid recomputing scanner_for_extension(path) in the
direct_ecosystems path selection flow: in the logic that builds
source_candidates and then iterates selected_sources, carry the scanner together
with each path so the same scanner object can be reused after _select_shallow.
Update the selected_sources loop in github_collector.py to use the precomputed
scanner instead of looking it up again, while keeping the existing
fetch_repo_file and imported_symbols update behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5fe0422c-62e3-4ccd-9bcc-be3c58ff04ba

📥 Commits

Reviewing files that changed from the base of the PR and between b152643 and 1a68f7f.

📒 Files selected for processing (17)
  • .claude/rules/backend/architecture.md
  • backend/app/services/intelligence/github/api_client.py
  • backend/app/services/intelligence/github_collector.py
  • backend/app/services/intelligence/github_link_service.py
  • backend/app/services/intelligence/skills/aggregator.py
  • backend/app/services/intelligence/skills/imports/__init__.py
  • backend/app/services/intelligence/skills/imports/base.py
  • backend/app/services/intelligence/skills/imports/go.py
  • backend/app/services/intelligence/skills/imports/js_ts.py
  • backend/app/services/intelligence/skills/imports/python.py
  • backend/app/services/intelligence/skills/imports/registry.py
  • backend/app/services/intelligence/skills/imports/rust.py
  • backend/tests/test_github_api_client.py
  • backend/tests/test_github_collector_extended.py
  • backend/tests/test_skill_aggregator.py
  • backend/tests/test_skill_import_scanners.py
  • docs/adr/0016-github-skill-inference.md

Comment on lines +12 to +14
_SPEC_RE = re.compile(
r"""(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']""",
)

@coderabbitaicoderabbitaiBotJun 30, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== target file excerpt ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== search downstream usages ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|_SPEC_RE" backend/app/services -S
printf'\n== related tests ==\n'
rg -n "exclude.*relative|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 632


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== target file ==\n'
sed -n '1,220p' backend/app/services/intelligence/skills/imports/js_ts.py | cat -n
printf'\n== related code ==\n'
rg -n "imported_symbols|manifest_declared|actual_import|JsTsImportScanner|_SPEC_RE" backend/app/services -S
printf'\n== tests ==\n'
rg -n "JsTsImportScanner|imported_symbols|manifest_declared|actual_import|require\\(" backend -g '*test*' -g '*spec*' -S

Repository: yusuke0610/devforge

Length of output: 6555


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== scanner tests ==\n'
sed -n '1,220p' backend/tests/test_skill_import_scanners.py | cat -n
printf'\n== aggregator tests around actual_import ==\n'
sed -n '160,260p' backend/tests/test_skill_aggregator.py | cat -n
printf'\n== regex probe ==\n'
python3 - <<'PY'import respec_re = re.compile(r'''(?:\bfrom|\bimport|\brequire)\s*\(?\s*["']([^"']+)["']''')cases = { "line_comment": "// require(\"react\")", "block_comment": "/* import 'left-pad' */", "string_literal": "const s = 'import(\"lodash\")'", "real_require": "const x = require('lodash');", "real_import": "import {x} from '`@scope/pkg/subpath`';", "dynamic_import": "const m = import('vite');", "example_text": "docs: import('react') in README",}for name, text in cases.items(): print(name, spec_re.findall(text))PY

Repository: yusuke0610/devforge

Length of output: 8860


Strip comments and string literals before scanning JS/TS imports
backend/app/services/intelligence/skills/imports/js_ts.py:12-40
This regex also matches text inside comments, docs, and string literals (// require("react"), 'import("lodash")'), so imported_symbols can include packages that never appear in real import syntax and wrongly promote manifest_declared to actual_import. Switch to token/AST-based extraction, or preprocess the file to exclude comments and strings before matching.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/services/intelligence/skills/imports/js_ts.py` around lines 12 -
14, The import scanner in js_ts.py is matching require/import text inside
comments, docs, and string literals, which can create false positives in
imported_symbols and incorrectly mark packages as actual imports. Update the
extraction logic around _SPEC_RE and the JS/TS import parsing path to ignore
comments and string contents before matching, preferably by using a
token/AST-based approach or a preprocessing step that strips non-code regions
before applying the regex.

Copy link
Copy Markdown
OwnerAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@coderabbitai RVして

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== git head ==\n'
git rev-parse --short HEAD
printf'\n== file outline ==\n'
ast-grep outline backend/app/services/intelligence/skills/imports/js_ts.py --view expanded ||trueprintf'\n== current js_ts.py ==\n'
nl -ba backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== related scanner tests ==\n'
nl -ba backend/tests/test_skill_import_scanners.py | sed -n '1,260p'printf'\n== search for new comment/string stripping logic ==\n'
rg -n -C2 "comment|string|strip|token|lexer|scan\(" backend/app/services/intelligence/skills/imports backend/tests/test_skill_import_scanners.py -S

Length of output: 1624


🏁 Script executed:

#!/bin/bashset -euo pipefail
printf'\n== current js_ts.py ==\n'
cat -n backend/app/services/intelligence/skills/imports/js_ts.py | sed -n '1,220p'printf'\n== JS/TS scanner tests excerpt ==\n'
cat -n backend/tests/test_skill_import_scanners.py | sed -n '40,180p'printf'\n== focused grep around false-positive coverage ==\n'
rg -n -C3 "comment|string literal|string_literal|relative_imports_excluded|require\\(|import\\(" backend/tests/test_skill_import_scanners.py backend/app/services/intelligence/skills/imports/js_ts.py -S

Length of output: 14549


@yusuke0610, 再RVしました。

  • コメント内の誤検出は解消されています。backend/app/services/intelligence/skills/imports/js_ts.pyLine 16-Line 28 でコメント除去が入り、Line 50 でそれを通してから走査しています。
  • そのうえで、backend/tests/test_skill_import_scanners.pyLine 90-Line 97 に回帰テストも追加されていました。

一方で、string literal 内の import("x") / require("x") 風テキストについては、backend/app/services/intelligence/skills/imports/js_ts.pyLine 25-Line 26 にある通り、現状は意図的に未対応です。

なので結論としては:

  • 元コメントのうち 「comments/docs で誤検出する」部分は解消済み
  • 「string literals でも誤検出しうる」部分はまだ残っている

という状態です。
必要なら、元の指摘は少し広すぎたので、コメント起因の部分は取り下げて、string literal 限定の懸念に絞るのが正確です。


🧠 Learnings used
Learnt from: yusuke0610
Repo: yusuke0610/devforge PR: 265
File: backend/app/routers/resume_imports.py:165-168
Timestamp: 2026-05-22T11:50:08.476Z
Learning: Follow the devforge project rule in `.claude/CLAUDE.md`: during code review, do NOT ask for extra error handling/fallbacks/defensive validation for states that are guaranteed not to occur by system invariants. Treat data written by the system’s own handlers within the same DB transaction (and tied to the same commit/invariant—e.g., fields associated with `status="completed"`) as trusted. Apply validation only at system boundaries (user input and external APIs) or when data can change across transaction boundaries / originate outside the trusted handler path. Example: keep `json.loads(record.result_json)` unguarded in `backend/app/routers/resume_imports.py` when `result_json` is guaranteed valid by the same handler that sets the corresponding completion status.

Comment threadbackend/app/services/intelligence/skills/imports/rust.py Outdated
- js_ts: コメント(// と /* */)を除去してから import を走査。コメント内の
import/require 誤検出による actual_import 誤昇格を防止(URL の // は保護)
- rust: pub use / pub(crate) use の re-export も外部クレート使用として拾うよう
visibility 接頭辞を許容
- github_collector: verify の source 選定で scanner_for_extension の二重計算を解消
(path→scanner を一度引いて持ち回る)
- スキャナ改善に対応するテストを追加
挙動は精度向上のみ。API 契約・スキーマは不変。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backendバックエンドdocumentationImprovements or additions to documentationfeature新機能testテスト追加・修正

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@yusuke0610