|
148 | 148 | "continue_from" {:type "string" :description "Session ID from a budget-exhausted run — resumes the agent from where it left off"}}) |
149 | 149 | :required ["question" "repo_path"]}} |
150 | 150 | {:name "noumenon_analyze" |
151 | | - :description "Run LLM analysis on repository files to enrich the knowledge graph with semantic metadata. Only analyzes files not yet analyzed. Requires a prior import." |
| 151 | + :description "Run LLM analysis on repository files to enrich the knowledge graph with semantic metadata. By default only analyzes files not yet analyzed. Pass reanalyze to re-analyze files: all, prompt-changed, model-changed, or stale. Requires a prior import." |
152 | 152 | :inputSchema {:type "object" |
153 | 153 | :properties (merge repo-path-prop |
154 | 154 | {"provider" {:type "string" |
|
158 | 158 | "concurrency" {:type "integer" |
159 | 159 | :description "Number of concurrent LLM calls (default: 3, max: 20)"} |
160 | 160 | "max_files" {:type "integer" |
161 | | - :description "Stop after analyzing N files (useful for sampling)"}}) |
| 161 | + :description "Stop after analyzing N files (useful for sampling)"} |
| 162 | + "reanalyze" {:type "string" |
| 163 | + :description "Re-analyze scope: all, prompt-changed, model-changed, stale (default: only unanalyzed files)"}}) |
162 | 164 | :required ["repo_path"]}} |
163 | 165 | {:name "noumenon_enrich" |
164 | 166 | :description "Extract cross-file import graph deterministically. No LLM calls — uses language-specific parsers. Requires a prior import." |
|
358 | 360 | (tool-result (or answer |
359 | 361 | (str "No answer found (status: " (name (:status result)) ")")))))))) |
360 | 362 |
|
| 363 | +(def ^:private valid-reanalyze-scopes |
| 364 | + #{"all" "prompt-changed" "model-changed" "stale"}) |
| 365 | + |
| 366 | +(defn- prepare-reanalysis! |
| 367 | + "Retract analysis attrs for files matching the reanalyze scope. |
| 368 | + Returns count of files marked for re-analysis, or nil if no scope given." |
| 369 | + [conn db reanalyze {:keys [prompt-hash model-id]}] |
| 370 | + (when reanalyze |
| 371 | + (let [scope (keyword reanalyze) |
| 372 | + files (analyze/files-for-reanalysis db scope {:prompt-hash prompt-hash |
| 373 | + :model-id model-id}) |
| 374 | + paths (mapv :file/path files) |
| 375 | + n (if (seq paths) (sync/retract-analysis! conn paths) 0)] |
| 376 | + (log! (str "Marked " n " file(s) for re-analysis (scope: " reanalyze ")")) |
| 377 | + n))) |
| 378 | + |
361 | 379 | (defn- handle-analyze [args defaults] |
362 | 380 | (validate-llm-inputs! args) |
363 | | - (with-conn args defaults |
364 | | - (fn [{:keys [conn repo-path]}] |
365 | | - (let [{:keys [prompt-fn model-id]} |
366 | | - (llm/wrap-as-prompt-fn-from-opts {:provider (or (args "provider") (:provider defaults)) |
367 | | - :model (or (args "model") (:model defaults))}) |
368 | | - concurrency (min (or (args "concurrency") 3) 20) |
369 | | - max-files (args "max_files") |
370 | | - result (analyze/analyze-repo! conn repo-path prompt-fn |
371 | | - (cond-> {:model-id model-id |
372 | | - :concurrency concurrency} |
373 | | - max-files (assoc :max-files max-files)))] |
374 | | - (tool-result (str "Analysis complete. " |
375 | | - (:files-analyzed result 0) " files analyzed" |
376 | | - (when (pos? (:files-parse-errored result 0)) |
377 | | - (str ", " (:files-parse-errored result 0) " parse errors")) |
378 | | - (when (pos? (:files-errored result 0)) |
379 | | - (str ", " (:files-errored result 0) " errors")) |
380 | | - ". " (get-in result [:total-usage :input-tokens] 0) |
381 | | - " in / " (get-in result [:total-usage :output-tokens] 0) " out tokens" |
382 | | - (when-let [c (get-in result [:total-usage :cost-usd])] |
383 | | - (when (pos? c) (str " ($" (format "%.2f" c) ")"))))))))) |
| 381 | + (let [reanalyze (args "reanalyze")] |
| 382 | + (when (and reanalyze (not (valid-reanalyze-scopes reanalyze))) |
| 383 | + (throw (ex-info (str "Invalid reanalyze scope: " reanalyze |
| 384 | + ". Must be one of: all, prompt-changed, model-changed, stale") |
| 385 | + {:scope reanalyze}))) |
| 386 | + (with-conn args defaults |
| 387 | + (fn [{:keys [conn repo-path]}] |
| 388 | + (let [{:keys [prompt-fn model-id]} |
| 389 | + (llm/wrap-as-prompt-fn-from-opts {:provider (or (args "provider") (:provider defaults)) |
| 390 | + :model (or (args "model") (:model defaults))}) |
| 391 | + prompt-hash (analyze/prompt-hash (:template (analyze/load-prompt-template)))] |
| 392 | + (prepare-reanalysis! conn (d/db conn) reanalyze |
| 393 | + {:prompt-hash prompt-hash :model-id model-id}) |
| 394 | + (let [concurrency (min (or (args "concurrency") 3) 20) |
| 395 | + max-files (args "max_files") |
| 396 | + result (analyze/analyze-repo! conn repo-path prompt-fn |
| 397 | + (cond-> {:model-id model-id |
| 398 | + :concurrency concurrency} |
| 399 | + max-files (assoc :max-files max-files)))] |
| 400 | + (tool-result (str "Analysis complete. " |
| 401 | + (:files-analyzed result 0) " files analyzed" |
| 402 | + (when (pos? (:files-parse-errored result 0)) |
| 403 | + (str ", " (:files-parse-errored result 0) " parse errors")) |
| 404 | + (when (pos? (:files-errored result 0)) |
| 405 | + (str ", " (:files-errored result 0) " errors")) |
| 406 | + ". " (get-in result [:total-usage :input-tokens] 0) |
| 407 | + " in / " (get-in result [:total-usage :output-tokens] 0) " out tokens" |
| 408 | + (when-let [c (get-in result [:total-usage :cost-usd])] |
| 409 | + (when (pos? c) (str " ($" (format "%.2f" c) ")"))))))))))) |
384 | 410 |
|
385 | 411 | (defn- handle-enrich [args defaults] |
386 | 412 | (with-conn args defaults |
|
0 commit comments