@mcptoolshop/style-dataset-lab 3.2.0 → 3.3.0
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.
- package/CHANGELOG.md +25 -1
- package/README.es.md +16 -5
- package/README.fr.md +54 -43
- package/README.hi.md +47 -36
- package/README.it.md +14 -3
- package/README.ja.md +16 -5
- package/README.md +11 -0
- package/README.pt-BR.md +16 -5
- package/README.zh.md +49 -38
- package/lib/adapters/comfyui-runner.js +36 -2
- package/lib/critique-engine.js +46 -1
- package/lib/critique-render.js +45 -0
- package/lib/freeze-gate.js +56 -13
- package/lib/run-manifest.js +308 -0
- package/lib/snapshot.js +7 -1
- package/lib/verifier.js +67 -0
- package/package.json +1 -1
- package/scripts/critique.js +32 -7
- package/scripts/curate.js +21 -0
- package/scripts/generate.js +24 -0
- package/scripts/qwen_generate.py +203 -0
- package/scripts/run-generate.js +3 -0
package/README.ja.md
CHANGED
|
@@ -113,6 +113,15 @@ sdlab canon drift --project my-project
|
|
|
113
113
|
|
|
114
114
|
実用的な結果:LoRAがドリフトした場合、なぜそうなるのかを尋ねることができます。次のトレーニングラウンドにより良いデータが必要な場合、どのレコードがほぼ合格であり、どの単一のルールに失敗したかを正確に知ることができます。新しいチームメンバーがプロジェクトのビジュアル言語について質問した場合、答えはFigmaボードではなく、1,182件の評価された例を含む検索可能な憲法です。
|
|
115
115
|
|
|
116
|
+
## 実運用で実績あり
|
|
117
|
+
|
|
118
|
+
これはデモ用のパイプラインではありません。2つの実際のスタイルLoRAが、このパイプラインを通じて最初から最後まで処理されました。キュレーションの範囲において正反対の位置にある、同じ「原作 → キュレーション → トレーニング → 配信」というサイクルです。
|
|
119
|
+
|
|
120
|
+
- **[Tallow Fen](handbook/case-study-tallow-fen/)**(クリーチャーデザイン)—最初から作成されたベストiaryの原作で、293件のキュレーション済みレコードのうち約34%が承認されました(169件は却下され、厳しい基準で審査されています)。`tallow_fen_style_v3.safetensors`を`qwen-image`上で1.5で使用して配信しました。
|
|
121
|
+
- **[Rustline](handbook/case-study-rustline/)**(コンセプトデザイン)—密度が高く、事前に形成された原作で、180件のレコードのうち約96%が承認されました。`rustline_v3ckpt_1500.safetensors`を`qwen-image`上で1.0で使用して配信し、別のプロジェクトでも再利用しました。
|
|
122
|
+
|
|
123
|
+
同じパイプラインで、2つの実運用プロファイル:キュレーションゲートは本物であり(オープンなテーマに対して厳しい基準で審査します)、厳格な原作によって高い承認率が得られます。
|
|
124
|
+
|
|
116
125
|
## 5つのドメイン、実際のルール
|
|
117
126
|
|
|
118
127
|
プレースホルダーテンプレートではありません。各ドメインには、実運用レベルのカノン憲法ルール、レーン定義、スコアリングルーブリック、およびグループ語彙が付属しています。
|
|
@@ -153,11 +162,13 @@ projects/my-project/
|
|
|
153
162
|
|
|
154
163
|
これらは理想的なものではありません。強制的に適用されます。
|
|
155
164
|
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
165
|
+
- **スナップショットは不変です。**構成のフィンガープリント(SHA-256)により、変更がないことが証明されます。
|
|
166
|
+
- **分割により情報漏洩を防ぎます。**被験者グループ(同一性、系統、またはIDサフィックスによって分類)は、決してパーティション境界を越えません。
|
|
167
|
+
- **マニフェストは固定された契約です。**エクスポートハッシュと構成のフィンガープリントが含まれます。変更がある場合は、新しいものを生成します。
|
|
168
|
+
- **実行は正確なグラフに固定されます。**すべての生成処理において、`comfy_workflow_sha` + モデル/LoRAコンテンツハッシュ + シードポリシーが記録されるため、一連の処理をバイト単位で完全に再現できます。これは、JavaScriptとPythonの両方のランナーで同じ結果になります。モデルのハッシュ化はオプション(`--hash-models`)であり、偽造されることはありません。
|
|
169
|
+
- **どのモデルも自身の出力を検証しません。**評価では、`judged_by_model`と`generator_model`が記録され、これらが同一のモデルである場合、警告が表示されます。
|
|
170
|
+
- **アダプターは真実を改ざんできません。**レイアウトは異なりますが、同じレコードを使用します。追加、削除、再分類は行われません。
|
|
171
|
+
- **生成された出力は、レビューを通じて再度処理されます。**バイパスはありません。他のものと同様に、キュレーションとバインドを行います。
|
|
161
172
|
|
|
162
173
|
## スター・フレイト
|
|
163
174
|
|
package/README.md
CHANGED
|
@@ -113,6 +113,15 @@ Style Dataset Lab makes the connection explicit. Your constitution defines the r
|
|
|
113
113
|
|
|
114
114
|
The practical result: when your LoRA drifts, you can ask *why*. When your next training round needs better data, you know exactly which records are near-misses and what single rule they failed. When a new team member asks what the project's visual language is, the answer isn't a Figma board — it's a searchable constitution with 1,182 graded examples.
|
|
115
115
|
|
|
116
|
+
## Proven in production
|
|
117
|
+
|
|
118
|
+
This isn't a demo pipeline. Two real style-LoRAs have shipped end-to-end through it — the same canon → curate → train → ship loop, at opposite ends of the curation spectrum.
|
|
119
|
+
|
|
120
|
+
- **[Tallow Fen](handbook/case-study-tallow-fen/)** (creature-design) — a from-scratch bestiary canon, **~34% approval** across 293 curated records (169 rejected — the gate rejects hard). Shipped `tallow_fen_style_v3.safetensors` @ 1.5 on `qwen-image`.
|
|
121
|
+
- **[Rustline](handbook/case-study-rustline/)** (concept-design) — dense, pre-formed canon, **~96% approval** across 180 records. Shipped `rustline_v3ckpt_1500.safetensors` @ 1.0 on `qwen-image`, reused downstream by a second project.
|
|
122
|
+
|
|
123
|
+
Same pipeline, two production profiles: the curation gate is real (it rejects hard on open subjects), and disciplined canon yields high acceptance.
|
|
124
|
+
|
|
116
125
|
## Five domains, real rules
|
|
117
126
|
|
|
118
127
|
Not placeholder templates. Each domain ships with production-grade constitution rules, lane definitions, scoring rubrics, and group vocabulary.
|
|
@@ -156,6 +165,8 @@ These are not aspirational. They are enforced.
|
|
|
156
165
|
- **Snapshots are immutable.** Config fingerprint (SHA-256) proves nothing changed.
|
|
157
166
|
- **Splits prevent leakage.** Subject families (by identity, lineage, or ID suffix) never cross partition boundaries.
|
|
158
167
|
- **Manifests are frozen contracts.** Export hash + config fingerprint. If anything changes, create a new one.
|
|
168
|
+
- **Runs pin their exact graph.** Every generation records `comfy_workflow_sha` + model/LoRA content hashes + seed policy, so a wave is byte-for-byte replayable — identical across the JS and Python runners. Model hashing is opt-in (`--hash-models`) and never fabricated.
|
|
169
|
+
- **No model verifies its own output.** Judgments record `judged_by_model` and `generator_model`; a warning fires if they are ever the same model.
|
|
159
170
|
- **Adapters cannot mutate truth.** Different layout, same records. No additions, no removals, no reclassification.
|
|
160
171
|
- **Generated outputs re-enter through review.** No bypass. Curate and bind like everything else.
|
|
161
172
|
|
package/README.pt-BR.md
CHANGED
|
@@ -113,6 +113,15 @@ O Style Dataset Lab torna a conexão explícita. Sua constituição define as re
|
|
|
113
113
|
|
|
114
114
|
O resultado prático: quando seu LoRA se desvia, você pode perguntar *por quê*. Quando sua próxima rodada de treinamento precisar de melhores dados, você saberá exatamente quais registros estão próximos do ideal e qual regra única eles não cumpriram. Quando um novo membro da equipe perguntar qual é a linguagem visual do projeto, a resposta não será uma placa Figma — será uma constituição pesquisável com 1.182 exemplos graduados.
|
|
115
115
|
|
|
116
|
+
## Comprovado em produção
|
|
117
|
+
|
|
118
|
+
Este não é um fluxo de trabalho de demonstração. Duas LoRAs reais foram processadas integralmente através dele — o mesmo ciclo de criação → curadoria → treinamento → lançamento, em extremos opostos do espectro da curadoria.
|
|
119
|
+
|
|
120
|
+
- **[Tallow Fen](handbook/case-study-tallow-fen/)** (design de criaturas) — um bestiário criado do zero, com aproximadamente **34% de aprovação** em 293 registros selecionados (169 rejeitados — a triagem é rigorosa). Lançado `tallow_fen_style_v3.safetensors` @ 1.5 no `qwen-image`.
|
|
121
|
+
- **[Rustline](handbook/case-study-rustline/)** (design de conceito) — um conjunto denso e pré-definido, com aproximadamente **96% de aprovação** em 180 registros. Lançado `rustline_v3ckpt_1500.safetensors` @ 1.0 no `qwen-image`, reutilizado posteriormente por um segundo projeto.
|
|
122
|
+
|
|
123
|
+
O mesmo fluxo de trabalho, dois perfis de produção: a triagem da curadoria é real (rejeita rigorosamente temas abertos) e uma abordagem disciplinada na criação resulta em alta aceitação.
|
|
124
|
+
|
|
116
125
|
## Cinco domínios, regras reais
|
|
117
126
|
|
|
118
127
|
Não são apenas modelos de exemplo. Cada domínio é fornecido com regras de constituição de nível de produção, definições de faixa, rubricas de pontuação e vocabulário de grupo.
|
|
@@ -153,11 +162,13 @@ projects/my-project/
|
|
|
153
162
|
|
|
154
163
|
Estas não são apenas aspirações. Elas são aplicadas.
|
|
155
164
|
|
|
156
|
-
- **Os
|
|
157
|
-
- **As divisões evitam vazamentos.** As famílias de
|
|
158
|
-
- **Os manifestos são contratos
|
|
159
|
-
- **
|
|
160
|
-
- **
|
|
165
|
+
- **Os instantâneos são imutáveis.** A impressão digital da configuração (SHA-256) comprova que nada foi alterado.
|
|
166
|
+
- **As divisões evitam vazamentos.** As famílias de temas (por identidade, linhagem ou sufixo de ID) nunca cruzam os limites das partições.
|
|
167
|
+
- **Os manifestos são contratos fixos.** Exporta o hash + impressão digital da configuração. Se algo mudar, crie um novo.
|
|
168
|
+
- **As execuções fixam seu grafo exato.** Cada geração registra `comfy_workflow_sha` + hashes do conteúdo do modelo/LoRA + política de semente, para que uma série possa ser reproduzida byte a byte — idêntica nos executores JS e Python. O hash do modelo é opcional (`--hash-models`) e nunca é fabricado.
|
|
169
|
+
- **Nenhum modelo verifica seu próprio resultado.** As avaliações registram `judged_by_model` e `generator_model`; um aviso é exibido se eles forem o mesmo modelo.
|
|
170
|
+
- **Os adaptadores não podem alterar a verdade.** Layout diferente, os mesmos registros. Sem adições, sem remoções, sem reclassificação.
|
|
171
|
+
- **As saídas geradas são reintroduzidas através da revisão.** Sem desvio. Curadoria e vinculação como em tudo o mais.
|
|
161
172
|
|
|
162
173
|
## Star Freight
|
|
163
174
|
|
package/README.zh.md
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License"></a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
编写你的视觉规则。生成艺术作品。根据这些规则评估每张图像。将结果作为版本化的、可审计的训练数据进行交付——然后将训练好的模型应用于实际的生产流程中,并将最佳输出反馈到你的语料库中。
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
“风格数据集实验室”将你记录下来的关于艺术风格的内容与你实际用于训练的数据集联系起来,然后贯穿整个生产过程,形成一个闭环。你可以定义一套规范——轮廓规则、调色板约束、材质语言,或者对你的项目有意义的任何内容。流水线会生成候选作品,根据这些规则对其进行评分,并将批准的作品打包成可重现的数据集,其中每条记录都解释了它被包含的原因。
|
|
19
19
|
|
|
20
|
-
然后,生产工作台接管:从项目的真实数据中编译生成简报,通过 ComfyUI
|
|
20
|
+
然后,生产工作台接管:从项目的真实数据中编译生成简报,通过 ComfyUI 运行它们,评估输出结果,批量生成表现图和环境板,选择最佳结果,并将这些结果作为新的候选作品重新导入。循环完成:生成、选择、审查、改进。
|
|
21
21
|
|
|
22
22
|
## 流水线
|
|
23
23
|
|
|
@@ -60,7 +60,7 @@ sdlab reingest selected --selection selection_2026-04-16_001
|
|
|
60
60
|
|
|
61
61
|
## 规范创作
|
|
62
62
|
|
|
63
|
-
在数据集流水线运行之前,“sdlab canon
|
|
63
|
+
在数据集流水线运行之前,“sdlab canon *”命名空间会将你的项目的规范实体存储转换为三个投影,这些投影是训练和生产实际使用的——并且锁定那些不应发生变化的项目。
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
66
|
# Build three projections from the canon entity store:
|
|
@@ -76,7 +76,7 @@ sdlab canon freeze kael_maren --project my-project --reason "prologue portrait l
|
|
|
76
76
|
sdlab canon drift --project my-project
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
“canon build
|
|
79
|
+
“canon build”(构建规范)是基于内容的——它的输出由一个“canon_sha”标识并进行缓存,因此未更改的规范可以立即重建。“canon freeze”(冻结规范)会针对特定的构建记录每次冻结操作,并将结果附加到“freeze-events.jsonl”审计跟踪中: “frozen”(已冻结)条目完全拒绝重新生成,“soft-advisory”(软提示)条目默认情况下拒绝(通过使用“--i-know”选项可以绕过)。“canon drift”(规范漂移)会重新计算每个受监控条目的哈希值,并标记自上次干净构建以来发生更改的任何内容。
|
|
80
80
|
|
|
81
81
|
完整的流程在手册中:[构建规范](handbook/canon-build/)、[冻结规范](handbook/canon-freeze/)和[双 LoRA 堆叠](handbook/two-lora-stacking/)。
|
|
82
82
|
|
|
@@ -86,11 +86,11 @@ sdlab canon drift --project my-project
|
|
|
86
86
|
|
|
87
87
|
| 工件 | 它是什么 |
|
|
88
88
|
|----------|-----------|
|
|
89
|
-
| **Snapshot** |
|
|
89
|
+
| **Snapshot** | 带有配置指纹的冻结记录选择。每个包含项都有明确的原因。 |
|
|
90
90
|
| **Split** | 训练/验证/测试分区,其中主题系列不会跨越边界。 |
|
|
91
|
-
| **Export package** |
|
|
91
|
+
| **Export package** | 自包含数据集:清单、元数据、图像、分割、数据集卡、校验和。 |
|
|
92
92
|
| **Eval pack** | 考虑规范的测试任务:车道覆盖率、禁止漂移、锚点/黄金标准、主题连续性。 |
|
|
93
|
-
| **Training package** |
|
|
93
|
+
| **Training package** | 通过适配器(“diffusers-lora”、“generic-image-caption”)实现的,可用于训练器的布局。相同的真实数据,不同的格式。 |
|
|
94
94
|
| **Eval scorecard** | 根据评估包对生成的输出进行评分后的每个任务的通过/失败结果。 |
|
|
95
95
|
| **Implementation pack** | 提示示例、已知错误、连续性测试和重新导入指南。 |
|
|
96
96
|
|
|
@@ -98,31 +98,40 @@ sdlab canon drift --project my-project
|
|
|
98
98
|
|
|
99
99
|
| 表面 | 它做什么 |
|
|
100
100
|
|---------|-------------|
|
|
101
|
-
| **Compiled brief** |
|
|
101
|
+
| **Compiled brief** | 来自工作流程配置文件 + 项目真实数据的确定性生成指令。 |
|
|
102
102
|
| **Run** | 冻结的执行工件:简报 + 种子 + ComfyUI 输出 + 清单。 |
|
|
103
103
|
| **Critique** | 对运行输出相对于规范进行结构化的多维评估。 |
|
|
104
|
-
| **Batch** |
|
|
105
|
-
| **Selection** |
|
|
106
|
-
| **Re-ingest** |
|
|
104
|
+
| **Batch** | 协调的多插槽生产(表现图、环境板、轮廓包)。 |
|
|
105
|
+
| **Selection** | 创意决策工件:选择了哪些输出,原因是什么,以及它们来自哪里。 |
|
|
106
|
+
| **Re-ingest** | 选定的输出作为带有完整生成来源的候选记录返回。 |
|
|
107
107
|
|
|
108
|
-
##
|
|
108
|
+
## 为什么这个系统存在
|
|
109
109
|
|
|
110
110
|
训练数据是任何视觉 AI 流水线中最重要的工件。但是,大多数训练数据只是一个包含图像的文件夹,没有历史记录、没有判断轨迹,也没有与它应该遵循的风格规则的联系。
|
|
111
111
|
|
|
112
112
|
“风格数据集实验室”使这种联系变得明确。你的规范定义了规则。你的评分标准定义了评分维度。你的策划记录了判断结果。你的规范绑定证明了这种联系。并且你的数据集将所有这些内容作为结构化、可查询、可重现的真实数据进行传递。
|
|
113
113
|
|
|
114
|
-
实际效果:当你的 LoRA 发生漂移时,你可以询问*原因*。当你的下一个训练轮次需要更好的数据时,你确切地知道哪些记录是接近成功的,以及它们未能满足哪个单一规则。当新的团队成员询问项目的视觉语言是什么时,答案不是 Figma 板——而是一个可搜索的规范,其中包含 1,182
|
|
114
|
+
实际效果:当你的 LoRA 发生漂移时,你可以询问*原因*。当你的下一个训练轮次需要更好的数据时,你确切地知道哪些记录是接近成功的,以及它们未能满足哪个单一规则。当新的团队成员询问项目的视觉语言是什么时,答案不是 Figma 板——而是一个可搜索的规范,其中包含 1,182 个分级的示例。
|
|
115
|
+
|
|
116
|
+
## 在生产中得到验证
|
|
117
|
+
|
|
118
|
+
这不是一个演示流水线。两个真实的风格 LoRA 都通过它完成了端到端的流程——相同的规范 → 策划 → 训练 → 发布循环,位于策划频谱的两端。
|
|
119
|
+
|
|
120
|
+
- **[Tallow Fen](handbook/case-study-tallow-fen/)**(生物设计)——一个从头开始构建的奇幻生物典籍规范,在 293 个策划记录中,大约有 **34% 的批准率**(169 个被拒绝——审核严格)。发布了 `tallow_fen_style_v3.safetensors`,权重为 1.5,用于 `qwen-image`。
|
|
121
|
+
- **[Rustline](handbook/case-study-rustline/)**(概念设计)——密集、预先构建的规范,在 180 个记录中,大约有 **96% 的批准率**。发布了 `rustline_v3ckpt_1500.safetensors`,权重为 1.0,用于 `qwen-image`,并在后续项目中被第二个项目重用。
|
|
122
|
+
|
|
123
|
+
相同的流水线,两种生产配置:策划审核是真实的(它会严格拒绝开放主题),并且严谨的规范可以产生很高的接受率。
|
|
115
124
|
|
|
116
125
|
## 五个领域,真实的规则
|
|
117
126
|
|
|
118
|
-
|
|
127
|
+
不是占位符模板。每个领域都提供生产级别的规范规则、车道定义、评分标准和组词汇表。
|
|
119
128
|
|
|
120
|
-
| 领域 |
|
|
129
|
+
| 领域 | 通道 | 评估标准 |
|
|
121
130
|
|--------|-------|-----------------|
|
|
122
|
-
| **game-art** |
|
|
123
|
-
| **character-design** |
|
|
124
|
-
| **creature-design** |
|
|
125
|
-
| **architecture** | 外部、内部、街景、结构细节、废墟、景观 |
|
|
131
|
+
| **game-art** | 角色、环境、道具、用户界面、飞船、内部场景、设备 | 游戏中的轮廓、阵营特征、磨损和老化效果 |
|
|
132
|
+
| **character-design** | 肖像、全身图、旋转图、表情表、动作姿势 | 比例、服装逻辑、个性、手势清晰度 |
|
|
133
|
+
| **creature-design** | 概念图、正投影图、细节研究、动态效果、比例参考、栖息地 | 解剖结构、进化逻辑、轮廓区分 |
|
|
134
|
+
| **architecture** | 外部、内部、街景、结构细节、废墟、景观 | 结构、材质一致性、透视、时代背景的一致性 |
|
|
126
135
|
| **vehicle-mech** | 外部、驾驶舱、组件、示意图、轮廓图、损坏变体 | 机械逻辑、设计语言、访问点、损坏叙事 |
|
|
127
136
|
|
|
128
137
|
## 项目结构
|
|
@@ -154,14 +163,16 @@ projects/my-project/
|
|
|
154
163
|
这些不是理想化的,而是强制执行的。
|
|
155
164
|
|
|
156
165
|
- **快照是不可变的。** 配置指纹(SHA-256)证明没有任何更改。
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
166
|
+
- **分割可以防止数据泄露。** 主题组(按身份、血统或 ID 后缀划分)永远不会跨越分区边界。
|
|
167
|
+
- **清单是冻结的合同。** 导出哈希 + 配置指纹。如果发生任何更改,请创建一个新的。
|
|
168
|
+
- **运行会固定其确切的图。** 每次生成都会记录 `comfy_workflow_sha` + 模型/LoRA 内容哈希 + 种子策略,因此一个批次可以完全重现——在 JS 和 Python 运行器中都是相同的。模型哈希是可选的(`--hash-models`),并且绝不会被伪造。
|
|
169
|
+
- **没有模型会验证自己的输出。** 评估会记录 `judged_by_model` 和 `generator_model`;如果它们曾经是同一个模型,则会发出警告。
|
|
170
|
+
- **适配器不能改变真相。** 不同的布局,相同的数据。没有添加、删除或重新分类。
|
|
171
|
+
- **生成的输出通过审核后重新进入。** 没有绕过。像其他所有内容一样进行策划和绑定。
|
|
161
172
|
|
|
162
173
|
## 星际货运
|
|
163
174
|
|
|
164
|
-
|
|
175
|
+
该仓库包含一个完整的可运行示例:1,182 条记录、5 个阵营、7 个通道、24 条宪法规则、892 个已批准的资源、2 个训练配置文件。这是一个粗犷的科幻 RPG 视觉规范,经过完全策划。
|
|
165
176
|
|
|
166
177
|
```bash
|
|
167
178
|
git clone https://github.com/mcp-tool-shop-org/style-dataset-lab
|
|
@@ -181,11 +192,11 @@ sdlab split build --project star-freight # zero subject leakage
|
|
|
181
192
|
npm install -g @mcptoolshop/style-dataset-lab
|
|
182
193
|
```
|
|
183
194
|
|
|
184
|
-
需要 Node.js 20+ 和本地主机上的 [ComfyUI](https://github.com/comfyanonymous/ComfyUI)
|
|
195
|
+
需要 Node.js 20+ 和本地主机上的 [ComfyUI](https://github.com/comfyanonymous/ComfyUI),地址为 8188,用于生成。
|
|
185
196
|
|
|
186
|
-
###
|
|
197
|
+
### 在不使用 ComfyUI 的情况下试用
|
|
187
198
|
|
|
188
|
-
|
|
199
|
+
您可以探索完整的非生成界面——检查、策划、快照、分割、导出——使用捆绑的星际货运项目,而无需安装 ComfyUI 或下载任何 SDXL 权重。
|
|
189
200
|
|
|
190
201
|
```bash
|
|
191
202
|
# Scaffold a fresh project (no ComfyUI needed)
|
|
@@ -198,13 +209,13 @@ sdlab project doctor --project test
|
|
|
198
209
|
sdlab snapshot create --dry-run --project star-freight
|
|
199
210
|
```
|
|
200
211
|
|
|
201
|
-
`sdlab project doctor`
|
|
212
|
+
`sdlab project doctor` 会验证每个项目配置(宪法、通道、评分标准、术语),并在不触及 GPU 的情况下报告资格。任何会更改生成状态的命令都接受 `--dry-run`,以便首先预览效果。
|
|
202
213
|
|
|
203
|
-
如果您忘记了 `--project`,CLI
|
|
214
|
+
如果您忘记了 `--project`,CLI 将回退到在 `projects/` 下找到的第一个项目并打印警告——明确传递 `--project` 以使其停止发出警告。
|
|
204
215
|
|
|
205
216
|
### 恢复中断的运行
|
|
206
217
|
|
|
207
|
-
|
|
218
|
+
可以恢复长时间的生成运行,而无需重新执行已完成的工作:
|
|
208
219
|
|
|
209
220
|
```bash
|
|
210
221
|
# Skip subjects whose record + image are already on disk.
|
|
@@ -222,8 +233,8 @@ sdlab batch generate --resume batch_2026-04-22_001 --project my-project
|
|
|
222
233
|
|
|
223
234
|
常见的失败模式和解决方法:
|
|
224
235
|
|
|
225
|
-
**任何 `sdlab generate` / `sdlab run generate` / `sdlab batch generate`
|
|
226
|
-
ComfyUI
|
|
236
|
+
**任何 `sdlab generate` / `sdlab run generate` / `sdlab batch generate` 上的 `ECONNREFUSED 127.0.0.1:8188`**
|
|
237
|
+
ComfyUI 没有运行。启动 ComfyUI(`python main.py --listen 127.0.0.1 --port 8188`),并使用 `curl http://127.0.0.1:8188/system_stats` 进行确认。要指向不同的主机/端口,请设置 `COMFY_URL=http://host:port`。
|
|
227
238
|
|
|
228
239
|
**`missing checkpoint` / `LoRA weight not found`**
|
|
229
240
|
您的工作流程配置文件指定了一个不在 ComfyUI 的 `models/checkpoints/` 或 `models/loras/` 文件夹中的模型文件。打开 `projects/<project>/workflows/profiles/<profile>.json`,找到 `checkpoint` 或 `lora` 字段,然后下载引用的权重或将其替换为您已有的权重。重新运行 `sdlab project doctor --project <project>` 以确认修复。
|
|
@@ -232,25 +243,25 @@ ComfyUI 未运行。启动 ComfyUI (`python main.py --listen 127.0.0.1 --port 81
|
|
|
232
243
|
Doctor 返回结构化的错误代码。常见的错误:
|
|
233
244
|
- `E_PROJECT_NOT_FOUND`——项目目录在 `projects/` 下不存在。检查拼写。
|
|
234
245
|
- `E_CONFIG_INVALID`——五个 JSON 配置文件中的一个未能通过模式验证。`hint` 字段指定了错误的 文件和字段。
|
|
235
|
-
- `E_RECORD_DRIFT
|
|
246
|
+
- `E_RECORD_DRIFT`——记录的配置指纹不再与其来源匹配。如提示所述,重新策划或重新绑定。
|
|
236
247
|
|
|
237
248
|
**`No --project specified, falling back to <name>`**
|
|
238
|
-
|
|
249
|
+
一个软警告。明确传递 `--project <name>` 以选择正确的项目并停止发出警告。
|
|
239
250
|
|
|
240
251
|
**绘画风格 / VRAM 内存不足问题**
|
|
241
252
|
请参阅 `docs/internal/HANDOFF.md`,了解有关绘画风格降噪调整说明。简而言之:降低降噪强度、减少批处理大小或在工作流程配置文件中切换到较小的检查点。
|
|
242
253
|
|
|
243
254
|
**报告错误**
|
|
244
|
-
在 https://github.com/mcp-tool-shop-org/style-dataset-lab/issues 上提交问题,并提供您的 sdlab
|
|
255
|
+
在 https://github.com/mcp-tool-shop-org/style-dataset-lab/issues 上提交问题,并提供您的 sdlab 版本(`sdlab --version`)、Node 版本(`node -v`)、完整的命令以及结构化的错误输出。一个错误报告模板会预填充这些字段。
|
|
245
256
|
|
|
246
257
|
## 安全性
|
|
247
258
|
|
|
248
259
|
仅限本地使用。没有遥测、分析或外部请求。图像保留在您的 GPU 和文件系统中。
|
|
249
260
|
|
|
250
|
-
##
|
|
261
|
+
## 许可协议
|
|
251
262
|
|
|
252
263
|
MIT
|
|
253
264
|
|
|
254
265
|
---
|
|
255
266
|
|
|
256
|
-
由 <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a>
|
|
267
|
+
由 <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a> 构建。
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
import { writeFile, mkdir } from 'node:fs/promises';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { comfyHealth, submitAndWait, downloadImage } from '../comfyui.js';
|
|
11
|
-
import { buildWorkflowGraph, resolveTemplateId } from './comfyui-workflows.js';
|
|
11
|
+
import { buildWorkflowGraph, resolveTemplateId, QWEN_DEFAULTS } from './comfyui-workflows.js';
|
|
12
12
|
import { buildSeedPlan, prepareRunDir, saveRunManifest } from '../runtime-runs.js';
|
|
13
13
|
import { loadProjectConfig } from '../config.js';
|
|
14
14
|
import { pickOutputImage } from '../comfyui-output.js';
|
|
15
|
+
import { buildPinning, seedPolicyFromMode, createHashCache, flushHashCache } from '../run-manifest.js';
|
|
16
|
+
import { SCHEMA_VERSION } from '../snapshot.js';
|
|
15
17
|
import { log, info, warn } from '../log.js';
|
|
16
18
|
import { runtimeError } from '../errors.js';
|
|
17
19
|
|
|
@@ -26,7 +28,7 @@ import { runtimeError } from '../errors.js';
|
|
|
26
28
|
* @param {boolean} [opts.dryRun] — if true, prepare run dir but do not submit
|
|
27
29
|
* @returns {Promise<Object>} — manifest
|
|
28
30
|
*/
|
|
29
|
-
export async function executeRun({ projectRoot, projectName, brief, baseSeed, dryRun = false }) {
|
|
31
|
+
export async function executeRun({ projectRoot, projectName, brief, baseSeed, dryRun = false, hashModels = false }) {
|
|
30
32
|
const config = loadProjectConfig(projectRoot);
|
|
31
33
|
const defaults = config.meta?.defaults ?? {};
|
|
32
34
|
const comfyUrl = defaults.comfy_url || process.env.COMFY_URL || 'http://127.0.0.1:8188';
|
|
@@ -138,9 +140,40 @@ export async function executeRun({ projectRoot, projectName, brief, baseSeed, dr
|
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
// PIN_PER_STEP: pin the exact graph + model/LoRA content identity + seed
|
|
144
|
+
// policy via the shared contract (lib/run-manifest.js). One run = one prompt
|
|
145
|
+
// across N seeds, so the pin is wave-level (per-item seed is normalized out of
|
|
146
|
+
// comfy_workflow_sha and kept in seed_plan). Best-effort — a build/hash failure
|
|
147
|
+
// degrades to null pinning rather than failing the run.
|
|
148
|
+
let pinning = null;
|
|
149
|
+
try {
|
|
150
|
+
const isQwen = String(defaults.base || '').toLowerCase() === 'qwen-image';
|
|
151
|
+
const pinModels = isQwen
|
|
152
|
+
? {
|
|
153
|
+
unet: defaults.unet || QWEN_DEFAULTS.unet,
|
|
154
|
+
clip: defaults.clip || QWEN_DEFAULTS.clip,
|
|
155
|
+
vae: defaults.vae || QWEN_DEFAULTS.vae,
|
|
156
|
+
}
|
|
157
|
+
: { checkpoint: defaults.checkpoint };
|
|
158
|
+
const pinCache = hashModels ? createHashCache() : undefined;
|
|
159
|
+
const { nodes: pinNodes } = buildWorkflowGraph({ brief, projectDefaults: defaults, seed: seedPlan.seeds[0] ?? 0 });
|
|
160
|
+
pinning = buildPinning({
|
|
161
|
+
graph: pinNodes,
|
|
162
|
+
models: pinModels,
|
|
163
|
+
loras: defaults.loras || [],
|
|
164
|
+
seedPolicy: seedPolicyFromMode(seedMode),
|
|
165
|
+
hashModels,
|
|
166
|
+
cache: pinCache,
|
|
167
|
+
});
|
|
168
|
+
flushHashCache(pinCache);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
warn(`pinning skipped: ${err.message}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
141
173
|
// Build manifest
|
|
142
174
|
const manifest = {
|
|
143
175
|
run_id: runId,
|
|
176
|
+
schema_version: SCHEMA_VERSION,
|
|
144
177
|
brief_id: brief.brief_id,
|
|
145
178
|
project_id: brief.project_id,
|
|
146
179
|
workflow_template_id: templateId,
|
|
@@ -152,6 +185,7 @@ export async function executeRun({ projectRoot, projectName, brief, baseSeed, dr
|
|
|
152
185
|
comfy_url: comfyUrl,
|
|
153
186
|
checkpoint: defaults.checkpoint,
|
|
154
187
|
loras: defaults.loras || [],
|
|
188
|
+
pinning,
|
|
155
189
|
runtime_plan: brief.runtime_plan,
|
|
156
190
|
outputs,
|
|
157
191
|
errors,
|
package/lib/critique-engine.js
CHANGED
|
@@ -19,6 +19,7 @@ import { loadRun } from './runtime-runs.js';
|
|
|
19
19
|
import { getWorkflowProfile } from './workflow-profiles.js';
|
|
20
20
|
import { loadProjectConfig } from './config.js';
|
|
21
21
|
import { inputError } from './errors.js';
|
|
22
|
+
import { RULE_BASED_JUDGE, generatorModelFromManifest, warnIfSelfVerification } from './verifier.js';
|
|
22
23
|
|
|
23
24
|
// ─── Dimension catalogue ─────────────────────────────────────────
|
|
24
25
|
|
|
@@ -188,7 +189,7 @@ export function getActiveDimensions({ brief, workflow }) {
|
|
|
188
189
|
* A human or LLM fills in the actual per-image observations via
|
|
189
190
|
* `sdlab critique --run <id>` which creates a template to complete.
|
|
190
191
|
*/
|
|
191
|
-
export function critiqueCandidate({ brief, workflow, candidate, activeDimensions }) {
|
|
192
|
+
export function critiqueCandidate({ brief, workflow, candidate, activeDimensions, generatorModel = null }) {
|
|
192
193
|
const strengths = [];
|
|
193
194
|
const driftIssues = [];
|
|
194
195
|
const preserveNextPass = [];
|
|
@@ -254,6 +255,10 @@ export function critiqueCandidate({ brief, workflow, candidate, activeDimensions
|
|
|
254
255
|
filename: candidate.filename,
|
|
255
256
|
seed: candidate.seed,
|
|
256
257
|
overall_fit: 'usable', // Default — human or LLM overrides
|
|
258
|
+
// EXTERNAL_VERIFIER: this rule-based engine is a different artifact class
|
|
259
|
+
// from the image generator, so it is a legitimate cross-artifact verifier.
|
|
260
|
+
judged_by_model: RULE_BASED_JUDGE,
|
|
261
|
+
generator_model: generatorModel,
|
|
257
262
|
strengths,
|
|
258
263
|
drift_issues: driftIssues,
|
|
259
264
|
preserve_next_pass: preserveNextPass,
|
|
@@ -261,6 +266,37 @@ export function critiqueCandidate({ brief, workflow, candidate, activeDimensions
|
|
|
261
266
|
};
|
|
262
267
|
}
|
|
263
268
|
|
|
269
|
+
// ─── Triage (UNCERTAINTY_GATED_HUMANS) ───────────────────────────
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Default drift-issue count at/above which a candidate is flagged for human
|
|
273
|
+
* attention. Tunable per invocation via `sdlab critique --triage --drift-threshold N`.
|
|
274
|
+
*/
|
|
275
|
+
export const DEFAULT_DRIFT_THRESHOLD = 3;
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Split candidates into the ones a human should look at vs the ones the
|
|
279
|
+
* rule-based pass cleared — so attention gates on UNCERTAINTY, not on every item
|
|
280
|
+
* (Buçinca et al. arXiv:2410.04253). A candidate is flagged when it is
|
|
281
|
+
* `off-model` (a human/LLM downgraded its fit) OR carries >= driftThreshold
|
|
282
|
+
* drift issues. The full report is unchanged; triage is a VIEW over it.
|
|
283
|
+
*
|
|
284
|
+
* @param {Array} candidates — report.candidates
|
|
285
|
+
* @param {Object} [opts]
|
|
286
|
+
* @param {number} [opts.driftThreshold=DEFAULT_DRIFT_THRESHOLD]
|
|
287
|
+
* @returns {{ flagged: Array, suppressed: Array, driftThreshold: number }}
|
|
288
|
+
*/
|
|
289
|
+
export function triageCandidates(candidates, { driftThreshold = DEFAULT_DRIFT_THRESHOLD } = {}) {
|
|
290
|
+
const flagged = [];
|
|
291
|
+
const suppressed = [];
|
|
292
|
+
for (const c of candidates || []) {
|
|
293
|
+
const driftCount = c.drift_issues?.length ?? 0;
|
|
294
|
+
const uncertain = c.overall_fit === 'off-model' || driftCount >= driftThreshold;
|
|
295
|
+
(uncertain ? flagged : suppressed).push(c);
|
|
296
|
+
}
|
|
297
|
+
return { flagged, suppressed, driftThreshold };
|
|
298
|
+
}
|
|
299
|
+
|
|
264
300
|
// ─── Run-level recommendation ────────────────────────────────────
|
|
265
301
|
|
|
266
302
|
/**
|
|
@@ -356,6 +392,12 @@ export async function critiqueRun({ projectRoot, projectId, runId }) {
|
|
|
356
392
|
);
|
|
357
393
|
}
|
|
358
394
|
|
|
395
|
+
// EXTERNAL_VERIFIER: derive the generator model id from the run manifest and
|
|
396
|
+
// warn once if the rule-based judge ever coincides with it (it never does
|
|
397
|
+
// today — the rule engine is a different artifact class from the generator).
|
|
398
|
+
const generatorModel = generatorModelFromManifest(manifest);
|
|
399
|
+
warnIfSelfVerification(RULE_BASED_JUDGE, generatorModel, `run ${runId}`);
|
|
400
|
+
|
|
359
401
|
// Critique each candidate
|
|
360
402
|
const candidates = successfulOutputs.map(output =>
|
|
361
403
|
critiqueCandidate({
|
|
@@ -363,6 +405,7 @@ export async function critiqueRun({ projectRoot, projectId, runId }) {
|
|
|
363
405
|
workflow,
|
|
364
406
|
candidate: { filename: output.filename, seed: output.seed },
|
|
365
407
|
activeDimensions,
|
|
408
|
+
generatorModel,
|
|
366
409
|
})
|
|
367
410
|
);
|
|
368
411
|
|
|
@@ -377,6 +420,8 @@ export async function critiqueRun({ projectRoot, projectId, runId }) {
|
|
|
377
420
|
workflow_id: brief.workflow_id,
|
|
378
421
|
created_at: new Date().toISOString(),
|
|
379
422
|
active_dimensions: activeDimensions,
|
|
423
|
+
judged_by_model: RULE_BASED_JUDGE,
|
|
424
|
+
generator_model: generatorModel,
|
|
380
425
|
candidates,
|
|
381
426
|
recommended_action: recommended,
|
|
382
427
|
};
|
package/lib/critique-render.js
CHANGED
|
@@ -123,6 +123,51 @@ export function renderCritiqueMarkdown(report) {
|
|
|
123
123
|
return lines.join('\n');
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// ─── Triage renderer (UNCERTAINTY_GATED_HUMANS) ──────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Render only the candidates that need human attention, with a contrastive
|
|
130
|
+
* lead explaining what was suppressed and why. `triage` is the output of
|
|
131
|
+
* critique-engine's triageCandidates({flagged, suppressed, driftThreshold}).
|
|
132
|
+
*/
|
|
133
|
+
export function renderCritiqueTriage(report, triage) {
|
|
134
|
+
const { flagged, suppressed, driftThreshold } = triage;
|
|
135
|
+
const total = flagged.length + suppressed.length;
|
|
136
|
+
const lines = [];
|
|
137
|
+
|
|
138
|
+
lines.push(`Critique triage: ${report.run_id}`);
|
|
139
|
+
lines.push(`Brief: ${report.brief_id} Workflow: ${report.workflow_id}`);
|
|
140
|
+
lines.push('');
|
|
141
|
+
// Contrastive framing: state the default (skip most) and why (attention gates
|
|
142
|
+
// on uncertainty, not on count). Horvitz CHI 1999 / Buçinca arXiv:2410.04253.
|
|
143
|
+
lines.push(
|
|
144
|
+
`Default: SKIP ${suppressed.length}/${total} candidate(s) the rule-based pass cleared. ` +
|
|
145
|
+
`Showing ${flagged.length} that need your attention — flagged as off-model OR ≥ ${driftThreshold} drift issues. ` +
|
|
146
|
+
`Attention gates on uncertainty, not on every item.`,
|
|
147
|
+
);
|
|
148
|
+
lines.push('');
|
|
149
|
+
|
|
150
|
+
if (flagged.length === 0) {
|
|
151
|
+
lines.push(' (nothing flagged — no candidate is off-model and none crossed the drift threshold)');
|
|
152
|
+
lines.push('');
|
|
153
|
+
return lines.join('\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const c of flagged) {
|
|
157
|
+
const badge = FIT_BADGES[c.overall_fit] || '? unknown';
|
|
158
|
+
const driftCount = c.drift_issues?.length ?? 0;
|
|
159
|
+
const why = c.overall_fit === 'off-model'
|
|
160
|
+
? 'off-model'
|
|
161
|
+
: `${driftCount} drift issues (≥ ${driftThreshold})`;
|
|
162
|
+
lines.push(` ${badge} ${c.filename} (seed: ${c.seed ?? '?'}) — flagged: ${why}`);
|
|
163
|
+
if (c.drift_issues?.length > 0) {
|
|
164
|
+
lines.push(` drift: ${c.drift_issues.slice(0, 3).join('; ')}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
lines.push('');
|
|
168
|
+
return lines.join('\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
126
171
|
// ─── Terminal text renderer ──────────────────────────────────────
|
|
127
172
|
|
|
128
173
|
/**
|
package/lib/freeze-gate.js
CHANGED
|
@@ -24,9 +24,43 @@ import { existsSync } from 'node:fs';
|
|
|
24
24
|
import { loadBuildConfig } from './canon-build/load-config.js';
|
|
25
25
|
import { loadCanonEntriesInDir, entryId } from './canon-build/load-entry.js';
|
|
26
26
|
import { readFreezeStatus } from './freeze-stamp.js';
|
|
27
|
-
import { appendEvent } from './freeze-events.js';
|
|
27
|
+
import { appendEvent, readEventsFor } from './freeze-events.js';
|
|
28
28
|
import { inputError } from './errors.js';
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Best-effort freeze date for an entry: the `at` of its most recent freeze /
|
|
32
|
+
* override event. Returns null when the log is absent/unreadable (the freeze
|
|
33
|
+
* block itself carries no timestamp). Used only to enrich the contrastive
|
|
34
|
+
* refuse message — never load-bearing for the gate decision.
|
|
35
|
+
*/
|
|
36
|
+
async function latestFreezeAt(projectRoot, id) {
|
|
37
|
+
try {
|
|
38
|
+
const events = await readEventsFor(projectRoot, id);
|
|
39
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
40
|
+
if (events[i].type === 'freeze' || events[i].type === 'override') return events[i].at;
|
|
41
|
+
}
|
|
42
|
+
} catch { /* no log / unreadable → no date */ }
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Contrastive lead (Horvitz CHI 1999): state the DEFAULT action and the system's
|
|
48
|
+
* reasoning — who froze it, when, and why — before the override path. This is the
|
|
49
|
+
* "you probably expected to just regenerate; I'm refusing, here's why" framing.
|
|
50
|
+
*/
|
|
51
|
+
function contrastiveLead(id, status, freeze, frozenAt) {
|
|
52
|
+
const by = freeze?.frozen_by || 'unknown';
|
|
53
|
+
const reason = freeze?.frozen_reason || '(no reason recorded)';
|
|
54
|
+
const when = frozenAt ? ` on ${String(frozenAt).slice(0, 10)}` : '';
|
|
55
|
+
return `Default: REFUSE — "${id}" is ${status} frozen by ${by}${when}, reason: "${reason}".`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Render the watched-field list for the contrastive override condition. */
|
|
59
|
+
function watchList(freeze) {
|
|
60
|
+
const w = freeze?.watch_fields || [];
|
|
61
|
+
return w.length ? `[${w.join(', ')}]` : '[no watched fields]';
|
|
62
|
+
}
|
|
63
|
+
|
|
30
64
|
/**
|
|
31
65
|
* Load every canon entry in a project, keyed by schema.
|
|
32
66
|
*
|
|
@@ -109,27 +143,36 @@ export async function assertNotFrozen({
|
|
|
109
143
|
const status = readFreezeStatus(fm);
|
|
110
144
|
const id = entryId(resolved.entry);
|
|
111
145
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
118
|
-
|
|
146
|
+
// Contrastive refuse messaging (Horvitz CHI 1999): lead with the default
|
|
147
|
+
// action + the system's reasoning (who/when/why) pulled from the freeze
|
|
148
|
+
// stamp, then state the override path and its condition (don't touch the
|
|
149
|
+
// watched fields). frozenAt is best-effort from the event log.
|
|
150
|
+
if (status === 'frozen' || status === 'soft-advisory') {
|
|
151
|
+
const frozenAt = await latestFreezeAt(projectRoot, id);
|
|
152
|
+
const lead = contrastiveLead(id, status, fm.freeze, frozenAt);
|
|
153
|
+
const watched = watchList(fm.freeze);
|
|
154
|
+
|
|
155
|
+
if (status === 'frozen') {
|
|
156
|
+
throw inputError(
|
|
157
|
+
'CANON_ENTRY_FROZEN',
|
|
158
|
+
`${lead} A hard freeze has no inline bypass, so "${action}" will not proceed.`,
|
|
159
|
+
`Unfreeze with: sdlab canon unfreeze ${id} --reason "<why>" (appended to canon-build/freeze-events.jsonl as an audit record). This freeze watches ${watched}.`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
119
162
|
|
|
120
|
-
|
|
163
|
+
// soft-advisory
|
|
121
164
|
if (!allowSoftAdvisoryBypass) {
|
|
122
165
|
throw inputError(
|
|
123
166
|
'CANON_ENTRY_FROZEN',
|
|
124
|
-
|
|
125
|
-
`
|
|
167
|
+
`${lead} "${action}" has no --i-know bypass.`,
|
|
168
|
+
`Run: sdlab canon unfreeze ${id} --reason "<why>" for a proper unfreeze, or pick a command that accepts --i-know. Watched fields: ${watched}.`,
|
|
126
169
|
);
|
|
127
170
|
}
|
|
128
171
|
if (!bypassReason) {
|
|
129
172
|
throw inputError(
|
|
130
173
|
'CANON_ENTRY_FROZEN',
|
|
131
|
-
|
|
132
|
-
`Re-run with
|
|
174
|
+
`${lead} Override requires --i-know AND --reason.`,
|
|
175
|
+
`Re-run with --i-know --reason "<why>" ONLY if your change does not touch the watched fields ${watched}. The bypass is logged to canon-build/freeze-events.jsonl.`,
|
|
133
176
|
);
|
|
134
177
|
}
|
|
135
178
|
// Bypass allowed — log and proceed.
|