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
4 changes: 2 additions & 2 deletions .config/jp/tools/src/git/diff_commit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ fn git_diff_commit_impl<R: ProcessRunner>(
};

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())
}

Expand Down
6 changes: 5 additions & 1 deletion .config/jp/tools/src/git/diff_commit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions .ignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
4 changes: 2 additions & 2 deletions .jp/config/personas/pr-reviewer.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
12 changes: 12 additions & 0 deletions .jp/config/skill/rust-development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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<i32> = \
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]]
Expand Down Expand Up @@ -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<i32> = vec![1, 2, 3];"
reason = "The macro already determines the type. Redundant annotation is noise."
3 changes: 2 additions & 1 deletion .jp/mcp/tools/git/add_intent.toml
Original file line number Diff line number Diff line change
@@ -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}}"
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
10 changes: 7 additions & 3 deletions crates/jp_cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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"] }
Expand Down Expand Up @@ -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 }
Expand Down
10 changes: 0 additions & 10 deletions crates/jp_cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,6 @@ struct Globals {
)]
config: Vec<KeyValueOrPath>,

#[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.
Expand Down
2 changes: 1 addition & 1 deletion crates/jp_cli/src/lib_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
5 changes: 5 additions & 0 deletions docs/architecture/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
114 changes: 114 additions & 0 deletions docs/architecture/ubiquitous-language.md
Original file line number Diff line number Diff line change
@@ -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

<!--toc:start-->
- [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)
<!--toc:end-->

## 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`.
2 changes: 1 addition & 1 deletion docs/rfd/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
)
}
Expand Down
70 changes: 70 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading