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
10 changes: 6 additions & 4 deletions crates/diffr-core/src/config/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,12 @@ mod tests {
shown["plugins"]["bundled"]["deleted-bodies"],
serde_json::json!({"enabled": true, "min_lines": 30})
);
assert!(shown["plugins"]["bundled"]["summarize"]["system_prompt"]
.as_str()
.unwrap()
.starts_with("For each listed fold"));
assert_eq!(
shown["plugins"]["bundled"]["summarize"]["system_prompt"],
crate::plugin::builtin::manifest("summarize")
.unwrap()
.options["system_prompt"]["default"]
);
let text = toml::to_string_pretty(&redacted(&config, false)).unwrap();
assert!(text.contains("[plugins.bundled.summarize]"), "{text}");
assert!(text.contains("system_prompt = "), "{text}");
Expand Down
43 changes: 5 additions & 38 deletions crates/diffr-core/src/plugin/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,39 +762,6 @@ impl PluginsConfig {
mod tests {
use crate::config::Config;

#[test]
fn the_prompt_description_links_to_its_default_in_the_source() {
let source = include_str!("../../../../plugins/shape/summarize/plugin.toml");
let lines: Vec<&str> = source.lines().collect();
let table = lines
.iter()
.position(|line| *line == "[options.system_prompt]")
.unwrap();
let start = table
+ lines[table..]
.iter()
.position(|line| line.starts_with("default = \"\"\""))
.unwrap();
let end = start
+ lines[start..]
.iter()
.position(|line| line.ends_with("\"\"\""))
.unwrap();
let manifest = super::builtin::manifest("summarize").unwrap();
let description = manifest.options["system_prompt"]["description"]
.as_str()
.unwrap();
let link = format!(
"https://github.com/devdotfast/diffr/blob/main/plugins/shape/summarize/plugin.toml#L{}-L{}",
start + 1,
end + 1
);
assert!(
description.contains(&link),
"{description}\nexpected {link}"
);
}

#[test]
fn defaults_can_follow_another_option() {
let summarize = |toml: &str| {
Expand Down Expand Up @@ -983,10 +950,10 @@ mod tests {
let summarize = &plugins["bundled"]["properties"]["summarize"]["properties"];
// A text setting, so settings screens let users edit the prompt.
assert!(summarize["system_prompt"].get("x-settings").is_none());
assert!(summarize["system_prompt"]["default"]
.as_str()
.unwrap()
.starts_with("For each listed fold, rewrite that function body"));
assert_eq!(
summarize["system_prompt"]["default"],
super::builtin::manifest("summarize").unwrap().options["system_prompt"]["default"]
);
let keys: Vec<&String> = plugins.as_object().unwrap().keys().collect();
assert_eq!(keys, ["order", "bundled", "external"]);
}
Expand Down Expand Up @@ -1084,7 +1051,7 @@ mod tests {
assert_eq!(classifier["hide_deleted"], false);
assert_eq!(
classifier["hide"],
serde_json::json!(["generated", "vendored", "test"])
serde_json::json!(["generated", "vendored"])
);
let summarize = &config.plugins.entries["bundled.summarize"];
assert_eq!(
Expand Down
1 change: 1 addition & 0 deletions crates/diffr-core/src/plugin/queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ mod tests {
"builtin:shared/queries/rust.scm",
"builtin:shared/queries/rust-docstrings.scm",
"builtin:deleted-bodies/queries/rust.scm",
"builtin:shared/queries/rust-tests.scm",
"builtin:test-bodies/queries/rust.scm",
"builtin:removed-runs/queries/rust.scm",
"builtin:context/queries/rust.scm",
Expand Down
4 changes: 2 additions & 2 deletions plugins/classify/plugin.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name = "classify"
title = "Classify"
description = "Tag each changed file as generated, vendored, docs or test from GitHub Linguist's rules, diffr's test-path rules and git attributes."
description = "Tag each changed file as generated, vendored, docs, test, integration or e2e from GitHub Linguist's rules, diffr's test-path rules and git attributes."

[options.hide]
type = "array"
items = { type = "string" }
title = "Tags that hide a file"
description = "A file carrying any of these tags starts hidden: it is diffed by line, no shape plugin runs on it, and it is shown collapsed. The first listed tag a file carries names the reason."
default = ["generated", "vendored", "test"]
default = ["generated", "vendored"]

[options.hide_deleted]
type = "boolean"
Expand Down
Binary file modified plugins/classify/plugin.wasm
Binary file not shown.
63 changes: 61 additions & 2 deletions plugins/classify/rust/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! The classifier: tags each changed file `generated`, `vendored`, `docs`,
//! `test`, and whatever a repository adds.
//! `test`, `integration`, `e2e`, and whatever a repository adds.
//!
//! Bundled rules come first and have the lowest precedence: GitHub
//! Linguist's `generated.rb` (ported in [`generated`]), its `vendor.yml` and
Expand All @@ -26,6 +26,8 @@ const GENERATED: &str = "generated";
const VENDORED: &str = "vendored";
const DOCS: &str = "docs";
const TEST: &str = "test";
const INTEGRATION: &str = "integration";
const E2E: &str = "e2e";

/// How much of a file the content rules read.
const PREFIX_BYTES: usize = 8 * 1024;
Expand All @@ -52,7 +54,15 @@ static DOCUMENTATION: LazyLock<Regex> = LazyLock::new(|| {
});

/// Directory names, anywhere in the path, that hold tests.
const TEST_DIRS: &[&str] = &["tests", "test", "__tests__", "spec"];
const TEST_DIRS: &[&str] = &[
"tests",
"test",
"__tests__",
"spec",
"e2e",
"cypress",
"playwright",
];

fn is_test(path: &str) -> bool {
let mut parts = path.split('/').filter(|part| !part.is_empty());
Expand All @@ -69,6 +79,49 @@ fn is_test(path: &str) -> bool {
|| name.contains(".test.")
|| name.contains(".spec.")
|| name.contains(".integration.")
|| name.contains(".e2e.")
}

/// `integration` or `e2e` for a test file whose path says which.
fn kind_by_path(path: &str) -> Option<&'static str> {
let segments: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect();
let parts = || {
segments
.iter()
.flat_map(|segment| segment.split(['.', '_', '-']))
};
if parts().any(|part| part == "e2e")
|| segments
.iter()
.any(|segment| ["cypress", "playwright"].contains(segment))
{
return Some(E2E);
}
if parts().any(|part| part == "integration") {
return Some(INTEGRATION);
}
let crate_tests = path.ends_with(".rs")
&& segments[..segments.len().saturating_sub(1)]
.iter()
.take_while(|segment| **segment != "src")
.any(|segment| *segment == "tests");
crate_tests.then_some(INTEGRATION)
}

/// `integration` or `e2e` for a test file whose first lines say which.
fn kind_by_content(text: &str) -> Option<&'static str> {
if text.contains("@playwright/test") {
return Some(E2E);
}
text.lines()
.map(str::trim)
.filter(|line| line.starts_with("//go:build") || line.starts_with("pytestmark"))
.flat_map(|line| line.split(|c: char| !c.is_ascii_alphanumeric()))
.find_map(|word| match word {
"e2e" => Some(E2E),
"integration" => Some(INTEGRATION),
_ => None,
})
}

/// Every bundled rule that looks at the repository-relative path alone.
Expand All @@ -85,6 +138,7 @@ fn from_path(path: &str) -> BTreeSet<&'static str> {
}
if is_test(path) {
tags.insert(TEST);
tags.extend(kind_by_path(path));
}
tags
}
Expand Down Expand Up @@ -317,6 +371,11 @@ impl GuestClassifier for Classify {
}
}
}
if bundled.contains(TEST) && !bundled.contains(INTEGRATION) && !bundled.contains(E2E) {
if let Some((bytes, _)) = prefix(side)? {
bundled.extend(kind_by_content(&String::from_utf8_lossy(&bytes)));
}
}
let tags: BTreeSet<String> = attributes.resolve(bundled).into_iter().collect();
Ok(Classification {
hidden: self.hidden(&file, &tags),
Expand Down
Binary file modified plugins/shape/context/plugin.wasm
Binary file not shown.
Binary file modified plugins/shape/removed-runs/plugin.wasm
Binary file not shown.
9 changes: 9 additions & 0 deletions plugins/shape/shared/queries/go-tests.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
; Test bodies, tagged for every plugin that treats tests specially.
((function_declaration name: (identifier) @_name body: (block "{" @fold.open (statement_list . [
(labeled_statement (label_name) . (_) @fold.indent)
(_) @fold.indent
]) "}" @fold.close) @fold)
(#not-match? @fold.indent "^[A-Za-z_][A-Za-z0-9_]*:\\s*\n")
(#match? @_name "^Test")
(#set! tag "summarize:test")
(#set! tag "test-bodies:test"))
10 changes: 10 additions & 0 deletions plugins/shape/shared/queries/javascript-tests.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
; Test bodies, tagged for every plugin that treats tests specially.
((call_expression
function: (identifier) @_name
arguments: (arguments [
(arrow_function body: (statement_block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
(function_expression body: (statement_block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
]))
(#match? @_name "^(it|test)$")
(#set! tag "summarize:test")
(#set! tag "test-bodies:test"))
5 changes: 5 additions & 0 deletions plugins/shape/shared/queries/python-tests.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
; Test bodies, tagged for every plugin that treats tests specially.
((function_definition name: (identifier) @_name ":" @fold.open body: (block . (_) @fold.indent) @fold)
(#match? @_name "^test_")
(#set! tag "summarize:test")
(#set! tag "test-bodies:test"))
5 changes: 5 additions & 0 deletions plugins/shape/shared/queries/rust-tests.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
; Test bodies, tagged for every plugin that treats tests specially.
((function_item attributes: (attributes (attribute_item) @_attribute) body: (block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
(#match? @_attribute "test")
(#set! tag "summarize:test")
(#set! tag "test-bodies:test"))
70 changes: 61 additions & 9 deletions plugins/shape/summarize/plugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description = "Pseudocode summaries for large new function bodies and tests."
# startup when a plugin that is on cannot be made.
[enabled]
title = "Summarize functions and tests"
description = "Summarize large new function bodies and right-side tests as pseudocode. Needs an API key: `api_key`, or the provider's variable in the environment: `GEMINI_API_KEY` or `GOOGLE_API_KEY`, `OPENAI_API_KEY`, or `ANTHROPIC_API_KEY`."
description = "Summarize large new function bodies and new tests as pseudocode. Needs an API key: `api_key`, or the provider's variable in the environment: `GEMINI_API_KEY` or `GOOGLE_API_KEY`, `OPENAI_API_KEY`, or `ANTHROPIC_API_KEY`."
default = false

[options.provider]
Expand Down Expand Up @@ -62,19 +62,71 @@ default = 3
[options.system_prompt]
type = "string"
title = "System prompt"
description = "The system instruction sent with every request; diffr builds each request's message from one body's numbered lines and its documentation. Unset uses the default: https://github.com/devdotfast/diffr/blob/main/plugins/shape/summarize/plugin.toml#L66-L72"
description = "The system instruction sent with every request; diffr builds each request's message from one body's numbered lines."
default = """\
For each listed fold, rewrite that function body as short \
pseudocode. Keep the names. No prose, no comments, no code fences. Use as few lines as \
possible: about one pseudocode line per five source lines, and never more than a third \
of the body's lines. When a fold lists a doc, also set "summary" to one sentence copied \
verbatim from that doc; otherwise leave it empty. Answer with a JSON object whose \
"summaries" array holds one {"id", "summary", "pseudocode"} object per fold."""
For each block of code, rewrite it as pseudocode a reviewer can \
read instead of the code. First decide what the code does. If uncertain, \
pick the option with the shortest output / explanation.

Examples below are delimited with code fences, you should not add those to your output.

A pure algorithm: plain-word steps, nested two spaces per level, for example:
```pseudo
if content is unchanged
return cached result
write new content
return fresh result
```

Control flow (mostly calls to other functions, in order): a call tree of the callees, \
each call's own calls indented under it, for example:
```pseudo
createSession
persistPrompt
launchAgent
navigateToSession
```

You can also create notional functions to make the pseudocode read easier in control-flow;
just add a note that you've done so, e.g.: \
```pseudo
loadSettings
reconcileSessions # summarization; not literal code
resumeRunningSessions
dropExpiredSessions
saveSettings
```

UI (renders components or markup): a component tree, with the hooks and state that \
matter under their component, and a component's module in parentheses when it comes \
from elsewhere, for example:
```pseudo
<SessionToolbar>
useSessionEvents()
<RunSkillButton> (packages/ui)
```

Break a test into setup, action being tested, and assertion phases; summarize setup with the \
methods above, e.g. control-flow pseudocode. For example:
```pseudo
Setup:
createSession
persistPrompt
stopAgent(session)
Action:
resumed := resumeSession(session.id)
Assertions:
assertEq(resumed.id, session.id)
assertEq(resumed.prompt, session.prompt)
assert agent is running
```

Reply with only pseudocode in the format above (code within the 'pseudo' blocks), nothing else."""

[options.tests]
type = "boolean"
title = "Summarize tests"
description = "Summarize right-side test bodies, including modified and unchanged tests in diffed files."
description = "Summarize new test bodies."
default = true

[options.test_min_lines]
Expand Down
Binary file modified plugins/shape/summarize/plugin.wasm
Binary file not shown.
9 changes: 1 addition & 8 deletions plugins/shape/summarize/queries/go.scm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
; inherits: builtin:shared/queries/go.scm, builtin:shared/queries/go-docstrings.scm
; inherits: builtin:shared/queries/go.scm, builtin:shared/queries/go-docstrings.scm, builtin:shared/queries/go-tests.scm
((function_declaration body: (block "{" @fold.open (statement_list . [
(labeled_statement (label_name) . (_) @fold.indent)
(_) @fold.indent
Expand All @@ -11,10 +11,3 @@
]) "}" @fold.close) @fold)
(#not-match? @fold.indent "^[A-Za-z_][A-Za-z0-9_]*:\\s*\n")
(#set! tag "summarize:function"))
((function_declaration name: (identifier) @_name body: (block "{" @fold.open (statement_list . [
(labeled_statement (label_name) . (_) @fold.indent)
(_) @fold.indent
]) "}" @fold.close) @fold)
(#not-match? @fold.indent "^[A-Za-z_][A-Za-z0-9_]*:\\s*\n")
(#match? @_name "^Test")
(#set! tag "summarize:test"))
10 changes: 1 addition & 9 deletions plugins/shape/summarize/queries/javascript.scm
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
; inherits: builtin:shared/queries/javascript.scm, builtin:shared/queries/javascript-docstrings.scm
; inherits: builtin:shared/queries/javascript.scm, builtin:shared/queries/javascript-docstrings.scm, builtin:shared/queries/javascript-tests.scm
((function_declaration body: (statement_block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
(#set! tag "summarize:function"))
((generator_function_declaration body: (statement_block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
Expand All @@ -12,11 +12,3 @@
(function_expression body: (statement_block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
])
(#set! tag "summarize:function"))
((call_expression
function: (identifier) @_name
arguments: (arguments [
(arrow_function body: (statement_block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
(function_expression body: (statement_block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
]))
(#match? @_name "^(it|test)$")
(#set! tag "summarize:test"))
5 changes: 1 addition & 4 deletions plugins/shape/summarize/queries/python.scm
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
; inherits: builtin:shared/queries/python.scm, builtin:shared/queries/python-docstrings.scm
; inherits: builtin:shared/queries/python.scm, builtin:shared/queries/python-docstrings.scm, builtin:shared/queries/python-tests.scm
((function_definition ":" @fold.open body: (block . (_) @fold.indent) @fold)
(#set! tag "summarize:function"))
((function_definition name: (identifier) @_name ":" @fold.open body: (block . (_) @fold.indent) @fold)
(#match? @_name "^test_")
(#set! tag "summarize:test"))
5 changes: 1 addition & 4 deletions plugins/shape/summarize/queries/rust.scm
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
; inherits: builtin:shared/queries/rust.scm, builtin:shared/queries/rust-docstrings.scm
; inherits: builtin:shared/queries/rust.scm, builtin:shared/queries/rust-docstrings.scm, builtin:shared/queries/rust-tests.scm
((function_item body: (block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
(#set! tag "summarize:function"))
((function_item attributes: (attributes (attribute_item) @_attribute) body: (block "{" @fold.open . (_) @fold.indent "}" @fold.close) @fold)
(#match? @_attribute "test")
(#set! tag "summarize:test"))
Loading
Loading