Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ agents.
- **Cost and risk analysis.** Workshop element cost, hotspots, and server-load
risk indicators, each labeled exact, static, or heuristic.
- **Semantic queries.** Symbols, references, control flow, call graph, cost.
- **Validated edits.** Renames that preview a diff before writing.
- **Validated edits.** Source edits that preview a diff before writing.
- **Agent and editor integration.** `wright serve` (JSON-RPC and MCP) and
`wright-lsp`.
- **CI-friendly output.** JSON reports and GitHub Actions annotations.
Expand Down Expand Up @@ -54,7 +54,6 @@ wright check [INPUT] # correctness diagnostics
wright lint [INPUT] # lint findings
wright analyze [INPUT] # cost, hotspots, and risk indicators
wright inspect [INPUT] # semantic summary
wright rename OLD NEW [INPUT] # preview a diff; --write applies it
wright compile [INPUT] # write <input>.ow; --to LOCALE selects the output locale
wright serve [INPUT] # agent session over stdio or MCP
```
Expand Down
2 changes: 1 addition & 1 deletion agent-guide/UPSTREAM.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendored agent guide

`wright/` is a verbatim copy of the `wright` skill from
`https://github.com/wrightkit/skills` at commit `478ea04`, embedded in the
`https://github.com/wrightkit/skills` at commit `cdd694f`, embedded in the
binary for `wright agent install`. The skills repository is the authoring
home; never edit this copy. To sync it, clone `wrightkit/skills` and run

Expand Down
2 changes: 1 addition & 1 deletion agent-guide/wright/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: wright
description: Use in a project of Overwatch Workshop rules or OverPy (.opy) source when you must judge what is wrong, risky, or expensive, or how a change propagates, and text search cannot answer it. Covers Workshop server freezes, infinite loops, rule cost, symbol and rule flow, safe renames, and OPY-to-Workshop compilation, through the `wright` CLI.
description: Use in a project of Overwatch Workshop rules or OverPy (.opy) source when you must judge what is wrong, risky, or expensive, or how a change propagates, and text search cannot answer it. Covers Workshop server freezes, infinite loops, rule cost, symbol and rule flow, and OPY-to-Workshop compilation, through the `wright` CLI.
license: AGPL-3.0
---

Expand Down
8 changes: 4 additions & 4 deletions agent-guide/wright/references/language-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ Confirm against `wright --help` and the `capabilities` session operation; suppor

## Raw Workshop (`.ws`, `.txt`)

- Fully owned by `workshop-rs`. Semantic queries, validated edit transactions, and semantic rename are supported here where the installed version declares them.
- Fully owned by `workshop-rs`. Semantic queries and validated edit transactions are supported here where the installed version declares them.
- `compile --to <LOCALE>` re-emits the program in another Workshop client locale (e.g. `en-US`, `zh-CN`) without changing the input; the serve `compile` operation takes the same `to` field. A target locale missing mappings for the program fails explicitly, with no fallback.
- A semantic rename previews first and refuses on stale source, name collision, or unsupported kind (rules are not rename targets). Treat a refusal as final.
- To rename a symbol, edit the source directly and re-verify with `check`.

## OverPy (`.opy`)

- Compiled natively by Wright with the first-party OPY provider (`--opy-provider` overrides it). `check`, `lint`, `analyze`, `inspect`, `rename`, and `compile` apply; the `inspect` query subcommands read the provider's compiled artifact. A provider without compile capability declines with its own diagnostic.
- Edits are provider-owned. `rename` resolves the declared name through the provider's own symbol listing and previews validated edits by default (`--write` applies them); unknown, ambiguous, and provider-declined names refuse with structured diagnostics — treat a refusal as final. The raw edit operations refuse OPY input and name the provider operations; use those when the version offers them, otherwise edit the source directly and re-verify.
- Compiled natively by Wright with the first-party OPY provider (`--opy-provider` overrides it). `check`, `lint`, `analyze`, `inspect`, and `compile` apply; the `inspect` query subcommands read the provider's compiled artifact. A provider without compile capability declines with its own diagnostic.
- Edits are provider-owned. The raw edit operations refuse OPY input and name the provider operations; use those when the version offers them, otherwise edit the source directly and re-verify with `check` — that same edit-and-check path covers renames. The provider-backed editor rename (F2) remains in `wright-lsp`.
- `convert` runs Workshop to OPY only, and only when the provider negotiates the `reconstruct` capability; a `capability-unavailable` refusal means the installed provider does not offer it. When available, output is canonical, without comments, macros, or formatting, and unrepresentable constructs are rejected without partial output.

