grift-cli 0.5.2__tar.gz → 0.5.4__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.
Potentially problematic release.
This version of grift-cli might be problematic. Click here for more details.
- {grift_cli-0.5.2 → grift_cli-0.5.4}/CHANGELOG.md +24 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/PKG-INFO +6 -4
- {grift_cli-0.5.2 → grift_cli-0.5.4}/README.en.md +7 -3
- {grift_cli-0.5.2 → grift_cli-0.5.4}/README.md +5 -3
- {grift_cli-0.5.2 → grift_cli-0.5.4}/action.yml +1 -1
- grift_cli-0.5.4/docs/metrics-guide.md +149 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/pyproject.toml +1 -1
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/hygiene_grep.py +4 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_cli/__main__.py +76 -9
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/version.py +2 -2
- grift_cli-0.5.4/tests/test_default_report_path.py +134 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_hygiene.py +4 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/.github/workflows/ci.yml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/.github/workflows/kpi-monthly.yml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/.github/workflows/publish-testpypi.yml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/.github/workflows/publish.yml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/.gitignore +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/LICENSE +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/AD-PILOT-20260822.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/DISCRIMINANT-v2026.09.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/DISCRIMINANT-v2026.11.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/ad-pilot.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/manifest.toml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/pins-v2026.09.toml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/pins-v2026.11.toml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/v2026.09/distributions.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/corpus/v2026.11/distributions.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/docs/corpus-protocol.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/docs/coverage-map.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/docs/export-schema.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/docs/identity-schema.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/docs/norms.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/docs/persona-answers.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/docs/report-schema.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/README.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/coverage.md +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G1-click.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G10-scvi-tools.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G11-kilo.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G12-httpx.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G13-voicevox.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G2-gitignore.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G3-spoon-knife.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G4-express.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G5-typer.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G6-claude-code.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G7-axios.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G8-co.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/expected/G9-pyscript.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/identity/G1-click.toml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/identity/example.toml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/golden/pins.toml +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/admission_v2026_11.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/build_dist_v2026_11.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/build_distributions.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/check_forbidden_vocab.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/corpus_ad_pilot.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/corpus_run.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/discriminant_v2026.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/discriminant_v2026_11.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/gen_coverage_table.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/scripts/gen_golden_expected.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_cli/__init__.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/__init__.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/activity.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/analyze.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/cochange.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/context_profile.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/contribute.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/core_period.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/data/v2026.09/distributions.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/data/v2026.11/distributions.json +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/export.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/gitutil.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/identity.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/lineage.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/observation.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/origin.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/paths.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/reference.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/report.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/rework.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/schema.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/scope.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/survival.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/tests_observed.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/src/tep_core/verify.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/conftest.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/coverage_matrix.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/git_fixture.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_activity.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_cli.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_cochange.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_context_profile.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_contribute.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_coverage.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_discriminant_claims.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_export.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_export_recipe.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_forbidden_vocab.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_gitutil.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_golden.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_h5_swarm.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_identity_validation.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_lifecycle_v2.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_not_observed.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_origin.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_persona_answers.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_release_gate.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_report.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_rework.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_schema.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_scope.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_shared_block.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_survival.py +0 -0
- {grift_cli-0.5.2 → grift_cli-0.5.4}/tests/test_verify.py +0 -0
|
@@ -4,6 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
タグ・PyPI・リポ public 化は本ファイルの記載対象外(人間ゲート)。
|
|
6
6
|
|
|
7
|
+
## [0.5.4] — 2026-08-23
|
|
8
|
+
|
|
9
|
+
`grift report` の stale 出力問題を解消 + 表示の版残存を一掃。
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- **`grift report`(引数なし)は既存の `./out/report.json` があっても常に現在の HEAD を再分析**する(提唱者実測で検出: 旧仕様は既存 JSON を再表示し、2コミット目以降の SHA が古いまま残った — 自己証明の罹患として重大)。再分析なしの再レンダリングは引数指定時のみ
|
|
14
|
+
- README 日英: Action 使用例を `@v0.5.4` へ・**PyPI 欄を版に依存しない表記に変更**(「最新 0.5.2」等の古い版番号が残り続ける問題を構造的に解消)・`grift report` を「初回の簡単導線」として条件付きで説明(主 CTA は `grift analyze . --scope repo --out ./out` を維持)
|
|
15
|
+
- action.yml 既定ピンを 0.5.4 へ
|
|
16
|
+
|
|
17
|
+
## [0.5.3] — 2026-08-23
|
|
18
|
+
|
|
19
|
+
UX 改善(代表フィードバック)+ 公開 README の整合。
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **`grift report`(引数なし)= そのディレクトリを分析して `./out/report.{json,md}` を自動作成**(`out/` がなければ作る)。引数を渡した場合は従来どおり既存 JSON からの再レンダリング(再分析なし)
|
|
24
|
+
- `grift contribute`(引数なし)は `./out/report.json` → `.grift-out/report.json` → `./report.json` を自動探索。無ければ「先に `grift report` を」と案内
|
|
25
|
+
- GitHub Action の使用例を `@v0.5.2` ぼ更新・「本公開は別ゲート」注記を公開済み表示に更新(README 日英)
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- hygiene 除外リストに内部調査文書プレフィックス(SMOKE / REREVIEW / RELEASE-REHEARSAL / INTERNAL-PILOT)を追加
|
|
30
|
+
|
|
7
31
|
## [0.5.2] — 2026-08-23
|
|
8
32
|
|
|
9
33
|
基準の憲法(norms / reader's guide / verify)+ 文脈層(context_profile v2・lifecycle-v2)+ corpus v2026.11 + contribute。
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: grift-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Deterministic TEP evidence metrics from a single git repository. No LLM.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Cor-Incorporated/grift-cli
|
|
6
6
|
Author: TEP contributors
|
|
@@ -40,12 +40,14 @@ Named after the Grift product line.
|
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
pipx install grift-cli
|
|
43
|
-
grift analyze ./my-repo --scope repo
|
|
43
|
+
grift analyze ./my-repo --scope repo --out ./out
|
|
44
44
|
```
|
|
45
45
|
|
|
46
46
|
- `--scope tenant`(既定): `.tep/identity.toml` に載った人の仕事 = **証拠用**
|
|
47
47
|
- `--scope repo`: bot 以外の全人間コミット = **プロセス観測・参照分布用**
|
|
48
48
|
|
|
49
|
+
**初回の簡単導線**: リポジトリ内で `grift report` だけでも現在の HEAD を分析して `./out/report.md` と `report.json` を書き出します(既存の `./out/report.json` があっても常に再分析します)。**確実に指定条件で測りたいとき・CI では `grift analyze . --scope repo --out ./out` を使ってください**(`report` は repo スコープ固定の省略形です)。
|
|
50
|
+
|
|
49
51
|
参照分布 v2026.09 は **repo スコープ同士**でのみ照合する。tenant の値を repo 分布に載せない(混ぜたら分布が嘘になる)。
|
|
50
52
|
|
|
51
53
|
## 実レポート例
|
|
@@ -107,7 +109,7 @@ v2026.09 の記録(Run 2: A vs C `separated` ほか)は `corpus/DISCRIMINANT
|
|
|
107
109
|
## GitHub Action(観測のみ)
|
|
108
110
|
|
|
109
111
|
```yaml
|
|
110
|
-
uses: Cor-Incorporated/grift-cli@v0.5.
|
|
112
|
+
uses: Cor-Incorporated/grift-cli@v0.5.4
|
|
111
113
|
with:
|
|
112
114
|
scope: repo
|
|
113
115
|
```
|
|
@@ -128,7 +130,7 @@ with:
|
|
|
128
130
|
|
|
129
131
|
- 公開リポ: https://github.com/Cor-Incorporated/grift-cli
|
|
130
132
|
- コマンド名: **`grift`**(方法論名 TEP はレポートに残す)
|
|
131
|
-
- PyPI: 配布名 **`grift-cli
|
|
133
|
+
- PyPI: 配布名 **`grift-cli`**(公開済み: `pipx install grift-cli`・最新版は [PyPI](https://pypi.org/project/grift-cli/) を参照)
|
|
132
134
|
|
|
133
135
|
## License
|
|
134
136
|
|
|
@@ -17,10 +17,14 @@ Commit counts, line counts, and “activity” inflate easily once agents and ge
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
pipx install grift-cli
|
|
20
|
-
grift analyze ./my-repo --scope repo
|
|
20
|
+
grift analyze ./my-repo --scope repo --out ./out
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Quickest first run**: inside a repository, `grift report` alone analyzes the
|
|
24
|
+
current HEAD and writes `./out/report.md` and `report.json` (it always
|
|
25
|
+
re-analyzes, even if `./out/report.json` already exists). For explicit
|
|
26
|
+
control and CI, use `grift analyze . --scope repo --out ./out` (`report` is a
|
|
27
|
+
fixed repo-scope shorthand).
|
|
24
28
|
|
|
25
29
|
- `--scope tenant` (default): work matched in `.tep/identity.toml` = **evidence**
|
|
26
30
|
- `--scope repo`: all non-bot human commits = **process observation and reference distributions**
|
|
@@ -82,7 +86,7 @@ The v2026.09 record (Run 2: A vs C `separated` etc.) remains as history in `corp
|
|
|
82
86
|
## GitHub Action (observation only)
|
|
83
87
|
|
|
84
88
|
```yaml
|
|
85
|
-
uses: Cor-Incorporated/grift-cli@v0.5.
|
|
89
|
+
uses: Cor-Incorporated/grift-cli@v0.5.4
|
|
86
90
|
with:
|
|
87
91
|
scope: repo
|
|
88
92
|
```
|
|
@@ -17,12 +17,14 @@ Named after the Grift product line.
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
pipx install grift-cli
|
|
20
|
-
grift analyze ./my-repo --scope repo
|
|
20
|
+
grift analyze ./my-repo --scope repo --out ./out
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
- `--scope tenant`(既定): `.tep/identity.toml` に載った人の仕事 = **証拠用**
|
|
24
24
|
- `--scope repo`: bot 以外の全人間コミット = **プロセス観測・参照分布用**
|
|
25
25
|
|
|
26
|
+
**初回の簡単導線**: リポジトリ内で `grift report` だけでも現在の HEAD を分析して `./out/report.md` と `report.json` を書き出します(既存の `./out/report.json` があっても常に再分析します)。**確実に指定条件で測りたいとき・CI では `grift analyze . --scope repo --out ./out` を使ってください**(`report` は repo スコープ固定の省略形です)。
|
|
27
|
+
|
|
26
28
|
参照分布 v2026.09 は **repo スコープ同士**でのみ照合する。tenant の値を repo 分布に載せない(混ぜたら分布が嘘になる)。
|
|
27
29
|
|
|
28
30
|
## 実レポート例
|
|
@@ -84,7 +86,7 @@ v2026.09 の記録(Run 2: A vs C `separated` ほか)は `corpus/DISCRIMINANT
|
|
|
84
86
|
## GitHub Action(観測のみ)
|
|
85
87
|
|
|
86
88
|
```yaml
|
|
87
|
-
uses: Cor-Incorporated/grift-cli@v0.5.
|
|
89
|
+
uses: Cor-Incorporated/grift-cli@v0.5.4
|
|
88
90
|
with:
|
|
89
91
|
scope: repo
|
|
90
92
|
```
|
|
@@ -105,7 +107,7 @@ with:
|
|
|
105
107
|
|
|
106
108
|
- 公開リポ: https://github.com/Cor-Incorporated/grift-cli
|
|
107
109
|
- コマンド名: **`grift`**(方法論名 TEP はレポートに残す)
|
|
108
|
-
- PyPI: 配布名 **`grift-cli
|
|
110
|
+
- PyPI: 配布名 **`grift-cli`**(公開済み: `pipx install grift-cli`・最新版は [PyPI](https://pypi.org/project/grift-cli/) を参照)
|
|
109
111
|
|
|
110
112
|
## License
|
|
111
113
|
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# grift レポートの読み方 — 指標ごとの平易な解説(v1・2026-08-23)
|
|
2
|
+
|
|
3
|
+
**対象**: grift 0.5.4 以降(指標の定義は全ての 0.5.x で共通)。非エンジニアの方向け。
|
|
4
|
+
**原則の再掲**: すべての数値は**観測された事実の記録**であって、優劣の評価ではありません。単独の数値での合否判断・人と人の比較は TEP 非準拠です([docs/norms.md](norms.md))。書かれていないこと(= not observed)は「無かったこと」を意味しません。
|
|
5
|
+
**参考値の出典**: 特に断らない限り、公開リポジトリ 117 件のコーパス(v2026.11・repo スコープ)と v0.5.2 実地調査(requests / flask / GitHub CLI / chalk ほか)に基づきます。参考値は「だいたいの目安」であり、分布は成長とともに新版として出ます。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## まず全体の流れ(report.md の構成)
|
|
10
|
+
|
|
11
|
+
1. **Shared block**: 3-5 行の要約(コピペで共有できる形式)
|
|
12
|
+
2. **Provenance**: いつ・どのコミット・どの定義版で測ったか(再現に必要な情報)
|
|
13
|
+
3. **証拠系**: test co-change(検証を伴った変更の割合)
|
|
14
|
+
4. **観測系**: rework(手戻りの傾向)— 証拠主張ではない
|
|
15
|
+
5. **文脈**: context profile(どんな repo か)
|
|
16
|
+
6. **読み方の注意**(全レポート末尾に自動付与)
|
|
17
|
+
|
|
18
|
+
2種類のスコープがあります:
|
|
19
|
+
- **tenant スコープ**: identity.toml に載った特定の人の仕事(= 自己証明用)
|
|
20
|
+
- **repo スコープ**: bot 以外の全員のコミット(= repo 全体の姿・参考分布との比較はこちらのみ)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 証拠系: test co-change(テスト同期変更率)
|
|
25
|
+
|
|
26
|
+
**単位**: ratio(0.0〜1.0)/**母数**: 本番コードを変えたコミット数
|
|
27
|
+
|
|
28
|
+
### 何を測っているか
|
|
29
|
+
|
|
30
|
+
「本番のコード(機能・ロジック)を変えたコミットのうち、**同じコミットでテストも変更した**割合」。テストを書く習慣が実際の変更に伴っているかを、申告ではなく git の記録から数えます。ドキュメントだけの変更は母数から除外されます。
|
|
31
|
+
|
|
32
|
+
### 読み方
|
|
33
|
+
|
|
34
|
+
- **高い(目安: 0.23 以上 ≒ 分布の上位30%)**: 本番変更にテストが伴う習慣が定着している。変更の意図を後から検証できる状態が保たれやすい
|
|
35
|
+
- **中程度(目安: 0.07〜0.23)**: 多くの公開リポジトリがここにいます(中央値はおよそ 0.11〜0.16)
|
|
36
|
+
- **低い / 0**: テスト基盤が無い・あっても本番変更と同期していない。**ただし「テストを書いていない」=「悪い」ではありません** — ドキュメント中心のリポジトリ、设计のみの期間、ツール類など、テストが前提でない仕事は正当に低くなります
|
|
37
|
+
- **not observed**: テスト基盤そのものが検出できない(framework も tests ディレクトリも無い)場合。観測不能であって 0 ではありません
|
|
38
|
+
|
|
39
|
+
### 分布の目安(v2026.11・n=68・repo スコープ)
|
|
40
|
+
|
|
41
|
+
| 位置 | 値の目安 |
|
|
42
|
+
|---|---|
|
|
43
|
+
| 中央値付近(第5十分位) | 約 0.11〜0.16 |
|
|
44
|
+
| 上位30%に入る | 0.23 以上 |
|
|
45
|
+
| 上位10%(第9-10十分位) | 0.49 以上 |
|
|
46
|
+
|
|
47
|
+
実例: GitHub CLI 0.37 / flask 0.20 / requests 0.12 / chalk 0.18(2026-08 実測)。検証文化で選んだ A クラス repo の中央値は 0.24。
|
|
48
|
+
|
|
49
|
+
### 十分位(decile)の読み方
|
|
50
|
+
|
|
51
|
+
`interpretation` に「第N十分位」が出るとき、それは**同じ repo スコープの公開コーパスの中での位置**です(例: 第7十分位 = 下から 70% の位置)。「優秀」の意味ではありません。n≥30 のときだけ表示されます。
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 観測系: corrective rework(修正的リワーク率)と path retouch
|
|
56
|
+
|
|
57
|
+
**単位**: ratio/**母数**: 本番変更コミット数/**注意**: **証拠主張ではありません**(観測専用)
|
|
58
|
+
|
|
59
|
+
### 何を測っているか
|
|
60
|
+
|
|
61
|
+
- **corrective rework**: 直近21日以内に触ったファイルを、`fix` / `revert` 等の件名で**すぐにやり直した**コミットの割合。「作った直後に手直しが必要になった頻度」の観測
|
|
62
|
+
- **path retouch**: 同じファイルを21日窓で再び触れた割合(手戻りというより「同じ場所を編集し続けている」傾向)
|
|
63
|
+
|
|
64
|
+
### 読み方(これは「数値が低い=優秀」では読まない)
|
|
65
|
+
|
|
66
|
+
- commit メッセージの慣習(fix を書く文化か)で出方が変わるため、**人と人の比較・合否に使えません**(レポートにも明記されます)
|
|
67
|
+
- 高い値の読み方の例: 検証が無いまま進めて後から直している/活発に試しては直しているフェーズにある — の**いずれとも取れる**ので、面接などでの質問の種に使います(「この期間に何がありましたか?」)
|
|
68
|
+
- 低くても「バグが少ない」の証明にはなりません(バグ件数ではありません)
|
|
69
|
+
|
|
70
|
+
### 分布の目安(v2026.11・n=101・repo スコープ)
|
|
71
|
+
|
|
72
|
+
中央値付近 約 0.07〜0.10 / 上位側(0.19 以上)で第8-9十分位。A クラス(検証文化)の中央値は 0.05 と低めですが、因果の主張はできません。
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## survival(行の生存率)— 0.5.x では任意指定
|
|
77
|
+
|
|
78
|
+
`--survival` を付けたときだけ計算(τ=180日)。**「今も残っている行の割合」**を指数で出します(1.0 に近い = 6ヶ月経ってもほぼ残っている)。
|
|
79
|
+
- 実測例: 小規模運用中 repo で 0.94 — ほぼ全行が残存
|
|
80
|
+
- 参照分布は **v2027 予定**(現時点で高低の判断基準は提供しません)
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 文脈: context profile(どんな repo か)
|
|
85
|
+
|
|
86
|
+
数値の意味は文脈で変わるため、repo の姿を並記します。**すべて分類であって序列ではありません。**
|
|
87
|
+
|
|
88
|
+
### collaboration_class(協働のかたち)
|
|
89
|
+
|
|
90
|
+
| クラス | 定義 | 読み方の例 |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| solo | 最多作成者の比率 90% 以上 | 個人项目。一貫性は保たれやすいが、バス係数(本人がいないと進まない度)は構造的に高い |
|
|
93
|
+
| small_team | 関与者 5 人以下 | 少人数で役割が重なる協働 |
|
|
94
|
+
| community | それ以上 | 多人数。審査プロセス(PR flow)や文書化が効いてきやすい |
|
|
95
|
+
|
|
96
|
+
実測例: requests 802人 / flask 870人 / GitHub CLI 722人は community。個人開発は solo。
|
|
97
|
+
|
|
98
|
+
### lifecycle_stage(活動密度)— 一次観測2つが本体
|
|
99
|
+
|
|
100
|
+
**クラスは便宜であり、必ず生の2数値と併せて読みます**:
|
|
101
|
+
|
|
102
|
+
- **active_days_180d**: 直近180日のうち、人がコミットした日が何日あったか(≥12 で active)
|
|
103
|
+
- **days_since_last_human_commit**: 最後のコミットから何日経ったか
|
|
104
|
+
|
|
105
|
+
| クラス | active_days_180d | 読み方の例 |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| active | 12日以上 | 現在も開発が続いている密度 |
|
|
108
|
+
| maintained | 3〜11日 | 維持運用は続いている(改修・追従) |
|
|
109
|
+
| dormant | 2日以下 | 180日窓ではほぼ静止している |
|
|
110
|
+
|
|
111
|
+
注意: **密度は「開発 vs 保守」の区別を測れません**(高密度の保守運用も active になります)。一斉ハウスキーピングで「新しそうに見える」のを防ぐため、新しさでなく密度を採っています。
|
|
112
|
+
|
|
113
|
+
### その他の文脈観測(抜粋)
|
|
114
|
+
|
|
115
|
+
- **pr_flow_share**: merge コミット(PR マージ)の割合。高い = レビュー流程を経る文化
|
|
116
|
+
- **conventional_commit_share**: `feat:` `fix:` のような定型件名の割合。高い = 変更種別が機械可読な管理
|
|
117
|
+
- **language_composition**: 言語別のファイル接触比率(多言語 repo の判別に)
|
|
118
|
+
- **dependency_manifests**: 依存管理ファイルの種類(複数 = polyglot / monorepo の兆候)
|
|
119
|
+
- **top_actor_share**: 最多作成者の比率(単独性の程度。名前は出ません)
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## よくある誤読と、してはいけない読み方
|
|
124
|
+
|
|
125
|
+
| 誤読 | 正しくは |
|
|
126
|
+
|---|---|
|
|
127
|
+
| 「co-change が低い人はテストを書けない人」 | テストが前提でない仕事(docs・設計・ツール)は正当に低くなる。不在・低値を能力の否定として読むのは非準拠(norms 1条) |
|
|
128
|
+
| 「corrective rework が高い = バグが多い人」 | 件名慣習への依存が強く、バグ件数でも品質でもない(観測専用) |
|
|
129
|
+
| 「dormant = 捨てられた repo」 | 保守周期が長いだけの成熟 repo もある。復帰の履歴は gap として可視化されるが、gap を負に読むのは非準拠 |
|
|
130
|
+
| 「第9十分位の人は第5十分位の人より優秀」 | 十分位は同スコープ repo 間の位置のみ。単独足切り・比較表は非準拠(norms 2条) |
|
|
131
|
+
| 「decile が無い = 隠している」 | 母数 <20 か分布 n<30 では出力規則により非表示(insufficient_population / reference_too_small)です |
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 数値が出ない(not observed)とき
|
|
136
|
+
|
|
137
|
+
「0 だった」と「測れなかった」は別物です。主な理由コード:
|
|
138
|
+
|
|
139
|
+
| reason | 意味 |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `no_test_framework_or_directory` | テスト基盤が検出できない |
|
|
142
|
+
| `insufficient_population` | 母数が 20 コミット未満(率を出さず件数のみ) |
|
|
143
|
+
| `pending_attribution` | identity(誰の仕事か)が未設定 |
|
|
144
|
+
| `scope_is_tenant` | 参照分布は repo スコープ専用のため tenant 実行では付かない |
|
|
145
|
+
| `survival_scan_disabled` | survival は `--survival` 指定時のみ |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
*本書は docs/report-schema.md(正本・機械検証付き)の平易版です。数値の厳密な定義・単位・符号化は正本を参照してください。参考値の更新は分布の新版(v2026.11 → vNext)とともに行われます。*
|
|
@@ -16,10 +16,11 @@ from tep_core.verify import CANNOT_VERIFY, MISMATCH, VERIFIED, verify_report
|
|
|
16
16
|
from tep_core.version import __version__
|
|
17
17
|
|
|
18
18
|
_EPILOG = """examples:
|
|
19
|
-
grift analyze . --scope repo
|
|
20
|
-
grift analyze
|
|
19
|
+
grift analyze . --scope repo --out ./out
|
|
20
|
+
grift report # analyze current HEAD → ./out/report.{json,md} (always re-analyzes)
|
|
21
|
+
grift report path/report.json # re-render md from an existing report (no re-analysis)
|
|
21
22
|
grift verify ./out/report.json --repo ./repo
|
|
22
|
-
grift contribute ./out/report.json
|
|
23
|
+
grift contribute # build opt-in payload from ./out/report.json (never sends)
|
|
23
24
|
詳細: README"""
|
|
24
25
|
|
|
25
26
|
|
|
@@ -141,11 +142,17 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
141
142
|
|
|
142
143
|
render = sub.add_parser(
|
|
143
144
|
"report",
|
|
144
|
-
help="
|
|
145
|
+
help="Analyze the current repo and write ./out/report.{json,md}; or re-render md from an existing report.json",
|
|
145
146
|
epilog=_EPILOG,
|
|
146
147
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
147
148
|
)
|
|
148
|
-
render.add_argument(
|
|
149
|
+
render.add_argument(
|
|
150
|
+
"report_json",
|
|
151
|
+
type=Path,
|
|
152
|
+
nargs="?",
|
|
153
|
+
default=None,
|
|
154
|
+
help="Path to an existing report.json to re-render (default: analyze the current repo into ./out)",
|
|
155
|
+
)
|
|
149
156
|
render.add_argument(
|
|
150
157
|
"--out",
|
|
151
158
|
type=Path,
|
|
@@ -159,7 +166,13 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
159
166
|
epilog=_EPILOG,
|
|
160
167
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
161
168
|
)
|
|
162
|
-
contribute.add_argument(
|
|
169
|
+
contribute.add_argument(
|
|
170
|
+
"report_json",
|
|
171
|
+
type=Path,
|
|
172
|
+
nargs="?",
|
|
173
|
+
default=None,
|
|
174
|
+
help="Path to a repo-scope report.json (default: search ./out, .grift-out, ./)",
|
|
175
|
+
)
|
|
163
176
|
contribute.add_argument(
|
|
164
177
|
"--out",
|
|
165
178
|
type=Path,
|
|
@@ -243,11 +256,35 @@ def _run_verify(args: argparse.Namespace) -> int:
|
|
|
243
256
|
return 2
|
|
244
257
|
|
|
245
258
|
|
|
259
|
+
_DEFAULT_REPORT_SEARCH = (
|
|
260
|
+
Path("out") / "report.json",
|
|
261
|
+
Path(".grift-out") / "report.json",
|
|
262
|
+
Path("report.json"),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _resolve_report_path(explicit: Path | None) -> tuple[Path | None, str]:
|
|
267
|
+
"""Return (path, guidance). Search ./out, .grift-out, ./ when no explicit path."""
|
|
268
|
+
if explicit is not None:
|
|
269
|
+
return explicit, ""
|
|
270
|
+
for candidate in _DEFAULT_REPORT_SEARCH:
|
|
271
|
+
if candidate.is_file():
|
|
272
|
+
return candidate, ""
|
|
273
|
+
return None, ""
|
|
274
|
+
|
|
275
|
+
|
|
246
276
|
def _run_report(args: argparse.Namespace) -> int:
|
|
277
|
+
if args.report_json is None:
|
|
278
|
+
# UX (代表 2026-08-23): bare `grift report` ALWAYS re-analyzes the
|
|
279
|
+
# current HEAD and rewrites ./out/report.{json,md}. It never falls
|
|
280
|
+
# back to a stale ./out/report.json — "report で測ったつもりが古い
|
|
281
|
+
# SHA のまま" は自己証明の罠になるため、既存ファイルは無条件に
|
|
282
|
+
# 上書きする。再分析なしの再レンダリングは引数指定時のみ。
|
|
283
|
+
return _analyze_to_out(scope="repo")
|
|
247
284
|
try:
|
|
248
285
|
payload = json.loads(args.report_json.read_text(encoding="utf-8"))
|
|
249
286
|
except (OSError, json.JSONDecodeError) as exc:
|
|
250
|
-
sys.stderr.write(f"report unreadable: {exc}\n")
|
|
287
|
+
sys.stderr.write(f"report unreadable ({args.report_json}): {exc}\n")
|
|
251
288
|
return 2
|
|
252
289
|
try:
|
|
253
290
|
markdown = render_markdown(payload)
|
|
@@ -263,13 +300,43 @@ def _run_report(args: argparse.Namespace) -> int:
|
|
|
263
300
|
return 0
|
|
264
301
|
|
|
265
302
|
|
|
303
|
+
def _analyze_to_out(*, scope: str = "repo") -> int:
|
|
304
|
+
"""Run the analysis in the current directory and write ./out/report.{json,md}.
|
|
305
|
+
|
|
306
|
+
UX (代表 2026-08-23): bare `grift report` should just produce the reports —
|
|
307
|
+
create ./out if missing. Reuses the analyze pipeline on '.'.
|
|
308
|
+
"""
|
|
309
|
+
repo = Path(".")
|
|
310
|
+
if not (repo / ".git").exists():
|
|
311
|
+
sys.stderr.write("grift: not a git repository (run inside the repo you want to analyze)\n")
|
|
312
|
+
return 2
|
|
313
|
+
identity = discover_identity(repo, None)
|
|
314
|
+
report = analyze_repository(repo, identity, Lineage(), scope=scope)
|
|
315
|
+
markdown = render_markdown(report)
|
|
316
|
+
encoded = json.dumps(report, indent=2, ensure_ascii=False) + "\n"
|
|
317
|
+
out_dir = Path("out")
|
|
318
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
319
|
+
(out_dir / "report.json").write_text(encoded, encoding="utf-8")
|
|
320
|
+
(out_dir / "report.md").write_text(markdown, encoding="utf-8")
|
|
321
|
+
sys.stdout.write(markdown)
|
|
322
|
+
sys.stderr.write(f"\nwrote {out_dir / 'report.json'} and {out_dir / 'report.md'}\n")
|
|
323
|
+
return 0
|
|
324
|
+
|
|
325
|
+
|
|
266
326
|
def _run_contribute(args: argparse.Namespace) -> int:
|
|
267
327
|
from tep_core.contribute import CONFIRMATION_TEXT, build_contribution, render_confirmation
|
|
268
328
|
|
|
329
|
+
report_path, _guidance = _resolve_report_path(args.report_json)
|
|
330
|
+
if report_path is None:
|
|
331
|
+
sys.stderr.write(
|
|
332
|
+
"contribute: no report.json found (searched ./out/report.json, .grift-out/report.json, ./report.json). "
|
|
333
|
+
"Run `grift report` first — it will analyze the repo and write ./out/report.json.\n"
|
|
334
|
+
)
|
|
335
|
+
return 2
|
|
269
336
|
try:
|
|
270
|
-
report = json.loads(
|
|
337
|
+
report = json.loads(report_path.read_text(encoding="utf-8"))
|
|
271
338
|
except (OSError, json.JSONDecodeError) as exc:
|
|
272
|
-
sys.stderr.write(f"report unreadable: {exc}\n")
|
|
339
|
+
sys.stderr.write(f"report unreadable ({report_path}): {exc}\n")
|
|
273
340
|
return 2
|
|
274
341
|
try:
|
|
275
342
|
payload = build_contribution(report)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
__version__ = "0.5.
|
|
1
|
+
__version__ = "0.5.4"
|
|
2
2
|
REPORT_SCHEMA_VERSION = "report-v1"
|
|
3
|
-
DEFINITION_VERSION = "tep-v0.5.
|
|
3
|
+
DEFINITION_VERSION = "tep-v0.5.4-2026-08-23"
|
|
4
4
|
ORIGIN_DEFINITION_VERSION = "origin-v1.1-2026-08-22"
|
|
5
5
|
ACTIVITY_DEFINITION_VERSION = "activity-v1-2026-08-16"
|
|
6
6
|
CORE_ACTIVITY_DEFINITION_VERSION = "core-activity-v1-2026-08-16"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Bare `grift report` / `grift contribute`: default-path search + guidance.
|
|
2
|
+
|
|
3
|
+
代表 UX feedback (2026-08-23): invoking the subcommands without a path used
|
|
4
|
+
to hard-error on argparse / file-not-found without telling the user what to
|
|
5
|
+
do first. Now they search ./out, .grift-out, ./ and, when nothing is found,
|
|
6
|
+
exit 2 with actionable guidance.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
from tep_cli.__main__ import main
|
|
15
|
+
|
|
16
|
+
from git_fixture import commit, init_repo
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _make_report_json(dir_path: Path) -> Path:
|
|
20
|
+
from tep_core.analyze import analyze_repository
|
|
21
|
+
from tep_core.identity import empty_identity
|
|
22
|
+
from tep_core.lineage import Lineage
|
|
23
|
+
|
|
24
|
+
repo = init_repo(dir_path / "repo")
|
|
25
|
+
(repo / "pyproject.toml").write_text(
|
|
26
|
+
'[project]\nname = "demo"\ndependencies = ["pytest"]\n', encoding="utf-8"
|
|
27
|
+
)
|
|
28
|
+
for index in range(12):
|
|
29
|
+
commit(
|
|
30
|
+
repo,
|
|
31
|
+
email="a@example.com",
|
|
32
|
+
date="2026-01-05",
|
|
33
|
+
message=f"feat: {index}",
|
|
34
|
+
filename="app.py",
|
|
35
|
+
)
|
|
36
|
+
report = analyze_repository(repo, empty_identity(), Lineage(), scope="repo")
|
|
37
|
+
target = dir_path / "out" / "report.json"
|
|
38
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
39
|
+
target.write_text(json.dumps(report, ensure_ascii=False), encoding="utf-8")
|
|
40
|
+
return target
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_report_bare_ignores_stale_out_and_reanalyzes(
|
|
44
|
+
tmp_path: Path, capsys: object, monkeypatch: object
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Critical UX (提唱者 2026-08-23): bare `grift report` with an EXISTING
|
|
47
|
+
./out/report.json must RE-ANALYZE the current HEAD, not re-render the
|
|
48
|
+
stale file."""
|
|
49
|
+
import json as _json
|
|
50
|
+
import subprocess as _sp
|
|
51
|
+
|
|
52
|
+
from git_fixture import commit as _commit, init_repo as _init
|
|
53
|
+
|
|
54
|
+
repo = _init(tmp_path / "repo")
|
|
55
|
+
(repo / "pyproject.toml").write_text(
|
|
56
|
+
'[project]\nname = "demo"\ndependencies = ["pytest"]\n', encoding="utf-8"
|
|
57
|
+
)
|
|
58
|
+
for index in range(5):
|
|
59
|
+
_commit(repo, email="a@example.com", date="2026-01-05", message=f"feat: {index}")
|
|
60
|
+
monkeypatch.chdir(repo)
|
|
61
|
+
assert main(["report"]) == 0
|
|
62
|
+
first_sha = _json.loads((repo / "out" / "report.json").read_text())["provenance"][
|
|
63
|
+
"analyzed_commit_sha"
|
|
64
|
+
]
|
|
65
|
+
_commit(repo, email="a@example.com", date="2026-01-06", message="feat: more")
|
|
66
|
+
assert main(["report"]) == 0
|
|
67
|
+
second_sha = _json.loads((repo / "out" / "report.json").read_text())["provenance"][
|
|
68
|
+
"analyzed_commit_sha"
|
|
69
|
+
]
|
|
70
|
+
assert first_sha != second_sha, "bare report must re-analyze, not re-render stale output"
|
|
71
|
+
head = _sp.run(
|
|
72
|
+
["git", "rev-parse", "HEAD"], capture_output=True, text=True, cwd=repo
|
|
73
|
+
).stdout.strip()
|
|
74
|
+
assert second_sha == head
|
|
75
|
+
assert "## Shared block" in capsys.readouterr().out
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_report_with_explicit_path_still_re_renders(tmp_path: Path, capsys: object) -> None:
|
|
79
|
+
"""Explicit path keeps the re-render (no re-analysis) contract."""
|
|
80
|
+
_make_report_json(tmp_path)
|
|
81
|
+
code = main(["report", str(tmp_path / "out" / "report.json")])
|
|
82
|
+
assert code == 0
|
|
83
|
+
assert "## Shared block" in capsys.readouterr().out
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_report_bare_without_prior_output_analyzes_cwd(
|
|
87
|
+
tmp_path: Path, capsys: object, monkeypatch: object
|
|
88
|
+
) -> None:
|
|
89
|
+
"""UX: bare `grift report` in a git repo with no ./out → analyze now and
|
|
90
|
+
write ./out/report.{json,md}."""
|
|
91
|
+
from git_fixture import commit as _commit, init_repo as _init
|
|
92
|
+
|
|
93
|
+
repo = _init(tmp_path / "repo")
|
|
94
|
+
for index in range(3):
|
|
95
|
+
_commit(repo, email="a@example.com", date="2026-01-05", message=f"feat: {index}")
|
|
96
|
+
monkeypatch.chdir(repo)
|
|
97
|
+
code = main(["report"])
|
|
98
|
+
assert code == 0
|
|
99
|
+
out = capsys.readouterr()
|
|
100
|
+
assert "## Shared block" in out.out
|
|
101
|
+
assert (repo / "out" / "report.json").is_file()
|
|
102
|
+
assert (repo / "out" / "report.md").is_file()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_report_bare_outside_git_repo_refused(
|
|
106
|
+
tmp_path: Path, capsys: object, monkeypatch: object
|
|
107
|
+
) -> None:
|
|
108
|
+
monkeypatch.chdir(tmp_path)
|
|
109
|
+
code = main(["report"])
|
|
110
|
+
assert code == 2
|
|
111
|
+
assert "not a git repository" in capsys.readouterr().err
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_contribute_bare_without_report_gives_guidance(
|
|
115
|
+
tmp_path: Path, capsys: object, monkeypatch: object
|
|
116
|
+
) -> None:
|
|
117
|
+
monkeypatch.chdir(tmp_path)
|
|
118
|
+
code = main(["contribute"])
|
|
119
|
+
assert code == 2
|
|
120
|
+
err = capsys.readouterr().err
|
|
121
|
+
assert "grift report" in err # guidance points to the one-step entry
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_contribute_bare_uses_out_default(
|
|
125
|
+
tmp_path: Path, capsys: object, monkeypatch: object
|
|
126
|
+
) -> None:
|
|
127
|
+
# repo-scope report in ./out → bare contribute reaches the F-C1 gate
|
|
128
|
+
_make_report_json(tmp_path)
|
|
129
|
+
monkeypatch.chdir(tmp_path)
|
|
130
|
+
code = main(["contribute", "--out", str(tmp_path / "c.json")]) # non-TTY, no --yes
|
|
131
|
+
assert code == 2 # F-C1 refusal path (not path-resolution error)
|
|
132
|
+
err = capsys.readouterr().err
|
|
133
|
+
assert "公開リポジトリに載ります" in err
|
|
134
|
+
assert "grift analyze . --out ./out" not in err # found the file; failed on consent instead
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|