Shell/python script inventory + port-to-Rust pipeline. Finds every bash/python script in the tree, maps its consumers (hooks, CI, other scripts, docs), queues each as tracked port-to-Rust work (TECH-DEBT section + deduplicated GitHub issues), and can drive a first-draft conversion through the claude CLI into an isolated draft crate that must pass cargo test before a human wires it in.
| Tier | tools |
| Role | unclassified (baselined) |
| Path | crates/tools/script-scan |
| Edition | 2021 |
| Targets | tools-script-scan, tools_script_scan |
| Public items | 23 across 0 modules |
| Tests | 10 |
What it is for
Shell/python script inventory + port-to-Rust pipeline.
The ecosystem rule is "all saved tooling is Rust, never bash/python" (fleet decision; see the tools family this crate lives in). Scripts still accumulate. This tool makes each one tracked work instead of quiet drift:
1. discover every .sh/.bash/.py (or bash/python shebang) file in the tree, 2. map consumers — the hooks, CI workflows, other scripts, Rust sources, and docs that invoke it — because a port is only done when every consumer is rewired, 3. queue the port as a TECH-DEBT entry + a deduplicated GitHub issue carrying the consumer checklist, 4. optionally draft the conversion through the claude CLI (--convert): the script, its consumers, and the process constraints go in; a candidate Rust crate comes out — written to an ISOLATED draft crate (own workspace, never auto-added to the main workspace) that must pass cargo test and human review before anything is wired.
Known-exempt: .claude/bootstrap.sh must stay bash — it runs before cargo exists on a fresh machine. docs/proposals/ scripts are illustrative spec artifacts, not tooling. Git hooks port behind a 2-line exec shim (git executes the hook file directly).
Capabilities
crate root
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
fn is_ported_shim(src : & str) -> bool |
fn classify(script_rel : & str, consumers : & Consumer) -> Class |
fn issue_title(script_path : & str) -> String |
pub const DEBT_BEGIN: & str |
pub const DEBT_END: & str |
fn splice_debt(existing : & str, section : & str) -> String |
Class
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
pub enum Class |
Class :: fn label(& self) -> & 'static str |
Consumer
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
pub struct Consumer |
ConvertPlan
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
pub struct ConvertPlan |
fn parse_convert_response(raw : & str) -> Result <ConvertPlan> |
fn write_draft(draft_root : & Path, plan : & ConvertPlan) -> Result <PathBuf> |
Lang
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
pub enum Lang |
Lang :: fn label(& self) -> & 'static str |
fn detect_lang(path : & Path, first_line : & str) -> Option <Lang> |
PlanFile
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
pub struct PlanFile |
Rewire
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
pub struct Rewire |
Script
Shell/python script inventory + port-to-Rust pipeline.
| Item |
|---|
pub struct Script |
fn scan(repo_root : & Path) -> Result <Vec <Script>> |
fn issue_body(s : & Script) -> String |
fn render_debt_section(scripts : & Script) -> String |
pub const MAX_SCRIPT_BYTES: usize |
fn build_convert_prompt(script : & Script, source : & str) -> String |
How to use it
No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.
Module structure
No public modules: the crate root is its whole surface.
Public surface
`crate root`
| Item | What it is |
|---|---|
pub enum Lang | Script language. |
Lang :: fn label(& self) -> & 'static str | — |
pub enum Class | Where a script is referenced from — drives porting priority. |
Class :: fn label(& self) -> & 'static str | — |
pub struct Consumer | One consumer reference site. |
pub struct Script | One discovered script. |
fn is_ported_shim(src : & str) -> bool | A ported exec shim: a tiny script whose only command execs the Rust replacement |
fn detect_lang(path : & Path, first_line : & str) -> Option <Lang> | Detect a script by extension or shebang. |
fn classify(script_rel : & str, consumers : & Consumer) -> Class | Classify from the consumer set + the script's own location. |
fn scan(repo_root : & Path) -> Result <Vec <Script>> | Discover every script under repo_root and map its consumers. |
fn issue_title(script_path : & str) -> String | Stable, deduplicatable issue title for a script's port. |
fn issue_body(s : & Script) -> String | Issue body: the port plan per our process, with the consumer checklist. |
pub const DEBT_BEGIN: & str | — |
pub const DEBT_END: & str | — |
fn render_debt_section(scripts : & Script) -> String | Render the managed TECH-DEBT.md section. |
fn splice_debt(existing : & str, section : & str) -> String | Splice the managed section into TECH-DEBT.md content. |
pub const MAX_SCRIPT_BYTES: usize | Cap on script source embedded in a conversion prompt. |
pub struct ConvertPlan | The JSON contract the model must return for --convert. |
pub struct PlanFile | — |
pub struct Rewire | — |
fn build_convert_prompt(script : & Script, source : & str) -> String | Build the headless conversion prompt: script + consumers + the process constraints the generated code must satisfy. |
fn parse_convert_response(raw : & str) -> Result <ConvertPlan> | Parse + validate the model's response |
fn write_draft(draft_root : & Path, plan : & ConvertPlan) -> Result <PathBuf> | Write the plan into an ISOLATED draft crate under draft_root |
No pub use re-exports: every item above is declared in this crate.
Boundary
Depends on no other workspace tier.
Shares tier tools with 84 other crates: tools-advisory-reach, tools-archive-guard, tools-artifact-scaffold, tools-ask-ai-core, tools-ask-ais, tools-ask-gemini, tools-book, tools-book-report, … (84 total).
_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._
Where it sits
| Tier (ontology) | tools |
| Architectural role (taxonomy) | unclassified (baselined) |
| Location | crates/tools/script-scan |
| Vocabulary in force (lexicon) | current |
Dependencies
Runtime, from outside the workspace.
| Crate | Requirement | Features | Optional | Only on |
|---|---|---|---|---|
anyhow | ^1 | — | no | always |
serde | ^1 | derive | no | always |
serde_json | ^1 | — | no | always |
Development. None.
Build. None.
Depended on by. Nothing in this workspace.
Feature flags
No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.
Targets
| Kind | Name | Source |
|---|---|---|
| bin | tools-script-scan | `src/main.rs` |
| lib | tools_script_scan | `src/lib.rs` |
Error model
No public error type was detected: no public item declares a type named *Error, and no public signature returns one.
Operational characteristics
| Property | Evidence |
|---|---|
| async public surface | none detected |
| async runtime | none detected |
| database access | none detected |
| network I/O | none detected |
| unsafe code | none detected |
| environment variables | yes |
No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.
Configuration
| Variable | Read in |
|---|---|
CARGO_MANIFEST_DIR | src/main.rs |
Related capabilities
No workspace crate depends on this one.
Verification
| Kind | Count |
|---|---|
| Unit tests | 10 |
| Integration tests | 0 |
| Examples | 0 |
| Doctests | 0 |
Evidence by module. How often each public module is named by something executable.
| Module | Tests | Examples | Consumers |
|---|---|---|---|
crate root | 21 | 0 | 0 |
What the tests establish, by name:
classification_priority_holds—src/lib.rsconsumer_word_match_rejects_partial_names—src/lib.rsconvert_prompt_embeds_script_constraints_and_consumers—src/lib.rsconvert_response_path_traversal_is_rejected—src/lib.rsconvert_response_valid_plan_parses_even_with_fences—src/lib.rsdebt_splice_is_idempotent—src/lib.rsdraft_write_is_isolated_and_refuses_overwrite—src/lib.rsissue_body_lists_consumers_and_hook_shim_note—src/lib.rslang_detection_by_extension_and_shebang—src/lib.rsported_exec_shim_is_not_pending_work—src/lib.rs
Documentation coverage
| Measure | Documented | Total |
|---|---|---|
| Public items with rustdoc | 17 | 23 |
Public modules with a //! block | 0 | 0 |
pie showData
title Public items with rustdoc
"Documented" : 17
"No rustdoc detected" : 6
Metrics
| Metric | Value |
|---|---|
| Rust source files | 2 |
| Source lines | 926 |
| Code lines | 786 |
| Public API items | 23 |
| Public modules | 0 |
| Tests | 10 |
| Examples | 0 |
| Cargo features | 0 |
| Direct runtime dependencies | 3 |
| Workspace reverse dependencies | 0 |
pie showData
title Public API by kind
"constant" : 3
"enum" : 2
"function" : 11
"method" : 2
"struct" : 5
pie showData
title Rust source composition
"Code" : 786
"Blank or comment" : 140
Generation
Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.