## DEL/OSTW
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/metrics/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
METRICS_DIR = Path(__file__).resolve().parent
CACHE_DIR = METRICS_DIR / ".cache"

# Mutation operations (semanticRename, validateEditTransaction, provider*)
# Mutation operations (validateEditTransaction and the provider* ops)
# are absent: they need caller-supplied transactions and are not measured.
AGENT_OPS = [
"capabilities",
Expand Down
4 changes: 2 additions & 2 deletions crates/wright-analyzer/src/canonical/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ pub struct UsageSummary {
}

/// The outcome of resolving a 1-based source position to the symbol whose
/// identifier occurrence covers it (#429, #555). Every surface — CLI/agent
/// rename targeting and editor hover/definition/references — shares this
/// identifier occurrence covers it (#429, #555). The editor surfaces —
/// hover, definition, and references — share this
/// one resolution rule, so a position resolves to exactly one symbol, to
/// none, or reports the ambiguity; no surface guesses by iteration order.
#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
2 changes: 1 addition & 1 deletion crates/wright-cli/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mod exit {

const SKILL_NAME: &str = "wright";
const INSTALLED_BY: &str = "wright agent install";
const UPSTREAM: &str = "wrightkit/skills@478ea04";
const UPSTREAM: &str = "wrightkit/skills@cdd694f";
const DEFAULT_DEST: &str = ".agents/skills";

/// The guide files `install` writes, as `(relative path, content)`.
Expand Down
29 changes: 2 additions & 27 deletions crates/wright-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ pub(crate) struct Cli {
pub(crate) const LONG_ABOUT: &str = "Wright compiler and Workshop tooling CLI.

Commands check correctness, summarize semantic hotspots, lint, compile,
rename Workshop symbols, resolve owner names and signatures (`lookup`), or
reconstruct source through the typed wright-driver result envelope.
resolve owner names and signatures (`lookup`), or reconstruct source
through the typed wright-driver result envelope.
`inspect`
prints the semantic summary, and its query subcommands (symbols, refs, cfg,
callgraph, cost) expose each detail area. `compile` writes the program to
Expand Down Expand Up @@ -97,9 +97,6 @@ pub(crate) enum Command {
subcommand_precedence_over_arg = true
)]
Inspect(InspectArgs),
/// Rename a Workshop variable or subroutine semantically (#434): previews
/// the validated source diff by default; `--write` applies it atomically.
Rename(RenameArgs),
/// Resolve a display name, near spelling, or guess to the language
/// owner's accepted spelling and signature (#529). Use it before writing
/// in an unfamiliar language and when a name is rejected.
Expand Down Expand Up @@ -277,28 +274,6 @@ pub(crate) struct CommonArgs {
pub(crate) color: ColorArg,
}

/// Arguments of `rename` (#434, #739): the declared symbol name and the
/// new identifier as positionals, then `[INPUT]` through the shared
/// workflow options. Raw Workshop input renames through `workshop-rs`;
/// OPY input resolves `NAME` through the provider's `lpp/symbols` and
/// renames through `lpp/rename` — the same semantic rename the agent
/// surface exposes.
#[derive(Debug, Args)]
pub(crate) struct RenameArgs {
/// The declared name of the symbol to rename.
#[arg(value_name = "NAME")]
pub(crate) name: String,
/// The new identifier.
#[arg(value_name = "NEW_NAME")]
pub(crate) to: String,
/// Apply the validated rename to the input file atomically instead of
/// previewing the diff.
#[arg(long)]
pub(crate) write: bool,
#[command(flatten)]
pub(crate) common: CommonArgs,
}

/// Arguments of `lookup` (#529): the free-text query, the language selector,
/// and the optional narrowing fields of the `lookup` operation. It answers
/// from language vocabulary alone, so it takes no input path.
Expand Down
6 changes: 0 additions & 6 deletions crates/wright-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,6 @@ fn run_workflow(command: Command) -> ExitCode {
)
})
}
Command::Rename(args) => run_configured(
config_from_common(&args.common),
present::Presentation::from_common(&args.common),
None,
move |session| session.rename(&args.name, &args.to, args.write),
),
Command::Lint(args) => {
let mut config = config_from_common(&args.common);
config.selection = selection_from_args(&args.select);
Expand Down
16 changes: 7 additions & 9 deletions crates/wright-cli/src/mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,32 +213,30 @@ mod tests {
"wright_lint_rules",
"wright_cost_estimate",
"wright_target_metadata",
"wright_semantic_rename",
"wright_validate_edit_transaction",
"wright_provider_semantic_rename",
"wright_provider_validate_edit",
"wright_lookup",
] {
assert!(names.iter().any(|n| n == name), "missing {name}");
}
assert_eq!(names.len(), 21);
assert_eq!(names.len(), 19);
assert!(names.iter().all(|name| name.starts_with("wright_")));
}

