Skip to content

Commit fb54459

Browse files
committed
release: publish 0.1.2
1 parent 4e3b6ce commit fb54459

14 files changed

Lines changed: 535 additions & 140 deletions

README.md

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,15 @@ Chinese first, with an English quick reference at the end.
3232
```bash
3333
export QWEN_API_KEY=...
3434
export CODE2SKILL_LLM=qwen
35-
export CODE2SKILL_MODEL=qwen-plus
35+
export CODE2SKILL_MODEL=qwen-plus-latest
36+
```
37+
38+
PowerShell:
39+
40+
```powershell
41+
$env:QWEN_API_KEY="..."
42+
$env:CODE2SKILL_LLM="qwen"
43+
$env:CODE2SKILL_MODEL="qwen-plus-latest"
3644
```
3745

3846
进入要分析的仓库目录后直接运行:
@@ -88,16 +96,34 @@ export ANTHROPIC_API_KEY=...
8896
export QWEN_API_KEY=...
8997
```
9098

99+
PowerShell:
100+
101+
```powershell
102+
$env:OPENAI_API_KEY="..."
103+
$env:ANTHROPIC_API_KEY="..."
104+
$env:QWEN_API_KEY="..."
105+
```
106+
91107
CLI 默认值:
92108

93109
```bash
94110
export CODE2SKILL_LLM=qwen
95-
export CODE2SKILL_MODEL=qwen-plus
111+
export CODE2SKILL_MODEL=qwen-plus-latest
96112
export CODE2SKILL_OUTPUT_DIR=.code2skill
97113
export CODE2SKILL_MAX_SKILLS=6
98114
export CODE2SKILL_BASE_REF=origin/main
99115
```
100116

117+
PowerShell:
118+
119+
```powershell
120+
$env:CODE2SKILL_LLM="qwen"
121+
$env:CODE2SKILL_MODEL="qwen-plus-latest"
122+
$env:CODE2SKILL_OUTPUT_DIR=".code2skill"
123+
$env:CODE2SKILL_MAX_SKILLS="6"
124+
$env:CODE2SKILL_BASE_REF="origin/main"
125+
```
126+
101127
说明:
102128

103129
- `qwen` 默认走阿里国际站兼容接口
@@ -109,7 +135,7 @@ export CODE2SKILL_BASE_REF=origin/main
109135
完整扫描并生成 Skill:
110136

111137
```bash
112-
code2skill scan --llm qwen --model qwen-plus
138+
code2skill scan --llm qwen --model qwen-plus-latest
113139
```
114140

115141
只做结构扫描:
@@ -298,7 +324,7 @@ jobs:
298324
env:
299325
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
300326
CODE2SKILL_LLM: qwen
301-
CODE2SKILL_MODEL: qwen-plus
327+
CODE2SKILL_MODEL: qwen-plus-latest
302328
run: |
303329
code2skill ci \
304330
--mode auto \
@@ -392,7 +418,16 @@ From the target repo root:
392418
```bash
393419
export QWEN_API_KEY=...
394420
export CODE2SKILL_LLM=qwen
395-
export CODE2SKILL_MODEL=qwen-plus
421+
export CODE2SKILL_MODEL=qwen-plus-latest
422+
code2skill scan
423+
```
424+
425+
PowerShell:
426+
427+
```powershell
428+
$env:QWEN_API_KEY="..."
429+
$env:CODE2SKILL_LLM="qwen"
430+
$env:CODE2SKILL_MODEL="qwen-plus-latest"
396431
code2skill scan
397432
```
398433

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "code2skill"
7-
version = "0.1.1"
7+
version = "0.1.2"
88
description = "Turn a code repository into structured AI skill context."
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/code2skill/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from typing import TYPE_CHECKING, Any
66

7-
__version__ = "0.1.1"
7+
__version__ = "0.1.2"
88

99
__all__ = [
1010
"ScanExecution",

src/code2skill/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def build_parser() -> argparse.ArgumentParser:
2323
),
2424
epilog=(
2525
"示例:\n"
26-
" code2skill scan --llm qwen --model qwen-plus\n"
26+
" code2skill scan --llm qwen --model qwen-plus-latest\n"
2727
" code2skill ci --mode auto --base-ref origin/main --llm qwen\n"
2828
" code2skill adapt --target codex"
2929
),
@@ -138,6 +138,8 @@ def main(argv: Sequence[str] | None = None) -> int:
138138
print(f"changed_files: {len(result.changed_files)}")
139139
if result.affected_skills:
140140
print(f"affected_skills: {', '.join(result.affected_skills)}")
141+
if result.generated_skills:
142+
print(f"generated_skills: {', '.join(result.generated_skills)}")
141143
print(f"output_dir: {result.output_dir}")
142144
if result.report_path is not None:
143145
print(f"report: {result.report_path}")

src/code2skill/core.py

Lines changed: 98 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
6868
state_store = StateStore(output_dir)
6969
previous_state = state_store.load()
7070
changed_diffs = _detect_changed_diffs(config, previous_state, git_client)
71-
changed_files = _changed_paths_from_diffs(changed_diffs)
71+
changed_files = _changed_paths_from_diffs(
72+
changed_diffs=changed_diffs,
73+
repo_path=repo_path,
74+
output_dir=output_dir,
75+
)
7276
effective_mode, notes = _choose_effective_mode(
7377
config=config,
7478
previous_state=previous_state,
@@ -155,8 +159,14 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
155159
patch_cost = cost_estimator.estimate_incremental_patch(rewrite_cost)
156160

157161
skill_artifacts: dict[str, str] = {}
162+
generated_skill_names: list[str] = []
163+
planned_skill_names: list[str] = []
158164
if _should_run_skill_pipeline(config):
159-
skill_artifacts = _build_skill_artifacts(
165+
(
166+
skill_artifacts,
167+
generated_skill_names,
168+
planned_skill_names,
169+
) = _build_skill_artifacts(
160170
config=config,
161171
effective_mode=effective_mode,
162172
repo_path=repo_path,
@@ -177,6 +187,13 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
177187
output_dir=output_dir,
178188
rendered_artifacts=rendered_artifacts,
179189
)
190+
if planned_skill_names:
191+
updated_files.extend(
192+
_prune_stale_skill_files(
193+
output_dir=output_dir,
194+
planned_skill_names=planned_skill_names,
195+
)
196+
)
180197

181198
report = _build_report(
182199
config=config,
@@ -188,6 +205,7 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
188205
changed_files=changed_files,
189206
affected_files=affected_files,
190207
affected_skills=affected_skills,
208+
generated_skills=generated_skill_names,
191209
written_files=written_files,
192210
updated_files=updated_files,
193211
head_commit=git_client.current_head() if git_client.is_repository() else None,
@@ -232,6 +250,7 @@ def execute_repository(config: ScanConfig) -> ScanExecution:
232250
changed_files=changed_files,
233251
affected_files=affected_files,
234252
affected_skills=affected_skills,
253+
generated_skills=generated_skill_names,
235254
report_path=report_path,
236255
report=report,
237256
)
@@ -615,8 +634,50 @@ def _detect_changed_diffs(
615634
return []
616635

617636

618-
def _changed_paths_from_diffs(changed_diffs: list[FileDiffPatch]) -> list[str]:
619-
return sorted({item.path for item in changed_diffs})
637+
def _changed_paths_from_diffs(
638+
changed_diffs: list[FileDiffPatch],
639+
repo_path: Path,
640+
output_dir: Path,
641+
) -> list[str]:
642+
return sorted(
643+
{
644+
item.path
645+
for item in changed_diffs
646+
if item.path
647+
and not _is_generated_artifact_path(
648+
path=item.path,
649+
repo_path=repo_path,
650+
output_dir=output_dir,
651+
)
652+
}
653+
)
654+
655+
656+
def _is_generated_artifact_path(
657+
path: str,
658+
repo_path: Path,
659+
output_dir: Path,
660+
) -> bool:
661+
normalized = Path(path).as_posix()
662+
first_part = normalized.split("/", 1)[0]
663+
if first_part.startswith(".code2skill"):
664+
return True
665+
if normalized in {"AGENTS.md", "CLAUDE.md", ".windsurfrules"}:
666+
return True
667+
if normalized == ".github/copilot-instructions.md":
668+
return True
669+
if normalized == ".cursor/rules" or normalized.startswith(".cursor/rules/"):
670+
return True
671+
672+
try:
673+
relative_output_dir = output_dir.relative_to(repo_path).as_posix()
674+
except ValueError:
675+
return False
676+
677+
return (
678+
normalized == relative_output_dir
679+
or normalized.startswith(f"{relative_output_dir}/")
680+
)
620681

621682

622683
def _choose_effective_mode(
@@ -764,7 +825,7 @@ def _build_skill_artifacts(
764825
changed_diffs: list[FileDiffPatch],
765826
affected_files: list[str],
766827
affected_skill_names: list[str],
767-
) -> dict[str, str]:
828+
) -> tuple[dict[str, str], list[str], list[str]]:
768829
from .skill_generator import SkillGenerator, match_planned_skills
769830
from .skill_planner import SkillPlanner, load_skill_plan, render_skill_plan
770831

@@ -795,7 +856,8 @@ def _build_skill_artifacts(
795856
"skill-plan.json": render_skill_plan(plan),
796857
}
797858
artifacts.update(generator.generate_all(blueprint=blueprint, plan=plan))
798-
return artifacts
859+
planned_names = [skill.name for skill in plan.skills]
860+
return artifacts, planned_names, planned_names
799861

800862
try:
801863
plan = load_skill_plan(plan_path)
@@ -805,10 +867,12 @@ def _build_skill_artifacts(
805867
"skill-plan.json": render_skill_plan(plan),
806868
}
807869
artifacts.update(generator.generate_all(blueprint=blueprint, plan=plan))
808-
return artifacts
870+
planned_names = [skill.name for skill in plan.skills]
871+
return artifacts, planned_names, planned_names
809872

810873
artifacts: dict[str, str] = {}
811874
plan_skill_names = {skill.name for skill in plan.skills}
875+
planned_names = [skill.name for skill in plan.skills]
812876
present_skills = [
813877
name for name in affected_skill_names
814878
if name in plan_skill_names
@@ -823,15 +887,13 @@ def _build_skill_artifacts(
823887
plan = planner.plan(blueprint=blueprint, repo_path=repo_path)
824888
artifacts["skill-plan.json"] = render_skill_plan(plan)
825889
plan_skill_names = {skill.name for skill in plan.skills}
826-
affected_skill_names = [
827-
name for name in affected_skill_names
828-
if name in plan_skill_names
829-
]
830-
if not affected_skill_names:
831-
affected_skill_names = match_planned_skills(affected_files, plan)
890+
planned_names = [skill.name for skill in plan.skills]
891+
affected_skill_names = match_planned_skills(affected_files, plan)
892+
artifacts.update(generator.generate_all(blueprint=blueprint, plan=plan))
893+
return artifacts, planned_names, planned_names
832894

833895
if not affected_skill_names:
834-
return artifacts
896+
return artifacts, [], planned_names
835897

836898
artifacts.update(
837899
generator.generate_incremental(
@@ -843,7 +905,7 @@ def _build_skill_artifacts(
843905
previous_state=previous_state,
844906
)
845907
)
846-
return artifacts
908+
return artifacts, affected_skill_names, planned_names
847909

848910

849911
def build_llm_backend(provider: str, model: str | None = None):
@@ -871,6 +933,25 @@ def _write_outputs(
871933
return written_files, updated_files
872934

873935

936+
def _prune_stale_skill_files(
937+
output_dir: Path,
938+
planned_skill_names: list[str],
939+
) -> list[Path]:
940+
skills_dir = output_dir / "skills"
941+
if not skills_dir.exists():
942+
return []
943+
944+
keep = {f"{name}.md" for name in planned_skill_names}
945+
keep.add("index.md")
946+
removed: list[Path] = []
947+
for path in skills_dir.glob("*.md"):
948+
if path.name in keep:
949+
continue
950+
path.unlink(missing_ok=True)
951+
removed.append(path)
952+
return removed
953+
954+
874955
def _build_report(
875956
config: ScanConfig,
876957
effective_mode: str,
@@ -881,6 +962,7 @@ def _build_report(
881962
changed_files: list[str],
882963
affected_files: list[str],
883964
affected_skills: list[str],
965+
generated_skills: list[str],
884966
written_files: list[Path],
885967
updated_files: list[Path],
886968
head_commit: str | None,
@@ -914,6 +996,7 @@ def _build_report(
914996
changed_files=changed_files,
915997
affected_files=affected_files,
916998
affected_skills=affected_skills,
999+
generated_skills=generated_skills,
9171000
),
9181001
first_generation_cost=first_generation_cost,
9191002
incremental_rewrite_cost=rewrite_cost,

0 commit comments

Comments
 (0)