proofline-evidence 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- proofline_evidence-2.0.0/.gitignore +173 -0
- proofline_evidence-2.0.0/LICENSE +21 -0
- proofline_evidence-2.0.0/PKG-INFO +103 -0
- proofline_evidence-2.0.0/README.md +69 -0
- proofline_evidence-2.0.0/apps/api/proofline/__init__.py +3 -0
- proofline_evidence-2.0.0/apps/api/proofline/anchors.py +217 -0
- proofline_evidence-2.0.0/apps/api/proofline/api.py +1977 -0
- proofline_evidence-2.0.0/apps/api/proofline/attestations.py +525 -0
- proofline_evidence-2.0.0/apps/api/proofline/backup.py +211 -0
- proofline_evidence-2.0.0/apps/api/proofline/cli.py +873 -0
- proofline_evidence-2.0.0/apps/api/proofline/config.py +152 -0
- proofline_evidence-2.0.0/apps/api/proofline/data/architecture-decision.md +31 -0
- proofline_evidence-2.0.0/apps/api/proofline/database.py +62 -0
- proofline_evidence-2.0.0/apps/api/proofline/decision_health.py +179 -0
- proofline_evidence-2.0.0/apps/api/proofline/decision_impact_api.py +79 -0
- proofline_evidence-2.0.0/apps/api/proofline/decision_impacts.py +201 -0
- proofline_evidence-2.0.0/apps/api/proofline/decision_policy.py +98 -0
- proofline_evidence-2.0.0/apps/api/proofline/decision_review_api.py +348 -0
- proofline_evidence-2.0.0/apps/api/proofline/decision_reviews.py +559 -0
- proofline_evidence-2.0.0/apps/api/proofline/embeddings.py +357 -0
- proofline_evidence-2.0.0/apps/api/proofline/evaluation.py +935 -0
- proofline_evidence-2.0.0/apps/api/proofline/evidence_packages.py +959 -0
- proofline_evidence-2.0.0/apps/api/proofline/extraction.py +232 -0
- proofline_evidence-2.0.0/apps/api/proofline/folder_scanning.py +477 -0
- proofline_evidence-2.0.0/apps/api/proofline/folder_watching.py +162 -0
- proofline_evidence-2.0.0/apps/api/proofline/git_ingestion.py +138 -0
- proofline_evidence-2.0.0/apps/api/proofline/grounding.py +326 -0
- proofline_evidence-2.0.0/apps/api/proofline/ingestion.py +833 -0
- proofline_evidence-2.0.0/apps/api/proofline/integrity.py +583 -0
- proofline_evidence-2.0.0/apps/api/proofline/learning.py +50 -0
- proofline_evidence-2.0.0/apps/api/proofline/main.py +84 -0
- proofline_evidence-2.0.0/apps/api/proofline/migrations.py +882 -0
- proofline_evidence-2.0.0/apps/api/proofline/model_gateway.py +578 -0
- proofline_evidence-2.0.0/apps/api/proofline/models.py +515 -0
- proofline_evidence-2.0.0/apps/api/proofline/notes.py +44 -0
- proofline_evidence-2.0.0/apps/api/proofline/pilot_simulation.py +310 -0
- proofline_evidence-2.0.0/apps/api/proofline/portability.py +1766 -0
- proofline_evidence-2.0.0/apps/api/proofline/portable_import.py +666 -0
- proofline_evidence-2.0.0/apps/api/proofline/provenance_benchmark.py +347 -0
- proofline_evidence-2.0.0/apps/api/proofline/provenance_conformance.py +161 -0
- proofline_evidence-2.0.0/apps/api/proofline/real_model_evaluation.py +725 -0
- proofline_evidence-2.0.0/apps/api/proofline/reranking.py +119 -0
- proofline_evidence-2.0.0/apps/api/proofline/retrieval.py +119 -0
- proofline_evidence-2.0.0/apps/api/proofline/review_receipts.py +201 -0
- proofline_evidence-2.0.0/apps/api/proofline/runtime.py +66 -0
- proofline_evidence-2.0.0/apps/api/proofline/sarif.py +162 -0
- proofline_evidence-2.0.0/apps/api/proofline/schemas.py +812 -0
- proofline_evidence-2.0.0/apps/api/proofline/secret_store.py +70 -0
- proofline_evidence-2.0.0/apps/api/proofline/server.py +150 -0
- proofline_evidence-2.0.0/apps/api/proofline/stale_decision_demo.py +219 -0
- proofline_evidence-2.0.0/apps/api/proofline/studio.py +208 -0
- proofline_evidence-2.0.0/apps/api/proofline/studio_exports.py +208 -0
- proofline_evidence-2.0.0/apps/api/proofline/web/assets/index-B6UyXvhp.js +205 -0
- proofline_evidence-2.0.0/apps/api/proofline/web/assets/index-Cq2uTymx.css +1 -0
- proofline_evidence-2.0.0/apps/api/proofline/web/index.html +15 -0
- proofline_evidence-2.0.0/pyproject.toml +96 -0
- proofline_evidence-2.0.0/spec/decision-evidence-package/README.md +17 -0
- proofline_evidence-2.0.0/spec/decision-evidence-package/VERSIONING.md +19 -0
- proofline_evidence-2.0.0/spec/decision-evidence-package/v1/schema.json +191 -0
- proofline_evidence-2.0.0/spec/decision-evidence-package/v1/test-vectors/README.md +16 -0
- proofline_evidence-2.0.0/spec/decision-evidence-package/v1/test-vectors/expected.json +1 -0
- proofline_evidence-2.0.0/spec/decision-evidence-package/v1/test-vectors/mutations.json +26 -0
- proofline_evidence-2.0.0/spec/decision-evidence-package/v1/test-vectors/valid-minimal.json +1 -0
- proofline_evidence-2.0.0/spec/decision-review-receipt/v1/schema.json +53 -0
- proofline_evidence-2.0.0/spec/decision-review-receipt/v1/test-vectors/expected.json +7 -0
- proofline_evidence-2.0.0/spec/decision-review-receipt/v1/test-vectors/mutations.json +12 -0
- proofline_evidence-2.0.0/spec/decision-review-receipt/v1/test-vectors/valid-minimal.json +21 -0
- proofline_evidence-2.0.0/spec/signed-attestation/v1/schema.json +55 -0
- proofline_evidence-2.0.0/spec/signed-attestation/v1/test-vectors/valid-ed25519.json +21 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Canonical ignore rules for repositories owned by thangldw.
|
|
2
|
+
# Keep this file identical across repositories.
|
|
3
|
+
|
|
4
|
+
# Operating systems and file managers
|
|
5
|
+
.DS_Store
|
|
6
|
+
.AppleDouble
|
|
7
|
+
.LSOverride
|
|
8
|
+
._*
|
|
9
|
+
.DocumentRevisions-V100
|
|
10
|
+
.Spotlight-V100
|
|
11
|
+
.TemporaryItems
|
|
12
|
+
.Trashes
|
|
13
|
+
.VolumeIcon.icns
|
|
14
|
+
.fseventsd
|
|
15
|
+
.localized
|
|
16
|
+
__MACOSX/
|
|
17
|
+
Thumbs.db
|
|
18
|
+
Thumbs.db:encryptable
|
|
19
|
+
ehthumbs.db
|
|
20
|
+
ehthumbs_vista.db
|
|
21
|
+
[Dd]esktop.ini
|
|
22
|
+
$RECYCLE.BIN/
|
|
23
|
+
*.lnk
|
|
24
|
+
|
|
25
|
+
# Editors and local assistants
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
*.code-workspace
|
|
29
|
+
*.sublime-project
|
|
30
|
+
*.sublime-workspace
|
|
31
|
+
*.swp
|
|
32
|
+
*.swo
|
|
33
|
+
*~
|
|
34
|
+
.history/
|
|
35
|
+
.claude/
|
|
36
|
+
.codex/
|
|
37
|
+
.worktrees/
|
|
38
|
+
|
|
39
|
+
# Secrets and local configuration
|
|
40
|
+
.env
|
|
41
|
+
.env.*
|
|
42
|
+
!.env.example
|
|
43
|
+
!.env.*.example
|
|
44
|
+
*.local
|
|
45
|
+
*.secret
|
|
46
|
+
*.secrets
|
|
47
|
+
*.key
|
|
48
|
+
*.pem
|
|
49
|
+
*.p12
|
|
50
|
+
*.pfx
|
|
51
|
+
credentials.json
|
|
52
|
+
service-account*.json
|
|
53
|
+
|
|
54
|
+
# Logs, temporary files, backups and local databases
|
|
55
|
+
*.log
|
|
56
|
+
*.tmp
|
|
57
|
+
*.temp
|
|
58
|
+
*.bak
|
|
59
|
+
*.backup
|
|
60
|
+
*.orig
|
|
61
|
+
*.rej
|
|
62
|
+
*.stackdump
|
|
63
|
+
tmp/
|
|
64
|
+
temp/
|
|
65
|
+
logs/
|
|
66
|
+
backups/
|
|
67
|
+
*.db
|
|
68
|
+
*.db-journal
|
|
69
|
+
*.db-shm
|
|
70
|
+
*.db-wal
|
|
71
|
+
*.sqlite
|
|
72
|
+
*.sqlite3
|
|
73
|
+
|
|
74
|
+
# JavaScript and TypeScript
|
|
75
|
+
node_modules/
|
|
76
|
+
.npm/
|
|
77
|
+
.npm-cache/
|
|
78
|
+
.yarn/
|
|
79
|
+
.pnpm-store/
|
|
80
|
+
.vite/
|
|
81
|
+
.next/
|
|
82
|
+
.nuxt/
|
|
83
|
+
.svelte-kit/
|
|
84
|
+
dist/
|
|
85
|
+
build/
|
|
86
|
+
generated/
|
|
87
|
+
coverage/
|
|
88
|
+
*.tsbuildinfo
|
|
89
|
+
playwright-report/
|
|
90
|
+
test-results/
|
|
91
|
+
blob-report/
|
|
92
|
+
src/generated/
|
|
93
|
+
supabase/.temp/
|
|
94
|
+
|
|
95
|
+
# Python
|
|
96
|
+
__pycache__/
|
|
97
|
+
*.py[cod]
|
|
98
|
+
*$py.class
|
|
99
|
+
.venv/
|
|
100
|
+
.venv*/
|
|
101
|
+
venv/
|
|
102
|
+
env/
|
|
103
|
+
ENV/
|
|
104
|
+
.python-version
|
|
105
|
+
.pytest_cache/
|
|
106
|
+
.ruff_cache/
|
|
107
|
+
.mypy_cache/
|
|
108
|
+
.pyre/
|
|
109
|
+
.pytype/
|
|
110
|
+
.hypothesis/
|
|
111
|
+
.tox/
|
|
112
|
+
.nox/
|
|
113
|
+
.coverage
|
|
114
|
+
.coverage.*
|
|
115
|
+
htmlcov/
|
|
116
|
+
*.egg-info/
|
|
117
|
+
.eggs/
|
|
118
|
+
wheels/
|
|
119
|
+
pip-wheel-metadata/
|
|
120
|
+
reports/
|
|
121
|
+
.ipynb_checkpoints/
|
|
122
|
+
**/.ipynb_checkpoints/
|
|
123
|
+
|
|
124
|
+
# Rust, Go, Java and .NET
|
|
125
|
+
**/target/
|
|
126
|
+
**/gen/schemas/
|
|
127
|
+
vendor/
|
|
128
|
+
bin/
|
|
129
|
+
obj/
|
|
130
|
+
*.class
|
|
131
|
+
*.jar
|
|
132
|
+
*.war
|
|
133
|
+
*.nupkg
|
|
134
|
+
|
|
135
|
+
# Swift and Apple build artifacts
|
|
136
|
+
**/.build/
|
|
137
|
+
DerivedData/
|
|
138
|
+
*.xcuserstate
|
|
139
|
+
*.xcworkspace/xcuserdata/
|
|
140
|
+
*.xcodeproj/xcuserdata/
|
|
141
|
+
*.app/
|
|
142
|
+
*.dmg
|
|
143
|
+
**/release/
|
|
144
|
+
|
|
145
|
+
# Windows installers and packaged artifacts
|
|
146
|
+
*.cab
|
|
147
|
+
*.msi
|
|
148
|
+
*.msix
|
|
149
|
+
*.msm
|
|
150
|
+
*.msp
|
|
151
|
+
*.exe
|
|
152
|
+
*.zip
|
|
153
|
+
release-artifacts/
|
|
154
|
+
artifacts/
|
|
155
|
+
|
|
156
|
+
# QA and generated visual output
|
|
157
|
+
qa/
|
|
158
|
+
qa-*.png
|
|
159
|
+
qa-*.jpg
|
|
160
|
+
qa-*.jpeg
|
|
161
|
+
audit-*.png
|
|
162
|
+
prototype-*.png
|
|
163
|
+
gameplay-*.png
|
|
164
|
+
design-qa.md
|
|
165
|
+
|
|
166
|
+
# Project-specific generated resources
|
|
167
|
+
public/ocr/paddleocr/
|
|
168
|
+
src-tauri/generated-resources/ocr/*
|
|
169
|
+
!src-tauri/generated-resources/ocr/.gitkeep
|
|
170
|
+
analysis/outputs/
|
|
171
|
+
analysis/*-artifact.json
|
|
172
|
+
analysis/*.html
|
|
173
|
+
NamiQuant-Claude-Design.html
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: proofline-evidence
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Evidence-first engineering decision memory
|
|
5
|
+
Project-URL: Homepage, https://github.com/thangldw/proofline
|
|
6
|
+
Project-URL: Repository, https://github.com/thangldw/proofline.git
|
|
7
|
+
Project-URL: Issues, https://github.com/thangldw/proofline/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/thangldw/proofline/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Thang Luu
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: engineering,knowledge,local-first,provenance,retrieval
|
|
13
|
+
Requires-Python: >=3.11
|
|
14
|
+
Requires-Dist: cryptography<51,>=50
|
|
15
|
+
Requires-Dist: fastapi<1,>=0.115
|
|
16
|
+
Requires-Dist: httpx<1,>=0.27
|
|
17
|
+
Requires-Dist: keyring<26,>=25
|
|
18
|
+
Requires-Dist: pillow<13,>=12
|
|
19
|
+
Requires-Dist: pydantic<3,>=2.9
|
|
20
|
+
Requires-Dist: python-pptx<2,>=1.0
|
|
21
|
+
Requires-Dist: sqlalchemy<3,>=2.0
|
|
22
|
+
Requires-Dist: uvicorn[standard]<1,>=0.32
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build<2,>=1.2; extra == 'dev'
|
|
25
|
+
Requires-Dist: hypothesis<7,>=6; extra == 'dev'
|
|
26
|
+
Requires-Dist: jsonschema<5,>=4.23; extra == 'dev'
|
|
27
|
+
Requires-Dist: pip-audit<3,>=2.10.1; extra == 'dev'
|
|
28
|
+
Requires-Dist: pip<27,>=26.2; extra == 'dev'
|
|
29
|
+
Requires-Dist: pyinstaller<7,>=6.21; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest<10,>=9.1.1; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff<1,>=0.8; extra == 'dev'
|
|
32
|
+
Requires-Dist: twine<8,>=7; extra == 'dev'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# Proofline
|
|
36
|
+
|
|
37
|
+
[English](#english) · [Tiếng Việt](#tiếng-việt) · [日本語](#日本語)
|
|
38
|
+
|
|
39
|
+
Proofline shows which immutable evidence justified an engineering decision, traces explicit downstream impact when that evidence changes, and signs portable evidence attestations.
|
|
40
|
+
|
|
41
|
+
```mermaid
|
|
42
|
+
%%{init: {"theme":"base","themeVariables":{"background":"#FFFFFF","fontFamily":"Arial, sans-serif","lineColor":"#667085","primaryTextColor":"#172B4D"}}}%%
|
|
43
|
+
flowchart LR
|
|
44
|
+
S["Source version<br/>Nguồn / ソース"]:::yellow
|
|
45
|
+
P["Exact span<br/>Dòng trích / 引用範囲"]:::blue
|
|
46
|
+
D["Decision<br/>Quyết định / 判断"]:::purple
|
|
47
|
+
C["Deterministic check<br/>Kiểm tra / 検証"]:::green
|
|
48
|
+
R["Review warning<br/>Cảnh báo / 警告"]:::pink
|
|
49
|
+
S --> P --> D --> C
|
|
50
|
+
C -->|changed| R
|
|
51
|
+
classDef yellow fill:#FFF4A3,stroke:#C9A227,stroke-width:2px,color:#172B4D
|
|
52
|
+
classDef blue fill:#D9EAFD,stroke:#4C78A8,stroke-width:2px,color:#172B4D
|
|
53
|
+
classDef purple fill:#E9DDF7,stroke:#8064A2,stroke-width:2px,color:#172B4D
|
|
54
|
+
classDef green fill:#DDF5E3,stroke:#4F9D69,stroke-width:2px,color:#172B4D
|
|
55
|
+
classDef pink fill:#FFE1E6,stroke:#C96A7B,stroke-width:2px,color:#172B4D
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## English
|
|
59
|
+
|
|
60
|
+
Proofline is a local-first Engineering Decision Memory for evidence-backed ADRs. It preserves source identity, source version and exact cited spans, exports self-contained Decision Evidence Packages, and performs deterministic stale-decision checks without requiring an AI provider.
|
|
61
|
+
|
|
62
|
+
Decision status and evidence health are separate: an approved ADR remains historical fact while a stale citation is shown as **Accepted · review required**. Proofline never silently rewrites an accepted decision.
|
|
63
|
+
|
|
64
|
+
Explicit `based_on` and `implements` relations produce cycle-safe transitive impact paths. Ed25519 attestations bind a verified package and optional review receipt to a trusted public key; signing and verification remain local and AI-provider independent.
|
|
65
|
+
|
|
66
|
+
Requirements: Python 3.11+, Node.js 20+ and npm.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
python -m pip install proofline-evidence
|
|
70
|
+
proofline demo stale-decision
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
For repository development:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
make setup
|
|
77
|
+
.venv/bin/proofline demo stale-decision
|
|
78
|
+
.venv/bin/proofline verify-package proofline-demo-stale-decision/evidence.zip
|
|
79
|
+
.venv/bin/proofline verify-review-receipt proofline-demo-stale-decision/decision-review.json
|
|
80
|
+
.venv/bin/proofline check-impacts --format sarif
|
|
81
|
+
make test
|
|
82
|
+
make check
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The current boundary is a single-user local workflow with recoverable data. Hosted sync, shared workspaces, organization identity, trusted timestamps, key revocation and permission-aware connector fleets are not implemented.
|
|
86
|
+
|
|
87
|
+
Current technical references: [architecture](docs/architecture.md), [evidence package formats](docs/evidence-packages.md), [operations](docs/OPERATIONS.md), [security](SECURITY.md) and [v2.0.0 release notes](docs/releases/v2.0.0.md).
|
|
88
|
+
|
|
89
|
+
The repository is also packaged as a local plugin for ChatGPT, Codex, Claude Code and Cowork. Proofline is listed in the public [OpenAI Plugins Directory](https://chatgpt.com/plugins/plugins_6a6efdf2ccbc81919ebb4cb01805ebaa); the exact submitted/public version is tracked in the [directory submission package](docs/submission/DIRECTORY_SUBMISSION.md). It does not claim hosted sync or a remote MCP connector. See the [privacy policy](PRIVACY.md), [terms](TERMS.md), and [support guidance](SUPPORT.md).
|
|
90
|
+
|
|
91
|
+
## Tiếng Việt
|
|
92
|
+
|
|
93
|
+
Proofline là bộ nhớ quyết định kỹ thuật local-first dành cho ADR có bằng chứng. Hệ thống giữ định danh nguồn, phiên bản nguồn và đúng đoạn trích; phát hiện transitive impact qua quan hệ explicit; xuất package độc lập và ký attestation Ed25519 mà không phụ thuộc nhà cung cấp AI.
|
|
94
|
+
|
|
95
|
+
Yêu cầu: Python 3.11+, Node.js 20+ và npm. Dùng các lệnh ở phần English để cài đặt, chạy demo và kiểm thử. Phạm vi hiện tại là quy trình local cho một người dùng; chưa có đồng bộ hosted, workspace dùng chung, định danh tổ chức, trusted timestamp hoặc hệ connector phân quyền.
|
|
96
|
+
|
|
97
|
+
## 日本語
|
|
98
|
+
|
|
99
|
+
Proofline は、根拠付き ADR のためのローカルファーストな Engineering Decision Memory です。正確な引用範囲を保持し、明示的な関係による推移的影響を追跡し、AI プロバイダーなしで Ed25519 attestation を署名・検証します。
|
|
100
|
+
|
|
101
|
+
必要環境は Python 3.11 以上、Node.js 20 以上、npm です。現在は単一ユーザーのローカル利用が対象で、ホスト同期、共有ワークスペース、組織 identity、信頼時刻、権限対応コネクター群は未実装です。
|
|
102
|
+
|
|
103
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Proofline
|
|
2
|
+
|
|
3
|
+
[English](#english) · [Tiếng Việt](#tiếng-việt) · [日本語](#日本語)
|
|
4
|
+
|
|
5
|
+
Proofline shows which immutable evidence justified an engineering decision, traces explicit downstream impact when that evidence changes, and signs portable evidence attestations.
|
|
6
|
+
|
|
7
|
+
```mermaid
|
|
8
|
+
%%{init: {"theme":"base","themeVariables":{"background":"#FFFFFF","fontFamily":"Arial, sans-serif","lineColor":"#667085","primaryTextColor":"#172B4D"}}}%%
|
|
9
|
+
flowchart LR
|
|
10
|
+
S["Source version<br/>Nguồn / ソース"]:::yellow
|
|
11
|
+
P["Exact span<br/>Dòng trích / 引用範囲"]:::blue
|
|
12
|
+
D["Decision<br/>Quyết định / 判断"]:::purple
|
|
13
|
+
C["Deterministic check<br/>Kiểm tra / 検証"]:::green
|
|
14
|
+
R["Review warning<br/>Cảnh báo / 警告"]:::pink
|
|
15
|
+
S --> P --> D --> C
|
|
16
|
+
C -->|changed| R
|
|
17
|
+
classDef yellow fill:#FFF4A3,stroke:#C9A227,stroke-width:2px,color:#172B4D
|
|
18
|
+
classDef blue fill:#D9EAFD,stroke:#4C78A8,stroke-width:2px,color:#172B4D
|
|
19
|
+
classDef purple fill:#E9DDF7,stroke:#8064A2,stroke-width:2px,color:#172B4D
|
|
20
|
+
classDef green fill:#DDF5E3,stroke:#4F9D69,stroke-width:2px,color:#172B4D
|
|
21
|
+
classDef pink fill:#FFE1E6,stroke:#C96A7B,stroke-width:2px,color:#172B4D
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## English
|
|
25
|
+
|
|
26
|
+
Proofline is a local-first Engineering Decision Memory for evidence-backed ADRs. It preserves source identity, source version and exact cited spans, exports self-contained Decision Evidence Packages, and performs deterministic stale-decision checks without requiring an AI provider.
|
|
27
|
+
|
|
28
|
+
Decision status and evidence health are separate: an approved ADR remains historical fact while a stale citation is shown as **Accepted · review required**. Proofline never silently rewrites an accepted decision.
|
|
29
|
+
|
|
30
|
+
Explicit `based_on` and `implements` relations produce cycle-safe transitive impact paths. Ed25519 attestations bind a verified package and optional review receipt to a trusted public key; signing and verification remain local and AI-provider independent.
|
|
31
|
+
|
|
32
|
+
Requirements: Python 3.11+, Node.js 20+ and npm.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
python -m pip install proofline-evidence
|
|
36
|
+
proofline demo stale-decision
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For repository development:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
make setup
|
|
43
|
+
.venv/bin/proofline demo stale-decision
|
|
44
|
+
.venv/bin/proofline verify-package proofline-demo-stale-decision/evidence.zip
|
|
45
|
+
.venv/bin/proofline verify-review-receipt proofline-demo-stale-decision/decision-review.json
|
|
46
|
+
.venv/bin/proofline check-impacts --format sarif
|
|
47
|
+
make test
|
|
48
|
+
make check
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The current boundary is a single-user local workflow with recoverable data. Hosted sync, shared workspaces, organization identity, trusted timestamps, key revocation and permission-aware connector fleets are not implemented.
|
|
52
|
+
|
|
53
|
+
Current technical references: [architecture](docs/architecture.md), [evidence package formats](docs/evidence-packages.md), [operations](docs/OPERATIONS.md), [security](SECURITY.md) and [v2.0.0 release notes](docs/releases/v2.0.0.md).
|
|
54
|
+
|
|
55
|
+
The repository is also packaged as a local plugin for ChatGPT, Codex, Claude Code and Cowork. Proofline is listed in the public [OpenAI Plugins Directory](https://chatgpt.com/plugins/plugins_6a6efdf2ccbc81919ebb4cb01805ebaa); the exact submitted/public version is tracked in the [directory submission package](docs/submission/DIRECTORY_SUBMISSION.md). It does not claim hosted sync or a remote MCP connector. See the [privacy policy](PRIVACY.md), [terms](TERMS.md), and [support guidance](SUPPORT.md).
|
|
56
|
+
|
|
57
|
+
## Tiếng Việt
|
|
58
|
+
|
|
59
|
+
Proofline là bộ nhớ quyết định kỹ thuật local-first dành cho ADR có bằng chứng. Hệ thống giữ định danh nguồn, phiên bản nguồn và đúng đoạn trích; phát hiện transitive impact qua quan hệ explicit; xuất package độc lập và ký attestation Ed25519 mà không phụ thuộc nhà cung cấp AI.
|
|
60
|
+
|
|
61
|
+
Yêu cầu: Python 3.11+, Node.js 20+ và npm. Dùng các lệnh ở phần English để cài đặt, chạy demo và kiểm thử. Phạm vi hiện tại là quy trình local cho một người dùng; chưa có đồng bộ hosted, workspace dùng chung, định danh tổ chức, trusted timestamp hoặc hệ connector phân quyền.
|
|
62
|
+
|
|
63
|
+
## 日本語
|
|
64
|
+
|
|
65
|
+
Proofline は、根拠付き ADR のためのローカルファーストな Engineering Decision Memory です。正確な引用範囲を保持し、明示的な関係による推移的影響を追跡し、AI プロバイダーなしで Ed25519 attestation を署名・検証します。
|
|
66
|
+
|
|
67
|
+
必要環境は Python 3.11 以上、Node.js 20 以上、npm です。現在は単一ユーザーのローカル利用が対象で、ホスト同期、共有ワークスペース、組織 identity、信頼時刻、権限対応コネクター群は未実装です。
|
|
68
|
+
|
|
69
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
import unicodedata
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from difflib import SequenceMatcher
|
|
8
|
+
from typing import Literal
|
|
9
|
+
|
|
10
|
+
ANCHOR_VERSION = "markdown-context-v1"
|
|
11
|
+
MIN_CHANGED_SIMILARITY = 0.60
|
|
12
|
+
EMPTY_SHA256 = hashlib.sha256(b"").hexdigest()
|
|
13
|
+
|
|
14
|
+
AnchorState = Literal["current", "unchanged", "moved", "ambiguous", "changed", "deleted"]
|
|
15
|
+
|
|
16
|
+
_HEADING = re.compile(r"^\s{0,3}(#{1,6})[ \t]+(.+?)\s*#*\s*$")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class EvidenceAnchor:
|
|
21
|
+
version: str
|
|
22
|
+
section_path: tuple[str, ...]
|
|
23
|
+
prefix_sha256: str
|
|
24
|
+
suffix_sha256: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class AnchorCandidate:
|
|
29
|
+
start_offset: int
|
|
30
|
+
end_offset: int
|
|
31
|
+
start_line: int
|
|
32
|
+
end_line: int
|
|
33
|
+
section_path: tuple[str, ...]
|
|
34
|
+
similarity: float
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class AnchorResolution:
|
|
39
|
+
state: AnchorState
|
|
40
|
+
candidates: tuple[AnchorCandidate, ...]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class _Line:
|
|
45
|
+
text: str
|
|
46
|
+
start: int
|
|
47
|
+
end: int
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _lines(content: str) -> list[_Line]:
|
|
51
|
+
records: list[_Line] = []
|
|
52
|
+
cursor = 0
|
|
53
|
+
for raw in content.splitlines(keepends=True):
|
|
54
|
+
text = raw.rstrip("\r\n")
|
|
55
|
+
records.append(_Line(text=text, start=cursor, end=cursor + len(text)))
|
|
56
|
+
cursor += len(raw)
|
|
57
|
+
if not records or cursor < len(content):
|
|
58
|
+
records.append(_Line(text=content[cursor:], start=cursor, end=len(content)))
|
|
59
|
+
return records
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _normalize(value: str) -> str:
|
|
63
|
+
return " ".join(unicodedata.normalize("NFC", value).strip().split())
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _hash_lines(values: list[str]) -> str:
|
|
67
|
+
return hashlib.sha256("\n".join(values).encode("utf-8")).hexdigest()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _section_paths(lines: list[_Line]) -> list[tuple[str, ...]]:
|
|
71
|
+
path: list[str] = []
|
|
72
|
+
result: list[tuple[str, ...]] = []
|
|
73
|
+
for line in lines:
|
|
74
|
+
match = _HEADING.match(line.text)
|
|
75
|
+
if match:
|
|
76
|
+
level = len(match.group(1))
|
|
77
|
+
heading = _normalize(match.group(2))
|
|
78
|
+
path = path[: level - 1]
|
|
79
|
+
path.append(heading)
|
|
80
|
+
result.append(tuple(path))
|
|
81
|
+
return result
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _line_index(lines: list[_Line], offset: int) -> int:
|
|
85
|
+
for index, line in enumerate(lines):
|
|
86
|
+
if line.start <= offset <= line.end:
|
|
87
|
+
return index
|
|
88
|
+
return len(lines) - 1
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def build_evidence_anchor(content: str, start_offset: int, end_offset: int) -> EvidenceAnchor:
|
|
92
|
+
if not 0 <= start_offset < end_offset <= len(content):
|
|
93
|
+
raise ValueError("anchor_span_invalid")
|
|
94
|
+
lines = _lines(content)
|
|
95
|
+
start_index = _line_index(lines, start_offset)
|
|
96
|
+
end_index = _line_index(lines, end_offset - 1)
|
|
97
|
+
paths = _section_paths(lines)
|
|
98
|
+
|
|
99
|
+
prefix: list[str] = []
|
|
100
|
+
for line in reversed(lines[:start_index]):
|
|
101
|
+
normalized = _normalize(line.text)
|
|
102
|
+
if normalized:
|
|
103
|
+
prefix.append(normalized)
|
|
104
|
+
if len(prefix) == 2:
|
|
105
|
+
break
|
|
106
|
+
prefix.reverse()
|
|
107
|
+
|
|
108
|
+
suffix: list[str] = []
|
|
109
|
+
for line in lines[end_index + 1 :]:
|
|
110
|
+
normalized = _normalize(line.text)
|
|
111
|
+
if normalized:
|
|
112
|
+
suffix.append(normalized)
|
|
113
|
+
if len(suffix) == 2:
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
return EvidenceAnchor(
|
|
117
|
+
version=ANCHOR_VERSION,
|
|
118
|
+
section_path=paths[start_index],
|
|
119
|
+
prefix_sha256=_hash_lines(prefix),
|
|
120
|
+
suffix_sha256=_hash_lines(suffix),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _candidate(content: str, start: int, end: int, similarity: float) -> AnchorCandidate:
|
|
125
|
+
anchor = build_evidence_anchor(content, start, end)
|
|
126
|
+
return AnchorCandidate(
|
|
127
|
+
start_offset=start,
|
|
128
|
+
end_offset=end,
|
|
129
|
+
start_line=content.count("\n", 0, start) + 1,
|
|
130
|
+
end_line=content.count("\n", 0, end - 1) + 1,
|
|
131
|
+
section_path=anchor.section_path,
|
|
132
|
+
similarity=similarity,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _exact_occurrences(content: str, quote: str) -> list[int]:
|
|
137
|
+
offsets: list[int] = []
|
|
138
|
+
cursor = 0
|
|
139
|
+
while True:
|
|
140
|
+
found = content.find(quote, cursor)
|
|
141
|
+
if found < 0:
|
|
142
|
+
return offsets
|
|
143
|
+
offsets.append(found)
|
|
144
|
+
cursor = found + 1
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _context_matches(cited: EvidenceAnchor, current: EvidenceAnchor) -> bool:
|
|
148
|
+
return (
|
|
149
|
+
cited.section_path == current.section_path
|
|
150
|
+
and (cited.prefix_sha256 == EMPTY_SHA256 or cited.prefix_sha256 == current.prefix_sha256)
|
|
151
|
+
and (cited.suffix_sha256 == EMPTY_SHA256 or cited.suffix_sha256 == current.suffix_sha256)
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _changed_candidates(
|
|
156
|
+
*, quote: str, cited_anchor: EvidenceAnchor, current_content: str
|
|
157
|
+
) -> tuple[AnchorCandidate, ...]:
|
|
158
|
+
lines = _lines(current_content)
|
|
159
|
+
paths = _section_paths(lines)
|
|
160
|
+
quote_line_count = max(1, len(quote.splitlines()))
|
|
161
|
+
normalized_quote = _normalize(quote)
|
|
162
|
+
candidates: list[AnchorCandidate] = []
|
|
163
|
+
terminal_heading = cited_anchor.section_path[-1:] or ()
|
|
164
|
+
|
|
165
|
+
for index in range(0, len(lines) - quote_line_count + 1):
|
|
166
|
+
window = lines[index : index + quote_line_count]
|
|
167
|
+
if terminal_heading and paths[index][-1:] != terminal_heading:
|
|
168
|
+
continue
|
|
169
|
+
start = window[0].start
|
|
170
|
+
end = window[-1].end
|
|
171
|
+
if end <= start:
|
|
172
|
+
continue
|
|
173
|
+
similarity = SequenceMatcher(
|
|
174
|
+
None,
|
|
175
|
+
normalized_quote,
|
|
176
|
+
_normalize(current_content[start:end]),
|
|
177
|
+
autojunk=False,
|
|
178
|
+
).ratio()
|
|
179
|
+
if similarity < MIN_CHANGED_SIMILARITY:
|
|
180
|
+
continue
|
|
181
|
+
candidates.append(_candidate(current_content, start, end, similarity))
|
|
182
|
+
|
|
183
|
+
return tuple(sorted(candidates, key=lambda item: (-item.similarity, item.start_offset)))
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def resolve_evidence_anchor(
|
|
187
|
+
*, quote: str, cited_anchor: EvidenceAnchor, current_content: str
|
|
188
|
+
) -> AnchorResolution:
|
|
189
|
+
if not quote:
|
|
190
|
+
raise ValueError("anchor_quote_invalid")
|
|
191
|
+
if cited_anchor.version != ANCHOR_VERSION:
|
|
192
|
+
raise ValueError("anchor_version_unsupported")
|
|
193
|
+
|
|
194
|
+
occurrences = _exact_occurrences(current_content, quote)
|
|
195
|
+
exact = tuple(
|
|
196
|
+
_candidate(current_content, start, start + len(quote), 1.0) for start in occurrences
|
|
197
|
+
)
|
|
198
|
+
if len(exact) > 1:
|
|
199
|
+
return AnchorResolution(state="ambiguous", candidates=exact)
|
|
200
|
+
if len(exact) == 1:
|
|
201
|
+
current_anchor = build_evidence_anchor(
|
|
202
|
+
current_content, exact[0].start_offset, exact[0].end_offset
|
|
203
|
+
)
|
|
204
|
+
state: AnchorState = (
|
|
205
|
+
"unchanged" if _context_matches(cited_anchor, current_anchor) else "moved"
|
|
206
|
+
)
|
|
207
|
+
return AnchorResolution(state=state, candidates=exact)
|
|
208
|
+
|
|
209
|
+
candidates = _changed_candidates(
|
|
210
|
+
quote=quote,
|
|
211
|
+
cited_anchor=cited_anchor,
|
|
212
|
+
current_content=current_content,
|
|
213
|
+
)
|
|
214
|
+
return AnchorResolution(
|
|
215
|
+
state="changed" if candidates else "deleted",
|
|
216
|
+
candidates=candidates,
|
|
217
|
+
)
|