Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Core tests and first-run demos

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
tests:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- name: Run standard-library tests and documented demos
run: python3 -B -m unittest discover -s tests -v
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ What it generates:

## Try it

For a first look, the bundled dry-run demos need only Python and the standard library. They preview the context sent to a model; they do not call a model, create reports, or modify source files.

Start with the [quick demo](#quick-demo). For a two-minute walkthrough and a focused feedback task, see [the demo and trial guide](docs/FIRST_TRIAL.zh-CN.md).

Check local readiness:

```bash
Expand All @@ -42,12 +46,14 @@ Use a stricter enterprise review profile:
python setup_local_ai.py --suggest-patch --project . --task "Add input validation" --files README.md --policy-profile enterprise-strict --dry-run
```

Generate a v0.6 evidence bundle:
Preview the configuration and prompt for a v0.6 evidence bundle:

```bash
python setup_local_ai.py --suggest-patch --project . --task "Add input validation" --files README.md --policy-profile supply-chain --report-bundle --dry-run
```

Remove `--dry-run` only after configuring a local model to generate a real bundle.

See a sanitized example bundle in [`examples/sample-report-bundle`](examples/sample-report-bundle).

## Who is this for?
Expand All @@ -66,7 +72,7 @@ Typical scenarios:
Try the bundled error-fix demo without calling Ollama:

```bash
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/error.log --context-lite --dry-run
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --context-lite --dry-run
```

Try the bundled TODO-completion demo without calling Ollama:
Expand Down Expand Up @@ -383,7 +389,7 @@ In local verification, `gemma4-local:latest` produced a good `--fix-error` patch
Try the bundled demos first:

```bash
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/error.log --context-lite --dry-run
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --context-lite --dry-run
python setup_local_ai.py --complete-todo --project examples/demo-todo-project --files app.py --context-lite --dry-run
```

Expand Down Expand Up @@ -620,3 +626,4 @@ This project is designed to be air-gap friendly, but does not claim absolute zer
## License

Apache License 2.0. See [LICENSE](LICENSE).

13 changes: 10 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ Cyber-Code-Shield 是面向本地 AI Coding 的审查优先审计层。

## 快速试用

首次体验只需 Python 和标准库,可先运行下方的[快速演示](#快速演示)。带 `--dry-run` 的命令只预览将发送给模型的上下文,不调用模型、不生成报告,也不修改源码。

[两分钟演示与试用指南](docs/FIRST_TRIAL.zh-CN.md)提供操作顺序、验收标准和反馈问题。

检查本地环境:

```bash
Expand All @@ -42,12 +46,14 @@ python setup_local_ai.py --suggest-patch --project . --task "Add input validatio
python setup_local_ai.py --suggest-patch --project . --task "Add input validation" --files README.md --policy-profile enterprise-strict --dry-run
```

生成 v0.6 证据包:
预览 v0.6 证据包配置与提示词:

```bash
python setup_local_ai.py --suggest-patch --project . --task "Add input validation" --files README.md --policy-profile supply-chain --report-bundle --dry-run
```

配置本地模型后,去掉 `--dry-run` 才会调用模型并生成真实证据包。

脱敏示例见 [`examples/sample-report-bundle`](examples/sample-report-bundle)。

## 适合谁?
Expand All @@ -66,7 +72,7 @@ Cyber-Code-Shield 适合已经体验过 AI 编程效率提升,但所在环境
先试内置错误修复 demo,不调用 Ollama:

```bash
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/error.log --context-lite --dry-run
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --context-lite --dry-run
```

再试内置 TODO 补全 demo,不调用 Ollama:
Expand Down Expand Up @@ -373,7 +379,7 @@ python setup_local_ai.py --suggest-patch --project /path/to/project --task "Fix
建议先试内置 demo:

```bash
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/error.log --context-lite --dry-run
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --context-lite --dry-run
python setup_local_ai.py --complete-todo --project examples/demo-todo-project --files app.py --context-lite --dry-run
```

Expand Down Expand Up @@ -607,3 +613,4 @@ Cyber-Code-Shield 是 local-first 工具,默认不配置云端 AI provider。
## License

Apache License 2.0. See [LICENSE](LICENSE).

58 changes: 58 additions & 0 deletions docs/FIRST_TRIAL.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# 两分钟演示与第一次试用

目标:让第一次接触项目的人完成一个小任务,理解本地 AI 补丁审查流程,并指出操作中的卡点。

## 准备

下载或克隆仓库,在仓库根目录打开终端。先运行 `python --version` 确认 Python 可用;如果系统使用 `python3` 或 `py`,相应替换下方命令的 `python`。

本次演示只使用 Python 标准库,不需要 Ollama、模型下载或 API Key。`--dry-run` 展示的是提示词及上下文预览,不会生成修复建议,也不修改代码。

## 两分钟录屏顺序

| 时间 | 屏幕操作 | 讲解要点 |
| --- | --- | --- |
| 0:00–0:20 | 打开示例 `app.py` 和 `traceback.txt` | 函数收到了 `raw_username`,却使用未定义的 `username`。 |
| 0:20–0:55 | 运行下面的错误修复预览命令 | 工具读取日志,定位 `app.py:2`,选取附近代码组织上下文。 |
| 0:55–1:20 | 在输出中找到主故障位置和代码片段 | 说明提供给模型的具体范围;这是预览,没有调用模型。 |
| 1:20–1:45 | 运行 TODO 预览命令 | 显式提供 `app.py`,关注 `is_valid_username` 的 ASCII 字符约束。 |
| 1:45–2:00 | 打开仓库的 `examples/sample-report-bundle` | 这是已有示例证据包。真实生成需要配置本地模型;补丁最终由人审查应用。 |

错误修复预览,在仓库根目录运行:

```bash
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --context-lite --dry-run
```

应看到:命令成功结束、主故障位置包含 `app.py:2`、提示词包含 `username` 的错误及对应源码。

TODO 补全预览:

```bash
python setup_local_ai.py --complete-todo --project examples/demo-todo-project --files app.py --context-lite --dry-run
```

应看到:提示词包含 `is_valid_username` 及 3–20 个小写 ASCII 字母、数字或下划线的约束。

## 给试用者的十分钟任务

找熟悉 Python 或使用过本地 AI 编程工具的人试用。初轮 2–3 人即可用于寻找明显卡点,并不代表市场验证。

1. 从 README 找到首次体验入口,完成错误修复预览。
2. 指出最可能需要修改的文件和行号。
3. 判断预览命令是否已经调用模型、生成报告或修改代码。
4. 记录一处说明不清或操作受阻的位置。

不要求试用者安装模型,也不要求泛泛评价“好不好”。收集:操作系统、Python 版本、实际命令、卡住步骤、期望结果;不要收集真实业务源码、密钥或个人账务资料。

## 验收标准

- 文档引用的示例文件实际存在。
- 两个预览命令退出码均为 0。
- 错误预览定位 `app.py:2`。
- 预览没有改变示例源码或创建补丁报告。
- 试用者能区分提示词预览、实际模型输出和样例证据包。

## 验证范围

自动测试验证了核心规则和这两个命令的预览路径。它没有验证不同本地模型的建议质量、真实企业部署或所有平台兼容性。实际模型验证应单独记录模型、硬件、输入、结果和失败案例。
32 changes: 32 additions & 0 deletions docs/PORTFOLIO_EVIDENCE.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 求职作品展示:证据记录

项目主题:本地 AI 编程补丁建议的审查与留痕。代码中的风险提示使用启发式规则,不能写成完整安全审计或合规认证能力。

## 展示材料

| 材料 | 应展示什么 |
| --- | --- |
| 两分钟录屏 | 按 `FIRST_TRIAL.zh-CN.md` 演示可复现的首次体验 |
| 测试记录 | 核心单元测试与首次演示回归测试的实际结果 |
| 一次问题修复 | 演示引用的日志文件未提交;如何复现、修复和防止回归 |
| 人机协作记录 | AI 完成哪些工作;你实际做过哪些配置、判断、验证和人工补位 |
| 已知限制 | 没有自动应用补丁;模型质量和真实用户易用性需分别验证 |

## 本轮可记录的事实

- 原有 33 项测试通过,但 README 的错误演示因缺少 `error.log` 无法从新下载的仓库直接运行。
- `.gitignore` 忽略 `*.log`;将演示材料改为随仓库保存的 `traceback.txt`,同步根目录文档、示例说明和 CLI 帮助。
- 增加首次体验回归测试,直接执行 README 中的预览命令,并检查代码未被修改。
- 增加 GitHub Actions 配置,在 PR 和主分支更新时运行标准库测试。是否实际成功执行,以 Actions 的运行结果为准。
- 本轮修复及测试由 ChatGPT 协助完成;维护者的验收、录屏和后续真实模型验证应分别记录。

## 你的个人贡献:按实际完成情况填写

- 我提出这个项目,是因为:
- 我使用的 AI 开发工具及分工是:
- AI 曾经卡住的具体步骤是:
- 我亲自解决或判断的事情是:
- 我如何确认结果有效:
- 我还没有验证的范围:

录屏、试用人数、模型效果、商业使用和性能指标都应依据实际记录填写。完成一次实际模型调用后,再补充模型输出的可用性评估。
13 changes: 9 additions & 4 deletions examples/demo-buggy-project/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,27 @@ From this directory:
python app.py
```

The sample traceback is saved in `error.log`.
A portable sample traceback is included in `traceback.txt`. It points to the deliberately broken line in `app.py`. The `.txt` extension keeps the fixture versioned while generated `*.log` files remain ignored.

Running `python app.py` is expected to fail with `NameError`; it does not save a log automatically. You can use the included fixture directly without running the broken app first.

## Generate a local fix suggestion

From the Cyber-Code-Shield repository root:

```bash
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/error.log --context-lite --dry-run
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --context-lite --dry-run
```

To call a local Ollama model instead of only previewing the prompt:

```bash
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/error.log --chat-model gemma4-local --context-lite --patch-timeout 600
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --chat-model gemma4-local --context-lite --patch-timeout 600
```

The expected suggestion should identify the wrong variable name and propose using `raw_username.strip().lower()` inside `normalize_username`.
In dry-run mode, expect an exit code of 0, `app.py:2` as the primary error location, and a prompt containing the undefined `username` variable. A model is not called, so this is a context preview rather than a generated repair.

The expected suggestion from a real model should identify the wrong variable name and propose using `raw_username.strip().lower()` inside `normalize_username`.

Cyber-Code-Shield only writes a Markdown suggestion file. It does not modify `app.py` automatically. Generated suggestion files are local review artifacts and should not be committed unless they are intentionally used as examples.

8 changes: 8 additions & 0 deletions examples/demo-buggy-project/traceback.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Traceback (most recent call last):
File "app.py", line 14, in <module>
print(create_user_profile(" Alice_Example "))
File "app.py", line 6, in create_user_profile
normalized_username = normalize_username(raw_username)
File "app.py", line 2, in normalize_username
return username.strip().lower()
NameError: name 'username' is not defined
3 changes: 2 additions & 1 deletion setup_local_ai.py
Original file line number Diff line number Diff line change
Expand Up @@ -2422,7 +2422,7 @@ def build_parser():
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""Examples:
python setup_local_ai.py --check
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/error.log --context-lite --dry-run
python setup_local_ai.py --fix-error --project examples/demo-buggy-project --error-log examples/demo-buggy-project/traceback.txt --context-lite --dry-run
python setup_local_ai.py --suggest-patch --project . --task "Add input validation" --files src/app.py --dry-run
python setup_local_ai.py --complete-todo --project examples/demo-todo-project --files app.py --context-lite --dry-run
python setup_local_ai.py --suggest-patch --project . --task "Fix bug" --inference-provider openai-compatible --api-base http://localhost:1234/v1 --chat-model local-model
Expand Down Expand Up @@ -2602,3 +2602,4 @@ def main():

if __name__ == "__main__":
sys.exit(main())

68 changes: 68 additions & 0 deletions tests/test_first_run_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""Regression coverage for the first commands a new user copies from the docs."""
import hashlib
import re
import shlex
import subprocess
import sys
import unittest
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(REPO_ROOT))
import setup_local_ai as ccs


class FirstRunDemoTest(unittest.TestCase):
def run_cli(self, args):
return subprocess.run(
[sys.executable, "-B", str(REPO_ROOT / "setup_local_ai.py"), *args],
cwd=REPO_ROOT, capture_output=True, text=True, encoding="utf-8", timeout=20,
)

def test_bundled_traceback_matches_the_primary_error(self):
project = REPO_ROOT / "examples/demo-buggy-project"
fixture = project / "traceback.txt"
self.assertTrue(fixture.is_file(), "The first-run fixture must ship with the repository")
locations = ccs.extract_error_locations_from_text(project, fixture.read_text(encoding="utf-8"))
primary = ccs.get_primary_error_location(locations)
self.assertEqual(primary["relative_path"], "app.py")
self.assertEqual(primary["line"], 2)
self.assertEqual((project / "app.py").read_text(encoding="utf-8").splitlines()[1].strip(),
"return username.strip().lower()")

def test_documented_error_demo_runs_from_a_clean_checkout(self):
project = REPO_ROOT / "examples/demo-buggy-project"
before = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in project.iterdir() if p.is_file()}
for document in ["README.md", "README.zh-CN.md", "examples/demo-buggy-project/README.md"]:
with self.subTest(document=document):
content = (REPO_ROOT / document).read_text(encoding="utf-8")
commands = [line.strip() for block in re.findall(r"```bash\n(.*?)```", content, flags=re.S)
for line in block.splitlines()
if line.startswith("python setup_local_ai.py --fix-error") and "--dry-run" in line]
self.assertTrue(commands, "The documented dry-run command must exist")
for command in commands:
result = self.run_cli(shlex.split(command)[2:])
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("app.py:2", result.stdout)
self.assertIn("username", result.stdout)
self.assertIn("Dry-run", result.stdout)
after = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in project.iterdir() if p.is_file()}
self.assertEqual(after, before, "Dry-run must not create reports or modify demo source")

def test_todo_preview_runs_without_a_local_model(self):
result = self.run_cli(["--complete-todo", "--project", "examples/demo-todo-project",
"--files", "app.py", "--context-lite", "--dry-run"])
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("is_valid_username", result.stdout)
self.assertIn("ASCII", result.stdout)
self.assertIn("Dry-run", result.stdout)

def test_cli_help_points_to_the_versioned_traceback(self):
result = self.run_cli(["--help"])
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
self.assertIn("examples/demo-buggy-project/traceback.txt", result.stdout)
self.assertNotIn("examples/demo-buggy-project/error.log", result.stdout)


if __name__ == "__main__":
unittest.main()
Loading