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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

All notable changes to the Toolpath workspace are documented here.

## toolpath 0.7.1 — 2026-08-14

Adds an optional `description` field to `StepMeta`, `PathMeta`, and
`GraphMeta` (issue #181): a human-readable summary of the object itself,
distinct from `intent`, which records the initial goal of the work. The
field is promoted from `additionalProperties` to a first-class key in
the JSON Schema and typed structs, and carried through the JSONL
`PathOpen`/`PathMeta` line kinds, so existing documents are unaffected.
The schema and RFC descriptions of `intent` were reworded to match its
actual definition.

## `path config edit` — 2026-08-14

- **`path-cli`** (0.18.0): new `path config` porcelain command, starting
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ edition = "2024"
license = "Apache-2.0"

[workspace.dependencies]
toolpath = { version = "0.7.0", path = "crates/toolpath" }
toolpath = { version = "0.7.1", path = "crates/toolpath" }
toolpath-convo = { version = "0.11.1", path = "crates/toolpath-convo" }
toolpath-git = { version = "0.6.0", path = "crates/toolpath-git" }
toolpath-claude = { version = "0.12.2", path = "crates/toolpath-claude", default-features = false }
Expand Down
19 changes: 12 additions & 7 deletions RFC.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,18 @@ identity and key information are provided in `meta.actors`.
The `meta` object holds all optional metadata. It can appear on both steps and
paths.

| Field | Description |
| ------------ | -------------------------------------------------- |
| `kind` | Path kind — see [Document Kind](#document-kind) (paths only) |
| `intent` | Human-readable description of purpose |
| `refs` | Links to issues, docs, reasoning |
| `actors` | Actor definitions with identities and keys |
| `signatures` | Cryptographic signatures for verification |
| Field | Description |
| ------------- | -------------------------------------------------- |
| `kind` | Path kind — see [Document Kind](#document-kind) (paths only) |
| `intent` | The initial goal of the work |
| `description` | Human-readable summary of the object itself |
| `refs` | Links to issues, docs, reasoning |
| `actors` | Actor definitions with identities and keys |
| `signatures` | Cryptographic signatures for verification |

`intent` and `description` differ in what they describe: `intent` records
what the work set out to do, fixed at the outset; `description` summarizes
the object as it stands, and may be written or revised after the fact.

#### Document Kind

Expand Down
2 changes: 1 addition & 1 deletion crates/toolpath/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "toolpath"
version = "0.7.0"
version = "0.7.1"
edition.workspace = true
license.workspace = true
repository = "https://github.com/empathic/toolpath"
Expand Down
18 changes: 15 additions & 3 deletions crates/toolpath/schema/toolpath.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,11 @@
"properties": {
"intent": {
"type": "string",
"description": "Human-readable description of purpose"
"description": "The initial goal of the work"
},
"description": {
"type": "string",
"description": "Human-readable summary of the step itself"
},
"source": {
"$ref": "#/$defs/vcsSource",
Expand Down Expand Up @@ -348,7 +352,11 @@
},
"intent": {
"type": "string",
"description": "Human-readable description of purpose"
"description": "The initial goal of the work"
},
"description": {
"type": "string",
"description": "Human-readable summary of the path itself"
},
"refs": {
"type": "array",
Expand Down Expand Up @@ -433,7 +441,11 @@
},
"intent": {
"type": "string",
"description": "Human-readable description of purpose"
"description": "The initial goal of the work"
},
"description": {
"type": "string",
"description": "Human-readable summary of the graph itself"
},
"refs": {
"type": "array",
Expand Down
63 changes: 63 additions & 0 deletions crates/toolpath/src/jsonl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub struct PathOpenMeta {
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub intent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub refs: Vec<Ref>,
#[serde(flatten, default)]
Expand Down Expand Up @@ -156,6 +158,8 @@ pub struct PathMetaPatch {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub intent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub refs: Option<Vec<Ref>>,
#[serde(flatten, default)]
pub extra: HashMap<String, serde_json::Value>,
Expand Down Expand Up @@ -354,6 +358,7 @@ impl Path {
meta.kind = m.kind;
meta.source = m.source;
meta.intent = m.intent;
meta.description = m.description;
meta.refs = m.refs;
meta.extra = m.extra;
}
Expand Down Expand Up @@ -518,6 +523,9 @@ fn apply_meta_patch(path_meta: &mut PathMeta, patch: PathMetaPatch) {
if let Some(v) = patch.intent {
path_meta.intent = Some(v);
}
if let Some(v) = patch.description {
path_meta.description = Some(v);
}
if let Some(v) = patch.refs {
path_meta.refs = v;
}
Expand Down Expand Up @@ -557,6 +565,7 @@ fn path_meta_is_empty(m: &PathMeta) -> bool {
&& m.kind.is_none()
&& m.source.is_none()
&& m.intent.is_none()
&& m.description.is_none()
&& m.refs.is_empty()
&& m.actors.as_ref().is_none_or(|a| a.is_empty())
&& m.signatures.is_empty()
Expand Down Expand Up @@ -667,6 +676,7 @@ fn write_line<W: Write>(w: &mut W, line: &JsonlLine) -> Result<(), JsonlError> {

fn step_meta_is_empty(m: &StepMeta) -> bool {
m.intent.is_none()
&& m.description.is_none()
&& m.source.is_none()
&& m.refs.is_empty()
&& m.actors.as_ref().is_none_or(|a| a.is_empty())
Expand All @@ -682,13 +692,15 @@ fn path_meta_for_open(m: &PathMeta) -> Option<PathOpenMeta> {
kind: m.kind.clone(),
source: m.source.clone(),
intent: m.intent.clone(),
description: m.description.clone(),
refs: m.refs.clone(),
extra: m.extra.clone(),
};
if open.title.is_none()
&& open.kind.is_none()
&& open.source.is_none()
&& open.intent.is_none()
&& open.description.is_none()
&& open.refs.is_empty()
&& open.extra.is_empty()
{
Expand Down Expand Up @@ -1200,6 +1212,57 @@ mod tests {
assert_eq!(canonical_json(&p), canonical_json(&back));
}

#[test]
fn roundtrip_description() {
let mut step = make_step("s1", None);
step.meta = Some(StepMeta {
description: Some("renames the config field".into()),
..Default::default()
});
let p = Path {
path: PathIdentity {
id: "p".into(),
base: None,
head: "s1".into(),
graph_ref: None,
},
steps: vec![step],
meta: Some(PathMeta {
intent: Some("fix the config bug".into()),
description: Some("a short session touching config parsing".into()),
..Default::default()
}),
};
let jsonl = p.to_jsonl_string().unwrap();
let back = Path::from_jsonl_str(&jsonl).unwrap();
assert_eq!(canonical_json(&p), canonical_json(&back));
// Typed field, not the extra catch-all.
let meta = back.meta.unwrap();
assert_eq!(
meta.description.as_deref(),
Some("a short session touching config parsing")
);
assert!(meta.extra.is_empty());
}

#[test]
fn reader_path_meta_patch_description() {
let input = concat!(
r#"{"PathOpen":{"version":"1","id":"p"}}"#,
"\n",
r#"{"Step":{"step":{"id":"s1","actor":"a","timestamp":"t"},"change":{}}}"#,
"\n",
r#"{"PathMeta":{"patch":{"description":"summary written later"}}}"#,
"\n",
r#"{"Head":{"step_id":"s1"}}"#,
"\n",
);
let path = Path::from_jsonl_str(input).unwrap();
let meta = path.meta.unwrap();
assert_eq!(meta.description.as_deref(), Some("summary written later"));
assert!(meta.extra.is_empty());
}

#[test]
fn roundtrip_dead_end_uses_explicit_head() {
let p = path_with_dead_end();
Expand Down
6 changes: 6 additions & 0 deletions crates/toolpath/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub struct GraphMeta {
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub intent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub refs: Vec<Ref>,
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -167,6 +169,8 @@ pub struct PathMeta {
pub source: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub intent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub refs: Vec<Ref>,
#[serde(default, skip_serializing_if = "Option::is_none")]
Expand Down Expand Up @@ -279,6 +283,8 @@ pub struct StepMeta {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub intent: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source: Option<VcsSource>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub refs: Vec<Ref>,
Expand Down
2 changes: 1 addition & 1 deletion site/_data/crates.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"name": "toolpath",
"version": "0.7.0",
"version": "0.7.1",
"description": "Core types, builders, and query API",
"docs": "https://docs.rs/toolpath",
"crate": "https://crates.io/crates/toolpath",
Expand Down
Loading