From 4b82b1f3e6b5b4857542ce27879322338d7bafc0 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Wed, 6 May 2026 13:10:49 +0200 Subject: [PATCH 1/2] chore(tools): Move diff note after the fenced code block Previously, the `[Showing ...]` note was written before the fenced diff block, which meant the note appeared at the top of the output. The output now places the fenced diff block first and appends the note after it, which reads more naturally when scanning the result. The test is updated accordingly: rather than asserting the output ends with the closing fence, it asserts both the fence and the note are present in the correct relative order. Signed-off-by: Jean Mertz --- .config/jp/tools/src/git/diff_commit.rs | 4 ++-- .config/jp/tools/src/git/diff_commit_tests.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.config/jp/tools/src/git/diff_commit.rs b/.config/jp/tools/src/git/diff_commit.rs index cfbd1373..a32aa6a8 100644 --- a/.config/jp/tools/src/git/diff_commit.rs +++ b/.config/jp/tools/src/git/diff_commit.rs @@ -65,10 +65,10 @@ fn git_diff_commit_impl( }; let mut result = String::new(); + write!(result, "```diff\n{}\n```", content.trim_end())?; if let Some(note) = note { - writeln!(result, "{note}\n")?; + writeln!(result, "\n\n{note}\n")?; } - write!(result, "```diff\n{}\n```", content.trim_end())?; Ok(result.into()) } diff --git a/.config/jp/tools/src/git/diff_commit_tests.rs b/.config/jp/tools/src/git/diff_commit_tests.rs index f06e8539..b0ac0309 100644 --- a/.config/jp/tools/src/git/diff_commit_tests.rs +++ b/.config/jp/tools/src/git/diff_commit_tests.rs @@ -205,7 +205,11 @@ fn diff_commit_with_pattern() { assert!(content.contains("```diff\n")); assert!(content.contains("world")); - assert!(content.ends_with("\n```")); + // The closing diff fence is followed by the `[Showing ...]` note, + // so we check that both the fence and the note are present rather + // than that the output ends with the fence. + assert!(content.contains("\n```\n"), "got: {content}"); + assert!(content.contains("[Showing"), "got: {content}"); } #[test] From 8b903c5c57d95044cb70a33c034e7666ee8443d4 Mon Sep 17 00:00:00 2001 From: Jean Mertz Date: Wed, 6 May 2026 13:27:51 +0200 Subject: [PATCH 2/2] chore: Remove unused `--no-inherit` flag and add ubiquitous language doc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the unused `-I`/`--no-inherit` global CLI flag from `jp_cli`. Fix a stale env var name in the test suite: `JP_GLOBAL_CONFIG_FILE` → `JP_GLOBAL_CONFIG_DIR`. Add a new `docs/architecture/ubiquitous-language.md` — a canonical glossary of JP's domain vocabulary (Workspace, Conversation, Thread, Turn, Attachment, Inquiry, Provider, RFD, Tool Call). This gives both human and AI contributors a shared, rigorous reference to avoid term drift across code, docs, and commit messages. Add a `rfd-triage` recipe to the justfile for triaging reviewer feedback on an RFD. Accepts the RFD number (permanent or draft), a mode (`new`/`continue`), and the conversation ID of the review run to pull feedback from, then invokes the `rfd-triager` persona. Also include minor housekeeping: ignore `.jp/local-conversations/`, include `r.num` in the RFD index search filter, switch the `pr-reviewer` persona to `gpt` with `max` reasoning effort, add a "no redundant type annotations" rule to the Rust development skill, and reorder a few dependency entries for consistency. Signed-off-by: Jean Mertz --- .ignore | 1 + .jp/config/personas/pr-reviewer.toml | 4 +- .jp/config/skill/rust-development.toml | 12 +++ .jp/mcp/tools/git/add_intent.toml | 3 +- Cargo.toml | 2 +- crates/jp_cli/Cargo.toml | 10 +- crates/jp_cli/src/lib.rs | 10 -- crates/jp_cli/src/lib_tests.rs | 2 +- docs/architecture/index.md | 5 + docs/architecture/ubiquitous-language.md | 114 +++++++++++++++++++++++ docs/rfd/index.md | 2 +- justfile | 70 ++++++++++++++ 12 files changed, 216 insertions(+), 19 deletions(-) create mode 100644 docs/architecture/ubiquitous-language.md diff --git a/.ignore b/.ignore index 744aee4d..3990a1fe 100644 --- a/.ignore +++ b/.ignore @@ -15,6 +15,7 @@ # Then the exclusions (applied within the un-ignored trees) .jp/conversations/ +.jp/local-conversations/ docs/.yarn/ docs/.vitepress/cache/ docs/.vitepress/dist/ diff --git a/.jp/config/personas/pr-reviewer.toml b/.jp/config/personas/pr-reviewer.toml index 7aaf0a82..3a928166 100644 --- a/.jp/config/personas/pr-reviewer.toml +++ b/.jp/config/personas/pr-reviewer.toml @@ -47,8 +47,8 @@ intentional: your job is to surface findings the user can curate before publishi """ [assistant.model] -id = "opus" -parameters.reasoning.effort = "xhigh" +id = "gpt" +parameters.reasoning.effort = "max" [[assistant.instructions]] title = "Review Workflow" diff --git a/.jp/config/skill/rust-development.toml b/.jp/config/skill/rust-development.toml index 03806ba1..28f09e50 100644 --- a/.jp/config/skill/rust-development.toml +++ b/.jp/config/skill/rust-development.toml @@ -83,6 +83,13 @@ items = [ underscore (`crate_`, `type_`) or a raw identifier (`r#type`). Never misspell the word (e.g. \ `krate` is wrong).\ """, + """\ + **No redundant type annotations.** Do not annotate `let` bindings when the right-hand side \ + already determines the type. Write `let foo = vec![1, 2, 3];` not `let foo: Vec = \ + vec![1, 2, 3];`. Annotations are fine when the compiler genuinely can't infer (e.g. \ + `.collect()` into a specific collection), when you need a non-default numeric type, or when \ + an annotation is clearer than a turbofish.\ + """, ] [[assistant.instructions.examples]] @@ -167,3 +174,8 @@ match val { }\ """ reason = "Never start a match arm pattern with |." + +[[assistant.instructions.examples]] +good = "let items = vec![1, 2, 3];" +bad = "let items: Vec = vec![1, 2, 3];" +reason = "The macro already determines the type. Redundant annotation is noise." diff --git a/.jp/mcp/tools/git/add_intent.toml b/.jp/mcp/tools/git/add_intent.toml index 135b08f9..4e7066d4 100644 --- a/.jp/mcp/tools/git/add_intent.toml +++ b/.jp/mcp/tools/git/add_intent.toml @@ -1,7 +1,8 @@ [conversation.tools.git_add_intent] enable = "explicit" run = "unattended" -style.inline_results = "full" +style.inline_results = "off" +style.parameters = "function_call" source = "local" command = "just serve-tools {{context}} {{tool}}" diff --git a/Cargo.toml b/Cargo.toml index 2d793d3f..d9111e78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,10 @@ resolver = "3" jp_attachment = { path = "crates/jp_attachment" } jp_attachment_bear_note = { path = "crates/jp_attachment_bear_note" } jp_attachment_cmd_output = { path = "crates/jp_attachment_cmd_output" } -jp_attachment_internal = { path = "crates/jp_attachment_internal" } jp_attachment_file_content = { path = "crates/jp_attachment_file_content" } jp_attachment_github = { path = "crates/jp_attachment_github" } jp_attachment_http_content = { path = "crates/jp_attachment_http_content" } +jp_attachment_internal = { path = "crates/jp_attachment_internal" } jp_attachment_mcp_resources = { path = "crates/jp_attachment_mcp_resources" } jp_config = { path = "crates/jp_config" } jp_conversation = { path = "crates/jp_conversation" } diff --git a/crates/jp_cli/Cargo.toml b/crates/jp_cli/Cargo.toml index 04ea7803..09f9f571 100644 --- a/crates/jp_cli/Cargo.toml +++ b/crates/jp_cli/Cargo.toml @@ -17,10 +17,10 @@ version.workspace = true jp_attachment = { workspace = true } jp_attachment_bear_note = { workspace = true } jp_attachment_cmd_output = { workspace = true } -jp_attachment_internal = { workspace = true } jp_attachment_file_content = { workspace = true } jp_attachment_github = { workspace = true } jp_attachment_http_content = { workspace = true } +jp_attachment_internal = { workspace = true } jp_attachment_mcp_resources = { workspace = true } jp_config = { workspace = true } jp_conversation = { workspace = true } @@ -69,8 +69,8 @@ indoc = { workspace = true } inquire = { workspace = true, features = ["crossterm"] } minijinja = { workspace = true } quick-xml = { workspace = true, features = ["serialize"] } -relative-path = { workspace = true } rayon = { workspace = true } +relative-path = { workspace = true } reqwest = { workspace = true } schemars = { workspace = true } schematic = { workspace = true, features = ["schema_serde", "renderer_template", "toml"] } @@ -103,7 +103,11 @@ which = { workspace = true, features = ["real-sys"] } libc = { workspace = true } [target.'cfg(windows)'.dependencies] -windows-sys = { workspace = true, features = ["Win32_System_Console", "Win32_System_Threading", "Win32_Foundation"] } +windows-sys = { workspace = true, features = [ + "Win32_System_Console", + "Win32_System_Threading", + "Win32_Foundation", +] } [build-dependencies] chrono = { workspace = true } diff --git a/crates/jp_cli/src/lib.rs b/crates/jp_cli/src/lib.rs index 3d20b49c..217496ca 100644 --- a/crates/jp_cli/src/lib.rs +++ b/crates/jp_cli/src/lib.rs @@ -112,16 +112,6 @@ struct Globals { )] config: Vec, - #[arg( - short = 'I', - long = "no-inherit", - global = true, - value_parser = BoolValueParser::new().map(|v| !v), - default_value_t = true, - help = "Disable loading of non-CLI provided config.", - )] - load_non_cli_config: bool, - /// Increase verbosity of logging. /// /// Can be specified multiple times to increase verbosity. diff --git a/crates/jp_cli/src/lib_tests.rs b/crates/jp_cli/src/lib_tests.rs index 7c82ac3e..ccbe361e 100644 --- a/crates/jp_cli/src/lib_tests.rs +++ b/crates/jp_cli/src/lib_tests.rs @@ -106,7 +106,7 @@ fn test_load_cli_cfg_args_user_global_root() { let result = build_cfg(partial, &overrides, None).unwrap(); assert_eq!(result.assistant.name.as_deref(), Some("from-global")); - unsafe { std::env::remove_var("JP_GLOBAL_CONFIG_FILE") }; + unsafe { std::env::remove_var("JP_GLOBAL_CONFIG_DIR") }; } #[test] diff --git a/docs/architecture/index.md b/docs/architecture/index.md index 5a08d642..cd54e513 100644 --- a/docs/architecture/index.md +++ b/docs/architecture/index.md @@ -27,3 +27,8 @@ This section describes the technical architecture of JP. WebAssembly components. Covers the `wasmtime` runtime, WIT contract, builtin tools (embedded), local Wasm tools (disk-loaded), and the `jp_tool_learn` guest crate. + +- [Ubiquitous Language](ubiquitous-language.md) - The canonical glossary of + JP's domain vocabulary. Defines the core terms (Workspace, Conversation, + Turn, Thread, Attachment, Inquiry, Provider, Backend, etc.) and the + distinctions between them. diff --git a/docs/architecture/ubiquitous-language.md b/docs/architecture/ubiquitous-language.md new file mode 100644 index 00000000..3abe53f3 --- /dev/null +++ b/docs/architecture/ubiquitous-language.md @@ -0,0 +1,114 @@ +# Ubiquitous Language + +This is JP's domain vocabulary: the shared, rigorous terms used across code, +documentation, commits, RFDs, CLI help, and error messages. Every contributor +(human or AI) should use these terms *as written* — don't paraphrase or +substitute near-synonyms. + +When you encounter a new concept that doesn't fit existing terms, add it here. +When an existing term is contradicted by usage or misleading, update the +definition — don't paper over the drift with aliases or inline comments +explaining the mismatch. + +In disagreements between code and docs, the code is authoritative. + +## Table of Contents + + +- [Ubiquitous Language](#ubiquitous-language) + - [Table of Contents](#table-of-contents) + - [Terms](#terms) + - [Attachment](#attachment) + - [Conversation](#conversation) + - [Conversation Event](#conversation-event) + - [Inquiry](#inquiry) + - [Persona](#persona) + - [Provider](#provider) + - [RFD](#rfd) + - [Thread](#thread) + - [Tool Call](#tool-call) + - [Turn](#turn) + - [Workspace](#workspace) + + +## Terms + +### Attachment + +External content attached to a conversation to provide context: a file, URL +contents, command output, Bear note, MCP resource, etc. Implemented as +`Attachment` in `jp_attachment`. Each attachment kind is a separate crate +(`jp_attachment_file_content`, `jp_attachment_cmd_output`, and so on). + +### Conversation + +A persistent sequence of events identified by a `ConversationId`, living within +a Workspace. Implemented as `ConversationStream` in `jp_conversation`. The +user-facing notion of "a chat history with the assistant." + +**Not to be confused with Thread.** A Conversation is the stored entity; a +Thread is what we build from it to send to an LLM. + +### Conversation Event + +The atomic unit of a conversation. Implemented as `ConversationEvent` (with +`EventKind`) in `jp_conversation`. The variants are `TurnStart`, `ChatRequest`, +`ChatResponse`, `ToolCallRequest`, `ToolCallResponse`, `InquiryRequest`, +`InquiryResponse`. + +Not every event is sent to LLM providers. `EventKind::is_provider_visible()` +filters the stream down to the chat and tool-call events; turn markers and +inquiries are internal. + +### Inquiry + +A structured question-and-answer pair between the assistant, a tool, and/or the +user — distinct from a regular chat message. Carried as `InquiryRequest` and +`InquiryResponse` events within a conversation. Used for mid-turn clarification +that should not appear in the main chat stream or be sent to the LLM provider as +context. + +### Provider + +An LLM vendor integration — one of `anthropic`, `google`, `openai`, +`openrouter`, `llamacpp`, `ollama`, `cerebras`, `deepseek`. Each implements the +`Provider` trait in `jp_llm`. + +### RFD + +"Request for Discussion" — JP's design document format, stored in `docs/rfd/`. +Each RFD captures design rationale for a significant change. Numeric-prefixed +RFDs (`001-`, `002-`, …) are the accepted series; `D`-prefixed RFDs (`D01-`, +`D02-`, …) are drafts or abandoned proposals. The process itself is defined in +[RFD-001](../rfd/001-jp-rfd-process.md). + +### Thread + +The decomposed, provider-facing projection of a Conversation: a rendered system +prompt, rendered instruction sections, raw attachments, and a filtered event +stream, ready to be sent to an LLM provider. Implemented as `Thread` in +`jp_conversation::thread`. + +A Conversation becomes a Thread at query time, via the config and conversation +pipeline. A Thread is transient; a Conversation is persisted. + +### Tool Call + +An LLM-requested function invocation (`ToolCallRequest`) and its eventual +response (`ToolCallResponse`). Tool calls are events within a Turn. The tool +itself can be a built-in, a local command, an MCP-provided tool, or a plugin. + +### Turn + +A group of conversation events delimited by a `TurnStart` marker: one user chat +request through the assistant's final response for that request, including any +intermediate tool calls and inquiries. Implemented as `Turn<'a>` in +`jp_conversation::stream::turn_iter`. + +A single Conversation contains many Turns, separated by `TurnStart` events. + +### Workspace + +The top-level project unit, housing conversations, configuration, plugins, and +state for JP. Identified by a `.jp/` directory at the project root. Implemented +as `Workspace` in `jp_workspace`. diff --git a/docs/rfd/index.md b/docs/rfd/index.md index 396b6761..9cd00b21 100644 --- a/docs/rfd/index.md +++ b/docs/rfd/index.md @@ -89,7 +89,7 @@ const filtered = computed(() => { if (textQuery) { rows = rows.filter(r => - [r.title, r.category, r.status, r.summary] + [r.num, r.title, r.category, r.status, r.summary] .some(v => v?.toLowerCase().includes(textQuery)) ) } diff --git a/justfile b/justfile index 49f41264..2131c527 100644 --- a/justfile +++ b/justfile @@ -354,6 +354,76 @@ rfd-review NNN *ARGS: _install-jp printf "Reviewing $file\n\n" >&2 jp query --attach "$file" --new --cfg=personas/rfd-reviewer $args +# Triage feedback on an RFD from a reviewer conversation. +# +# NNN is the RFD (permanent number like 41/041, or draft ID like D01). +# MODE is either `new` (start a fresh triage conversation) or `continue` +# (append to the current session, e.g. to follow up on the implementation +# conversation that produced the RFD). +# CONVO is the conversation ID of the `rfd-review` run to pull feedback from. +# Only the final assistant response of that conversation is attached. +[group('rfd')] +[positional-arguments] +rfd-triage NNN MODE CONVO *ARGS: _install-jp + #!/usr/bin/env sh + set -eu + + shift 3 # remove NNN, MODE, CONVO from positional params + args="$@" + msg="I received feedback on the RFD. Read the attached reviewer response \ + carefully, then triage it item by item. Ground each point against the code \ + and related RFDs. Do not assume the feedback is correct. For each item \ + give a verdict (accept / amend / dismiss / defer) with reasoning, and for \ + accepted or amended items describe the concrete change you would make to \ + the RFD. Do NOT edit the RFD yet; give your opinion first." + + # Resolve the target RFD file. + arg="{{NNN}}" + if echo "$arg" | grep -qiE '^D[0-9]+$'; then + draft_id=$(echo "$arg" | tr '[:lower:]' '[:upper:]') + file=$(ls docs/rfd/drafts/${draft_id}-*.md 2>/dev/null | head -1) + if [ -z "$file" ]; then + echo "No draft RFD found with ID ${draft_id}." >&2; exit 1 + fi + elif echo "$arg" | grep -qE '^[0-9]+$'; then + n=$(echo "$arg" | sed 's/^0*//') + num=$(printf "%03d" "${n:-0}") + file=$(ls docs/rfd/${num}-*.md 2>/dev/null | head -1) + if [ -z "$file" ]; then + echo "No RFD found with number ${num}." >&2; exit 1 + fi + else + echo "Invalid argument '${arg}'. Use a number (41) or draft ID (D01)." >&2; exit 1 + fi + + # Resolve MODE. Explicit to avoid silently picking a default. + case "{{MODE}}" in + new) new_flag="--new" ;; + continue) new_flag="" ;; + *) + echo "Invalid MODE '{{MODE}}'. Use 'new' or 'continue'." >&2 + exit 1 ;; + esac + + starts_with() { case ${2-} in "$1"*) true;; *) false;; esac; } + contains() { case ${2-} in *"$1"*) true;; *) false;; esac; } + if starts_with "-- " "$@"; then + elif starts_with "-" "$@" && ! contains "-- " "$@"; then + args="$* -- $msg" + elif [ -n "$args" ]; then + args="$msg\n\n Here is additional context: $args" + elif [ -z "$args" ]; then + args="$msg" + fi + + printf "Triaging feedback on $file (mode: {{MODE}})\n\n" >&2 + jp query \ + --attach "file://$file" \ + --attach "jp://{{CONVO}}?select=a" \ + $new_flag \ + --cfg=personas/rfd-triager \ + $args + # Create a new RFD draft. CATEGORY is 'design', 'decision', 'guide', or 'process'. # Drafts are created as docs/rfd/drafts/DNN-slug.md; a permanent number is assigned # and the file is moved up to docs/rfd/ at Discussion.