mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-21 02:28:07 +02:00
feat(cli): surface stored-query @description/@instruction in queries list (#280)
* test: e2e coverage for @description/@instruction surfaces Add end-to-end tests pinning the two annotation surfaces as they exist today, at their real boundaries: - engine (lifecycle.rs): schema-level @description (node/edge/property) and @instruction (node/edge) persist verbatim into the on-disk _schema.ir.json through Omnigraph::init; property-level @instruction aborts init and writes no schema IR. - server (stored_queries.rs): query-level @description/@instruction on a stored query surface as typed QueryCatalogEntry fields over GET /queries, and a query declaring neither omits both fields. No behavior change — these document the current contract. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(cli): surface stored-query @description/@instruction in `queries list` A stored query's @description/@instruction are its catalog metadata — what it does and how to invoke it. The HTTP GET /queries catalog already carries them, but `omnigraph queries list` dropped both fields in human and --json output even though they were available on the registry entry. Carry description/instruction on QueriesListItem (Option, skipped when None) and copy them from the query decl. Human output prints an indented `description:` / `instruction:` line per query when present; --json includes the fields when present and omits them otherwise — matching the HTTP catalog shape documented in docs/user/operations/server.md. Tests (cli_queries.rs): a query with both annotations surfaces them in human + --json; a query with neither prints no annotation lines and omits both JSON fields. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(cli): document `queries list` output incl. description/instruction Per AGENTS.md maintenance Rule 1, document the user-visible `queries list` output alongside the field addition. The `queries` command family had no row in the CLI reference top-level table; add one covering `list` (human + --json shapes, with description/instruction shown only when declared, matching the HTTP GET /queries catalog) and `validate`. Addresses the Greptile P2 review finding on PR #280. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): indent multiline stored-query annotations in `queries list` A `@description`/`@instruction` value can be multiline (GQ string literals admit newlines), which made the human `queries list` output break back to the left margin on continuation lines. Indent continuation lines to align under the first via a `print_query_annotation` helper. Addresses review feedback from @martin-g on PR #280. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Ragnor Comerford <ragnor.comerford@gmail.com>
This commit is contained in:
parent
7fd23c54a3
commit
3feb23af05
6 changed files with 300 additions and 0 deletions
|
|
@ -231,6 +231,125 @@ fn queries_list_prints_registered_query() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queries_list_surfaces_description_and_instruction() {
|
||||
// `@description`/`@instruction` are the whole point of a stored query in a
|
||||
// catalog — they tell an agent/operator what it does and how to invoke it.
|
||||
// The CLI catalog must surface them in both human and --json output, to
|
||||
// match the HTTP `GET /queries` surface.
|
||||
let cluster = converged_cluster_with_query(
|
||||
"described.gq",
|
||||
"query described($name: String) \
|
||||
@description(\"Find a person by exact name.\") \
|
||||
@instruction(\"Use for exact lookups; prefer search for fuzzy matches.\") \
|
||||
{ match { $p: Person { name: $name } } return { $p.age } }",
|
||||
" described:\n file: ./described.gq\n",
|
||||
);
|
||||
|
||||
// Human output.
|
||||
let output = output_success(
|
||||
cli().arg("queries").arg("list").arg("--cluster").arg(cluster.path()),
|
||||
);
|
||||
let stdout = stdout_string(&output);
|
||||
assert!(
|
||||
stdout.contains("description: Find a person by exact name."),
|
||||
"human list must show @description; stdout:\n{stdout}"
|
||||
);
|
||||
assert!(
|
||||
stdout.contains("instruction: Use for exact lookups; prefer search for fuzzy matches."),
|
||||
"human list must show @instruction; stdout:\n{stdout}"
|
||||
);
|
||||
|
||||
// --json output.
|
||||
let output = output_success(
|
||||
cli()
|
||||
.arg("queries")
|
||||
.arg("list")
|
||||
.arg("--cluster")
|
||||
.arg(cluster.path())
|
||||
.arg("--json"),
|
||||
);
|
||||
let body: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
|
||||
let entry = body["queries"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|q| q["name"] == "described")
|
||||
.unwrap();
|
||||
assert_eq!(entry["description"], "Find a person by exact name.");
|
||||
assert_eq!(
|
||||
entry["instruction"],
|
||||
"Use for exact lookups; prefer search for fuzzy matches."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queries_list_indents_multiline_annotation_continuation() {
|
||||
// GQ string literals admit newlines, so a `@description`/`@instruction`
|
||||
// can be multiline. Human output must indent continuation lines to align
|
||||
// under the first rather than breaking back to the left margin.
|
||||
let cluster = converged_cluster_with_query(
|
||||
"multi.gq",
|
||||
"query multi($name: String) \
|
||||
@description(\"line one\\nline two\") \
|
||||
{ match { $p: Person { name: $name } } return { $p.age } }",
|
||||
" multi:\n file: ./multi.gq\n",
|
||||
);
|
||||
let output = output_success(
|
||||
cli().arg("queries").arg("list").arg("--cluster").arg(cluster.path()),
|
||||
);
|
||||
let stdout = stdout_string(&output);
|
||||
// " description: " is 17 chars wide; the continuation aligns under it.
|
||||
assert!(
|
||||
stdout.contains(" description: line one\n line two"),
|
||||
"multiline annotation must indent the continuation; stdout:\n{stdout}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queries_list_omits_annotations_when_absent() {
|
||||
// The other half of the contract: a query that declares neither annotation
|
||||
// prints no extra lines and omits both JSON fields entirely. This keeps the
|
||||
// catalog clean rather than echoing empty `description:`/`instruction:`.
|
||||
let cluster = converged_cluster_with_query(
|
||||
"bare.gq",
|
||||
"query bare() { match { $p: Person } return { $p.name } }",
|
||||
" bare:\n file: ./bare.gq\n",
|
||||
);
|
||||
|
||||
// Human output: the query is listed, but no annotation lines.
|
||||
let output = output_success(
|
||||
cli().arg("queries").arg("list").arg("--cluster").arg(cluster.path()),
|
||||
);
|
||||
let stdout = stdout_string(&output);
|
||||
assert!(stdout.contains("bare()"), "stdout:\n{stdout}");
|
||||
assert!(
|
||||
!stdout.contains("description:") && !stdout.contains("instruction:"),
|
||||
"a query without annotations prints no annotation lines; stdout:\n{stdout}"
|
||||
);
|
||||
|
||||
// --json output: both fields omitted (not present as null).
|
||||
let output = output_success(
|
||||
cli()
|
||||
.arg("queries")
|
||||
.arg("list")
|
||||
.arg("--cluster")
|
||||
.arg(cluster.path())
|
||||
.arg("--json"),
|
||||
);
|
||||
let body: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
|
||||
let entry = body["queries"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|q| q["name"] == "bare")
|
||||
.unwrap();
|
||||
assert!(
|
||||
entry.get("description").is_none() && entry.get("instruction").is_none(),
|
||||
"a query without annotations omits both JSON fields: {entry}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn queries_validate_requires_a_cluster() {
|
||||
// RFC-011: with no --cluster (and no cluster profile), the command errors
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue