1panel-toolkit 0.3.1__tar.gz → 0.3.2__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.
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/CHANGELOG.md +26 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/PKG-INFO +1 -1
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/docs/generation-depth.md +4 -1
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/pyproject.toml +1 -1
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/__init__.py +1 -1
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/cli.py +7 -4
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/depth.py +28 -11
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_depth.py +50 -1
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/.gitignore +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/LICENSE +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/README.md +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/docs/patterns.md +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/examples/demo-db-spec.json +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/examples/demo-spec.json +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/skill/SKILL.md +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/__init__.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/compose.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/forms.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/hygiene.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/i18n.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/lessons.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/structure.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/checks/substance.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/config.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/dependencies.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/deploy.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/errors.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/evidence.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/fix.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/gen.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/lifecycle.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/loader.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/logo.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/model.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/net.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/panel.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/patterns/__init__.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/patterns/catalog.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/patterns/data/patterns.json +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/patterns/extract.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/remote.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/report.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/resolve/__init__.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/resolve/analyze.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/resolve/sources.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/__init__.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/data/decisions.yaml +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/data/dependencies.yaml +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/data/envkeys.yaml +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/data/form-design.yaml +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/data/i18n.yaml +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/data/lessons.yaml +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/rules/data/store.yaml +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/src/p1toolkit/selftest.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/conftest.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_checks.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_config.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_corpus.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_dependencies.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_deploy.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_evidence.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_fix.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_forms.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_gen.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_github_source.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_lifecycle.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_net.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_panel.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_patterns.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_regressions.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_remote.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_remote_cli.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_resolve.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_rules_data.py +0 -0
- {1panel_toolkit-0.3.1 → 1panel_toolkit-0.3.2}/tests/test_selftest.py +0 -0
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# 变更记录
|
|
2
2
|
|
|
3
|
+
## 0.3.2 — 2026-09-20
|
|
4
|
+
|
|
5
|
+
`p1 depth` 的判定阈值校准。用 `p1 fix` 收敛用户自己的四个包时发现的误判。
|
|
6
|
+
|
|
7
|
+
### 判定:只缺一个维度不再是 thin
|
|
8
|
+
|
|
9
|
+
原来的 `assess()` 是"三个多样性维度(对外端口 / 持久化挂载 / 应用自有变量)里
|
|
10
|
+
缺任意一个 → thin"。于是 `xiaozhi-esp32-server` 被判成 `thin`——可它有 2 个对外端口、
|
|
11
|
+
2 个挂载点(其中一个还是模型文件 `model.pt`)、`security_opt`、`init.sh`,只是把配置
|
|
12
|
+
全放在挂载的 `data/.config.yaml` 里,本来就不该有应用变量。
|
|
13
|
+
|
|
14
|
+
现在:**缺两个及以上才算薄**;只缺一个仍是 `adapted`,但会打一条 `·` 说明;
|
|
15
|
+
与不相干官方包重合 ≥ 50% 仍是 `possibly-reskinned`。顺带 `p1 depth <包>`
|
|
16
|
+
不再需要 `--corpus` 也会给判定(只有"最接近的官方包"那节需要语料)。
|
|
17
|
+
|
|
18
|
+
四个真实包的判定:hindsight `adapted`、openviking `adapted`、
|
|
19
|
+
xiaozhi-esp32-server `adapted`(附一条"没有任何应用自有环境变量"的说明)、
|
|
20
|
+
xiaozhi-esp32-server-full `adapted`。
|
|
21
|
+
|
|
22
|
+
### 验证
|
|
23
|
+
|
|
24
|
+
- 新增 2 条测试(缺一个维度 → adapted + note;缺两个 → thin),`pytest` 257 passed。
|
|
25
|
+
- 收敛后的四个包 `p1 check`:hindsight `0 fail, 2 warn`、openviking `0 fail, 1 warn`、
|
|
26
|
+
xiaozhi-esp32-server `0 fail, 1 warn`、xiaozhi-esp32-server-full `OK no issues`,
|
|
27
|
+
与 README 表格一致;`p1 fix` 二次运行四个包都是"没有需要修复的内容"(幂等)。
|
|
28
|
+
|
|
3
29
|
## 0.3.1 — 2026-09-20
|
|
4
30
|
|
|
5
31
|
独立验证(另一个 session 的只读复现)挖出的三个真 bug,外加发布产物卫生。
|
|
@@ -99,7 +99,10 @@ p1 depth <包目录> --corpus <官方apps目录> # 单个包的判定 +
|
|
|
99
99
|
判定逻辑基于"应用特有特征指纹"(容器内端口、挂载点、应用变量名、镜像、额外运行时
|
|
100
100
|
字段),做 Jaccard 相似度:
|
|
101
101
|
|
|
102
|
-
-
|
|
102
|
+
- 三个"多样性维度"(对外端口 / 持久化挂载 / 应用自有变量)里**缺两个及以上** → `thin`
|
|
103
|
+
- 只缺一个 → 仍是 `adapted`,但会打一条 `·` 说明。应用完全可以把配置放在挂载的
|
|
104
|
+
配置文件里(xiaozhi 的 `data/.config.yaml`、openviking 的 `ov.conf`),这时
|
|
105
|
+
"没有应用变量"是设计而不是薄——把这种情况判成 thin 会逼人往 compose 里塞无意义的变量
|
|
103
106
|
- 与某个不相干的包重合度 ≥ 50% → `possibly-reskinned`
|
|
104
107
|
- 都不满足 → `adapted`
|
|
105
108
|
|
|
@@ -268,11 +268,14 @@ def cmd_depth(args: argparse.Namespace) -> int:
|
|
|
268
268
|
print(f" - {value}")
|
|
269
269
|
if len(values) > 12:
|
|
270
270
|
print(f" ... 另外 {len(values) - 12} 项")
|
|
271
|
+
# The profile-based verdict needs no corpus; only the twin search does.
|
|
272
|
+
verdict = depth_module.assess(profile, corpus)
|
|
273
|
+
print(f"\n判定:{verdict.status}")
|
|
274
|
+
for reason in verdict.reasons:
|
|
275
|
+
print(f" ! {reason}")
|
|
276
|
+
for note in verdict.notes:
|
|
277
|
+
print(f" · {note}")
|
|
271
278
|
if corpus:
|
|
272
|
-
verdict = depth_module.assess(profile, corpus)
|
|
273
|
-
print(f"\n判定:{verdict.status}")
|
|
274
|
-
for reason in verdict.reasons:
|
|
275
|
-
print(f" ! {reason}")
|
|
276
279
|
print("\n与官方语料最接近的包:")
|
|
277
280
|
found = depth_module.nearest(profile, corpus)
|
|
278
281
|
if not found:
|
|
@@ -366,6 +366,7 @@ class Verdict:
|
|
|
366
366
|
status: str
|
|
367
367
|
score: float
|
|
368
368
|
reasons: list[str] = field(default_factory=list)
|
|
369
|
+
notes: list[str] = field(default_factory=list)
|
|
369
370
|
twin: str | None = None
|
|
370
371
|
|
|
371
372
|
@property
|
|
@@ -380,18 +381,28 @@ def assess(
|
|
|
380
381
|
|
|
381
382
|
The dimensions that carry all the diversity in the store (container ports,
|
|
382
383
|
mount targets, application variable names) are exactly the ones a template
|
|
383
|
-
cannot answer. A package
|
|
384
|
-
|
|
384
|
+
cannot answer. A package missing *one* of them is a judgement call and only
|
|
385
|
+
gets a note — an app can legitimately carry its configuration in a mounted
|
|
386
|
+
file instead of environment variables. Missing two or more means the
|
|
387
|
+
package is a shell; a near-identical unrelated app means it is a reskin.
|
|
385
388
|
"""
|
|
386
389
|
reasons: list[str] = []
|
|
390
|
+
notes: list[str] = []
|
|
387
391
|
if not profile.images:
|
|
388
392
|
reasons.append("compose 里没有可识别的镜像")
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
393
|
+
missing = [
|
|
394
|
+
text
|
|
395
|
+
for present, text in (
|
|
396
|
+
(profile.port_pairs, "没有对外端口映射"),
|
|
397
|
+
(profile.mount_targets, "没有持久化挂载点(除非这个应用真的无状态)"),
|
|
398
|
+
(profile.app_env_keys, "没有任何应用自有环境变量(多数应用都有配置项)"),
|
|
399
|
+
)
|
|
400
|
+
if not present
|
|
401
|
+
]
|
|
402
|
+
if len(missing) >= 2:
|
|
403
|
+
reasons.extend(missing)
|
|
404
|
+
else:
|
|
405
|
+
notes.extend(missing)
|
|
395
406
|
twin = None
|
|
396
407
|
score = 0.0
|
|
397
408
|
found = nearest(profile, corpus, threshold=twin_threshold, limit=1)
|
|
@@ -404,10 +415,16 @@ def assess(
|
|
|
404
415
|
)
|
|
405
416
|
|
|
406
417
|
if not reasons:
|
|
407
|
-
return Verdict(status="adapted", score=score)
|
|
418
|
+
return Verdict(status="adapted", score=score, notes=notes)
|
|
408
419
|
if twin:
|
|
409
|
-
return Verdict(
|
|
410
|
-
|
|
420
|
+
return Verdict(
|
|
421
|
+
status="possibly-reskinned",
|
|
422
|
+
score=score,
|
|
423
|
+
reasons=reasons,
|
|
424
|
+
notes=notes,
|
|
425
|
+
twin=twin,
|
|
426
|
+
)
|
|
427
|
+
return Verdict(status="thin", score=score, reasons=reasons, notes=notes)
|
|
411
428
|
|
|
412
429
|
|
|
413
430
|
def to_json(data: dict[str, Any]) -> str:
|
|
@@ -64,6 +64,56 @@ def test_adapted_package_passes(tmp_path: Path) -> None:
|
|
|
64
64
|
assert assess(profile, []).status == "adapted"
|
|
65
65
|
|
|
66
66
|
|
|
67
|
+
def test_missing_one_dimension_is_a_note_not_a_verdict(tmp_path: Path) -> None:
|
|
68
|
+
"""An app can carry its config in a mounted file instead of env vars.
|
|
69
|
+
|
|
70
|
+
xiaozhi-esp32-server is exactly that shape: two ports, a model-file mount,
|
|
71
|
+
security_opt, an init script, and zero application variables because
|
|
72
|
+
everything lives in `data/.config.yaml`. Calling that "thin" is wrong.
|
|
73
|
+
"""
|
|
74
|
+
compose = (
|
|
75
|
+
"services:\n"
|
|
76
|
+
" app:\n"
|
|
77
|
+
' image: "ghcr.io/example/app:1.0"\n'
|
|
78
|
+
" container_name: ${CONTAINER_NAME}\n"
|
|
79
|
+
" networks:\n"
|
|
80
|
+
" - 1panel-network\n"
|
|
81
|
+
" ports:\n"
|
|
82
|
+
' - "${PANEL_APP_PORT_HTTP}:8000"\n'
|
|
83
|
+
" volumes:\n"
|
|
84
|
+
" - ./data:/opt/app/data\n"
|
|
85
|
+
"networks:\n"
|
|
86
|
+
" 1panel-network:\n"
|
|
87
|
+
" external: true\n"
|
|
88
|
+
)
|
|
89
|
+
profile = profile_app(write_package(tmp_path / "demo", key="demo", compose=compose))
|
|
90
|
+
verdict = assess(profile, [])
|
|
91
|
+
assert verdict.status == "adapted"
|
|
92
|
+
assert not verdict.reasons
|
|
93
|
+
assert any("环境变量" in note for note in verdict.notes)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_two_missing_dimensions_are_still_thin(tmp_path: Path) -> None:
|
|
97
|
+
compose = (
|
|
98
|
+
"services:\n"
|
|
99
|
+
" app:\n"
|
|
100
|
+
' image: "ghcr.io/example/app:1.0"\n'
|
|
101
|
+
" container_name: ${CONTAINER_NAME}\n"
|
|
102
|
+
" networks:\n"
|
|
103
|
+
" - 1panel-network\n"
|
|
104
|
+
" ports:\n"
|
|
105
|
+
' - "${PANEL_APP_PORT_HTTP}:8000"\n'
|
|
106
|
+
"networks:\n"
|
|
107
|
+
" 1panel-network:\n"
|
|
108
|
+
" external: true\n"
|
|
109
|
+
)
|
|
110
|
+
profile = profile_app(write_package(tmp_path / "demo", key="demo", compose=compose))
|
|
111
|
+
verdict = assess(profile, [])
|
|
112
|
+
assert verdict.shallow
|
|
113
|
+
assert any("挂载" in reason for reason in verdict.reasons)
|
|
114
|
+
assert any("环境变量" in reason for reason in verdict.reasons)
|
|
115
|
+
|
|
116
|
+
|
|
67
117
|
def test_similarity_finds_a_reskin(tmp_path: Path) -> None:
|
|
68
118
|
first = profile_app(write_package(tmp_path / "a", key="a"))
|
|
69
119
|
second = profile_app(write_package(tmp_path / "b", key="b"))
|
|
@@ -131,4 +181,3 @@ def test_templateable_dimensions_documented() -> None:
|
|
|
131
181
|
templatable = data.get("templatable") or []
|
|
132
182
|
assert templatable
|
|
133
183
|
assert any("重启策略" in str(item.get("dimension")) for item in templatable)
|
|
134
|
-
|
|
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
|