code_query.py python
162 lines 5.7 KB
Raw
sha256:cf6265cea8c21d9228d90dec13ef6ec2dab5103d466db9cc4590681832de4bf8 docs(KD-STAGING): sync governance after KD-6b DONE Human 12 days ago
1 """``muse code code-query`` — predicate query over code commit history.
2
3 Search the commit graph for code changes matching a structured predicate::
4
5 muse code code-query "symbol == 'my_function' and change == 'added'"
6 muse code code-query "language == 'Python' and author == 'agent-x'"
7 muse code code-query "agent_id == 'claude' and sem_ver_bump == 'major'"
8 muse code code-query "file == 'src/core.py'"
9 muse code code-query "change == 'removed' and kind == 'class'"
10 muse code code-query "model_id contains 'claude'"
11 muse code code-query "symbol endswith _handler"
12 muse code code-query "author == 'gabriel'" --since 2026-01-01
13 muse code code-query "sem_ver_bump == 'major'" --count
14
15 Fields
16 ------
17
18 ``symbol`` Qualified symbol name (e.g. ``"MyClass.method"``).
19 ``file`` Workspace-relative file path.
20 ``language`` Language name (``"Python"``, ``"TypeScript"``…).
21 ``kind`` Symbol kind (``"function"``, ``"class"``, ``"method"``…).
22 ``change`` ``"added"``, ``"removed"``, or ``"modified"``.
23 ``author`` Commit author string.
24 ``agent_id`` Agent identity from commit provenance.
25 ``model_id`` Model ID from commit provenance.
26 ``toolchain_id`` Toolchain string from commit provenance.
27 ``sem_ver_bump`` ``"none"``, ``"patch"``, ``"minor"``, or ``"major"``.
28 ``branch`` Branch name.
29
30 Operators: ``==``, ``!=``, ``contains``, ``startswith``, ``endswith``
31
32 Usage::
33
34 muse code code-query QUERY
35 muse code code-query QUERY --branch dev --max 100
36 muse code code-query QUERY --since 2026-01-01 --until 2026-06-30
37 muse code code-query QUERY --limit 10
38 muse code code-query QUERY --count
39 muse code code-query QUERY --json
40 """
41
42 from __future__ import annotations
43
44 import argparse
45 import datetime
46 import json
47 import logging
48 import pathlib
49 import sys
50
51 from muse.core.query_engine import format_matches, walk_history
52 from muse.core.repo import parse_date_arg, require_repo
53 from muse.core.store import read_current_branch
54 from muse.plugins.code._code_query import build_evaluator
55 from muse.core.validation import clamp_int
56
57 logger = logging.getLogger(__name__)
58
59
60 def register(subparsers: "argparse._SubParsersAction[argparse.ArgumentParser]") -> None:
61 """Register the code-query subcommand."""
62 parser = subparsers.add_parser(
63 "code-query",
64 help="Query the code commit history using a structured predicate.",
65 description=__doc__,
66 formatter_class=argparse.RawDescriptionHelpFormatter,
67 )
68 parser.add_argument(
69 "query",
70 help="Query expression (see muse code code-query --help).",
71 )
72 parser.add_argument(
73 "--branch", default=None,
74 help="Branch to search (default: HEAD branch).",
75 )
76 parser.add_argument(
77 "--max", type=int, default=200, dest="max_commits",
78 help="Maximum commits to inspect (walk depth). Default: 200.",
79 )
80 parser.add_argument(
81 "--limit", type=int, default=None, dest="limit",
82 help="Maximum matches to display. Does not affect walk depth (see --max).",
83 )
84 parser.add_argument(
85 "--since", default=None, metavar="DATE",
86 help="Only include commits on or after DATE (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS).",
87 )
88 parser.add_argument(
89 "--until", default=None, metavar="DATE",
90 help="Only include commits on or before DATE (YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS).",
91 )
92 parser.add_argument(
93 "--count", action="store_true",
94 help="Print only the total match count, not the matches themselves.",
95 )
96 parser.add_argument(
97 "--json", action="store_true", dest="as_json",
98 help="Emit JSON array of matches.",
99 )
100 parser.set_defaults(func=run)
101
102
103 def run(args: argparse.Namespace) -> None:
104 """Query the code commit history using a structured predicate.
105
106 Walks up to *max* commits from HEAD on the specified branch and returns
107 all commits (and symbol-level changes) matching the predicate.
108
109 Examples::
110
111 muse code code-query "symbol == 'parse_query' and change == 'added'"
112 muse code code-query "agent_id contains 'claude' and sem_ver_bump == 'major'"
113 muse code code-query "file == 'muse/core/store.py'"
114 muse code code-query "author == 'gabriel'" --since 2026-01-01
115 muse code code-query "sem_ver_bump == 'major'" --count
116 """
117 query: str = args.query
118 branch: str | None = args.branch
119 max_commits: int = clamp_int(args.max_commits, 1, 100_000, 'max_commits')
120 limit: int | None = args.limit
121 as_json: bool = args.as_json
122 count_only: bool = args.count
123
124 since: datetime.datetime | None = (
125 parse_date_arg(args.since, "--since") if args.since else None
126 )
127 until: datetime.datetime | None = (
128 parse_date_arg(args.until, "--until") if args.until else None
129 )
130
131 root: pathlib.Path = require_repo()
132
133 try:
134 evaluator = build_evaluator(query)
135 except ValueError as exc:
136 print(f"❌ Query parse error: {exc}", file=sys.stderr)
137 raise SystemExit(1) from exc
138
139 resolved_branch = branch or read_current_branch(root)
140
141 # The code evaluator reads commit.structured_delta only — it never touches
142 # the snapshot manifest. Skipping manifest I/O cuts one file-read per commit.
143 matches = walk_history(
144 root,
145 resolved_branch,
146 evaluator,
147 max_commits=max_commits,
148 load_manifest=False,
149 since=since,
150 until=until,
151 )
152
153 if count_only:
154 print(len(matches))
155 return
156
157 if as_json:
158 print(json.dumps({"total": len(matches), "results": matches}))
159 return
160
161 display_limit = limit if limit is not None else 50
162 print(format_matches(matches, max_results=display_limit))
File History 1 commit
sha256:cf6265cea8c21d9228d90dec13ef6ec2dab5103d466db9cc4590681832de4bf8 docs(KD-STAGING): sync governance after KD-6b DONE Human 12 days ago