1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use super::args::{
ExportCliArgs, FixCliArgs, ImportReviewsCliArgs, InitCliArgs, LearnCliArgs,
MemoryPackageFormatArg, RecallCliArgs, ReviewCliArgs, StatusLane, SyncCliArgs,
};
#[derive(Parser)]
#[command(name = "difflore")]
#[command(bin_name = "difflore")]
#[command(about = "Source-backed team rules for local coding agents")]
#[command(next_line_help = true)]
#[command(
long_about = "DiffLore turns your team's past PR review judgment into local memory \
your AI agents can recall before they code. The core loop is: \
`difflore init`, `difflore import-reviews`, `difflore agents install`, then \
`difflore recall --diff` or `difflore review --diff all`. Background memory \
autopilot handles high-confidence local memory automatically; use \
`difflore memory`, `difflore memory review`, and `difflore memory log` to \
inspect and decide. Cloud sync is optional."
)]
pub(crate) struct Cli {
/// Bare `difflore` shows local memory status and the next command.
#[command(subcommand)]
pub(crate) command: Option<Commands>,
/// Disable interactive prompts in commands that support them.
#[arg(long, global = true)]
pub(crate) no_interactive: bool,
}
#[derive(Subcommand)]
pub(crate) enum Commands {
/// See DiffLore work on a bundled sample — no setup, no repo, nothing written.
#[command(
long_about = concat!(
"Run a zero-config demo with bundled review memory and a sample edit.\n",
"It shows which memories fire and the next command to run.\n",
"Nothing leaves your laptop and nothing is written to disk."
)
)]
Try,
/// Run first-time setup for this repo.
Init(InitCliArgs),
/// Show local memory status and the next command.
Status {
/// Output as JSON.
#[arg(long)]
json: bool,
/// Filter readiness output to a release stage.
#[arg(long, value_enum, default_value_t = StatusLane::All, hide = true)]
lane: StatusLane,
},
/// Print the stable AI-facing CLI/MCP capability contract.
Capabilities {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Import past PR/MR review comments (GitHub, GitLab) as source-backed rule evidence.
#[command(
next_line_help = false,
long_about = concat!(
"Import past GitHub PR or GitLab MR review comments into local rules.\n",
"The provider is auto-detected from the git remote.\n",
"For self-managed GitLab, pass `--provider gitlab --gitlab-host <HOST>`\n",
"or store a PAT with `difflore auth gitlab --host <HOST>`.\n",
"Use `--dry-run` to preview; then run `difflore recall --diff`."
)
)]
ImportReviews(ImportReviewsCliArgs),
/// Review and approve the rules DiffLore has learned.
Memory {
/// Output the compact memory summary as JSON.
#[arg(long)]
json: bool,
#[command(subcommand)]
command: Option<MemoryCommands>,
},
/// Force DiffLore to learn from the latest session now.
#[command(
long_about = concat!(
"Run the session-mining gate immediately over the latest Claude Code transcript, ",
"optionally with a note. Learned items are queued as draft candidates and still ",
"require normal review/approval."
)
)]
Learn(LearnCliArgs),
/// Preview which team rules an agent would see for an intent or current diff.
Recall(RecallCliArgs),
/// Review the current diff using source-backed team review judgment.
#[command(
next_line_help = false,
long_about = concat!(
"Analyze staged, working-tree, or PR changes against team review memory.\n",
"Review never modifies files.\n",
"Use `difflore review --ci` for a machine gate that exits non-zero on actionable findings.\n",
"Use `difflore fix` when you want to apply suggested patches."
)
)]
Review(ReviewCliArgs),
/// Apply local patches using source-backed team review judgment.
#[command(
next_line_help = false,
long_about = concat!(
"Apply safe local patches for the current diff or a GitHub PR.\n",
"Start with `difflore review --diff all` when you only want analysis.\n",
"Accepted changes only touch the working tree.\n",
"DiffLore never commits, pushes, opens PRs, or posts GitHub comments."
)
)]
Fix(FixCliArgs),
/// Export this repo's team rules into static agent context files.
#[command(
next_line_help = false,
long_about = concat!(
"Write recalled rules into AGENTS.md, CLAUDE.md, or both.\n\n",
"Export is a static snapshot: it goes stale and cannot match the file being edited.\n",
"Prefer `difflore agents install` for live, diff-aware injection.\n\n",
"Side effects: writes only selected files and only inside DiffLore markers.\n",
"Only the BEGIN/END DIFFLORE RULES block is managed.\n",
"DiffLore never commits, pushes, or edits .gitignore."
)
)]
Export(ExportCliArgs),
/// Ask the team's source-backed rules a natural-language question.
Ask {
/// The question to ask.
query: String,
/// File path for scoping (optional; drives file-pattern recall).
#[arg(long, value_name = "PATH")]
file: Option<String>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Compatibility alias for local memory draft review.
#[command(hide = true)]
Drafts {
#[command(subcommand)]
command: DraftsCommands,
},
/// Optional: sync team state and selected cloud queues.
Cloud {
#[command(subcommand)]
command: CloudCommands,
},
/// Store GitLab credentials used for review import.
Auth {
#[command(subcommand)]
command: AuthCommands,
},
/// Connect DiffLore to your local coding agents.
Agents {
#[command(subcommand)]
command: AgentsCommands,
},
/// Refresh installed agent blocks and run diagnostics.
#[command(
long_about = concat!(
"Refresh DiffLore installs safely.\n",
"Shows binary update guidance when available.\n",
"Re-renders unchanged agent config and hook blocks, then runs `doctor`."
)
)]
Update {
/// Preview agent block changes without touching disk; skips doctor.
#[arg(long)]
dry_run: bool,
/// Overwrite agent blocks that were locally edited since DiffLore wrote them.
#[arg(long)]
force: bool,
},
/// Choose the local AI backend DiffLore uses for fixes.
Providers {
#[command(subcommand)]
command: ProviderCommands,
},
/// Configure optional semantic search for higher-quality recall.
Embeddings {
#[command(subcommand)]
command: EmbeddingsCommands,
},
/// Run an internal retrieval sanity check.
#[command(
hide = true,
long_about = "Run an internal retrieval sanity check. Deterministic and offline; \
nothing is written to your real indexes. Not a published benchmark or competitive comparison."
)]
Eval {
/// Number of rules to sample (1..=200). Ignored with `--golden`.
#[arg(long, value_name = "N")]
samples: Option<usize>,
/// Score the committed golden-case fixture (paraphrase recall,
/// precision, forbidden-exclusion, abstention) instead of self-recall.
/// Offline and deterministic; needs no local corpus.
#[arg(long)]
golden: bool,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Replay a recorded review's decision trail — every issue traced to its memory evidence.
#[command(
hide = true,
long_about = "Replay one recorded review decision trail from DiffLore Cloud. \
Pass `--json` for the raw document."
)]
Trajectory {
/// The review id (UUID) to replay. Shown after a review runs.
#[arg(value_name = "REVIEW_ID")]
review_id: String,
/// Output the raw trajectory document as JSON.
#[arg(long)]
json: bool,
},
/// Show readiness and blockers; pass `--report` for a full diagnostic file.
Doctor {
/// Write a redacted support report. With no value, writes under
/// `~/.difflore/reports/`; pass `-` for stdout or a path for a file.
#[arg(
long,
value_name = "PATH",
num_args = 0..=1,
default_missing_value = ""
)]
report: Option<String>,
/// Auto-repair the safe subset of detected problems.
#[arg(long)]
fix: bool,
/// Preview retrying uploads that stopped after a login/auth outage.
#[arg(long, hide = true)]
drain_abandoned: bool,
/// Only include uploads whose last attempt is older than this duration.
/// Accepts `30d`, `7d`, `24h`, `1h`, `30m`.
#[arg(long, value_name = "DURATION", default_value = "30d", hide = true)]
older_than: String,
/// Apply the retry queue changes instead of previewing them.
#[arg(long, default_value_t = false, hide = true)]
no_dry_run: bool,
/// JSON output for the retry summary. Has no effect outside
/// `--drain-abandoned`.
#[arg(long, hide = true)]
json: bool,
},
/// Internal MCP stdio transport used by installed agents.
#[command(name = "mcp-server", hide = true)]
McpServer,
/// Internal warm hook-forward daemon for one project (spawned by the shim).
#[command(name = "__hook-daemon", hide = true)]
HookDaemon {
/// Stable per-project hash selecting the index pool this daemon serves.
#[arg(long, value_name = "HASH")]
project_hash: String,
},
/// Internal cloud-outbox drain daemon (spawned best-effort by hooks).
#[command(name = "__outbox-daemon", hide = true)]
OutboxDaemon {
/// Seconds between background drain passes.
#[arg(long, default_value_t = 5, hide = true)]
tick_interval_secs: u64,
/// Maximum cloud_outbox rows claimed per pass.
#[arg(long, default_value_t = 64, hide = true)]
batch_size: usize,
},
/// Local skill-store maintenance utilities.
#[command(hide = true)]
Skills {
#[command(subcommand)]
command: SkillsCommands,
},
/// Verify plugin distribution manifests (maintainer-only release guardrail).
#[command(hide = true)]
Dist {
#[command(subcommand)]
command: DistCommands,
},
}
#[derive(Subcommand)]
pub(crate) enum SkillsCommands {
/// Preview cleanup for stale local memory records.
Sweep {
/// Apply the cleanup. Without this flag, prints a preview only.
#[arg(long, default_value_t = false)]
no_dry_run: bool,
/// Confidence multiplier applied during cleanup.
#[arg(long, default_value_t = 0.5)]
decay_factor: f32,
/// Ignore records newer than this many days.
#[arg(long, default_value_t = 14)]
days: u32,
/// Also quarantine unguided review records.
#[arg(long, default_value_t = false)]
quarantine_unguided: bool,
},
/// Preview repair for old accepted-fix attribution records.
#[command(name = "backfill-attribution")]
BackfillAttribution {
/// Apply the repair. Without this flag, prints a preview only.
#[arg(long, default_value_t = false)]
no_dry_run: bool,
},
}
#[derive(Subcommand)]
pub(crate) enum DistCommands {
/// Check plugin/marketplace manifests against the CLI version and bundle.
Verify {
/// Output as JSON.
#[arg(long)]
json: bool,
},
}
#[derive(Subcommand)]
pub(crate) enum DraftsCommands {
/// List pending memory drafts.
List {
/// Filter drafts to a GitHub OWNER/REPO.
#[arg(long, value_name = "OWNER/REPO")]
repo: Option<String>,
/// Maximum drafts to show.
#[arg(long, value_name = "N")]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show one draft with full rule text and source evidence.
Show {
/// Pending draft id.
id: String,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Review pending drafts interactively.
Review {
/// Filter drafts to a GitHub OWNER/REPO.
#[arg(long, value_name = "OWNER/REPO")]
repo: Option<String>,
/// Maximum drafts to review.
#[arg(long, value_name = "N")]
limit: Option<usize>,
},
/// Approve a draft and activate it as local memory.
Approve {
/// Pending draft id. Omit when using --all.
id: Option<String>,
/// Approve every matching draft.
#[arg(long)]
all: bool,
/// Filter --all to a GitHub OWNER/REPO.
#[arg(long, value_name = "OWNER/REPO")]
repo: Option<String>,
/// Skip the confirmation prompt for --all.
#[arg(long)]
yes: bool,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Reject a draft and remove it from the local queue.
Reject {
/// Pending draft id. Omit when using --all.
id: Option<String>,
/// Reject every matching draft.
#[arg(long)]
all: bool,
/// Filter --all to a GitHub OWNER/REPO.
#[arg(long, value_name = "OWNER/REPO")]
repo: Option<String>,
/// Skip the confirmation prompt for --all.
#[arg(long)]
yes: bool,
/// Output as JSON.
#[arg(long)]
json: bool,
},
}
#[derive(Subcommand)]
pub(crate) enum MemoryCommands {
/// Show everything waiting for review, plus local rules already active.
Inbox {
/// Show all available rows instead of the default short preview.
#[arg(long)]
all: bool,
/// Maximum rows to show per section.
#[arg(long)]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show active memory rules currently available to agents.
Active {
/// Show active rules from every repo. By default only the current repo is shown.
#[arg(long)]
all: bool,
/// Maximum rules to show.
#[arg(long)]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show local evidence that agents retrieved or were shown rules.
Activity {
/// Look back this many days.
#[arg(long, default_value_t = 30)]
days: i64,
/// Maximum recent events to show.
#[arg(long)]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show one memory item with its rule text and source evidence.
Show {
/// Item id, such as rule:<skill-id>, draft:<skill-id>, or session:<content_hash>.
item_id: String,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Save and immediately enable a user-requested coding rule.
#[command(
long_about = concat!(
"Save and immediately enable a user-requested coding rule.\n",
"Agents should prefer the `remember_rule` MCP tool when available; this\n",
"command is the CLI fallback for user phrases like \"remember this rule\",\n",
"\"from now on\", or \"don't do this again\".\n",
"\n",
"Because the user explicitly asked to remember the rule, this command treats\n",
"that request as approval and activates the rule for local agents."
)
)]
Remember {
/// Short imperative title for the rule.
#[arg(long, value_name = "TEXT")]
title: String,
/// Full rule body and context. If omitted, non-interactive stdin is read.
#[arg(long, value_name = "TEXT")]
body: Option<String>,
/// File glob the rule applies to. Repeat for multiple globs.
#[arg(long = "file-pattern", value_name = "GLOB")]
file_patterns: Vec<String>,
/// Optional snippet showing the pattern to avoid.
#[arg(long, value_name = "TEXT")]
bad_code: Option<String>,
/// Optional snippet showing the preferred pattern.
#[arg(long, value_name = "TEXT")]
good_code: Option<String>,
/// Optional severity hint: low, medium, or high.
#[arg(long, value_name = "LEVEL")]
severity: Option<String>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Import project agent memory files into local DiffLore memory.
ImportAgentFiles {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Walk through everything pending approval, one item at a time.
Review {
/// Maximum pending items to review.
#[arg(long)]
limit: Option<usize>,
},
/// Let DiffLore locally enable high-confidence memories and leave noisy ones for review.
Autopilot {
/// Preview what would be enabled without changing local memory.
#[arg(long)]
dry_run: bool,
/// Maximum groups to enable in one run.
#[arg(long, value_name = "N")]
max_auto_enable: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
/// Internal detached worker entry point.
#[arg(long, hide = true)]
background: bool,
/// Internal lease owner token for the detached worker.
#[arg(long, hide = true)]
lease_owner: Option<String>,
},
/// Clean up duplicate or already-active pending memory candidates.
#[command(
long_about = concat!(
"Clean up local pending memory candidates that are safe to remove.\n",
"By default this previews only. Pass --apply to reject reviewable session\n",
"candidates that already match an active rule, plus duplicate rows inside\n",
"a candidate group. Approved optional-sync rows are left untouched."
)
)]
Cleanup {
/// Apply the cleanup. Without this flag, only print a preview.
#[arg(long)]
apply: bool,
/// Maximum candidate groups to scan.
#[arg(long, value_name = "N")]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Summarize active memory and pending candidate groups.
Digest {
/// Maximum candidate groups to show.
#[arg(long)]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show memory groups DiffLore recommends for approval.
Recommended {
/// Show all recommended groups instead of the default short preview.
#[arg(long)]
all: bool,
/// Maximum recommended groups to show.
#[arg(long)]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show recent local autopilot and disable events.
Log {
/// Maximum events to show.
#[arg(long)]
limit: Option<usize>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show persisted candidate-vs-active rule conflicts for review.
Conflicts {
/// Maximum conflict records to show.
#[arg(long)]
limit: Option<usize>,
/// Only show conflicts in this status (e.g. detected).
#[arg(long)]
status: Option<String>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Stop serving an active local rule to agents.
Disable {
/// Rule id, such as rule:<skill-id> or <skill-id>.
rule_id: String,
/// Reason to record in the local audit log.
#[arg(long)]
reason: Option<String>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Approve one pending item into active local memory.
Approve {
/// Item id, such as session:<content_hash> or draft:<skill-id>.
item_id: String,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Reject one pending item and keep it out of active memory.
Reject {
/// Item id, such as session:<content_hash> or draft:<skill-id>.
item_id: String,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Pull published team rules; raw local queues require opt-in flags.
Sync(SyncCliArgs),
/// Export active local/team memory rules as an editable package.
#[command(
long_about = concat!(
"Export active memory rules to a versioned package for review or hand editing.\n",
"With --format json this writes one JSON file. With --format markdown this writes\n",
"manifest.json plus one editable Markdown file per rule. The target must be\n",
"missing, an empty directory, or an empty file; DiffLore refuses to overwrite\n",
"non-empty package targets."
)
)]
ExportPackage {
/// Output path. `.json` infers JSON; any other path infers a Markdown directory.
#[arg(long, value_name = "PATH")]
output: PathBuf,
/// Package format.
#[arg(long, value_enum, default_value_t = MemoryPackageFormatArg::Auto)]
format: MemoryPackageFormatArg,
/// Preview the package plan without writing files.
#[arg(long)]
dry_run: bool,
/// Output as JSON.
#[arg(long)]
json: bool,
/// Export local rules only; exclude team/cloud-synced rules.
#[arg(long)]
local_only: bool,
/// Cap export to the first N rules. Unlimited when omitted.
#[arg(long, value_name = "N", value_parser = clap::value_parser!(u64).range(1..))]
max_rules: Option<u64>,
},
/// Import an editable memory package and update matching existing rules.
#[command(
long_about = concat!(
"Import a versioned memory package from a JSON file or Markdown directory.\n",
"The minimal safe loop updates existing rules by id. Missing ids are reported\n",
"and never created implicitly. Use --dry-run to validate and preview changes."
)
)]
ImportPackage {
/// Package file or directory.
#[arg(long, value_name = "PATH")]
source: PathBuf,
/// Validate and preview without updating local memory.
#[arg(long)]
dry_run: bool,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Review drafts mined from imported reviews (a subset of the inbox).
#[command(hide = true)]
Drafts {
#[command(subcommand)]
command: DraftsCommands,
},
}
#[derive(Subcommand)]
pub(crate) enum AuthCommands {
/// Store or verify a GitLab personal access token (needs `read_api` scope).
#[command(next_line_help = false, long_about = concat!(
"Store a GitLab personal access token for review import. The token is\n",
"encrypted at rest with the same mechanism as the cloud login token.\n",
"\n",
"Pipe the token via stdin so it never lands in shell history:\n",
"\n",
" echo \"<TOKEN>\" | difflore auth gitlab\n",
" echo \"<TOKEN>\" | difflore auth gitlab --host gitlab.corp.example\n",
"\n",
"At import time the token is resolved in this order:\n",
"DIFFLORE_GITLAB_TOKEN env, GITLAB_TOKEN env, then stored token.\n",
"\n",
"Use `--check` to verify the resolved token against the host's\n",
"/api/v4/user endpoint, and `--remove` to delete the stored token.",
))]
Gitlab {
/// GitLab host (self-managed instances supported, e.g. gitlab.corp.example).
#[arg(long, value_name = "HOST", default_value = difflore_core::ingest::gitlab::auth::DEFAULT_GITLAB_HOST)]
host: String,
/// Verify the resolved token against GET https://<HOST>/api/v4/user instead of storing.
#[arg(long, conflicts_with = "remove")]
check: bool,
/// Remove the stored token for this host.
#[arg(long)]
remove: bool,
},
}
#[derive(Subcommand)]
pub(crate) enum CloudCommands {
/// Show current cloud login, plan, and team info.
Status {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Sign in for team sync, dashboards, and managed semantic recall.
#[command(next_line_help = false, long_about = concat!(
"Log in to DiffLore Cloud.\n",
"Use this for team sync, dashboards, and managed semantic recall.\n",
"\n",
"Browser login is the default for interactive use:\n",
"\n",
" difflore cloud login\n",
" difflore cloud login --browser\n",
" difflore cloud login --github\n",
"\n",
"For headless environments, pass a token with `--token`, stdin, or ",
"`DIFFLORE_CLOUD_TOKEN`.",
))]
Login {
/// Bearer token. Prefer stdin or `DIFFLORE_CLOUD_TOKEN` to keep it out of shell history.
#[arg(long)]
token: Option<String>,
/// Force the browser OAuth flow even from local non-TTY shells; prints the auth URL.
#[arg(long, conflicts_with_all = ["token", "github"])]
browser: bool,
/// Exchange the local GitHub CLI auth token for a DiffLore Cloud token.
#[arg(long, conflicts_with = "token")]
github: bool,
},
/// Pull published team rules; raw observation/session queues upload only with opt-in flags.
Sync(SyncCliArgs),
/// Show your team workspace and what it still needs.
Team {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Publish a local rule to the current cloud team.
Publish {
/// Local or cloud rule id to publish.
#[arg(long, value_name = "RULE_ID")]
rule: String,
/// Team id. Defaults to the current cloud team.
#[arg(long, value_name = "TEAM_ID")]
team_id: Option<String>,
/// Team enforcement policy.
#[arg(long, default_value = "recommended", value_parser = ["recommended", "required"])]
enforcement: String,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Remove a published rule from the current cloud team.
Unpublish {
/// Local or cloud rule id to unpublish.
#[arg(long, value_name = "RULE_ID")]
rule: String,
/// Team id. Defaults to the current cloud team.
#[arg(long, value_name = "TEAM_ID")]
team_id: Option<String>,
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Show how your team's rules changed real reviews.
Impact {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Clear the stored cloud token on this device.
Logout,
}
#[derive(Subcommand)]
pub(crate) enum AgentsCommands {
/// Install DiffLore into every detected local agent (MCP + hooks).
Install {
/// Preview what would change without touching disk.
#[arg(long)]
dry_run: bool,
},
/// Remove DiffLore from every agent it was installed into.
#[command(
long_about = "Undo `difflore agents install`: remove the DiffLore MCP entry and \
DiffLore hook groups from each agent it was wired into (preserving every other entry), \
then delete the canonical install record. Pass `--dry-run` to preview first."
)]
Uninstall {
/// Preview what would be removed without touching disk.
#[arg(long)]
dry_run: bool,
},
/// Show which agents are connected.
Status {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Re-render DiffLore blocks that are unchanged since DiffLore wrote them.
#[command(
long_about = "Safely upgrade DiffLore's config/hook blocks to the current shape. \
Blocks that are byte-identical to what DiffLore last wrote are re-rendered in place; \
blocks you (or another tool) hand-edited are left untouched unless you pass `--force`. \
Pass `--dry-run` to preview the plan first."
)]
Update {
/// Preview what would change without touching disk.
#[arg(long)]
dry_run: bool,
/// Overwrite blocks that were locally edited since DiffLore wrote them.
#[arg(long)]
force: bool,
},
}
#[derive(Subcommand)]
pub(crate) enum ProviderCommands {
/// List configured AI backends.
List {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Interactive AI backend picker (Claude, Codex, Gemini, or OpenCode CLI).
Setup,
/// Add a local AI CLI backend.
Add {
/// Agent CLI to use: `claude`, `codex`, `gemini`, or `opencode`.
#[arg(long, value_name = "TOOL")]
tool: String,
/// Optional model override. Defaults to the agent CLI's own default.
#[arg(long)]
model: Option<String>,
},
/// Remove an AI backend by ID.
Remove {
/// Provider ID to remove.
id: String,
/// Skip the confirmation prompt.
#[arg(long)]
yes: bool,
},
/// Set active AI backend.
SetActive {
/// Provider ID.
id: String,
},
}
#[derive(Subcommand)]
pub(crate) enum EmbeddingsCommands {
/// Show the active search mode.
Status {
/// Output as JSON.
#[arg(long)]
json: bool,
},
/// Configure a BYOK semantic embedding provider (OpenAI-compatible).
Setup {
/// Base URL of the OpenAI-compatible embedding endpoint.
#[arg(long, value_name = "URL")]
provider_url: Option<String>,
/// Embedding model name.
#[arg(long, value_name = "MODEL")]
model: Option<String>,
/// Output vector dimensionality.
#[arg(long, value_name = "N")]
dim: Option<usize>,
/// API key. Prefer the `DIFFLORE_EMBEDDING_KEY` env var or piped stdin.
#[arg(long, value_name = "KEY")]
key: Option<String>,
/// Configure a keyless local provider (no API key required).
#[arg(long, conflicts_with = "key")]
no_key: bool,
},
/// Turn off semantic search and use fast local keyword matching.
Disable,
/// Rebuild the local semantic search index for this repo.
Rebuild {
/// Output as JSON.
#[arg(long)]
json: bool,
},
}