Skip to content

Commit 020d3da

Browse files
committed
Harden adoption workflow and open-source packaging
1 parent 07ec399 commit 020d3da

16 files changed

Lines changed: 253 additions & 62 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,13 @@ Detailed notes for each tagged release live under [`docs/releases/`](./docs/rele
1212
- Added OpenAI-compatible Responses endpoint configuration through `CODE2SKILL_OPENAI_BASE_URL`.
1313
- Improved Skill planning and writing prompts to avoid documentation/packaging-only pseudo-skills and produce more maintainer-oriented guidance.
1414
- Improved PyPI-facing README content, package metadata, and sdist documentation inclusion.
15+
- Added explicit user personas and business scenarios to README/use-case documentation.
16+
- Hardened `adapt` to reject incomplete generated Skill directories before writing target files.
17+
- Hardened `doctor --target cursor` to require the copy manifest used for stale generated-rule cleanup.
18+
- Hardened CLI environment variable parsing to report invalid values instead of silently falling back to defaults.
19+
- Restricted sdist docs inclusion to public docs and release notes, excluding internal planning docs.
1520
- Fixed cost-estimate assumption text that rendered as mojibake in CLI/report output.
21+
- Fixed LLM invalid-JSON responses to produce a clear runtime error.
1622
- Fixed incremental generation to replan when affected Skills are missing from the existing plan.
1723

1824
## v0.1.7

CONTRIBUTING.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,15 @@ python -m pip install -e .[dev]
1313

1414
Run these before opening a pull request:
1515

16-
```bash
16+
```powershell
17+
if (Test-Path dist) { Remove-Item -Recurse -Force dist }
1718
python -m pytest -q
18-
python -m build
19+
python -m build --sdist --wheel
1920
python -m twine check dist/*
2021
```
2122

23+
On non-Windows shells, use `rm -rf dist` before the build.
24+
2225
## Repository Areas
2326

2427
- `src/code2skill/`: production package code
@@ -31,6 +34,7 @@ python -m twine check dist/*
3134
- keep changes scoped and reviewable
3235
- add or update tests for behavior changes
3336
- update docs when command behavior, API shape, or release process changes
37+
- for user-facing changes, state the target persona and business scenario being improved
3438
- update `CHANGELOG.md` and add `docs/releases/vX.Y.Z.md` when preparing a release
3539

3640
## Release Update Steps

MANIFEST.in

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ include LICENSE
22
include README.md
33
include README.zh-CN.md
44
include CHANGELOG.md
5-
recursive-include docs *.md
5+
include docs/*.md
6+
recursive-include docs/releases *.md
7+
prune docs/superpowers
68
recursive-include src/code2skill py.typed
79
prune .code2skill
810
prune tests

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ Use it when a Python project needs AI coding assistants to understand current mo
2121
- Validates generated bundles and adapted tool files with `doctor`.
2222
- Supports OpenAI Responses API, OpenAI-compatible Responses endpoints, Claude, and Qwen.
2323

24+
## Who Uses It
25+
26+
| User profile | What they need | How code2skill helps |
27+
|---|---|---|
28+
| Python maintainers | AI assistants that respect current module boundaries and extension patterns | Generates evidence-backed Skills from source code and keeps them reviewable |
29+
| DevEx and platform teams | A repeatable way to standardize AI coding context across many repositories | Exposes CLI, Python API, CI refresh, and readiness checks |
30+
| Open-source maintainers | Contributor-facing AI instructions that can be audited like normal docs | Writes committed artifacts and target files instead of relying on private chat history |
31+
| AI tooling evaluators | One repository knowledge layer that works across several assistants | Publishes the same Skills to Codex, Claude Code, Cursor, GitHub Copilot, and Windsurf |
32+
33+
## Business Scenarios
34+
35+
| Scenario | Trigger | Success signal |
36+
|---|---|---|
37+
| First AI adoption | A repository starts using Codex, Cursor, Claude Code, Copilot, or Windsurf | `scan`, `adapt`, and `doctor` produce a ready target file |
38+
| PR knowledge refresh | Code changes may invalidate existing AI instructions | `ci --mode auto` reports affected files and affected Skills |
39+
| Multi-tool rollout | A team uses more than one AI coding assistant | `adapt --target all` writes consistent target outputs |
40+
| Platform automation | A DevEx team runs repository-knowledge checks across many services | Python API returns structured results and readiness status |
41+
| Open-source contributor onboarding | New contributors need implementation rules before changing code | Generated Skills and README/docs explain the repo's working contracts |
42+
2443
## Install
2544

2645
Requires Python 3.10 or newer.

README.zh-CN.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,25 @@ English documentation: [README.md](https://github.com/oceanusXXD/code2skill/blob
2121
-`doctor` 校验 bundle、Skill plan、state 和目标工具文件是否可用。
2222
- 支持 OpenAI Responses API、OpenAI-compatible Responses endpoint、Claude 和 Qwen。
2323

24+
## 用户画像
25+
26+
| 用户画像 | 需要什么 | code2skill 如何帮助 |
27+
|---|---|---|
28+
| Python 仓库维护者 | AI 助手遵守当前模块边界和扩展方式 | 从源码证据生成可审阅 Skills |
29+
| DevEx 和平台团队 | 在多个仓库中标准化 AI 编程上下文 | 提供 CLI、Python API、CI 刷新和可用性校验 |
30+
| 开源维护者 | 像审阅普通文档一样审阅 contributor-facing AI 说明 | 把知识写入可提交文件,而不是留在私有聊天记录 |
31+
| AI tooling 评估者 | 一套仓库知识发布到多个 AI 助手 | 同一套 Skills 可适配 Codex、Claude Code、Cursor、GitHub Copilot 和 Windsurf |
32+
33+
## 业务场景
34+
35+
| 场景 | 触发条件 | 成功信号 |
36+
|---|---|---|
37+
| 首次 AI 接入 | 仓库开始使用 Codex、Cursor、Claude Code、Copilot 或 Windsurf | `scan``adapt``doctor` 产出 ready 的目标文件 |
38+
| PR 知识刷新 | 代码改动可能让 AI 说明过期 | `ci --mode auto` 报告 affected files 和 affected Skills |
39+
| 多工具 rollout | 团队同时使用多个 AI 编程助手 | `adapt --target all` 写出一致的目标工具文件 |
40+
| 平台自动化 | DevEx 团队跨多个服务运行仓库知识检查 | Python API 返回结构化结果和 readiness |
41+
| 开源 contributor onboarding | 新贡献者改代码前需要项目实现规则 | 生成的 Skills 和 README/docs 明确仓库工作契约 |
42+
2443
## 安装
2544

2645
需要 Python 3.10 或更高版本。

docs/use-cases.md

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
# Use Cases
22

3-
`code2skill` is designed around three practical adoption scenarios for Python repositories.
3+
`code2skill` is built for maintainers who want AI coding assistants to work from the same repository knowledge that humans can review in Git.
44

5-
## 1. First Repository Knowledge Layer
5+
## Primary Personas
66

7-
### Problem
7+
| Persona | Repository pain | Desired outcome |
8+
|---|---|---|
9+
| Python maintainer | AI tools miss local boundaries, style, and workflow contracts | Generated Skills describe how to modify the current codebase |
10+
| DevEx or platform owner | Each team writes different AI rules, often by hand | One repeatable workflow standardizes assistant context across services |
11+
| Open-source maintainer | Contributors bring different tools and private chat context | Public, committed AI instructions make project expectations auditable |
12+
| AI tooling evaluator | A repository needs to compare Codex, Cursor, Claude Code, Copilot, and Windsurf | One Skill layer can be adapted into every supported target |
813

9-
AI coding assistants enter a repository with incomplete context. They read README files, scattered docs, previous code, and chat history, but those sources are not structured as stable implementation guidance.
14+
## Scenario 1: First Repository Knowledge Layer
15+
16+
### Trigger
17+
18+
A Python repository is adopting an AI coding assistant and needs a durable project entry point.
1019

1120
### Workflow
1221

@@ -18,22 +27,22 @@ code2skill adapt . --target codex
1827
code2skill doctor . --target codex
1928
```
2029

21-
### Output
30+
### Outputs
2231

2332
- `.code2skill/adoption-guide.md`
2433
- `.code2skill/skills/index.md`
2534
- `.code2skill/skills/*.md`
2635
- `AGENTS.md` or another target instruction file
2736

28-
### Business Value
37+
### Success Signal
2938

30-
The team gets a reviewable AI-facing project entry point that can be committed and maintained like normal repository documentation.
39+
`doctor` reports `ready: true`, generated Skills are reviewable, and the target instruction file can be committed.
3140

32-
## 2. Pull Request And CI Refresh
41+
## Scenario 2: Pull Request And CI Refresh
3342

34-
### Problem
43+
### Trigger
3544

36-
AI-facing project knowledge becomes stale when code changes. Manually updating tool-specific rules is easy to skip, and full regeneration on every PR can be wasteful.
45+
Code changes may invalidate existing AI-facing project knowledge.
3746

3847
### Workflow
3948

@@ -43,22 +52,22 @@ code2skill adapt . --target codex
4352
code2skill doctor . --target codex
4453
```
4554

46-
### Output
55+
### Outputs
4756

4857
- refreshed Skill files when affected
4958
- refreshed target instruction files
50-
- `.code2skill/report.json` showing mode, changed files, affected files, affected Skills, and written artifacts
59+
- `.code2skill/report.json` with execution mode, changed files, affected files, affected Skills, and written artifacts
5160
- `.code2skill/state/analysis-state.json` for later incremental reuse
5261

53-
### Business Value
62+
### Success Signal
5463

55-
Teams can make AI knowledge maintenance part of the normal PR loop, with clear evidence for what changed and why.
64+
The PR shows exactly which AI-facing artifacts changed and why.
5665

57-
## 3. One Knowledge Source For Multiple AI Tools
66+
## Scenario 3: One Knowledge Source For Multiple AI Tools
5867

59-
### Problem
68+
### Trigger
6069

61-
Codex, Claude Code, Cursor, GitHub Copilot, and Windsurf all expect different instruction-file locations or formats. Maintaining each by hand creates drift.
70+
A team uses more than one AI coding assistant and wants consistent project context across tools.
6271

6372
### Workflow
6473

@@ -68,23 +77,23 @@ code2skill adapt . --target all
6877
code2skill doctor . --target all
6978
```
7079

71-
### Output
80+
### Outputs
7281

7382
- `AGENTS.md`
7483
- `CLAUDE.md`
7584
- `.cursor/rules/*`
7685
- `.github/copilot-instructions.md`
7786
- `.windsurfrules`
7887

79-
### Business Value
88+
### Success Signal
8089

81-
The repository owns one generated Skill layer and publishes consistent context to every supported assistant.
90+
All supported target files are generated from the same Skill layer, and `doctor --target all` reports readiness.
8291

83-
## 4. Platform Or DevEx Automation
92+
## Scenario 4: Platform Or DevEx Automation
8493

85-
### Problem
94+
### Trigger
8695

87-
Platform teams may need to run the same repository-knowledge workflow across multiple Python services without shelling out manually.
96+
A platform team needs to run the same repository-knowledge workflow across multiple Python services.
8897

8998
### Workflow
9099

@@ -99,9 +108,29 @@ for repo in repositories:
99108
raise RuntimeError((repo, readiness.next_steps))
100109
```
101110

102-
### Business Value
111+
### Success Signal
112+
113+
Automation can make a binary decision from structured readiness data instead of scraping free-form command output.
114+
115+
## Scenario 5: Open-Source Contributor Onboarding
116+
117+
### Trigger
118+
119+
An open-source project wants new contributors and AI assistants to share the same implementation rules before a change is proposed.
120+
121+
### Workflow
122+
123+
```bash
124+
code2skill scan . --llm qwen --model qwen-plus-latest
125+
code2skill adapt . --target codex
126+
code2skill doctor . --target codex
127+
```
128+
129+
Then link the generated target file and `.code2skill/skills/index.md` from contributor documentation.
130+
131+
### Success Signal
103132

104-
The same workflow can be embedded into internal automation while preserving the same artifact layout and readiness checks as the CLI.
133+
Contributors can inspect the same AI-facing project guidance that maintainers review and commit.
105134

106135
## Selection Guide
107136

@@ -111,3 +140,4 @@ The same workflow can be embedded into internal automation while preserving the
111140
| PR refresh | `ci --mode auto` | `adapt` | `doctor` |
112141
| Multi-tool publishing | `scan` | `adapt --target all` | `doctor --target all` |
113142
| Platform automation | Python API | `run_ci`, `adapt_repository` | `doctor` |
143+
| Open-source onboarding | `scan` | docs link plus `adapt` | `doctor` |

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ Repository = "https://github.com/oceanusXXD/code2skill"
5757
Documentation = "https://github.com/oceanusXXD/code2skill#readme"
5858
Issues = "https://github.com/oceanusXXD/code2skill/issues"
5959
Changelog = "https://github.com/oceanusXXD/code2skill/blob/main/CHANGELOG.md"
60+
Contributing = "https://github.com/oceanusXXD/code2skill/blob/main/CONTRIBUTING.md"
61+
Security = "https://github.com/oceanusXXD/code2skill/blob/main/SECURITY.md"
6062

6163
[project.optional-dependencies]
6264
test = [

src/code2skill/adapt.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ def adapt_skills(
2121
source_path = _resolve_path(destination_root_path, source_dir)
2222
if not source_path.exists() or not source_path.is_dir():
2323
raise FileNotFoundError(f"Skill directory does not exist: {source_path}")
24+
_validate_skill_source(source_path)
2425

2526
written: list[Path] = []
2627
for target_definition in get_target_definitions(target):
@@ -39,6 +40,20 @@ def _resolve_path(root: Path, candidate: Path | str) -> Path:
3940
return (root / path).resolve()
4041

4142

43+
def _validate_skill_source(source_dir: Path) -> None:
44+
index_path = source_dir / "index.md"
45+
skill_files = [
46+
path
47+
for path in source_dir.glob("*.md")
48+
if path.name != "index.md"
49+
]
50+
if not index_path.is_file() or not skill_files:
51+
raise ValueError(
52+
"Generated skills directory is incomplete: expected index.md and at least "
53+
"one Skill .md file. Run `code2skill scan .` without `--structure-only` first."
54+
)
55+
56+
4257
def _copy_skills(source_dir: Path, destination_dir: Path) -> list[Path]:
4358
destination_dir.mkdir(parents=True, exist_ok=True)
4459
skill_files = sorted(source_dir.glob("*.md"))

src/code2skill/capabilities/adoption_service.py

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -285,11 +285,8 @@ def _check_target_outputs(repo_root: Path, target: str, skills_dir: Path) -> lis
285285
checks.append(_check_copy_target(definition.name, destination, skills_dir))
286286
continue
287287
else:
288-
ok = (
289-
destination.is_file()
290-
and MANAGED_BLOCK_START in destination.read_text(encoding="utf-8")
291-
and MANAGED_BLOCK_END in destination.read_text(encoding="utf-8")
292-
)
288+
content = destination.read_text(encoding="utf-8") if destination.is_file() else ""
289+
ok = MANAGED_BLOCK_START in content and MANAGED_BLOCK_END in content
293290
checks.append(
294291
AdoptionCheck(
295292
name=f"target_{definition.name}",
@@ -351,23 +348,32 @@ def _check_copy_target(name: str, destination: Path, skills_dir: Path) -> Adopti
351348
)
352349

353350
manifest_path = destination / COPY_MANIFEST_NAME
354-
if manifest_path.is_file():
355-
try:
356-
manifest_names = _read_copy_manifest(manifest_path)
357-
except ValueError as exc:
358-
return AdoptionCheck(
359-
name=f"target_{name}",
360-
status="invalid",
361-
message=str(exc),
362-
path=manifest_path,
363-
)
364-
if manifest_names != expected_names:
365-
return AdoptionCheck(
366-
name=f"target_{name}",
367-
status="invalid",
368-
message=f"{name} target manifest does not match current generated Skills.",
369-
path=manifest_path,
370-
)
351+
if not manifest_path.is_file():
352+
return AdoptionCheck(
353+
name=f"target_{name}",
354+
status="missing",
355+
message=(
356+
f"{name} target manifest is missing. Run `code2skill adapt . --target {name}` "
357+
"so stale generated files can be tracked safely."
358+
),
359+
path=manifest_path,
360+
)
361+
try:
362+
manifest_names = _read_copy_manifest(manifest_path)
363+
except ValueError as exc:
364+
return AdoptionCheck(
365+
name=f"target_{name}",
366+
status="invalid",
367+
message=str(exc),
368+
path=manifest_path,
369+
)
370+
if manifest_names != expected_names:
371+
return AdoptionCheck(
372+
name=f"target_{name}",
373+
status="invalid",
374+
message=f"{name} target manifest does not match current generated Skills.",
375+
path=manifest_path,
376+
)
371377

372378
return AdoptionCheck(
373379
name=f"target_{name}",

src/code2skill/cli.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ def build_parser() -> argparse.ArgumentParser:
154154

155155

156156
def main(argv: Sequence[str] | None = None) -> int:
157-
parser = build_parser()
158157
try:
158+
parser = build_parser()
159159
args = parser.parse_args(argv)
160160
return _run_command(parser, args)
161161
except KeyboardInterrupt:
@@ -334,20 +334,25 @@ def _print_stderr(message: str) -> None:
334334

335335

336336
def _env_choice(name: str, default: str, choices: tuple[str, ...]) -> str:
337-
value = os.getenv(name, "").strip().lower()
337+
raw_value = os.getenv(name)
338+
if raw_value is None or not raw_value.strip():
339+
return default
340+
value = raw_value.strip().lower()
338341
if value in choices:
339342
return value
340-
return default
343+
raise ValueError(
344+
f"{name} must be one of {', '.join(choices)}; got {raw_value!r}."
345+
)
341346

342347

343348
def _env_int(name: str, default: int) -> int:
344-
value = os.getenv(name, "").strip()
345-
if not value:
349+
raw_value = os.getenv(name)
350+
if raw_value is None or not raw_value.strip():
346351
return default
347352
try:
348-
return int(value)
353+
return int(raw_value.strip())
349354
except ValueError:
350-
return default
355+
raise ValueError(f"{name} must be an integer; got {raw_value!r}.") from None
351356

352357

353358
def _env_optional(name: str) -> str | None:

0 commit comments

Comments
 (0)