#[test]
fn input_schemas_drop_op_and_edit_sources() {
let defs = crate::tooldefs::schema_defs();
let rename = TOOL_SPECS
let edit = TOOL_SPECS
.iter()
.find(|spec| spec.op == "semanticRename")
.find(|spec| spec.op == "validateEditTransaction")
.unwrap();
let input = input_schema(&defs, rename, rename.drop_fields);
let input = input_schema(&defs, edit, edit.drop_fields);
assert_eq!(input["type"], "object");
assert!(input["properties"].get("op").is_none());
assert!(input["properties"].get("sources").is_none());
assert!(input["properties"].get("target").is_some());
assert_eq!(input["required"], json!(["target"]));
assert!(input["$defs"].get("RenameTarget").is_some());
assert!(input["properties"].get("transaction").is_some());
assert_eq!(input["required"], json!(["transaction"]));
assert!(input["$defs"].get("EditTransaction").is_some());
let project = TOOL_SPECS.iter().find(|spec| spec.op == "project").unwrap();
let input = input_schema(&defs, project, &[]);
assert!(input["required"].is_null());
Expand Down
42 changes: 2 additions & 40 deletions crates/wright-cli/src/present.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ use std::time::Duration;

use wright_driver::Severity;
use wright_driver::config::OutputFormat;
use wright_driver::edit::RenameResult;
use wright_driver::progress::{ProgressEvent, ProgressObserver, ProgressPhase, ProgressUnit};
use wright_driver::result::{
CallGraphResult, CfgResult, CheckResult, CompileResult, ConvertResult, CostResult, Envelope,
Expand Down Expand Up @@ -283,7 +282,7 @@ pub(crate) fn render<T: serde::Serialize + ResultPresentation>(
) {
if pres.format == OutputFormat::Json {
let mut value = serde_json::to_value(envelope).expect("serializes");
if !matches!(envelope.command.as_str(), "compile" | "convert" | "rename") {
if !matches!(envelope.command.as_str(), "compile" | "convert") {
wright_driver::essential::envelope(&mut value, pres.all);
}
if envelope.command == "check" {
Expand Down Expand Up @@ -360,7 +359,7 @@ fn render_report<T: serde::Serialize + ResultPresentation>(
let mut ordered: Vec<&wright_driver::Diagnostic> = envelope.diagnostics.iter().collect();
ordered.sort_by_key(|diagnostic| severity_rank(diagnostic.severity));
let diagnostic_total = ordered.len();
let bounded = !matches!(envelope.command.as_str(), "compile" | "convert" | "rename");
let bounded = !matches!(envelope.command.as_str(), "compile" | "convert");
let limit = if pres.all || !bounded { usize::MAX } else { 20 };
for diagnostic in ordered.into_iter().take(limit) {
let marks = pres.marks();
Expand Down Expand Up @@ -847,22 +846,6 @@ impl ResultPresentation for CheckResult {
fn render_body(&self, _ctx: &RenderContext<'_>) {}
}

impl ResultPresentation for RenameResult {
fn metadata(&self) -> Option<String> {
let transaction = self.transaction.as_ref()?;
let edits = transaction.edits.len();
let sources = self.preview.as_ref().map_or(0, Vec::len);
Some(if self.written.is_empty() {
format!("{edits} edit(s) across {sources} source(s); preview — pass --write to apply")
} else {
format!("{edits} edit(s) applied to {}", self.written.join(", "))
})
}
fn render_body(&self, _ctx: &RenderContext<'_>) {
render_rename(self);
}
}

impl ResultPresentation for LintResult {
/// The lint verdict leads with finding counts by severity — the counts a
/// human needs to judge priority. Under a selection the per-severity
Expand Down Expand Up @@ -1706,27 +1689,6 @@ fn render_convert(result: &ConvertResult) {
print!("{}", result.text);
}

/// The `rename` human report (#434): per-source, the lines the validated
/// transaction changes, shown as `-`/`+` pairs with their line numbers —
/// rename edits only ever rewrite identifier occurrences in place. Written
/// files are listed after the diff when `--write` applied them.
fn render_rename(result: &RenameResult) {
let Some(previews) = &result.preview else {
return;
};
for preview in previews {
println!("\n{}", preview.source);
let original = result
.originals
.get(&preview.source)
.map_or("", String::as_str);
render_diff_lines(original, &preview.new_text, " ");
}
for written in &result.written {
println!("wrote {written}");
}
}

/// A line diff of `new_text` against `original`: the common prefix and
/// suffix collapse, and the changed middle aligns on its longest common
/// subsequence so lines that moved through the edit stay paired. Changed
Expand Down
18 changes: 1 addition & 17 deletions crates/wright-cli/src/tooldefs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,28 +183,12 @@ pub(crate) const TOOL_SPECS: &[ToolSpec] = &[
description: "Validate and preview a source-edit transaction atomically against the session's project; no filesystem writes. Sources default to the on-disk text.",
result: "the transaction's validation status, diagnostics, and previews when valid",
},
ToolSpec {
op: "semanticRename",
request_def: "SemanticRenameRequest",
drop_fields: &["sources"],
mcp: true,
description: "Propose a semantic rename: returns the validated transaction or structured refusal diagnostics. The target is a symbol id or declared name, or a source/line/col position. Sources default to the on-disk text.",
result: "the validated rename transaction or a structured refusal",
},
ToolSpec {
op: "providerSemanticRename",
request_def: "ProviderSemanticRenameRequest",
drop_fields: &["documents", "sources"],
mcp: true,
description: "Provider-owned rename for source-language projects (e.g. OverPy): the configured provider computes the edits, Wright verifies document versions and source preconditions, the provider validates the transaction, and the edited project is rechecked — returning validated edits or a structured refusal. The document set defaults to the session's loaded project (every member's disk text at version 0, keyed by `file://` URI); `position_document_uri` must be one of those URIs. `position` is 0-based line/UTF-16-character.",
result: "the provider-resolved rename transaction or a structured refusal",
},
ToolSpec {
op: "providerValidateEdit",
request_def: "ProviderValidateEditRequest",
drop_fields: &["documents", "sources"],
mcp: true,
description: "Validate a caller-proposed source-edit transaction for a provider-owned language through the same provider-backed pipeline as providerSemanticRename; no filesystem writes. The document set defaults to the session's loaded project (every member's disk text at version 0, keyed by `file://` URI), so every `edits[].source` must be one of those URIs and each `source_identity` verifies against the member's disk text — errors in any member document block the transaction.",
description: "Validate a caller-proposed source-edit transaction for a provider-owned language through the provider-backed pipeline: the provider validates each edited document and the edited project is rechecked; no filesystem writes. The document set defaults to the session's loaded project (every member's disk text at version 0, keyed by `file://` URI), so every `edits[].source` must be one of those URIs and each `source_identity` verifies against the member's disk text — errors in any member document block the transaction.",
result: "the provider-validated transaction or a structured refusal",
},
ToolSpec {
Expand Down
Loading
Loading