scitex 2.16.1__py3-none-any.whl → 2.17.0__py3-none-any.whl
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.
- scitex/_mcp_resources/_cheatsheet.py +1 -1
- scitex/_mcp_resources/_modules.py +1 -1
- scitex/_mcp_tools/__init__.py +2 -0
- scitex/_mcp_tools/verify.py +256 -0
- scitex/cli/main.py +2 -0
- scitex/cli/verify.py +476 -0
- scitex/dev/plt/__init__.py +1 -1
- scitex/dev/plt/mpl/get_dir_ax.py +1 -1
- scitex/dev/plt/mpl/get_signatures.py +1 -1
- scitex/dev/plt/mpl/get_signatures_details.py +1 -1
- scitex/io/_load.py +8 -1
- scitex/io/_save.py +12 -0
- scitex/session/README.md +2 -2
- scitex/session/__init__.py +1 -0
- scitex/session/_decorator.py +57 -33
- scitex/session/_lifecycle/__init__.py +23 -0
- scitex/session/_lifecycle/_close.py +225 -0
- scitex/session/_lifecycle/_config.py +112 -0
- scitex/session/_lifecycle/_matplotlib.py +83 -0
- scitex/session/_lifecycle/_start.py +246 -0
- scitex/session/_lifecycle/_utils.py +186 -0
- scitex/session/_manager.py +40 -3
- scitex/session/template.py +1 -1
- scitex/template/_templates/plt.py +1 -1
- scitex/template/_templates/session.py +1 -1
- scitex/verify/README.md +312 -0
- scitex/verify/__init__.py +212 -0
- scitex/verify/_chain.py +369 -0
- scitex/verify/_db.py +600 -0
- scitex/verify/_hash.py +187 -0
- scitex/verify/_integration.py +127 -0
- scitex/verify/_rerun.py +253 -0
- scitex/verify/_tracker.py +330 -0
- scitex/verify/_visualize.py +48 -0
- scitex/verify/_viz/__init__.py +56 -0
- scitex/verify/_viz/_colors.py +84 -0
- scitex/verify/_viz/_format.py +302 -0
- scitex/verify/_viz/_json.py +192 -0
- scitex/verify/_viz/_mermaid.py +440 -0
- scitex/verify/_viz/_plotly.py +193 -0
- scitex/verify/_viz/_templates.py +246 -0
- scitex/verify/_viz/_utils.py +56 -0
- {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/METADATA +1 -1
- {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/RECORD +47 -23
- scitex/session/_lifecycle.py +0 -827
- {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/WHEEL +0 -0
- {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/entry_points.txt +0 -0
- {scitex-2.16.1.dist-info → scitex-2.17.0.dist-info}/licenses/LICENSE +0 -0
scitex/cli/verify.py
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Timestamp: "2026-02-01 (ywatanabe)"
|
|
3
|
+
# File: /home/ywatanabe/proj/scitex-python/src/scitex/cli/verify.py
|
|
4
|
+
"""
|
|
5
|
+
SciTeX CLI - Verify Commands (Hash-based verification).
|
|
6
|
+
|
|
7
|
+
Provides commands for tracking and verifying reproducibility of computations.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import click
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group(
|
|
17
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
18
|
+
invoke_without_command=True,
|
|
19
|
+
)
|
|
20
|
+
@click.option("--help-recursive", is_flag=True, help="Show help for all subcommands")
|
|
21
|
+
@click.pass_context
|
|
22
|
+
def verify(ctx, help_recursive):
|
|
23
|
+
"""
|
|
24
|
+
Hash-based verification for reproducible science.
|
|
25
|
+
|
|
26
|
+
\b
|
|
27
|
+
Commands:
|
|
28
|
+
list List all tracked runs with verification status
|
|
29
|
+
run Verify a specific session run
|
|
30
|
+
chain Verify dependency chain for a target file
|
|
31
|
+
status Show changed files (like git status)
|
|
32
|
+
stats Show database statistics
|
|
33
|
+
|
|
34
|
+
\b
|
|
35
|
+
Examples:
|
|
36
|
+
scitex verify list # List all runs
|
|
37
|
+
scitex verify run 2025Y-11M-18D-09h12m03s # Verify specific run
|
|
38
|
+
scitex verify chain ./results/figure3.png # Trace back to source
|
|
39
|
+
scitex verify status # Show changes
|
|
40
|
+
"""
|
|
41
|
+
if help_recursive:
|
|
42
|
+
_print_help_recursive(ctx)
|
|
43
|
+
ctx.exit(0)
|
|
44
|
+
elif ctx.invoked_subcommand is None:
|
|
45
|
+
click.echo(ctx.get_help())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _print_help_recursive(ctx):
|
|
49
|
+
"""Print help for all commands recursively."""
|
|
50
|
+
fake_parent = click.Context(click.Group(), info_name="scitex")
|
|
51
|
+
parent_ctx = click.Context(verify, info_name="verify", parent=fake_parent)
|
|
52
|
+
click.secho("━━━ scitex verify ━━━", fg="cyan", bold=True)
|
|
53
|
+
click.echo(verify.get_help(parent_ctx))
|
|
54
|
+
for name in sorted(verify.list_commands(ctx) or []):
|
|
55
|
+
cmd = verify.get_command(ctx, name)
|
|
56
|
+
if cmd is None:
|
|
57
|
+
continue
|
|
58
|
+
click.echo()
|
|
59
|
+
click.secho(f"━━━ scitex verify {name} ━━━", fg="cyan", bold=True)
|
|
60
|
+
with click.Context(cmd, info_name=name, parent=parent_ctx) as sub_ctx:
|
|
61
|
+
click.echo(cmd.get_help(sub_ctx))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@verify.command("list")
|
|
65
|
+
@click.option(
|
|
66
|
+
"--limit", "-n", type=int, default=50, help="Maximum number of runs to show"
|
|
67
|
+
)
|
|
68
|
+
@click.option(
|
|
69
|
+
"--filter-status",
|
|
70
|
+
"-s",
|
|
71
|
+
type=click.Choice(["all", "success", "failed", "running"]),
|
|
72
|
+
default="all",
|
|
73
|
+
help="Filter by run status",
|
|
74
|
+
)
|
|
75
|
+
@click.option("--no-verify", is_flag=True, help="Skip verification (faster)")
|
|
76
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
77
|
+
def list_runs(limit, filter_status, no_verify, as_json):
|
|
78
|
+
"""
|
|
79
|
+
List all tracked runs with verification status.
|
|
80
|
+
|
|
81
|
+
\b
|
|
82
|
+
Examples:
|
|
83
|
+
scitex verify list # List all runs
|
|
84
|
+
scitex verify list -n 10 # Limit to 10 runs
|
|
85
|
+
scitex verify list -s success # Only successful runs
|
|
86
|
+
scitex verify list --no-verify # Skip verification (faster)
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
from scitex.verify import format_list, get_db
|
|
90
|
+
|
|
91
|
+
db = get_db()
|
|
92
|
+
status_filter = None if filter_status == "all" else filter_status
|
|
93
|
+
runs = db.list_runs(status=status_filter, limit=limit)
|
|
94
|
+
|
|
95
|
+
if as_json:
|
|
96
|
+
import json
|
|
97
|
+
|
|
98
|
+
output = []
|
|
99
|
+
for run in runs:
|
|
100
|
+
output.append(
|
|
101
|
+
{
|
|
102
|
+
"session_id": run["session_id"],
|
|
103
|
+
"script_path": run.get("script_path"),
|
|
104
|
+
"status": run.get("status"),
|
|
105
|
+
"started_at": run.get("started_at"),
|
|
106
|
+
"finished_at": run.get("finished_at"),
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
click.echo(json.dumps(output, indent=2))
|
|
110
|
+
else:
|
|
111
|
+
if not runs:
|
|
112
|
+
click.echo("No tracked runs found.")
|
|
113
|
+
click.echo(
|
|
114
|
+
"\nTo start tracking, use @stx.session decorator with stx.io."
|
|
115
|
+
)
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
output = format_list(runs, verify=not no_verify)
|
|
119
|
+
click.echo(output)
|
|
120
|
+
click.echo(f"\nShowing {len(runs)} runs (use --limit to change)")
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
124
|
+
sys.exit(1)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@verify.command("run")
|
|
128
|
+
@click.argument("targets", nargs=-1, required=True)
|
|
129
|
+
@click.option("--rerun", is_flag=True, help="Re-execute script and compare (thorough)")
|
|
130
|
+
@click.option("-v", "--verbose", is_flag=True, help="Show detailed file information")
|
|
131
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
132
|
+
def verify_run_cmd(targets, rerun, verbose, as_json):
|
|
133
|
+
"""
|
|
134
|
+
Verify session run(s).
|
|
135
|
+
|
|
136
|
+
TARGETS can be (one or multiple):
|
|
137
|
+
- A session ID: 2025Y-11M-18D-09h12m03s_HmH5
|
|
138
|
+
- A script path: ./script.py (latest run of this script)
|
|
139
|
+
- An artifact path: ./results/figure3.png (session that produced it)
|
|
140
|
+
|
|
141
|
+
\b
|
|
142
|
+
Examples:
|
|
143
|
+
scitex verify run 2025Y-11M-18D-09h12m03s_HmH5
|
|
144
|
+
scitex verify run ./results/figure3.png
|
|
145
|
+
scitex verify run ./script.py --rerun
|
|
146
|
+
scitex verify run file1.csv file2.csv --rerun
|
|
147
|
+
"""
|
|
148
|
+
try:
|
|
149
|
+
from scitex.verify import format_run_verification, verify_by_rerun, verify_run
|
|
150
|
+
|
|
151
|
+
results = []
|
|
152
|
+
for target in targets:
|
|
153
|
+
if rerun:
|
|
154
|
+
verification = verify_by_rerun(target)
|
|
155
|
+
else:
|
|
156
|
+
verification = verify_run(target)
|
|
157
|
+
results.append(verification)
|
|
158
|
+
|
|
159
|
+
if as_json:
|
|
160
|
+
import json
|
|
161
|
+
|
|
162
|
+
output = [
|
|
163
|
+
{
|
|
164
|
+
"session_id": v.session_id,
|
|
165
|
+
"script_path": v.script_path,
|
|
166
|
+
"status": v.status.value,
|
|
167
|
+
"level": v.level.value,
|
|
168
|
+
"is_verified": v.is_verified,
|
|
169
|
+
}
|
|
170
|
+
for v in results
|
|
171
|
+
]
|
|
172
|
+
click.echo(json.dumps(output, indent=2))
|
|
173
|
+
else:
|
|
174
|
+
all_verified = True
|
|
175
|
+
for v in results:
|
|
176
|
+
badge = "✓✓" if v.level.value == "rerun" else "✓"
|
|
177
|
+
if v.is_verified:
|
|
178
|
+
click.secho(f"{badge} {v.session_id}", fg="green")
|
|
179
|
+
else:
|
|
180
|
+
click.secho(f"✗ {v.session_id}", fg="red")
|
|
181
|
+
all_verified = False
|
|
182
|
+
if verbose:
|
|
183
|
+
click.echo(format_run_verification(v, verbose=True))
|
|
184
|
+
|
|
185
|
+
if not all_verified:
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
|
|
188
|
+
except Exception as e:
|
|
189
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
190
|
+
sys.exit(1)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@verify.command("chain")
|
|
194
|
+
@click.argument("target_file", type=click.Path(exists=True))
|
|
195
|
+
@click.option("-v", "--verbose", is_flag=True, help="Show detailed information")
|
|
196
|
+
@click.option("--mermaid", is_flag=True, help="Output as Mermaid diagram")
|
|
197
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
198
|
+
def verify_chain_cmd(target_file, verbose, mermaid, as_json):
|
|
199
|
+
"""
|
|
200
|
+
Verify the dependency chain for a target file.
|
|
201
|
+
|
|
202
|
+
Traces back through all sessions that contributed to producing
|
|
203
|
+
the target file and verifies each one.
|
|
204
|
+
|
|
205
|
+
\b
|
|
206
|
+
Examples:
|
|
207
|
+
scitex verify chain ./results/figure3.png
|
|
208
|
+
scitex verify chain ./results/figure3.png -v
|
|
209
|
+
scitex verify chain ./results/figure3.png --mermaid
|
|
210
|
+
"""
|
|
211
|
+
try:
|
|
212
|
+
from scitex.verify import (
|
|
213
|
+
format_chain_verification,
|
|
214
|
+
generate_mermaid_dag,
|
|
215
|
+
verify_chain,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
chain = verify_chain(target_file)
|
|
219
|
+
|
|
220
|
+
if mermaid:
|
|
221
|
+
output = generate_mermaid_dag(target_file=str(Path(target_file).resolve()))
|
|
222
|
+
click.echo(output)
|
|
223
|
+
elif as_json:
|
|
224
|
+
import json
|
|
225
|
+
|
|
226
|
+
output = {
|
|
227
|
+
"target_file": chain.target_file,
|
|
228
|
+
"status": chain.status.value,
|
|
229
|
+
"is_verified": chain.is_verified,
|
|
230
|
+
"runs": [
|
|
231
|
+
{
|
|
232
|
+
"session_id": r.session_id,
|
|
233
|
+
"script_path": r.script_path,
|
|
234
|
+
"status": r.status.value,
|
|
235
|
+
"is_verified": r.is_verified,
|
|
236
|
+
}
|
|
237
|
+
for r in chain.runs
|
|
238
|
+
],
|
|
239
|
+
}
|
|
240
|
+
click.echo(json.dumps(output, indent=2))
|
|
241
|
+
else:
|
|
242
|
+
output = format_chain_verification(chain, verbose=verbose)
|
|
243
|
+
click.echo(output)
|
|
244
|
+
|
|
245
|
+
if chain.is_verified:
|
|
246
|
+
click.echo()
|
|
247
|
+
click.secho("✓ Chain fully verified!", fg="green")
|
|
248
|
+
else:
|
|
249
|
+
click.echo()
|
|
250
|
+
click.secho("✗ Chain verification failed", fg="red")
|
|
251
|
+
if chain.failed_runs:
|
|
252
|
+
click.echo(f" {len(chain.failed_runs)} run(s) have issues")
|
|
253
|
+
sys.exit(1)
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
257
|
+
sys.exit(1)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@verify.command("render")
|
|
261
|
+
@click.argument("output_path", type=click.Path())
|
|
262
|
+
@click.option("--session", "-s", help="Session ID to visualize")
|
|
263
|
+
@click.option("--file", "-f", "target_file", help="Target file to trace chain")
|
|
264
|
+
@click.option("--title", "-t", default="Verification DAG", help="Title for output")
|
|
265
|
+
@click.option("--plotly", "-p", is_flag=True, help="Use Plotly (interactive)")
|
|
266
|
+
def render_cmd(output_path, session, target_file, title, plotly):
|
|
267
|
+
"""
|
|
268
|
+
Render verification DAG to file (HTML, PNG, SVG, or Mermaid).
|
|
269
|
+
|
|
270
|
+
The output format is determined by the file extension:
|
|
271
|
+
- .html: Interactive HTML (Mermaid.js or Plotly with --plotly)
|
|
272
|
+
- .png: PNG image
|
|
273
|
+
- .svg: SVG image
|
|
274
|
+
- .mmd: Raw Mermaid code
|
|
275
|
+
|
|
276
|
+
\b
|
|
277
|
+
Examples:
|
|
278
|
+
scitex verify render dag.html --file ./results/fig.png
|
|
279
|
+
scitex verify render dag.html --file ./results/fig.png --plotly
|
|
280
|
+
scitex verify render dag.png --session 2025Y-11M-18D-09h12m03s
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
if not session and not target_file:
|
|
284
|
+
click.secho("Error: Specify --session or --file", fg="red", err=True)
|
|
285
|
+
sys.exit(1)
|
|
286
|
+
|
|
287
|
+
if plotly:
|
|
288
|
+
from scitex.verify import render_plotly_dag
|
|
289
|
+
|
|
290
|
+
result_path = render_plotly_dag(
|
|
291
|
+
output_path=output_path,
|
|
292
|
+
session_id=session,
|
|
293
|
+
target_file=target_file,
|
|
294
|
+
title=title,
|
|
295
|
+
)
|
|
296
|
+
else:
|
|
297
|
+
from scitex.verify import render_dag
|
|
298
|
+
|
|
299
|
+
result_path = render_dag(
|
|
300
|
+
output_path=output_path,
|
|
301
|
+
session_id=session,
|
|
302
|
+
target_file=target_file,
|
|
303
|
+
title=title,
|
|
304
|
+
)
|
|
305
|
+
click.secho(f"Rendered to: {result_path}", fg="green")
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
309
|
+
sys.exit(1)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@verify.command("status")
|
|
313
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
314
|
+
def status_cmd(as_json):
|
|
315
|
+
"""
|
|
316
|
+
Show verification status (like git status).
|
|
317
|
+
|
|
318
|
+
Displays a summary of all tracked runs and highlights any
|
|
319
|
+
that have changed files (hash mismatches) or missing files.
|
|
320
|
+
|
|
321
|
+
\b
|
|
322
|
+
Examples:
|
|
323
|
+
scitex verify status
|
|
324
|
+
scitex verify status --json
|
|
325
|
+
"""
|
|
326
|
+
try:
|
|
327
|
+
from scitex.verify import format_status, get_status
|
|
328
|
+
|
|
329
|
+
status = get_status()
|
|
330
|
+
|
|
331
|
+
if as_json:
|
|
332
|
+
import json
|
|
333
|
+
|
|
334
|
+
click.echo(json.dumps(status, indent=2))
|
|
335
|
+
else:
|
|
336
|
+
output = format_status(status)
|
|
337
|
+
click.echo(output)
|
|
338
|
+
|
|
339
|
+
except Exception as e:
|
|
340
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
341
|
+
sys.exit(1)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
@verify.command("stats")
|
|
345
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
346
|
+
def stats_cmd(as_json):
|
|
347
|
+
"""
|
|
348
|
+
Show database statistics.
|
|
349
|
+
|
|
350
|
+
\b
|
|
351
|
+
Examples:
|
|
352
|
+
scitex verify stats
|
|
353
|
+
scitex verify stats --json
|
|
354
|
+
"""
|
|
355
|
+
try:
|
|
356
|
+
from scitex.verify import get_db
|
|
357
|
+
|
|
358
|
+
db = get_db()
|
|
359
|
+
db_stats = db.stats()
|
|
360
|
+
|
|
361
|
+
if as_json:
|
|
362
|
+
import json
|
|
363
|
+
|
|
364
|
+
click.echo(json.dumps(db_stats, indent=2))
|
|
365
|
+
else:
|
|
366
|
+
click.secho("Verification Database Statistics", fg="cyan", bold=True)
|
|
367
|
+
click.echo("=" * 40)
|
|
368
|
+
click.echo(f"Database path: {db_stats['db_path']}")
|
|
369
|
+
click.echo(f"Total runs: {db_stats['total_runs']}")
|
|
370
|
+
click.echo(f" Successful: {db_stats['success_runs']}")
|
|
371
|
+
click.echo(f" Failed: {db_stats['failed_runs']}")
|
|
372
|
+
click.echo(f"Total file records: {db_stats['total_file_records']}")
|
|
373
|
+
click.echo(f"Unique files tracked: {db_stats['unique_files']}")
|
|
374
|
+
|
|
375
|
+
except Exception as e:
|
|
376
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
377
|
+
sys.exit(1)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
@verify.command("clear")
|
|
381
|
+
@click.option("--force", "-f", is_flag=True, help="Skip confirmation")
|
|
382
|
+
def clear_cmd(force):
|
|
383
|
+
"""
|
|
384
|
+
Clear the verification database.
|
|
385
|
+
|
|
386
|
+
\b
|
|
387
|
+
Examples:
|
|
388
|
+
scitex verify clear
|
|
389
|
+
scitex verify clear -f
|
|
390
|
+
"""
|
|
391
|
+
try:
|
|
392
|
+
from scitex.verify import get_db
|
|
393
|
+
|
|
394
|
+
db = get_db()
|
|
395
|
+
db_stats = db.stats()
|
|
396
|
+
|
|
397
|
+
if not force:
|
|
398
|
+
click.echo(f"This will delete {db_stats['total_runs']} runs and ")
|
|
399
|
+
click.echo(f"{db_stats['total_file_records']} file records.")
|
|
400
|
+
if not click.confirm("Are you sure?"):
|
|
401
|
+
click.echo("Cancelled.")
|
|
402
|
+
return
|
|
403
|
+
|
|
404
|
+
# Delete the database file
|
|
405
|
+
db_path = Path(db_stats["db_path"])
|
|
406
|
+
if db_path.exists():
|
|
407
|
+
db_path.unlink()
|
|
408
|
+
click.secho("Database cleared.", fg="green")
|
|
409
|
+
else:
|
|
410
|
+
click.echo("Database already empty.")
|
|
411
|
+
|
|
412
|
+
except Exception as e:
|
|
413
|
+
click.secho(f"Error: {e}", fg="red", err=True)
|
|
414
|
+
sys.exit(1)
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
@verify.group(invoke_without_command=True)
|
|
418
|
+
@click.pass_context
|
|
419
|
+
def mcp(ctx):
|
|
420
|
+
"""
|
|
421
|
+
MCP (Model Context Protocol) server operations.
|
|
422
|
+
|
|
423
|
+
\b
|
|
424
|
+
Commands:
|
|
425
|
+
list-tools - List available MCP tools
|
|
426
|
+
|
|
427
|
+
\b
|
|
428
|
+
Examples:
|
|
429
|
+
scitex verify mcp list-tools
|
|
430
|
+
"""
|
|
431
|
+
if ctx.invoked_subcommand is None:
|
|
432
|
+
click.echo(ctx.get_help())
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
@mcp.command("list-tools")
|
|
436
|
+
def list_tools():
|
|
437
|
+
"""
|
|
438
|
+
List available MCP tools for verification.
|
|
439
|
+
|
|
440
|
+
\b
|
|
441
|
+
Example:
|
|
442
|
+
scitex verify mcp list-tools
|
|
443
|
+
"""
|
|
444
|
+
click.secho("Verify MCP Tools", fg="cyan", bold=True)
|
|
445
|
+
click.echo()
|
|
446
|
+
tools = [
|
|
447
|
+
("verify_list", "List all tracked runs with verification status"),
|
|
448
|
+
("verify_run", "Verify a specific session run"),
|
|
449
|
+
("verify_chain", "Verify dependency chain for a target file"),
|
|
450
|
+
("verify_status", "Show verification status (like git status)"),
|
|
451
|
+
("verify_stats", "Show database statistics"),
|
|
452
|
+
]
|
|
453
|
+
for name, desc in tools:
|
|
454
|
+
click.echo(f" {name}: {desc}")
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
@verify.command("list-python-apis")
|
|
458
|
+
@click.option("-v", "--verbose", count=True, help="Verbosity: -v +doc, -vv full doc")
|
|
459
|
+
@click.option("-d", "--max-depth", type=int, default=5, help="Max recursion depth")
|
|
460
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
|
|
461
|
+
@click.pass_context
|
|
462
|
+
def list_python_apis(ctx, verbose, max_depth, as_json):
|
|
463
|
+
"""List Python APIs (alias for: scitex introspect api scitex.verify)."""
|
|
464
|
+
from scitex.cli.introspect import api
|
|
465
|
+
|
|
466
|
+
ctx.invoke(
|
|
467
|
+
api,
|
|
468
|
+
dotted_path="scitex.verify",
|
|
469
|
+
verbose=verbose,
|
|
470
|
+
max_depth=max_depth,
|
|
471
|
+
as_json=as_json,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
if __name__ == "__main__":
|
|
476
|
+
verify()
|
scitex/dev/plt/__init__.py
CHANGED
scitex/dev/plt/mpl/get_dir_ax.py
CHANGED
scitex/io/_load.py
CHANGED
|
@@ -286,7 +286,6 @@ def load(
|
|
|
286
286
|
>>> # Load file without extension (e.g., UUID PDF)
|
|
287
287
|
>>> pdf = load('f2694ccb-1b6f-4994-add8-5111fd4d52f1', ext='pdf')
|
|
288
288
|
"""
|
|
289
|
-
|
|
290
289
|
# Don't use clean_path as it breaks relative paths like ./file.txt
|
|
291
290
|
# lpath = clean_path(lpath)
|
|
292
291
|
|
|
@@ -445,6 +444,14 @@ def load(
|
|
|
445
444
|
if verbose:
|
|
446
445
|
print(f"[Cache STORED] Cached data for: {lpath}")
|
|
447
446
|
|
|
447
|
+
# Track input for verification (if session is active)
|
|
448
|
+
try:
|
|
449
|
+
from scitex.verify import on_io_load
|
|
450
|
+
|
|
451
|
+
on_io_load(lpath)
|
|
452
|
+
except Exception:
|
|
453
|
+
pass # Silent fail - don't interrupt load operations
|
|
454
|
+
|
|
448
455
|
return result
|
|
449
456
|
except (ValueError, FileNotFoundError) as e:
|
|
450
457
|
raise ValueError(f"Error loading file {lpath}: {str(e)}")
|
scitex/io/_save.py
CHANGED
|
@@ -71,6 +71,7 @@ def save(
|
|
|
71
71
|
crop_margin_mm: float = 1.0,
|
|
72
72
|
metadata_extra: dict = None,
|
|
73
73
|
json_schema: str = "editable",
|
|
74
|
+
track: bool = True,
|
|
74
75
|
**kwargs,
|
|
75
76
|
) -> None:
|
|
76
77
|
"""
|
|
@@ -102,6 +103,8 @@ def save(
|
|
|
102
103
|
Additional metadata to merge with auto-collected metadata.
|
|
103
104
|
json_schema : str, optional
|
|
104
105
|
Schema type for JSON metadata output. Default is "editable".
|
|
106
|
+
track : bool, optional
|
|
107
|
+
If True, track this file in verification system. Default is True.
|
|
105
108
|
**kwargs
|
|
106
109
|
Additional keyword arguments for the underlying save function.
|
|
107
110
|
"""
|
|
@@ -149,6 +152,15 @@ def save(
|
|
|
149
152
|
# Symbolic links
|
|
150
153
|
_symlink(spath, spath_cwd, symlink_from_cwd, verbose)
|
|
151
154
|
_symlink_to(spath_final, symlink_to, verbose)
|
|
155
|
+
|
|
156
|
+
# Track output for verification (if session is active)
|
|
157
|
+
try:
|
|
158
|
+
from scitex.verify import on_io_save
|
|
159
|
+
|
|
160
|
+
on_io_save(spath_final, track=track)
|
|
161
|
+
except Exception:
|
|
162
|
+
pass # Silent fail - don't interrupt save operations
|
|
163
|
+
|
|
152
164
|
return Path(spath)
|
|
153
165
|
|
|
154
166
|
except AssertionError:
|
scitex/session/README.md
CHANGED
|
@@ -22,7 +22,7 @@ def main(
|
|
|
22
22
|
CONFIG=scitex.INJECTED,
|
|
23
23
|
plt=scitex.INJECTED,
|
|
24
24
|
COLORS=scitex.INJECTED,
|
|
25
|
-
|
|
25
|
+
rngg=scitex.INJECTED,
|
|
26
26
|
):
|
|
27
27
|
"""Args injected by @scitex.session decorator"""
|
|
28
28
|
print(f"Session ID: {CONFIG['ID']}")
|
|
@@ -46,7 +46,7 @@ def main(
|
|
|
46
46
|
CONFIG=scitex.INJECTED,
|
|
47
47
|
plt=scitex.INJECTED,
|
|
48
48
|
COLORS=scitex.INJECTED,
|
|
49
|
-
|
|
49
|
+
rngg=scitex.INJECTED,
|
|
50
50
|
):
|
|
51
51
|
"""Args injected by @scitex.session decorator"""
|
|
52
52
|
print(f"Session ID: {CONFIG['ID']}")
|
scitex/session/__init__.py
CHANGED
|
@@ -52,6 +52,7 @@ INJECTED = _InjectedSentinel()
|
|
|
52
52
|
|
|
53
53
|
|
|
54
54
|
# Import session management functionality
|
|
55
|
+
# Use refactored _lifecycle subpackage (verification hooks included)
|
|
55
56
|
from ._decorator import run, session
|
|
56
57
|
from ._lifecycle import close, running2finished, start
|
|
57
58
|
from ._manager import SessionManager
|