drydock-cli 3.0.116__tar.gz → 3.0.118__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.
- {drydock_cli-3.0.116/drydock_cli.egg-info → drydock_cli-3.0.118}/PKG-INFO +1 -1
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/recipes.py +113 -0
- drydock_cli-3.0.118/drydock/suggest.py +30 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tui/app.py +45 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118/drydock_cli.egg-info}/PKG-INFO +1 -1
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock_cli.egg-info/SOURCES.txt +2 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/pyproject.toml +1 -1
- drydock_cli-3.0.118/tests/test_suggest.py +20 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/LICENSE +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/NOTICE +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/README.md +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/__init__.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/__main__.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/advisor.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/agent.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/bash_safety.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/builtin_skills/__init__.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/builtin_skills/rmf-categorize.md +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/builtin_skills/rmf-control.md +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/builtin_skills/rmf-poam.md +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/builtin_skills/rmf-review.md +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/builtin_skills/stig-assess.md +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/builtin_skills/stig-remediate.md +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/cci.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/cli.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/compaction.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/config.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/detect.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/extract.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/gittools.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/graphrag.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/guards.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/loop_detect.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/mcp.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/poam.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/providers.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/rmf.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/rmf_graph.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/skills.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/stig.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tool_registry.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tools/__init__.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tui/__init__.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tui/approval.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tui/messages.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tui/widgets.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/tuning.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock/web.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock_cli.egg-info/dependency_links.txt +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock_cli.egg-info/entry_points.txt +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock_cli.egg-info/requires.txt +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/drydock_cli.egg-info/top_level.txt +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/setup.cfg +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_advisor.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_approval.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_back_command.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_background.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_binary_output.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_crossplatform.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_output_bounding.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_process_group.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_safety.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_sanitize.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_shell.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_stdin.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_stop_partial.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_timeout_network.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_bash_timeout_param.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_cci.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_cli_agents.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_compact_command.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_compaction.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_config.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_config_migration.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_context_limit_config.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_detect.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_dispatch.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_e2e_connected.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_edit_replace_all.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_empty_response.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_extract.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_failure_loop.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_first_run_setup.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_gittools.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_glob_edit_edges.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_graphify_example.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_graphrag.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_graphrag_quoted_path.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_grep_and_read_robust.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_guards_and_tools.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_hallucinated_tools.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_leaked_tool_call.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_loop_detect.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_mcp.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_oneshot_unreachable.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_overthink_interrupt.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_plan_autocontinue.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_poam.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_providers_unreachable.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_read_index.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_recipes.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_repetition_interrupt.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_rmf.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_rmf_graph.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_rmf_stig_graph.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_runaway_repetition.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_screenshot.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_server_probe.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_skills.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_stall_retry.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_stig.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_stop.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_streaming_newlines.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_subagent.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_system_prompt_help.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_todo.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_tool_arg_coercion.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_tool_arg_coercion_more.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_tool_arg_parsing.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_tools_undo.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_tui.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_tuning.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_viewimage.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_vision_input.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_web_tools.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_windows_shell.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_write_content_coerce.py +0 -0
- {drydock_cli-3.0.116 → drydock_cli-3.0.118}/tests/test_xccdf.py +0 -0
|
@@ -132,6 +132,119 @@ RECIPES: list[tuple[str, list[str], str]] = [
|
|
|
132
132
|
" log f from tangent lines at support points; sample from the hull's exp, accept/\n"
|
|
133
133
|
" reject, and ADD each rejected point to the hull so it tightens over time.\n"
|
|
134
134
|
"Seed the RNG for reproducibility and validate the sample mean/var against f."),
|
|
135
|
+
|
|
136
|
+
("Classification metrics — accuracy, precision, recall, F1, MCC, ROC/AUC, confusion matrix",
|
|
137
|
+
["metric", "metrics", "accuracy", "precision", "recall", "f1", "mcc", "roc",
|
|
138
|
+
"auc", "confusion", "tp", "fp", "fn", "tn", "classification", "eval",
|
|
139
|
+
"evaluate", "score", "true", "positive", "negative"],
|
|
140
|
+
"Given y_true and y_pred (and y_score = P(positive) for ROC):\n"
|
|
141
|
+
" from sklearn.metrics import (accuracy_score, precision_score, recall_score,\n"
|
|
142
|
+
" f1_score, matthews_corrcoef, roc_auc_score, roc_curve, confusion_matrix)\n"
|
|
143
|
+
" acc=accuracy_score(y_true,y_pred); f1=f1_score(y_true,y_pred)\n"
|
|
144
|
+
" prec=precision_score(y_true,y_pred); rec=recall_score(y_true,y_pred)\n"
|
|
145
|
+
" mcc=matthews_corrcoef(y_true,y_pred); auc=roc_auc_score(y_true,y_score)\n"
|
|
146
|
+
" tn,fp,fn,tp = confusion_matrix(y_true,y_pred).ravel() # BINARY order!\n"
|
|
147
|
+
" fpr,tpr,thr = roc_curve(y_true,y_score) # plot tpr vs fpr; AUC=area\n"
|
|
148
|
+
"Definitions: precision=TP/(TP+FP), recall=TP/(TP+FN), F1=2PR/(P+R),\n"
|
|
149
|
+
"MCC=(TP*TN-FP*FN)/sqrt((TP+FP)(TP+FN)(TN+FP)(TN+FN)). Multiclass: pass\n"
|
|
150
|
+
"average='macro'/'weighted'. Save metrics as JSON; plot ROC with matplotlib\n"
|
|
151
|
+
"(plt.plot(fpr,tpr); savefig). Watch class order and the positive label."),
|
|
152
|
+
|
|
153
|
+
("PyTorch training / eval loop (correct structure)",
|
|
154
|
+
["pytorch", "torch", "train", "training", "model", "loss", "optimizer",
|
|
155
|
+
"epoch", "cnn", "network", "nn", "fit", "gpu", "cuda", "dataloader"],
|
|
156
|
+
" import torch, torch.nn as nn\n"
|
|
157
|
+
" dev = 'cuda' if torch.cuda.is_available() else 'cpu'; model.to(dev)\n"
|
|
158
|
+
" opt = torch.optim.AdamW(model.parameters(), lr=1e-3)\n"
|
|
159
|
+
" loss_fn = nn.CrossEntropyLoss() # expects raw logits + int class targets\n"
|
|
160
|
+
" for epoch in range(E):\n"
|
|
161
|
+
" model.train()\n"
|
|
162
|
+
" for xb,yb in train_loader:\n"
|
|
163
|
+
" xb,yb = xb.to(dev), yb.to(dev)\n"
|
|
164
|
+
" opt.zero_grad(); out = model(xb); loss = loss_fn(out, yb)\n"
|
|
165
|
+
" loss.backward(); opt.step()\n"
|
|
166
|
+
" model.eval()\n"
|
|
167
|
+
" with torch.no_grad(): ... # compute val metric, no grad\n"
|
|
168
|
+
"Gotchas: zero_grad EVERY step; CrossEntropyLoss takes logits (no softmax);\n"
|
|
169
|
+
"targets are Long class indices; move BOTH model and batch to the same device;\n"
|
|
170
|
+
"set model.eval() (disables dropout/BN update) for validation. Seed torch for repro."),
|
|
171
|
+
|
|
172
|
+
("LoRA / full fine-tuning with transformers + peft",
|
|
173
|
+
["lora", "peft", "finetune", "fine-tune", "fine", "tuning", "adapter",
|
|
174
|
+
"transformers", "pretrained", "huggingface", "bert", "gpt", "vit", "freeze"],
|
|
175
|
+
"FULL fine-tune: load a pretrained model, train ALL params on your data with a\n"
|
|
176
|
+
"small LR (2e-5..5e-5), AdamW, a few epochs; save with model.save_pretrained(dir).\n"
|
|
177
|
+
"LoRA (train ~0.1% of params): \n"
|
|
178
|
+
" from peft import LoraConfig, get_peft_model, TaskType\n"
|
|
179
|
+
" cfg = LoraConfig(r=8, lora_alpha=16, lora_dropout=0.05,\n"
|
|
180
|
+
" target_modules=['q_proj','v_proj'], task_type=TaskType.SEQ_CLS)\n"
|
|
181
|
+
" model = get_peft_model(base_model, cfg)\n"
|
|
182
|
+
" model.print_trainable_parameters() # sanity: only adapters train\n"
|
|
183
|
+
" ... train ... ; model.save_pretrained('adapter')\n"
|
|
184
|
+
"Reload: PeftModel.from_pretrained(base, 'adapter'). target_modules names differ\n"
|
|
185
|
+
"per architecture — inspect model.named_modules() to find the linear layers."),
|
|
186
|
+
|
|
187
|
+
("Prepare / organize / split a dataset",
|
|
188
|
+
["data", "dataset", "prep", "prepare", "organize", "split", "train", "val",
|
|
189
|
+
"test", "normalize", "preprocess", "balance", "stratify", "label", "clean"],
|
|
190
|
+
" from sklearn.model_selection import train_test_split\n"
|
|
191
|
+
" Xtr,Xtmp,ytr,ytmp = train_test_split(X,y,test_size=0.3,stratify=y,random_state=0)\n"
|
|
192
|
+
" Xval,Xte,yval,yte = train_test_split(Xtmp,ytmp,test_size=0.5,stratify=ytmp,random_state=0)\n"
|
|
193
|
+
"Normalize features on TRAIN ONLY, then apply to val/test (no leakage):\n"
|
|
194
|
+
" from sklearn.preprocessing import StandardScaler\n"
|
|
195
|
+
" sc=StandardScaler().fit(Xtr); Xtr=sc.transform(Xtr); Xte=sc.transform(Xte)\n"
|
|
196
|
+
"Images: resize + ToTensor + Normalize(mean,std). Keep a fixed split (seed) and\n"
|
|
197
|
+
"check class balance (np.bincount(y)). Never fit scalers/encoders on test data."),
|
|
198
|
+
|
|
199
|
+
("Read/write HDF5 (.h5) and convert to/from JSON",
|
|
200
|
+
["h5", "hdf5", "h5py", "json", "convert", "dataset", "array", "save", "load",
|
|
201
|
+
"file", "format", "serialize"],
|
|
202
|
+
" import h5py, numpy as np, json\n"
|
|
203
|
+
" with h5py.File('data.h5','r') as f:\n"
|
|
204
|
+
" print(list(f.keys())); arr = f['images'][:]; labels = f['labels'][:]\n"
|
|
205
|
+
" attrs = dict(f['images'].attrs) # metadata\n"
|
|
206
|
+
" with h5py.File('out.h5','w') as f:\n"
|
|
207
|
+
" f.create_dataset('images', data=arr, compression='gzip')\n"
|
|
208
|
+
"H5<->JSON: arrays aren't JSON-native — use arr.tolist() to dump, np.array to\n"
|
|
209
|
+
"reload. json.dump({'shape':list(arr.shape),'data':arr.tolist()}, open('x.json','w')).\n"
|
|
210
|
+
"For big arrays keep them in H5 and put only metadata/paths in JSON."),
|
|
211
|
+
|
|
212
|
+
("Implement a Transformer block / ViT",
|
|
213
|
+
["transformer", "attention", "vit", "vision", "encoder", "block", "self-attention",
|
|
214
|
+
"multihead", "patch", "embedding", "positional", "layernorm"],
|
|
215
|
+
"Transformer encoder block (pre-norm):\n"
|
|
216
|
+
" attn = nn.MultiheadAttention(d, heads, batch_first=True)\n"
|
|
217
|
+
" x = x + attn(ln1(x), ln1(x), ln1(x))[0] # residual\n"
|
|
218
|
+
" x = x + mlp(ln2(x)) # mlp: Linear(d,4d)->GELU->Linear(4d,d)\n"
|
|
219
|
+
"ViT: split image into patches (Conv2d(kernel=stride=patch) -> flatten), prepend a\n"
|
|
220
|
+
"learnable [CLS] token, ADD positional embeddings, run N encoder blocks, classify\n"
|
|
221
|
+
"from the CLS token. Shapes: (B,C,H,W)->(B, num_patches+1, d). Verify a forward\n"
|
|
222
|
+
"pass runs and output is (B, num_classes); check grads flow (loss.backward())."),
|
|
223
|
+
|
|
224
|
+
("Diagnose a broken PyTorch training run",
|
|
225
|
+
["debug", "diagnose", "nan", "loss", "not", "learning", "overfit", "underfit",
|
|
226
|
+
"shape", "mismatch", "error", "troubleshoot", "fix", "exploding", "gradient"],
|
|
227
|
+
"- Loss is NaN/Inf: LR too high (lower it), log/sqrt of <=0, divide-by-zero, or\n"
|
|
228
|
+
" unnormalized inputs. Add torch.autograd.set_detect_anomaly(True); clip grads\n"
|
|
229
|
+
" (nn.utils.clip_grad_norm_); check for NaN in the data.\n"
|
|
230
|
+
"- Loss flat / not learning: forgot opt.zero_grad or opt.step; LR too low; wrong\n"
|
|
231
|
+
" loss (softmax before CrossEntropyLoss); labels/logits misaligned; model not in\n"
|
|
232
|
+
" train(); frozen params. Print loss each step + a param's grad norm.\n"
|
|
233
|
+
"- Shape mismatch: print .shape at each layer; CrossEntropyLoss wants logits\n"
|
|
234
|
+
" (N,C) + targets (N,) Long. - Train acc high, val low: overfitting -> augment/\n"
|
|
235
|
+
" regularize/early-stop. Always overfit a tiny batch first as a sanity check."),
|
|
236
|
+
|
|
237
|
+
("Generate a LaTeX report / results table",
|
|
238
|
+
["latex", "report", "table", "pdf", "document", "tex", "pdflatex", "figure",
|
|
239
|
+
"results", "write", "tabular", "booktabs"],
|
|
240
|
+
"Emit a compilable .tex, then build it:\n"
|
|
241
|
+
" \\documentclass{article}\\usepackage{booktabs,graphicx}\\begin{document}\n"
|
|
242
|
+
" \\begin{tabular}{lr}\\toprule Metric & Value\\\\\\midrule\n"
|
|
243
|
+
" Accuracy & 0.94\\\\ F1 & 0.92\\\\\\bottomrule\\end{tabular}\n"
|
|
244
|
+
" \\includegraphics[width=.6\\linewidth]{roc.png}\\end{document}\n"
|
|
245
|
+
"Build: pdflatex -interaction=nonstopmode report.tex (run twice for refs).\n"
|
|
246
|
+
"Escape %, _, &, # in text. pandas: df.to_latex('t.tex', index=False). Confirm\n"
|
|
247
|
+
"report.pdf exists and pdflatex exit code is 0."),
|
|
135
248
|
]
|
|
136
249
|
|
|
137
250
|
_WORD = re.compile(r"[a-z0-9.]+")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Recommended next command — a single dimmed hint shown after each turn (à la
|
|
2
|
+
Claude Code's ghost suggestion). Pure logic so it's testable; the TUI renders it.
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def suggest_next_command(
|
|
8
|
+
*,
|
|
9
|
+
ctx_pct: int,
|
|
10
|
+
wrote_files: bool,
|
|
11
|
+
ran_bash: bool,
|
|
12
|
+
had_error: bool,
|
|
13
|
+
in_git: bool,
|
|
14
|
+
plan_remaining: bool,
|
|
15
|
+
) -> str | None:
|
|
16
|
+
"""The single most useful next step given what the last turn did, or None to
|
|
17
|
+
show nothing (better no hint than a noisy one). Order = priority."""
|
|
18
|
+
if ctx_pct >= 78:
|
|
19
|
+
return "/compact" # context nearly full — shrink it before continuing
|
|
20
|
+
if had_error:
|
|
21
|
+
return "fix the error above"
|
|
22
|
+
if plan_remaining:
|
|
23
|
+
return "continue" # the model left plan steps unfinished
|
|
24
|
+
if wrote_files and in_git:
|
|
25
|
+
return "git diff" # review what changed
|
|
26
|
+
if wrote_files:
|
|
27
|
+
return "run the tests"
|
|
28
|
+
if ran_bash:
|
|
29
|
+
return None # ran a command but nothing else obvious — no clutter
|
|
30
|
+
return None
|
|
@@ -111,6 +111,8 @@ class DrydockApp(App):
|
|
|
111
111
|
height: auto; margin: 0 2 1 2; padding: 0 1; color: #d7e6ee;
|
|
112
112
|
border-left: thick #c9a227; background: #14241c;
|
|
113
113
|
}
|
|
114
|
+
/* Dimmed recommended-next-command hint (empty → 0 lines). */
|
|
115
|
+
#suggest { height: auto; margin: 0 3; color: #4a6b78; text-style: italic; }
|
|
114
116
|
/* One bottom-docked footer holds, top→bottom: working-line, prompt, status. */
|
|
115
117
|
#footer { dock: bottom; height: auto; background: #0b1f2a; }
|
|
116
118
|
#prompt {
|
|
@@ -140,6 +142,7 @@ class DrydockApp(App):
|
|
|
140
142
|
# does the same. priority so it isn't swallowed by the focused prompt.
|
|
141
143
|
Binding("escape", "stop", "Stop the running turn", priority=True),
|
|
142
144
|
Binding("ctrl+o", "toggle_tools", "Expand/collapse details"),
|
|
145
|
+
Binding("ctrl+n", "use_suggestion", "Use suggested next command", show=False),
|
|
143
146
|
# Scroll the transcript from the keyboard (focus stays on the prompt,
|
|
144
147
|
# and SSH sessions often don't forward the mouse wheel). priority=True
|
|
145
148
|
# so the prompt's TextArea doesn't swallow PageUp/PageDown first.
|
|
@@ -250,6 +253,13 @@ class DrydockApp(App):
|
|
|
250
253
|
self._skills = load_skills(config.get("cwd") or ".")
|
|
251
254
|
self._current_assistant: AssistantMessage | None = None
|
|
252
255
|
self._last_card: ToolCard | None = None
|
|
256
|
+
# Recommended-next-command hint state.
|
|
257
|
+
self._turn_tools: set[str] = set() # tool names used this turn
|
|
258
|
+
self._turn_error = False
|
|
259
|
+
self._plan_remaining = False
|
|
260
|
+
self._suggestion = ""
|
|
261
|
+
import os
|
|
262
|
+
self._in_git = os.path.isdir(os.path.join(config.get("cwd") or ".", ".git"))
|
|
253
263
|
self._busy = False
|
|
254
264
|
# STOP signal: Escape / "/stop" sets it; the agent loop checks it at safe
|
|
255
265
|
# points and ends the turn cleanly (session preserved). Lives in config
|
|
@@ -300,6 +310,7 @@ class DrydockApp(App):
|
|
|
300
310
|
with Vertical(id="footer"):
|
|
301
311
|
yield Static("", id="todo") # pinned task checklist (empty = hidden)
|
|
302
312
|
yield Static("", id="working") # in-line activity (empty when idle)
|
|
313
|
+
yield Static("", id="suggest") # dimmed recommended-next-command hint
|
|
303
314
|
yield PromptArea(id="prompt")
|
|
304
315
|
yield Static(self._status_text(), id="status")
|
|
305
316
|
|
|
@@ -446,6 +457,9 @@ class DrydockApp(App):
|
|
|
446
457
|
# turn. If this turn emits its own todo, _render_todo repopulates it.
|
|
447
458
|
self.config.pop("_todo", None)
|
|
448
459
|
self.query_one("#todo", Static).update("")
|
|
460
|
+
self._turn_tools = set()
|
|
461
|
+
self._turn_error = False
|
|
462
|
+
self.query_one("#suggest", Static).update("") # hide the hint while working
|
|
449
463
|
self._busy = True
|
|
450
464
|
self._work_start = time.monotonic()
|
|
451
465
|
self._last_progress = self._work_start
|
|
@@ -1210,6 +1224,7 @@ class DrydockApp(App):
|
|
|
1210
1224
|
|
|
1211
1225
|
def on_agent_tool_start(self, m: AgentToolStart) -> None:
|
|
1212
1226
|
self._current_assistant = None # end the current text block
|
|
1227
|
+
self._turn_tools.add(m.name)
|
|
1213
1228
|
if m.name == "todo":
|
|
1214
1229
|
self._render_todo(m.inputs.get("tasks", ""))
|
|
1215
1230
|
self._last_card = None # tool_end is a no-op for the checklist
|
|
@@ -1229,6 +1244,7 @@ class DrydockApp(App):
|
|
|
1229
1244
|
|
|
1230
1245
|
items = parse_todo(tasks)
|
|
1231
1246
|
panel = self.query_one("#todo", Static)
|
|
1247
|
+
self._plan_remaining = any(s != "done" for _, s in items)
|
|
1232
1248
|
if not items:
|
|
1233
1249
|
panel.update("") # collapses to 0 height
|
|
1234
1250
|
return
|
|
@@ -1269,9 +1285,38 @@ class DrydockApp(App):
|
|
|
1269
1285
|
return
|
|
1270
1286
|
self._repeat = None
|
|
1271
1287
|
self._refresh_status()
|
|
1288
|
+
self._update_suggestion()
|
|
1272
1289
|
self.query_one("#prompt", PromptArea).focus()
|
|
1273
1290
|
|
|
1291
|
+
def _update_suggestion(self) -> None:
|
|
1292
|
+
"""Compute + render the dimmed recommended-next-command hint (Claude-Code-
|
|
1293
|
+
style). Empty when nothing useful to suggest."""
|
|
1294
|
+
from drydock.suggest import suggest_next_command
|
|
1295
|
+
limit = self.config.get("context_limit", 65536) or 65536
|
|
1296
|
+
pct = min(100, round(self._ctx_tokens / limit * 100)) if limit else 0
|
|
1297
|
+
wrote = bool(self._turn_tools & {"Write", "Edit"})
|
|
1298
|
+
ran = "Bash" in self._turn_tools
|
|
1299
|
+
self._suggestion = suggest_next_command(
|
|
1300
|
+
ctx_pct=pct, wrote_files=wrote, ran_bash=ran, had_error=self._turn_error,
|
|
1301
|
+
in_git=self._in_git, plan_remaining=self._plan_remaining,
|
|
1302
|
+
) or ""
|
|
1303
|
+
hint = self.query_one("#suggest", Static)
|
|
1304
|
+
if self._suggestion:
|
|
1305
|
+
from rich.markup import escape
|
|
1306
|
+
hint.update(f"→ next: {escape(self._suggestion)} [dim](ctrl+n to use)[/]")
|
|
1307
|
+
else:
|
|
1308
|
+
hint.update("")
|
|
1309
|
+
|
|
1310
|
+
def action_use_suggestion(self) -> None:
|
|
1311
|
+
"""Accept the recommended next command — drop it into the prompt to edit/send."""
|
|
1312
|
+
if self._suggestion and not self._busy:
|
|
1313
|
+
prompt = self.query_one("#prompt", PromptArea)
|
|
1314
|
+
prompt.text = self._suggestion
|
|
1315
|
+
prompt.move_cursor(prompt.document.end)
|
|
1316
|
+
prompt.focus()
|
|
1317
|
+
|
|
1274
1318
|
def on_agent_error(self, m: AgentError) -> None:
|
|
1319
|
+
self._turn_error = True
|
|
1275
1320
|
self._mount(ErrorMessage(m.error))
|
|
1276
1321
|
|
|
1277
1322
|
|
|
@@ -25,6 +25,7 @@ drydock/rmf.py
|
|
|
25
25
|
drydock/rmf_graph.py
|
|
26
26
|
drydock/skills.py
|
|
27
27
|
drydock/stig.py
|
|
28
|
+
drydock/suggest.py
|
|
28
29
|
drydock/tool_registry.py
|
|
29
30
|
drydock/tuning.py
|
|
30
31
|
drydock/web.py
|
|
@@ -108,6 +109,7 @@ tests/test_stig.py
|
|
|
108
109
|
tests/test_stop.py
|
|
109
110
|
tests/test_streaming_newlines.py
|
|
110
111
|
tests/test_subagent.py
|
|
112
|
+
tests/test_suggest.py
|
|
111
113
|
tests/test_system_prompt_help.py
|
|
112
114
|
tests/test_todo.py
|
|
113
115
|
tests/test_tool_arg_coercion.py
|
|
@@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta"
|
|
|
7
7
|
# PyPI distribution name is drydock-cli (the established install name, continued
|
|
8
8
|
# from the retired v2 fork); the import package + CLI command stay `drydock`.
|
|
9
9
|
name = "drydock-cli"
|
|
10
|
-
version = "3.0.
|
|
10
|
+
version = "3.0.118"
|
|
11
11
|
description = "Drydock — a local, provider-agnostic terminal coding agent for local LLMs"
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Recommended-next-command suggestion logic (priority-ordered, None when noisy)."""
|
|
2
|
+
from drydock.suggest import suggest_next_command as s
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_priority_and_cases():
|
|
6
|
+
base = dict(ctx_pct=10, wrote_files=False, ran_bash=False, had_error=False,
|
|
7
|
+
in_git=False, plan_remaining=False)
|
|
8
|
+
assert s(**{**base, "ctx_pct": 80}) == "/compact" # ctx wins
|
|
9
|
+
assert s(**{**base, "ctx_pct": 80, "had_error": True}) == "/compact"
|
|
10
|
+
assert s(**{**base, "had_error": True}) == "fix the error above"
|
|
11
|
+
assert s(**{**base, "plan_remaining": True}) == "continue"
|
|
12
|
+
assert s(**{**base, "wrote_files": True, "in_git": True}) == "git diff"
|
|
13
|
+
assert s(**{**base, "wrote_files": True}) == "run the tests"
|
|
14
|
+
assert s(**{**base, "ran_bash": True}) is None # no clutter
|
|
15
|
+
assert s(**base) is None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_error_beats_plan_and_writes():
|
|
19
|
+
assert s(ctx_pct=10, wrote_files=True, ran_bash=True, had_error=True,
|
|
20
|
+
in_git=True, plan_remaining=True) == "fix the error above"
|
|
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
|
|
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
|