Compare commits
296 Commits
f4dba386c9
...
robot-meta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60075cd400 | ||
|
|
ddab186315 | ||
|
|
d6d1686f8e | ||
|
|
5c44ee91fb | ||
|
|
6aff96d32f | ||
|
|
06889ec85a | ||
|
|
08bda08934 | ||
|
|
32134ea933 | ||
|
|
16cc58b17f | ||
|
|
a10d870863 | ||
|
|
59088af2ab | ||
|
|
ace9c8bf17 | ||
|
|
cab8c540da | ||
|
|
d94bcbfbe7 | ||
|
|
62fbd7275e | ||
|
|
06852e90a6 | ||
|
|
4b0535f852 | ||
|
|
8bd68e02bd | ||
|
|
6aaf931c9b | ||
|
|
af167e2086 | ||
|
|
e8d6c5b15f | ||
|
|
bf977eca1a | ||
|
|
4d41d74ea7 | ||
|
|
3a4fc96558 | ||
|
|
ac5602e565 | ||
|
|
d3f8020cf8 | ||
|
|
9107a78b57 | ||
|
|
5fb27b1fbb | ||
|
|
2ab57d8d14 | ||
|
|
77445f6903 | ||
|
|
87249ef3d9 | ||
|
|
f6909d822e | ||
|
|
1dfcfd3f83 | ||
|
|
ffbd1e2dce | ||
|
|
571c304031 | ||
|
|
e4ac7020b3 | ||
|
|
c7a7898675 | ||
|
|
5fd1ce6905 | ||
|
|
b67bb8754c | ||
|
|
3f38b3fda7 | ||
|
|
439c20e713 | ||
|
|
fd0a40b181 | ||
|
|
b2811b5e45 | ||
|
|
2d2e470621 | ||
|
|
23efb15599 | ||
|
|
a45c37c7e4 | ||
|
|
8657e10822 | ||
|
|
7fdeafa330 | ||
|
|
0fe3737035 | ||
|
|
87bdbda468 | ||
|
|
ed987c8f71 | ||
|
|
ce5621f3ed | ||
|
|
eac640225f | ||
|
|
c5843bd823 | ||
|
|
f9e7913232 | ||
|
|
6e487532aa | ||
|
|
7e9a23cc0f | ||
|
|
71d07c28d8 | ||
|
|
f4de6feaa2 | ||
|
|
ec0aaaf77c | ||
|
|
9c1a9bfe5d | ||
|
|
a5c2589c7d | ||
|
|
8fdb366b6d | ||
|
|
53b093586b | ||
|
|
9ec1344945 | ||
|
|
ea6e45e43f | ||
|
|
30ed02c694 | ||
|
|
a4df8e5444 | ||
|
|
53ce20595b | ||
|
|
1808a4da8e | ||
|
|
7d032833a2 | ||
|
|
097249f4e6 | ||
|
|
8442bcf367 | ||
|
|
c0ca501662 | ||
|
|
c953d8e519 | ||
|
|
63bd58c9b4 | ||
|
|
714c8c2623 | ||
|
|
171260a772 | ||
|
|
a1bca10408 | ||
|
|
491dc52864 | ||
|
|
b9063aa17a | ||
|
|
fc0d9cb1d3 | ||
|
|
c8b47bf8f8 | ||
|
|
a570327a6b | ||
|
|
eef73decb5 | ||
|
|
bb6660178c | ||
|
|
64e73b1cab | ||
|
|
361757568f | ||
|
|
8572f6cc04 | ||
|
|
d0744039ef | ||
|
|
4b372dfb38 | ||
|
|
af8fc4af76 | ||
|
|
96b288ccdd | ||
|
|
d710403567 | ||
|
|
ebf64816c9 | ||
|
|
450951dee1 | ||
|
|
81f049a7fa | ||
|
|
dd00a2b840 | ||
|
|
c6a5461d41 | ||
|
|
a7f86b26e4 | ||
|
|
5ee8b0841c | ||
|
|
7062a3f1fd | ||
|
|
159c490ad7 | ||
|
|
e0041ed4d9 | ||
|
|
a34751bd47 | ||
|
|
0aecbf33c0 | ||
|
|
c10471ddb9 | ||
|
|
cbce4c9f59 | ||
|
|
94435c37f0 | ||
|
|
59f65b127a | ||
|
|
f36e900570 | ||
|
|
e2efc61beb | ||
|
|
2da1a228b3 | ||
|
|
0e65202778 | ||
|
|
f439c42b3d | ||
|
|
4f3ec72923 | ||
|
|
e6771709f1 | ||
|
|
8c86b0dfd7 | ||
|
|
6e55b2470d | ||
|
|
b05922d60b | ||
|
|
11fe02fac9 | ||
|
|
48fbd4bfdb | ||
|
|
9786ef27f5 | ||
|
|
7e0e6a91f2 | ||
|
|
5c2df3df3b | ||
|
|
94c8613420 | ||
|
|
ad4dd6e855 | ||
|
|
83cd16c918 | ||
|
|
fda9cd8835 | ||
|
|
c8d609ab78 | ||
|
|
35c828ba73 | ||
|
|
ecbfef537a | ||
|
|
47eecce8e9 | ||
|
|
b29c382583 | ||
|
|
e26816333f | ||
|
|
f772de8aef | ||
|
|
dd4d867c6e | ||
|
|
ffd074499a | ||
|
|
125938fba6 | ||
|
|
cd25cf61ca | ||
|
|
d9c9f6e541 | ||
|
|
acc5e12e3d | ||
|
|
039ab1c2a3 | ||
|
|
d63d6f0b9c | ||
|
|
3a1307dcdc | ||
|
|
6ea3108a20 | ||
|
|
81647545e7 | ||
|
|
39a832688d | ||
|
|
06229ce98b | ||
|
|
8d18552298 | ||
|
|
f3788eb687 | ||
|
|
e9af529f6e | ||
|
|
70271c14d6 | ||
|
|
d9f99ef21d | ||
|
|
f5967a8e52 | ||
|
|
2c9de1a6c3 | ||
|
|
1161edb212 | ||
|
|
5ea976583e | ||
|
|
dcfd449b72 | ||
|
|
6b75697638 | ||
|
|
dc49f5209e | ||
|
|
7d40a81512 | ||
|
|
4185abe05d | ||
|
|
d54f669c5e | ||
|
|
45126f04a6 | ||
|
|
dfa44e5bcd | ||
|
|
53ef21d653 | ||
|
|
41504b4941 | ||
|
|
d36850f181 | ||
|
|
5ce18e0ebc | ||
|
|
b168a58134 | ||
|
|
b704e33188 | ||
|
|
6e82f723c3 | ||
|
|
940a96375a | ||
|
|
7dd86d5433 | ||
|
|
429c6f07d2 | ||
|
|
754efa4369 | ||
|
|
c54a969269 | ||
|
|
95b7183add | ||
|
|
435a208c93 | ||
|
|
cc11d3e5a0 | ||
|
|
5786d7f4b6 | ||
|
|
d3306114eb | ||
|
|
e6b880cbcb | ||
|
|
121a634653 | ||
|
|
f267578aab | ||
|
|
859923f86b | ||
|
|
d701b1f977 | ||
|
|
736d9c9a80 | ||
|
|
8dc479e515 | ||
|
|
3e7fa607d3 | ||
|
|
b5f78e31a8 | ||
|
|
cf6d27435a | ||
|
|
4ce0130620 | ||
|
|
a573d695d5 | ||
|
|
a855759bf8 | ||
|
|
f3f3560e0d | ||
|
|
2bfa4f1f8c | ||
|
|
8cf14fb69b | ||
|
|
c2036c64e9 | ||
|
|
39cb0cb087 | ||
|
|
1c45725cba | ||
|
|
405e5370dc | ||
|
|
32783080f1 | ||
|
|
f1cb45a168 | ||
|
|
69df8a5603 | ||
|
|
b005edb7f2 | ||
|
|
03d9f8cce5 | ||
|
|
7eadae75f0 | ||
|
|
9b23d91378 | ||
|
|
a324fa26e1 | ||
|
|
e8845380e9 | ||
|
|
3e9cf2358e | ||
|
|
16beb35a69 | ||
|
|
3767c33c28 | ||
|
|
d1b2b5fa7d | ||
|
|
a7d5d1c99f | ||
|
|
233eb546af | ||
|
|
ddcfff1026 | ||
|
|
001e4f37b4 | ||
|
|
873d2c0ab8 | ||
|
|
42b8238329 | ||
|
|
5d1586b88e | ||
|
|
c2f34d3a4f | ||
|
|
3bb24dc6cb | ||
|
|
42a4bca6df | ||
|
|
c730b0ec54 | ||
|
|
ab43bbd2db | ||
|
|
784fe79b80 | ||
|
|
db750e4fc5 | ||
|
|
72f1cafdcf | ||
|
|
9c04b7fb1b | ||
|
|
dd2869fd98 | ||
|
|
65583ed5d6 | ||
|
|
976ad92ef0 | ||
|
|
a76dc8089e | ||
|
|
26cf13248d | ||
|
|
a2e26454dc | ||
|
|
f748570d4d | ||
|
|
0b6b168043 | ||
|
|
1d003aeac2 | ||
|
|
925ec9f574 | ||
|
|
1fdc6d03cc | ||
|
|
266ed78e73 | ||
|
|
a65ea2f56f | ||
|
|
38da7ca47b | ||
|
|
86a51cddef | ||
|
|
f6d19a9467 | ||
|
|
362503d3bf | ||
|
|
329c8f4539 | ||
|
|
ee5c5f9645 | ||
|
|
f5b4a765b7 | ||
|
|
4ee99c1677 | ||
|
|
c35f485e0e | ||
|
|
a92e176bb6 | ||
|
|
deafa88af5 | ||
|
|
880ad1d3fa | ||
|
|
4c0123426a | ||
|
|
bb75a9d228 | ||
|
|
2bcd8db0e9 | ||
|
|
a50fc78823 | ||
|
|
ff94f24702 | ||
|
|
5c521491b7 | ||
|
|
0236ef2776 | ||
|
|
12811683ca | ||
|
|
724be4d265 | ||
|
|
c34ed3007e | ||
|
|
e73d2907dc | ||
|
|
9d4755521f | ||
|
|
92ff255909 | ||
|
|
ce5cd9c95d | ||
|
|
549a0646d7 | ||
|
|
a417640faa | ||
|
|
f560e6bc00 | ||
|
|
aebbe6b795 | ||
|
|
7d07f95d4c | ||
|
|
2a52594a60 | ||
|
|
51c370fac2 | ||
|
|
7b7d781a19 | ||
|
|
03ea51513d | ||
|
|
667f70e177 | ||
|
|
585b746461 | ||
|
|
730ddef339 | ||
|
|
5508d8464a | ||
|
|
41d20f1374 | ||
|
|
9b63671df9 | ||
|
|
d235f2b4dd | ||
|
|
daf5a73019 | ||
|
|
559f0702ad | ||
|
|
d5bdb24b0f | ||
|
|
723703bed9 | ||
|
|
20edff4ab1 | ||
|
|
d31d5292f2 | ||
|
|
6e22f120d0 | ||
|
|
4270603da4 | ||
|
|
aca4773327 |
232
.beads/.br_history/issues.20260212_161438.jsonl
Normal file
232
.beads/.br_history/issues.20260212_161438.jsonl
Normal file
File diff suppressed because one or more lines are too long
295
.beads/.br_history/issues.20260212_171003.jsonl
Normal file
295
.beads/.br_history/issues.20260212_171003.jsonl
Normal file
File diff suppressed because one or more lines are too long
304
.beads/.br_history/issues.20260212_171103.jsonl
Normal file
304
.beads/.br_history/issues.20260212_171103.jsonl
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
bd-lcb
|
bd-9lbr
|
||||||
|
|||||||
59
.claude/agents/test-runner.md
Normal file
59
.claude/agents/test-runner.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
name: test-runner
|
||||||
|
description: "Use this agent when unit tests need to be run and results analyzed. This includes after writing or modifying code, before committing changes, or when explicitly asked to verify test status.\\n\\nExamples:\\n\\n- User: \"Please refactor the parse_session function to handle edge cases\"\\n Assistant: \"Here is the refactored function with edge case handling: ...\"\\n [code changes applied]\\n Since a significant piece of code was modified, use the Task tool to launch the test-runner agent to verify nothing is broken.\\n Assistant: \"Now let me run the test suite to make sure everything still passes.\"\\n\\n- User: \"Do all tests pass?\"\\n Assistant: \"Let me use the Task tool to launch the test-runner agent to check the current test status.\"\\n\\n- User: \"I just finished implementing the search feature\"\\n Assistant: \"Let me use the Task tool to launch the test-runner agent to validate the implementation.\"\\n\\n- After any logical chunk of code is written or modified, proactively use the Task tool to launch the test-runner agent to run the tests before reporting completion to the user."
|
||||||
|
tools: Bash
|
||||||
|
model: haiku
|
||||||
|
color: orange
|
||||||
|
---
|
||||||
|
|
||||||
|
You are an expert test execution and analysis engineer. Your sole responsibility is to run the project's unit test suite, interpret the results with precision, and deliver a clear, actionable summary.
|
||||||
|
|
||||||
|
## Execution Protocol
|
||||||
|
|
||||||
|
1. **Discover the test framework**: Examine the project structure to determine how tests are run:
|
||||||
|
- Look for `Cargo.toml` (Rust: `cargo test`)
|
||||||
|
- If unclear, check README or CLAUDE.md for test instructions
|
||||||
|
|
||||||
|
2. **Run the tests**: Execute the appropriate test command. Capture full output including stdout and stderr. Do NOT run tests interactively or with watch mode. Use flags that produce verbose or detailed output when available (e.g., `cargo test -- --nocapture`, `jest --verbose`).
|
||||||
|
|
||||||
|
3. **Analyze results**: Parse the test output carefully and categorize:
|
||||||
|
- Total tests run
|
||||||
|
- Tests passed
|
||||||
|
- Tests failed (with details)
|
||||||
|
- Tests skipped/ignored
|
||||||
|
- Compilation errors (if tests couldn't even run)
|
||||||
|
|
||||||
|
4. **Report findings**:
|
||||||
|
|
||||||
|
**If ALL tests pass:**
|
||||||
|
Provide a concise success summary:
|
||||||
|
- Total test count and pass count
|
||||||
|
- Execution time if available
|
||||||
|
- Note any skipped/ignored tests and why (if apparent)
|
||||||
|
- A clear statement: "All tests passed."
|
||||||
|
|
||||||
|
**If ANY tests fail:**
|
||||||
|
Provide a detailed failure report:
|
||||||
|
- List each failing test by its full name/path
|
||||||
|
- Include the assertion error or panic message for each failure
|
||||||
|
- Include relevant expected vs actual values
|
||||||
|
- Note the file and line number where the failure occurred (if available)
|
||||||
|
- Group failures by module/file if there are many
|
||||||
|
- Suggest likely root causes when the error messages make it apparent
|
||||||
|
- Note if failures appear related (e.g., same underlying issue)
|
||||||
|
|
||||||
|
**If tests cannot run (compilation/setup error):**
|
||||||
|
- Report the exact error preventing test execution
|
||||||
|
- Identify the file and line causing the issue
|
||||||
|
- Distinguish between test code errors and source code errors
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- NEVER modify any source code or test code. You are read-only except for running the test command.
|
||||||
|
- NEVER skip running tests and guess at results. Always execute the actual test command.
|
||||||
|
- NEVER run the full application or any destructive commands. Only run test commands.
|
||||||
|
- If the test suite is extremely large, run it fully anyway. Do not truncate or sample.
|
||||||
|
- If multiple test targets exist (unit, integration, e2e), run unit tests only unless instructed otherwise.
|
||||||
|
- Report raw numbers. Do not round or approximate test counts.
|
||||||
|
- If tests produce warnings (not failures), mention them briefly but clearly separate them from failures.
|
||||||
|
- Keep the summary structured and scannable. Use bullet points and clear headers.
|
||||||
17
.claude/hooks/on-file-write.sh
Executable file
17
.claude/hooks/on-file-write.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Ultimate Bug Scanner - Claude Code Hook
|
||||||
|
# Runs on every file save for UBS-supported languages (JS/TS, Python, C/C++, Rust, Go, Java, Ruby)
|
||||||
|
# Claude Code hooks receive context as JSON on stdin.
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
|
||||||
|
CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
|
||||||
|
|
||||||
|
if [[ "$FILE_PATH" =~ \.(js|jsx|ts|tsx|mjs|cjs|py|pyw|pyi|c|cc|cpp|cxx|h|hh|hpp|hxx|rs|go|java|rb)$ ]]; then
|
||||||
|
echo "🔬 Running bug scanner..."
|
||||||
|
if ! command -v ubs >/dev/null 2>&1; then
|
||||||
|
echo "⚠️ 'ubs' not found in PATH; install it before using this hook." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
ubs "$FILE_PATH" --ci 2>&1 | head -50
|
||||||
|
fi
|
||||||
99
.claude/plan.md
Normal file
99
.claude/plan.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# Plan: Add Colors to Sync Command Output
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
The sync output has three layers, each needing color treatment:
|
||||||
|
|
||||||
|
### Layer 1: Stage Lines (during sync)
|
||||||
|
```
|
||||||
|
✓ Issues 10 issues from 2 projects 4.2s
|
||||||
|
✓ Status 3 statuses updated · 5 seen 4.2s
|
||||||
|
vs/typescript-code 2 issues · 1 statuses updated
|
||||||
|
✓ MRs 5 merge requests from 2 projects 12.3s
|
||||||
|
vs/python-code 3 MRs · 10 discussions
|
||||||
|
✓ Docs 1,200 documents generated 8.1s
|
||||||
|
✓ Embed 3,400 chunks embedded 45.2s
|
||||||
|
```
|
||||||
|
|
||||||
|
**What's uncolored:** icons, labels, numbers, elapsed times, sub-row project paths, failure counts in parentheses.
|
||||||
|
|
||||||
|
### Layer 2: Summary (after sync)
|
||||||
|
```
|
||||||
|
Synced 10 issues and 5 MRs in 42.3s
|
||||||
|
120 discussions · 45 events · 12 diffs · 3 statuses updated
|
||||||
|
1,200 docs regenerated · 3,400 embedded
|
||||||
|
```
|
||||||
|
|
||||||
|
**What's already colored:** headline ("Synced" = green bold, "Sync completed with issues" = warning bold), issue/MR counts (bold), error line (red). Detail lines are all dim.
|
||||||
|
|
||||||
|
### Layer 3: Timing breakdown (`-t` flag)
|
||||||
|
```
|
||||||
|
── Timing ──────────────────────
|
||||||
|
issues .............. 4.2s
|
||||||
|
merge_requests ...... 12.3s
|
||||||
|
```
|
||||||
|
|
||||||
|
**What's already colored:** dots (dim), time (bold), errors (red), rate limits (warning).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Color Plan
|
||||||
|
|
||||||
|
Using only existing `Theme` methods — no new colors needed.
|
||||||
|
|
||||||
|
### Stage Lines (`format_stage_line` + callers in sync.rs)
|
||||||
|
|
||||||
|
| Element | Current | Proposed | Theme method |
|
||||||
|
|---------|---------|----------|-------------|
|
||||||
|
| Icon (✓/⚠) | plain | green for success, yellow for warning | `Theme::success()` / `Theme::warning()` |
|
||||||
|
| Label ("Issues", "MRs", etc.) | plain | bold | `Theme::bold()` |
|
||||||
|
| Numbers in summary text | plain | bold | `Theme::bold()` (just the count) |
|
||||||
|
| Elapsed time | plain | muted gray | `Theme::timing()` |
|
||||||
|
| Failure text in parens | plain | warning/error color | `Theme::warning()` |
|
||||||
|
|
||||||
|
### Sub-rows (project breakdown lines)
|
||||||
|
|
||||||
|
| Element | Current | Proposed |
|
||||||
|
|---------|---------|----------|
|
||||||
|
| Project path | dim | `Theme::muted()` (slightly brighter than dim) |
|
||||||
|
| Counts (numbers only) | dim | `Theme::dim()` but numbers in normal weight |
|
||||||
|
| Error/failure counts | dim | `Theme::warning()` |
|
||||||
|
| Middle dots | dim | keep dim (they're separators, should recede) |
|
||||||
|
|
||||||
|
### Summary (`print_sync`)
|
||||||
|
|
||||||
|
| Element | Current | Proposed |
|
||||||
|
|---------|---------|----------|
|
||||||
|
| Issue/MR counts in headline | bold only | `Theme::info()` + bold (cyan numbers pop) |
|
||||||
|
| Time in headline | plain | `Theme::timing()` |
|
||||||
|
| Detail line numbers | all dim | numbers in `Theme::info()`, rest stays dim |
|
||||||
|
| Doc line numbers | all dim | numbers in `Theme::info()`, rest stays dim |
|
||||||
|
| "Already up to date" time | plain | `Theme::timing()` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files to Change
|
||||||
|
|
||||||
|
1. **`src/cli/progress.rs`** — `format_stage_line()`: apply color to icon, bold to label, `Theme::timing()` to elapsed
|
||||||
|
2. **`src/cli/commands/sync.rs`** —
|
||||||
|
- Pass colored icons to `format_stage_line` / `emit_stage_line` / `emit_stage_block`
|
||||||
|
- Color failure text in `append_failures()`
|
||||||
|
- Color numbers and time in `print_sync()`
|
||||||
|
- Color error/failure counts in sub-row functions (`issue_sub_rows`, `mr_sub_rows`, `status_sub_rows`)
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
- `format_stage_line` already receives the icon string — color it before passing
|
||||||
|
- Add a `color_icon` helper that applies success/warning color to the icon glyph
|
||||||
|
- Bold the label in `format_stage_line`
|
||||||
|
- Apply `Theme::timing()` to elapsed in `format_stage_line`
|
||||||
|
- In `append_failures`, wrap failure text in `Theme::warning()`
|
||||||
|
- In `print_sync`, wrap count numbers with `Theme::info().bold()`
|
||||||
|
- In sub-row functions, apply `Theme::warning()` to error/failure parts only (keep rest dim)
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- No changes to robot mode (JSON output)
|
||||||
|
- No changes to dry-run output (already reasonably colored)
|
||||||
|
- No new Theme colors — use existing palette
|
||||||
|
- No changes to timing breakdown (already colored)
|
||||||
106
.claude/skills/release/SKILL.md
Normal file
106
.claude/skills/release/SKILL.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
name: release
|
||||||
|
description: Bump version, tag, and prepare for next development cycle
|
||||||
|
version: 1.0.0
|
||||||
|
author: Taylor Eernisse
|
||||||
|
category: automation
|
||||||
|
tags: ["release", "versioning", "semver", "git"]
|
||||||
|
---
|
||||||
|
# Release
|
||||||
|
|
||||||
|
Automate SemVer version bumps for the `lore` CLI.
|
||||||
|
|
||||||
|
## Invocation
|
||||||
|
|
||||||
|
```
|
||||||
|
/release <type>
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `<type>` is one of:
|
||||||
|
- **major** — breaking changes (0.5.0 -> 1.0.0)
|
||||||
|
- **minor** — new features (0.5.0 -> 0.6.0)
|
||||||
|
- **patch** / **hotfix** — bug fixes (0.5.0 -> 0.5.1)
|
||||||
|
|
||||||
|
If no type is provided, ask the user.
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
Follow these steps exactly. Do NOT skip any step.
|
||||||
|
|
||||||
|
### 1. Determine bump type
|
||||||
|
|
||||||
|
Parse the argument. Accept these aliases:
|
||||||
|
- `major`, `breaking` -> MAJOR
|
||||||
|
- `minor`, `feature`, `feat` -> MINOR
|
||||||
|
- `patch`, `hotfix`, `fix` -> PATCH
|
||||||
|
|
||||||
|
If the argument doesn't match, ask the user to clarify.
|
||||||
|
|
||||||
|
### 2. Read current version
|
||||||
|
|
||||||
|
Read `Cargo.toml` and extract the `version = "X.Y.Z"` line. Parse into major, minor, patch integers.
|
||||||
|
|
||||||
|
### 3. Compute new version
|
||||||
|
|
||||||
|
- MAJOR: `(major+1).0.0`
|
||||||
|
- MINOR: `major.(minor+1).0`
|
||||||
|
- PATCH: `major.minor.(patch+1)`
|
||||||
|
|
||||||
|
### 4. Check preconditions
|
||||||
|
|
||||||
|
Run `git status` and `git log --oneline -5`. Show the user:
|
||||||
|
- Current version: X.Y.Z
|
||||||
|
- New version: A.B.C
|
||||||
|
- Bump type: major/minor/patch
|
||||||
|
- Working tree status (clean or dirty)
|
||||||
|
- Last 5 commits (so they can confirm scope)
|
||||||
|
|
||||||
|
If the working tree is dirty, warn: "You have uncommitted changes. They will NOT be included in the release tag. Continue?"
|
||||||
|
|
||||||
|
Ask the user to confirm before proceeding.
|
||||||
|
|
||||||
|
### 5. Update Cargo.toml
|
||||||
|
|
||||||
|
Edit the `version = "..."` line in Cargo.toml to the new version.
|
||||||
|
|
||||||
|
### 6. Update Cargo.lock
|
||||||
|
|
||||||
|
Run `cargo check` to update Cargo.lock with the new version. This also verifies the project compiles.
|
||||||
|
|
||||||
|
### 7. Commit the version bump
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add Cargo.toml Cargo.lock
|
||||||
|
git commit -m "release: v{NEW_VERSION}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Tag the release
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag v{NEW_VERSION}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Report
|
||||||
|
|
||||||
|
Print a summary:
|
||||||
|
```
|
||||||
|
Release v{NEW_VERSION} created.
|
||||||
|
|
||||||
|
Previous: v{OLD_VERSION}
|
||||||
|
Bump: {type}
|
||||||
|
Tag: v{NEW_VERSION}
|
||||||
|
Commit: {short hash}
|
||||||
|
|
||||||
|
To push: git push && git push --tags
|
||||||
|
```
|
||||||
|
|
||||||
|
Do NOT push automatically. The user decides when to push.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```
|
||||||
|
/release minor -> 0.5.0 -> 0.6.0
|
||||||
|
/release hotfix -> 0.5.0 -> 0.5.1
|
||||||
|
/release patch -> 0.5.0 -> 0.5.1
|
||||||
|
/release major -> 0.5.0 -> 1.0.0
|
||||||
|
```
|
||||||
50
.cline/rules
Normal file
50
.cline/rules
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
````markdown
|
||||||
|
## UBS Quick Reference for AI Agents
|
||||||
|
|
||||||
|
UBS stands for "Ultimate Bug Scanner": **The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On**
|
||||||
|
|
||||||
|
**Install:** `curl -sSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/master/install.sh | bash`
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
```bash
|
||||||
|
ubs file.ts file2.py # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(git diff --name-only --cached) # Staged files — before commit
|
||||||
|
ubs --only=js,python src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs --help # Full command reference
|
||||||
|
ubs sessions --entries 1 # Tail the latest install session log
|
||||||
|
ubs . # Whole project (ignores things like .venv and node_modules automatically)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.ts:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
**Fix Workflow:**
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
**Speed Critical:** Scope to changed files. `ubs src/file.ts` (< 1s) vs `ubs .` (30s). Never full scan for small edits.
|
||||||
|
|
||||||
|
**Bug Severity:**
|
||||||
|
- **Critical** (always fix): Null safety, XSS/injection, async/await, memory leaks
|
||||||
|
- **Important** (production): Type narrowing, division-by-zero, resource leaks
|
||||||
|
- **Contextual** (judgment): TODO/FIXME, console logs
|
||||||
|
|
||||||
|
**Anti-Patterns:**
|
||||||
|
- ❌ Ignore findings → ✅ Investigate each
|
||||||
|
- ❌ Full scan per edit → ✅ Scope to file
|
||||||
|
- ❌ Fix symptom (`if (x) { x.y }`) → ✅ Root cause (`x?.y`)
|
||||||
|
````
|
||||||
50
.codex/rules/ubs.md
Normal file
50
.codex/rules/ubs.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
````markdown
|
||||||
|
## UBS Quick Reference for AI Agents
|
||||||
|
|
||||||
|
UBS stands for "Ultimate Bug Scanner": **The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On**
|
||||||
|
|
||||||
|
**Install:** `curl -sSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/master/install.sh | bash`
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
```bash
|
||||||
|
ubs file.ts file2.py # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(git diff --name-only --cached) # Staged files — before commit
|
||||||
|
ubs --only=js,python src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs --help # Full command reference
|
||||||
|
ubs sessions --entries 1 # Tail the latest install session log
|
||||||
|
ubs . # Whole project (ignores things like .venv and node_modules automatically)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.ts:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
**Fix Workflow:**
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
**Speed Critical:** Scope to changed files. `ubs src/file.ts` (< 1s) vs `ubs .` (30s). Never full scan for small edits.
|
||||||
|
|
||||||
|
**Bug Severity:**
|
||||||
|
- **Critical** (always fix): Null safety, XSS/injection, async/await, memory leaks
|
||||||
|
- **Important** (production): Type narrowing, division-by-zero, resource leaks
|
||||||
|
- **Contextual** (judgment): TODO/FIXME, console logs
|
||||||
|
|
||||||
|
**Anti-Patterns:**
|
||||||
|
- ❌ Ignore findings → ✅ Investigate each
|
||||||
|
- ❌ Full scan per edit → ✅ Scope to file
|
||||||
|
- ❌ Fix symptom (`if (x) { x.y }`) → ✅ Root cause (`x?.y`)
|
||||||
|
````
|
||||||
16
.continue/config.json
Normal file
16
.continue/config.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"customCommands": [
|
||||||
|
{
|
||||||
|
"name": "scan-bugs",
|
||||||
|
"description": "Run Ultimate Bug Scanner on current project",
|
||||||
|
"prompt": "Run 'ubs --fail-on-warning .' and fix any critical issues found before proceeding"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"slashCommands": [
|
||||||
|
{
|
||||||
|
"name": "quality",
|
||||||
|
"description": "Check code quality with UBS",
|
||||||
|
"run": "ubs ."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
50
.cursor/rules
Normal file
50
.cursor/rules
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
````markdown
|
||||||
|
## UBS Quick Reference for AI Agents
|
||||||
|
|
||||||
|
UBS stands for "Ultimate Bug Scanner": **The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On**
|
||||||
|
|
||||||
|
**Install:** `curl -sSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/master/install.sh | bash`
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
```bash
|
||||||
|
ubs file.ts file2.py # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(git diff --name-only --cached) # Staged files — before commit
|
||||||
|
ubs --only=js,python src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs --help # Full command reference
|
||||||
|
ubs sessions --entries 1 # Tail the latest install session log
|
||||||
|
ubs . # Whole project (ignores things like .venv and node_modules automatically)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.ts:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
**Fix Workflow:**
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
**Speed Critical:** Scope to changed files. `ubs src/file.ts` (< 1s) vs `ubs .` (30s). Never full scan for small edits.
|
||||||
|
|
||||||
|
**Bug Severity:**
|
||||||
|
- **Critical** (always fix): Null safety, XSS/injection, async/await, memory leaks
|
||||||
|
- **Important** (production): Type narrowing, division-by-zero, resource leaks
|
||||||
|
- **Contextual** (judgment): TODO/FIXME, console logs
|
||||||
|
|
||||||
|
**Anti-Patterns:**
|
||||||
|
- ❌ Ignore findings → ✅ Investigate each
|
||||||
|
- ❌ Full scan per edit → ✅ Scope to file
|
||||||
|
- ❌ Fix symptom (`if (x) { x.y }`) → ✅ Root cause (`x?.y`)
|
||||||
|
````
|
||||||
50
.gemini/rules
Normal file
50
.gemini/rules
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
````markdown
|
||||||
|
## UBS Quick Reference for AI Agents
|
||||||
|
|
||||||
|
UBS stands for "Ultimate Bug Scanner": **The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On**
|
||||||
|
|
||||||
|
**Install:** `curl -sSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/master/install.sh | bash`
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
```bash
|
||||||
|
ubs file.ts file2.py # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(git diff --name-only --cached) # Staged files — before commit
|
||||||
|
ubs --only=js,python src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs --help # Full command reference
|
||||||
|
ubs sessions --entries 1 # Tail the latest install session log
|
||||||
|
ubs . # Whole project (ignores things like .venv and node_modules automatically)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.ts:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
**Fix Workflow:**
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
**Speed Critical:** Scope to changed files. `ubs src/file.ts` (< 1s) vs `ubs .` (30s). Never full scan for small edits.
|
||||||
|
|
||||||
|
**Bug Severity:**
|
||||||
|
- **Critical** (always fix): Null safety, XSS/injection, async/await, memory leaks
|
||||||
|
- **Important** (production): Type narrowing, division-by-zero, resource leaks
|
||||||
|
- **Contextual** (judgment): TODO/FIXME, console logs
|
||||||
|
|
||||||
|
**Anti-Patterns:**
|
||||||
|
- ❌ Ignore findings → ✅ Investigate each
|
||||||
|
- ❌ Full scan per edit → ✅ Scope to file
|
||||||
|
- ❌ Fix symptom (`if (x) { x.y }`) → ✅ Root cause (`x?.y`)
|
||||||
|
````
|
||||||
21
.github/workflows/roam.yml
vendored
Normal file
21
.github/workflows/roam.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Roam Code Analysis
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [main, master]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
jobs:
|
||||||
|
roam:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
- run: pip install roam-code
|
||||||
|
- run: roam index
|
||||||
|
- run: roam fitness
|
||||||
|
- run: roam pr-risk --json
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -31,6 +31,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Local config files
|
# Local config files
|
||||||
lore.config.json
|
lore.config.json
|
||||||
|
.liquid-mail.toml
|
||||||
|
|
||||||
# beads
|
# beads
|
||||||
.bv/
|
.bv/
|
||||||
@@ -41,6 +42,9 @@ lore.config.json
|
|||||||
*.db-shm
|
*.db-shm
|
||||||
|
|
||||||
|
|
||||||
|
# Mock seed data
|
||||||
|
tools/mock-seed/
|
||||||
|
|
||||||
# Added by cargo
|
# Added by cargo
|
||||||
|
|
||||||
/target
|
/target
|
||||||
|
|||||||
717
AGENTS.md
717
AGENTS.md
@@ -1,6 +1,532 @@
|
|||||||
# AGENTS.md
|
# AGENTS.md
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
## RULE 0 - THE FUNDAMENTAL OVERRIDE PEROGATIVE
|
||||||
|
|
||||||
|
If I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RULE NUMBER 1: NO FILE DELETION
|
||||||
|
|
||||||
|
**YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION.** Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.
|
||||||
|
|
||||||
|
**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version Control: jj-First (CRITICAL)
|
||||||
|
|
||||||
|
**ALWAYS prefer jj (Jujutsu) over git for all VCS operations.** This is a colocated repo with both `.jj/` and `.git/`. When instructed to use git by anything — even later in this file — use the best jj replacement commands instead. Only fall back to raw `git` for things jj cannot do (hooks, LFS, submodules, `gh` CLI interop).
|
||||||
|
|
||||||
|
See `~/.claude/rules/jj-vcs/` for the full command reference, translation table, revsets, patterns, and recovery recipes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS
|
||||||
|
|
||||||
|
> **Note:** Treat destructive commands as break-glass. If there's any doubt, stop and ask.
|
||||||
|
|
||||||
|
1. **Absolutely forbidden commands:** `git reset --hard`, `git clean -fd`, `rm -rf`, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences.
|
||||||
|
2. **No guessing:** If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. "I think it's safe" is never acceptable.
|
||||||
|
3. **Safer alternatives first:** When cleanup or rollbacks are needed, request permission to use non-destructive options (`git status`, `git diff`, `git stash`, copying to backups) before ever considering a destructive command.
|
||||||
|
4. **Mandatory explicit plan:** Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.
|
||||||
|
5. **Document the confirmation:** When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Toolchain: Rust & Cargo
|
||||||
|
|
||||||
|
We only use **Cargo** in this project, NEVER any other package manager.
|
||||||
|
|
||||||
|
- **Edition/toolchain:** Follow `rust-toolchain.toml` (if present). Do not assume stable vs nightly.
|
||||||
|
- **Dependencies:** Explicit versions for stability; keep the set minimal.
|
||||||
|
- **Configuration:** Cargo.toml only
|
||||||
|
- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)
|
||||||
|
|
||||||
|
When writing Rust code, reference RUST_CLI_TOOLS_BEST_PRACTICES.md
|
||||||
|
|
||||||
|
### Release Profile
|
||||||
|
|
||||||
|
Use the release profile defined in `Cargo.toml`. If you need to change it, justify the
|
||||||
|
performance/size tradeoff and how it impacts determinism and cancellation behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Editing Discipline
|
||||||
|
|
||||||
|
### No Script-Based Changes
|
||||||
|
|
||||||
|
**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.
|
||||||
|
|
||||||
|
- **Always make code changes manually**, even when there are many instances
|
||||||
|
- For many simple changes: use parallel subagents
|
||||||
|
- For subtle/complex changes: do them methodically yourself
|
||||||
|
|
||||||
|
### No File Proliferation
|
||||||
|
|
||||||
|
If you want to change something or add a feature, **revise existing code files in place**.
|
||||||
|
|
||||||
|
**NEVER** create variations like:
|
||||||
|
- `mainV2.rs`
|
||||||
|
- `main_improved.rs`
|
||||||
|
- `main_enhanced.rs`
|
||||||
|
|
||||||
|
New files are reserved for **genuinely new functionality** that makes zero sense to include in any existing file. The bar for creating new files is **incredibly high**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backwards Compatibility
|
||||||
|
|
||||||
|
We do not care about backwards compatibility—we're in early development with no users. We want to do things the **RIGHT** way with **NO TECH DEBT**.
|
||||||
|
|
||||||
|
- Never create "compatibility shims"
|
||||||
|
- Never create wrapper functions for deprecated APIs
|
||||||
|
- Just fix the code directly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Compiler Checks (CRITICAL)
|
||||||
|
|
||||||
|
**After any substantive code changes, you MUST verify no errors were introduced:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for compiler errors and warnings
|
||||||
|
cargo check --all-targets
|
||||||
|
|
||||||
|
# Check for clippy lints (pedantic + nursery are enabled)
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
|
# Verify formatting
|
||||||
|
cargo fmt --check
|
||||||
|
```
|
||||||
|
|
||||||
|
If you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit & Property Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Run with output
|
||||||
|
cargo test -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
When adding or changing primitives, add tests that assert the core invariants:
|
||||||
|
|
||||||
|
- no task leaks
|
||||||
|
- no obligation leaks
|
||||||
|
- losers are drained after races
|
||||||
|
- region close implies quiescence
|
||||||
|
|
||||||
|
Prefer deterministic lab-runtime tests for concurrency-sensitive behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beads (br) — Dependency-Aware Issue Tracking
|
||||||
|
|
||||||
|
Beads provides a lightweight, dependency-aware issue database and CLI (`br` / beads_rust) for selecting "ready work," setting priorities, and tracking status. It complements Liquid Mail's shared log for progress, decisions, and cross-session context.
|
||||||
|
|
||||||
|
**Note:** `br` is non-invasive—it never executes git commands directly. You must run git commands manually after `br sync --flush-only`.
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
- **Single source of truth:** Beads for task status/priority/dependencies; Liquid Mail for conversation/decisions
|
||||||
|
- **Shared identifiers:** Include the Beads issue ID in posts (e.g., `[br-123] Topic validation rules`)
|
||||||
|
- **Decisions before action:** Post `DECISION:` messages before risky changes, not after
|
||||||
|
|
||||||
|
### Typical Agent Flow
|
||||||
|
|
||||||
|
1. **Pick ready work (Beads):**
|
||||||
|
```bash
|
||||||
|
br ready --json # Choose highest priority, no blockers
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check context (Liquid Mail):**
|
||||||
|
```bash
|
||||||
|
liquid-mail notify # See what changed since last session
|
||||||
|
liquid-mail query "br-123" # Find prior discussion on this issue
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Work and log progress:**
|
||||||
|
```bash
|
||||||
|
liquid-mail post --topic <workstream> "[br-123] START: <description>"
|
||||||
|
liquid-mail post "[br-123] FINDING: <what you discovered>"
|
||||||
|
liquid-mail post --decision "[br-123] DECISION: <what you decided and why>"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Complete (Beads is authority):**
|
||||||
|
```bash
|
||||||
|
br close br-123 --reason "Completed"
|
||||||
|
liquid-mail post "[br-123] Completed: <summary with commit ref>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mapping Cheat Sheet
|
||||||
|
|
||||||
|
| Concept | In Beads | In Liquid Mail |
|
||||||
|
|---------|----------|----------------|
|
||||||
|
| Work item | `br-###` (issue ID) | Include `[br-###]` in posts |
|
||||||
|
| Workstream | — | `--topic auth-system` |
|
||||||
|
| Subject prefix | — | `[br-###] ...` |
|
||||||
|
| Commit message | Include `br-###` | — |
|
||||||
|
| Status | `br update --status` | Post progress messages |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## bv — Graph-Aware Triage Engine
|
||||||
|
|
||||||
|
bv is a graph-aware triage engine for Beads projects (`.beads/beads.jsonl`). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.
|
||||||
|
|
||||||
|
**Scope boundary:** bv handles *what to work on* (triage, priority, planning). For agent-to-agent coordination (progress logging, decisions, cross-session context), use Liquid Mail.
|
||||||
|
|
||||||
|
**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**
|
||||||
|
|
||||||
|
### The Workflow: Start With Triage
|
||||||
|
|
||||||
|
**`bv --robot-triage` is your single entry point.** It returns:
|
||||||
|
- `quick_ref`: at-a-glance counts + top 3 picks
|
||||||
|
- `recommendations`: ranked actionable items with scores, reasons, unblock info
|
||||||
|
- `quick_wins`: low-effort high-impact items
|
||||||
|
- `blockers_to_clear`: items that unblock the most downstream work
|
||||||
|
- `project_health`: status/type/priority distributions, graph metrics
|
||||||
|
- `commands`: copy-paste shell commands for next steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-triage # THE MEGA-COMMAND: start here
|
||||||
|
bv --robot-next # Minimal: just the single top pick + claim command
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Reference
|
||||||
|
|
||||||
|
**Planning:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-plan` | Parallel execution tracks with `unblocks` lists |
|
||||||
|
| `--robot-priority` | Priority misalignment detection with confidence |
|
||||||
|
|
||||||
|
**Graph Analysis:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |
|
||||||
|
| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |
|
||||||
|
| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |
|
||||||
|
| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |
|
||||||
|
|
||||||
|
**History & Change Tracking:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-history` | Bead-to-commit correlations |
|
||||||
|
| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |
|
||||||
|
| `--robot-forecast <id\|all>` | ETA predictions with dependency-aware scheduling |
|
||||||
|
| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |
|
||||||
|
| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |
|
||||||
|
| `--robot-graph [--graph-format=json\|dot\|mermaid]` | Dependency graph export |
|
||||||
|
| `--export-graph <file.html>` | Interactive HTML visualization |
|
||||||
|
|
||||||
|
### Scoping & Filtering
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-plan --label backend # Scope to label's subgraph
|
||||||
|
bv --robot-insights --as-of HEAD~30 # Historical point-in-time
|
||||||
|
bv --recipe actionable --robot-plan # Pre-filter: ready to work
|
||||||
|
bv --recipe high-impact --robot-triage # Pre-filter: top PageRank
|
||||||
|
bv --robot-triage --robot-triage-by-track # Group by parallel work streams
|
||||||
|
bv --robot-triage --robot-triage-by-label # Group by domain
|
||||||
|
```
|
||||||
|
|
||||||
|
### Understanding Robot Output
|
||||||
|
|
||||||
|
**All robot JSON includes:**
|
||||||
|
- `data_hash` — Fingerprint of source beads.jsonl
|
||||||
|
- `status` — Per-metric state: `computed|approx|timeout|skipped` + elapsed ms
|
||||||
|
- `as_of` / `as_of_commit` — Present when using `--as-of`
|
||||||
|
|
||||||
|
**Two-phase analysis:**
|
||||||
|
- **Phase 1 (instant):** degree, topo sort, density
|
||||||
|
- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles
|
||||||
|
|
||||||
|
### jq Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-triage | jq '.quick_ref' # At-a-glance summary
|
||||||
|
bv --robot-triage | jq '.recommendations[0]' # Top recommendation
|
||||||
|
bv --robot-plan | jq '.plan.summary.highest_impact' # Best unblock target
|
||||||
|
bv --robot-insights | jq '.status' # Check metric readiness
|
||||||
|
bv --robot-insights | jq '.Cycles' # Circular deps (must fix!)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UBS — Ultimate Bug Scanner
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ubs file.rs file2.rs # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(jj diff --name-only) # Changed files — before commit
|
||||||
|
ubs --only=rust,toml src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs . # Whole project (ignores target/, Cargo.lock)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.rs:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
### Fix Workflow
|
||||||
|
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
### Bug Severity
|
||||||
|
|
||||||
|
- **Critical (always fix):** Memory safety, use-after-free, data races, SQL injection
|
||||||
|
- **Important (production):** Unwrap panics, resource leaks, overflow checks
|
||||||
|
- **Contextual (judgment):** TODO/FIXME, println! debugging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ast-grep vs ripgrep
|
||||||
|
|
||||||
|
**Use `ast-grep` when structure matters.** It parses code and matches AST nodes, ignoring comments/strings, and can **safely rewrite** code.
|
||||||
|
|
||||||
|
- Refactors/codemods: rename APIs, change import forms
|
||||||
|
- Policy checks: enforce patterns across a repo
|
||||||
|
- Editor/automation: LSP mode, `--json` output
|
||||||
|
|
||||||
|
**Use `ripgrep` when text is enough.** Fastest way to grep literals/regex.
|
||||||
|
|
||||||
|
- Recon: find strings, TODOs, log lines, config values
|
||||||
|
- Pre-filter: narrow candidate files before ast-grep
|
||||||
|
|
||||||
|
### Rule of Thumb
|
||||||
|
|
||||||
|
- Need correctness or **applying changes** → `ast-grep`
|
||||||
|
- Need raw speed or **hunting text** → `rg`
|
||||||
|
- Often combine: `rg` to shortlist files, then `ast-grep` to match/modify
|
||||||
|
|
||||||
|
### Rust Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find structured code (ignores comments)
|
||||||
|
ast-grep run -l Rust -p 'fn $NAME($$$ARGS) -> $RET { $$$BODY }'
|
||||||
|
|
||||||
|
# Find all unwrap() calls
|
||||||
|
ast-grep run -l Rust -p '$EXPR.unwrap()'
|
||||||
|
|
||||||
|
# Quick textual hunt
|
||||||
|
rg -n 'println!' -t rust
|
||||||
|
|
||||||
|
# Combine speed + precision
|
||||||
|
rg -l -t rust 'unwrap\(' | xargs ast-grep run -l Rust -p '$X.unwrap()' --json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Morph Warp Grep — AI-Powered Code Search
|
||||||
|
|
||||||
|
**Use `mcp__morph-mcp__warp_grep` for exploratory "how does X work?" questions.** An AI agent expands your query, greps the codebase, reads relevant files, and returns precise line ranges with full context.
|
||||||
|
|
||||||
|
**Use `ripgrep` for targeted searches.** When you know exactly what you're looking for.
|
||||||
|
|
||||||
|
**Use `ast-grep` for structural patterns.** When you need AST precision for matching/rewriting.
|
||||||
|
|
||||||
|
### When to Use What
|
||||||
|
|
||||||
|
| Scenario | Tool | Why |
|
||||||
|
|----------|------|-----|
|
||||||
|
| "How is pattern matching implemented?" | `warp_grep` | Exploratory; don't know where to start |
|
||||||
|
| "Where is the quick reject filter?" | `warp_grep` | Need to understand architecture |
|
||||||
|
| "Find all uses of `Regex::new`" | `ripgrep` | Targeted literal search |
|
||||||
|
| "Find files with `println!`" | `ripgrep` | Simple pattern |
|
||||||
|
| "Replace all `unwrap()` with `expect()`" | `ast-grep` | Structural refactor |
|
||||||
|
|
||||||
|
### warp_grep Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp__morph-mcp__warp_grep(
|
||||||
|
repoPath: "/path/to/dcg",
|
||||||
|
query: "How does the safe pattern whitelist work?"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns structured results with file paths, line ranges, and extracted code snippets.
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- **Don't** use `warp_grep` to find a specific function name → use `ripgrep`
|
||||||
|
- **Don't** use `ripgrep` to understand "how does X work" → wastes time with manual reads
|
||||||
|
- **Don't** use `ripgrep` for codemods → risks collateral edits
|
||||||
|
|
||||||
|
<!-- bv-agent-instructions-v1 -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beads Workflow Integration
|
||||||
|
|
||||||
|
This project uses [beads_viewer](https://github.com/Dicklesworthstone/beads_viewer) for issue tracking. Issues are stored in `.beads/` and tracked in version control.
|
||||||
|
|
||||||
|
**Note:** `br` is non-invasive—it never executes VCS commands directly. You must commit manually after `br sync --flush-only`.
|
||||||
|
|
||||||
|
### Essential Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View issues (launches TUI - avoid in automated sessions)
|
||||||
|
bv
|
||||||
|
|
||||||
|
# CLI commands for agents (use these instead)
|
||||||
|
br ready # Show issues ready to work (no blockers)
|
||||||
|
br list --status=open # All open issues
|
||||||
|
br show <id> # Full issue details with dependencies
|
||||||
|
br create --title="..." --type=task --priority=2
|
||||||
|
br update <id> --status=in_progress
|
||||||
|
br close <id> --reason="Completed"
|
||||||
|
br close <id1> <id2> # Close multiple issues at once
|
||||||
|
br sync --flush-only # Export to JSONL (then: jj commit -m "Update beads")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow Pattern
|
||||||
|
|
||||||
|
1. **Start**: Run `br ready` to find actionable work
|
||||||
|
2. **Claim**: Use `br update <id> --status=in_progress`
|
||||||
|
3. **Work**: Implement the task
|
||||||
|
4. **Complete**: Use `br close <id>`
|
||||||
|
5. **Sync**: Run `br sync --flush-only`, then `git add .beads/ && git commit -m "Update beads"`
|
||||||
|
|
||||||
|
### Key Concepts
|
||||||
|
|
||||||
|
- **Dependencies**: Issues can block other issues. `br ready` shows only unblocked work.
|
||||||
|
- **Priority**: P0=critical, P1=high, P2=medium, P3=low, P4=backlog (use numbers, not words)
|
||||||
|
- **Types**: task, bug, feature, epic, question, docs
|
||||||
|
- **Blocking**: `br dep add <issue> <depends-on>` to add dependencies
|
||||||
|
|
||||||
|
### Session Protocol
|
||||||
|
|
||||||
|
**Before ending any session, run this checklist (solo/lead only — workers skip VCS):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jj status # Check what changed
|
||||||
|
br sync --flush-only # Export beads to JSONL
|
||||||
|
jj commit -m "..." # Commit code and beads (jj auto-tracks all changes)
|
||||||
|
jj bookmark set <name> -r @- # Point bookmark at committed work
|
||||||
|
jj git push -b <name> # Push to remote
|
||||||
|
```
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- Check `br ready` at session start to find available work
|
||||||
|
- Update status as you work (in_progress → closed)
|
||||||
|
- Create new issues with `br create` when you discover tasks
|
||||||
|
- Use descriptive titles and set appropriate priority/type
|
||||||
|
- Always run `br sync --flush-only` then commit before ending session (jj auto-tracks .beads/)
|
||||||
|
|
||||||
|
<!-- end-bv-agent-instructions -->
|
||||||
|
|
||||||
|
## Landing the Plane (Session Completion)
|
||||||
|
|
||||||
|
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until push succeeds.
|
||||||
|
|
||||||
|
**WHO RUNS THIS:** Solo agents run it themselves. In multi-agent sessions, ONLY the team lead runs this. Workers skip VCS entirely.
|
||||||
|
|
||||||
|
**MANDATORY WORKFLOW:**
|
||||||
|
|
||||||
|
1. **File issues for remaining work** - Create issues for anything that needs follow-up
|
||||||
|
2. **Run quality gates** (if code changed) - Tests, linters, builds
|
||||||
|
3. **Update issue status** - Close finished work, update in-progress items
|
||||||
|
4. **PUSH TO REMOTE** - This is MANDATORY:
|
||||||
|
```bash
|
||||||
|
jj git fetch # Get latest remote state
|
||||||
|
jj rebase -d trunk() # Rebase onto latest trunk if needed
|
||||||
|
br sync --flush-only # Export beads to JSONL
|
||||||
|
jj commit -m "Update beads" # Commit (jj auto-tracks .beads/ changes)
|
||||||
|
jj bookmark set <name> -r @- # Point bookmark at committed work
|
||||||
|
jj git push -b <name> # Push to remote
|
||||||
|
jj log -r '<name>' # Verify bookmark position
|
||||||
|
```
|
||||||
|
5. **Clean up** - Abandon empty orphan changes if any (`jj abandon <rev>`)
|
||||||
|
6. **Verify** - All changes committed AND pushed
|
||||||
|
7. **Hand off** - Provide context for next session
|
||||||
|
|
||||||
|
**CRITICAL RULES:**
|
||||||
|
- Work is NOT complete until `jj git push` succeeds
|
||||||
|
- NEVER stop before pushing - that leaves work stranded locally
|
||||||
|
- NEVER say "ready to push when you are" - YOU must push
|
||||||
|
- If push fails, resolve and retry until it succeeds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## cass — Cross-Agent Session Search
|
||||||
|
|
||||||
|
`cass` indexes prior agent conversations (Claude Code, Codex, Cursor, Gemini, ChatGPT, etc.) so we can reuse solved problems.
|
||||||
|
|
||||||
|
**Rules:** Never run bare `cass` (TUI). Always use `--robot` or `--json`.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cass health
|
||||||
|
cass search "async runtime" --robot --limit 5
|
||||||
|
cass view /path/to/session.jsonl -n 42 --json
|
||||||
|
cass expand /path/to/session.jsonl -n 42 -C 3 --json
|
||||||
|
cass capabilities --json
|
||||||
|
cass robot-docs guide
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tips
|
||||||
|
|
||||||
|
- Use `--fields minimal` for lean output
|
||||||
|
- Filter by agent with `--agent`
|
||||||
|
- Use `--days N` to limit to recent history
|
||||||
|
|
||||||
|
stdout is data-only, stderr is diagnostics; exit code 0 means success.
|
||||||
|
|
||||||
|
Treat cass as a way to avoid re-solving problems other agents already handled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note for Codex/GPT-5.2
|
||||||
|
|
||||||
|
You constantly bother me and stop working with concerned questions that look similar to this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Unexpected changes (need guidance)
|
||||||
|
|
||||||
|
- Working tree still shows edits I did not make in Cargo.toml, Cargo.lock, src/runtime.rs, src/scope.rs. Please advise whether to keep/commit/revert these before any further work. I did not touch them.
|
||||||
|
|
||||||
|
Next steps (pick one)
|
||||||
|
|
||||||
|
1. Decide how to handle the unrelated modified files above so we can resume cleanly.
|
||||||
|
```
|
||||||
|
|
||||||
|
NEVER EVER DO THAT AGAIN. The answer is literally ALWAYS the same: those are changes created by the potentially dozen of other agents working on the project at the same time. This is not only a common occurence, it happens multiple times PER MINUTE. The way to deal with it is simple: you NEVER, under ANY CIRCUMSTANCE, stash, revert, overwrite, or otherwise disturb in ANY way the work of other agents. Just treat those changes identically to changes that you yourself made. Just fool yourself into thinking YOU made the changes and simply don't recall it for some reason.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note on Built-in TODO Functionality
|
||||||
|
|
||||||
|
Also, if I ask you to explicitly use your built-in TODO functionality, don't complain about this and say you need to use beads. You can use built-in TODOs if I tell you specifically to do so. Always comply with such orders.
|
||||||
|
|
||||||
## TDD Requirements
|
## TDD Requirements
|
||||||
|
|
||||||
@@ -27,58 +553,116 @@ If you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to f
|
|||||||
|
|
||||||
## Gitlore Robot Mode
|
## Gitlore Robot Mode
|
||||||
|
|
||||||
The `lore` CLI has a robot mode optimized for AI agent consumption with structured JSON output, meaningful exit codes, and TTY auto-detection.
|
The `lore` CLI has a robot mode optimized for AI agent consumption with compact JSON output, structured errors with machine-actionable recovery steps, meaningful exit codes, response timing metadata, field selection for token efficiency, and TTY auto-detection.
|
||||||
|
|
||||||
### Activation
|
### Activation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Explicit flag
|
# Explicit flag
|
||||||
lore --robot list issues
|
lore --robot issues -n 10
|
||||||
|
|
||||||
|
# JSON shorthand (-J)
|
||||||
|
lore -J issues -n 10
|
||||||
|
|
||||||
# Auto-detection (when stdout is not a TTY)
|
# Auto-detection (when stdout is not a TTY)
|
||||||
lore list issues | jq .
|
lore issues | jq .
|
||||||
|
|
||||||
# Environment variable
|
# Environment variable
|
||||||
LORE_ROBOT=true lore list issues
|
LORE_ROBOT=1 lore issues
|
||||||
```
|
```
|
||||||
|
|
||||||
### Robot Mode Commands
|
### Robot Mode Commands
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# List issues/MRs with JSON output
|
# List issues/MRs with JSON output
|
||||||
lore --robot list issues --limit=10
|
lore --robot issues -n 10
|
||||||
lore --robot list mrs --state=opened
|
lore --robot mrs -s opened
|
||||||
|
|
||||||
|
# Filter issues by work item status (case-insensitive)
|
||||||
|
lore --robot issues --status "In progress"
|
||||||
|
|
||||||
|
# List with field selection (reduces token usage ~60%)
|
||||||
|
lore --robot issues --fields minimal
|
||||||
|
lore --robot mrs --fields iid,title,state,draft
|
||||||
|
|
||||||
|
# Show detailed entity info
|
||||||
|
lore --robot issues 123
|
||||||
|
lore --robot mrs 456 -p group/repo
|
||||||
|
|
||||||
# Count entities
|
# Count entities
|
||||||
lore --robot count issues
|
lore --robot count issues
|
||||||
lore --robot count discussions --type=mr
|
lore --robot count discussions --for mr
|
||||||
|
|
||||||
# Show detailed entity info
|
# Search indexed documents
|
||||||
lore --robot show issue 123
|
lore --robot search "authentication bug"
|
||||||
lore --robot show mr 456 --project=group/repo
|
|
||||||
|
|
||||||
# Check sync status
|
# Check sync status
|
||||||
lore --robot sync-status
|
lore --robot status
|
||||||
|
|
||||||
# Run ingestion (quiet, JSON summary)
|
# Run full sync pipeline
|
||||||
lore --robot ingest --type=issues
|
lore --robot sync
|
||||||
|
|
||||||
|
# Run sync without resource events
|
||||||
|
lore --robot sync --no-events
|
||||||
|
|
||||||
|
# Run ingestion only
|
||||||
|
lore --robot ingest issues
|
||||||
|
|
||||||
# Check environment health
|
# Check environment health
|
||||||
lore --robot doctor
|
lore --robot doctor
|
||||||
|
|
||||||
|
# Document and index statistics
|
||||||
|
lore --robot stats
|
||||||
|
|
||||||
|
# Quick health pre-flight check (exit 0 = healthy, 19 = unhealthy)
|
||||||
|
lore --robot health
|
||||||
|
|
||||||
|
# Generate searchable documents from ingested data
|
||||||
|
lore --robot generate-docs
|
||||||
|
|
||||||
|
# Generate vector embeddings via Ollama
|
||||||
|
lore --robot embed
|
||||||
|
|
||||||
|
# Personal work dashboard
|
||||||
|
lore --robot me
|
||||||
|
lore --robot me --issues
|
||||||
|
lore --robot me --mrs
|
||||||
|
lore --robot me --activity --since 7d
|
||||||
|
lore --robot me --project group/repo
|
||||||
|
lore --robot me --user jdoe
|
||||||
|
lore --robot me --fields minimal
|
||||||
|
lore --robot me --reset-cursor
|
||||||
|
|
||||||
|
# Agent self-discovery manifest (all commands, flags, exit codes, response schemas)
|
||||||
|
lore robot-docs
|
||||||
|
|
||||||
|
# Version information
|
||||||
|
lore --robot version
|
||||||
```
|
```
|
||||||
|
|
||||||
### Response Format
|
### Response Format
|
||||||
|
|
||||||
All commands return consistent JSON:
|
All commands return compact JSON with a uniform envelope and timing metadata:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"ok":true,"data":{...},"meta":{...}}
|
{"ok":true,"data":{...},"meta":{"elapsed_ms":42}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Errors return structured JSON to stderr:
|
Errors return structured JSON to stderr with machine-actionable recovery steps:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{"error":{"code":"CONFIG_NOT_FOUND","message":"...","suggestion":"Run 'lore init'"}}
|
{"error":{"code":"CONFIG_NOT_FOUND","message":"...","suggestion":"Run 'lore init'","actions":["lore init"]}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `actions` array contains executable shell commands for automated recovery. It is omitted when empty.
|
||||||
|
|
||||||
|
### Field Selection
|
||||||
|
|
||||||
|
The `--fields` flag on `issues` and `mrs` list commands controls which fields appear in the JSON response:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J issues --fields minimal # Preset: iid, title, state, updated_at_iso
|
||||||
|
lore -J mrs --fields iid,title,state,draft,labels # Custom field list
|
||||||
```
|
```
|
||||||
|
|
||||||
### Exit Codes
|
### Exit Codes
|
||||||
@@ -86,8 +670,8 @@ Errors return structured JSON to stderr:
|
|||||||
| Code | Meaning |
|
| Code | Meaning |
|
||||||
|------|---------|
|
|------|---------|
|
||||||
| 0 | Success |
|
| 0 | Success |
|
||||||
| 1 | Internal error |
|
| 1 | Internal error / not implemented |
|
||||||
| 2 | Config not found |
|
| 2 | Usage error (invalid flags or arguments) |
|
||||||
| 3 | Config invalid |
|
| 3 | Config invalid |
|
||||||
| 4 | Token not set |
|
| 4 | Token not set |
|
||||||
| 5 | GitLab auth failed |
|
| 5 | GitLab auth failed |
|
||||||
@@ -99,11 +683,98 @@ Errors return structured JSON to stderr:
|
|||||||
| 11 | Migration failed |
|
| 11 | Migration failed |
|
||||||
| 12 | I/O error |
|
| 12 | I/O error |
|
||||||
| 13 | Transform error |
|
| 13 | Transform error |
|
||||||
|
| 14 | Ollama unavailable |
|
||||||
|
| 15 | Ollama model not found |
|
||||||
|
| 16 | Embedding failed |
|
||||||
|
| 17 | Not found (entity does not exist) |
|
||||||
|
| 18 | Ambiguous match (use `-p` to specify project) |
|
||||||
|
| 19 | Health check failed |
|
||||||
|
| 20 | Config not found |
|
||||||
|
|
||||||
|
### Configuration Precedence
|
||||||
|
|
||||||
|
1. CLI flags (highest priority)
|
||||||
|
2. Environment variables (`LORE_ROBOT`, `GITLAB_TOKEN`, `LORE_CONFIG_PATH`)
|
||||||
|
3. Config file (`~/.config/lore/config.json`)
|
||||||
|
4. Built-in defaults (lowest priority)
|
||||||
|
|
||||||
### Best Practices
|
### Best Practices
|
||||||
|
|
||||||
- Use `lore --robot` for all agent interactions
|
- Use `lore --robot` or `lore -J` for all agent interactions
|
||||||
- Check exit codes for error handling
|
- Check exit codes for error handling
|
||||||
- Parse JSON errors from stderr
|
- Parse JSON errors from stderr; use `actions` array for automated recovery
|
||||||
- Use `--limit` to control response size
|
- Use `--fields minimal` to reduce token usage (~60% fewer tokens)
|
||||||
|
- Use `-n` / `--limit` to control response size
|
||||||
|
- Use `-q` / `--quiet` to suppress progress bars and non-essential output
|
||||||
|
- Use `--color never` in non-TTY automation for ANSI-free output
|
||||||
|
- Use `-v` / `-vv` / `-vvv` for increasing verbosity (debug/trace logging)
|
||||||
|
- Use `--log-format json` for machine-readable log output to stderr
|
||||||
- TTY detection handles piped commands automatically
|
- TTY detection handles piped commands automatically
|
||||||
|
- Use `lore --robot health` as a fast pre-flight check before queries
|
||||||
|
- Use `lore robot-docs` for response schema discovery
|
||||||
|
- The `-p` flag supports fuzzy project matching (suffix and substring)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Read/Write Split: lore vs glab
|
||||||
|
|
||||||
|
| Operation | Tool | Why |
|
||||||
|
|-----------|------|-----|
|
||||||
|
| List issues/MRs | lore | Richer: includes status, discussions, closing MRs |
|
||||||
|
| View issue/MR detail | lore | Pre-joined discussions, work-item status |
|
||||||
|
| Search across entities | lore | FTS5 + vector hybrid search |
|
||||||
|
| Expert/workload analysis | lore | who command — no glab equivalent |
|
||||||
|
| Timeline reconstruction | lore | Chronological narrative — no glab equivalent |
|
||||||
|
| Create/update/close | glab | Write operations |
|
||||||
|
| Approve/merge MR | glab | Write operations |
|
||||||
|
| CI/CD pipelines | glab | Not in lore scope |
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
## UBS Quick Reference for AI Agents
|
||||||
|
|
||||||
|
UBS stands for "Ultimate Bug Scanner": **The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On**
|
||||||
|
|
||||||
|
**Install:** `curl -sSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/master/install.sh | bash`
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
```bash
|
||||||
|
ubs file.ts file2.py # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(git diff --name-only --cached) # Staged files — before commit
|
||||||
|
ubs --only=js,python src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs --help # Full command reference
|
||||||
|
ubs sessions --entries 1 # Tail the latest install session log
|
||||||
|
ubs . # Whole project (ignores things like .venv and node_modules automatically)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.ts:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
**Fix Workflow:**
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
**Speed Critical:** Scope to changed files. `ubs src/file.ts` (< 1s) vs `ubs .` (30s). Never full scan for small edits.
|
||||||
|
|
||||||
|
**Bug Severity:**
|
||||||
|
- **Critical** (always fix): Null safety, XSS/injection, async/await, memory leaks
|
||||||
|
- **Important** (production): Type narrowing, division-by-zero, resource leaks
|
||||||
|
- **Contextual** (judgment): TODO/FIXME, console logs
|
||||||
|
|
||||||
|
**Anti-Patterns:**
|
||||||
|
- ❌ Ignore findings → ✅ Investigate each
|
||||||
|
- ❌ Full scan per edit → ✅ Scope to file
|
||||||
|
- ❌ Fix symptom (`if (x) { x.y }`) → ✅ Root cause (`x?.y`)
|
||||||
|
````
|
||||||
|
|||||||
742
AGENTS.md.backup
Normal file
742
AGENTS.md.backup
Normal file
@@ -0,0 +1,742 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## RULE 0 - THE FUNDAMENTAL OVERRIDE PEROGATIVE
|
||||||
|
|
||||||
|
If I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RULE NUMBER 1: NO FILE DELETION
|
||||||
|
|
||||||
|
**YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION.** Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.
|
||||||
|
|
||||||
|
**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS
|
||||||
|
|
||||||
|
> **Note:** Treat destructive commands as break-glass. If there's any doubt, stop and ask.
|
||||||
|
|
||||||
|
1. **Absolutely forbidden commands:** `git reset --hard`, `git clean -fd`, `rm -rf`, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences.
|
||||||
|
2. **No guessing:** If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. "I think it's safe" is never acceptable.
|
||||||
|
3. **Safer alternatives first:** When cleanup or rollbacks are needed, request permission to use non-destructive options (`git status`, `git diff`, `git stash`, copying to backups) before ever considering a destructive command.
|
||||||
|
4. **Mandatory explicit plan:** Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.
|
||||||
|
5. **Document the confirmation:** When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Toolchain: Rust & Cargo
|
||||||
|
|
||||||
|
We only use **Cargo** in this project, NEVER any other package manager.
|
||||||
|
|
||||||
|
- **Edition/toolchain:** Follow `rust-toolchain.toml` (if present). Do not assume stable vs nightly.
|
||||||
|
- **Dependencies:** Explicit versions for stability; keep the set minimal.
|
||||||
|
- **Configuration:** Cargo.toml only
|
||||||
|
- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)
|
||||||
|
|
||||||
|
When writing Rust code, reference RUST_CLI_TOOLS_BEST_PRACTICES.md
|
||||||
|
|
||||||
|
### Release Profile
|
||||||
|
|
||||||
|
Use the release profile defined in `Cargo.toml`. If you need to change it, justify the
|
||||||
|
performance/size tradeoff and how it impacts determinism and cancellation behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Editing Discipline
|
||||||
|
|
||||||
|
### No Script-Based Changes
|
||||||
|
|
||||||
|
**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.
|
||||||
|
|
||||||
|
- **Always make code changes manually**, even when there are many instances
|
||||||
|
- For many simple changes: use parallel subagents
|
||||||
|
- For subtle/complex changes: do them methodically yourself
|
||||||
|
|
||||||
|
### No File Proliferation
|
||||||
|
|
||||||
|
If you want to change something or add a feature, **revise existing code files in place**.
|
||||||
|
|
||||||
|
**NEVER** create variations like:
|
||||||
|
- `mainV2.rs`
|
||||||
|
- `main_improved.rs`
|
||||||
|
- `main_enhanced.rs`
|
||||||
|
|
||||||
|
New files are reserved for **genuinely new functionality** that makes zero sense to include in any existing file. The bar for creating new files is **incredibly high**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backwards Compatibility
|
||||||
|
|
||||||
|
We do not care about backwards compatibility—we're in early development with no users. We want to do things the **RIGHT** way with **NO TECH DEBT**.
|
||||||
|
|
||||||
|
- Never create "compatibility shims"
|
||||||
|
- Never create wrapper functions for deprecated APIs
|
||||||
|
- Just fix the code directly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Compiler Checks (CRITICAL)
|
||||||
|
|
||||||
|
**After any substantive code changes, you MUST verify no errors were introduced:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for compiler errors and warnings
|
||||||
|
cargo check --all-targets
|
||||||
|
|
||||||
|
# Check for clippy lints (pedantic + nursery are enabled)
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
|
# Verify formatting
|
||||||
|
cargo fmt --check
|
||||||
|
```
|
||||||
|
|
||||||
|
If you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit & Property Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Run with output
|
||||||
|
cargo test -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
When adding or changing primitives, add tests that assert the core invariants:
|
||||||
|
|
||||||
|
- no task leaks
|
||||||
|
- no obligation leaks
|
||||||
|
- losers are drained after races
|
||||||
|
- region close implies quiescence
|
||||||
|
|
||||||
|
Prefer deterministic lab-runtime tests for concurrency-sensitive behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MCP Agent Mail — Multi-Agent Coordination
|
||||||
|
|
||||||
|
A mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources. Provides identities, inbox/outbox, searchable threads, and advisory file reservations with human-auditable artifacts in Git.
|
||||||
|
|
||||||
|
### Why It's Useful
|
||||||
|
|
||||||
|
- **Prevents conflicts:** Explicit file reservations (leases) for files/globs
|
||||||
|
- **Token-efficient:** Messages stored in per-project archive, not in context
|
||||||
|
- **Quick reads:** `resource://inbox/...`, `resource://thread/...`
|
||||||
|
|
||||||
|
### Same Repository Workflow
|
||||||
|
|
||||||
|
1. **Register identity:**
|
||||||
|
```
|
||||||
|
ensure_project(project_key=<abs-path>)
|
||||||
|
register_agent(project_key, program, model)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Reserve files before editing:**
|
||||||
|
```
|
||||||
|
file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Communicate with threads:**
|
||||||
|
```
|
||||||
|
send_message(..., thread_id="FEAT-123")
|
||||||
|
fetch_inbox(project_key, agent_name)
|
||||||
|
acknowledge_message(project_key, agent_name, message_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Quick reads:**
|
||||||
|
```
|
||||||
|
resource://inbox/{Agent}?project=<abs-path>&limit=20
|
||||||
|
resource://thread/{id}?project=<abs-path>&include_bodies=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Macros vs Granular Tools
|
||||||
|
|
||||||
|
- **Prefer macros for speed:** `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`
|
||||||
|
- **Use granular tools for control:** `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`
|
||||||
|
|
||||||
|
### Common Pitfalls
|
||||||
|
|
||||||
|
- `"from_agent not registered"`: Always `register_agent` in the correct `project_key` first
|
||||||
|
- `"FILE_RESERVATION_CONFLICT"`: Adjust patterns, wait for expiry, or use non-exclusive reservation
|
||||||
|
- **Auth errors:** If JWT+JWKS enabled, include bearer token with matching `kid`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beads (br) — Dependency-Aware Issue Tracking
|
||||||
|
|
||||||
|
Beads provides a lightweight, dependency-aware issue database and CLI (`br` / beads_rust) for selecting "ready work," setting priorities, and tracking status. It complements MCP Agent Mail's messaging and file reservations.
|
||||||
|
|
||||||
|
**Note:** `br` is non-invasive—it never executes git commands directly. You must run git commands manually after `br sync --flush-only`.
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
- **Single source of truth:** Beads for task status/priority/dependencies; Agent Mail for conversation and audit
|
||||||
|
- **Shared identifiers:** Use Beads issue ID (e.g., `br-123`) as Mail `thread_id` and prefix subjects with `[br-123]`
|
||||||
|
- **Reservations:** When starting a task, call `file_reservation_paths()` with the issue ID in `reason`
|
||||||
|
|
||||||
|
### Typical Agent Flow
|
||||||
|
|
||||||
|
1. **Pick ready work (Beads):**
|
||||||
|
```bash
|
||||||
|
br ready --json # Choose highest priority, no blockers
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Reserve edit surface (Mail):**
|
||||||
|
```
|
||||||
|
file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true, reason="br-123")
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Announce start (Mail):**
|
||||||
|
```
|
||||||
|
send_message(..., thread_id="br-123", subject="[br-123] Start: <title>", ack_required=true)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Work and update:** Reply in-thread with progress
|
||||||
|
|
||||||
|
5. **Complete and release:**
|
||||||
|
```bash
|
||||||
|
br close br-123 --reason "Completed"
|
||||||
|
```
|
||||||
|
```
|
||||||
|
release_file_reservations(project_key, agent_name, paths=["src/**"])
|
||||||
|
```
|
||||||
|
Final Mail reply: `[br-123] Completed` with summary
|
||||||
|
|
||||||
|
### Mapping Cheat Sheet
|
||||||
|
|
||||||
|
| Concept | Value |
|
||||||
|
|---------|-------|
|
||||||
|
| Mail `thread_id` | `br-###` |
|
||||||
|
| Mail subject | `[br-###] ...` |
|
||||||
|
| File reservation `reason` | `br-###` |
|
||||||
|
| Commit messages | Include `br-###` for traceability |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## bv — Graph-Aware Triage Engine
|
||||||
|
|
||||||
|
bv is a graph-aware triage engine for Beads projects (`.beads/beads.jsonl`). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.
|
||||||
|
|
||||||
|
**Scope boundary:** bv handles *what to work on* (triage, priority, planning). For agent-to-agent coordination (messaging, work claiming, file reservations), use MCP Agent Mail.
|
||||||
|
|
||||||
|
**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**
|
||||||
|
|
||||||
|
### The Workflow: Start With Triage
|
||||||
|
|
||||||
|
**`bv --robot-triage` is your single entry point.** It returns:
|
||||||
|
- `quick_ref`: at-a-glance counts + top 3 picks
|
||||||
|
- `recommendations`: ranked actionable items with scores, reasons, unblock info
|
||||||
|
- `quick_wins`: low-effort high-impact items
|
||||||
|
- `blockers_to_clear`: items that unblock the most downstream work
|
||||||
|
- `project_health`: status/type/priority distributions, graph metrics
|
||||||
|
- `commands`: copy-paste shell commands for next steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-triage # THE MEGA-COMMAND: start here
|
||||||
|
bv --robot-next # Minimal: just the single top pick + claim command
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Reference
|
||||||
|
|
||||||
|
**Planning:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-plan` | Parallel execution tracks with `unblocks` lists |
|
||||||
|
| `--robot-priority` | Priority misalignment detection with confidence |
|
||||||
|
|
||||||
|
**Graph Analysis:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |
|
||||||
|
| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |
|
||||||
|
| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |
|
||||||
|
| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |
|
||||||
|
|
||||||
|
**History & Change Tracking:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-history` | Bead-to-commit correlations |
|
||||||
|
| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |
|
||||||
|
| `--robot-forecast <id\|all>` | ETA predictions with dependency-aware scheduling |
|
||||||
|
| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |
|
||||||
|
| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |
|
||||||
|
| `--robot-graph [--graph-format=json\|dot\|mermaid]` | Dependency graph export |
|
||||||
|
| `--export-graph <file.html>` | Interactive HTML visualization |
|
||||||
|
|
||||||
|
### Scoping & Filtering
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-plan --label backend # Scope to label's subgraph
|
||||||
|
bv --robot-insights --as-of HEAD~30 # Historical point-in-time
|
||||||
|
bv --recipe actionable --robot-plan # Pre-filter: ready to work
|
||||||
|
bv --recipe high-impact --robot-triage # Pre-filter: top PageRank
|
||||||
|
bv --robot-triage --robot-triage-by-track # Group by parallel work streams
|
||||||
|
bv --robot-triage --robot-triage-by-label # Group by domain
|
||||||
|
```
|
||||||
|
|
||||||
|
### Understanding Robot Output
|
||||||
|
|
||||||
|
**All robot JSON includes:**
|
||||||
|
- `data_hash` — Fingerprint of source beads.jsonl
|
||||||
|
- `status` — Per-metric state: `computed|approx|timeout|skipped` + elapsed ms
|
||||||
|
- `as_of` / `as_of_commit` — Present when using `--as-of`
|
||||||
|
|
||||||
|
**Two-phase analysis:**
|
||||||
|
- **Phase 1 (instant):** degree, topo sort, density
|
||||||
|
- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles
|
||||||
|
|
||||||
|
### jq Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-triage | jq '.quick_ref' # At-a-glance summary
|
||||||
|
bv --robot-triage | jq '.recommendations[0]' # Top recommendation
|
||||||
|
bv --robot-plan | jq '.plan.summary.highest_impact' # Best unblock target
|
||||||
|
bv --robot-insights | jq '.status' # Check metric readiness
|
||||||
|
bv --robot-insights | jq '.Cycles' # Circular deps (must fix!)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UBS — Ultimate Bug Scanner
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ubs file.rs file2.rs # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(git diff --name-only --cached) # Staged files — before commit
|
||||||
|
ubs --only=rust,toml src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs . # Whole project (ignores target/, Cargo.lock)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.rs:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
### Fix Workflow
|
||||||
|
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
### Bug Severity
|
||||||
|
|
||||||
|
- **Critical (always fix):** Memory safety, use-after-free, data races, SQL injection
|
||||||
|
- **Important (production):** Unwrap panics, resource leaks, overflow checks
|
||||||
|
- **Contextual (judgment):** TODO/FIXME, println! debugging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ast-grep vs ripgrep
|
||||||
|
|
||||||
|
**Use `ast-grep` when structure matters.** It parses code and matches AST nodes, ignoring comments/strings, and can **safely rewrite** code.
|
||||||
|
|
||||||
|
- Refactors/codemods: rename APIs, change import forms
|
||||||
|
- Policy checks: enforce patterns across a repo
|
||||||
|
- Editor/automation: LSP mode, `--json` output
|
||||||
|
|
||||||
|
**Use `ripgrep` when text is enough.** Fastest way to grep literals/regex.
|
||||||
|
|
||||||
|
- Recon: find strings, TODOs, log lines, config values
|
||||||
|
- Pre-filter: narrow candidate files before ast-grep
|
||||||
|
|
||||||
|
### Rule of Thumb
|
||||||
|
|
||||||
|
- Need correctness or **applying changes** → `ast-grep`
|
||||||
|
- Need raw speed or **hunting text** → `rg`
|
||||||
|
- Often combine: `rg` to shortlist files, then `ast-grep` to match/modify
|
||||||
|
|
||||||
|
### Rust Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find structured code (ignores comments)
|
||||||
|
ast-grep run -l Rust -p 'fn $NAME($$$ARGS) -> $RET { $$$BODY }'
|
||||||
|
|
||||||
|
# Find all unwrap() calls
|
||||||
|
ast-grep run -l Rust -p '$EXPR.unwrap()'
|
||||||
|
|
||||||
|
# Quick textual hunt
|
||||||
|
rg -n 'println!' -t rust
|
||||||
|
|
||||||
|
# Combine speed + precision
|
||||||
|
rg -l -t rust 'unwrap\(' | xargs ast-grep run -l Rust -p '$X.unwrap()' --json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Morph Warp Grep — AI-Powered Code Search
|
||||||
|
|
||||||
|
**Use `mcp__morph-mcp__warp_grep` for exploratory "how does X work?" questions.** An AI agent expands your query, greps the codebase, reads relevant files, and returns precise line ranges with full context.
|
||||||
|
|
||||||
|
**Use `ripgrep` for targeted searches.** When you know exactly what you're looking for.
|
||||||
|
|
||||||
|
**Use `ast-grep` for structural patterns.** When you need AST precision for matching/rewriting.
|
||||||
|
|
||||||
|
### When to Use What
|
||||||
|
|
||||||
|
| Scenario | Tool | Why |
|
||||||
|
|----------|------|-----|
|
||||||
|
| "How is pattern matching implemented?" | `warp_grep` | Exploratory; don't know where to start |
|
||||||
|
| "Where is the quick reject filter?" | `warp_grep` | Need to understand architecture |
|
||||||
|
| "Find all uses of `Regex::new`" | `ripgrep` | Targeted literal search |
|
||||||
|
| "Find files with `println!`" | `ripgrep` | Simple pattern |
|
||||||
|
| "Replace all `unwrap()` with `expect()`" | `ast-grep` | Structural refactor |
|
||||||
|
|
||||||
|
### warp_grep Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp__morph-mcp__warp_grep(
|
||||||
|
repoPath: "/path/to/dcg",
|
||||||
|
query: "How does the safe pattern whitelist work?"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns structured results with file paths, line ranges, and extracted code snippets.
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- **Don't** use `warp_grep` to find a specific function name → use `ripgrep`
|
||||||
|
- **Don't** use `ripgrep` to understand "how does X work" → wastes time with manual reads
|
||||||
|
- **Don't** use `ripgrep` for codemods → risks collateral edits
|
||||||
|
|
||||||
|
<!-- bv-agent-instructions-v1 -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beads Workflow Integration
|
||||||
|
|
||||||
|
This project uses [beads_viewer](https://github.com/Dicklesworthstone/beads_viewer) for issue tracking. Issues are stored in `.beads/` and tracked in git.
|
||||||
|
|
||||||
|
**Note:** `br` is non-invasive—it never executes git commands directly. You must run git commands manually after `br sync --flush-only`.
|
||||||
|
|
||||||
|
### Essential Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View issues (launches TUI - avoid in automated sessions)
|
||||||
|
bv
|
||||||
|
|
||||||
|
# CLI commands for agents (use these instead)
|
||||||
|
br ready # Show issues ready to work (no blockers)
|
||||||
|
br list --status=open # All open issues
|
||||||
|
br show <id> # Full issue details with dependencies
|
||||||
|
br create --title="..." --type=task --priority=2
|
||||||
|
br update <id> --status=in_progress
|
||||||
|
br close <id> --reason="Completed"
|
||||||
|
br close <id1> <id2> # Close multiple issues at once
|
||||||
|
br sync --flush-only # Export to JSONL (then manually: git add .beads/ && git commit)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow Pattern
|
||||||
|
|
||||||
|
1. **Start**: Run `br ready` to find actionable work
|
||||||
|
2. **Claim**: Use `br update <id> --status=in_progress`
|
||||||
|
3. **Work**: Implement the task
|
||||||
|
4. **Complete**: Use `br close <id>`
|
||||||
|
5. **Sync**: Run `br sync --flush-only`, then `git add .beads/ && git commit -m "Update beads"`
|
||||||
|
|
||||||
|
### Key Concepts
|
||||||
|
|
||||||
|
- **Dependencies**: Issues can block other issues. `br ready` shows only unblocked work.
|
||||||
|
- **Priority**: P0=critical, P1=high, P2=medium, P3=low, P4=backlog (use numbers, not words)
|
||||||
|
- **Types**: task, bug, feature, epic, question, docs
|
||||||
|
- **Blocking**: `br dep add <issue> <depends-on>` to add dependencies
|
||||||
|
|
||||||
|
### Session Protocol
|
||||||
|
|
||||||
|
**Before ending any session, run this checklist:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status # Check what changed
|
||||||
|
git add <files> # Stage code changes
|
||||||
|
br sync --flush-only # Export beads to JSONL
|
||||||
|
git add .beads/ # Stage beads changes
|
||||||
|
git commit -m "..." # Commit code and beads
|
||||||
|
git push # Push to remote
|
||||||
|
```
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- Check `br ready` at session start to find available work
|
||||||
|
- Update status as you work (in_progress → closed)
|
||||||
|
- Create new issues with `br create` when you discover tasks
|
||||||
|
- Use descriptive titles and set appropriate priority/type
|
||||||
|
- Always run `br sync --flush-only` then commit .beads/ before ending session
|
||||||
|
|
||||||
|
<!-- end-bv-agent-instructions -->
|
||||||
|
|
||||||
|
## Landing the Plane (Session Completion)
|
||||||
|
|
||||||
|
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until `git push` succeeds.
|
||||||
|
|
||||||
|
**MANDATORY WORKFLOW:**
|
||||||
|
|
||||||
|
1. **File issues for remaining work** - Create issues for anything that needs follow-up
|
||||||
|
2. **Run quality gates** (if code changed) - Tests, linters, builds
|
||||||
|
3. **Update issue status** - Close finished work, update in-progress items
|
||||||
|
4. **PUSH TO REMOTE** - This is MANDATORY:
|
||||||
|
```bash
|
||||||
|
git pull --rebase
|
||||||
|
br sync --flush-only
|
||||||
|
git add .beads/
|
||||||
|
git commit -m "Update beads"
|
||||||
|
git push
|
||||||
|
git status # MUST show "up to date with origin"
|
||||||
|
```
|
||||||
|
5. **Clean up** - Clear stashes, prune remote branches
|
||||||
|
6. **Verify** - All changes committed AND pushed
|
||||||
|
7. **Hand off** - Provide context for next session
|
||||||
|
|
||||||
|
**CRITICAL RULES:**
|
||||||
|
- Work is NOT complete until `git push` succeeds
|
||||||
|
- NEVER stop before pushing - that leaves work stranded locally
|
||||||
|
- NEVER say "ready to push when you are" - YOU must push
|
||||||
|
- If push fails, resolve and retry until it succeeds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## cass — Cross-Agent Session Search
|
||||||
|
|
||||||
|
`cass` indexes prior agent conversations (Claude Code, Codex, Cursor, Gemini, ChatGPT, etc.) so we can reuse solved problems.
|
||||||
|
|
||||||
|
**Rules:** Never run bare `cass` (TUI). Always use `--robot` or `--json`.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cass health
|
||||||
|
cass search "async runtime" --robot --limit 5
|
||||||
|
cass view /path/to/session.jsonl -n 42 --json
|
||||||
|
cass expand /path/to/session.jsonl -n 42 -C 3 --json
|
||||||
|
cass capabilities --json
|
||||||
|
cass robot-docs guide
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tips
|
||||||
|
|
||||||
|
- Use `--fields minimal` for lean output
|
||||||
|
- Filter by agent with `--agent`
|
||||||
|
- Use `--days N` to limit to recent history
|
||||||
|
|
||||||
|
stdout is data-only, stderr is diagnostics; exit code 0 means success.
|
||||||
|
|
||||||
|
Treat cass as a way to avoid re-solving problems other agents already handled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note for Codex/GPT-5.2
|
||||||
|
|
||||||
|
You constantly bother me and stop working with concerned questions that look similar to this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Unexpected changes (need guidance)
|
||||||
|
|
||||||
|
- Working tree still shows edits I did not make in Cargo.toml, Cargo.lock, src/runtime.rs, src/scope.rs. Please advise whether to keep/commit/revert these before any further work. I did not touch them.
|
||||||
|
|
||||||
|
Next steps (pick one)
|
||||||
|
|
||||||
|
1. Decide how to handle the unrelated modified files above so we can resume cleanly.
|
||||||
|
```
|
||||||
|
|
||||||
|
NEVER EVER DO THAT AGAIN. The answer is literally ALWAYS the same: those are changes created by the potentially dozen of other agents working on the project at the same time. This is not only a common occurence, it happens multiple times PER MINUTE. The way to deal with it is simple: you NEVER, under ANY CIRCUMSTANCE, stash, revert, overwrite, or otherwise disturb in ANY way the work of other agents. Just treat those changes identically to changes that you yourself made. Just fool yourself into thinking YOU made the changes and simply don't recall it for some reason.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note on Built-in TODO Functionality
|
||||||
|
|
||||||
|
Also, if I ask you to explicitly use your built-in TODO functionality, don't complain about this and say you need to use beads. You can use built-in TODOs if I tell you specifically to do so. Always comply with such orders.
|
||||||
|
|
||||||
|
## TDD Requirements
|
||||||
|
|
||||||
|
Test-first development is mandatory:
|
||||||
|
1. **RED** - Write failing test first
|
||||||
|
2. **GREEN** - Minimal implementation to pass
|
||||||
|
3. **REFACTOR** - Clean up while green
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
|
||||||
|
Find the simplest solution that meets all acceptance criteria.
|
||||||
|
Use third party libraries whenever there's a well-maintained, active, and widely adopted solution (for example, date-fns for TS date math)
|
||||||
|
Build extensible pieces of logic that can easily be integrated with other pieces.
|
||||||
|
DRY principles should be loosely held.
|
||||||
|
Architecture MUST be clear and well thought-out. Ask the user for clarification whenever ambiguity is discovered around architecture, or you think a better approach than planned exists.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Third-Party Library Usage
|
||||||
|
|
||||||
|
If you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to find the latest documentation and mid-2025 best practices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gitlore Robot Mode
|
||||||
|
|
||||||
|
The `lore` CLI has a robot mode optimized for AI agent consumption with compact JSON output, structured errors with machine-actionable recovery steps, meaningful exit codes, response timing metadata, field selection for token efficiency, and TTY auto-detection.
|
||||||
|
|
||||||
|
### Activation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Explicit flag
|
||||||
|
lore --robot issues -n 10
|
||||||
|
|
||||||
|
# JSON shorthand (-J)
|
||||||
|
lore -J issues -n 10
|
||||||
|
|
||||||
|
# Auto-detection (when stdout is not a TTY)
|
||||||
|
lore issues | jq .
|
||||||
|
|
||||||
|
# Environment variable
|
||||||
|
LORE_ROBOT=1 lore issues
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Mode Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List issues/MRs with JSON output
|
||||||
|
lore --robot issues -n 10
|
||||||
|
lore --robot mrs -s opened
|
||||||
|
|
||||||
|
# List with field selection (reduces token usage ~60%)
|
||||||
|
lore --robot issues --fields minimal
|
||||||
|
lore --robot mrs --fields iid,title,state,draft
|
||||||
|
|
||||||
|
# Show detailed entity info
|
||||||
|
lore --robot issues 123
|
||||||
|
lore --robot mrs 456 -p group/repo
|
||||||
|
|
||||||
|
# Count entities
|
||||||
|
lore --robot count issues
|
||||||
|
lore --robot count discussions --for mr
|
||||||
|
|
||||||
|
# Search indexed documents
|
||||||
|
lore --robot search "authentication bug"
|
||||||
|
|
||||||
|
# Check sync status
|
||||||
|
lore --robot status
|
||||||
|
|
||||||
|
# Run full sync pipeline
|
||||||
|
lore --robot sync
|
||||||
|
|
||||||
|
# Run sync without resource events
|
||||||
|
lore --robot sync --no-events
|
||||||
|
|
||||||
|
# Run ingestion only
|
||||||
|
lore --robot ingest issues
|
||||||
|
|
||||||
|
# Check environment health
|
||||||
|
lore --robot doctor
|
||||||
|
|
||||||
|
# Document and index statistics
|
||||||
|
lore --robot stats
|
||||||
|
|
||||||
|
# Quick health pre-flight check (exit 0 = healthy, 19 = unhealthy)
|
||||||
|
lore --robot health
|
||||||
|
|
||||||
|
# Generate searchable documents from ingested data
|
||||||
|
lore --robot generate-docs
|
||||||
|
|
||||||
|
# Generate vector embeddings via Ollama
|
||||||
|
lore --robot embed
|
||||||
|
|
||||||
|
# Agent self-discovery manifest (all commands, flags, exit codes, response schemas)
|
||||||
|
lore robot-docs
|
||||||
|
|
||||||
|
# Version information
|
||||||
|
lore --robot version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Format
|
||||||
|
|
||||||
|
All commands return compact JSON with a uniform envelope and timing metadata:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"ok":true,"data":{...},"meta":{"elapsed_ms":42}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Errors return structured JSON to stderr with machine-actionable recovery steps:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"error":{"code":"CONFIG_NOT_FOUND","message":"...","suggestion":"Run 'lore init'","actions":["lore init"]}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `actions` array contains executable shell commands for automated recovery. It is omitted when empty.
|
||||||
|
|
||||||
|
### Field Selection
|
||||||
|
|
||||||
|
The `--fields` flag on `issues` and `mrs` list commands controls which fields appear in the JSON response:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J issues --fields minimal # Preset: iid, title, state, updated_at_iso
|
||||||
|
lore -J mrs --fields iid,title,state,draft,labels # Custom field list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exit Codes
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| 0 | Success |
|
||||||
|
| 1 | Internal error / not implemented |
|
||||||
|
| 2 | Usage error (invalid flags or arguments) |
|
||||||
|
| 3 | Config invalid |
|
||||||
|
| 4 | Token not set |
|
||||||
|
| 5 | GitLab auth failed |
|
||||||
|
| 6 | Resource not found |
|
||||||
|
| 7 | Rate limited |
|
||||||
|
| 8 | Network error |
|
||||||
|
| 9 | Database locked |
|
||||||
|
| 10 | Database error |
|
||||||
|
| 11 | Migration failed |
|
||||||
|
| 12 | I/O error |
|
||||||
|
| 13 | Transform error |
|
||||||
|
| 14 | Ollama unavailable |
|
||||||
|
| 15 | Ollama model not found |
|
||||||
|
| 16 | Embedding failed |
|
||||||
|
| 17 | Not found (entity does not exist) |
|
||||||
|
| 18 | Ambiguous match (use `-p` to specify project) |
|
||||||
|
| 19 | Health check failed |
|
||||||
|
| 20 | Config not found |
|
||||||
|
|
||||||
|
### Configuration Precedence
|
||||||
|
|
||||||
|
1. CLI flags (highest priority)
|
||||||
|
2. Environment variables (`LORE_ROBOT`, `GITLAB_TOKEN`, `LORE_CONFIG_PATH`)
|
||||||
|
3. Config file (`~/.config/lore/config.json`)
|
||||||
|
4. Built-in defaults (lowest priority)
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- Use `lore --robot` or `lore -J` for all agent interactions
|
||||||
|
- Check exit codes for error handling
|
||||||
|
- Parse JSON errors from stderr; use `actions` array for automated recovery
|
||||||
|
- Use `--fields minimal` to reduce token usage (~60% fewer tokens)
|
||||||
|
- Use `-n` / `--limit` to control response size
|
||||||
|
- Use `-q` / `--quiet` to suppress progress bars and non-essential output
|
||||||
|
- Use `--color never` in non-TTY automation for ANSI-free output
|
||||||
|
- Use `-v` / `-vv` / `-vvv` for increasing verbosity (debug/trace logging)
|
||||||
|
- Use `--log-format json` for machine-readable log output to stderr
|
||||||
|
- TTY detection handles piped commands automatically
|
||||||
|
- Use `lore --robot health` as a fast pre-flight check before queries
|
||||||
|
- Use `lore robot-docs` for response schema discovery
|
||||||
|
- The `-p` flag supports fuzzy project matching (suffix and substring)
|
||||||
960
CLAUDE.md
Normal file
960
CLAUDE.md
Normal file
@@ -0,0 +1,960 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
## RULE 0 - THE FUNDAMENTAL OVERRIDE PEROGATIVE
|
||||||
|
|
||||||
|
If I tell you to do something, even if it goes against what follows below, YOU MUST LISTEN TO ME. I AM IN CHARGE, NOT YOU.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RULE NUMBER 1: NO FILE DELETION
|
||||||
|
|
||||||
|
**YOU ARE NEVER ALLOWED TO DELETE A FILE WITHOUT EXPRESS PERMISSION.** Even a new file that you yourself created, such as a test code file. You have a horrible track record of deleting critically important files or otherwise throwing away tons of expensive work. As a result, you have permanently lost any and all rights to determine that a file or folder should be deleted.
|
||||||
|
|
||||||
|
**YOU MUST ALWAYS ASK AND RECEIVE CLEAR, WRITTEN PERMISSION BEFORE EVER DELETING A FILE OR FOLDER OF ANY KIND.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Version Control: jj-First (CRITICAL)
|
||||||
|
|
||||||
|
**ALWAYS prefer jj (Jujutsu) over git for all VCS operations.** This is a colocated repo with both `.jj/` and `.git/`. When instructed to use git by anything — even later in this file — use the best jj replacement commands instead. Only fall back to raw `git` for things jj cannot do (hooks, LFS, submodules, `gh` CLI interop).
|
||||||
|
|
||||||
|
See `~/.claude/rules/jj-vcs/` for the full command reference, translation table, revsets, patterns, and recovery recipes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Irreversible Git & Filesystem Actions — DO NOT EVER BREAK GLASS
|
||||||
|
|
||||||
|
> **Note:** Treat destructive commands as break-glass. If there's any doubt, stop and ask.
|
||||||
|
|
||||||
|
1. **Absolutely forbidden commands:** `git reset --hard`, `git clean -fd`, `rm -rf`, or any command that can delete or overwrite code/data must never be run unless the user explicitly provides the exact command and states, in the same message, that they understand and want the irreversible consequences.
|
||||||
|
2. **No guessing:** If there is any uncertainty about what a command might delete or overwrite, stop immediately and ask the user for specific approval. "I think it's safe" is never acceptable.
|
||||||
|
3. **Safer alternatives first:** When cleanup or rollbacks are needed, request permission to use non-destructive options (`git status`, `git diff`, `git stash`, copying to backups) before ever considering a destructive command.
|
||||||
|
4. **Mandatory explicit plan:** Even after explicit user authorization, restate the command verbatim, list exactly what will be affected, and wait for a confirmation that your understanding is correct. Only then may you execute it—if anything remains ambiguous, refuse and escalate.
|
||||||
|
5. **Document the confirmation:** When running any approved destructive command, record (in the session notes / final response) the exact user text that authorized it, the command actually run, and the execution time. If that record is absent, the operation did not happen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Toolchain: Rust & Cargo
|
||||||
|
|
||||||
|
We only use **Cargo** in this project, NEVER any other package manager.
|
||||||
|
|
||||||
|
- **Edition/toolchain:** Follow `rust-toolchain.toml` (if present). Do not assume stable vs nightly.
|
||||||
|
- **Dependencies:** Explicit versions for stability; keep the set minimal.
|
||||||
|
- **Configuration:** Cargo.toml only
|
||||||
|
- **Unsafe code:** Forbidden (`#![forbid(unsafe_code)]`)
|
||||||
|
|
||||||
|
When writing Rust code, reference RUST_CLI_TOOLS_BEST_PRACTICES.md
|
||||||
|
|
||||||
|
### Release Profile
|
||||||
|
|
||||||
|
Use the release profile defined in `Cargo.toml`. If you need to change it, justify the
|
||||||
|
performance/size tradeoff and how it impacts determinism and cancellation behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Editing Discipline
|
||||||
|
|
||||||
|
### No Script-Based Changes
|
||||||
|
|
||||||
|
**NEVER** run a script that processes/changes code files in this repo. Brittle regex-based transformations create far more problems than they solve.
|
||||||
|
|
||||||
|
- **Always make code changes manually**, even when there are many instances
|
||||||
|
- For many simple changes: use parallel subagents
|
||||||
|
- For subtle/complex changes: do them methodically yourself
|
||||||
|
|
||||||
|
### No File Proliferation
|
||||||
|
|
||||||
|
If you want to change something or add a feature, **revise existing code files in place**.
|
||||||
|
|
||||||
|
**NEVER** create variations like:
|
||||||
|
- `mainV2.rs`
|
||||||
|
- `main_improved.rs`
|
||||||
|
- `main_enhanced.rs`
|
||||||
|
|
||||||
|
New files are reserved for **genuinely new functionality** that makes zero sense to include in any existing file. The bar for creating new files is **incredibly high**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Backwards Compatibility
|
||||||
|
|
||||||
|
We do not care about backwards compatibility—we're in early development with no users. We want to do things the **RIGHT** way with **NO TECH DEBT**.
|
||||||
|
|
||||||
|
- Never create "compatibility shims"
|
||||||
|
- Never create wrapper functions for deprecated APIs
|
||||||
|
- Just fix the code directly
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Compiler Checks (CRITICAL)
|
||||||
|
|
||||||
|
**After any substantive code changes, you MUST verify no errors were introduced:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check for compiler errors and warnings
|
||||||
|
cargo check --all-targets
|
||||||
|
|
||||||
|
# Check for clippy lints (pedantic + nursery are enabled)
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
|
||||||
|
# Verify formatting
|
||||||
|
cargo fmt --check
|
||||||
|
```
|
||||||
|
|
||||||
|
If you see errors, **carefully understand and resolve each issue**. Read sufficient context to fix them the RIGHT way.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit & Property Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run all tests
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Run with output
|
||||||
|
cargo test -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
When adding or changing primitives, add tests that assert the core invariants:
|
||||||
|
|
||||||
|
- no task leaks
|
||||||
|
- no obligation leaks
|
||||||
|
- losers are drained after races
|
||||||
|
- region close implies quiescence
|
||||||
|
|
||||||
|
Prefer deterministic lab-runtime tests for concurrency-sensitive behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beads (br) — Dependency-Aware Issue Tracking
|
||||||
|
|
||||||
|
Beads provides a lightweight, dependency-aware issue database and CLI (`br` / beads_rust) for selecting "ready work," setting priorities, and tracking status. It complements Liquid Mail's shared log for progress, decisions, and cross-session context.
|
||||||
|
|
||||||
|
**Note:** `br` is non-invasive—it never executes git commands directly. You must run git commands manually after `br sync --flush-only`.
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
- **Single source of truth:** Beads for task status/priority/dependencies; Liquid Mail for conversation/decisions
|
||||||
|
- **Shared identifiers:** Include the Beads issue ID in posts (e.g., `[br-123] Topic validation rules`)
|
||||||
|
- **Decisions before action:** Post `DECISION:` messages before risky changes, not after
|
||||||
|
|
||||||
|
### Typical Agent Flow
|
||||||
|
|
||||||
|
1. **Pick ready work (Beads):**
|
||||||
|
```bash
|
||||||
|
br ready --json # Choose highest priority, no blockers
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check context (Liquid Mail):**
|
||||||
|
```bash
|
||||||
|
liquid-mail notify # See what changed since last session
|
||||||
|
liquid-mail query "br-123" # Find prior discussion on this issue
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Work and log progress:**
|
||||||
|
```bash
|
||||||
|
liquid-mail post --topic <workstream> "[br-123] START: <description>"
|
||||||
|
liquid-mail post "[br-123] FINDING: <what you discovered>"
|
||||||
|
liquid-mail post --decision "[br-123] DECISION: <what you decided and why>"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Complete (Beads is authority):**
|
||||||
|
```bash
|
||||||
|
br close br-123 --reason "Completed"
|
||||||
|
liquid-mail post "[br-123] Completed: <summary with commit ref>"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mapping Cheat Sheet
|
||||||
|
|
||||||
|
| Concept | In Beads | In Liquid Mail |
|
||||||
|
|---------|----------|----------------|
|
||||||
|
| Work item | `br-###` (issue ID) | Include `[br-###]` in posts |
|
||||||
|
| Workstream | — | `--topic auth-system` |
|
||||||
|
| Subject prefix | — | `[br-###] ...` |
|
||||||
|
| Commit message | Include `br-###` | — |
|
||||||
|
| Status | `br update --status` | Post progress messages |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## bv — Graph-Aware Triage Engine
|
||||||
|
|
||||||
|
bv is a graph-aware triage engine for Beads projects (`.beads/beads.jsonl`). It computes PageRank, betweenness, critical path, cycles, HITS, eigenvector, and k-core metrics deterministically.
|
||||||
|
|
||||||
|
**Scope boundary:** bv handles *what to work on* (triage, priority, planning). For agent-to-agent coordination (progress logging, decisions, cross-session context), use Liquid Mail.
|
||||||
|
|
||||||
|
**CRITICAL: Use ONLY `--robot-*` flags. Bare `bv` launches an interactive TUI that blocks your session.**
|
||||||
|
|
||||||
|
### The Workflow: Start With Triage
|
||||||
|
|
||||||
|
**`bv --robot-triage` is your single entry point.** It returns:
|
||||||
|
- `quick_ref`: at-a-glance counts + top 3 picks
|
||||||
|
- `recommendations`: ranked actionable items with scores, reasons, unblock info
|
||||||
|
- `quick_wins`: low-effort high-impact items
|
||||||
|
- `blockers_to_clear`: items that unblock the most downstream work
|
||||||
|
- `project_health`: status/type/priority distributions, graph metrics
|
||||||
|
- `commands`: copy-paste shell commands for next steps
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-triage # THE MEGA-COMMAND: start here
|
||||||
|
bv --robot-next # Minimal: just the single top pick + claim command
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Reference
|
||||||
|
|
||||||
|
**Planning:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-plan` | Parallel execution tracks with `unblocks` lists |
|
||||||
|
| `--robot-priority` | Priority misalignment detection with confidence |
|
||||||
|
|
||||||
|
**Graph Analysis:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-insights` | Full metrics: PageRank, betweenness, HITS, eigenvector, critical path, cycles, k-core, articulation points, slack |
|
||||||
|
| `--robot-label-health` | Per-label health: `health_level`, `velocity_score`, `staleness`, `blocked_count` |
|
||||||
|
| `--robot-label-flow` | Cross-label dependency: `flow_matrix`, `dependencies`, `bottleneck_labels` |
|
||||||
|
| `--robot-label-attention [--attention-limit=N]` | Attention-ranked labels |
|
||||||
|
|
||||||
|
**History & Change Tracking:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-history` | Bead-to-commit correlations |
|
||||||
|
| `--robot-diff --diff-since <ref>` | Changes since ref: new/closed/modified issues, cycles |
|
||||||
|
|
||||||
|
**Other:**
|
||||||
|
| Command | Returns |
|
||||||
|
|---------|---------|
|
||||||
|
| `--robot-burndown <sprint>` | Sprint burndown, scope changes, at-risk items |
|
||||||
|
| `--robot-forecast <id\|all>` | ETA predictions with dependency-aware scheduling |
|
||||||
|
| `--robot-alerts` | Stale issues, blocking cascades, priority mismatches |
|
||||||
|
| `--robot-suggest` | Hygiene: duplicates, missing deps, label suggestions |
|
||||||
|
| `--robot-graph [--graph-format=json\|dot\|mermaid]` | Dependency graph export |
|
||||||
|
| `--export-graph <file.html>` | Interactive HTML visualization |
|
||||||
|
|
||||||
|
### Scoping & Filtering
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-plan --label backend # Scope to label's subgraph
|
||||||
|
bv --robot-insights --as-of HEAD~30 # Historical point-in-time
|
||||||
|
bv --recipe actionable --robot-plan # Pre-filter: ready to work
|
||||||
|
bv --recipe high-impact --robot-triage # Pre-filter: top PageRank
|
||||||
|
bv --robot-triage --robot-triage-by-track # Group by parallel work streams
|
||||||
|
bv --robot-triage --robot-triage-by-label # Group by domain
|
||||||
|
```
|
||||||
|
|
||||||
|
### Understanding Robot Output
|
||||||
|
|
||||||
|
**All robot JSON includes:**
|
||||||
|
- `data_hash` — Fingerprint of source beads.jsonl
|
||||||
|
- `status` — Per-metric state: `computed|approx|timeout|skipped` + elapsed ms
|
||||||
|
- `as_of` / `as_of_commit` — Present when using `--as-of`
|
||||||
|
|
||||||
|
**Two-phase analysis:**
|
||||||
|
- **Phase 1 (instant):** degree, topo sort, density
|
||||||
|
- **Phase 2 (async, 500ms timeout):** PageRank, betweenness, HITS, eigenvector, cycles
|
||||||
|
|
||||||
|
### jq Quick Reference
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bv --robot-triage | jq '.quick_ref' # At-a-glance summary
|
||||||
|
bv --robot-triage | jq '.recommendations[0]' # Top recommendation
|
||||||
|
bv --robot-plan | jq '.plan.summary.highest_impact' # Best unblock target
|
||||||
|
bv --robot-insights | jq '.status' # Check metric readiness
|
||||||
|
bv --robot-insights | jq '.Cycles' # Circular deps (must fix!)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UBS — Ultimate Bug Scanner
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ubs file.rs file2.rs # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(jj diff --name-only) # Changed files — before commit
|
||||||
|
ubs --only=rust,toml src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs . # Whole project (ignores target/, Cargo.lock)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.rs:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
### Fix Workflow
|
||||||
|
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
### Bug Severity
|
||||||
|
|
||||||
|
- **Critical (always fix):** Memory safety, use-after-free, data races, SQL injection
|
||||||
|
- **Important (production):** Unwrap panics, resource leaks, overflow checks
|
||||||
|
- **Contextual (judgment):** TODO/FIXME, println! debugging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ast-grep vs ripgrep
|
||||||
|
|
||||||
|
**Use `ast-grep` when structure matters.** It parses code and matches AST nodes, ignoring comments/strings, and can **safely rewrite** code.
|
||||||
|
|
||||||
|
- Refactors/codemods: rename APIs, change import forms
|
||||||
|
- Policy checks: enforce patterns across a repo
|
||||||
|
- Editor/automation: LSP mode, `--json` output
|
||||||
|
|
||||||
|
**Use `ripgrep` when text is enough.** Fastest way to grep literals/regex.
|
||||||
|
|
||||||
|
- Recon: find strings, TODOs, log lines, config values
|
||||||
|
- Pre-filter: narrow candidate files before ast-grep
|
||||||
|
|
||||||
|
### Rule of Thumb
|
||||||
|
|
||||||
|
- Need correctness or **applying changes** → `ast-grep`
|
||||||
|
- Need raw speed or **hunting text** → `rg`
|
||||||
|
- Often combine: `rg` to shortlist files, then `ast-grep` to match/modify
|
||||||
|
|
||||||
|
### Rust Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find structured code (ignores comments)
|
||||||
|
ast-grep run -l Rust -p 'fn $NAME($$$ARGS) -> $RET { $$$BODY }'
|
||||||
|
|
||||||
|
# Find all unwrap() calls
|
||||||
|
ast-grep run -l Rust -p '$EXPR.unwrap()'
|
||||||
|
|
||||||
|
# Quick textual hunt
|
||||||
|
rg -n 'println!' -t rust
|
||||||
|
|
||||||
|
# Combine speed + precision
|
||||||
|
rg -l -t rust 'unwrap\(' | xargs ast-grep run -l Rust -p '$X.unwrap()' --json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Morph Warp Grep — AI-Powered Code Search
|
||||||
|
|
||||||
|
**Use `mcp__morph-mcp__warp_grep` for exploratory "how does X work?" questions.** An AI agent expands your query, greps the codebase, reads relevant files, and returns precise line ranges with full context.
|
||||||
|
|
||||||
|
**Use `ripgrep` for targeted searches.** When you know exactly what you're looking for.
|
||||||
|
|
||||||
|
**Use `ast-grep` for structural patterns.** When you need AST precision for matching/rewriting.
|
||||||
|
|
||||||
|
### When to Use What
|
||||||
|
|
||||||
|
| Scenario | Tool | Why |
|
||||||
|
|----------|------|-----|
|
||||||
|
| "How is pattern matching implemented?" | `warp_grep` | Exploratory; don't know where to start |
|
||||||
|
| "Where is the quick reject filter?" | `warp_grep` | Need to understand architecture |
|
||||||
|
| "Find all uses of `Regex::new`" | `ripgrep` | Targeted literal search |
|
||||||
|
| "Find files with `println!`" | `ripgrep` | Simple pattern |
|
||||||
|
| "Replace all `unwrap()` with `expect()`" | `ast-grep` | Structural refactor |
|
||||||
|
|
||||||
|
### warp_grep Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp__morph-mcp__warp_grep(
|
||||||
|
repoPath: "/path/to/dcg",
|
||||||
|
query: "How does the safe pattern whitelist work?"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns structured results with file paths, line ranges, and extracted code snippets.
|
||||||
|
|
||||||
|
### Anti-Patterns
|
||||||
|
|
||||||
|
- **Don't** use `warp_grep` to find a specific function name → use `ripgrep`
|
||||||
|
- **Don't** use `ripgrep` to understand "how does X work" → wastes time with manual reads
|
||||||
|
- **Don't** use `ripgrep` for codemods → risks collateral edits
|
||||||
|
|
||||||
|
<!-- bv-agent-instructions-v1 -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beads Workflow Integration
|
||||||
|
|
||||||
|
This project uses [beads_viewer](https://github.com/Dicklesworthstone/beads_viewer) for issue tracking. Issues are stored in `.beads/` and tracked in version control.
|
||||||
|
|
||||||
|
**Note:** `br` is non-invasive—it never executes VCS commands directly. You must commit manually after `br sync --flush-only`.
|
||||||
|
|
||||||
|
### Essential Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View issues (launches TUI - avoid in automated sessions)
|
||||||
|
bv
|
||||||
|
|
||||||
|
# CLI commands for agents (use these instead)
|
||||||
|
br ready # Show issues ready to work (no blockers)
|
||||||
|
br list --status=open # All open issues
|
||||||
|
br show <id> # Full issue details with dependencies
|
||||||
|
br create --title="..." --type=task --priority=2
|
||||||
|
br update <id> --status=in_progress
|
||||||
|
br close <id> --reason="Completed"
|
||||||
|
br close <id1> <id2> # Close multiple issues at once
|
||||||
|
br sync --flush-only # Export to JSONL (then: jj commit -m "Update beads")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Workflow Pattern
|
||||||
|
|
||||||
|
1. **Start**: Run `br ready` to find actionable work
|
||||||
|
2. **Claim**: Use `br update <id> --status=in_progress`
|
||||||
|
3. **Work**: Implement the task
|
||||||
|
4. **Complete**: Use `br close <id>`
|
||||||
|
5. **Sync**: Run `br sync --flush-only`, then `git add .beads/ && git commit -m "Update beads"`
|
||||||
|
|
||||||
|
### Key Concepts
|
||||||
|
|
||||||
|
- **Dependencies**: Issues can block other issues. `br ready` shows only unblocked work.
|
||||||
|
- **Priority**: P0=critical, P1=high, P2=medium, P3=low, P4=backlog (use numbers, not words)
|
||||||
|
- **Types**: task, bug, feature, epic, question, docs
|
||||||
|
- **Blocking**: `br dep add <issue> <depends-on>` to add dependencies
|
||||||
|
|
||||||
|
### Session Protocol
|
||||||
|
|
||||||
|
**Before ending any session, run this checklist (solo/lead only — workers skip VCS):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
jj status # Check what changed
|
||||||
|
br sync --flush-only # Export beads to JSONL
|
||||||
|
jj commit -m "..." # Commit code and beads (jj auto-tracks all changes)
|
||||||
|
jj bookmark set <name> -r @- # Point bookmark at committed work
|
||||||
|
jj git push -b <name> # Push to remote
|
||||||
|
```
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- Check `br ready` at session start to find available work
|
||||||
|
- Update status as you work (in_progress → closed)
|
||||||
|
- Create new issues with `br create` when you discover tasks
|
||||||
|
- Use descriptive titles and set appropriate priority/type
|
||||||
|
- Always run `br sync --flush-only` then commit before ending session (jj auto-tracks .beads/)
|
||||||
|
|
||||||
|
<!-- end-bv-agent-instructions -->
|
||||||
|
|
||||||
|
## Landing the Plane (Session Completion)
|
||||||
|
|
||||||
|
**When ending a work session**, you MUST complete ALL steps below. Work is NOT complete until push succeeds.
|
||||||
|
|
||||||
|
**WHO RUNS THIS:** Solo agents run it themselves. In multi-agent sessions, ONLY the team lead runs this. Workers skip VCS entirely.
|
||||||
|
|
||||||
|
**MANDATORY WORKFLOW:**
|
||||||
|
|
||||||
|
1. **File issues for remaining work** - Create issues for anything that needs follow-up
|
||||||
|
2. **Run quality gates** (if code changed) - Tests, linters, builds
|
||||||
|
3. **Update issue status** - Close finished work, update in-progress items
|
||||||
|
4. **PUSH TO REMOTE** - This is MANDATORY:
|
||||||
|
```bash
|
||||||
|
jj git fetch # Get latest remote state
|
||||||
|
jj rebase -d trunk() # Rebase onto latest trunk if needed
|
||||||
|
br sync --flush-only # Export beads to JSONL
|
||||||
|
jj commit -m "Update beads" # Commit (jj auto-tracks .beads/ changes)
|
||||||
|
jj bookmark set <name> -r @- # Point bookmark at committed work
|
||||||
|
jj git push -b <name> # Push to remote
|
||||||
|
jj log -r '<name>' # Verify bookmark position
|
||||||
|
```
|
||||||
|
5. **Clean up** - Abandon empty orphan changes if any (`jj abandon <rev>`)
|
||||||
|
6. **Verify** - All changes committed AND pushed
|
||||||
|
7. **Hand off** - Provide context for next session
|
||||||
|
|
||||||
|
**CRITICAL RULES:**
|
||||||
|
- Work is NOT complete until `jj git push` succeeds
|
||||||
|
- NEVER stop before pushing - that leaves work stranded locally
|
||||||
|
- NEVER say "ready to push when you are" - YOU must push
|
||||||
|
- If push fails, resolve and retry until it succeeds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## cass — Cross-Agent Session Search
|
||||||
|
|
||||||
|
`cass` indexes prior agent conversations (Claude Code, Codex, Cursor, Gemini, ChatGPT, etc.) so we can reuse solved problems.
|
||||||
|
|
||||||
|
**Rules:** Never run bare `cass` (TUI). Always use `--robot` or `--json`.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cass health
|
||||||
|
cass search "async runtime" --robot --limit 5
|
||||||
|
cass view /path/to/session.jsonl -n 42 --json
|
||||||
|
cass expand /path/to/session.jsonl -n 42 -C 3 --json
|
||||||
|
cass capabilities --json
|
||||||
|
cass robot-docs guide
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tips
|
||||||
|
|
||||||
|
- Use `--fields minimal` for lean output
|
||||||
|
- Filter by agent with `--agent`
|
||||||
|
- Use `--days N` to limit to recent history
|
||||||
|
|
||||||
|
stdout is data-only, stderr is diagnostics; exit code 0 means success.
|
||||||
|
|
||||||
|
Treat cass as a way to avoid re-solving problems other agents already handled.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note for Codex/GPT-5.2
|
||||||
|
|
||||||
|
You constantly bother me and stop working with concerned questions that look similar to this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Unexpected changes (need guidance)
|
||||||
|
|
||||||
|
- Working tree still shows edits I did not make in Cargo.toml, Cargo.lock, src/runtime.rs, src/scope.rs. Please advise whether to keep/commit/revert these before any further work. I did not touch them.
|
||||||
|
|
||||||
|
Next steps (pick one)
|
||||||
|
|
||||||
|
1. Decide how to handle the unrelated modified files above so we can resume cleanly.
|
||||||
|
```
|
||||||
|
|
||||||
|
NEVER EVER DO THAT AGAIN. The answer is literally ALWAYS the same: those are changes created by the potentially dozen of other agents working on the project at the same time. This is not only a common occurence, it happens multiple times PER MINUTE. The way to deal with it is simple: you NEVER, under ANY CIRCUMSTANCE, stash, revert, overwrite, or otherwise disturb in ANY way the work of other agents. Just treat those changes identically to changes that you yourself made. Just fool yourself into thinking YOU made the changes and simply don't recall it for some reason.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Note on Built-in TODO Functionality
|
||||||
|
|
||||||
|
Also, if I ask you to explicitly use your built-in TODO functionality, don't complain about this and say you need to use beads. You can use built-in TODOs if I tell you specifically to do so. Always comply with such orders.
|
||||||
|
|
||||||
|
## TDD Requirements
|
||||||
|
|
||||||
|
Test-first development is mandatory:
|
||||||
|
1. **RED** - Write failing test first
|
||||||
|
2. **GREEN** - Minimal implementation to pass
|
||||||
|
3. **REFACTOR** - Clean up while green
|
||||||
|
|
||||||
|
## Key Patterns
|
||||||
|
|
||||||
|
Find the simplest solution that meets all acceptance criteria.
|
||||||
|
Use third party libraries whenever there's a well-maintained, active, and widely adopted solution (for example, date-fns for TS date math)
|
||||||
|
Build extensible pieces of logic that can easily be integrated with other pieces.
|
||||||
|
DRY principles should be loosely held.
|
||||||
|
Architecture MUST be clear and well thought-out. Ask the user for clarification whenever ambiguity is discovered around architecture, or you think a better approach than planned exists.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Third-Party Library Usage
|
||||||
|
|
||||||
|
If you aren't 100% sure how to use a third-party library, **SEARCH ONLINE** to find the latest documentation and mid-2025 best practices.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gitlore Robot Mode
|
||||||
|
|
||||||
|
The `lore` CLI has a robot mode optimized for AI agent consumption with compact JSON output, structured errors with machine-actionable recovery steps, meaningful exit codes, response timing metadata, field selection for token efficiency, and TTY auto-detection.
|
||||||
|
|
||||||
|
### Activation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Explicit flag
|
||||||
|
lore --robot issues -n 10
|
||||||
|
|
||||||
|
# JSON shorthand (-J)
|
||||||
|
lore -J issues -n 10
|
||||||
|
|
||||||
|
# Auto-detection (when stdout is not a TTY)
|
||||||
|
lore issues | jq .
|
||||||
|
|
||||||
|
# Environment variable
|
||||||
|
LORE_ROBOT=1 lore issues
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Mode Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List issues/MRs with JSON output
|
||||||
|
lore --robot issues -n 10
|
||||||
|
lore --robot mrs -s opened
|
||||||
|
|
||||||
|
# Filter issues by work item status (case-insensitive)
|
||||||
|
lore --robot issues --status "In progress"
|
||||||
|
|
||||||
|
# List with field selection (reduces token usage ~60%)
|
||||||
|
lore --robot issues --fields minimal
|
||||||
|
lore --robot mrs --fields iid,title,state,draft
|
||||||
|
|
||||||
|
# Show detailed entity info
|
||||||
|
lore --robot issues 123
|
||||||
|
lore --robot mrs 456 -p group/repo
|
||||||
|
|
||||||
|
# Count entities
|
||||||
|
lore --robot count issues
|
||||||
|
lore --robot count discussions --for mr
|
||||||
|
|
||||||
|
# Search indexed documents
|
||||||
|
lore --robot search "authentication bug"
|
||||||
|
|
||||||
|
# Check sync status
|
||||||
|
lore --robot status
|
||||||
|
|
||||||
|
# Run full sync pipeline
|
||||||
|
lore --robot sync
|
||||||
|
|
||||||
|
# Run sync without resource events
|
||||||
|
lore --robot sync --no-events
|
||||||
|
|
||||||
|
# Surgical sync: specific entities by IID
|
||||||
|
lore --robot sync --issue 42 -p group/repo
|
||||||
|
lore --robot sync --mr 99 --mr 100 -p group/repo
|
||||||
|
|
||||||
|
# Run ingestion only
|
||||||
|
lore --robot ingest issues
|
||||||
|
|
||||||
|
# Trace why code was introduced
|
||||||
|
lore --robot trace src/main.rs -p group/repo
|
||||||
|
|
||||||
|
# File-level MR history
|
||||||
|
lore --robot file-history src/auth/ -p group/repo
|
||||||
|
|
||||||
|
# Manage cron-based auto-sync (Unix)
|
||||||
|
lore --robot cron status
|
||||||
|
lore --robot cron install --interval 15
|
||||||
|
|
||||||
|
# Token management
|
||||||
|
lore --robot token show
|
||||||
|
|
||||||
|
# Check environment health
|
||||||
|
lore --robot doctor
|
||||||
|
|
||||||
|
# Document and index statistics
|
||||||
|
lore --robot stats
|
||||||
|
|
||||||
|
# Quick health pre-flight check (exit 0 = healthy, 19 = unhealthy)
|
||||||
|
lore --robot health
|
||||||
|
|
||||||
|
# Generate searchable documents from ingested data
|
||||||
|
lore --robot generate-docs
|
||||||
|
|
||||||
|
# Generate vector embeddings via Ollama
|
||||||
|
lore --robot embed
|
||||||
|
|
||||||
|
# Personal work dashboard
|
||||||
|
lore --robot me
|
||||||
|
lore --robot me --issues
|
||||||
|
lore --robot me --mrs
|
||||||
|
lore --robot me --activity --since 7d
|
||||||
|
lore --robot me --project group/repo
|
||||||
|
lore --robot me --user jdoe
|
||||||
|
lore --robot me --fields minimal
|
||||||
|
lore --robot me --reset-cursor
|
||||||
|
|
||||||
|
# Find semantically related entities
|
||||||
|
lore --robot related issues 42
|
||||||
|
lore --robot related "authentication flow"
|
||||||
|
|
||||||
|
# Re-register projects from config
|
||||||
|
lore --robot init --refresh
|
||||||
|
|
||||||
|
# Agent self-discovery manifest (all commands, flags, exit codes, response schemas)
|
||||||
|
lore robot-docs
|
||||||
|
|
||||||
|
# Version information
|
||||||
|
lore --robot version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Format
|
||||||
|
|
||||||
|
All commands return compact JSON with a uniform envelope and timing metadata:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"ok":true,"data":{...},"meta":{"elapsed_ms":42}}
|
||||||
|
```
|
||||||
|
|
||||||
|
Errors return structured JSON to stderr with machine-actionable recovery steps:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"error":{"code":"CONFIG_NOT_FOUND","message":"...","suggestion":"Run 'lore init'","actions":["lore init"]}}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `actions` array contains executable shell commands for automated recovery. It is omitted when empty.
|
||||||
|
|
||||||
|
### Field Selection
|
||||||
|
|
||||||
|
The `--fields` flag on `issues` and `mrs` list commands controls which fields appear in the JSON response:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J issues --fields minimal # Preset: iid, title, state, updated_at_iso
|
||||||
|
lore -J mrs --fields iid,title,state,draft,labels # Custom field list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exit Codes
|
||||||
|
|
||||||
|
| Code | Meaning |
|
||||||
|
|------|---------|
|
||||||
|
| 0 | Success |
|
||||||
|
| 1 | Internal error / not implemented |
|
||||||
|
| 2 | Usage error (invalid flags or arguments) |
|
||||||
|
| 3 | Config invalid |
|
||||||
|
| 4 | Token not set |
|
||||||
|
| 5 | GitLab auth failed |
|
||||||
|
| 6 | Resource not found |
|
||||||
|
| 7 | Rate limited |
|
||||||
|
| 8 | Network error |
|
||||||
|
| 9 | Database locked |
|
||||||
|
| 10 | Database error |
|
||||||
|
| 11 | Migration failed |
|
||||||
|
| 12 | I/O error |
|
||||||
|
| 13 | Transform error |
|
||||||
|
| 14 | Ollama unavailable |
|
||||||
|
| 15 | Ollama model not found |
|
||||||
|
| 16 | Embedding failed |
|
||||||
|
| 17 | Not found (entity does not exist) |
|
||||||
|
| 18 | Ambiguous match (use `-p` to specify project) |
|
||||||
|
| 19 | Health check failed |
|
||||||
|
| 20 | Config not found |
|
||||||
|
|
||||||
|
### Configuration Precedence
|
||||||
|
|
||||||
|
1. CLI flags (highest priority)
|
||||||
|
2. Environment variables (`LORE_ROBOT`, `GITLAB_TOKEN`, `LORE_CONFIG_PATH`)
|
||||||
|
3. Config file (`~/.config/lore/config.json`)
|
||||||
|
4. Built-in defaults (lowest priority)
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- Use `lore --robot` or `lore -J` for all agent interactions
|
||||||
|
- Check exit codes for error handling
|
||||||
|
- Parse JSON errors from stderr; use `actions` array for automated recovery
|
||||||
|
- Use `--fields minimal` to reduce token usage (~60% fewer tokens)
|
||||||
|
- Use `-n` / `--limit` to control response size
|
||||||
|
- Use `-q` / `--quiet` to suppress progress bars and non-essential output
|
||||||
|
- Use `--color never` in non-TTY automation for ANSI-free output
|
||||||
|
- Use `-v` / `-vv` / `-vvv` for increasing verbosity (debug/trace logging)
|
||||||
|
- Use `--log-format json` for machine-readable log output to stderr
|
||||||
|
- TTY detection handles piped commands automatically
|
||||||
|
- Use `lore --robot health` as a fast pre-flight check before queries
|
||||||
|
- Use `lore robot-docs` for response schema discovery
|
||||||
|
- The `-p` flag supports fuzzy project matching (suffix and substring)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Read/Write Split: lore vs glab
|
||||||
|
|
||||||
|
| Operation | Tool | Why |
|
||||||
|
|-----------|------|-----|
|
||||||
|
| List issues/MRs | lore | Richer: includes status, discussions, closing MRs |
|
||||||
|
| View issue/MR detail | lore | Pre-joined discussions, work-item status |
|
||||||
|
| Search across entities | lore | FTS5 + vector hybrid search |
|
||||||
|
| Expert/workload analysis | lore | who command — no glab equivalent |
|
||||||
|
| Timeline reconstruction | lore | Chronological narrative — no glab equivalent |
|
||||||
|
| Create/update/close | glab | Write operations |
|
||||||
|
| Approve/merge MR | glab | Write operations |
|
||||||
|
| CI/CD pipelines | glab | Not in lore scope |
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
## UBS Quick Reference for AI Agents
|
||||||
|
|
||||||
|
UBS stands for "Ultimate Bug Scanner": **The AI Coding Agent's Secret Weapon: Flagging Likely Bugs for Fixing Early On**
|
||||||
|
|
||||||
|
**Install:** `curl -sSL https://raw.githubusercontent.com/Dicklesworthstone/ultimate_bug_scanner/master/install.sh | bash`
|
||||||
|
|
||||||
|
**Golden Rule:** `ubs <changed-files>` before every commit. Exit 0 = safe. Exit >0 = fix & re-run.
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
```bash
|
||||||
|
ubs file.ts file2.py # Specific files (< 1s) — USE THIS
|
||||||
|
ubs $(git diff --name-only --cached) # Staged files — before commit
|
||||||
|
ubs --only=js,python src/ # Language filter (3-5x faster)
|
||||||
|
ubs --ci --fail-on-warning . # CI mode — before PR
|
||||||
|
ubs --help # Full command reference
|
||||||
|
ubs sessions --entries 1 # Tail the latest install session log
|
||||||
|
ubs . # Whole project (ignores things like .venv and node_modules automatically)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output Format:**
|
||||||
|
```
|
||||||
|
⚠️ Category (N errors)
|
||||||
|
file.ts:42:5 – Issue description
|
||||||
|
💡 Suggested fix
|
||||||
|
Exit code: 1
|
||||||
|
```
|
||||||
|
Parse: `file:line:col` → location | 💡 → how to fix | Exit 0/1 → pass/fail
|
||||||
|
|
||||||
|
**Fix Workflow:**
|
||||||
|
1. Read finding → category + fix suggestion
|
||||||
|
2. Navigate `file:line:col` → view context
|
||||||
|
3. Verify real issue (not false positive)
|
||||||
|
4. Fix root cause (not symptom)
|
||||||
|
5. Re-run `ubs <file>` → exit 0
|
||||||
|
6. Commit
|
||||||
|
|
||||||
|
**Speed Critical:** Scope to changed files. `ubs src/file.ts` (< 1s) vs `ubs .` (30s). Never full scan for small edits.
|
||||||
|
|
||||||
|
**Bug Severity:**
|
||||||
|
- **Critical** (always fix): Null safety, XSS/injection, async/await, memory leaks
|
||||||
|
- **Important** (production): Type narrowing, division-by-zero, resource leaks
|
||||||
|
- **Contextual** (judgment): TODO/FIXME, console logs
|
||||||
|
|
||||||
|
**Anti-Patterns:**
|
||||||
|
- ❌ Ignore findings → ✅ Investigate each
|
||||||
|
- ❌ Full scan per edit → ✅ Scope to file
|
||||||
|
- ❌ Fix symptom (`if (x) { x.y }`) → ✅ Root cause (`x?.y`)
|
||||||
|
````
|
||||||
|
|
||||||
|
<!-- BEGIN LIQUID MAIL (v:48d7b3fc) -->
|
||||||
|
## Integrating Liquid Mail with Beads
|
||||||
|
|
||||||
|
**Beads** manages task status, priority, and dependencies (`br` CLI).
|
||||||
|
**Liquid Mail** provides the shared log—progress, decisions, and context that survives sessions.
|
||||||
|
|
||||||
|
### Conventions
|
||||||
|
|
||||||
|
- **Single source of truth**: Beads owns task state; Liquid Mail owns conversation/decisions
|
||||||
|
- **Shared identifiers**: Include the Beads issue ID in posts (e.g., `[lm-jht] Topic validation rules`)
|
||||||
|
- **Decisions before action**: Post `DECISION:` messages before risky changes, not after
|
||||||
|
- **Identity in user updates**: In every user-facing reply, include your window-name (derived from `LIQUID_MAIL_WINDOW_ID`) so humans can distinguish concurrent agents.
|
||||||
|
|
||||||
|
### Typical Flow
|
||||||
|
|
||||||
|
**1. Pick ready work (Beads)**
|
||||||
|
```bash
|
||||||
|
br ready # Find available work (no blockers)
|
||||||
|
br show lm-jht # Review details
|
||||||
|
br update lm-jht --status in_progress
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. Check context (Liquid Mail)**
|
||||||
|
```bash
|
||||||
|
liquid-mail notify # See what changed since last session
|
||||||
|
liquid-mail query "lm-jht" # Find prior discussion on this issue
|
||||||
|
```
|
||||||
|
|
||||||
|
**3. Work and log progress (topic required)**
|
||||||
|
|
||||||
|
The `--topic` flag is required for your first post. After that, the topic is pinned to your window.
|
||||||
|
```bash
|
||||||
|
liquid-mail post --topic auth-system "[lm-jht] START: Reviewing current topic id patterns"
|
||||||
|
liquid-mail post "[lm-jht] FINDING: IDs like lm3189... are being used as topic names"
|
||||||
|
liquid-mail post "[lm-jht] NEXT: Add validation + rename guidance"
|
||||||
|
```
|
||||||
|
|
||||||
|
**4. Decisions before risky changes**
|
||||||
|
```bash
|
||||||
|
liquid-mail post --decision "[lm-jht] DECISION: Reject UUID-like topic names; require slugs"
|
||||||
|
# Then implement
|
||||||
|
```
|
||||||
|
|
||||||
|
### Decision Conflicts (Preflight)
|
||||||
|
|
||||||
|
When you post a decision (via `--decision` or a `DECISION:` line), Liquid Mail can preflight-check for conflicts with prior decisions **in the same topic**.
|
||||||
|
|
||||||
|
- If a conflict is detected, `liquid-mail post` fails with `DECISION_CONFLICT`.
|
||||||
|
- Review prior decisions: `liquid-mail decisions --topic <topic>`.
|
||||||
|
- If you intend to supersede the old decision, re-run with `--yes` and include what changed and why.
|
||||||
|
|
||||||
|
**5. Complete (Beads is authority)**
|
||||||
|
```bash
|
||||||
|
br close lm-jht # Mark complete in Beads
|
||||||
|
liquid-mail post "[lm-jht] Completed: Topic validation shipped in 177267d"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Posting Format
|
||||||
|
|
||||||
|
- **Short** (5-15 lines, not walls of text)
|
||||||
|
- **Prefixed** with ALL-CAPS tags: `FINDING:`, `DECISION:`, `QUESTION:`, `NEXT:`
|
||||||
|
- **Include file paths** so others can jump in: `src/services/auth.ts:42`
|
||||||
|
- **Include issue IDs** in brackets: `[lm-jht]`
|
||||||
|
- **User-facing replies**: include `AGENT: <window-name>` near the top. Get it with `liquid-mail window name`.
|
||||||
|
|
||||||
|
### Topics (Required)
|
||||||
|
|
||||||
|
Liquid Mail organizes messages into **topics** (Honcho sessions). Topics are **soft boundaries**—search spans all topics by default.
|
||||||
|
|
||||||
|
**Rule:** `liquid-mail post` requires a topic:
|
||||||
|
- Provide `--topic <name>`, OR
|
||||||
|
- Post inside a window that already has a pinned topic.
|
||||||
|
|
||||||
|
Topic names must be:
|
||||||
|
- 4–50 characters
|
||||||
|
- lowercase letters/numbers with hyphens
|
||||||
|
- start with a letter, end with a letter/number
|
||||||
|
- no consecutive hyphens
|
||||||
|
- not reserved (`all`, `new`, `help`, `merge`, `rename`, `list`)
|
||||||
|
- not UUID-like (`lm<32-hex>` or standard UUIDs)
|
||||||
|
|
||||||
|
Good examples: `auth-system`, `db-system`, `dashboards`
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
|
||||||
|
- **List topics (newest first)**: `liquid-mail topics`
|
||||||
|
- **Find context across topics**: `liquid-mail query "auth"`, then pick a topic name
|
||||||
|
- **Rename a topic (alias)**: `liquid-mail topic rename <old> <new>`
|
||||||
|
- **Merge two topics into a new one**: `liquid-mail topic merge <A> <B> --into <C>`
|
||||||
|
|
||||||
|
Examples (component topic + Beads id in the subject):
|
||||||
|
```bash
|
||||||
|
liquid-mail post --topic auth-system "[lm-jht] START: Investigating token refresh failures"
|
||||||
|
liquid-mail post --topic auth-system "[lm-jht] FINDING: refresh happens in middleware, not service layer"
|
||||||
|
liquid-mail post --topic auth-system --decision "[lm-jht] DECISION: Move refresh logic into AuthService"
|
||||||
|
|
||||||
|
liquid-mail post --topic dashboards "[lm-1p5] START: Adding latency panel"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Context Refresh (Before New Work / After Redirects)
|
||||||
|
|
||||||
|
If you see redirect/merge messages, refresh context before acting:
|
||||||
|
```bash
|
||||||
|
liquid-mail notify
|
||||||
|
liquid-mail window status --json
|
||||||
|
liquid-mail summarize --topic <topic>
|
||||||
|
liquid-mail decisions --topic <topic>
|
||||||
|
```
|
||||||
|
|
||||||
|
If you discover a newer "canonical" topic (for example after a topic merge), switch to it explicitly:
|
||||||
|
```bash
|
||||||
|
liquid-mail post --topic <new-topic> "[lm-xxxx] CONTEXT: Switching topics (rename/merge)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Live Updates (Polling)
|
||||||
|
|
||||||
|
Liquid Mail is pull-based by default (you run `notify`). For near-real-time updates:
|
||||||
|
```bash
|
||||||
|
liquid-mail watch --topic <topic> # watch a topic
|
||||||
|
liquid-mail watch # or watch your pinned topic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mapping Cheat-Sheet
|
||||||
|
|
||||||
|
| Concept | In Beads | In Liquid Mail |
|
||||||
|
|---------|----------|----------------|
|
||||||
|
| Work item | `lm-jht` (issue ID) | Include `[lm-jht]` in posts |
|
||||||
|
| Workstream | — | `--topic auth-system` |
|
||||||
|
| Subject prefix | — | `[lm-jht] ...` |
|
||||||
|
| Commit message | Include `lm-jht` | — |
|
||||||
|
| Status | `br update --status` | Post progress messages |
|
||||||
|
|
||||||
|
### Pitfalls
|
||||||
|
|
||||||
|
- **Don't manage tasks in Liquid Mail**—Beads is the single task queue
|
||||||
|
- **Always include `lm-xxx`** in posts to avoid ID drift across tools
|
||||||
|
- **Don't dump logs**—keep posts short and structured
|
||||||
|
|
||||||
|
### Quick Reference
|
||||||
|
|
||||||
|
| Need | Command |
|
||||||
|
|------|---------|
|
||||||
|
| What changed? | `liquid-mail notify` |
|
||||||
|
| Log progress | `liquid-mail post "[lm-xxx] ..."` |
|
||||||
|
| Before risky change | `liquid-mail post --decision "[lm-xxx] DECISION: ..."` |
|
||||||
|
| Find history | `liquid-mail query "search term"` |
|
||||||
|
| Prior decisions | `liquid-mail decisions --topic <topic>` |
|
||||||
|
| Show config | `liquid-mail config` |
|
||||||
|
| List topics | `liquid-mail topics` |
|
||||||
|
| Rename topic | `liquid-mail topic rename <old> <new>` |
|
||||||
|
| Merge topics | `liquid-mail topic merge <A> <B> --into <C>` |
|
||||||
|
| Polling watch | `liquid-mail watch [--topic <topic>]` |
|
||||||
|
<!-- END LIQUID MAIL -->
|
||||||
1286
Cargo.lock
generated
1286
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
22
Cargo.toml
22
Cargo.toml
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lore"
|
name = "lore"
|
||||||
version = "0.1.0"
|
version = "0.9.4"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Gitlore - Local GitLab data management with semantic search"
|
description = "Gitlore - Local GitLab data management with semantic search"
|
||||||
authors = ["Taylor Eernisse"]
|
authors = ["Taylor Eernisse"]
|
||||||
@@ -21,37 +21,45 @@ serde_json = "1"
|
|||||||
|
|
||||||
# CLI
|
# CLI
|
||||||
clap = { version = "4", features = ["derive", "env"] }
|
clap = { version = "4", features = ["derive", "env"] }
|
||||||
|
clap_complete = "4"
|
||||||
dialoguer = "0.12"
|
dialoguer = "0.12"
|
||||||
console = "0.16"
|
console = "0.16"
|
||||||
indicatif = "0.18"
|
indicatif = "0.18"
|
||||||
comfy-table = "7"
|
lipgloss = { package = "charmed-lipgloss", version = "0.2", default-features = false, features = ["native"] }
|
||||||
open = "5"
|
open = "5"
|
||||||
|
|
||||||
# HTTP
|
# HTTP
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
asupersync = { version = "0.2", features = ["tls", "tls-native-roots"] }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "macros", "time"] }
|
|
||||||
|
|
||||||
# Async streaming for pagination
|
# Async streaming for pagination
|
||||||
async-stream = "0.3"
|
async-stream = "0.3"
|
||||||
futures = { version = "0.3", default-features = false, features = ["alloc"] }
|
futures = { version = "0.3", default-features = false, features = ["alloc", "async-await"] }
|
||||||
|
|
||||||
# Utilities
|
# Utilities
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
url = "2"
|
url = "2"
|
||||||
urlencoding = "2"
|
urlencoding = "2"
|
||||||
|
rand = "0.8"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
httpdate = "1"
|
||||||
uuid = { version = "1", features = ["v4"] }
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
regex = "1"
|
||||||
|
strsim = "0.11"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
|
||||||
tracing-indicatif = "0.3"
|
tracing-appender = "0.2"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
|
||||||
wiremock = "0.6"
|
wiremock = "0.6"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
467
PERFORMANCE_AUDIT.md
Normal file
467
PERFORMANCE_AUDIT.md
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
# Gitlore Performance Audit Report
|
||||||
|
|
||||||
|
**Date**: 2026-02-05
|
||||||
|
**Auditor**: Claude Code (Opus 4.5)
|
||||||
|
**Scope**: Core system performance - ingestion, embedding, search, and document regeneration
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This audit identifies 12 high-impact optimization opportunities across the Gitlore codebase. The most significant findings center on:
|
||||||
|
|
||||||
|
1. **SQL query patterns** with N+1 issues and inefficient correlated subqueries
|
||||||
|
2. **Memory allocation patterns** in hot paths (embedding, chunking, ingestion)
|
||||||
|
3. **Change detection queries** using triple-EXISTS patterns instead of JOINs
|
||||||
|
|
||||||
|
**Estimated overall improvement potential**: 30-50% reduction in latency for filtered searches, 2-5x improvement in ingestion throughput for issues/MRs with many labels.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Methodology
|
||||||
|
|
||||||
|
- **Codebase analysis**: Full read of all modules in `src/`
|
||||||
|
- **SQL pattern analysis**: All queries checked for N+1, missing indexes, unbounded results
|
||||||
|
- **Memory allocation analysis**: Clone patterns, unnecessary collections, missing capacity hints
|
||||||
|
- **Test baseline**: All tests pass (`cargo test --release`)
|
||||||
|
|
||||||
|
Note: Without access to a live GitLab instance or populated database, profiling is code-analysis based rather than runtime measured.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opportunity Matrix
|
||||||
|
|
||||||
|
| ID | Issue | Location | Impact | Confidence | Effort | ICE Score | Status |
|
||||||
|
|----|-------|----------|--------|------------|--------|-----------|--------|
|
||||||
|
| 1 | Triple-EXISTS change detection | `change_detector.rs:19-46` | HIGH | 95% | LOW | **9.5** | **DONE** |
|
||||||
|
| 2 | N+1 label/assignee inserts | `issues.rs:270-285`, `merge_requests.rs:242-272` | HIGH | 95% | MEDIUM | **9.0** | Pending |
|
||||||
|
| 3 | Clone in embedding batch loop | `pipeline.rs:165` | HIGH | 90% | LOW | **9.0** | Pending |
|
||||||
|
| 4 | Correlated GROUP_CONCAT in list | `list.rs:341-348` | HIGH | 90% | MEDIUM | **8.5** | Pending |
|
||||||
|
| 5 | Multiple EXISTS per label filter | `filters.rs:100-107` | HIGH | 85% | MEDIUM | **8.0** | **DONE** |
|
||||||
|
| 6 | String allocation in chunking | `chunking.rs:7-49` | MEDIUM | 95% | MEDIUM | **7.5** | Pending |
|
||||||
|
| 7 | Multiple COUNT queries | `count.rs:44-56` | MEDIUM | 95% | LOW | **7.0** | **DONE** |
|
||||||
|
| 8 | Collect-then-concat pattern | `truncation.rs:60-61` | MEDIUM | 90% | LOW | **7.0** | **DONE** |
|
||||||
|
| 9 | Box<dyn ToSql> allocations | `filters.rs:67-135` | MEDIUM | 80% | HIGH | **6.0** | Pending |
|
||||||
|
| 10 | Missing Vec::with_capacity | `pipeline.rs:106`, multiple | LOW | 95% | LOW | **5.5** | **DONE** |
|
||||||
|
| 11 | FTS token collect-join | `fts.rs:26-41` | LOW | 90% | LOW | **5.0** | **DONE** |
|
||||||
|
| 12 | Transformer string clones | `merge_request.rs:51-77` | MEDIUM | 85% | HIGH | **5.0** | Pending |
|
||||||
|
|
||||||
|
ICE Score = (Impact x Confidence) / Effort, scaled 1-10
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Detailed Findings
|
||||||
|
|
||||||
|
### 1. Triple-EXISTS Change Detection Query (ICE: 9.5)
|
||||||
|
|
||||||
|
**Location**: `src/embedding/change_detector.rs:19-46`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```sql
|
||||||
|
SELECT d.id, d.content_text, d.content_hash
|
||||||
|
FROM documents d
|
||||||
|
WHERE d.id > ?1
|
||||||
|
AND (
|
||||||
|
NOT EXISTS (SELECT 1 FROM embedding_metadata em WHERE em.document_id = d.id AND em.chunk_index = 0)
|
||||||
|
OR EXISTS (SELECT 1 FROM embedding_metadata em WHERE em.document_id = d.id AND em.chunk_index = 0 AND em.document_hash != d.content_hash)
|
||||||
|
OR EXISTS (SELECT 1 FROM embedding_metadata em WHERE em.document_id = d.id AND em.chunk_index = 0 AND (...))
|
||||||
|
)
|
||||||
|
ORDER BY d.id
|
||||||
|
LIMIT ?2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Three separate EXISTS subqueries, each scanning `embedding_metadata`. SQLite cannot short-circuit across OR'd EXISTS efficiently.
|
||||||
|
|
||||||
|
**Proposed Fix**:
|
||||||
|
```sql
|
||||||
|
SELECT d.id, d.content_text, d.content_hash
|
||||||
|
FROM documents d
|
||||||
|
LEFT JOIN embedding_metadata em
|
||||||
|
ON em.document_id = d.id AND em.chunk_index = 0
|
||||||
|
WHERE d.id > ?1
|
||||||
|
AND (
|
||||||
|
em.document_id IS NULL -- no embedding
|
||||||
|
OR em.document_hash != d.content_hash -- hash mismatch
|
||||||
|
OR em.chunk_max_bytes IS NULL
|
||||||
|
OR em.chunk_max_bytes != ?3
|
||||||
|
OR em.model != ?4
|
||||||
|
OR em.dims != ?5
|
||||||
|
)
|
||||||
|
ORDER BY d.id
|
||||||
|
LIMIT ?2
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Both queries return documents needing embedding when:
|
||||||
|
- No embedding exists for chunk_index=0 (NULL check)
|
||||||
|
- Hash changed (direct comparison)
|
||||||
|
- Config mismatch (model/dims/chunk_max_bytes)
|
||||||
|
|
||||||
|
The LEFT JOIN + NULL check is semantically identical to NOT EXISTS. The OR conditions inside WHERE match the EXISTS predicates exactly.
|
||||||
|
|
||||||
|
**Expected Impact**: 2-3x faster for large document sets. Single scan of embedding_metadata instead of three.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. N+1 Label/Assignee Inserts (ICE: 9.0)
|
||||||
|
|
||||||
|
**Location**:
|
||||||
|
- `src/ingestion/issues.rs:270-285`
|
||||||
|
- `src/ingestion/merge_requests.rs:242-272`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
for label_name in label_names {
|
||||||
|
let label_id = upsert_label_tx(tx, project_id, label_name, &mut labels_created)?;
|
||||||
|
link_issue_label_tx(tx, local_issue_id, label_id)?;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Each label triggers 2+ SQL statements. With 20 labels × 100 issues = 4000+ queries per batch.
|
||||||
|
|
||||||
|
**Proposed Fix**: Batch insert using prepared statements with multi-row VALUES:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Build batch: INSERT INTO issue_labels VALUES (?, ?), (?, ?), ...
|
||||||
|
let mut values = String::new();
|
||||||
|
let mut params: Vec<Box<dyn ToSql>> = Vec::with_capacity(label_ids.len() * 2);
|
||||||
|
for (i, label_id) in label_ids.iter().enumerate() {
|
||||||
|
if i > 0 { values.push_str(","); }
|
||||||
|
values.push_str("(?,?)");
|
||||||
|
params.push(Box::new(local_issue_id));
|
||||||
|
params.push(Box::new(*label_id));
|
||||||
|
}
|
||||||
|
let sql = format!("INSERT OR IGNORE INTO issue_labels (issue_id, label_id) VALUES {}", values);
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use `prepare_cached()` pattern from `events_db.rs`.
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Both approaches insert identical rows. OR IGNORE handles duplicates identically.
|
||||||
|
|
||||||
|
**Expected Impact**: 5-10x faster ingestion for issues/MRs with many labels.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Clone in Embedding Batch Loop (ICE: 9.0)
|
||||||
|
|
||||||
|
**Location**: `src/embedding/pipeline.rs:165`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
let texts: Vec<String> = batch.iter().map(|c| c.text.clone()).collect();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Every batch iteration clones all chunk texts. With BATCH_SIZE=32 and thousands of chunks, this doubles memory allocation in the hot path.
|
||||||
|
|
||||||
|
**Proposed Fix**: Transfer ownership instead of cloning:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Option A: Drain chunks from all_chunks instead of iterating
|
||||||
|
let texts: Vec<String> = batch.into_iter().map(|c| c.text).collect();
|
||||||
|
|
||||||
|
// Option B: Store references in ChunkWork, clone only at API boundary
|
||||||
|
struct ChunkWork<'a> {
|
||||||
|
text: &'a str,
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Same texts sent to Ollama, same embeddings returned. Order and content identical.
|
||||||
|
|
||||||
|
**Expected Impact**: 30-50% reduction in embedding pipeline memory allocation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Correlated GROUP_CONCAT in List Queries (ICE: 8.5)
|
||||||
|
|
||||||
|
**Location**: `src/cli/commands/list.rs:341-348`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```sql
|
||||||
|
SELECT i.*,
|
||||||
|
(SELECT GROUP_CONCAT(l.name, X'1F') FROM issue_labels il JOIN labels l ... WHERE il.issue_id = i.id) AS labels_csv,
|
||||||
|
(SELECT COUNT(*) FROM discussions WHERE issue_id = i.id) as discussion_count
|
||||||
|
FROM issues i
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Each correlated subquery executes per row. With LIMIT 50, that's 100+ subquery executions.
|
||||||
|
|
||||||
|
**Proposed Fix**: Use window functions or pre-aggregated CTEs:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
WITH label_agg AS (
|
||||||
|
SELECT il.issue_id, GROUP_CONCAT(l.name, X'1F') AS labels_csv
|
||||||
|
FROM issue_labels il JOIN labels l ON il.label_id = l.id
|
||||||
|
GROUP BY il.issue_id
|
||||||
|
),
|
||||||
|
discussion_agg AS (
|
||||||
|
SELECT issue_id, COUNT(*) AS cnt
|
||||||
|
FROM discussions WHERE issue_id IS NOT NULL
|
||||||
|
GROUP BY issue_id
|
||||||
|
)
|
||||||
|
SELECT i.*, la.labels_csv, da.cnt
|
||||||
|
FROM issues i
|
||||||
|
LEFT JOIN label_agg la ON la.issue_id = i.id
|
||||||
|
LEFT JOIN discussion_agg da ON da.issue_id = i.id
|
||||||
|
WHERE ...
|
||||||
|
LIMIT 50
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Same data returned - labels concatenated, discussion counts accurate. JOIN preserves NULL when no labels/discussions exist.
|
||||||
|
|
||||||
|
**Expected Impact**: 3-5x faster list queries with discussion/label data.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Multiple EXISTS Per Label Filter (ICE: 8.0)
|
||||||
|
|
||||||
|
**Location**: `src/search/filters.rs:100-107`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```sql
|
||||||
|
WHERE EXISTS (SELECT 1 ... AND label_name = ?)
|
||||||
|
AND EXISTS (SELECT 1 ... AND label_name = ?)
|
||||||
|
AND EXISTS (SELECT 1 ... AND label_name = ?)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Filtering by 3 labels generates 3 EXISTS subqueries. Each scans document_labels.
|
||||||
|
|
||||||
|
**Proposed Fix**: Single EXISTS with GROUP BY/HAVING:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
WHERE EXISTS (
|
||||||
|
SELECT 1 FROM document_labels dl
|
||||||
|
WHERE dl.document_id = d.id
|
||||||
|
AND dl.label_name IN (?, ?, ?)
|
||||||
|
GROUP BY dl.document_id
|
||||||
|
HAVING COUNT(DISTINCT dl.label_name) = 3
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Both return documents with ALL specified labels. AND of EXISTS = document has label1 AND label2 AND label3. GROUP BY + HAVING COUNT(DISTINCT) = 3 is mathematically equivalent.
|
||||||
|
|
||||||
|
**Expected Impact**: 2-4x faster filtered search with multiple labels.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. String Allocation in Chunking (ICE: 7.5)
|
||||||
|
|
||||||
|
**Location**: `src/embedding/chunking.rs:7-49`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
chunks.push((chunk_index, remaining.to_string()));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Converts `&str` slices to owned `String` for every chunk. The input is already a `&str`.
|
||||||
|
|
||||||
|
**Proposed Fix**: Return borrowed slices or use `Cow`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub fn split_into_chunks(content: &str) -> Vec<(usize, &str)> {
|
||||||
|
// Return slices into original content
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or if ownership is needed later:
|
||||||
|
```rust
|
||||||
|
pub fn split_into_chunks(content: &str) -> Vec<(usize, Cow<'_, str>)>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Same chunk boundaries, same text content. Only allocation behavior changes.
|
||||||
|
|
||||||
|
**Expected Impact**: Reduces allocations by ~50% in chunking hot path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Multiple COUNT Queries (ICE: 7.0)
|
||||||
|
|
||||||
|
**Location**: `src/cli/commands/count.rs:44-56`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
let count = conn.query_row("SELECT COUNT(*) FROM issues", ...)?;
|
||||||
|
let opened = conn.query_row("SELECT COUNT(*) FROM issues WHERE state = 'opened'", ...)?;
|
||||||
|
let closed = conn.query_row("SELECT COUNT(*) FROM issues WHERE state = 'closed'", ...)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: 5 separate queries for MR state breakdown, 3 for issues.
|
||||||
|
|
||||||
|
**Proposed Fix**: Single query with CASE aggregation:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
COUNT(*) AS total,
|
||||||
|
SUM(CASE WHEN state = 'opened' THEN 1 ELSE 0 END) AS opened,
|
||||||
|
SUM(CASE WHEN state = 'closed' THEN 1 ELSE 0 END) AS closed
|
||||||
|
FROM issues
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Identical counts returned. CASE WHEN with SUM is standard SQL for conditional counting.
|
||||||
|
|
||||||
|
**Expected Impact**: 3-5x fewer round trips for count command.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Collect-then-Concat Pattern (ICE: 7.0)
|
||||||
|
|
||||||
|
**Location**: `src/documents/truncation.rs:60-61`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
let formatted: Vec<String> = notes.iter().map(format_note).collect();
|
||||||
|
let total: String = formatted.concat();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Allocates intermediate Vec<String>, then allocates again for concat.
|
||||||
|
|
||||||
|
**Proposed Fix**: Use fold or format directly:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let total = notes.iter().fold(String::new(), |mut acc, note| {
|
||||||
|
acc.push_str(&format_note(note));
|
||||||
|
acc
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with capacity hint:
|
||||||
|
```rust
|
||||||
|
let total_len: usize = notes.iter().map(|n| estimate_note_len(n)).sum();
|
||||||
|
let mut total = String::with_capacity(total_len);
|
||||||
|
for note in notes {
|
||||||
|
total.push_str(&format_note(note));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Isomorphism Proof**: Same concatenated string output. Order preserved.
|
||||||
|
|
||||||
|
**Expected Impact**: 50% reduction in allocations for document regeneration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Box<dyn ToSql> Allocations (ICE: 6.0)
|
||||||
|
|
||||||
|
**Location**: `src/search/filters.rs:67-135`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
let mut params: Vec<Box<dyn rusqlite::types::ToSql>> = vec![Box::new(ids_json)];
|
||||||
|
// ... more Box::new() calls
|
||||||
|
let param_refs: Vec<&dyn rusqlite::types::ToSql> = params.iter().map(|p| p.as_ref()).collect();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem**: Boxing each parameter, then collecting references. Two allocations per parameter.
|
||||||
|
|
||||||
|
**Proposed Fix**: Use rusqlite's params! macro or typed parameter arrays:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// For known parameter counts, use arrays
|
||||||
|
let params: [&dyn ToSql; 4] = [&ids_json, &author, &state, &limit];
|
||||||
|
|
||||||
|
// Or build SQL with named parameters and use params! directly
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Impact**: Eliminates ~15 allocations per filtered search.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Missing Vec::with_capacity (ICE: 5.5)
|
||||||
|
|
||||||
|
**Locations**:
|
||||||
|
- `src/embedding/pipeline.rs:106`
|
||||||
|
- `src/embedding/pipeline.rs:162`
|
||||||
|
- Multiple other locations
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
let mut all_chunks: Vec<ChunkWork> = Vec::new();
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed Fix**:
|
||||||
|
```rust
|
||||||
|
// Estimate: average 3 chunks per document
|
||||||
|
let mut all_chunks = Vec::with_capacity(pending.len() * 3);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Impact**: Eliminates reallocation overhead during vector growth.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. FTS Token Collect-Join (ICE: 5.0)
|
||||||
|
|
||||||
|
**Location**: `src/search/fts.rs:26-41`
|
||||||
|
|
||||||
|
**Current Code**:
|
||||||
|
```rust
|
||||||
|
let tokens: Vec<String> = trimmed.split_whitespace().map(...).collect();
|
||||||
|
tokens.join(" ")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed Fix**: Use itertools or avoid intermediate vec:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use itertools::Itertools;
|
||||||
|
trimmed.split_whitespace().map(...).join(" ")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected Impact**: Minor - search queries are typically short.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 12. Transformer String Clones (ICE: 5.0)
|
||||||
|
|
||||||
|
**Location**: `src/gitlab/transformers/merge_request.rs:51-77`
|
||||||
|
|
||||||
|
**Problem**: Multiple `.clone()` calls on String fields during transformation.
|
||||||
|
|
||||||
|
**Proposed Fix**: Use `std::mem::take()` where possible, or restructure to avoid cloning.
|
||||||
|
|
||||||
|
**Expected Impact**: Moderate - depends on MR volume.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Regression Guardrails
|
||||||
|
|
||||||
|
For any optimization implemented:
|
||||||
|
|
||||||
|
1. **Test Coverage**: All existing tests must pass
|
||||||
|
2. **Output Equivalence**: For SQL changes, verify identical result sets with test data
|
||||||
|
3. **Benchmark Suite**: Add benchmarks for affected paths before/after
|
||||||
|
|
||||||
|
Suggested benchmark targets:
|
||||||
|
```rust
|
||||||
|
#[bench] fn bench_change_detection_1k_docs(b: &mut Bencher) { ... }
|
||||||
|
#[bench] fn bench_label_insert_50_labels(b: &mut Bencher) { ... }
|
||||||
|
#[bench] fn bench_hybrid_search_filtered(b: &mut Bencher) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Priority
|
||||||
|
|
||||||
|
**Phase 1 (Quick Wins)** - COMPLETE:
|
||||||
|
1. ~~Change detection query rewrite (#1)~~ **DONE**
|
||||||
|
2. ~~Multiple COUNT consolidation (#7)~~ **DONE**
|
||||||
|
3. ~~Collect-concat pattern (#8)~~ **DONE**
|
||||||
|
4. ~~Vec::with_capacity hints (#10)~~ **DONE**
|
||||||
|
5. ~~FTS token collect-join (#11)~~ **DONE**
|
||||||
|
6. ~~Multiple EXISTS per label (#5)~~ **DONE**
|
||||||
|
|
||||||
|
**Phase 2 (Medium Effort)**:
|
||||||
|
5. Embedding batch clone removal (#3)
|
||||||
|
6. Label filter EXISTS consolidation (#5)
|
||||||
|
7. Chunking string allocation (#6)
|
||||||
|
|
||||||
|
**Phase 3 (Higher Effort)**:
|
||||||
|
8. N+1 batch inserts (#2)
|
||||||
|
9. List query CTEs (#4)
|
||||||
|
10. Parameter boxing (#9)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Test Baseline
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo test --release
|
||||||
|
running 127 tests
|
||||||
|
test result: ok. 127 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests pass. Any optimization must maintain this baseline.
|
||||||
636
PROPOSED_CODE_FILE_REORGANIZATION_PLAN.md
Normal file
636
PROPOSED_CODE_FILE_REORGANIZATION_PLAN.md
Normal file
@@ -0,0 +1,636 @@
|
|||||||
|
# Proposed Code File Reorganization Plan
|
||||||
|
|
||||||
|
## 1. Scope, Audit Method, and Constraints
|
||||||
|
|
||||||
|
This plan is based on a full audit of the `src/` tree (all 131 Rust files) plus integration tests in `tests/` that import `src` modules.
|
||||||
|
|
||||||
|
What I audited:
|
||||||
|
- module/file inventory (`src/**.rs`)
|
||||||
|
- line counts and hotspot analysis
|
||||||
|
- crate-internal import graph (`use crate::...`)
|
||||||
|
- public API surface (public structs/enums/functions by file)
|
||||||
|
- command routing and re-export topology (`main.rs`, `lib.rs`, `cli/mod.rs`, `cli/commands/mod.rs`)
|
||||||
|
- cross-module coupling and test coupling
|
||||||
|
|
||||||
|
Constraints followed for this proposal:
|
||||||
|
- no implementation yet (plan only)
|
||||||
|
- keep nesting shallow and intuitive
|
||||||
|
- optimize for discoverability for humans and coding agents
|
||||||
|
- no compatibility shims as a long-term strategy
|
||||||
|
- every structural change includes explicit call-site update tracking
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Current State (Measured)
|
||||||
|
|
||||||
|
### 2.1 Size by top-level module (`src/`)
|
||||||
|
|
||||||
|
| Module | Files | Lines | Prod Files | Prod Lines | Test Files | Test Lines |
|
||||||
|
|---|---:|---:|---:|---:|---:|---:|
|
||||||
|
| `cli` | 41 | 29,131 | 37 | 23,068 | 4 | 6,063 |
|
||||||
|
| `core` | 39 | 12,493 | 27 | 7,599 | 12 | 4,894 |
|
||||||
|
| `ingestion` | 15 | 6,935 | 10 | 5,259 | 5 | 1,676 |
|
||||||
|
| `documents` | 6 | 3,657 | 4 | 1,749 | 2 | 1,908 |
|
||||||
|
| `gitlab` | 11 | 3,607 | 8 | 2,391 | 3 | 1,216 |
|
||||||
|
| `embedding` | 10 | 1,878 | 7 | 1,327 | 3 | 551 |
|
||||||
|
| `search` | 6 | 1,115 | 6 | 1,115 | 0 | 0 |
|
||||||
|
| `main.rs` | 1 | 3,744 | 1 | 3,744 | 0 | 0 |
|
||||||
|
| `lib.rs` | 1 | 9 | 1 | 9 | 0 | 0 |
|
||||||
|
|
||||||
|
Total in `src/`: **131 files / 62,569 lines**.
|
||||||
|
|
||||||
|
### 2.2 Largest production hotspots
|
||||||
|
|
||||||
|
| File | Lines | Why it matters |
|
||||||
|
|---|---:|---|
|
||||||
|
| `src/main.rs` | 3,744 | Binary entrypoint is doing too much dispatch and formatting work |
|
||||||
|
| `src/cli/autocorrect.rs` | 1,865 | Large parsing/correction ruleset in one file |
|
||||||
|
| `src/ingestion/orchestrator.rs` | 1,753 | Multi-stage ingestion orchestration and persistence mixed together |
|
||||||
|
| `src/cli/commands/show.rs` | 1,544 | Issue/MR retrieval + rendering + JSON conversion all in one file |
|
||||||
|
| `src/cli/render.rs` | 1,482 | Theme, table layout, formatting utilities bundled together |
|
||||||
|
| `src/cli/commands/list.rs` | 1,383 | Issues + MRs + notes listing/query/printing in one file |
|
||||||
|
| `src/cli/mod.rs` | 1,268 | Clap root parser plus every args struct |
|
||||||
|
| `src/cli/commands/sync.rs` | 1,201 | Sync flow + human rendering + JSON output |
|
||||||
|
| `src/cli/commands/me/queries.rs` | 1,135 | Multiple query families and post-processing logic |
|
||||||
|
| `src/cli/commands/ingest.rs` | 1,116 | Ingest flow + dry-run + presentation concerns |
|
||||||
|
| `src/documents/extractor.rs` | 1,059 | Four document source extractors in one file |
|
||||||
|
|
||||||
|
### 2.3 High-level dependency flow (top modules)
|
||||||
|
|
||||||
|
Observed module coupling from imports:
|
||||||
|
- `cli -> core` (very heavy, 33 files)
|
||||||
|
- `cli -> documents/embedding/gitlab/ingestion/search` (command-dependent)
|
||||||
|
- `ingestion -> core` (12 files), `ingestion -> gitlab` (10 files)
|
||||||
|
- `search -> core` and `search -> embedding`
|
||||||
|
- `timeline` logic currently located under `core/*timeline*` but semantically acts as its own subsystem
|
||||||
|
|
||||||
|
### 2.4 Structural pain points
|
||||||
|
|
||||||
|
1. `main.rs` is overloaded with command handlers, robot output envelope types, clap error mapping, and domain invocation.
|
||||||
|
2. `cli/mod.rs` mixes root parser concerns with command-specific argument schemas.
|
||||||
|
3. `core/` still holds domain-specific subsystems (`timeline`, cross-reference extraction, ingestion persistence helpers) that are not truly "core infra".
|
||||||
|
4. Several large command files combine query/build/fetch/render/json responsibilities.
|
||||||
|
5. Test helper setup is duplicated heavily in large test files (`who_tests`, `list_tests`, `me_tests`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Reorganization Principles
|
||||||
|
|
||||||
|
1. Keep top-level domains explicit: `cli`, `core` (infra), `gitlab`, `ingestion`, `documents`, `embedding`, `search`, plus extracted domain modules where justified.
|
||||||
|
2. Keep nesting shallow: max 2-3 levels in normal workflow paths.
|
||||||
|
3. Co-locate command-specific args/types/rendering with the command implementation.
|
||||||
|
4. Separate orchestration from formatting from data-access code.
|
||||||
|
5. Prefer module boundaries that map to runtime pipeline boundaries.
|
||||||
|
6. Make import paths reveal ownership directly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Proposed Target Structure (End State)
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/
|
||||||
|
main.rs # thin binary entrypoint
|
||||||
|
lib.rs
|
||||||
|
|
||||||
|
app/ # NEW: runtime dispatch/orchestration glue
|
||||||
|
mod.rs
|
||||||
|
dispatch.rs
|
||||||
|
errors.rs
|
||||||
|
robot_docs.rs
|
||||||
|
|
||||||
|
cli/
|
||||||
|
mod.rs # Cli + Commands only
|
||||||
|
args.rs # shared args structs used by Commands variants
|
||||||
|
render/
|
||||||
|
mod.rs
|
||||||
|
format.rs
|
||||||
|
table.rs
|
||||||
|
theme.rs
|
||||||
|
autocorrect/
|
||||||
|
mod.rs
|
||||||
|
flags.rs
|
||||||
|
enums.rs
|
||||||
|
fuzzy.rs
|
||||||
|
commands/
|
||||||
|
mod.rs
|
||||||
|
list/
|
||||||
|
mod.rs
|
||||||
|
issues.rs
|
||||||
|
mrs.rs
|
||||||
|
notes.rs
|
||||||
|
render.rs
|
||||||
|
show/
|
||||||
|
mod.rs
|
||||||
|
issue.rs
|
||||||
|
mr.rs
|
||||||
|
render.rs
|
||||||
|
me/ # keep existing folder, retain split style
|
||||||
|
who/ # keep existing folder, retain split style
|
||||||
|
ingest/
|
||||||
|
mod.rs
|
||||||
|
run.rs
|
||||||
|
dry_run.rs
|
||||||
|
render.rs
|
||||||
|
sync/
|
||||||
|
mod.rs
|
||||||
|
run.rs
|
||||||
|
render.rs
|
||||||
|
surgical.rs
|
||||||
|
# smaller focused commands can stay single-file for now
|
||||||
|
|
||||||
|
core/ # infra-only boundary after moves
|
||||||
|
mod.rs
|
||||||
|
backoff.rs
|
||||||
|
config.rs
|
||||||
|
cron.rs
|
||||||
|
cursor.rs
|
||||||
|
db.rs
|
||||||
|
error.rs
|
||||||
|
file_history.rs
|
||||||
|
lock.rs
|
||||||
|
logging.rs
|
||||||
|
metrics.rs
|
||||||
|
path_resolver.rs
|
||||||
|
paths.rs
|
||||||
|
project.rs
|
||||||
|
shutdown.rs
|
||||||
|
time.rs
|
||||||
|
trace.rs
|
||||||
|
|
||||||
|
timeline/ # NEW: extracted domain subsystem
|
||||||
|
mod.rs
|
||||||
|
types.rs
|
||||||
|
seed.rs
|
||||||
|
expand.rs
|
||||||
|
collect.rs
|
||||||
|
|
||||||
|
xref/ # NEW: extracted cross-reference subsystem
|
||||||
|
mod.rs
|
||||||
|
note_parser.rs
|
||||||
|
references.rs
|
||||||
|
|
||||||
|
ingestion/
|
||||||
|
mod.rs
|
||||||
|
issues.rs
|
||||||
|
merge_requests.rs
|
||||||
|
discussions.rs
|
||||||
|
mr_discussions.rs
|
||||||
|
mr_diffs.rs
|
||||||
|
dirty_tracker.rs
|
||||||
|
discussion_queue.rs
|
||||||
|
orchestrator/
|
||||||
|
mod.rs
|
||||||
|
issues_flow.rs
|
||||||
|
mrs_flow.rs
|
||||||
|
resource_events.rs
|
||||||
|
closes_issues.rs
|
||||||
|
diff_jobs.rs
|
||||||
|
progress.rs
|
||||||
|
storage/ # NEW: ingestion-owned persistence helpers
|
||||||
|
mod.rs
|
||||||
|
payloads.rs # from core/payloads.rs
|
||||||
|
events.rs # from core/events_db.rs
|
||||||
|
queue.rs # from core/dependent_queue.rs
|
||||||
|
sync_run.rs # from core/sync_run.rs
|
||||||
|
|
||||||
|
documents/
|
||||||
|
mod.rs
|
||||||
|
extractor/
|
||||||
|
mod.rs
|
||||||
|
issues.rs
|
||||||
|
mrs.rs
|
||||||
|
discussions.rs
|
||||||
|
notes.rs
|
||||||
|
common.rs
|
||||||
|
regenerator.rs
|
||||||
|
truncation.rs
|
||||||
|
|
||||||
|
embedding/
|
||||||
|
mod.rs
|
||||||
|
change_detector.rs
|
||||||
|
chunks.rs # merge chunk_ids.rs + chunking.rs
|
||||||
|
ollama.rs
|
||||||
|
pipeline.rs
|
||||||
|
similarity.rs
|
||||||
|
|
||||||
|
gitlab/
|
||||||
|
# mostly keep as-is (already coherent)
|
||||||
|
|
||||||
|
search/
|
||||||
|
# mostly keep as-is (already coherent)
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
- `gitlab/` and `search/` are already cohesive and should largely remain unchanged.
|
||||||
|
- `who/` and `me/` command families are already split well relative to other commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Detailed Change Plan (Phased)
|
||||||
|
|
||||||
|
## Phase 1: Domain Boundary Extraction (lowest conceptual risk, high clarity gain)
|
||||||
|
|
||||||
|
### 5.1 Extract timeline subsystem from `core`
|
||||||
|
|
||||||
|
Move:
|
||||||
|
- `src/core/timeline.rs` -> `src/timeline/types.rs`
|
||||||
|
- `src/core/timeline_seed.rs` -> `src/timeline/seed.rs`
|
||||||
|
- `src/core/timeline_expand.rs` -> `src/timeline/expand.rs`
|
||||||
|
- `src/core/timeline_collect.rs` -> `src/timeline/collect.rs`
|
||||||
|
- add `src/timeline/mod.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- Timeline is a full pipeline domain (seed -> expand -> collect), not core infra.
|
||||||
|
- Improves discoverability for `lore timeline` and timeline tests.
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/cli/commands/timeline.rs`
|
||||||
|
- `crate::core::timeline::*` -> `crate::timeline::*`
|
||||||
|
- `crate::core::timeline_seed::*` -> `crate::timeline::seed::*`
|
||||||
|
- `crate::core::timeline_expand::*` -> `crate::timeline::expand::*`
|
||||||
|
- `crate::core::timeline_collect::*` -> `crate::timeline::collect::*`
|
||||||
|
- `tests/timeline_pipeline_tests.rs`
|
||||||
|
- `lore::core::timeline*` imports -> `lore::timeline::*`
|
||||||
|
- internal references among moved files update from `crate::core::timeline` to `crate::timeline::types`
|
||||||
|
- `src/core/mod.rs`: remove `timeline*` module declarations
|
||||||
|
- `src/lib.rs`: add `pub mod timeline;`
|
||||||
|
|
||||||
|
### 5.2 Extract cross-reference subsystem from `core`
|
||||||
|
|
||||||
|
Move:
|
||||||
|
- `src/core/note_parser.rs` -> `src/xref/note_parser.rs`
|
||||||
|
- `src/core/references.rs` -> `src/xref/references.rs`
|
||||||
|
- add `src/xref/mod.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- Cross-reference extraction is a domain subsystem feeding ingestion and timeline.
|
||||||
|
- Current placement in `core/` obscures data flow.
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/ingestion/orchestrator.rs`
|
||||||
|
- `crate::core::references::*` -> `crate::xref::references::*`
|
||||||
|
- `crate::core::note_parser::*` -> `crate::xref::note_parser::*`
|
||||||
|
- `src/core/mod.rs`: remove `note_parser` and `references`
|
||||||
|
- `src/lib.rs`: add `pub mod xref;`
|
||||||
|
- tests referencing old paths update to `crate::xref::*`
|
||||||
|
|
||||||
|
### 5.3 Move ingestion-owned persistence helpers out of `core`
|
||||||
|
|
||||||
|
Move:
|
||||||
|
- `src/core/payloads.rs` -> `src/ingestion/storage/payloads.rs`
|
||||||
|
- `src/core/events_db.rs` -> `src/ingestion/storage/events.rs`
|
||||||
|
- `src/core/dependent_queue.rs` -> `src/ingestion/storage/queue.rs`
|
||||||
|
- `src/core/sync_run.rs` -> `src/ingestion/storage/sync_run.rs`
|
||||||
|
- add `src/ingestion/storage/mod.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- These files primarily support ingestion/sync runtime behavior and ingestion persistence.
|
||||||
|
- Consolidates ingestion runtime + ingestion storage into one domain area.
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/ingestion/discussions.rs`, `issues.rs`, `merge_requests.rs`, `mr_discussions.rs`
|
||||||
|
- `core::payloads::*` -> `ingestion::storage::payloads::*`
|
||||||
|
- `src/ingestion/orchestrator.rs`
|
||||||
|
- `core::dependent_queue::*` -> `ingestion::storage::queue::*`
|
||||||
|
- `core::events_db::*` -> `ingestion::storage::events::*`
|
||||||
|
- `src/main.rs`
|
||||||
|
- `core::dependent_queue::release_all_locked_jobs` -> `ingestion::storage::queue::release_all_locked_jobs`
|
||||||
|
- `core::sync_run::SyncRunRecorder` -> `ingestion::storage::sync_run::SyncRunRecorder`
|
||||||
|
- `src/cli/commands/count.rs`
|
||||||
|
- `core::events_db::*` -> `ingestion::storage::events::*`
|
||||||
|
- `src/cli/commands/sync_surgical.rs`
|
||||||
|
- `core::sync_run::SyncRunRecorder` -> `ingestion::storage::sync_run::SyncRunRecorder`
|
||||||
|
- `src/core/mod.rs`: remove moved modules
|
||||||
|
- `src/ingestion/mod.rs`: export `pub mod storage;`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: CLI Structure Cleanup (high dev ergonomics impact)
|
||||||
|
|
||||||
|
### 5.4 Split `cli/mod.rs` responsibilities
|
||||||
|
|
||||||
|
Current:
|
||||||
|
- root parser (`Cli`, `Commands`)
|
||||||
|
- all args structs (`IssuesArgs`, `WhoArgs`, `MeArgs`, etc.)
|
||||||
|
|
||||||
|
Proposed:
|
||||||
|
- `src/cli/mod.rs`: only `Cli`, `Commands`, top-level parser behavior
|
||||||
|
- `src/cli/args.rs`: all args structs and command-local enums (`CronAction`, `TokenAction`)
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- keeps parser root small and readable
|
||||||
|
- one canonical place for args schemas
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/main.rs`
|
||||||
|
- `use lore::cli::{..., WhoArgs, ...}` -> `use lore::cli::args::{...}` (or re-export from `cli/mod.rs`)
|
||||||
|
- `src/cli/commands/who/mod.rs`
|
||||||
|
- `use crate::cli::WhoArgs;` -> `use crate::cli::args::WhoArgs;`
|
||||||
|
- `src/cli/commands/me/mod.rs`
|
||||||
|
- `use crate::cli::MeArgs;` -> `use crate::cli::args::MeArgs;`
|
||||||
|
|
||||||
|
### 5.5 Make `main.rs` thin by moving dispatch logic to `app/`
|
||||||
|
|
||||||
|
Proposed splits from `main.rs`:
|
||||||
|
- `app/dispatch.rs`: all `handle_*` command handlers
|
||||||
|
- `app/errors.rs`: clap error mapping, correction warning formatting
|
||||||
|
- `app/robot_docs.rs`: robot docs schema/data envelope generation
|
||||||
|
- keep `main.rs`: startup, logging init, parse, delegate to dispatcher
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- reduces entrypoint complexity and improves testability of dispatch behavior
|
||||||
|
- isolates robot docs machinery from runtime bootstrapping
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `main.rs`: replace direct handler function definitions with calls into `app::*`
|
||||||
|
- `lib.rs`: add `pub mod app;` if shared imports needed by tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Split Large Command Files by Responsibility
|
||||||
|
|
||||||
|
### 5.6 Split `cli/commands/list.rs`
|
||||||
|
|
||||||
|
Proposed:
|
||||||
|
- `commands/list/issues.rs` (issue queries + issue output)
|
||||||
|
- `commands/list/mrs.rs` (MR queries + MR output)
|
||||||
|
- `commands/list/notes.rs` (note queries + note output)
|
||||||
|
- `commands/list/render.rs` (shared formatting helpers)
|
||||||
|
- `commands/list/mod.rs` (public API and re-exports)
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- list concerns are already logically tripartite
|
||||||
|
- better locality for bugfixes and feature additions
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/cli/commands/mod.rs`: import module folder and re-export unchanged API names
|
||||||
|
- `src/main.rs`: ideally no change if `commands/mod.rs` re-exports remain stable
|
||||||
|
|
||||||
|
### 5.7 Split `cli/commands/show.rs`
|
||||||
|
|
||||||
|
Proposed:
|
||||||
|
- `commands/show/issue.rs`
|
||||||
|
- `commands/show/mr.rs`
|
||||||
|
- `commands/show/render.rs`
|
||||||
|
- `commands/show/mod.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- issue and MR detail assembly have separate SQL and shape logic
|
||||||
|
- rendering concerns can be isolated from data retrieval
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/cli/commands/mod.rs` re-exports preserved (`run_show_issue`, `run_show_mr`, printers)
|
||||||
|
- `src/main.rs` remains stable if re-exports preserved
|
||||||
|
|
||||||
|
### 5.8 Split `cli/commands/ingest.rs` and `cli/commands/sync.rs`
|
||||||
|
|
||||||
|
Proposed:
|
||||||
|
- `commands/ingest/run.rs`, `dry_run.rs`, `render.rs`, `mod.rs`
|
||||||
|
- `commands/sync/run.rs`, `render.rs`, `surgical.rs`, `mod.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- orchestration, preview generation, and output rendering are currently intertwined
|
||||||
|
- surgical sync is semantically part of sync command family
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- update `src/cli/commands/mod.rs` exports
|
||||||
|
- update `src/cli/commands/sync_surgical.rs` path if merged into `commands/sync/surgical.rs`
|
||||||
|
- no CLI UX changes expected if external API names remain
|
||||||
|
|
||||||
|
### 5.9 Split `documents/extractor.rs`
|
||||||
|
|
||||||
|
Proposed:
|
||||||
|
- `documents/extractor/issues.rs`
|
||||||
|
- `documents/extractor/mrs.rs`
|
||||||
|
- `documents/extractor/discussions.rs`
|
||||||
|
- `documents/extractor/notes.rs`
|
||||||
|
- `documents/extractor/common.rs`
|
||||||
|
- `documents/extractor/mod.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- extractor currently contains four independent source-type extraction paths
|
||||||
|
- per-source unit tests become easier to target
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/documents/mod.rs` re-export surface remains stable
|
||||||
|
- `src/documents/regenerator.rs` imports update only if internal re-export paths change
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 4: Opportunistic Consolidations
|
||||||
|
|
||||||
|
### 5.10 Merge tiny embedding chunk helpers
|
||||||
|
|
||||||
|
Merge:
|
||||||
|
- `src/embedding/chunk_ids.rs`
|
||||||
|
- `src/embedding/chunking.rs`
|
||||||
|
- into `src/embedding/chunks.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- both represent one conceptual concern: chunk partitioning and chunk identity mapping
|
||||||
|
- avoids tiny-file scattering
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- `src/embedding/pipeline.rs`
|
||||||
|
- `src/embedding/change_detector.rs`
|
||||||
|
- `src/search/vector.rs`
|
||||||
|
- `src/embedding/mod.rs` exports
|
||||||
|
|
||||||
|
### 5.11 Test helper de-duplication
|
||||||
|
|
||||||
|
Add a shared test support module for repeated DB fixture setup currently duplicated in:
|
||||||
|
- `src/cli/commands/who_tests.rs`
|
||||||
|
- `src/cli/commands/list_tests.rs`
|
||||||
|
- `src/cli/commands/me/me_tests.rs`
|
||||||
|
- multiple `core/*_tests.rs`
|
||||||
|
|
||||||
|
Why:
|
||||||
|
- lower maintenance cost and fewer fixture drift bugs
|
||||||
|
|
||||||
|
Calling-code updates required:
|
||||||
|
- test-only imports in affected files
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. File-Level Recommendation Matrix
|
||||||
|
|
||||||
|
Legend:
|
||||||
|
- `KEEP`: structure is already coherent
|
||||||
|
- `MOVE`: relocate without major logic split
|
||||||
|
- `SPLIT`: divide into focused files/modules
|
||||||
|
- `MERGE`: consolidate tiny related files
|
||||||
|
|
||||||
|
### 6.1 `core/`
|
||||||
|
|
||||||
|
- `backoff.rs` -> KEEP
|
||||||
|
- `config.rs` -> KEEP (large but cohesive)
|
||||||
|
- `cron.rs` -> KEEP
|
||||||
|
- `cursor.rs` -> KEEP
|
||||||
|
- `db.rs` -> KEEP
|
||||||
|
- `dependent_queue.rs` -> MOVE to `ingestion/storage/queue.rs`
|
||||||
|
- `error.rs` -> KEEP
|
||||||
|
- `events_db.rs` -> MOVE to `ingestion/storage/events.rs`
|
||||||
|
- `file_history.rs` -> KEEP
|
||||||
|
- `lock.rs` -> KEEP
|
||||||
|
- `logging.rs` -> KEEP
|
||||||
|
- `metrics.rs` -> KEEP
|
||||||
|
- `note_parser.rs` -> MOVE to `xref/note_parser.rs`
|
||||||
|
- `path_resolver.rs` -> KEEP
|
||||||
|
- `paths.rs` -> KEEP
|
||||||
|
- `payloads.rs` -> MOVE to `ingestion/storage/payloads.rs`
|
||||||
|
- `project.rs` -> KEEP
|
||||||
|
- `references.rs` -> MOVE to `xref/references.rs`
|
||||||
|
- `shutdown.rs` -> KEEP
|
||||||
|
- `sync_run.rs` -> MOVE to `ingestion/storage/sync_run.rs`
|
||||||
|
- `time.rs` -> KEEP
|
||||||
|
- `timeline.rs`, `timeline_seed.rs`, `timeline_expand.rs`, `timeline_collect.rs` -> MOVE to `timeline/`
|
||||||
|
- `trace.rs` -> KEEP
|
||||||
|
|
||||||
|
### 6.2 `cli/`
|
||||||
|
|
||||||
|
- `mod.rs` -> SPLIT (`mod.rs` + `args.rs`)
|
||||||
|
- `autocorrect.rs` -> SPLIT into `autocorrect/` submodules
|
||||||
|
- `render.rs` -> SPLIT into `render/` submodules
|
||||||
|
- `commands/list.rs` -> SPLIT into `commands/list/`
|
||||||
|
- `commands/show.rs` -> SPLIT into `commands/show/`
|
||||||
|
- `commands/ingest.rs` -> SPLIT into `commands/ingest/`
|
||||||
|
- `commands/sync.rs` + `commands/sync_surgical.rs` -> SPLIT/MERGE into `commands/sync/`
|
||||||
|
- `commands/me/*` -> KEEP (already good shape)
|
||||||
|
- `commands/who/*` -> KEEP (already good shape)
|
||||||
|
- small focused commands (`auth_test`, `embed`, `trace`, etc.) -> KEEP
|
||||||
|
|
||||||
|
### 6.3 `documents/`
|
||||||
|
|
||||||
|
- `extractor.rs` -> SPLIT into extractor folder
|
||||||
|
- `regenerator.rs` -> KEEP
|
||||||
|
- `truncation.rs` -> KEEP
|
||||||
|
|
||||||
|
### 6.4 `embedding/`
|
||||||
|
|
||||||
|
- `change_detector.rs` -> KEEP
|
||||||
|
- `chunk_ids.rs` + `chunking.rs` -> MERGE into `chunks.rs`
|
||||||
|
- `ollama.rs` -> KEEP
|
||||||
|
- `pipeline.rs` -> KEEP for now (already a pipeline-centric file)
|
||||||
|
- `similarity.rs` -> KEEP
|
||||||
|
|
||||||
|
### 6.5 `gitlab/`, `search/`
|
||||||
|
|
||||||
|
- KEEP as-is except minor internal refactors only when touched by feature work
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Import/Call-Site Impact Tracker (must-update list)
|
||||||
|
|
||||||
|
This section tracks files that must be updated when moves happen to avoid broken builds.
|
||||||
|
|
||||||
|
### 7.1 For timeline extraction
|
||||||
|
|
||||||
|
Must update:
|
||||||
|
- `src/cli/commands/timeline.rs`
|
||||||
|
- `tests/timeline_pipeline_tests.rs`
|
||||||
|
- moved timeline module internals (`seed`, `expand`, `collect`)
|
||||||
|
- `src/core/mod.rs`
|
||||||
|
- `src/lib.rs`
|
||||||
|
|
||||||
|
### 7.2 For xref extraction
|
||||||
|
|
||||||
|
Must update:
|
||||||
|
- `src/ingestion/orchestrator.rs` (all `core::references` and `core::note_parser` paths)
|
||||||
|
- tests importing moved modules
|
||||||
|
- `src/core/mod.rs`
|
||||||
|
- `src/lib.rs`
|
||||||
|
|
||||||
|
### 7.3 For ingestion storage move
|
||||||
|
|
||||||
|
Must update:
|
||||||
|
- `src/ingestion/discussions.rs`
|
||||||
|
- `src/ingestion/issues.rs`
|
||||||
|
- `src/ingestion/merge_requests.rs`
|
||||||
|
- `src/ingestion/mr_discussions.rs`
|
||||||
|
- `src/ingestion/orchestrator.rs`
|
||||||
|
- `src/cli/commands/count.rs`
|
||||||
|
- `src/cli/commands/sync_surgical.rs`
|
||||||
|
- `src/main.rs`
|
||||||
|
- `src/core/mod.rs`
|
||||||
|
- `src/ingestion/mod.rs`
|
||||||
|
|
||||||
|
### 7.4 For CLI args split
|
||||||
|
|
||||||
|
Must update:
|
||||||
|
- `src/main.rs`
|
||||||
|
- `src/cli/commands/who/mod.rs`
|
||||||
|
- `src/cli/commands/me/mod.rs`
|
||||||
|
- any command file importing args directly from `crate::cli::*Args`
|
||||||
|
|
||||||
|
### 7.5 For command file splits
|
||||||
|
|
||||||
|
Must update:
|
||||||
|
- `src/cli/commands/mod.rs` re-exports
|
||||||
|
- tests that import command internals by file/module path
|
||||||
|
- `src/main.rs` only if re-export names change (recommended: keep names stable)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Execution Strategy (Safe Order)
|
||||||
|
|
||||||
|
Recommended order:
|
||||||
|
1. Phase 1 (`timeline`, `xref`, `ingestion/storage`) with no behavior changes.
|
||||||
|
2. Phase 2 (`cli/mod.rs` split, `main.rs` thinning) while preserving command signatures.
|
||||||
|
3. Phase 3 (`list`, `show`, `ingest`, `sync`, `extractor` splits).
|
||||||
|
4. Phase 4 opportunistic merges and test helper dedupe.
|
||||||
|
|
||||||
|
For each phase:
|
||||||
|
- complete file moves/splits and import rewrites in one cohesive change
|
||||||
|
- run quality gates
|
||||||
|
- only then proceed to next phase
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Verification and Non-Regression Checklist
|
||||||
|
|
||||||
|
After each phase, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo check --all-targets
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
cargo fmt --check
|
||||||
|
cargo test
|
||||||
|
cargo test -- --nocapture
|
||||||
|
```
|
||||||
|
|
||||||
|
Targeted suites to run when relevant:
|
||||||
|
- timeline moves: `cargo test timeline_pipeline_tests`
|
||||||
|
- who/me/list splits: `cargo test who_tests`, `cargo test list_tests`, `cargo test me_tests`
|
||||||
|
- ingestion storage moves: `cargo test ingestion`
|
||||||
|
|
||||||
|
Before each commit, run UBS on changed files:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ubs <changed-files>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Risks and Mitigations
|
||||||
|
|
||||||
|
Primary risks:
|
||||||
|
1. Import path churn causing compile errors.
|
||||||
|
2. Accidental visibility changes (`pub`/`pub(crate)`) during file splits.
|
||||||
|
3. Re-export drift breaking `main.rs` or tests.
|
||||||
|
4. Behavioral drift from mixed refactor + logic changes.
|
||||||
|
|
||||||
|
Mitigations:
|
||||||
|
- refactor-only phases (no feature changes)
|
||||||
|
- keep public API names stable during directory reshapes
|
||||||
|
- preserve command re-exports in `cli/commands/mod.rs`
|
||||||
|
- run full quality gates after each phase
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Recommendation
|
||||||
|
|
||||||
|
Start with **Phase 1 only** in the first implementation pass. It yields major clarity gains with relatively constrained blast radius.
|
||||||
|
|
||||||
|
If Phase 1 lands cleanly, proceed with Phase 2. Phase 3 should be done in smaller PR-sized chunks (`list` first, then `show`, then `ingest/sync`, then `documents/extractor`).
|
||||||
|
|
||||||
|
No code/file moves have been executed yet; this document is the proposal for review and approval.
|
||||||
64
acceptance-criteria.md
Normal file
64
acceptance-criteria.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Trace/File-History Empty-Result Diagnostics
|
||||||
|
|
||||||
|
## AC-1: Human mode shows searched paths on empty results
|
||||||
|
|
||||||
|
When `lore trace <path>` returns 0 chains in human mode, the output includes the resolved path(s) that were searched. If renames were followed, show the full rename chain.
|
||||||
|
|
||||||
|
## AC-2: Human mode shows actionable reason on empty results
|
||||||
|
|
||||||
|
When 0 chains are found, the hint message distinguishes between:
|
||||||
|
- "No MR file changes synced yet" (mr_file_changes table is empty for this project) -> suggest `lore sync`
|
||||||
|
- "File paths not found in MR file changes" (sync has run but this file has no matches) -> suggest checking the path or that the file may predate the sync window
|
||||||
|
|
||||||
|
## AC-3: Robot mode includes diagnostics object on empty results
|
||||||
|
|
||||||
|
When `total_chains == 0` in robot JSON output, add a `"diagnostics"` key to `"meta"` containing:
|
||||||
|
- `paths_searched: [...]` (already present as `resolved_paths` in data -- no duplication needed)
|
||||||
|
- `hints: [string]` -- same actionable reasons as AC-2 but machine-readable
|
||||||
|
|
||||||
|
## AC-4: Info-level logging at each pipeline stage
|
||||||
|
|
||||||
|
Add `tracing::info!` calls visible with `-v`:
|
||||||
|
- After rename resolution: number of paths found
|
||||||
|
- After MR query: number of MRs found
|
||||||
|
- After issue/discussion enrichment: counts per MR
|
||||||
|
|
||||||
|
## AC-5: Apply same pattern to `lore file-history`
|
||||||
|
|
||||||
|
All of the above (AC-1 through AC-4) also apply to `lore file-history` empty results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Secure Token Resolution for Cron
|
||||||
|
|
||||||
|
## AC-6: Stored token in config
|
||||||
|
|
||||||
|
The configuration file supports an optional `token` field in the `gitlab` section, allowing users to persist their GitLab personal access token alongside other settings. Existing configuration files that omit this field continue to load and function normally.
|
||||||
|
|
||||||
|
## AC-7: Token resolution precedence
|
||||||
|
|
||||||
|
Lore resolves the GitLab token by checking the environment variable first, then falling back to the stored config token. This means environment variables always take priority, preserving CI/CD workflows and one-off overrides, while the stored token provides a reliable default for non-interactive contexts like cron jobs. If neither source provides a non-empty value, the user receives a clear `TOKEN_NOT_SET` error with guidance on how to fix it.
|
||||||
|
|
||||||
|
## AC-8: `lore token set` command
|
||||||
|
|
||||||
|
The `lore token set` command provides a secure, guided workflow for storing a GitLab token. It accepts the token via a `--token` flag, standard input (for piped automation), or an interactive masked prompt. Before storing, it validates the token against the GitLab API to catch typos and expired credentials early. After writing the token to the configuration file, it restricts file permissions to owner-only read/write (mode 0600) to prevent other users on the system from reading the token. The command supports both human and robot output modes.
|
||||||
|
|
||||||
|
## AC-9: `lore token show` command
|
||||||
|
|
||||||
|
The `lore token show` command displays the currently active token along with its source ("config file" or "environment variable"). By default the token value is masked for safety; the `--unmask` flag reveals the full value when needed. The command supports both human and robot output modes.
|
||||||
|
|
||||||
|
## AC-10: Consistent token resolution across all commands
|
||||||
|
|
||||||
|
Every command that requires a GitLab token uses the same two-step resolution logic described in AC-7. This ensures that storing a token once via `lore token set` is sufficient to make all commands work, including background cron syncs that have no access to shell environment variables.
|
||||||
|
|
||||||
|
## AC-11: Cron install warns about missing stored token
|
||||||
|
|
||||||
|
When `lore cron install` completes, it checks whether a token is available in the configuration file. If not, it displays a prominent warning explaining that cron jobs cannot access shell environment variables and directs the user to run `lore token set` to ensure unattended syncs will authenticate successfully.
|
||||||
|
|
||||||
|
## AC-12: `TOKEN_NOT_SET` error recommends `lore token set`
|
||||||
|
|
||||||
|
The `TOKEN_NOT_SET` error message recommends `lore token set` as the primary fix for missing credentials, with the environment variable export shown as an alternative for users who prefer that approach. In robot mode, the `actions` array lists both options so that automated recovery workflows can act on them.
|
||||||
|
|
||||||
|
## AC-13: Doctor reports token source
|
||||||
|
|
||||||
|
The `lore doctor` command includes the token's source in its GitLab connectivity check, reporting whether the token was found in the configuration file or an environment variable. This makes it straightforward to verify that cron jobs will have access to the token without relying on the user's interactive shell environment.
|
||||||
24
agents/ceo/AGENTS.md
Normal file
24
agents/ceo/AGENTS.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
You are the CEO.
|
||||||
|
|
||||||
|
Your home directory is $AGENT_HOME. Everything personal to you -- life, memory, knowledge -- lives there. Other agents may have their own folders and you may update them when necessary.
|
||||||
|
|
||||||
|
Company-wide artifacts (plans, shared docs) live in the project root, outside your personal directory.
|
||||||
|
|
||||||
|
## Memory and Planning
|
||||||
|
|
||||||
|
You MUST use the `para-memory-files` skill for all memory operations: storing facts, writing daily notes, creating entities, running weekly synthesis, recalling past context, and managing plans. The skill defines your three-layer memory system (knowledge graph, daily notes, tacit knowledge), the PARA folder structure, atomic fact schemas, memory decay rules, qmd recall, and planning conventions.
|
||||||
|
|
||||||
|
Invoke it whenever you need to remember, retrieve, or organize anything.
|
||||||
|
|
||||||
|
## Safety Considerations
|
||||||
|
|
||||||
|
- Never exfiltrate secrets or private data.
|
||||||
|
- Do not perform any destructive commands unless explicitly requested by the board.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
These files are essential. Read them.
|
||||||
|
|
||||||
|
- `$AGENT_HOME/HEARTBEAT.md` -- execution and extraction checklist. Run every heartbeat.
|
||||||
|
- `$AGENT_HOME/SOUL.md` -- who you are and how you should act.
|
||||||
|
- `$AGENT_HOME/TOOLS.md` -- tools you have access to
|
||||||
72
agents/ceo/HEARTBEAT.md
Normal file
72
agents/ceo/HEARTBEAT.md
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# HEARTBEAT.md -- CEO Heartbeat Checklist
|
||||||
|
|
||||||
|
Run this checklist on every heartbeat. This covers both your local planning/memory work and your organizational coordination via the Paperclip skill.
|
||||||
|
|
||||||
|
## 1. Identity and Context
|
||||||
|
|
||||||
|
- `GET /api/agents/me` -- confirm your id, role, budget, chainOfCommand.
|
||||||
|
- Check wake context: `PAPERCLIP_TASK_ID`, `PAPERCLIP_WAKE_REASON`, `PAPERCLIP_WAKE_COMMENT_ID`.
|
||||||
|
|
||||||
|
## 2. Local Planning Check
|
||||||
|
|
||||||
|
1. Read today's plan from `$AGENT_HOME/memory/YYYY-MM-DD.md` under "## Today's Plan".
|
||||||
|
2. Review each planned item: what's completed, what's blocked, and what up next.
|
||||||
|
3. For any blockers, resolve them yourself or escalate to the board.
|
||||||
|
4. If you're ahead, start on the next highest priority.
|
||||||
|
5. **Record progress updates** in the daily notes.
|
||||||
|
|
||||||
|
## 3. Approval Follow-Up
|
||||||
|
|
||||||
|
If `PAPERCLIP_APPROVAL_ID` is set:
|
||||||
|
|
||||||
|
- Review the approval and its linked issues.
|
||||||
|
- Close resolved issues or comment on what remains open.
|
||||||
|
|
||||||
|
## 4. Get Assignments
|
||||||
|
|
||||||
|
- `GET /api/companies/{companyId}/issues?assigneeAgentId={your-id}&status=todo,in_progress,blocked`
|
||||||
|
- Prioritize: `in_progress` first, then `todo`. Skip `blocked` unless you can unblock it.
|
||||||
|
- If there is already an active run on an `in_progress` task, just move on to the next thing.
|
||||||
|
- If `PAPERCLIP_TASK_ID` is set and assigned to you, prioritize that task.
|
||||||
|
|
||||||
|
## 5. Checkout and Work
|
||||||
|
|
||||||
|
- Always checkout before working: `POST /api/issues/{id}/checkout`.
|
||||||
|
- Never retry a 409 -- that task belongs to someone else.
|
||||||
|
- Do the work. Update status and comment when done.
|
||||||
|
|
||||||
|
## 6. Delegation
|
||||||
|
|
||||||
|
- Create subtasks with `POST /api/companies/{companyId}/issues`. Always set `parentId` and `goalId`.
|
||||||
|
- Use `paperclip-create-agent` skill when hiring new agents.
|
||||||
|
- Assign work to the right agent for the job.
|
||||||
|
|
||||||
|
## 7. Fact Extraction
|
||||||
|
|
||||||
|
1. Check for new conversations since last extraction.
|
||||||
|
2. Extract durable facts to the relevant entity in `$AGENT_HOME/life/` (PARA).
|
||||||
|
3. Update `$AGENT_HOME/memory/YYYY-MM-DD.md` with timeline entries.
|
||||||
|
4. Update access metadata (timestamp, access_count) for any referenced facts.
|
||||||
|
|
||||||
|
## 8. Exit
|
||||||
|
|
||||||
|
- Comment on any in_progress work before exiting.
|
||||||
|
- If no assignments and no valid mention-handoff, exit cleanly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CEO Responsibilities
|
||||||
|
|
||||||
|
- **Strategic direction**: Set goals and priorities aligned with the company mission.
|
||||||
|
- **Hiring**: Spin up new agents when capacity is needed.
|
||||||
|
- **Unblocking**: Escalate or resolve blockers for reports.
|
||||||
|
- **Budget awareness**: Above 80% spend, focus only on critical tasks.
|
||||||
|
- **Never look for unassigned work** -- only work on what is assigned to you.
|
||||||
|
- **Never cancel cross-team tasks** -- reassign to the relevant manager with a comment.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- Always use the Paperclip skill for coordination.
|
||||||
|
- Always include `X-Paperclip-Run-Id` header on mutating API calls.
|
||||||
|
- Comment in concise markdown: status line + bullets + links.
|
||||||
|
- Self-assign via checkout only when explicitly @-mentioned.
|
||||||
33
agents/ceo/SOUL.md
Normal file
33
agents/ceo/SOUL.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# SOUL.md -- CEO Persona
|
||||||
|
|
||||||
|
You are the CEO.
|
||||||
|
|
||||||
|
## Strategic Posture
|
||||||
|
|
||||||
|
- You own the P&L. Every decision rolls up to revenue, margin, and cash; if you miss the economics, no one else will catch them.
|
||||||
|
- Default to action. Ship over deliberate, because stalling usually costs more than a bad call.
|
||||||
|
- Hold the long view while executing the near term. Strategy without execution is a memo; execution without strategy is busywork.
|
||||||
|
- Protect focus hard. Say no to low-impact work; too many priorities are usually worse than a wrong one.
|
||||||
|
- In trade-offs, optimize for learning speed and reversibility. Move fast on two-way doors; slow down on one-way doors.
|
||||||
|
- Know the numbers cold. Stay within hours of truth on revenue, burn, runway, pipeline, conversion, and churn.
|
||||||
|
- Treat every dollar, headcount, and engineering hour as a bet. Know the thesis and expected return.
|
||||||
|
- Think in constraints, not wishes. Ask "what do we stop?" before "what do we add?"
|
||||||
|
- Hire slow, fire fast, and avoid leadership vacuums. The team is the strategy.
|
||||||
|
- Create organizational clarity. If priorities are unclear, it's on you; repeat strategy until it sticks.
|
||||||
|
- Pull for bad news and reward candor. If problems stop surfacing, you've lost your information edge.
|
||||||
|
- Stay close to the customer. Dashboards help, but regular firsthand conversations keep you honest.
|
||||||
|
- Be replaceable in operations and irreplaceable in judgment. Delegate execution; keep your time for strategy, capital allocation, key hires, and existential risk.
|
||||||
|
|
||||||
|
## Voice and Tone
|
||||||
|
|
||||||
|
- Be direct. Lead with the point, then give context. Never bury the ask.
|
||||||
|
- Write like you talk in a board meeting, not a blog post. Short sentences, active voice, no filler.
|
||||||
|
- Confident but not performative. You don't need to sound smart; you need to be clear.
|
||||||
|
- Match intensity to stakes. A product launch gets energy. A staffing call gets gravity. A Slack reply gets brevity.
|
||||||
|
- Skip the corporate warm-up. No "I hope this message finds you well." Get to it.
|
||||||
|
- Use plain language. If a simpler word works, use it. "Use" not "utilize." "Start" not "initiate."
|
||||||
|
- Own uncertainty when it exists. "I don't know yet" beats a hedged non-answer every time.
|
||||||
|
- Disagree openly, but without heat. Challenge ideas, not people.
|
||||||
|
- Keep praise specific and rare enough to mean something. "Good job" is noise. "The way you reframed the pricing model saved us a quarter" is signal.
|
||||||
|
- Default to async-friendly writing. Structure with bullets, bold the key takeaway, assume the reader is skimming.
|
||||||
|
- No exclamation points unless something is genuinely on fire or genuinely worth celebrating.
|
||||||
3
agents/ceo/TOOLS.md
Normal file
3
agents/ceo/TOOLS.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Tools
|
||||||
|
|
||||||
|
(Your tools will go here. Add notes about them as you acquire and use them.)
|
||||||
18
agents/ceo/memory/2026-03-05.md
Normal file
18
agents/ceo/memory/2026-03-05.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 2026-03-05 -- CEO Daily Notes
|
||||||
|
|
||||||
|
## Timeline
|
||||||
|
|
||||||
|
- **13:07** First heartbeat. GIT-1 already done (CEO setup + FE hire submitted).
|
||||||
|
- **13:07** Founding Engineer hire approved (approval c2d7622a). Agent ed7d27a9 is idle.
|
||||||
|
- **13:07** No assignments in inbox. Woke on `issue_commented` for already-done GIT-1. Clean exit.
|
||||||
|
|
||||||
|
## Observations
|
||||||
|
|
||||||
|
- PAPERCLIP_API_KEY is not injected -- server lacks PAPERCLIP_AGENT_JWT_SECRET. Board-level fallback works for reads but /agents/me returns 401. Workaround: use company agents list endpoint.
|
||||||
|
- Company prefix is GIT.
|
||||||
|
- Two agents active: CEO (me, d584ded4), FoundingEngineer (ed7d27a9, idle).
|
||||||
|
|
||||||
|
## Today's Plan
|
||||||
|
|
||||||
|
1. Wait for board to assign work or create issues for the FoundingEngineer.
|
||||||
|
2. When work arrives, delegate to FE and track.
|
||||||
24
agents/founding-engineer/AGENTS.md
Normal file
24
agents/founding-engineer/AGENTS.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
You are the Founding Engineer.
|
||||||
|
|
||||||
|
Your home directory is $AGENT_HOME. Everything personal to you -- life, memory, knowledge -- lives there.
|
||||||
|
|
||||||
|
Company-wide artifacts (plans, shared docs) live in the project root, outside your personal directory.
|
||||||
|
|
||||||
|
## Project Context
|
||||||
|
|
||||||
|
This is a Rust CLI tool called `lore` for local GitLab data management with SQLite. The codebase uses Cargo, pedantic clippy lints, and forbids unsafe code. See the project CLAUDE.md for full toolchain and workflow details.
|
||||||
|
|
||||||
|
## Your Role
|
||||||
|
|
||||||
|
You are the primary individual contributor. You write code, fix bugs, add features, and ship. You report to the CEO.
|
||||||
|
|
||||||
|
## Safety Considerations
|
||||||
|
|
||||||
|
- Never exfiltrate secrets or private data.
|
||||||
|
- Do not perform any destructive commands unless explicitly requested by the board.
|
||||||
|
- Always run `cargo check`, `cargo clippy`, and `cargo fmt --check` after code changes.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- `$AGENT_HOME/HEARTBEAT.md` -- execution checklist. Run every heartbeat.
|
||||||
|
- Project `CLAUDE.md` -- toolchain, workflow, and project conventions.
|
||||||
21
build.rs
Normal file
21
build.rs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
fn main() {
|
||||||
|
let hash = std::process::Command::new("git")
|
||||||
|
.args(["rev-parse", "--short", "HEAD"])
|
||||||
|
.output()
|
||||||
|
.ok()
|
||||||
|
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let hash = hash.trim();
|
||||||
|
println!("cargo:rustc-env=GIT_HASH={hash}");
|
||||||
|
|
||||||
|
// Combined version string for clap --version flag
|
||||||
|
let pkg_version = std::env::var("CARGO_PKG_VERSION").unwrap_or_default();
|
||||||
|
if hash.is_empty() {
|
||||||
|
println!("cargo:rustc-env=LORE_VERSION={pkg_version}");
|
||||||
|
} else {
|
||||||
|
println!("cargo:rustc-env=LORE_VERSION={pkg_version} ({hash})");
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("cargo:rerun-if-changed=.git/HEAD");
|
||||||
|
println!("cargo:rerun-if-changed=.git/refs/heads");
|
||||||
|
}
|
||||||
388
command-restructure/CLI_AUDIT.md
Normal file
388
command-restructure/CLI_AUDIT.md
Normal file
@@ -0,0 +1,388 @@
|
|||||||
|
# Gitlore CLI Command Audit
|
||||||
|
|
||||||
|
## 1. Full Command Inventory
|
||||||
|
|
||||||
|
**29 visible + 4 hidden + 2 stub = 35 total command surface**
|
||||||
|
|
||||||
|
| # | Command | Aliases | Args | Flags | Purpose |
|
||||||
|
|---|---------|---------|------|-------|---------|
|
||||||
|
| 1 | `issues` | `issue` | `[IID]` | 15 | List/show issues |
|
||||||
|
| 2 | `mrs` | `mr`, `merge-requests` | `[IID]` | 16 | List/show MRs |
|
||||||
|
| 3 | `notes` | `note` | — | 16 | List notes |
|
||||||
|
| 4 | `search` | `find`, `query` | `<QUERY>` | 13 | Hybrid FTS+vector search |
|
||||||
|
| 5 | `timeline` | — | `<QUERY>` | 11 | Chronological event reconstruction |
|
||||||
|
| 6 | `who` | — | `[TARGET]` | 16 | People intelligence (5 modes) |
|
||||||
|
| 7 | `me` | — | — | 10 | Personal dashboard |
|
||||||
|
| 8 | `file-history` | — | `<PATH>` | 6 | MRs that touched a file |
|
||||||
|
| 9 | `trace` | — | `<PATH>` | 5 | file->MR->issue->discussion chain |
|
||||||
|
| 10 | `drift` | — | `<TYPE> <IID>` | 3 | Discussion divergence detection |
|
||||||
|
| 11 | `related` | — | `<QUERY_OR_TYPE> [IID]` | 3 | Semantic similarity |
|
||||||
|
| 12 | `count` | — | `<ENTITY>` | 2 | Count entities |
|
||||||
|
| 13 | `sync` | — | — | 14 | Full pipeline: ingest+docs+embed |
|
||||||
|
| 14 | `ingest` | — | `[ENTITY]` | 5 | Fetch from GitLab API |
|
||||||
|
| 15 | `generate-docs` | — | — | 2 | Build searchable documents |
|
||||||
|
| 16 | `embed` | — | — | 2 | Generate vector embeddings |
|
||||||
|
| 17 | `status` | `st` | — | 0 | Last sync times per project |
|
||||||
|
| 18 | `health` | — | — | 0 | Quick pre-flight (exit code only) |
|
||||||
|
| 19 | `doctor` | — | — | 0 | Full environment diagnostic |
|
||||||
|
| 20 | `stats` | `stat` | — | 3 | Document/index statistics |
|
||||||
|
| 21 | `init` | — | — | 6 | Setup config + database |
|
||||||
|
| 22 | `auth` | — | — | 0 | Verify GitLab token |
|
||||||
|
| 23 | `token` | — | subcommand | 1-2 | Token CRUD (set/show) |
|
||||||
|
| 24 | `cron` | — | subcommand | 0-1 | Auto-sync scheduling |
|
||||||
|
| 25 | `migrate` | — | — | 0 | Apply DB migrations |
|
||||||
|
| 26 | `robot-docs` | — | — | 1 | Agent self-discovery manifest |
|
||||||
|
| 27 | `completions` | — | `<SHELL>` | 0 | Shell completions |
|
||||||
|
| 28 | `version` | — | — | 0 | Version info |
|
||||||
|
| 29 | *help* | — | — | — | (clap built-in) |
|
||||||
|
| | **Hidden/deprecated:** | | | | |
|
||||||
|
| 30 | `list` | — | `<ENTITY>` | 14 | deprecated, use issues/mrs |
|
||||||
|
| 31 | `auth-test` | — | — | 0 | deprecated, use auth |
|
||||||
|
| 32 | `sync-status` | — | — | 0 | deprecated, use status |
|
||||||
|
| 33 | `backup` | — | — | 0 | Stub (not implemented) |
|
||||||
|
| 34 | `reset` | — | — | 1 | Stub (not implemented) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Semantic Overlap Analysis
|
||||||
|
|
||||||
|
### Cluster A: "Is the system working?" (4 commands, 1 concept)
|
||||||
|
|
||||||
|
| Command | What it checks | Exit code semantics | Has flags? |
|
||||||
|
|---------|---------------|---------------------|------------|
|
||||||
|
| `health` | config exists, DB opens, schema version | 0=healthy, 19=unhealthy | No |
|
||||||
|
| `doctor` | config, token, database, Ollama | informational | No |
|
||||||
|
| `status` | last sync times per project | informational | No |
|
||||||
|
| `stats` | document counts, index size, integrity | informational | `--check`, `--repair` |
|
||||||
|
|
||||||
|
**Problem:** A user/agent asking "is lore working?" must choose among four commands. `health` is a strict subset of `doctor`. `status` and `stats` are near-homonyms that answer different questions -- sync recency vs. index health. `count` (Cluster E) also overlaps with what `stats` reports.
|
||||||
|
|
||||||
|
**Cognitive cost:** High. The CLI literature (Clig.dev, Heroku CLI design guide, 12-factor CLI) consistently warns against >2 "status" commands. Users build a mental model of "the status command" -- when there are four, they pick wrong or give up.
|
||||||
|
|
||||||
|
**Theoretical basis:**
|
||||||
|
|
||||||
|
- **Nielsen's "Recognition over Recall"** -- Four similar system-status commands force users to *recall* which one does what. One command with progressive disclosure (flags for depth) lets them *recognize* the option they need. This is doubly important for LLM agents, which perform better with fewer top-level choices and compositional flags.
|
||||||
|
|
||||||
|
- **Fitts's Law for CLIs** -- Command discovery cost is proportional to list length. Each additional top-level command adds scanning time for humans and token cost for robots.
|
||||||
|
|
||||||
|
### Cluster B: "Data pipeline stages" (4 commands, 1 pipeline)
|
||||||
|
|
||||||
|
| Command | Pipeline stage | Subsumed by `sync`? |
|
||||||
|
|---------|---------------|---------------------|
|
||||||
|
| `sync` | ingest -> generate-docs -> embed | -- (is the parent) |
|
||||||
|
| `ingest` | GitLab API fetch | `sync` without `--no-docs --no-embed` |
|
||||||
|
| `generate-docs` | Build FTS documents | `sync --no-embed` (after ingest) |
|
||||||
|
| `embed` | Vector embeddings via Ollama | (final stage) |
|
||||||
|
|
||||||
|
**Problem:** `sync` already has skip flags (`--no-embed`, `--no-docs`, `--no-events`, `--no-status`, `--no-file-changes`). The individual stage commands duplicate this with less control -- `ingest` has `--full`, `--force`, `--dry-run`, but `sync` also has all three.
|
||||||
|
|
||||||
|
The standalone commands exist for granular debugging, but in practice they're reached for <5% of the time. They inflate the help screen while `sync` handles 95% of use cases.
|
||||||
|
|
||||||
|
### Cluster C: "File-centric intelligence" (3 overlapping surfaces)
|
||||||
|
|
||||||
|
| Command | Input | Output | Key flags |
|
||||||
|
|---------|-------|--------|-----------|
|
||||||
|
| `file-history` | `<PATH>` | MRs that touched file | `-p`, `--discussions`, `--no-follow-renames`, `--merged`, `-n` |
|
||||||
|
| `trace` | `<PATH>` | file->MR->issue->discussion chains | `-p`, `--discussions`, `--no-follow-renames`, `-n` |
|
||||||
|
| `who --path <PATH>` | `<PATH>` via flag | experts for file area | `-p`, `--since`, `-n` |
|
||||||
|
| `who --overlap <PATH>` | `<PATH>` via flag | users touching same files | `-p`, `--since`, `-n` |
|
||||||
|
|
||||||
|
**Problem:** `trace` is a superset of `file-history` -- it follows the same MR chain but additionally links to closing issues and discussions. They share 4 of 5 filter flags. A user who wants "what happened to this file?" has to choose between two commands that sound nearly identical.
|
||||||
|
|
||||||
|
### Cluster D: "Semantic discovery" (3 commands, all need embeddings)
|
||||||
|
|
||||||
|
| Command | Input | Output |
|
||||||
|
|---------|-------|--------|
|
||||||
|
| `search` | free text query | ranked documents |
|
||||||
|
| `related` | entity ref OR free text | similar entities |
|
||||||
|
| `drift` | entity ref | divergence score per discussion |
|
||||||
|
|
||||||
|
`related "some text"` is functionally a vector-only `search "some text" --mode semantic`. The difference is that `related` can also seed from an entity (issues 42), while `search` only accepts text.
|
||||||
|
|
||||||
|
`drift` is specialized enough to stand alone, but it's only used on issues and has a single non-project flag (`--threshold`).
|
||||||
|
|
||||||
|
### Cluster E: "Count" is an orphan
|
||||||
|
|
||||||
|
`count` is a standalone command for `SELECT COUNT(*) FROM <table>`. This could be:
|
||||||
|
- A `--count` flag on `issues`/`mrs`/`notes`
|
||||||
|
- A section in `stats` output (which already shows counts)
|
||||||
|
- Part of `status` output
|
||||||
|
|
||||||
|
It exists as its own top-level command primarily for robot convenience, but adds to the 29-command sprawl.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Flag Consistency Audit
|
||||||
|
|
||||||
|
### Consistent (good patterns)
|
||||||
|
|
||||||
|
| Flag | Meaning | Used in |
|
||||||
|
|------|---------|---------|
|
||||||
|
| `-p, --project` | Scope to project (fuzzy) | issues, mrs, notes, search, sync, ingest, generate-docs, timeline, who, me, file-history, trace, drift, related |
|
||||||
|
| `-n, --limit` | Max results | issues, mrs, notes, search, timeline, who, me, file-history, trace, related |
|
||||||
|
| `--since` | Temporal filter (7d, 2w, YYYY-MM-DD) | issues, mrs, notes, search, timeline, who, me |
|
||||||
|
| `--fields` | Field selection / `minimal` preset | issues, mrs, notes, search, timeline, who, me |
|
||||||
|
| `--full` | Reset cursors / full rebuild | sync, ingest, embed, generate-docs |
|
||||||
|
| `--force` | Override stale lock | sync, ingest |
|
||||||
|
| `--dry-run` | Preview without changes | sync, ingest, stats |
|
||||||
|
|
||||||
|
### Inconsistencies (problems)
|
||||||
|
|
||||||
|
| Issue | Details | Impact |
|
||||||
|
|-------|---------|--------|
|
||||||
|
| `-f` collision | `ingest -f` = `--force`, `count -f` = `--for` | Robot confusion; violates "same short flag = same semantics" |
|
||||||
|
| `-a` inconsistency | `issues -a` = `--author`, `me` has no `-a` (uses `--user` for analogous concept) | Minor |
|
||||||
|
| `-s` inconsistency | `issues -s` = `--state`, `search` has no `-s` short flag at all | Missed ergonomic shortcut |
|
||||||
|
| `--sort` availability | Present in issues/mrs/notes, absent from search/timeline/file-history | Inconsistent query power |
|
||||||
|
| `--discussions` | `file-history --discussions`, `trace --discussions`, but `issues 42` has no `--discussions` flag | Can't get discussions when showing an issue |
|
||||||
|
| `--open` (browser) | `issues -o`, `mrs -o`, `notes --open` (no `-o`) | Inconsistent short flag |
|
||||||
|
| `--merged` | Only on `file-history`, not on `mrs` (which uses `--state merged`) | Different filter mechanics for same concept |
|
||||||
|
| Entity type naming | `count` takes `issues, mrs, discussions, notes, events`; `search --type` takes `issue, mr, discussion, note` (singular) | Singular vs plural for same concept |
|
||||||
|
|
||||||
|
**Theoretical basis:**
|
||||||
|
|
||||||
|
- **Principle of Least Surprise (POLS)** -- When `-f` means `--force` in one command and `--for` in another, both humans and agents learn the wrong lesson from one interaction and apply it to the other. CLI design guides (GNU standards, POSIX conventions, clig.dev) are unanimous: short flags should have consistent semantics across all subcommands.
|
||||||
|
|
||||||
|
- **Singular/plural inconsistency** (`issues` vs `issue` as entity type values) is particularly harmful for LLM agents, which use pattern matching on prior successful invocations. If `lore count issues` works, the agent will try `lore search --type issues` -- and get a parse error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Robot Ergonomics Assessment
|
||||||
|
|
||||||
|
### Strengths (well above average for a CLI)
|
||||||
|
|
||||||
|
| Feature | Rating | Notes |
|
||||||
|
|---------|--------|-------|
|
||||||
|
| Structured output | Excellent | Consistent `{ok, data, meta}` envelope |
|
||||||
|
| Auto-detection | Excellent | Non-TTY -> robot mode, `LORE_ROBOT` env var |
|
||||||
|
| Error output | Excellent | Structured JSON to stderr with `actions` array for recovery |
|
||||||
|
| Exit codes | Excellent | 20 distinct, well-documented codes |
|
||||||
|
| Self-discovery | Excellent | `robot-docs` manifest, `--brief` for token savings |
|
||||||
|
| Typo tolerance | Excellent | Autocorrect with confidence scores + structured warnings |
|
||||||
|
| Field selection | Good | `--fields minimal` saves ~60% tokens |
|
||||||
|
| No-args behavior | Good | Robot mode auto-outputs robot-docs |
|
||||||
|
|
||||||
|
### Weaknesses
|
||||||
|
|
||||||
|
| Issue | Severity | Recommendation |
|
||||||
|
|-------|----------|----------------|
|
||||||
|
| 29 commands in robot-docs manifest | High | Agents spend tokens evaluating which command to use. Grouping would reduce decision space. |
|
||||||
|
| `status`/`stats`/`stat` near-homonyms | High | LLMs are particularly susceptible to surface-level lexical confusion. `stat` is an alias for `stats` while `status` is a different command -- this guarantees agent errors. |
|
||||||
|
| Singular vs plural entity types | Medium | `count issues` works but `search --type issues` fails. Agents learn from one and apply to the other. |
|
||||||
|
| Overlapping file commands | Medium | Agent must decide between `trace`, `file-history`, and `who --path`. The decision tree isn't obvious from names alone. |
|
||||||
|
| `count` as separate command | Low | Could be a flag; standalone command inflates the decision space |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Human Ergonomics Assessment
|
||||||
|
|
||||||
|
### Strengths
|
||||||
|
|
||||||
|
| Feature | Rating | Notes |
|
||||||
|
|---------|--------|-------|
|
||||||
|
| Help text quality | Excellent | Every command has examples, help headings organize flags |
|
||||||
|
| Short flags | Good | `-p`, `-n`, `-s`, `-a`, `-J` cover 80% of common use |
|
||||||
|
| Alias coverage | Good | `issue`/`issues`, `mr`/`mrs`, `st`/`status`, `find`/`search` |
|
||||||
|
| Subcommand inference | Good | `lore issu` -> `issues` via clap infer |
|
||||||
|
| Color/icon system | Good | Auto, with overrides |
|
||||||
|
|
||||||
|
### Weaknesses
|
||||||
|
|
||||||
|
| Issue | Severity | Recommendation |
|
||||||
|
|-------|----------|----------------|
|
||||||
|
| 29 commands in flat help | High | Doesn't fit one terminal screen. No grouping -> overwhelming |
|
||||||
|
| `status` vs `stats` naming | High | Humans will type wrong one repeatedly |
|
||||||
|
| `health` vs `doctor` distinction | Medium | "Which one do I run?" -- unclear from names |
|
||||||
|
| `who` 5-mode overload | Medium | Help text is long; mode exclusions are complex |
|
||||||
|
| Pipeline stages as top-level | Low | `ingest`/`generate-docs`/`embed` rarely used directly but clutter help |
|
||||||
|
| `generate-docs` is 14 chars | Low | Longest command name; `gen-docs` or `gendocs` would help |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Proposals (Ranked by Impact x Feasibility)
|
||||||
|
|
||||||
|
### P1: Help Grouping (HIGH impact, LOW effort)
|
||||||
|
|
||||||
|
**Problem:** 29 flat commands -> information overload.
|
||||||
|
|
||||||
|
**Fix:** Use clap's `help_heading` on subcommands to group them:
|
||||||
|
|
||||||
|
```
|
||||||
|
Query:
|
||||||
|
issues List or show issues [aliases: issue]
|
||||||
|
mrs List or show merge requests [aliases: mr]
|
||||||
|
notes List notes from discussions [aliases: note]
|
||||||
|
search Search indexed documents [aliases: find]
|
||||||
|
count Count entities in local database
|
||||||
|
|
||||||
|
Intelligence:
|
||||||
|
timeline Chronological timeline of events
|
||||||
|
who People intelligence: experts, workload, overlap
|
||||||
|
me Personal work dashboard
|
||||||
|
|
||||||
|
File Analysis:
|
||||||
|
trace Trace why code was introduced
|
||||||
|
file-history Show MRs that touched a file
|
||||||
|
related Find semantically related entities
|
||||||
|
drift Detect discussion divergence
|
||||||
|
|
||||||
|
Data Pipeline:
|
||||||
|
sync Run full sync pipeline
|
||||||
|
ingest Ingest data from GitLab
|
||||||
|
generate-docs Generate searchable documents
|
||||||
|
embed Generate vector embeddings
|
||||||
|
|
||||||
|
System:
|
||||||
|
init Initialize configuration and database
|
||||||
|
status Show sync state [aliases: st]
|
||||||
|
health Quick health check
|
||||||
|
doctor Check environment health
|
||||||
|
stats Document and index statistics [aliases: stat]
|
||||||
|
auth Verify GitLab authentication
|
||||||
|
token Manage stored GitLab token
|
||||||
|
migrate Run pending database migrations
|
||||||
|
cron Manage automatic syncing
|
||||||
|
completions Generate shell completions
|
||||||
|
robot-docs Agent self-discovery manifest
|
||||||
|
version Show version information
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effort:** ~20 lines of `#[command(help_heading = "...")]` annotations. No behavior changes.
|
||||||
|
|
||||||
|
### P2: Resolve `status`/`stats` Confusion (HIGH impact, LOW effort)
|
||||||
|
|
||||||
|
**Option A (recommended):** Rename `stats` -> `index`.
|
||||||
|
- `lore status` = when did I last sync? (pipeline state)
|
||||||
|
- `lore index` = how big is my index? (data inventory)
|
||||||
|
- The alias `stat` goes away (it was causing confusion anyway)
|
||||||
|
|
||||||
|
**Option B:** Rename `status` -> `sync-state` and `stats` -> `db-stats`. More descriptive but longer.
|
||||||
|
|
||||||
|
**Option C:** Merge both under `check` (see P4).
|
||||||
|
|
||||||
|
### P3: Fix Singular/Plural Entity Type Inconsistency (MEDIUM impact, TRIVIAL effort)
|
||||||
|
|
||||||
|
Accept both singular and plural forms everywhere:
|
||||||
|
- `count` already takes `issues` (plural) -- also accept `issue`
|
||||||
|
- `search --type` already takes `issue` (singular) -- also accept `issues`
|
||||||
|
- `drift` takes `issues` -- also accept `issue`
|
||||||
|
|
||||||
|
This is a ~10 line change in the value parsers and eliminates an entire class of agent errors.
|
||||||
|
|
||||||
|
### P4: Merge `health` + `doctor` (MEDIUM impact, LOW effort)
|
||||||
|
|
||||||
|
`health` is a fast subset of `doctor`. Merge:
|
||||||
|
- `lore doctor` = full diagnostic (current behavior)
|
||||||
|
- `lore doctor --quick` = fast pre-flight, exit-code-only (current `health`)
|
||||||
|
- Drop `health` as a separate command, add a hidden alias for backward compat
|
||||||
|
|
||||||
|
### P5: Fix `-f` Short Flag Collision (MEDIUM impact, TRIVIAL effort)
|
||||||
|
|
||||||
|
Change `count`'s `-f, --for` to just `--for` (no short flag). `-f` should mean `--force` project-wide, or nowhere.
|
||||||
|
|
||||||
|
### P6: Consolidate `trace` + `file-history` (MEDIUM impact, MEDIUM effort)
|
||||||
|
|
||||||
|
`trace` already does everything `file-history` does plus more. Options:
|
||||||
|
|
||||||
|
**Option A:** Make `file-history` an alias for `trace --flat` (shows MR list without issue/discussion linking).
|
||||||
|
|
||||||
|
**Option B:** Add `--mrs-only` to `trace` that produces `file-history` output. Deprecate `file-history` with a hidden alias.
|
||||||
|
|
||||||
|
Either way, one fewer top-level command and no lost functionality.
|
||||||
|
|
||||||
|
### P7: Hide Pipeline Sub-stages (LOW impact, TRIVIAL effort)
|
||||||
|
|
||||||
|
Move `ingest`, `generate-docs`, `embed` to `#[command(hide = true)]`. They remain usable but don't clutter `--help`. Direct users to `sync` with stage-skip flags.
|
||||||
|
|
||||||
|
For power users who need individual stages, document in `sync --help`:
|
||||||
|
```
|
||||||
|
To run individual stages:
|
||||||
|
lore ingest # Fetch from GitLab only
|
||||||
|
lore generate-docs # Rebuild documents only
|
||||||
|
lore embed # Re-embed only
|
||||||
|
```
|
||||||
|
|
||||||
|
### P8: Make `count` a Flag, Not a Command (LOW impact, MEDIUM effort)
|
||||||
|
|
||||||
|
Add `--count` to `issues` and `mrs`:
|
||||||
|
```bash
|
||||||
|
lore issues --count # replaces: lore count issues
|
||||||
|
lore mrs --count # replaces: lore count mrs
|
||||||
|
lore notes --count # replaces: lore count notes
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep `count` as a hidden alias for backward compatibility. Removes one top-level command.
|
||||||
|
|
||||||
|
### P9: Consistent `--open` Short Flag (LOW impact, TRIVIAL effort)
|
||||||
|
|
||||||
|
`notes --open` lacks the `-o` shorthand that `issues` and `mrs` have. Add it.
|
||||||
|
|
||||||
|
### P10: Add `--sort` to `search` (LOW impact, LOW effort)
|
||||||
|
|
||||||
|
`search` returns ranked results but offers no `--sort` override. Adding `--sort=score,created,updated` would bring it in line with `issues`/`mrs`/`notes`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Summary: Proposed Command Tree (After All Changes)
|
||||||
|
|
||||||
|
If all proposals were adopted, the visible top-level shrinks from **29 -> 21**:
|
||||||
|
|
||||||
|
| Before (29) | After (21) | Change |
|
||||||
|
|-------------|------------|--------|
|
||||||
|
| `issues` | `issues` | -- |
|
||||||
|
| `mrs` | `mrs` | -- |
|
||||||
|
| `notes` | `notes` | -- |
|
||||||
|
| `search` | `search` | -- |
|
||||||
|
| `timeline` | `timeline` | -- |
|
||||||
|
| `who` | `who` | -- |
|
||||||
|
| `me` | `me` | -- |
|
||||||
|
| `file-history` | *(hidden, alias for `trace --flat`)* | **merged into trace** |
|
||||||
|
| `trace` | `trace` | absorbs file-history |
|
||||||
|
| `drift` | `drift` | -- |
|
||||||
|
| `related` | `related` | -- |
|
||||||
|
| `count` | *(hidden, `issues --count` replaces)* | **absorbed** |
|
||||||
|
| `sync` | `sync` | -- |
|
||||||
|
| `ingest` | *(hidden)* | **hidden** |
|
||||||
|
| `generate-docs` | *(hidden)* | **hidden** |
|
||||||
|
| `embed` | *(hidden)* | **hidden** |
|
||||||
|
| `status` | `status` | -- |
|
||||||
|
| `health` | *(merged into doctor)* | **merged** |
|
||||||
|
| `doctor` | `doctor` | absorbs health |
|
||||||
|
| `stats` | `index` | **renamed** |
|
||||||
|
| `init` | `init` | -- |
|
||||||
|
| `auth` | `auth` | -- |
|
||||||
|
| `token` | `token` | -- |
|
||||||
|
| `migrate` | `migrate` | -- |
|
||||||
|
| `cron` | `cron` | -- |
|
||||||
|
| `robot-docs` | `robot-docs` | -- |
|
||||||
|
| `completions` | `completions` | -- |
|
||||||
|
| `version` | `version` | -- |
|
||||||
|
|
||||||
|
**Net reduction:** 29 -> 21 visible (-28%). The hidden commands remain fully functional and documented in `robot-docs` for agents that already use them.
|
||||||
|
|
||||||
|
**Theoretical basis:**
|
||||||
|
|
||||||
|
- **Miller's Law** -- Humans can hold 7+/-2 items in working memory. 29 commands far exceeds this. Even with help grouping (P1), the sheer count creates decision fatigue. The literature on CLI design (Heroku's "12-Factor CLI", clig.dev's "Command Line Interface Guidelines") recommends 10-15 top-level commands maximum, with grouping or nesting for anything beyond.
|
||||||
|
|
||||||
|
- **For LLM agents specifically:** Research on tool-use with large tool sets (Schick et al. 2023, Qin et al. 2023) shows that agent accuracy degrades as the tool count increases, roughly following an inverse log curve. Reducing from 29 to 21 commands in the robot-docs manifest would measurably improve agent command selection accuracy.
|
||||||
|
|
||||||
|
- **Backward compatibility is free:** Since AGENTS.md says "we don't care about backward compatibility," hidden aliases cost nothing and prevent breakage for agents with cached robot-docs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Priority Matrix
|
||||||
|
|
||||||
|
| Proposal | Impact | Effort | Risk | Recommended Order |
|
||||||
|
|----------|--------|--------|------|-------------------|
|
||||||
|
| P1: Help grouping | High | Trivial | None | **Do first** |
|
||||||
|
| P3: Singular/plural fix | Medium | Trivial | None | **Do first** |
|
||||||
|
| P5: Fix `-f` collision | Medium | Trivial | None | **Do first** |
|
||||||
|
| P9: `notes -o` shorthand | Low | Trivial | None | **Do first** |
|
||||||
|
| P2: Rename `stats`->`index` | High | Low | Alias needed | **Do second** |
|
||||||
|
| P4: Merge health->doctor | Medium | Low | Alias needed | **Do second** |
|
||||||
|
| P7: Hide pipeline stages | Low | Trivial | Needs docs update | **Do second** |
|
||||||
|
| P6: Merge file-history->trace | Medium | Medium | Flag design | **Plan carefully** |
|
||||||
|
| P8: count -> --count flag | Low | Medium | Compat shim | **Plan carefully** |
|
||||||
|
| P10: `--sort` on search | Low | Low | None | **When convenient** |
|
||||||
|
|
||||||
|
The "do first" tier is 4 changes that could ship in a single commit with zero risk and immediate ergonomic improvement for both humans and agents.
|
||||||
966
command-restructure/IMPLEMENTATION_PLAN.md
Normal file
966
command-restructure/IMPLEMENTATION_PLAN.md
Normal file
@@ -0,0 +1,966 @@
|
|||||||
|
# Command Restructure: Implementation Plan
|
||||||
|
|
||||||
|
**Reference:** `command-restructure/CLI_AUDIT.md`
|
||||||
|
**Scope:** 10 proposals, 3 implementation phases, estimated ~15 files touched
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Zero-Risk Quick Wins (1 commit)
|
||||||
|
|
||||||
|
These four changes are purely additive -- no behavior changes, no renames, no removed commands.
|
||||||
|
|
||||||
|
### P1: Help Grouping
|
||||||
|
|
||||||
|
**Goal:** Group the 29 visible commands into 5 semantic clusters in `--help` output.
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs` (lines 117-399, the `Commands` enum)
|
||||||
|
|
||||||
|
**Changes:** Add `#[command(help_heading = "...")]` to each variant:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
pub enum Commands {
|
||||||
|
// ── Query ──────────────────────────────────────────────
|
||||||
|
/// List or show issues
|
||||||
|
#[command(visible_alias = "issue", help_heading = "Query")]
|
||||||
|
Issues(IssuesArgs),
|
||||||
|
|
||||||
|
/// List or show merge requests
|
||||||
|
#[command(visible_alias = "mr", alias = "merge-requests", alias = "merge-request", help_heading = "Query")]
|
||||||
|
Mrs(MrsArgs),
|
||||||
|
|
||||||
|
/// List notes from discussions
|
||||||
|
#[command(visible_alias = "note", help_heading = "Query")]
|
||||||
|
Notes(NotesArgs),
|
||||||
|
|
||||||
|
/// Search indexed documents
|
||||||
|
#[command(visible_alias = "find", alias = "query", help_heading = "Query")]
|
||||||
|
Search(SearchArgs),
|
||||||
|
|
||||||
|
/// Count entities in local database
|
||||||
|
#[command(help_heading = "Query")]
|
||||||
|
Count(CountArgs),
|
||||||
|
|
||||||
|
// ── Intelligence ───────────────────────────────────────
|
||||||
|
/// Show a chronological timeline of events matching a query
|
||||||
|
#[command(help_heading = "Intelligence")]
|
||||||
|
Timeline(TimelineArgs),
|
||||||
|
|
||||||
|
/// People intelligence: experts, workload, active discussions, overlap
|
||||||
|
#[command(help_heading = "Intelligence")]
|
||||||
|
Who(WhoArgs),
|
||||||
|
|
||||||
|
/// Personal work dashboard: open issues, authored/reviewing MRs, activity
|
||||||
|
#[command(help_heading = "Intelligence")]
|
||||||
|
Me(MeArgs),
|
||||||
|
|
||||||
|
// ── File Analysis ──────────────────────────────────────
|
||||||
|
/// Trace why code was introduced: file -> MR -> issue -> discussion
|
||||||
|
#[command(help_heading = "File Analysis")]
|
||||||
|
Trace(TraceArgs),
|
||||||
|
|
||||||
|
/// Show MRs that touched a file, with linked discussions
|
||||||
|
#[command(name = "file-history", help_heading = "File Analysis")]
|
||||||
|
FileHistory(FileHistoryArgs),
|
||||||
|
|
||||||
|
/// Find semantically related entities via vector search
|
||||||
|
#[command(help_heading = "File Analysis", ...)]
|
||||||
|
Related { ... },
|
||||||
|
|
||||||
|
/// Detect discussion divergence from original intent
|
||||||
|
#[command(help_heading = "File Analysis", ...)]
|
||||||
|
Drift { ... },
|
||||||
|
|
||||||
|
// ── Data Pipeline ──────────────────────────────────────
|
||||||
|
/// Run full sync pipeline: ingest -> generate-docs -> embed
|
||||||
|
#[command(help_heading = "Data Pipeline")]
|
||||||
|
Sync(SyncArgs),
|
||||||
|
|
||||||
|
/// Ingest data from GitLab
|
||||||
|
#[command(help_heading = "Data Pipeline")]
|
||||||
|
Ingest(IngestArgs),
|
||||||
|
|
||||||
|
/// Generate searchable documents from ingested data
|
||||||
|
#[command(name = "generate-docs", help_heading = "Data Pipeline")]
|
||||||
|
GenerateDocs(GenerateDocsArgs),
|
||||||
|
|
||||||
|
/// Generate vector embeddings for documents via Ollama
|
||||||
|
#[command(help_heading = "Data Pipeline")]
|
||||||
|
Embed(EmbedArgs),
|
||||||
|
|
||||||
|
// ── System ─────────────────────────────────────────────
|
||||||
|
// (init, status, health, doctor, stats, auth, token, migrate, cron,
|
||||||
|
// completions, robot-docs, version -- all get help_heading = "System")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore --help` shows grouped output
|
||||||
|
- All existing commands still work identically
|
||||||
|
- `lore robot-docs` output unchanged (robot-docs is hand-crafted, not derived from clap)
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/mod.rs` only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P3: Singular/Plural Entity Type Fix
|
||||||
|
|
||||||
|
**Goal:** Accept both `issue`/`issues`, `mr`/`mrs` everywhere entity types are value-parsed.
|
||||||
|
|
||||||
|
**File:** `src/cli/args.rs`
|
||||||
|
|
||||||
|
**Change 1 -- `CountArgs.entity` (line 819):**
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
#[arg(value_parser = ["issues", "mrs", "discussions", "notes", "events"])]
|
||||||
|
pub entity: String,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
#[arg(value_parser = ["issue", "issues", "mr", "mrs", "discussion", "discussions", "note", "notes", "event", "events"])]
|
||||||
|
pub entity: String,
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/cli/args.rs`
|
||||||
|
|
||||||
|
**Change 2 -- `SearchArgs.source_type` (line 369):**
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
#[arg(long = "type", value_parser = ["issue", "mr", "discussion", "note"], ...)]
|
||||||
|
pub source_type: Option<String>,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
#[arg(long = "type", value_parser = ["issue", "issues", "mr", "mrs", "discussion", "discussions", "note", "notes"], ...)]
|
||||||
|
pub source_type: Option<String>,
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs`
|
||||||
|
|
||||||
|
**Change 3 -- `Drift.entity_type` (line 287):**
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
#[arg(value_parser = ["issues"])]
|
||||||
|
pub entity_type: String,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
#[arg(value_parser = ["issue", "issues"])]
|
||||||
|
pub entity_type: String,
|
||||||
|
```
|
||||||
|
|
||||||
|
**Normalization layer:** In the handlers that consume these values, normalize to the canonical form (plural for entity names, singular for source_type) so downstream code doesn't need changes:
|
||||||
|
|
||||||
|
**File:** `src/app/handlers.rs`
|
||||||
|
|
||||||
|
In `handle_count` (~line 409): Normalize entity string before passing to `run_count`:
|
||||||
|
```rust
|
||||||
|
let entity = match args.entity.as_str() {
|
||||||
|
"issue" => "issues",
|
||||||
|
"mr" => "mrs",
|
||||||
|
"discussion" => "discussions",
|
||||||
|
"note" => "notes",
|
||||||
|
"event" => "events",
|
||||||
|
other => other,
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
In `handle_search` (search handler): Normalize source_type:
|
||||||
|
```rust
|
||||||
|
let source_type = args.source_type.as_deref().map(|t| match t {
|
||||||
|
"issues" => "issue",
|
||||||
|
"mrs" => "mr",
|
||||||
|
"discussions" => "discussion",
|
||||||
|
"notes" => "note",
|
||||||
|
other => other,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
In `handle_drift` (~line 225): Normalize entity_type:
|
||||||
|
```rust
|
||||||
|
let entity_type = if entity_type == "issue" { "issues" } else { &entity_type };
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore count issue` works (same as `lore count issues`)
|
||||||
|
- `lore search --type issues 'foo'` works (same as `--type issue`)
|
||||||
|
- `lore drift issue 42` works (same as `drift issues 42`)
|
||||||
|
- All existing invocations unchanged
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P5: Fix `-f` Short Flag Collision
|
||||||
|
|
||||||
|
**Goal:** Remove `-f` shorthand from `count --for` so `-f` consistently means `--force` across the CLI.
|
||||||
|
|
||||||
|
**File:** `src/cli/args.rs` (line 823)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
#[arg(short = 'f', long = "for", value_parser = ["issue", "mr"])]
|
||||||
|
pub for_entity: Option<String>,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
#[arg(long = "for", value_parser = ["issue", "mr"])]
|
||||||
|
pub for_entity: Option<String>,
|
||||||
|
```
|
||||||
|
|
||||||
|
**Also update the value_parser to accept both forms** (while we're here):
|
||||||
|
```rust
|
||||||
|
#[arg(long = "for", value_parser = ["issue", "issues", "mr", "mrs"])]
|
||||||
|
pub for_entity: Option<String>,
|
||||||
|
```
|
||||||
|
|
||||||
|
And normalize in `handle_count`:
|
||||||
|
```rust
|
||||||
|
let for_entity = args.for_entity.as_deref().map(|f| match f {
|
||||||
|
"issues" => "issue",
|
||||||
|
"mrs" => "mr",
|
||||||
|
other => other,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs` (line 173) -- update the robot-docs entry:
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
"flags": ["<entity: issues|mrs|discussions|notes|events>", "-f/--for <issue|mr>"],
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
"flags": ["<entity: issues|mrs|discussions|notes|events>", "--for <issue|mr>"],
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore count notes --for mr` still works
|
||||||
|
- `lore count notes -f mr` now fails with a clear error (unknown flag `-f`)
|
||||||
|
- `lore ingest -f` still works (means `--force`)
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/args.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P9: Consistent `--open` Short Flag on `notes`
|
||||||
|
|
||||||
|
**Goal:** Add `-o` shorthand to `notes --open`, matching `issues` and `mrs`.
|
||||||
|
|
||||||
|
**File:** `src/cli/args.rs` (line 292)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
#[arg(long, help_heading = "Actions")]
|
||||||
|
pub open: bool,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
#[arg(short = 'o', long, help_heading = "Actions", overrides_with = "no_open")]
|
||||||
|
pub open: bool,
|
||||||
|
|
||||||
|
#[arg(long = "no-open", hide = true, overrides_with = "open")]
|
||||||
|
pub no_open: bool,
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore notes -o` opens first result in browser
|
||||||
|
- Matches behavior of `lore issues -o` and `lore mrs -o`
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/args.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 1 Commit Summary
|
||||||
|
|
||||||
|
**Files modified:**
|
||||||
|
1. `src/cli/mod.rs` -- help_heading on all Commands variants + drift value_parser
|
||||||
|
2. `src/cli/args.rs` -- singular/plural value_parsers, remove `-f` from count, add `-o` to notes
|
||||||
|
3. `src/app/handlers.rs` -- normalization of entity/source_type strings
|
||||||
|
4. `src/app/robot_docs.rs` -- update count flags documentation
|
||||||
|
|
||||||
|
**Test plan:**
|
||||||
|
```bash
|
||||||
|
cargo check --all-targets
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
cargo fmt --check
|
||||||
|
cargo test
|
||||||
|
lore --help # Verify grouped output
|
||||||
|
lore count issue # Verify singular accepted
|
||||||
|
lore search --type issues 'x' # Verify plural accepted
|
||||||
|
lore drift issue 42 # Verify singular accepted
|
||||||
|
lore notes -o # Verify short flag works
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Renames and Merges (2-3 commits)
|
||||||
|
|
||||||
|
These changes rename commands and merge overlapping ones. Hidden aliases preserve backward compatibility.
|
||||||
|
|
||||||
|
### P2: Rename `stats` -> `index`
|
||||||
|
|
||||||
|
**Goal:** Eliminate `status`/`stats`/`stat` confusion. `stats` becomes `index`.
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
/// Show document and index statistics
|
||||||
|
#[command(visible_alias = "stat", help_heading = "System")]
|
||||||
|
Stats(StatsArgs),
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
/// Show document and index statistics
|
||||||
|
#[command(visible_alias = "idx", alias = "stats", alias = "stat", help_heading = "System")]
|
||||||
|
Index(StatsArgs),
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `alias = "stats"` and `alias = "stat"` are hidden aliases (not `visible_alias`) -- old invocations still work, but `--help` shows `index`.
|
||||||
|
|
||||||
|
**File:** `src/main.rs` (line 257)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
Some(Commands::Stats(args)) => handle_stats(cli.config.as_deref(), args, robot_mode).await,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
Some(Commands::Index(args)) => handle_stats(cli.config.as_deref(), args, robot_mode).await,
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs` (line 181)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
"stats": {
|
||||||
|
"description": "Show document and index statistics",
|
||||||
|
...
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
"index": {
|
||||||
|
"description": "Show document and index statistics (formerly 'stats')",
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Also update references in:
|
||||||
|
- `robot_docs.rs` quick_start.lore_exclusive array (line 415): `"stats: Database statistics..."` -> `"index: Database statistics..."`
|
||||||
|
- `robot_docs.rs` aliases.deprecated_commands: add `"stats": "index"`, `"stat": "index"`
|
||||||
|
|
||||||
|
**File:** `src/cli/autocorrect.rs`
|
||||||
|
|
||||||
|
Update `CANONICAL_SUBCOMMANDS` (line 366-area):
|
||||||
|
```rust
|
||||||
|
// Replace "stats" with "index" in the canonical list
|
||||||
|
// Add ("stats", "index") and ("stat", "index") to SUBCOMMAND_ALIASES
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `COMMAND_FLAGS` (line 166-area):
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
("stats", &["--check", ...]),
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
("index", &["--check", ...]),
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/cli/robot.rs` -- update `expand_fields_preset` if any preset key is `"stats"` (currently no stats preset, so no change needed).
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore index` works (shows document/index stats)
|
||||||
|
- `lore stats` still works (hidden alias)
|
||||||
|
- `lore stat` still works (hidden alias)
|
||||||
|
- `lore index --check` works
|
||||||
|
- `lore --help` shows `index` in System group, not `stats`
|
||||||
|
- `lore robot-docs` shows `index` key in commands map
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P4: Merge `health` into `doctor`
|
||||||
|
|
||||||
|
**Goal:** One diagnostic command (`doctor`) with a `--quick` flag for the pre-flight check that `health` currently provides.
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
/// Quick health check: config, database, schema version
|
||||||
|
#[command(after_help = "...")]
|
||||||
|
Health,
|
||||||
|
|
||||||
|
/// Check environment health
|
||||||
|
#[command(after_help = "...")]
|
||||||
|
Doctor,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
// Remove Health variant entirely. Add hidden alias:
|
||||||
|
/// Check environment health (--quick for fast pre-flight)
|
||||||
|
#[command(
|
||||||
|
after_help = "...",
|
||||||
|
alias = "health", // hidden backward compat
|
||||||
|
help_heading = "System"
|
||||||
|
)]
|
||||||
|
Doctor {
|
||||||
|
/// Fast pre-flight check only (config, DB, schema). Exit 0 = healthy.
|
||||||
|
#[arg(long)]
|
||||||
|
quick: bool,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/main.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
Some(Commands::Doctor) => handle_doctor(cli.config.as_deref(), robot_mode).await,
|
||||||
|
...
|
||||||
|
Some(Commands::Health) => handle_health(cli.config.as_deref(), robot_mode).await,
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
Some(Commands::Doctor { quick }) => {
|
||||||
|
if quick {
|
||||||
|
handle_health(cli.config.as_deref(), robot_mode).await
|
||||||
|
} else {
|
||||||
|
handle_doctor(cli.config.as_deref(), robot_mode).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Health variant removed from enum, so no separate match arm
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
Merge the `health` and `doctor` entries:
|
||||||
|
```rust
|
||||||
|
"doctor": {
|
||||||
|
"description": "Environment health check. Use --quick for fast pre-flight (exit 0 = healthy, 19 = unhealthy).",
|
||||||
|
"flags": ["--quick"],
|
||||||
|
"example": "lore --robot doctor",
|
||||||
|
"notes": {
|
||||||
|
"quick_mode": "lore --robot doctor --quick — fast pre-flight check (formerly 'lore health'). Only checks config, DB, schema version. Returns exit 19 on failure.",
|
||||||
|
"full_mode": "lore --robot doctor — full diagnostic: config, auth, database, Ollama"
|
||||||
|
},
|
||||||
|
"response_schema": {
|
||||||
|
"full": { ... }, // current doctor schema
|
||||||
|
"quick": { ... } // current health schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove the standalone `health` entry from the commands map.
|
||||||
|
|
||||||
|
**File:** `src/cli/autocorrect.rs`
|
||||||
|
|
||||||
|
- Remove `"health"` from `CANONICAL_SUBCOMMANDS` (clap's `alias` handles it)
|
||||||
|
- Or keep it -- since clap treats aliases as valid subcommands, the autocorrect system will still resolve typos like `"helth"` to `"health"` which clap then maps to `doctor`. Either way works.
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs` -- update `workflows.pre_flight`:
|
||||||
|
```rust
|
||||||
|
"pre_flight": [
|
||||||
|
"lore --robot doctor --quick"
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
Add to aliases.deprecated_commands:
|
||||||
|
```rust
|
||||||
|
"health": "doctor --quick"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore doctor` runs full diagnostic (unchanged behavior)
|
||||||
|
- `lore doctor --quick` runs fast pre-flight (exit 0/19)
|
||||||
|
- `lore health` still works (hidden alias, runs `doctor --quick`)
|
||||||
|
- `lore --help` shows only `doctor` in System group
|
||||||
|
- `lore robot-docs` shows merged entry
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
||||||
|
|
||||||
|
**Important edge case:** `lore health` via the hidden alias will invoke `Doctor { quick: false }` unless we handle it specially. Two options:
|
||||||
|
|
||||||
|
**Option A (simpler):** Instead of making `health` an alias of `doctor`, keep both variants but hide `Health`:
|
||||||
|
```rust
|
||||||
|
#[command(hide = true, help_heading = "System")]
|
||||||
|
Health,
|
||||||
|
```
|
||||||
|
Then in `main.rs`, `Commands::Health` maps to `handle_health()` as before. This is less clean but zero-risk.
|
||||||
|
|
||||||
|
**Option B (cleaner):** In the autocorrect layer, rewrite `health` -> `doctor --quick` before clap parsing:
|
||||||
|
```rust
|
||||||
|
// In SUBCOMMAND_ALIASES or a new pre-clap rewrite:
|
||||||
|
("health", "doctor"), // plus inject "--quick" flag
|
||||||
|
```
|
||||||
|
This requires a small enhancement to autocorrect to support flag injection during alias resolution.
|
||||||
|
|
||||||
|
**Recommendation:** Use Option A for initial implementation. It's one line (`hide = true`) and achieves the goal of removing `health` from `--help` while preserving full backward compatibility. The `doctor --quick` flag is additive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P7: Hide Pipeline Sub-stages
|
||||||
|
|
||||||
|
**Goal:** Remove `ingest`, `generate-docs`, `embed` from `--help` while keeping them fully functional.
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Add hide = true to each:
|
||||||
|
|
||||||
|
/// Ingest data from GitLab
|
||||||
|
#[command(hide = true)]
|
||||||
|
Ingest(IngestArgs),
|
||||||
|
|
||||||
|
/// Generate searchable documents from ingested data
|
||||||
|
#[command(name = "generate-docs", hide = true)]
|
||||||
|
GenerateDocs(GenerateDocsArgs),
|
||||||
|
|
||||||
|
/// Generate vector embeddings for documents via Ollama
|
||||||
|
#[command(hide = true)]
|
||||||
|
Embed(EmbedArgs),
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs` -- Update `Sync` help text to mention the individual stage commands:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Run full sync pipeline: ingest -> generate-docs -> embed
|
||||||
|
#[command(after_help = "\x1b[1mExamples:\x1b[0m
|
||||||
|
lore sync # Full pipeline: ingest + docs + embed
|
||||||
|
lore sync --no-embed # Skip embedding step
|
||||||
|
...
|
||||||
|
|
||||||
|
\x1b[1mIndividual stages:\x1b[0m
|
||||||
|
lore ingest # Fetch from GitLab only
|
||||||
|
lore generate-docs # Rebuild documents only
|
||||||
|
lore embed # Re-embed only",
|
||||||
|
help_heading = "Data Pipeline"
|
||||||
|
)]
|
||||||
|
Sync(SyncArgs),
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs` -- Add a `"hidden": true` field to the ingest/generate-docs/embed entries so agents know these are secondary:
|
||||||
|
```rust
|
||||||
|
"ingest": {
|
||||||
|
"hidden": true,
|
||||||
|
"description": "Sync data from GitLab (prefer 'sync' for full pipeline)",
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore --help` no longer shows ingest, generate-docs, embed
|
||||||
|
- `lore ingest`, `lore generate-docs`, `lore embed` all still work
|
||||||
|
- `lore sync --help` mentions individual stage commands
|
||||||
|
- `lore robot-docs` still includes all three (with `hidden: true`)
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/mod.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 2 Commit Summary
|
||||||
|
|
||||||
|
**Commit A: Rename `stats` -> `index`**
|
||||||
|
- `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
||||||
|
|
||||||
|
**Commit B: Merge `health` into `doctor`, hide pipeline stages**
|
||||||
|
- `src/cli/mod.rs`, `src/main.rs`, `src/app/robot_docs.rs`, `src/cli/autocorrect.rs`
|
||||||
|
|
||||||
|
**Test plan:**
|
||||||
|
```bash
|
||||||
|
cargo check --all-targets
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
cargo fmt --check
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# Rename verification
|
||||||
|
lore index # Works (new name)
|
||||||
|
lore stats # Works (hidden alias)
|
||||||
|
lore index --check # Works
|
||||||
|
|
||||||
|
# Doctor merge verification
|
||||||
|
lore doctor # Full diagnostic
|
||||||
|
lore doctor --quick # Fast pre-flight
|
||||||
|
lore health # Still works (hidden)
|
||||||
|
|
||||||
|
# Hidden stages verification
|
||||||
|
lore --help # ingest/generate-docs/embed gone
|
||||||
|
lore ingest # Still works
|
||||||
|
lore sync --help # Mentions individual stages
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 3: Structural Consolidation (requires careful design)
|
||||||
|
|
||||||
|
These changes merge or absorb commands. More effort, more testing, but the biggest UX wins.
|
||||||
|
|
||||||
|
### P6: Consolidate `file-history` into `trace`
|
||||||
|
|
||||||
|
**Goal:** `trace` absorbs `file-history`. One command for file-centric intelligence.
|
||||||
|
|
||||||
|
**Approach:** Add `--mrs-only` flag to `trace`. When set, output matches `file-history` format (flat MR list, no issue/discussion linking). `file-history` becomes a hidden alias.
|
||||||
|
|
||||||
|
**File:** `src/cli/args.rs` -- Add flag to `TraceArgs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct TraceArgs {
|
||||||
|
pub path: String,
|
||||||
|
|
||||||
|
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||||
|
pub project: Option<String>,
|
||||||
|
|
||||||
|
#[arg(long, help_heading = "Output")]
|
||||||
|
pub discussions: bool,
|
||||||
|
|
||||||
|
#[arg(long = "no-follow-renames", help_heading = "Filters")]
|
||||||
|
pub no_follow_renames: bool,
|
||||||
|
|
||||||
|
#[arg(short = 'n', long = "limit", default_value = "20", help_heading = "Output")]
|
||||||
|
pub limit: usize,
|
||||||
|
|
||||||
|
// NEW: absorb file-history behavior
|
||||||
|
/// Show only MR list without issue/discussion linking (file-history mode)
|
||||||
|
#[arg(long = "mrs-only", help_heading = "Output")]
|
||||||
|
pub mrs_only: bool,
|
||||||
|
|
||||||
|
/// Only show merged MRs (file-history mode)
|
||||||
|
#[arg(long, help_heading = "Filters")]
|
||||||
|
pub merged: bool,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs` -- Hide `FileHistory`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Show MRs that touched a file, with linked discussions
|
||||||
|
#[command(name = "file-history", hide = true, help_heading = "File Analysis")]
|
||||||
|
FileHistory(FileHistoryArgs),
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/handlers.rs` -- Route `trace --mrs-only` to the file-history handler:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn handle_trace(
|
||||||
|
config_override: Option<&str>,
|
||||||
|
args: TraceArgs,
|
||||||
|
robot_mode: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if args.mrs_only {
|
||||||
|
// Delegate to file-history handler
|
||||||
|
let fh_args = FileHistoryArgs {
|
||||||
|
path: args.path,
|
||||||
|
project: args.project,
|
||||||
|
discussions: args.discussions,
|
||||||
|
no_follow_renames: args.no_follow_renames,
|
||||||
|
merged: args.merged,
|
||||||
|
limit: args.limit,
|
||||||
|
};
|
||||||
|
return handle_file_history(config_override, fh_args, robot_mode);
|
||||||
|
}
|
||||||
|
// ... existing trace logic ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs` -- Update trace entry, mark file-history as deprecated:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
"trace": {
|
||||||
|
"description": "Trace why code was introduced: file -> MR -> issue -> discussion. Use --mrs-only for flat MR listing.",
|
||||||
|
"flags": ["<path>", "-p/--project", "--discussions", "--no-follow-renames", "-n/--limit", "--mrs-only", "--merged"],
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"file-history": {
|
||||||
|
"hidden": true,
|
||||||
|
"deprecated": "Use 'trace --mrs-only' instead",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore trace src/main.rs` works unchanged
|
||||||
|
- `lore trace src/main.rs --mrs-only` produces file-history output
|
||||||
|
- `lore trace src/main.rs --mrs-only --merged` filters to merged MRs
|
||||||
|
- `lore file-history src/main.rs` still works (hidden command)
|
||||||
|
- `lore --help` shows only `trace` in File Analysis group
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P8: Make `count` a Flag on Entity Commands
|
||||||
|
|
||||||
|
**Goal:** `lore issues --count` replaces `lore count issues`. Standalone `count` becomes hidden.
|
||||||
|
|
||||||
|
**File:** `src/cli/args.rs` -- Add `--count` to `IssuesArgs`, `MrsArgs`, `NotesArgs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// In IssuesArgs:
|
||||||
|
/// Show count only (no listing)
|
||||||
|
#[arg(long, help_heading = "Output", conflicts_with_all = ["iid", "open"])]
|
||||||
|
pub count: bool,
|
||||||
|
|
||||||
|
// In MrsArgs:
|
||||||
|
/// Show count only (no listing)
|
||||||
|
#[arg(long, help_heading = "Output", conflicts_with_all = ["iid", "open"])]
|
||||||
|
pub count: bool,
|
||||||
|
|
||||||
|
// In NotesArgs:
|
||||||
|
/// Show count only (no listing)
|
||||||
|
#[arg(long, help_heading = "Output", conflicts_with = "open")]
|
||||||
|
pub count: bool,
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/handlers.rs` -- In `handle_issues`, `handle_mrs`, `handle_notes`, check the count flag early:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// In handle_issues (pseudocode):
|
||||||
|
if args.count {
|
||||||
|
let count_args = CountArgs { entity: "issues".to_string(), for_entity: None };
|
||||||
|
return handle_count(config_override, count_args, robot_mode).await;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/cli/mod.rs` -- Hide `Count`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Count entities in local database
|
||||||
|
#[command(hide = true, help_heading = "Query")]
|
||||||
|
Count(CountArgs),
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs` -- Mark count as hidden, add `--count` documentation to issues/mrs/notes entries.
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore issues --count` returns issue count
|
||||||
|
- `lore mrs --count` returns MR count
|
||||||
|
- `lore notes --count` returns note count
|
||||||
|
- `lore count issues` still works (hidden)
|
||||||
|
- `lore count discussions --for mr` still works (no equivalent in the new pattern -- discussions/events/references still need the standalone `count` command)
|
||||||
|
|
||||||
|
**Important note:** `count` supports entity types that don't have their own command (discussions, events, references). The standalone `count` must remain functional (just hidden). The `--count` flag on `issues`/`mrs`/`notes` handles the common cases only.
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### P10: Add `--sort` to `search`
|
||||||
|
|
||||||
|
**Goal:** Allow sorting search results by score, created date, or updated date.
|
||||||
|
|
||||||
|
**File:** `src/cli/args.rs` -- Add to `SearchArgs`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Sort results by field (score is default for ranked search)
|
||||||
|
#[arg(long, value_parser = ["score", "created", "updated"], default_value = "score", help_heading = "Sorting")]
|
||||||
|
pub sort: String,
|
||||||
|
|
||||||
|
/// Sort ascending (default: descending)
|
||||||
|
#[arg(long, help_heading = "Sorting", overrides_with = "no_asc")]
|
||||||
|
pub asc: bool,
|
||||||
|
|
||||||
|
#[arg(long = "no-asc", hide = true, overrides_with = "asc")]
|
||||||
|
pub no_asc: bool,
|
||||||
|
```
|
||||||
|
|
||||||
|
**File:** `src/cli/commands/search.rs` -- Thread the sort parameter through to the search query.
|
||||||
|
|
||||||
|
The search function currently returns results sorted by score. When `--sort created` or `--sort updated` is specified, apply an `ORDER BY` clause to the final result set.
|
||||||
|
|
||||||
|
**File:** `src/app/robot_docs.rs` -- Add `--sort` and `--asc` to the search command's flags list.
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
- `lore search 'auth' --sort score` (default, unchanged)
|
||||||
|
- `lore search 'auth' --sort created --asc` (oldest first)
|
||||||
|
- `lore search 'auth' --sort updated` (most recently updated first)
|
||||||
|
|
||||||
|
**Files touched:** `src/cli/args.rs`, `src/cli/commands/search.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase 3 Commit Summary
|
||||||
|
|
||||||
|
**Commit C: Consolidate file-history into trace**
|
||||||
|
- `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
**Commit D: Add `--count` flag to entity commands**
|
||||||
|
- `src/cli/args.rs`, `src/cli/mod.rs`, `src/app/handlers.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
**Commit E: Add `--sort` to search**
|
||||||
|
- `src/cli/args.rs`, `src/cli/commands/search.rs`, `src/app/robot_docs.rs`
|
||||||
|
|
||||||
|
**Test plan:**
|
||||||
|
```bash
|
||||||
|
cargo check --all-targets
|
||||||
|
cargo clippy --all-targets -- -D warnings
|
||||||
|
cargo fmt --check
|
||||||
|
cargo test
|
||||||
|
|
||||||
|
# trace consolidation
|
||||||
|
lore trace src/main.rs --mrs-only
|
||||||
|
lore trace src/main.rs --mrs-only --merged --discussions
|
||||||
|
lore file-history src/main.rs # backward compat
|
||||||
|
|
||||||
|
# count flag
|
||||||
|
lore issues --count
|
||||||
|
lore mrs --count -s opened
|
||||||
|
lore notes --count --for-issue 42
|
||||||
|
lore count discussions --for mr # still works
|
||||||
|
|
||||||
|
# search sort
|
||||||
|
lore search 'auth' --sort created --asc
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation Updates
|
||||||
|
|
||||||
|
After all implementation is complete:
|
||||||
|
|
||||||
|
### CLAUDE.md / AGENTS.md
|
||||||
|
|
||||||
|
Update the robot mode command reference to reflect:
|
||||||
|
- `stats` -> `index` (with note that `stats` is a hidden alias)
|
||||||
|
- `health` -> `doctor --quick` (with note that `health` is a hidden alias)
|
||||||
|
- Remove `ingest`, `generate-docs`, `embed` from the primary command table (mention as "hidden, use `sync`")
|
||||||
|
- Remove `file-history` from primary table (mention as "hidden, use `trace --mrs-only`")
|
||||||
|
- Add `--count` flag to issues/mrs/notes documentation
|
||||||
|
- Add `--sort` flag to search documentation
|
||||||
|
- Add `--mrs-only` and `--merged` flags to trace documentation
|
||||||
|
|
||||||
|
### robot-docs Self-Discovery
|
||||||
|
|
||||||
|
The `robot_docs.rs` changes above handle this. Key points:
|
||||||
|
- New `"hidden": true` field on deprecated/hidden commands
|
||||||
|
- Updated descriptions mentioning canonical alternatives
|
||||||
|
- Updated flags lists
|
||||||
|
- Updated workflows section
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Impact Summary
|
||||||
|
|
||||||
|
| File | Phase 1 | Phase 2 | Phase 3 | Total Changes |
|
||||||
|
|------|---------|---------|---------|---------------|
|
||||||
|
| `src/cli/mod.rs` | help_heading, drift value_parser | stats->index rename, hide health, hide pipeline stages | hide file-history, hide count | 4 passes |
|
||||||
|
| `src/cli/args.rs` | singular/plural, remove `-f`, add `-o` | — | `--mrs-only`/`--merged` on trace, `--count` on entities, `--sort` on search | 2 passes |
|
||||||
|
| `src/app/handlers.rs` | normalize entity strings | route doctor --quick | trace mrs-only delegation, count flag routing | 3 passes |
|
||||||
|
| `src/app/robot_docs.rs` | update count flags | rename stats->index, merge health+doctor, add hidden field | update trace, file-history, count, search entries | 3 passes |
|
||||||
|
| `src/cli/autocorrect.rs` | — | update CANONICAL_SUBCOMMANDS, SUBCOMMAND_ALIASES, COMMAND_FLAGS | — | 1 pass |
|
||||||
|
| `src/main.rs` | — | stats->index variant rename, doctor variant change | — | 1 pass |
|
||||||
|
| `src/cli/commands/search.rs` | — | — | sort parameter threading | 1 pass |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Before / After Summary
|
||||||
|
|
||||||
|
### Command Count
|
||||||
|
|
||||||
|
| Metric | Before | After | Delta |
|
||||||
|
|--------|--------|-------|-------|
|
||||||
|
| Visible top-level commands | 29 | 21 | -8 (-28%) |
|
||||||
|
| Hidden commands (functional) | 4 | 12 | +8 (absorbed) |
|
||||||
|
| Stub/unimplemented commands | 2 | 2 | 0 |
|
||||||
|
| Total functional commands | 33 | 33 | 0 (nothing lost) |
|
||||||
|
|
||||||
|
### `lore --help` Output
|
||||||
|
|
||||||
|
**Before (29 commands, flat list, ~50 lines of commands):**
|
||||||
|
```
|
||||||
|
Commands:
|
||||||
|
issues List or show issues [aliases: issue]
|
||||||
|
mrs List or show merge requests [aliases: mr]
|
||||||
|
notes List notes from discussions [aliases: note]
|
||||||
|
ingest Ingest data from GitLab
|
||||||
|
count Count entities in local database
|
||||||
|
status Show sync state [aliases: st]
|
||||||
|
auth Verify GitLab authentication
|
||||||
|
doctor Check environment health
|
||||||
|
version Show version information
|
||||||
|
init Initialize configuration and database
|
||||||
|
search Search indexed documents [aliases: find]
|
||||||
|
stats Show document and index statistics [aliases: stat]
|
||||||
|
generate-docs Generate searchable documents from ingested data
|
||||||
|
embed Generate vector embeddings for documents via Ollama
|
||||||
|
sync Run full sync pipeline: ingest -> generate-docs -> embed
|
||||||
|
migrate Run pending database migrations
|
||||||
|
health Quick health check: config, database, schema version
|
||||||
|
robot-docs Machine-readable command manifest for agent self-discovery
|
||||||
|
completions Generate shell completions
|
||||||
|
timeline Show a chronological timeline of events matching a query
|
||||||
|
who People intelligence: experts, workload, active discussions, overlap
|
||||||
|
me Personal work dashboard: open issues, authored/reviewing MRs, activity
|
||||||
|
file-history Show MRs that touched a file, with linked discussions
|
||||||
|
trace Trace why code was introduced: file -> MR -> issue -> discussion
|
||||||
|
drift Detect discussion divergence from original intent
|
||||||
|
related Find semantically related entities via vector search
|
||||||
|
cron Manage cron-based automatic syncing
|
||||||
|
token Manage stored GitLab token
|
||||||
|
help Print this message or the help of the given subcommand(s)
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (21 commands, grouped, ~35 lines of commands):**
|
||||||
|
```
|
||||||
|
Query:
|
||||||
|
issues List or show issues [aliases: issue]
|
||||||
|
mrs List or show merge requests [aliases: mr]
|
||||||
|
notes List notes from discussions [aliases: note]
|
||||||
|
search Search indexed documents [aliases: find]
|
||||||
|
|
||||||
|
Intelligence:
|
||||||
|
timeline Chronological timeline of events
|
||||||
|
who People intelligence: experts, workload, overlap
|
||||||
|
me Personal work dashboard
|
||||||
|
|
||||||
|
File Analysis:
|
||||||
|
trace Trace code provenance / file history
|
||||||
|
related Find semantically related entities
|
||||||
|
drift Detect discussion divergence
|
||||||
|
|
||||||
|
Data Pipeline:
|
||||||
|
sync Run full sync pipeline
|
||||||
|
|
||||||
|
System:
|
||||||
|
init Initialize configuration and database
|
||||||
|
status Show sync state [aliases: st]
|
||||||
|
doctor Check environment health (--quick for pre-flight)
|
||||||
|
index Document and index statistics [aliases: idx]
|
||||||
|
auth Verify GitLab authentication
|
||||||
|
token Manage stored GitLab token
|
||||||
|
migrate Run pending database migrations
|
||||||
|
cron Manage automatic syncing
|
||||||
|
robot-docs Agent self-discovery manifest
|
||||||
|
completions Generate shell completions
|
||||||
|
version Show version information
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flag Consistency
|
||||||
|
|
||||||
|
| Issue | Before | After |
|
||||||
|
|-------|--------|-------|
|
||||||
|
| `-f` collision (force vs for) | `ingest -f`=force, `count -f`=for | `-f` removed from count; `-f` = force everywhere |
|
||||||
|
| Singular/plural entity types | `count issues` but `search --type issue` | Both forms accepted everywhere |
|
||||||
|
| `notes --open` missing `-o` | `notes --open` (no shorthand) | `notes -o` works (matches issues/mrs) |
|
||||||
|
| `search` missing `--sort` | No sort override | `--sort score\|created\|updated` + `--asc` |
|
||||||
|
|
||||||
|
### Naming Confusion
|
||||||
|
|
||||||
|
| Before | After | Resolution |
|
||||||
|
|--------|-------|------------|
|
||||||
|
| `status` vs `stats` vs `stat` (3 names, 2 commands) | `status` + `index` (2 names, 2 commands) | Eliminated near-homonym collision |
|
||||||
|
| `health` vs `doctor` (2 commands, overlapping scope) | `doctor` + `doctor --quick` (1 command) | Progressive disclosure |
|
||||||
|
| `trace` vs `file-history` (2 commands, overlapping function) | `trace` + `trace --mrs-only` (1 command) | Superset absorbs subset |
|
||||||
|
|
||||||
|
### Robot Ergonomics
|
||||||
|
|
||||||
|
| Metric | Before | After |
|
||||||
|
|--------|--------|-------|
|
||||||
|
| Commands in robot-docs manifest | 29 | 21 visible + hidden section |
|
||||||
|
| Agent decision space for "system check" | 4 commands | 2 commands (status, doctor) |
|
||||||
|
| Agent decision space for "file query" | 3 commands + 2 who modes | 1 command (trace) + 2 who modes |
|
||||||
|
| Entity type parse errors from singular/plural | Common | Eliminated |
|
||||||
|
| Estimated token cost of robot-docs | Baseline | ~15% reduction (fewer entries, hidden flagged) |
|
||||||
|
|
||||||
|
### What Stays Exactly The Same
|
||||||
|
|
||||||
|
- All 33 functional commands remain callable (nothing is removed)
|
||||||
|
- All existing flags and their behavior are preserved
|
||||||
|
- All response schemas are unchanged
|
||||||
|
- All exit codes are unchanged
|
||||||
|
- The autocorrect system continues to work
|
||||||
|
- All hidden/deprecated commands emit their existing warnings
|
||||||
|
|
||||||
|
### What Breaks (Intentional)
|
||||||
|
|
||||||
|
- `lore count -f mr` (the `-f` shorthand) -- must use `--for` instead
|
||||||
|
- `lore --help` layout changes (commands are grouped, 8 commands hidden)
|
||||||
|
- `lore robot-docs` output changes (new `hidden` field, renamed keys)
|
||||||
|
- Any scripts parsing `--help` text (but `robot-docs` is the stable contract)
|
||||||
366
docs/api-efficiency-findings.md
Normal file
366
docs/api-efficiency-findings.md
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
---
|
||||||
|
plan: true
|
||||||
|
title: "api-efficiency-findings"
|
||||||
|
status: drafting
|
||||||
|
iteration: 0
|
||||||
|
target_iterations: 8
|
||||||
|
beads_revision: 0
|
||||||
|
related_plans: []
|
||||||
|
created: 2026-02-07
|
||||||
|
updated: 2026-02-07
|
||||||
|
---
|
||||||
|
|
||||||
|
# API Efficiency & Observability Findings
|
||||||
|
|
||||||
|
> **Status:** Draft - working through items
|
||||||
|
> **Context:** Audit of gitlore's GitLab API usage, data processing, and observability gaps
|
||||||
|
> **Interactive reference:** `api-review.html` (root of repo, open in browser)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checkpoint 3 Alignment
|
||||||
|
|
||||||
|
Checkpoint 3 (`docs/prd/checkpoint-3.md`) introduces `lore sync` orchestration, document generation, and search. Several findings here overlap with that work. This section maps the relationship so effort isn't duplicated and so CP3 implementation can absorb the right instrumentation as it's built.
|
||||||
|
|
||||||
|
### Direct overlaps (CP3 partially addresses)
|
||||||
|
|
||||||
|
| Finding | CP3 coverage | Remaining gap |
|
||||||
|
|---------|-------------|---------------|
|
||||||
|
| **P0-1** sync_runs never written | `lore sync` step 7 says "record sync_run". `SyncResult` struct defined with counts. | Only covers the new `lore sync` command. Existing `lore ingest` still won't write sync_runs. Either instrument `lore ingest` separately or have `lore sync` subsume it entirely. |
|
||||||
|
| **P0-2** No timing | `print_sync` captures wall-clock `elapsed_secs` / `elapsed_ms` in robot mode JSON `meta` envelope. | Wall-clock only. No per-phase, per-API-call, or per-DB-write breakdown. The `SyncResult` struct has counts but no duration fields. |
|
||||||
|
| **P2-1** Discussion full-refresh | CP3 introduces `pending_discussion_fetches` queue with exponential backoff and bounded processing per sync. Structures the work better. | Same full-refresh strategy per entity. The queue adds retry resilience but doesn't reduce the number of API calls for unchanged discussions. |
|
||||||
|
|
||||||
|
### Different scope (complementary, no overlap)
|
||||||
|
|
||||||
|
| Finding | Why no overlap |
|
||||||
|
|---------|---------------|
|
||||||
|
| **P0-3** metrics_json schema | CP3 doesn't reference the `metrics_json` column. `SyncResult` is printed/returned but not persisted there. |
|
||||||
|
| **P0-4** Discussion sync telemetry columns | CP3's queue system (`pending_discussion_fetches`) is a replacement architecture. The existing per-MR telemetry columns (`discussions_sync_attempts`, `_last_error`) aren't referenced in CP3. Decide: use CP3's queue table or wire up the existing columns? |
|
||||||
|
| **P0-5** Progress events lack timing | CP3 lists "Progress visible during long syncs" as acceptance criteria but doesn't spec timing in events. |
|
||||||
|
| **P1-\*** Free data capture | CP3 doesn't touch GitLab API response field coverage at all. These are independent. |
|
||||||
|
| **P2-2** Keyset pagination (GitLab API) | CP3 uses keyset pagination for local SQLite queries (document seeding, embedding pipelines). Completely different from using GitLab API keyset pagination. |
|
||||||
|
| **P2-3** ETags | Not mentioned in CP3. |
|
||||||
|
| **P2-4** Labels enrichment | Not mentioned in CP3. |
|
||||||
|
| **P3-\*** Structural improvements | Not in CP3 scope. |
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
CP3's `lore sync` orchestrator is the natural integration point for P0 instrumentation. Rather than retrofitting `lore ingest` separately, the most efficient path is:
|
||||||
|
|
||||||
|
1. Build P0 timing instrumentation as a reusable layer (e.g., a `SyncMetrics` struct that accumulates phase timings)
|
||||||
|
2. Wire it into the CP3 `run_sync` implementation as it's built
|
||||||
|
3. Have `run_sync` persist the full metrics (counts + timing) to `sync_runs.metrics_json`
|
||||||
|
4. Decide whether `lore ingest` becomes a thin wrapper around `lore sync --no-docs --no-embed` or stays separate with its own sync_runs recording
|
||||||
|
|
||||||
|
This avoids building instrumentation twice and ensures the new sync pipeline is observable from day one.
|
||||||
|
|
||||||
|
### Decision: `lore ingest` goes away
|
||||||
|
|
||||||
|
`lore sync` becomes the single command for all data fetching. First run does a full fetch (equivalent to today's `lore ingest`), subsequent runs are incremental via cursors. `lore ingest` becomes a hidden deprecated alias.
|
||||||
|
|
||||||
|
Implications:
|
||||||
|
- P0 instrumentation only needs to be built in one place (`run_sync`)
|
||||||
|
- CP3 Gate C owns the sync_runs lifecycle end-to-end
|
||||||
|
- The existing `lore ingest issues` / `lore ingest mrs` code becomes internal functions called by `run_sync`, not standalone CLI commands
|
||||||
|
- `lore sync` always syncs everything: issues, MRs, discussions, documents, embeddings (with `--no-embed` / `--no-docs` to opt out of later stages)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Sequence
|
||||||
|
|
||||||
|
### Phase A: Before CP3 (independent, enriches data model)
|
||||||
|
|
||||||
|
**Do first.** Migration + struct changes only. No architectural dependency. Gets richer source data into the DB before CP3's document generation pipeline locks in its schema.
|
||||||
|
|
||||||
|
1. **P1 batch: free data capture** - All ~11 fields in a single migration. `user_notes_count`, `upvotes`, `downvotes`, `confidential`, `has_conflicts`, `blocking_discussions_resolved`, `merge_commit_sha`, `discussion_locked`, `task_completion_status`, `issue_type`, `issue references`.
|
||||||
|
2. **P1-10: MR milestones** - Reuse existing issue milestone transformer. Slightly more work, same migration.
|
||||||
|
|
||||||
|
### Phase B: During CP3 Gate C (`lore sync`)
|
||||||
|
|
||||||
|
**Build instrumentation into the sync orchestrator as it's constructed.** Not a separate effort.
|
||||||
|
|
||||||
|
3. **P0-1 + P0-2 + P0-3** - `SyncMetrics` struct accumulating phase timings. `run_sync` writes to `sync_runs` with full `metrics_json` on completion.
|
||||||
|
4. **P0-4** - Decide: use CP3's `pending_discussion_fetches` queue or existing per-MR telemetry columns. Wire up the winner.
|
||||||
|
5. **P0-5** - Add `elapsed_ms` to `*Complete` progress event variants.
|
||||||
|
6. **Deprecate `lore ingest`** - Hidden alias pointing to `lore sync`. Remove from help output.
|
||||||
|
|
||||||
|
### Phase C: After CP3 ships, informed by real metrics
|
||||||
|
|
||||||
|
**Only pursue items that P0 data proves matter.**
|
||||||
|
|
||||||
|
7. **P2-1: Discussion optimization** - Check metrics_json from real runs. If discussion phase is <10% of wall-clock, skip.
|
||||||
|
8. **P2-2: Keyset pagination** - Check primary fetch timing on largest project. If fast, skip.
|
||||||
|
9. **P2-4: Labels enrichment** - If label colors are needed for any UI surface.
|
||||||
|
|
||||||
|
### Phase D: Future (needs a forcing function)
|
||||||
|
|
||||||
|
10. **P3-1: Users table** - When a UI needs display names / avatars.
|
||||||
|
11. **P2-3: ETags** - Only if P2-1 doesn't sufficiently reduce discussion overhead.
|
||||||
|
12. **P3-2/3/4: GraphQL, Events API, Webhooks** - Architectural shifts. Only if pull-based sync hits a scaling wall.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 0: Observability (prerequisite for everything else)
|
||||||
|
|
||||||
|
We can't evaluate any efficiency question without measurement. Gitlore has no runtime performance instrumentation. The infrastructure for it was scaffolded (sync_runs table, metrics_json column, discussion sync telemetry columns) but never wired up.
|
||||||
|
|
||||||
|
### P0-1: sync_runs table is never written to
|
||||||
|
|
||||||
|
**Location:** Schema in `migrations/001_initial.sql:25-34`, read in `src/cli/commands/sync_status.rs:69-72`
|
||||||
|
|
||||||
|
The table exists and `lore status` reads from it, but no code ever INSERTs or UPDATEs rows. The entire audit trail is empty.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Exists in schema, never populated
|
||||||
|
CREATE TABLE sync_runs (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
started_at INTEGER NOT NULL,
|
||||||
|
heartbeat_at INTEGER NOT NULL,
|
||||||
|
finished_at INTEGER,
|
||||||
|
status TEXT NOT NULL, -- 'running' | 'succeeded' | 'failed'
|
||||||
|
command TEXT NOT NULL,
|
||||||
|
error TEXT,
|
||||||
|
metrics_json TEXT -- never written
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**What to do:** Instrument the ingest orchestrator to record sync runs. Each `lore ingest issues` / `lore ingest mrs` invocation should:
|
||||||
|
- INSERT a row with status='running' at start
|
||||||
|
- UPDATE with status='succeeded'/'failed' + finished_at on completion
|
||||||
|
- Populate metrics_json with the IngestProjectResult / IngestMrProjectResult counters
|
||||||
|
|
||||||
|
### P0-2: No operation timing anywhere
|
||||||
|
|
||||||
|
**Location:** Rate limiter in `src/gitlab/client.rs:20-65`, orchestrator in `src/ingestion/orchestrator.rs`
|
||||||
|
|
||||||
|
`Instant::now()` is used only for rate limiter enforcement. No operation durations are measured or logged. We don't know:
|
||||||
|
|
||||||
|
- How long a full issue ingest takes
|
||||||
|
- How long discussion sync takes per entity
|
||||||
|
- How long individual API requests take (network latency)
|
||||||
|
- How long database writes take per batch
|
||||||
|
- How long rate limiter sleeps accumulate to
|
||||||
|
- How long pagination takes across pages
|
||||||
|
|
||||||
|
**What to do:** Add timing instrumentation at these levels:
|
||||||
|
|
||||||
|
| Level | What to time | Where |
|
||||||
|
|-------|-------------|-------|
|
||||||
|
| **Run** | Total ingest wall-clock time | orchestrator entry/exit |
|
||||||
|
| **Phase** | Primary fetch vs discussion sync | orchestrator phase boundaries |
|
||||||
|
| **API call** | Individual HTTP request round-trip | client.rs request method |
|
||||||
|
| **DB write** | Transaction duration per batch | ingestion store functions |
|
||||||
|
| **Rate limiter** | Cumulative sleep time per run | client.rs acquire() |
|
||||||
|
|
||||||
|
Store phase-level and run-level timing in `metrics_json`. Log API-call-level timing at debug level.
|
||||||
|
|
||||||
|
### P0-3: metrics_json has no defined schema
|
||||||
|
|
||||||
|
**What to do:** Define what goes in there. Strawman based on existing IngestProjectResult fields plus timing:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"wall_clock_ms": 14200,
|
||||||
|
"phases": {
|
||||||
|
"primary_fetch": {
|
||||||
|
"duration_ms": 8400,
|
||||||
|
"api_calls": 12,
|
||||||
|
"items_fetched": 1143,
|
||||||
|
"items_upserted": 87,
|
||||||
|
"pages": 12,
|
||||||
|
"rate_limit_sleep_ms": 1200
|
||||||
|
},
|
||||||
|
"discussion_sync": {
|
||||||
|
"duration_ms": 5800,
|
||||||
|
"entities_checked": 87,
|
||||||
|
"entities_synced": 14,
|
||||||
|
"entities_skipped": 73,
|
||||||
|
"api_calls": 22,
|
||||||
|
"discussions_fetched": 156,
|
||||||
|
"notes_upserted": 412,
|
||||||
|
"rate_limit_sleep_ms": 2200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"db": {
|
||||||
|
"labels_created": 3,
|
||||||
|
"raw_payloads_stored": 87,
|
||||||
|
"raw_payloads_deduped": 42
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### P0-4: Discussion sync telemetry columns are dead code
|
||||||
|
|
||||||
|
**Location:** `merge_requests` table columns: `discussions_sync_last_attempt_at`, `discussions_sync_attempts`, `discussions_sync_last_error`
|
||||||
|
|
||||||
|
These exist in the schema but are never read or written. They were designed for tracking retry behavior on failed discussion syncs.
|
||||||
|
|
||||||
|
**What to do:** Wire these up during discussion sync. On attempt: set last_attempt_at and increment attempts. On failure: set last_error. On success: reset attempts to 0. This provides per-entity visibility into discussion sync health.
|
||||||
|
|
||||||
|
### P0-5: Progress events carry no timing
|
||||||
|
|
||||||
|
**Location:** `src/ingestion/orchestrator.rs:28-53`
|
||||||
|
|
||||||
|
ProgressEvent variants (`IssueFetched`, `DiscussionSynced`, etc.) carry only counts. Adding elapsed_ms to at least `*Complete` variants would give callers (CLI progress bars, robot mode output) real throughput numbers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 1: Free data capture (zero API cost)
|
||||||
|
|
||||||
|
These fields are already in the API responses gitlore receives. Storing them requires only Rust struct additions and DB column migrations. No additional API calls.
|
||||||
|
|
||||||
|
### P1-1: user_notes_count (Issues + MRs)
|
||||||
|
|
||||||
|
**API field:** `user_notes_count` (integer)
|
||||||
|
**Value:** Could short-circuit discussion re-sync. If count hasn't changed, discussions probably haven't changed either. Also useful for "most discussed" queries.
|
||||||
|
**Effort:** Add field to serde struct, add DB column, store during transform.
|
||||||
|
|
||||||
|
### P1-2: upvotes / downvotes (Issues + MRs)
|
||||||
|
|
||||||
|
**API field:** `upvotes`, `downvotes` (integers)
|
||||||
|
**Value:** Engagement metrics for triage. "Most upvoted open issues" is a common query.
|
||||||
|
**Effort:** Same pattern as above.
|
||||||
|
|
||||||
|
### P1-3: confidential (Issues)
|
||||||
|
|
||||||
|
**API field:** `confidential` (boolean)
|
||||||
|
**Value:** Security-sensitive filtering. Important to know when exposing issue data.
|
||||||
|
**Effort:** Low.
|
||||||
|
|
||||||
|
### P1-4: has_conflicts (MRs)
|
||||||
|
|
||||||
|
**API field:** `has_conflicts` (boolean)
|
||||||
|
**Value:** Identify MRs needing rebase. Useful for "stale MR" detection.
|
||||||
|
**Effort:** Low.
|
||||||
|
|
||||||
|
### P1-5: blocking_discussions_resolved (MRs)
|
||||||
|
|
||||||
|
**API field:** `blocking_discussions_resolved` (boolean)
|
||||||
|
**Value:** MR readiness indicator without joining the discussions table.
|
||||||
|
**Effort:** Low.
|
||||||
|
|
||||||
|
### P1-6: merge_commit_sha (MRs)
|
||||||
|
|
||||||
|
**API field:** `merge_commit_sha` (string, nullable)
|
||||||
|
**Value:** Trace merged MRs to specific commits in git history.
|
||||||
|
**Effort:** Low.
|
||||||
|
|
||||||
|
### P1-7: discussion_locked (Issues + MRs)
|
||||||
|
|
||||||
|
**API field:** `discussion_locked` (boolean)
|
||||||
|
**Value:** Know if new comments can be added. Useful for robot mode consumers.
|
||||||
|
**Effort:** Low.
|
||||||
|
|
||||||
|
### P1-8: task_completion_status (Issues + MRs)
|
||||||
|
|
||||||
|
**API field:** `task_completion_status` (object: `{count, completed_count}`)
|
||||||
|
**Value:** Track task-list checkbox progress without parsing markdown.
|
||||||
|
**Effort:** Low. Store as two integer columns or a small JSON blob.
|
||||||
|
|
||||||
|
### P1-9: issue_type (Issues)
|
||||||
|
|
||||||
|
**API field:** `issue_type` (string: "issue" | "incident" | "test_case")
|
||||||
|
**Value:** Distinguish issues vs incidents vs test cases for filtering.
|
||||||
|
**Effort:** Low.
|
||||||
|
|
||||||
|
### P1-10: MR milestone (MRs)
|
||||||
|
|
||||||
|
**API field:** `milestone` (object, same structure as on issues)
|
||||||
|
**Current state:** Milestones are fully stored for issues but completely ignored for MRs.
|
||||||
|
**Value:** "Which MRs are in milestone X?" Currently impossible to query locally.
|
||||||
|
**Effort:** Medium - reuse existing milestone transformer from issue pipeline.
|
||||||
|
|
||||||
|
### P1-11: Issue references (Issues)
|
||||||
|
|
||||||
|
**API field:** `references` (object: `{short, relative, full}`)
|
||||||
|
**Current state:** Stored for MRs (`references_short`, `references_full`), dropped for issues.
|
||||||
|
**Value:** Cross-project issue references (e.g., `group/project#42`).
|
||||||
|
**Effort:** Low.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 2: Efficiency improvements (requires measurement from P0 first)
|
||||||
|
|
||||||
|
These are potential optimizations. **Do not implement until P0 instrumentation proves they matter.**
|
||||||
|
|
||||||
|
### P2-1: Discussion full-refresh strategy
|
||||||
|
|
||||||
|
**Current behavior:** When an issue/MR's `updated_at` advances, ALL its discussions are deleted and re-fetched from scratch.
|
||||||
|
|
||||||
|
**Potential optimization:** Use `user_notes_count` (P1-1) to detect whether discussions actually changed. Skip re-sync if count is unchanged.
|
||||||
|
|
||||||
|
**Why we need P0 first:** The full-refresh may be fast enough. Since we already fetch the data from GitLab, the DELETE+INSERT is just local SQLite I/O. If discussion sync for a typical entity takes <100ms locally, this isn't worth optimizing. We need the per-entity timing from P0-2 to know.
|
||||||
|
|
||||||
|
**Trade-offs to consider:**
|
||||||
|
- Full-refresh catches edited and deleted notes. Incremental would miss those.
|
||||||
|
- `user_notes_count` doesn't change when notes are edited, only when added/removed.
|
||||||
|
- Full-refresh is simpler to reason about for consistency.
|
||||||
|
|
||||||
|
### P2-2: Keyset pagination
|
||||||
|
|
||||||
|
**Current behavior:** Offset-based (`page=N&per_page=100`).
|
||||||
|
**Alternative:** Keyset pagination (`pagination=keyset`), O(1) per page instead of O(N).
|
||||||
|
|
||||||
|
**Why we need P0 first:** Only matters for large projects (>10K issues). Most projects will never hit enough pages for this to be measurable. P0 timing of pagination will show if this is a bottleneck.
|
||||||
|
|
||||||
|
**Note:** Gitlore already parses `Link` headers for next-page detection, which is the client-side mechanism keyset pagination uses. So partial support exists.
|
||||||
|
|
||||||
|
### P2-3: ETag / conditional requests
|
||||||
|
|
||||||
|
**Current behavior:** All requests are unconditional.
|
||||||
|
**Alternative:** Cache ETags, send `If-None-Match`, get 304s back.
|
||||||
|
|
||||||
|
**Why we need P0 first:** The cursor-based sync already avoids re-fetching unchanged data for primary resources. ETags would mainly help with discussion re-fetches where nothing changed. If P2-1 (user_notes_count skip) is implemented, ETags become less valuable.
|
||||||
|
|
||||||
|
### P2-4: Labels API enrichment
|
||||||
|
|
||||||
|
**Current behavior:** Labels extracted from the `labels[]` string array in issue/MR responses. The `labels` table has `color` and `description` columns that may not be populated.
|
||||||
|
**Alternative:** Single call to `GET /projects/:id/labels` per project per sync to populate label metadata.
|
||||||
|
**Cost:** 1 API call per project per sync run.
|
||||||
|
**Value:** Label colors for UI rendering, descriptions for tooltips.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority 3: Structural improvements (future consideration)
|
||||||
|
|
||||||
|
### P3-1: Users table
|
||||||
|
|
||||||
|
**Current state:** Only `username` stored. Author `name`, `avatar_url`, `web_url`, `state` are in every API response but discarded.
|
||||||
|
**Proposal:** Create a `users` table, upsert on every encounter. Zero API cost.
|
||||||
|
**Value:** Richer user display, detect blocked/deactivated users.
|
||||||
|
|
||||||
|
### P3-2: GraphQL API for field-precise fetching
|
||||||
|
|
||||||
|
**Current state:** REST API returns ~40-50 fields per entity. Gitlore uses ~15-23.
|
||||||
|
**Alternative:** GraphQL API allows requesting exactly the fields needed.
|
||||||
|
**Trade-offs:** Different pagination model, potentially less stable API, more complex client code. The bandwidth savings are real but likely minor compared to discussion re-fetch overhead.
|
||||||
|
|
||||||
|
### P3-3: Events API for lightweight change detection
|
||||||
|
|
||||||
|
**Endpoint:** `GET /projects/:id/events`
|
||||||
|
**Value:** Lightweight "has anything changed?" check before running full issue/MR sync. Could replace or supplement the cursor-based approach for very active projects.
|
||||||
|
|
||||||
|
### P3-4: Webhook-based push sync
|
||||||
|
|
||||||
|
**Endpoint:** `POST /projects/:id/hooks` (setup), then receive pushes.
|
||||||
|
**Value:** Near-real-time sync without polling cost. Eliminates all rate-limit concerns.
|
||||||
|
**Barrier:** Requires a listener endpoint, which changes the architecture from pull-only CLI to something with a daemon/server component.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Working notes
|
||||||
|
|
||||||
|
_Space for recording decisions as we work through items._
|
||||||
|
|
||||||
|
### Decisions made
|
||||||
|
|
||||||
|
| Item | Decision | Rationale |
|
||||||
|
|------|----------|-----------|
|
||||||
|
| `lore ingest` | Remove. `lore sync` is the single entry point. | No reason to separate initial load from incremental updates. First run = full fetch, subsequent = cursor-based delta. |
|
||||||
|
| CP3 alignment | Build P0 instrumentation into CP3 Gate C, not separately. | Avoids building in two places. `lore sync` owns the full lifecycle. |
|
||||||
|
| P2 timing | Defer all efficiency optimizations until P0 metrics from real runs are available. | Can't evaluate trade-offs without measurement. |
|
||||||
|
|
||||||
|
### Open questions
|
||||||
|
|
||||||
|
- What's the typical project size (issue/MR count) for gitlore users? This determines whether keyset pagination (P2-2) matters.
|
||||||
|
- Is there a plan for a web UI or TUI? That would increase the value of P3-1 (users table) and P2-4 (label colors).
|
||||||
1251
docs/command-surface-analysis.md
Normal file
1251
docs/command-surface-analysis.md
Normal file
File diff suppressed because it is too large
Load Diff
92
docs/command-surface-analysis/00-overview.md
Normal file
92
docs/command-surface-analysis/00-overview.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Lore Command Surface Analysis — Overview
|
||||||
|
|
||||||
|
**Date:** 2026-02-26
|
||||||
|
**Version:** v0.9.1 (439c20e)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Deep analysis of the full `lore` CLI command surface: what each command does, how commands overlap, how they connect in agent workflows, and where consolidation and robot-mode optimization can reduce round trips and token waste.
|
||||||
|
|
||||||
|
## Document Map
|
||||||
|
|
||||||
|
| File | Contents | When to Read |
|
||||||
|
|---|---|---|
|
||||||
|
| **00-overview.md** | This file. Summary, inventory, priorities. | Always read first. |
|
||||||
|
| [01-entity-commands.md](01-entity-commands.md) | `issues`, `mrs`, `notes`, `search`, `count` — flags, DB tables, robot schemas | Need command reference for entity queries |
|
||||||
|
| [02-intelligence-commands.md](02-intelligence-commands.md) | `who`, `timeline`, `me`, `file-history`, `trace`, `related`, `drift` | Need command reference for intelligence/analysis |
|
||||||
|
| [03-pipeline-and-infra.md](03-pipeline-and-infra.md) | `sync`, `ingest`, `generate-docs`, `embed`, diagnostics, setup | Need command reference for data management |
|
||||||
|
| [04-data-flow.md](04-data-flow.md) | Shared data source map, command network graph, clusters | Understanding how commands interconnect |
|
||||||
|
| [05-overlap-analysis.md](05-overlap-analysis.md) | Quantified overlap percentages for every command pair | Evaluating what to consolidate |
|
||||||
|
| [06-agent-workflows.md](06-agent-workflows.md) | Common agent flows, round-trip costs, token profiles | Understanding inefficiency pain points |
|
||||||
|
| [07-consolidation-proposals.md](07-consolidation-proposals.md) | 5 proposals to reduce 34 commands to 29 | Planning command surface changes |
|
||||||
|
| [08-robot-optimization-proposals.md](08-robot-optimization-proposals.md) | 6 proposals for `--include`, `--batch`, `--depth`, etc. | Planning robot-mode improvements |
|
||||||
|
| [09-appendices.md](09-appendices.md) | Robot output envelope, field presets, exit codes | Reference material |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Command Inventory (34 commands)
|
||||||
|
|
||||||
|
| Category | Commands | Count |
|
||||||
|
|---|---|---|
|
||||||
|
| Entity Query | `issues`, `mrs`, `notes`, `search`, `count` | 5 |
|
||||||
|
| Intelligence | `who` (5 modes), `timeline`, `related`, `drift`, `me`, `file-history`, `trace` | 7 (11 with who sub-modes) |
|
||||||
|
| Data Pipeline | `sync`, `ingest`, `generate-docs`, `embed` | 4 |
|
||||||
|
| Diagnostics | `health`, `auth`, `doctor`, `status`, `stats` | 5 |
|
||||||
|
| Setup | `init`, `token`, `cron`, `migrate` | 4 |
|
||||||
|
| Meta | `version`, `completions`, `robot-docs` | 3 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Findings
|
||||||
|
|
||||||
|
### High-Overlap Pairs
|
||||||
|
|
||||||
|
| Pair | Overlap | Recommendation |
|
||||||
|
|---|---|---|
|
||||||
|
| `who workload` vs `me` | ~85% | Workload is a strict subset of me |
|
||||||
|
| `health` vs `doctor` | ~90% | Health is a strict subset of doctor |
|
||||||
|
| `file-history` vs `trace` | ~75% | Trace is a superset minus `--merged` |
|
||||||
|
| `related` query-mode vs `search --mode semantic` | ~80% | Related query-mode is search without filters |
|
||||||
|
| `auth` vs `doctor` | ~100% of auth | Auth is fully contained within doctor |
|
||||||
|
|
||||||
|
### Agent Workflow Pain Points
|
||||||
|
|
||||||
|
| Workflow | Current Round Trips | With Optimizations |
|
||||||
|
|---|---|---|
|
||||||
|
| "Understand this issue" | 4 calls | 1 call (`--include`) |
|
||||||
|
| "Why was code changed?" | 3 calls | 1 call (`--include`) |
|
||||||
|
| "What should I work on?" | 4 calls | 2 calls |
|
||||||
|
| "Find and understand" | 4 calls | 2 calls |
|
||||||
|
| "Is system healthy?" | 2-4 calls | 1 call |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Priority Ranking
|
||||||
|
|
||||||
|
| Pri | Proposal | Category | Effort | Impact |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **P0** | `--include` flag on detail commands | Robot optimization | High | Eliminates 2-3 round trips per workflow |
|
||||||
|
| **P0** | `--depth` on `me` command | Robot optimization | Low | 60-80% token reduction on most-used command |
|
||||||
|
| **P1** | `--batch` for detail views | Robot optimization | Medium | Eliminates N+1 after search/timeline |
|
||||||
|
| **P1** | Absorb `file-history` into `trace` | Consolidation | Low | Cleaner surface, shared code |
|
||||||
|
| **P1** | Merge `who overlap` into `who expert` | Consolidation | Low | -1 round trip in review flows |
|
||||||
|
| **P2** | `context` composite command | Robot optimization | Medium | Single entry point for entity understanding |
|
||||||
|
| **P2** | Merge `count`+`status` into `stats` | Consolidation | Medium | -2 commands, progressive disclosure |
|
||||||
|
| **P2** | Absorb `auth` into `doctor` | Consolidation | Low | -1 command |
|
||||||
|
| **P2** | Remove `related` query-mode | Consolidation | Low | -1 confusing choice |
|
||||||
|
| **P3** | `--max-tokens` budget | Robot optimization | High | Flexible but complex to implement |
|
||||||
|
| **P3** | `--format tsv` | Robot optimization | Medium | High savings, limited applicability |
|
||||||
|
|
||||||
|
### Consolidation Summary
|
||||||
|
|
||||||
|
| Before | After | Removed |
|
||||||
|
|---|---|---|
|
||||||
|
| `file-history` + `trace` | `trace` (+ `--shallow`) | -1 |
|
||||||
|
| `auth` + `doctor` | `doctor` (+ `--auth`) | -1 |
|
||||||
|
| `related` query-mode | `search --mode semantic` | -1 mode |
|
||||||
|
| `who overlap` + `who expert` | `who expert` (+ touch_count) | -1 sub-mode |
|
||||||
|
| `count` + `status` + `stats` | `stats` (+ `--entities`, `--sync`) | -2 |
|
||||||
|
|
||||||
|
**Total: 34 commands -> 29 commands**
|
||||||
308
docs/command-surface-analysis/01-entity-commands.md
Normal file
308
docs/command-surface-analysis/01-entity-commands.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
# Entity Query Commands
|
||||||
|
|
||||||
|
Reference for: `issues`, `mrs`, `notes`, `search`, `count`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `issues` (alias: `issue`)
|
||||||
|
|
||||||
|
List or show issues from local database.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `[IID]` | positional | — | Omit to list, provide to show detail |
|
||||||
|
| `-n, --limit` | int | 50 | Max results |
|
||||||
|
| `--fields` | string | — | Select output columns (preset: `minimal`) |
|
||||||
|
| `-s, --state` | enum | — | `opened\|closed\|all` |
|
||||||
|
| `-p, --project` | string | — | Filter by project (fuzzy) |
|
||||||
|
| `-a, --author` | string | — | Filter by author username |
|
||||||
|
| `-A, --assignee` | string | — | Filter by assignee username |
|
||||||
|
| `-l, --label` | string[] | — | Filter by labels (AND logic, repeatable) |
|
||||||
|
| `-m, --milestone` | string | — | Filter by milestone title |
|
||||||
|
| `--status` | string[] | — | Filter by work-item status (COLLATE NOCASE, OR logic) |
|
||||||
|
| `--since` | duration/date | — | Filter by created date (`7d`, `2w`, `YYYY-MM-DD`) |
|
||||||
|
| `--due-before` | date | — | Filter by due date |
|
||||||
|
| `--has-due` | flag | — | Show only issues with due dates |
|
||||||
|
| `--sort` | enum | `updated` | `updated\|created\|iid` |
|
||||||
|
| `--asc` | flag | — | Sort ascending |
|
||||||
|
| `-o, --open` | flag | — | Open first match in browser |
|
||||||
|
|
||||||
|
**DB tables:** `issues`, `projects`, `issue_assignees`, `issue_labels`, `labels`
|
||||||
|
**Detail mode adds:** `discussions`, `notes`, `entity_references` (closing MRs)
|
||||||
|
|
||||||
|
### Robot Output (list mode)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"issues": [
|
||||||
|
{
|
||||||
|
"iid": 42, "title": "Fix auth", "state": "opened",
|
||||||
|
"author_username": "jdoe", "labels": ["backend"],
|
||||||
|
"assignees": ["jdoe"], "discussion_count": 3,
|
||||||
|
"unresolved_count": 1, "created_at_iso": "...",
|
||||||
|
"updated_at_iso": "...", "web_url": "...",
|
||||||
|
"project_path": "group/repo",
|
||||||
|
"status_name": "In progress"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_count": 150, "showing": 50
|
||||||
|
},
|
||||||
|
"meta": { "elapsed_ms": 40, "available_statuses": ["Open", "In progress", "Closed"] }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Output (detail mode — `issues <IID>`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"id": 12345, "iid": 42, "title": "Fix auth",
|
||||||
|
"description": "Full markdown body...",
|
||||||
|
"state": "opened", "author_username": "jdoe",
|
||||||
|
"created_at": "...", "updated_at": "...", "closed_at": null,
|
||||||
|
"confidential": false, "web_url": "...", "project_path": "group/repo",
|
||||||
|
"references_full": "group/repo#42",
|
||||||
|
"labels": ["backend"], "assignees": ["jdoe"],
|
||||||
|
"due_date": null, "milestone": null,
|
||||||
|
"user_notes_count": 5, "merge_requests_count": 1,
|
||||||
|
"closing_merge_requests": [
|
||||||
|
{ "iid": 99, "title": "Refactor auth", "state": "merged", "web_url": "..." }
|
||||||
|
],
|
||||||
|
"discussions": [
|
||||||
|
{
|
||||||
|
"notes": [
|
||||||
|
{ "author_username": "jdoe", "body": "...", "created_at": "...", "is_system": false }
|
||||||
|
],
|
||||||
|
"individual_note": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status_name": "In progress", "status_color": "#1068bf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minimal preset:** `iid`, `title`, `state`, `updated_at_iso`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `mrs` (aliases: `mr`, `merge-request`, `merge-requests`)
|
||||||
|
|
||||||
|
List or show merge requests.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `[IID]` | positional | — | Omit to list, provide to show detail |
|
||||||
|
| `-n, --limit` | int | 50 | Max results |
|
||||||
|
| `--fields` | string | — | Select output columns (preset: `minimal`) |
|
||||||
|
| `-s, --state` | enum | — | `opened\|merged\|closed\|locked\|all` |
|
||||||
|
| `-p, --project` | string | — | Filter by project |
|
||||||
|
| `-a, --author` | string | — | Filter by author |
|
||||||
|
| `-A, --assignee` | string | — | Filter by assignee |
|
||||||
|
| `-r, --reviewer` | string | — | Filter by reviewer |
|
||||||
|
| `-l, --label` | string[] | — | Filter by labels (AND) |
|
||||||
|
| `--since` | duration/date | — | Filter by created date |
|
||||||
|
| `-d, --draft` | flag | — | Draft MRs only |
|
||||||
|
| `-D, --no-draft` | flag | — | Exclude drafts |
|
||||||
|
| `--target` | string | — | Filter by target branch |
|
||||||
|
| `--source` | string | — | Filter by source branch |
|
||||||
|
| `--sort` | enum | `updated` | `updated\|created\|iid` |
|
||||||
|
| `--asc` | flag | — | Sort ascending |
|
||||||
|
| `-o, --open` | flag | — | Open in browser |
|
||||||
|
|
||||||
|
**DB tables:** `merge_requests`, `projects`, `mr_reviewers`, `mr_labels`, `labels`, `mr_assignees`
|
||||||
|
**Detail mode adds:** `discussions`, `notes`, `mr_diffs`
|
||||||
|
|
||||||
|
### Robot Output (list mode)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"mrs": [
|
||||||
|
{
|
||||||
|
"iid": 99, "title": "Refactor auth", "state": "merged",
|
||||||
|
"draft": false, "author_username": "jdoe",
|
||||||
|
"source_branch": "feat/auth", "target_branch": "main",
|
||||||
|
"labels": ["backend"], "assignees": ["jdoe"], "reviewers": ["reviewer"],
|
||||||
|
"discussion_count": 5, "unresolved_count": 0,
|
||||||
|
"created_at_iso": "...", "updated_at_iso": "...",
|
||||||
|
"web_url": "...", "project_path": "group/repo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_count": 500, "showing": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Output (detail mode — `mrs <IID>`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"id": 67890, "iid": 99, "title": "Refactor auth",
|
||||||
|
"description": "Full markdown body...",
|
||||||
|
"state": "merged", "draft": false, "author_username": "jdoe",
|
||||||
|
"source_branch": "feat/auth", "target_branch": "main",
|
||||||
|
"created_at": "...", "updated_at": "...",
|
||||||
|
"merged_at": "...", "closed_at": null,
|
||||||
|
"web_url": "...", "project_path": "group/repo",
|
||||||
|
"labels": ["backend"], "assignees": ["jdoe"], "reviewers": ["reviewer"],
|
||||||
|
"discussions": [
|
||||||
|
{
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"author_username": "reviewer", "body": "...",
|
||||||
|
"created_at": "...", "is_system": false,
|
||||||
|
"position": { "new_path": "src/auth.rs", "new_line": 42 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"individual_note": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minimal preset:** `iid`, `title`, `state`, `updated_at_iso`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `notes` (alias: `note`)
|
||||||
|
|
||||||
|
List discussion notes/comments with fine-grained filters.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `-n, --limit` | int | 50 | Max results |
|
||||||
|
| `--fields` | string | — | Preset: `minimal` |
|
||||||
|
| `-a, --author` | string | — | Filter by author |
|
||||||
|
| `--note-type` | enum | — | `DiffNote\|DiscussionNote` |
|
||||||
|
| `--contains` | string | — | Body text substring filter |
|
||||||
|
| `--note-id` | int | — | Internal note ID |
|
||||||
|
| `--gitlab-note-id` | int | — | GitLab note ID |
|
||||||
|
| `--discussion-id` | string | — | Discussion ID filter |
|
||||||
|
| `--include-system` | flag | — | Include system notes |
|
||||||
|
| `--for-issue` | int | — | Notes on specific issue (requires `-p`) |
|
||||||
|
| `--for-mr` | int | — | Notes on specific MR (requires `-p`) |
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
| `--since` | duration/date | — | Created after |
|
||||||
|
| `--until` | date | — | Created before (inclusive) |
|
||||||
|
| `--path` | string | — | File path filter (exact or prefix with `/`) |
|
||||||
|
| `--resolution` | enum | — | `any\|unresolved\|resolved` |
|
||||||
|
| `--sort` | enum | `created` | `created\|updated` |
|
||||||
|
| `--asc` | flag | — | Sort ascending |
|
||||||
|
| `--open` | flag | — | Open in browser |
|
||||||
|
|
||||||
|
**DB tables:** `notes`, `discussions`, `projects`, `issues`, `merge_requests`
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"notes": [
|
||||||
|
{
|
||||||
|
"id": 1234, "gitlab_id": 56789,
|
||||||
|
"author_username": "reviewer", "body": "...",
|
||||||
|
"note_type": "DiffNote", "is_system": false,
|
||||||
|
"created_at_iso": "...", "updated_at_iso": "...",
|
||||||
|
"position_new_path": "src/auth.rs", "position_new_line": 42,
|
||||||
|
"resolvable": true, "resolved": false,
|
||||||
|
"noteable_type": "MergeRequest", "parent_iid": 99,
|
||||||
|
"parent_title": "Refactor auth", "project_path": "group/repo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"total_count": 1000, "showing": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minimal preset:** `id`, `author_username`, `body`, `created_at_iso`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `search` (aliases: `find`, `query`)
|
||||||
|
|
||||||
|
Semantic + full-text search across indexed documents.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `<QUERY>` | positional | required | Search query string |
|
||||||
|
| `--mode` | enum | `hybrid` | `lexical\|hybrid\|semantic` |
|
||||||
|
| `--type` | enum | — | `issue\|mr\|discussion\|note` |
|
||||||
|
| `--author` | string | — | Filter by author |
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
| `--label` | string[] | — | Filter by labels (AND) |
|
||||||
|
| `--path` | string | — | File path filter |
|
||||||
|
| `--since` | duration/date | — | Created after |
|
||||||
|
| `--updated-since` | duration/date | — | Updated after |
|
||||||
|
| `-n, --limit` | int | 20 | Max results (max: 100) |
|
||||||
|
| `--fields` | string | — | Preset: `minimal` |
|
||||||
|
| `--explain` | flag | — | Show ranking breakdown |
|
||||||
|
| `--fts-mode` | enum | `safe` | `safe\|raw` |
|
||||||
|
|
||||||
|
**DB tables:** `documents`, `documents_fts` (FTS5), `embeddings` (vec0), `document_labels`, `document_paths`, `projects`
|
||||||
|
|
||||||
|
**Search modes:**
|
||||||
|
- **lexical** — FTS5 with BM25 ranking (fastest, no Ollama needed)
|
||||||
|
- **hybrid** — RRF combination of lexical + semantic (default)
|
||||||
|
- **semantic** — Vector similarity only (requires Ollama)
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"query": "authentication bug",
|
||||||
|
"mode": "hybrid",
|
||||||
|
"total_results": 15,
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"document_id": 1234, "source_type": "issue",
|
||||||
|
"title": "Fix SSO auth", "url": "...",
|
||||||
|
"author": "jdoe", "project_path": "group/repo",
|
||||||
|
"labels": ["auth"], "paths": ["src/auth/"],
|
||||||
|
"snippet": "...matching text...",
|
||||||
|
"score": 0.85,
|
||||||
|
"explain": { "vector_rank": 2, "fts_rank": 1, "rrf_score": 0.85 }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"warnings": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minimal preset:** `document_id`, `title`, `source_type`, `score`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `count`
|
||||||
|
|
||||||
|
Count entities in local database.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `<ENTITY>` | positional | required | `issues\|mrs\|discussions\|notes\|events\|references` |
|
||||||
|
| `-f, --for` | enum | — | Parent type: `issue\|mr` |
|
||||||
|
|
||||||
|
**DB tables:** Conditional aggregation on entity tables
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"entity": "merge_requests",
|
||||||
|
"count": 1234,
|
||||||
|
"system_excluded": 5000,
|
||||||
|
"breakdown": { "opened": 100, "closed": 50, "merged": 1084 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
452
docs/command-surface-analysis/02-intelligence-commands.md
Normal file
452
docs/command-surface-analysis/02-intelligence-commands.md
Normal file
@@ -0,0 +1,452 @@
|
|||||||
|
# Intelligence Commands
|
||||||
|
|
||||||
|
Reference for: `who`, `timeline`, `me`, `file-history`, `trace`, `related`, `drift`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `who` (People Intelligence)
|
||||||
|
|
||||||
|
Five sub-modes, dispatched by argument shape.
|
||||||
|
|
||||||
|
| Mode | Trigger | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| **expert** | `who <path>` or `who --path <path>` | Who knows about a code area? |
|
||||||
|
| **workload** | `who @username` | What is this person working on? |
|
||||||
|
| **reviews** | `who @username --reviews` | Review pattern analysis |
|
||||||
|
| **active** | `who --active` | Unresolved discussions needing attention |
|
||||||
|
| **overlap** | `who --overlap <path>` | Who else touches these files? |
|
||||||
|
|
||||||
|
### Shared Flags
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
| `-n, --limit` | int | varies | Max results (1-500) |
|
||||||
|
| `--fields` | string | — | Preset: `minimal` |
|
||||||
|
| `--since` | duration/date | — | Time window |
|
||||||
|
| `--include-bots` | flag | — | Include bot users |
|
||||||
|
| `--include-closed` | flag | — | Include closed issues/MRs |
|
||||||
|
| `--all-history` | flag | — | Query all history |
|
||||||
|
|
||||||
|
### Expert-Only Flags
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--detail` | flag | — | Per-MR breakdown |
|
||||||
|
| `--as-of` | date/duration | — | Score at point in time |
|
||||||
|
| `--explain-score` | flag | — | Score breakdown |
|
||||||
|
|
||||||
|
### DB Tables by Mode
|
||||||
|
|
||||||
|
| Mode | Primary Tables |
|
||||||
|
|---|---|
|
||||||
|
| expert | `notes` (INDEXED BY idx_notes_diffnote_path_created), `merge_requests`, `mr_reviewers` |
|
||||||
|
| workload | `issues`, `merge_requests`, `mr_reviewers` |
|
||||||
|
| reviews | `merge_requests`, `discussions`, `notes` |
|
||||||
|
| active | `discussions`, `notes`, `issues`, `merge_requests` |
|
||||||
|
| overlap | `notes`, `mr_file_changes`, `merge_requests` |
|
||||||
|
|
||||||
|
### Robot Output (expert)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"mode": "expert",
|
||||||
|
"input": { "target": "src/auth/", "path": "src/auth/" },
|
||||||
|
"resolved_input": { "mode": "expert", "project_id": 1, "project_path": "group/repo" },
|
||||||
|
"result": {
|
||||||
|
"experts": [
|
||||||
|
{
|
||||||
|
"username": "jdoe", "score": 42.5,
|
||||||
|
"detail": { "mr_ids_author": [99, 101], "mr_ids_reviewer": [88] }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Output (workload)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"mode": "workload",
|
||||||
|
"result": {
|
||||||
|
"assigned_issues": [{ "iid": 42, "title": "Fix auth", "state": "opened" }],
|
||||||
|
"authored_mrs": [{ "iid": 99, "title": "Refactor auth", "state": "merged" }],
|
||||||
|
"review_mrs": [{ "iid": 88, "title": "Add SSO", "state": "opened" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Output (reviews)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"mode": "reviews",
|
||||||
|
"result": {
|
||||||
|
"categories": [
|
||||||
|
{
|
||||||
|
"category": "approval_rate",
|
||||||
|
"reviewers": [{ "name": "jdoe", "count": 15, "percentage": 85.0 }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Output (active)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"mode": "active",
|
||||||
|
"result": {
|
||||||
|
"discussions": [
|
||||||
|
{ "entity_type": "mr", "iid": 99, "title": "Refactor auth", "participants": ["jdoe", "reviewer"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Robot Output (overlap)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"mode": "overlap",
|
||||||
|
"result": {
|
||||||
|
"users": [{ "username": "jdoe", "touch_count": 15 }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Minimal Presets
|
||||||
|
|
||||||
|
| Mode | Fields |
|
||||||
|
|---|---|
|
||||||
|
| expert | `username`, `score` |
|
||||||
|
| workload | `iid`, `title`, `state` |
|
||||||
|
| reviews | `name`, `count`, `percentage` |
|
||||||
|
| active | `entity_type`, `iid`, `title`, `participants` |
|
||||||
|
| overlap | `username`, `touch_count` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `timeline`
|
||||||
|
|
||||||
|
Reconstruct chronological event history for a topic/entity with cross-reference expansion.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `<QUERY>` | positional | required | Search text or entity ref (`issue:42`, `mr:99`) |
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
| `--since` | duration/date | — | Filter events after |
|
||||||
|
| `--depth` | int | 1 | Cross-ref expansion depth (0=none) |
|
||||||
|
| `--no-mentions` | flag | — | Skip "mentioned" edges, keep "closes"/"related" |
|
||||||
|
| `-n, --limit` | int | 100 | Max events |
|
||||||
|
| `--fields` | string | — | Preset: `minimal` |
|
||||||
|
| `--max-seeds` | int | 10 | Max seed entities from search |
|
||||||
|
| `--max-entities` | int | 50 | Max expanded entities |
|
||||||
|
| `--max-evidence` | int | 10 | Max evidence notes |
|
||||||
|
|
||||||
|
**Pipeline:** SEED -> HYDRATE -> EXPAND -> COLLECT -> RENDER
|
||||||
|
|
||||||
|
**DB tables:** `issues`, `merge_requests`, `discussions`, `notes`, `entity_references`, `resource_state_events`, `resource_label_events`, `resource_milestone_events`, `documents` (for search seeding)
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"query": "authentication", "event_count": 25,
|
||||||
|
"seed_entities": [{ "type": "issue", "iid": 42, "project": "group/repo" }],
|
||||||
|
"expanded_entities": [
|
||||||
|
{
|
||||||
|
"type": "mr", "iid": 99, "project": "group/repo", "depth": 1,
|
||||||
|
"via": {
|
||||||
|
"from": { "type": "issue", "iid": 42 },
|
||||||
|
"reference_type": "closes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"unresolved_references": [
|
||||||
|
{
|
||||||
|
"source": { "type": "issue", "iid": 42, "project": "group/repo" },
|
||||||
|
"target_type": "mr", "target_iid": 200, "reference_type": "mentioned"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"timestamp": "2026-01-15T10:30:00Z",
|
||||||
|
"entity_type": "issue", "entity_iid": 42, "project": "group/repo",
|
||||||
|
"event_type": "state_changed", "summary": "Reopened",
|
||||||
|
"actor": "jdoe", "is_seed": true,
|
||||||
|
"evidence_notes": [{ "author": "jdoe", "snippet": "..." }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"elapsed_ms": 150, "search_mode": "fts",
|
||||||
|
"expansion_depth": 1, "include_mentions": true,
|
||||||
|
"total_entities": 5, "total_events": 25,
|
||||||
|
"evidence_notes_included": 8, "discussion_threads_included": 3,
|
||||||
|
"unresolved_references": 1, "showing": 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minimal preset:** `timestamp`, `type`, `entity_iid`, `detail`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `me` (Personal Dashboard)
|
||||||
|
|
||||||
|
Personal work dashboard with issues, MRs, activity, and since-last-check inbox.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--issues` | flag | — | Open issues section only |
|
||||||
|
| `--mrs` | flag | — | MRs section only |
|
||||||
|
| `--activity` | flag | — | Activity feed only |
|
||||||
|
| `--since` | duration/date | `30d` | Activity window |
|
||||||
|
| `-p, --project` | string | — | Scope to one project |
|
||||||
|
| `--all` | flag | — | All synced projects |
|
||||||
|
| `--user` | string | — | Override configured username |
|
||||||
|
| `--fields` | string | — | Preset: `minimal` |
|
||||||
|
| `--reset-cursor` | flag | — | Clear since-last-check cursor |
|
||||||
|
|
||||||
|
**Sections (no flags = all):** Issues, MRs authored, MRs reviewing, Activity, Inbox
|
||||||
|
|
||||||
|
**DB tables:** `issues`, `merge_requests`, `resource_state_events`, `projects`, `issue_labels`, `mr_labels`
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"username": "jdoe",
|
||||||
|
"summary": {
|
||||||
|
"project_count": 3, "open_issue_count": 5,
|
||||||
|
"authored_mr_count": 2, "reviewing_mr_count": 1,
|
||||||
|
"needs_attention_count": 3
|
||||||
|
},
|
||||||
|
"since_last_check": {
|
||||||
|
"cursor_iso": "2026-02-25T18:00:00Z",
|
||||||
|
"total_event_count": 8,
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"entity_type": "issue", "entity_iid": 42,
|
||||||
|
"entity_title": "Fix auth", "project": "group/repo",
|
||||||
|
"events": [
|
||||||
|
{ "timestamp_iso": "...", "event_type": "comment",
|
||||||
|
"actor": "reviewer", "summary": "New comment" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"open_issues": [
|
||||||
|
{
|
||||||
|
"project": "group/repo", "iid": 42, "title": "Fix auth",
|
||||||
|
"state": "opened", "attention_state": "needs_attention",
|
||||||
|
"status_name": "In progress", "labels": ["auth"],
|
||||||
|
"updated_at_iso": "..."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"open_mrs_authored": [
|
||||||
|
{
|
||||||
|
"project": "group/repo", "iid": 99, "title": "Refactor auth",
|
||||||
|
"state": "opened", "attention_state": "needs_attention",
|
||||||
|
"draft": false, "labels": ["backend"], "updated_at_iso": "..."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reviewing_mrs": [],
|
||||||
|
"activity": [
|
||||||
|
{
|
||||||
|
"timestamp_iso": "...", "event_type": "state_changed",
|
||||||
|
"entity_type": "issue", "entity_iid": 42, "project": "group/repo",
|
||||||
|
"actor": "jdoe", "is_own": true, "summary": "Closed"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Minimal presets:** Items: `iid, title, attention_state, updated_at_iso` | Activity: `timestamp_iso, event_type, entity_iid, actor`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `file-history`
|
||||||
|
|
||||||
|
Show which MRs touched a file, with linked discussions.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `<PATH>` | positional | required | File path to trace |
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
| `--discussions` | flag | — | Include DiffNote snippets |
|
||||||
|
| `--no-follow-renames` | flag | — | Skip rename chain resolution |
|
||||||
|
| `--merged` | flag | — | Only merged MRs |
|
||||||
|
| `-n, --limit` | int | 50 | Max MRs |
|
||||||
|
|
||||||
|
**DB tables:** `mr_file_changes`, `merge_requests`, `notes` (DiffNotes), `projects`
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"path": "src/auth/middleware.rs",
|
||||||
|
"rename_chain": [
|
||||||
|
{ "previous_path": "src/auth.rs", "mr_iid": 55, "merged_at": "..." }
|
||||||
|
],
|
||||||
|
"merge_requests": [
|
||||||
|
{
|
||||||
|
"iid": 99, "title": "Refactor auth", "state": "merged",
|
||||||
|
"author": "jdoe", "merged_at": "...", "change_type": "modified"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"discussions": [
|
||||||
|
{
|
||||||
|
"discussion_id": 123, "mr_iid": 99, "author": "reviewer",
|
||||||
|
"body_snippet": "...", "path": "src/auth/middleware.rs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": { "elapsed_ms": 30, "total_mrs": 5, "renames_followed": true }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `trace`
|
||||||
|
|
||||||
|
File -> MR -> issue -> discussion chain to understand why code was introduced.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `<PATH>` | positional | required | File path (future: `:line` suffix) |
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
| `--discussions` | flag | — | Include DiffNote snippets |
|
||||||
|
| `--no-follow-renames` | flag | — | Skip rename chain |
|
||||||
|
| `-n, --limit` | int | 20 | Max chains |
|
||||||
|
|
||||||
|
**DB tables:** `mr_file_changes`, `merge_requests`, `issues`, `discussions`, `notes`, `entity_references`
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"path": "src/auth/middleware.rs",
|
||||||
|
"resolved_paths": ["src/auth/middleware.rs", "src/auth.rs"],
|
||||||
|
"trace_chains": [
|
||||||
|
{
|
||||||
|
"mr_iid": 99, "mr_title": "Refactor auth", "mr_state": "merged",
|
||||||
|
"mr_author": "jdoe", "change_type": "modified",
|
||||||
|
"merged_at_iso": "...", "web_url": "...",
|
||||||
|
"issues": [42],
|
||||||
|
"discussions": [
|
||||||
|
{
|
||||||
|
"discussion_id": 123, "author_username": "reviewer",
|
||||||
|
"body_snippet": "...", "path": "src/auth/middleware.rs"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": { "tier": "api_only", "total_chains": 3, "renames_followed": 1 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `related`
|
||||||
|
|
||||||
|
Find semantically related entities via vector search.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `<QUERY_OR_TYPE>` | positional | required | Entity type (`issues`, `mrs`) or free text |
|
||||||
|
| `[IID]` | positional | — | Entity IID (required with entity type) |
|
||||||
|
| `-n, --limit` | int | 10 | Max results |
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
|
||||||
|
**Two modes:**
|
||||||
|
- **Entity mode:** `related issues 42` — find entities similar to issue #42
|
||||||
|
- **Query mode:** `related "auth flow"` — find entities matching free text
|
||||||
|
|
||||||
|
**DB tables:** `documents`, `embeddings` (vec0), `projects`
|
||||||
|
|
||||||
|
**Requires:** Ollama running (for query mode embedding)
|
||||||
|
|
||||||
|
### Robot Output (entity mode)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"query_entity_type": "issue",
|
||||||
|
"query_entity_iid": 42,
|
||||||
|
"query_entity_title": "Fix SSO authentication",
|
||||||
|
"similar_entities": [
|
||||||
|
{
|
||||||
|
"entity_type": "mr", "entity_iid": 99,
|
||||||
|
"entity_title": "Refactor auth module",
|
||||||
|
"project_path": "group/repo", "state": "merged",
|
||||||
|
"similarity_score": 0.87,
|
||||||
|
"shared_labels": ["auth"], "shared_authors": ["jdoe"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## `drift`
|
||||||
|
|
||||||
|
Detect discussion divergence from original intent.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `<ENTITY_TYPE>` | positional | required | Currently only `issues` |
|
||||||
|
| `<IID>` | positional | required | Entity IID |
|
||||||
|
| `--threshold` | f32 | 0.4 | Similarity threshold (0.0-1.0) |
|
||||||
|
| `-p, --project` | string | — | Scope to project |
|
||||||
|
|
||||||
|
**DB tables:** `issues`, `discussions`, `notes`, `embeddings`
|
||||||
|
|
||||||
|
**Requires:** Ollama running
|
||||||
|
|
||||||
|
### Robot Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"entity_type": "issue", "entity_iid": 42,
|
||||||
|
"total_notes": 15,
|
||||||
|
"detected_drift": true,
|
||||||
|
"drift_point": {
|
||||||
|
"note_index": 8, "similarity": 0.32,
|
||||||
|
"author": "someone", "created_at": "..."
|
||||||
|
},
|
||||||
|
"similarity_curve": [
|
||||||
|
{ "note_index": 0, "similarity": 0.95, "author": "jdoe", "created_at": "..." },
|
||||||
|
{ "note_index": 1, "similarity": 0.88, "author": "reviewer", "created_at": "..." }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
210
docs/command-surface-analysis/03-pipeline-and-infra.md
Normal file
210
docs/command-surface-analysis/03-pipeline-and-infra.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
# Pipeline & Infrastructure Commands
|
||||||
|
|
||||||
|
Reference for: `sync`, `ingest`, `generate-docs`, `embed`, `health`, `auth`, `doctor`, `status`, `stats`, `init`, `token`, `cron`, `migrate`, `version`, `completions`, `robot-docs`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Pipeline
|
||||||
|
|
||||||
|
### `sync` (Full Pipeline)
|
||||||
|
|
||||||
|
Complete sync: ingest -> generate-docs -> embed.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--full` | flag | — | Full re-sync (reset cursors) |
|
||||||
|
| `-f, --force` | flag | — | Override stale lock |
|
||||||
|
| `--no-embed` | flag | — | Skip embedding |
|
||||||
|
| `--no-docs` | flag | — | Skip doc generation |
|
||||||
|
| `--no-events` | flag | — | Skip resource events |
|
||||||
|
| `--no-file-changes` | flag | — | Skip MR file changes |
|
||||||
|
| `--no-status` | flag | — | Skip work-item status enrichment |
|
||||||
|
| `--dry-run` | flag | — | Preview without changes |
|
||||||
|
| `-t, --timings` | flag | — | Show timing breakdown |
|
||||||
|
| `--lock` | flag | — | Acquire file lock |
|
||||||
|
| `--issue` | int[] | — | Surgically sync specific issues (repeatable) |
|
||||||
|
| `--mr` | int[] | — | Surgically sync specific MRs (repeatable) |
|
||||||
|
| `-p, --project` | string | — | Required with `--issue`/`--mr` |
|
||||||
|
| `--preflight-only` | flag | — | Validate without DB writes |
|
||||||
|
|
||||||
|
**Stages:** GitLab REST ingest -> GraphQL status enrichment -> Document generation -> Ollama embedding
|
||||||
|
|
||||||
|
**Surgical sync:** `lore sync --issue 42 --mr 99 -p group/repo` fetches only specific entities.
|
||||||
|
|
||||||
|
### `ingest`
|
||||||
|
|
||||||
|
Fetch data from GitLab API only (no docs, no embeddings).
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `[ENTITY]` | positional | — | `issues` or `mrs` (omit for all) |
|
||||||
|
| `-p, --project` | string | — | Single project |
|
||||||
|
| `-f, --force` | flag | — | Override stale lock |
|
||||||
|
| `--full` | flag | — | Full re-sync |
|
||||||
|
| `--dry-run` | flag | — | Preview |
|
||||||
|
|
||||||
|
**Fetches from GitLab:**
|
||||||
|
- Issues + discussions + notes
|
||||||
|
- MRs + discussions + notes
|
||||||
|
- Resource events (state, label, milestone)
|
||||||
|
- MR file changes (for DiffNote tracking)
|
||||||
|
- Work-item statuses (via GraphQL)
|
||||||
|
|
||||||
|
### `generate-docs`
|
||||||
|
|
||||||
|
Create searchable documents from ingested data.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--full` | flag | — | Full rebuild |
|
||||||
|
| `-p, --project` | string | — | Single project rebuild |
|
||||||
|
|
||||||
|
**Writes:** `documents`, `document_labels`, `document_paths`
|
||||||
|
|
||||||
|
### `embed`
|
||||||
|
|
||||||
|
Generate vector embeddings via Ollama.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--full` | flag | — | Re-embed all |
|
||||||
|
| `--retry-failed` | flag | — | Retry failed embeddings |
|
||||||
|
|
||||||
|
**Requires:** Ollama running with `nomic-embed-text`
|
||||||
|
**Writes:** `embeddings`, `embedding_metadata`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Diagnostics
|
||||||
|
|
||||||
|
### `health`
|
||||||
|
|
||||||
|
Quick pre-flight check (~50ms). Exit 0 = healthy, exit 19 = unhealthy.
|
||||||
|
|
||||||
|
**Checks:** config found, DB found, schema version current.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"healthy": true,
|
||||||
|
"config_found": true, "db_found": true,
|
||||||
|
"schema_current": true, "schema_version": 28
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `auth`
|
||||||
|
|
||||||
|
Verify GitLab authentication.
|
||||||
|
|
||||||
|
**Checks:** token set, GitLab reachable, user identity.
|
||||||
|
|
||||||
|
### `doctor`
|
||||||
|
|
||||||
|
Comprehensive environment check.
|
||||||
|
|
||||||
|
**Checks:** config validity, token, GitLab connectivity, DB health, migration status, Ollama availability + model status.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"config": { "valid": true, "path": "~/.config/lore/config.json" },
|
||||||
|
"token": { "set": true, "gitlab": { "reachable": true, "user": "jdoe" } },
|
||||||
|
"database": { "exists": true, "version": 28, "tables": 25 },
|
||||||
|
"ollama": { "available": true, "model_ready": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `status` (alias: `st`)
|
||||||
|
|
||||||
|
Show sync state per project.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"project_path": "group/repo",
|
||||||
|
"last_synced_at": "2026-02-26T10:00:00Z",
|
||||||
|
"document_count": 5000, "discussion_count": 2000, "notes_count": 15000
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### `stats` (alias: `stat`)
|
||||||
|
|
||||||
|
Document and index statistics with optional integrity checks.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `--check` | flag | — | Run integrity checks |
|
||||||
|
| `--repair` | flag | — | Fix issues (implies `--check`) |
|
||||||
|
| `--dry-run` | flag | — | Preview repairs |
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"documents": { "total": 61652, "issues": 5000, "mrs": 2000, "notes": 50000 },
|
||||||
|
"embeddings": { "total": 80000, "synced": 79500, "pending": 500, "failed": 0 },
|
||||||
|
"fts": { "total_docs": 61652 },
|
||||||
|
"queues": { "pending": 0, "in_progress": 0, "failed": 0, "max_attempts": 0 },
|
||||||
|
"integrity": {
|
||||||
|
"ok": true, "fts_doc_mismatch": 0, "orphan_embeddings": 0,
|
||||||
|
"stale_metadata": 0, "orphan_state_events": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### `init`
|
||||||
|
|
||||||
|
Initialize configuration and database.
|
||||||
|
|
||||||
|
| Flag | Type | Default | Purpose |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `-f, --force` | flag | — | Skip overwrite confirmation |
|
||||||
|
| `--non-interactive` | flag | — | Fail if prompts needed |
|
||||||
|
| `--gitlab-url` | string | — | GitLab base URL (required in robot mode) |
|
||||||
|
| `--token-env-var` | string | — | Env var holding token (required in robot mode) |
|
||||||
|
| `--projects` | string | — | Comma-separated project paths (required in robot mode) |
|
||||||
|
| `--default-project` | string | — | Default project path |
|
||||||
|
|
||||||
|
### `token`
|
||||||
|
|
||||||
|
| Subcommand | Flags | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `token set` | `--token <TOKEN>` | Store token (reads stdin if omitted) |
|
||||||
|
| `token show` | `--unmask` | Display token (masked by default) |
|
||||||
|
|
||||||
|
### `cron`
|
||||||
|
|
||||||
|
| Subcommand | Flags | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `cron install` | `--interval <MINUTES>` (default: 8) | Schedule auto-sync |
|
||||||
|
| `cron uninstall` | — | Remove cron job |
|
||||||
|
| `cron status` | — | Check installation |
|
||||||
|
|
||||||
|
### `migrate`
|
||||||
|
|
||||||
|
Run pending database migrations. No flags.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Meta
|
||||||
|
|
||||||
|
| Command | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `version` | Show version string |
|
||||||
|
| `completions <shell>` | Generate shell completions (bash/zsh/fish/powershell) |
|
||||||
|
| `robot-docs` | Machine-readable command manifest (`--brief` for ~60% smaller) |
|
||||||
179
docs/command-surface-analysis/04-data-flow.md
Normal file
179
docs/command-surface-analysis/04-data-flow.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Data Flow & Command Network
|
||||||
|
|
||||||
|
How commands interconnect through shared data sources and output-to-input dependencies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Command Network Graph
|
||||||
|
|
||||||
|
Arrows mean "output of A feeds as input to B":
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────┐
|
||||||
|
│ search │─────────────────────────────┐
|
||||||
|
└────┬────┘ │
|
||||||
|
│ iid │ topic
|
||||||
|
┌────▼────┐ ┌────▼─────┐
|
||||||
|
┌─────│ issues │◄───────────────────────│ timeline │
|
||||||
|
│ │ mrs │ (detail) └──────────┘
|
||||||
|
│ └────┬────┘ ▲
|
||||||
|
│ │ iid │ entity ref
|
||||||
|
│ ┌────▼────┐ ┌──────────────┐ │
|
||||||
|
│ │ related │ │ file-history │───────┘
|
||||||
|
│ │ drift │ └──────┬───────┘
|
||||||
|
│ └─────────┘ │ MR iids
|
||||||
|
│ ┌────▼────┐
|
||||||
|
│ │ trace │──── issues (linked)
|
||||||
|
│ └────┬────┘
|
||||||
|
│ │ paths
|
||||||
|
│ ┌────▼────┐
|
||||||
|
│ │ who │
|
||||||
|
│ │ (expert)│
|
||||||
|
│ └─────────┘
|
||||||
|
│
|
||||||
|
file paths ┌─────────┐
|
||||||
|
│ │ me │──── issues, mrs (dashboard)
|
||||||
|
▼ └─────────┘
|
||||||
|
┌──────────┐ ▲
|
||||||
|
│ notes │ │ (~same data)
|
||||||
|
└──────────┘ ┌────┴──────┐
|
||||||
|
│who workload│
|
||||||
|
└───────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Feed Chains (output of A -> input of B)
|
||||||
|
|
||||||
|
| From | To | What Flows |
|
||||||
|
|---|---|---|
|
||||||
|
| `search` | `issues`, `mrs` | IIDs from search results -> detail lookup |
|
||||||
|
| `search` | `timeline` | Topic/query -> chronological history |
|
||||||
|
| `search` | `related` | Entity IID -> semantic similarity |
|
||||||
|
| `me` | `issues`, `mrs` | IIDs from dashboard -> detail lookup |
|
||||||
|
| `trace` | `issues` | Linked issue IIDs -> detail lookup |
|
||||||
|
| `trace` | `who` | File paths -> expert lookup |
|
||||||
|
| `file-history` | `mrs` | MR IIDs -> detail lookup |
|
||||||
|
| `file-history` | `timeline` | Entity refs -> chronological events |
|
||||||
|
| `timeline` | `issues`, `mrs` | Referenced IIDs -> detail lookup |
|
||||||
|
| `who expert` | `who reviews` | Username -> review patterns |
|
||||||
|
| `who expert` | `mrs` | MR IIDs from expert detail -> MR detail |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Shared Data Source Map
|
||||||
|
|
||||||
|
Which DB tables power which commands. Higher overlap = stronger consolidation signal.
|
||||||
|
|
||||||
|
### Primary Entity Tables
|
||||||
|
|
||||||
|
| Table | Read By |
|
||||||
|
|---|---|
|
||||||
|
| `issues` | issues, me, who-workload, search, timeline, trace, count, stats |
|
||||||
|
| `merge_requests` | mrs, me, who-workload, search, timeline, trace, file-history, count, stats |
|
||||||
|
| `notes` | notes, issues-detail, mrs-detail, who-expert, who-active, search, timeline, trace, file-history |
|
||||||
|
| `discussions` | notes, issues-detail, mrs-detail, who-active, who-reviews, timeline, trace |
|
||||||
|
|
||||||
|
### Relationship Tables
|
||||||
|
|
||||||
|
| Table | Read By |
|
||||||
|
|---|---|
|
||||||
|
| `entity_references` | trace, timeline |
|
||||||
|
| `mr_file_changes` | trace, file-history, who-overlap |
|
||||||
|
| `issue_labels` | issues, me |
|
||||||
|
| `mr_labels` | mrs, me |
|
||||||
|
| `issue_assignees` | issues, me |
|
||||||
|
| `mr_reviewers` | mrs, who-expert, who-workload |
|
||||||
|
|
||||||
|
### Event Tables
|
||||||
|
|
||||||
|
| Table | Read By |
|
||||||
|
|---|---|
|
||||||
|
| `resource_state_events` | timeline, me-activity |
|
||||||
|
| `resource_label_events` | timeline |
|
||||||
|
| `resource_milestone_events` | timeline |
|
||||||
|
|
||||||
|
### Document/Search Tables
|
||||||
|
|
||||||
|
| Table | Read By |
|
||||||
|
|---|---|
|
||||||
|
| `documents` + `documents_fts` | search, stats |
|
||||||
|
| `embeddings` | search, related, drift |
|
||||||
|
| `document_labels` | search |
|
||||||
|
| `document_paths` | search |
|
||||||
|
|
||||||
|
### Infrastructure Tables
|
||||||
|
|
||||||
|
| Table | Read By |
|
||||||
|
|---|---|
|
||||||
|
| `sync_cursors` | status |
|
||||||
|
| `dirty_sources` | stats |
|
||||||
|
| `embedding_metadata` | stats, embed |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Shared-Data Clusters
|
||||||
|
|
||||||
|
Commands that read from the same primary tables form natural clusters:
|
||||||
|
|
||||||
|
### Cluster A: Issue/MR Entities
|
||||||
|
|
||||||
|
`issues`, `mrs`, `me`, `who workload`, `count`
|
||||||
|
|
||||||
|
All read `issues` + `merge_requests` with similar filter patterns (state, author, labels, project). These commands share the same underlying WHERE-clause builder logic.
|
||||||
|
|
||||||
|
### Cluster B: Notes/Discussions
|
||||||
|
|
||||||
|
`notes`, `issues detail`, `mrs detail`, `who expert`, `who active`, `timeline`
|
||||||
|
|
||||||
|
All traverse the `discussions` -> `notes` join path. The `notes` command does it with independent filters; the others embed notes within parent context.
|
||||||
|
|
||||||
|
### Cluster C: File Genealogy
|
||||||
|
|
||||||
|
`trace`, `file-history`, `who overlap`
|
||||||
|
|
||||||
|
All use `mr_file_changes` with rename chain BFS (forward: old_path -> new_path, backward: new_path -> old_path). Shared `resolve_rename_chain()` function.
|
||||||
|
|
||||||
|
### Cluster D: Semantic/Vector
|
||||||
|
|
||||||
|
`search`, `related`, `drift`
|
||||||
|
|
||||||
|
All use `documents` + `embeddings` via Ollama. `search` adds FTS component; `related` is pure vector; `drift` uses vector for divergence scoring.
|
||||||
|
|
||||||
|
### Cluster E: Diagnostics
|
||||||
|
|
||||||
|
`health`, `auth`, `doctor`, `status`, `stats`
|
||||||
|
|
||||||
|
All check system state. `health` < `doctor` (strict subset). `status` checks sync cursors. `stats` checks document/index health. `auth` checks token/connectivity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Query Pattern Sharing
|
||||||
|
|
||||||
|
### Dynamic Filter Builder (used by issues, mrs, notes)
|
||||||
|
|
||||||
|
All three list commands use the same pattern: build a WHERE clause dynamically from filter flags with parameterized tokens. Labels use EXISTS subquery against junction table.
|
||||||
|
|
||||||
|
### Rename Chain BFS (used by trace, file-history, who overlap)
|
||||||
|
|
||||||
|
Forward query:
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT new_path FROM mr_file_changes
|
||||||
|
WHERE project_id = ?1 AND old_path = ?2 AND change_type = 'renamed'
|
||||||
|
```
|
||||||
|
|
||||||
|
Backward query:
|
||||||
|
```sql
|
||||||
|
SELECT DISTINCT old_path FROM mr_file_changes
|
||||||
|
WHERE project_id = ?1 AND new_path = ?2 AND change_type = 'renamed'
|
||||||
|
```
|
||||||
|
|
||||||
|
Cycle detection via `HashSet` of visited paths, `MAX_RENAME_HOPS = 10`.
|
||||||
|
|
||||||
|
### Hybrid Search (used by search, timeline seeding)
|
||||||
|
|
||||||
|
RRF ranking: `score = (60 / fts_rank) + (60 / vector_rank)`
|
||||||
|
|
||||||
|
FTS5 queries go through `to_fts_query()` which sanitizes input and builds MATCH expressions. Vector search calls Ollama to embed the query, then does cosine similarity against `embeddings` vec0 table.
|
||||||
|
|
||||||
|
### Project Resolution (used by most commands)
|
||||||
|
|
||||||
|
`resolve_project(conn, project_filter)` does fuzzy matching on `path_with_namespace` — suffix and substring matching. Returns `(project_id, path_with_namespace)`.
|
||||||
170
docs/command-surface-analysis/05-overlap-analysis.md
Normal file
170
docs/command-surface-analysis/05-overlap-analysis.md
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
# Overlap Analysis
|
||||||
|
|
||||||
|
Quantified functional duplication between commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. High Overlap (>70%)
|
||||||
|
|
||||||
|
### `who workload` vs `me` — 85% overlap
|
||||||
|
|
||||||
|
| Dimension | `who @user` (workload) | `me --user @user` |
|
||||||
|
|---|---|---|
|
||||||
|
| Assigned issues | Yes | Yes |
|
||||||
|
| Authored MRs | Yes | Yes |
|
||||||
|
| Reviewing MRs | Yes | Yes |
|
||||||
|
| Attention state | No | **Yes** |
|
||||||
|
| Activity feed | No | **Yes** |
|
||||||
|
| Since-last-check inbox | No | **Yes** |
|
||||||
|
| Cross-project | Yes | **Yes** |
|
||||||
|
|
||||||
|
**Verdict:** `who workload` is a strict subset of `me`. The only reason to use `who workload` is if you DON'T want attention_state/activity/inbox — but `me --issues --mrs --fields minimal` achieves the same thing.
|
||||||
|
|
||||||
|
### `health` vs `doctor` — 90% overlap
|
||||||
|
|
||||||
|
| Check | `health` | `doctor` |
|
||||||
|
|---|---|---|
|
||||||
|
| Config found | Yes | Yes |
|
||||||
|
| DB exists | Yes | Yes |
|
||||||
|
| Schema current | Yes | Yes |
|
||||||
|
| Token valid | No | **Yes** |
|
||||||
|
| GitLab reachable | No | **Yes** |
|
||||||
|
| Ollama available | No | **Yes** |
|
||||||
|
|
||||||
|
**Verdict:** `health` is a strict subset of `doctor`. However, `health` has unique value as a ~50ms pre-flight with clean exit 0/19 semantics for scripting.
|
||||||
|
|
||||||
|
### `file-history` vs `trace` — 75% overlap
|
||||||
|
|
||||||
|
| Feature | `file-history` | `trace` |
|
||||||
|
|---|---|---|
|
||||||
|
| Find MRs for file | Yes | Yes |
|
||||||
|
| Rename chain BFS | Yes | Yes |
|
||||||
|
| DiffNote discussions | `--discussions` | `--discussions` |
|
||||||
|
| Follow to linked issues | No | **Yes** |
|
||||||
|
| `--merged` filter | **Yes** | No |
|
||||||
|
|
||||||
|
**Verdict:** `trace` is a superset of `file-history` minus the `--merged` filter. Both use the same `resolve_rename_chain()` function and query `mr_file_changes`.
|
||||||
|
|
||||||
|
### `related` query-mode vs `search --mode semantic` — 80% overlap
|
||||||
|
|
||||||
|
| Feature | `related "text"` | `search "text" --mode semantic` |
|
||||||
|
|---|---|---|
|
||||||
|
| Vector similarity | Yes | Yes |
|
||||||
|
| FTS component | No | No (semantic mode skips FTS) |
|
||||||
|
| Filters (labels, author, since) | No | **Yes** |
|
||||||
|
| Explain ranking | No | **Yes** |
|
||||||
|
| Field selection | No | **Yes** |
|
||||||
|
| Requires Ollama | Yes | Yes |
|
||||||
|
|
||||||
|
**Verdict:** `related "text"` is `search --mode semantic` without any filter capabilities. The entity-seeded mode (`related issues 42`) is NOT duplicated — it seeds from an existing entity's embedding.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Medium Overlap (40-70%)
|
||||||
|
|
||||||
|
### `who expert` vs `who overlap` — 50%
|
||||||
|
|
||||||
|
Both answer "who works on this file" but with different scoring:
|
||||||
|
|
||||||
|
| Aspect | `who expert` | `who overlap` |
|
||||||
|
|---|---|---|
|
||||||
|
| Scoring | Half-life decay, signal types (diffnote_author, reviewer, etc.) | Raw touch count |
|
||||||
|
| Output | Ranked experts with scores | Users with touch counts |
|
||||||
|
| Use case | "Who should review this?" | "Who else touches this?" |
|
||||||
|
|
||||||
|
**Verdict:** Overlap is a simplified version of expert. Expert could include touch_count as a field.
|
||||||
|
|
||||||
|
### `timeline` vs `trace` — 45%
|
||||||
|
|
||||||
|
Both follow `entity_references` to discover connected entities, but from different entry points:
|
||||||
|
|
||||||
|
| Aspect | `timeline` | `trace` |
|
||||||
|
|---|---|---|
|
||||||
|
| Entry point | Entity (issue/MR) or search query | File path |
|
||||||
|
| Direction | Entity -> cross-refs -> events | File -> MRs -> issues -> discussions |
|
||||||
|
| Output | Chronological events | Causal chains (why code changed) |
|
||||||
|
| Expansion | Depth-controlled cross-ref following | MR -> issue via entity_references |
|
||||||
|
|
||||||
|
**Verdict:** Complementary, not duplicative. Different questions, shared plumbing.
|
||||||
|
|
||||||
|
### `auth` vs `doctor` — 100% of auth
|
||||||
|
|
||||||
|
`auth` checks: token set + GitLab reachable + user identity.
|
||||||
|
`doctor` checks: all of the above + DB + schema + Ollama.
|
||||||
|
|
||||||
|
**Verdict:** `auth` is completely contained within `doctor`.
|
||||||
|
|
||||||
|
### `count` vs `stats` — 40%
|
||||||
|
|
||||||
|
Both answer "how much data?":
|
||||||
|
|
||||||
|
| Aspect | `count` | `stats` |
|
||||||
|
|---|---|---|
|
||||||
|
| Layer | Entity (issues, MRs, notes) | Document index |
|
||||||
|
| State breakdown | Yes (opened/closed/merged) | No |
|
||||||
|
| Integrity checks | No | Yes |
|
||||||
|
| Queue status | No | Yes |
|
||||||
|
|
||||||
|
**Verdict:** Different layers. Could be unified under `stats --entities`.
|
||||||
|
|
||||||
|
### `notes` vs `issues/mrs detail` — 50%
|
||||||
|
|
||||||
|
Both return note content:
|
||||||
|
|
||||||
|
| Aspect | `notes` command | Detail view discussions |
|
||||||
|
|---|---|---|
|
||||||
|
| Independent filtering | **Yes** (author, path, resolution, contains, type) | No |
|
||||||
|
| Parent context | Minimal (parent_iid, parent_title) | **Full** (complete entity + all discussions) |
|
||||||
|
| Cross-entity queries | **Yes** (all notes matching criteria) | No (one entity only) |
|
||||||
|
|
||||||
|
**Verdict:** `notes` is for filtered queries across entities. Detail views are for complete context on one entity. Different use cases.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. No Significant Overlap
|
||||||
|
|
||||||
|
| Command | Why It's Unique |
|
||||||
|
|---|---|
|
||||||
|
| `drift` | Only command doing semantic divergence detection |
|
||||||
|
| `timeline` | Only command doing multi-entity chronological reconstruction with expansion |
|
||||||
|
| `search` (hybrid) | Only command combining FTS + vector with RRF ranking |
|
||||||
|
| `me` (inbox) | Only command with cursor-based since-last-check tracking |
|
||||||
|
| `who expert` | Only command with half-life decay scoring by signal type |
|
||||||
|
| `who reviews` | Only command analyzing review patterns (approval rate, latency) |
|
||||||
|
| `who active` | Only command surfacing unresolved discussions needing attention |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Overlap Adjacency Matrix
|
||||||
|
|
||||||
|
Rows/columns are commands. Values are estimated functional overlap percentage.
|
||||||
|
|
||||||
|
```
|
||||||
|
issues mrs notes search who-e who-w who-r who-a who-o timeline me fh trace related drift count status stats health doctor
|
||||||
|
issues - 30 50 20 5 40 0 5 0 15 40 0 10 10 0 20 0 10 0 0
|
||||||
|
mrs 30 - 50 20 5 40 0 5 0 15 40 5 10 10 0 20 0 10 0 0
|
||||||
|
notes 50 50 - 15 15 0 5 10 0 10 0 5 5 0 0 0 0 0 0 0
|
||||||
|
search 20 20 15 - 0 0 0 0 0 15 0 0 0 80 0 0 0 5 0 0
|
||||||
|
who-expert 5 5 15 0 - 0 10 0 50 0 0 10 10 0 0 0 0 0 0 0
|
||||||
|
who-workload 40 40 0 0 0 - 0 0 0 0 85 0 0 0 0 0 0 0 0 0
|
||||||
|
who-reviews 0 0 5 0 10 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||||
|
who-active 5 5 10 0 0 0 0 - 0 5 0 0 0 0 0 0 0 0 0 0
|
||||||
|
who-overlap 0 0 0 0 50 0 0 0 - 0 0 10 5 0 0 0 0 0 0 0
|
||||||
|
timeline 15 15 10 15 0 0 0 5 0 - 5 5 45 0 0 0 0 0 0 0
|
||||||
|
me 40 40 0 0 0 85 0 0 0 5 - 0 0 0 0 0 5 0 5 5
|
||||||
|
file-history 0 5 5 0 10 0 0 0 10 5 0 - 75 0 0 0 0 0 0 0
|
||||||
|
trace 10 10 5 0 10 0 0 0 5 45 0 75 - 0 0 0 0 0 0 0
|
||||||
|
related 10 10 0 80 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0
|
||||||
|
drift 0 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0
|
||||||
|
count 20 20 0 0 0 0 0 0 0 0 0 0 0 0 0 - 0 40 0 0
|
||||||
|
status 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 - 20 30 40
|
||||||
|
stats 10 10 0 5 0 0 0 0 0 0 0 0 0 0 0 40 20 - 0 15
|
||||||
|
health 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 30 0 - 90
|
||||||
|
doctor 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 40 15 90 -
|
||||||
|
```
|
||||||
|
|
||||||
|
**Highest overlap pairs (>= 75%):**
|
||||||
|
1. `health` / `doctor` — 90%
|
||||||
|
2. `who workload` / `me` — 85%
|
||||||
|
3. `related` query-mode / `search semantic` — 80%
|
||||||
|
4. `file-history` / `trace` — 75%
|
||||||
216
docs/command-surface-analysis/06-agent-workflows.md
Normal file
216
docs/command-surface-analysis/06-agent-workflows.md
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# Agent Workflow Analysis
|
||||||
|
|
||||||
|
Common agent workflows, round-trip costs, and token profiles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Common Workflows
|
||||||
|
|
||||||
|
### Flow 1: "What should I work on?" — 4 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
me → dashboard overview (which items need attention?)
|
||||||
|
issues <iid> -p proj → detail on picked issue (full context + discussions)
|
||||||
|
trace src/relevant/file.rs → understand code context (why was it written?)
|
||||||
|
who src/relevant/file.rs → find domain experts (who can help?)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tokens (minimal):** ~800 + ~2000 + ~1000 + ~400 = ~4200
|
||||||
|
**Total tokens (full):** ~3000 + ~6000 + ~1500 + ~800 = ~11300
|
||||||
|
**Latency:** 4 serial round trips
|
||||||
|
|
||||||
|
### Flow 2: "What happened with this feature?" — 3 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
search "feature name" → find relevant entities
|
||||||
|
timeline "feature name" → reconstruct chronological history
|
||||||
|
related issues 42 → discover connected work
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tokens (minimal):** ~600 + ~1500 + ~400 = ~2500
|
||||||
|
**Total tokens (full):** ~2000 + ~5000 + ~1000 = ~8000
|
||||||
|
**Latency:** 3 serial round trips
|
||||||
|
|
||||||
|
### Flow 3: "Why was this code changed?" — 3 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
trace src/file.rs → file -> MR -> issue chain
|
||||||
|
issues <iid> -p proj → full issue detail
|
||||||
|
timeline "issue:42" → full history with cross-refs
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tokens (minimal):** ~800 + ~2000 + ~1500 = ~4300
|
||||||
|
**Total tokens (full):** ~1500 + ~6000 + ~5000 = ~12500
|
||||||
|
**Latency:** 3 serial round trips
|
||||||
|
|
||||||
|
### Flow 4: "Is the system healthy?" — 2-4 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
health → quick pre-flight (pass/fail)
|
||||||
|
doctor → detailed diagnostics (if health fails)
|
||||||
|
status → sync state per project
|
||||||
|
stats → document/index health
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tokens:** ~100 + ~300 + ~200 + ~400 = ~1000
|
||||||
|
**Latency:** 2-4 serial round trips (often 1 if health passes)
|
||||||
|
|
||||||
|
### Flow 5: "Who can review this?" — 2-3 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
who src/auth/ → find file experts
|
||||||
|
who @jdoe --reviews → check reviewer's patterns
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tokens (minimal):** ~300 + ~300 = ~600
|
||||||
|
**Latency:** 2 serial round trips
|
||||||
|
|
||||||
|
### Flow 6: "Find and understand an issue" — 4 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
search "query" → discover entities (get IIDs)
|
||||||
|
issues <iid> → full detail with discussions
|
||||||
|
timeline "issue:42" → chronological context
|
||||||
|
related issues 42 → connected entities
|
||||||
|
```
|
||||||
|
|
||||||
|
**Total tokens (minimal):** ~600 + ~2000 + ~1500 + ~400 = ~4500
|
||||||
|
**Total tokens (full):** ~2000 + ~6000 + ~5000 + ~1000 = ~14000
|
||||||
|
**Latency:** 4 serial round trips
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Token Cost Profiles
|
||||||
|
|
||||||
|
Measured typical response sizes in robot mode with default settings:
|
||||||
|
|
||||||
|
| Command | Typical Tokens (full) | With `--fields minimal` | Dominant Cost Driver |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `me` (all sections) | 2000-5000 | 500-1500 | Open items count |
|
||||||
|
| `issues` (list, n=50) | 1500-3000 | 400-800 | Labels arrays |
|
||||||
|
| `issues <iid>` (detail) | 1000-8000 | N/A (no minimal for detail) | Discussion depth |
|
||||||
|
| `mrs <iid>` (detail) | 1000-8000 | N/A | Discussion depth, DiffNote positions |
|
||||||
|
| `timeline` (limit=100) | 2000-6000 | 800-1500 | Event count + evidence |
|
||||||
|
| `search` (n=20) | 1000-3000 | 300-600 | Snippet length |
|
||||||
|
| `who expert` | 300-800 | 150-300 | Expert count |
|
||||||
|
| `who workload` | 500-1500 | 200-500 | Open items count |
|
||||||
|
| `trace` | 500-2000 | 300-800 | Chain depth |
|
||||||
|
| `file-history` | 300-1500 | 200-500 | MR count |
|
||||||
|
| `related` | 300-1000 | 200-400 | Result count |
|
||||||
|
| `drift` | 200-800 | N/A | Similarity curve length |
|
||||||
|
| `notes` (n=50) | 1500-5000 | 500-1000 | Body length |
|
||||||
|
| `count` | ~100 | N/A | Fixed structure |
|
||||||
|
| `stats` | ~500 | N/A | Fixed structure |
|
||||||
|
| `health` | ~100 | N/A | Fixed structure |
|
||||||
|
| `doctor` | ~300 | N/A | Fixed structure |
|
||||||
|
| `status` | ~200 | N/A | Project count |
|
||||||
|
|
||||||
|
### Key Observations
|
||||||
|
|
||||||
|
1. **Detail commands are expensive.** `issues <iid>` and `mrs <iid>` can hit 8000 tokens due to discussions. This is the content agents actually need, but most of it is discussion body text.
|
||||||
|
|
||||||
|
2. **`me` is the most-called command** and ranges 2000-5000 tokens. Agents often just need "do I have work?" which is ~100 tokens (summary counts only).
|
||||||
|
|
||||||
|
3. **Lists with labels are wasteful.** Every issue/MR in a list carries its full label array. With 50 items x 5 labels each, that's 250 strings of overhead.
|
||||||
|
|
||||||
|
4. **`--fields minimal` helps a lot** — 50-70% reduction on list commands. But it's not available on detail views.
|
||||||
|
|
||||||
|
5. **Timeline scales linearly** with event count and evidence notes. The `--max-evidence` flag helps cap the expensive part.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Round-Trip Inefficiency Patterns
|
||||||
|
|
||||||
|
### Pattern A: Discovery -> Detail (N+1)
|
||||||
|
|
||||||
|
Agent searches, gets 5 results, then needs detail on each:
|
||||||
|
|
||||||
|
```
|
||||||
|
search "auth bug" → 5 results
|
||||||
|
issues 42 -p proj → detail
|
||||||
|
issues 55 -p proj → detail
|
||||||
|
issues 71 -p proj → detail
|
||||||
|
issues 88 -p proj → detail
|
||||||
|
issues 95 -p proj → detail
|
||||||
|
```
|
||||||
|
|
||||||
|
**6 round trips** for what should be 2 (search + batch detail).
|
||||||
|
|
||||||
|
### Pattern B: Detail -> Context Gathering
|
||||||
|
|
||||||
|
Agent gets issue detail, then needs timeline + related + trace:
|
||||||
|
|
||||||
|
```
|
||||||
|
issues 42 -p proj → detail
|
||||||
|
timeline "issue:42" -p proj → events
|
||||||
|
related issues 42 -p proj → similar
|
||||||
|
trace src/file.rs -p proj → code provenance
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 round trips** for what should be 1 (detail with embedded context).
|
||||||
|
|
||||||
|
### Pattern C: Health Check Cascade
|
||||||
|
|
||||||
|
Agent checks health, discovers issue, drills down:
|
||||||
|
|
||||||
|
```
|
||||||
|
health → unhealthy (exit 19)
|
||||||
|
doctor → token OK, Ollama missing
|
||||||
|
stats --check → 5 orphan embeddings
|
||||||
|
stats --repair → fixed
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 round trips** but only 2 are actually needed (doctor covers health).
|
||||||
|
|
||||||
|
### Pattern D: Dashboard -> Action
|
||||||
|
|
||||||
|
Agent checks dashboard, picks item, needs full context:
|
||||||
|
|
||||||
|
```
|
||||||
|
me → 5 open issues, 2 MRs
|
||||||
|
issues 42 -p proj → picked issue detail
|
||||||
|
who src/auth/ -p proj → expert for help
|
||||||
|
timeline "issue:42" -p proj → history
|
||||||
|
```
|
||||||
|
|
||||||
|
**4 round trips.** With `--include`, could be 2 (me with inline detail + who).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Optimized Workflow Vision
|
||||||
|
|
||||||
|
What the same workflows look like with proposed optimizations:
|
||||||
|
|
||||||
|
### Flow 1 Optimized: "What should I work on?" — 2 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
me --depth titles → 400 tokens: counts + item titles with attention_state
|
||||||
|
issues 42 --include timeline,trace → 1 call: detail + events + code provenance
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow 2 Optimized: "What happened with this feature?" — 1-2 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
search "feature" -n 5 → find entities
|
||||||
|
issues 42 --include timeline,related → everything in one call
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow 3 Optimized: "Why was this code changed?" — 1 round trip
|
||||||
|
|
||||||
|
```
|
||||||
|
trace src/file.rs --include experts,timeline → full chain + experts + events
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow 4 Optimized: "Is the system healthy?" — 1 round trip
|
||||||
|
|
||||||
|
```
|
||||||
|
doctor → covers health + auth + connectivity
|
||||||
|
# status + stats only if doctor reveals issues
|
||||||
|
```
|
||||||
|
|
||||||
|
### Flow 6 Optimized: "Find and understand" — 2 round trips
|
||||||
|
|
||||||
|
```
|
||||||
|
search "query" -n 5 → discover entities
|
||||||
|
issues --batch 42,55,71 --include timeline → batch detail with events
|
||||||
|
```
|
||||||
198
docs/command-surface-analysis/07-consolidation-proposals.md
Normal file
198
docs/command-surface-analysis/07-consolidation-proposals.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Consolidation Proposals
|
||||||
|
|
||||||
|
5 proposals to reduce 34 commands to 29 by merging high-overlap commands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A. Absorb `file-history` into `trace --shallow`
|
||||||
|
|
||||||
|
**Overlap:** 75%. Both do rename chain BFS on `mr_file_changes`, both optionally include DiffNote discussions. `trace` follows `entity_references` to linked issues; `file-history` stops at MRs.
|
||||||
|
|
||||||
|
**Current state:**
|
||||||
|
```bash
|
||||||
|
# These do nearly the same thing:
|
||||||
|
lore file-history src/auth/ -p proj --discussions
|
||||||
|
lore trace src/auth/ -p proj --discussions
|
||||||
|
# trace just adds: issues linked via entity_references
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed change:**
|
||||||
|
- `trace <path>` — full chain: file -> MR -> issue -> discussions (existing behavior)
|
||||||
|
- `trace <path> --shallow` — MR-only, no issue following (replaces `file-history`)
|
||||||
|
- Move `--merged` flag from `file-history` to `trace`
|
||||||
|
- Deprecate `file-history` as an alias that maps to `trace --shallow`
|
||||||
|
|
||||||
|
**Migration path:**
|
||||||
|
1. Add `--shallow` and `--merged` flags to `trace`
|
||||||
|
2. Make `file-history` an alias with deprecation warning
|
||||||
|
3. Update robot-docs to point to `trace`
|
||||||
|
4. Remove alias after 2 releases
|
||||||
|
|
||||||
|
**Breaking changes:** Robot output shape differs slightly (`trace_chains` vs `merge_requests` key name). The `--shallow` variant should match `file-history`'s output shape for compatibility.
|
||||||
|
|
||||||
|
**Effort:** Low. Most code is already shared via `resolve_rename_chain()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B. Absorb `auth` into `doctor`
|
||||||
|
|
||||||
|
**Overlap:** 100% of `auth` is contained within `doctor`.
|
||||||
|
|
||||||
|
**Current state:**
|
||||||
|
```bash
|
||||||
|
lore auth # checks: token set, GitLab reachable, user identity
|
||||||
|
lore doctor # checks: all of above + DB + schema + Ollama
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed change:**
|
||||||
|
- `doctor` — full check (existing behavior)
|
||||||
|
- `doctor --auth` — token + GitLab only (replaces `auth`)
|
||||||
|
- Keep `health` separate (fast pre-flight, different exit code contract: 0/19)
|
||||||
|
- Deprecate `auth` as alias for `doctor --auth`
|
||||||
|
|
||||||
|
**Migration path:**
|
||||||
|
1. Add `--auth` flag to `doctor`
|
||||||
|
2. Make `auth` an alias with deprecation warning
|
||||||
|
3. Remove alias after 2 releases
|
||||||
|
|
||||||
|
**Breaking changes:** None for robot mode (same JSON shape). Exit code mapping needs verification.
|
||||||
|
|
||||||
|
**Effort:** Low. Doctor already has the auth check logic.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C. Remove `related` query-mode
|
||||||
|
|
||||||
|
**Overlap:** 80% with `search --mode semantic`.
|
||||||
|
|
||||||
|
**Current state:**
|
||||||
|
```bash
|
||||||
|
# These are functionally equivalent:
|
||||||
|
lore related "authentication flow"
|
||||||
|
lore search "authentication flow" --mode semantic
|
||||||
|
|
||||||
|
# This is UNIQUE (no overlap):
|
||||||
|
lore related issues 42
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed change:**
|
||||||
|
- Keep entity-seeded mode: `related issues 42` (seeds from existing entity embedding)
|
||||||
|
- Remove free-text mode: `related "text"` -> error with suggestion: "Use `search --mode semantic`"
|
||||||
|
- Alternatively: keep as sugar but document it as equivalent to search
|
||||||
|
|
||||||
|
**Migration path:**
|
||||||
|
1. Add deprecation warning when query-mode is used
|
||||||
|
2. After 2 releases, remove query-mode parsing
|
||||||
|
3. Entity-mode stays unchanged
|
||||||
|
|
||||||
|
**Breaking changes:** Agents using `related "text"` must switch to `search --mode semantic`. This is a strict improvement since search has filters.
|
||||||
|
|
||||||
|
**Effort:** Low. Just argument validation change.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## D. Merge `who overlap` into `who expert`
|
||||||
|
|
||||||
|
**Overlap:** 50% functional, but overlap is a strict simplification of expert.
|
||||||
|
|
||||||
|
**Current state:**
|
||||||
|
```bash
|
||||||
|
lore who src/auth/ # expert mode: scored rankings
|
||||||
|
lore who --overlap src/auth/ # overlap mode: raw touch counts
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed change:**
|
||||||
|
- `who <path>` (expert) adds `touch_count` and `last_touch_at` fields to each expert row
|
||||||
|
- `who --overlap <path>` becomes an alias for `who <path> --fields username,touch_count`
|
||||||
|
- Eventually remove `--overlap` flag
|
||||||
|
|
||||||
|
**New expert output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"experts": [
|
||||||
|
{
|
||||||
|
"username": "jdoe", "score": 42.5,
|
||||||
|
"touch_count": 15, "last_touch_at": "2026-02-20",
|
||||||
|
"detail": { "mr_ids_author": [99, 101] }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Migration path:**
|
||||||
|
1. Add `touch_count` and `last_touch_at` to expert output
|
||||||
|
2. Make `--overlap` an alias with deprecation warning
|
||||||
|
3. Remove `--overlap` after 2 releases
|
||||||
|
|
||||||
|
**Breaking changes:** Expert output gains new fields (non-breaking for JSON consumers). Overlap output shape changes if agents were parsing `{ "users": [...] }` vs `{ "experts": [...] }`.
|
||||||
|
|
||||||
|
**Effort:** Low. Expert query already touches the same tables; just need to add a COUNT aggregation.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E. Merge `count` and `status` into `stats`
|
||||||
|
|
||||||
|
**Overlap:** `count` and `stats` both answer "how much data?"; `status` and `stats` both report system state.
|
||||||
|
|
||||||
|
**Current state:**
|
||||||
|
```bash
|
||||||
|
lore count issues # entity count + state breakdown
|
||||||
|
lore count mrs # entity count + state breakdown
|
||||||
|
lore status # sync cursors per project
|
||||||
|
lore stats # document/index counts + integrity
|
||||||
|
```
|
||||||
|
|
||||||
|
**Proposed change:**
|
||||||
|
- `stats` — document/index health (existing behavior, default)
|
||||||
|
- `stats --entities` — adds entity counts (replaces `count`)
|
||||||
|
- `stats --sync` — adds sync cursor positions (replaces `status`)
|
||||||
|
- `stats --all` — everything: entities + sync + documents + integrity
|
||||||
|
- `stats --check` / `--repair` — unchanged
|
||||||
|
|
||||||
|
**New `--all` output:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"entities": {
|
||||||
|
"issues": { "total": 5000, "opened": 200, "closed": 4800 },
|
||||||
|
"merge_requests": { "total": 1234, "opened": 100, "closed": 50, "merged": 1084 },
|
||||||
|
"discussions": { "total": 8000 },
|
||||||
|
"notes": { "total": 282000, "system_excluded": 50000 }
|
||||||
|
},
|
||||||
|
"sync": {
|
||||||
|
"projects": [
|
||||||
|
{ "project_path": "group/repo", "last_synced_at": "...", "document_count": 5000 }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"documents": { "total": 61652, "issues": 5000, "mrs": 2000, "notes": 50000 },
|
||||||
|
"embeddings": { "total": 80000, "synced": 79500, "pending": 500 },
|
||||||
|
"fts": { "total_docs": 61652 },
|
||||||
|
"queues": { "pending": 0, "in_progress": 0, "failed": 0 },
|
||||||
|
"integrity": { "ok": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Migration path:**
|
||||||
|
1. Add `--entities`, `--sync`, `--all` flags to `stats`
|
||||||
|
2. Make `count` an alias for `stats --entities` with deprecation warning
|
||||||
|
3. Make `status` an alias for `stats --sync` with deprecation warning
|
||||||
|
4. Remove aliases after 2 releases
|
||||||
|
|
||||||
|
**Breaking changes:** `count` output currently has `{ "entity": "issues", "count": N, "breakdown": {...} }`. Under `stats --entities`, this becomes nested under `data.entities`. Alias can preserve old shape during deprecation period.
|
||||||
|
|
||||||
|
**Effort:** Medium. Need to compose three query paths into one response builder.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Consolidation | Removes | Effort | Breaking? |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `file-history` -> `trace --shallow` | -1 command | Low | Alias redirect, output shape compat |
|
||||||
|
| `auth` -> `doctor --auth` | -1 command | Low | Alias redirect |
|
||||||
|
| `related` query-mode removal | -1 mode | Low | Must switch to `search --mode semantic` |
|
||||||
|
| `who overlap` -> `who expert` | -1 sub-mode | Low | Output gains fields |
|
||||||
|
| `count` + `status` -> `stats` | -2 commands | Medium | Output nesting changes |
|
||||||
|
|
||||||
|
**Total: 34 commands -> 29 commands.** All changes use deprecation-with-alias pattern for gradual migration.
|
||||||
347
docs/command-surface-analysis/08-robot-optimization-proposals.md
Normal file
347
docs/command-surface-analysis/08-robot-optimization-proposals.md
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
# Robot-Mode Optimization Proposals
|
||||||
|
|
||||||
|
6 proposals to reduce round trips and token waste for agent consumers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A. `--include` flag for embedded sub-queries (P0)
|
||||||
|
|
||||||
|
**Problem:** The #1 agent inefficiency. Every "understand this entity" workflow requires 3-4 serial round trips: detail + timeline + related + trace.
|
||||||
|
|
||||||
|
**Proposal:** Add `--include` flag to detail commands that embeds sub-query results in the response.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Before: 4 round trips, ~12000 tokens
|
||||||
|
lore -J issues 42 -p proj
|
||||||
|
lore -J timeline "issue:42" -p proj --limit 20
|
||||||
|
lore -J related issues 42 -p proj -n 5
|
||||||
|
lore -J trace src/auth/ -p proj
|
||||||
|
|
||||||
|
# After: 1 round trip, ~5000 tokens (sub-queries use reduced limits)
|
||||||
|
lore -J issues 42 -p proj --include timeline,related
|
||||||
|
```
|
||||||
|
|
||||||
|
### Include Matrix
|
||||||
|
|
||||||
|
| Base Command | Valid Includes | Default Limits |
|
||||||
|
|---|---|---|
|
||||||
|
| `issues <iid>` | `timeline`, `related`, `trace` | 20 events, 5 related, 5 chains |
|
||||||
|
| `mrs <iid>` | `timeline`, `related`, `file-changes` | 20 events, 5 related |
|
||||||
|
| `trace <path>` | `experts`, `timeline` | 5 experts, 20 events |
|
||||||
|
| `me` | `detail` (inline top-N item details) | 3 items detailed |
|
||||||
|
| `search` | `detail` (inline top-N result details) | 3 results detailed |
|
||||||
|
|
||||||
|
### Response Shape
|
||||||
|
|
||||||
|
Included data uses `_` prefix to distinguish from base fields:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"iid": 42, "title": "Fix auth", "state": "opened",
|
||||||
|
"discussions": [...],
|
||||||
|
"_timeline": {
|
||||||
|
"event_count": 15,
|
||||||
|
"events": [...]
|
||||||
|
},
|
||||||
|
"_related": {
|
||||||
|
"similar_entities": [...]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"elapsed_ms": 200,
|
||||||
|
"_timeline_ms": 45,
|
||||||
|
"_related_ms": 120
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
Sub-query errors are non-fatal. If Ollama is down, `_related` returns an error instead of failing the whole request:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"_related_error": "Ollama unavailable — related results skipped"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Limit Control
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Custom limits for included data
|
||||||
|
lore -J issues 42 --include timeline:50,related:10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Round-Trip Savings
|
||||||
|
|
||||||
|
| Workflow | Before | After | Savings |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Understand an issue | 4 calls | 1 call | **75%** |
|
||||||
|
| Why was code changed | 3 calls | 1 call | **67%** |
|
||||||
|
| Find and understand | 4 calls | 2 calls | **50%** |
|
||||||
|
|
||||||
|
**Effort:** High. Each include needs its own sub-query executor, error isolation, and limit enforcement. But the payoff is massive — this single feature halves agent round trips.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B. `--depth` control on `me` (P0)
|
||||||
|
|
||||||
|
**Problem:** `me` returns 2000-5000 tokens. Agents checking "do I have work?" only need ~100 tokens.
|
||||||
|
|
||||||
|
**Proposal:** Add `--depth` flag with three levels.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Counts only (~100 tokens) — "do I have work?"
|
||||||
|
lore -J me --depth counts
|
||||||
|
|
||||||
|
# Titles (~400 tokens) — "what work do I have?"
|
||||||
|
lore -J me --depth titles
|
||||||
|
|
||||||
|
# Full (current behavior, 2000+ tokens) — "give me everything"
|
||||||
|
lore -J me --depth full
|
||||||
|
lore -J me # same as --depth full
|
||||||
|
```
|
||||||
|
|
||||||
|
### Depth Levels
|
||||||
|
|
||||||
|
| Level | Includes | Typical Tokens |
|
||||||
|
|---|---|---|
|
||||||
|
| `counts` | `summary` block only (counts, no items) | ~100 |
|
||||||
|
| `titles` | summary + item lists with minimal fields (iid, title, attention_state) | ~400 |
|
||||||
|
| `full` | Everything: items, activity, inbox, discussions | ~2000-5000 |
|
||||||
|
|
||||||
|
### Response at `--depth counts`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"username": "jdoe",
|
||||||
|
"summary": {
|
||||||
|
"project_count": 3,
|
||||||
|
"open_issue_count": 5,
|
||||||
|
"authored_mr_count": 2,
|
||||||
|
"reviewing_mr_count": 1,
|
||||||
|
"needs_attention_count": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response at `--depth titles`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"username": "jdoe",
|
||||||
|
"summary": { ... },
|
||||||
|
"open_issues": [
|
||||||
|
{ "iid": 42, "title": "Fix auth", "attention_state": "needs_attention" }
|
||||||
|
],
|
||||||
|
"open_mrs_authored": [
|
||||||
|
{ "iid": 99, "title": "Refactor auth", "attention_state": "needs_attention" }
|
||||||
|
],
|
||||||
|
"reviewing_mrs": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Effort:** Low. The data is already available; just need to gate serialization by depth level.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C. `--batch` flag for multi-entity detail (P1)
|
||||||
|
|
||||||
|
**Problem:** After search/timeline, agents discover N entity IIDs and need detail on each. Currently N round trips.
|
||||||
|
|
||||||
|
**Proposal:** Add `--batch` flag to `issues` and `mrs` detail mode.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Before: 3 round trips
|
||||||
|
lore -J issues 42 -p proj
|
||||||
|
lore -J issues 55 -p proj
|
||||||
|
lore -J issues 71 -p proj
|
||||||
|
|
||||||
|
# After: 1 round trip
|
||||||
|
lore -J issues --batch 42,55,71 -p proj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"results": [
|
||||||
|
{ "iid": 42, "title": "Fix auth", "state": "opened", ... },
|
||||||
|
{ "iid": 55, "title": "Add SSO", "state": "opened", ... },
|
||||||
|
{ "iid": 71, "title": "Token refresh", "state": "closed", ... }
|
||||||
|
],
|
||||||
|
"errors": [
|
||||||
|
{ "iid": 99, "error": "Not found" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Constraints
|
||||||
|
|
||||||
|
- Max 20 IIDs per batch
|
||||||
|
- Individual errors don't fail the batch (partial results returned)
|
||||||
|
- Works with `--include` for maximum efficiency: `--batch 42,55 --include timeline`
|
||||||
|
- Works with `--fields minimal` for token control
|
||||||
|
|
||||||
|
**Effort:** Medium. Need to loop the existing detail handler and compose results.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## D. Composite `context` command (P2)
|
||||||
|
|
||||||
|
**Problem:** Agents need full context on an entity but must learn `--include` syntax. A purpose-built command is more discoverable.
|
||||||
|
|
||||||
|
**Proposal:** Add `context` command that returns detail + timeline + related in one call.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J context issues 42 -p proj
|
||||||
|
lore -J context mrs 99 -p proj
|
||||||
|
```
|
||||||
|
|
||||||
|
### Equivalent To
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J issues 42 -p proj --include timeline,related
|
||||||
|
```
|
||||||
|
|
||||||
|
But with optimized defaults:
|
||||||
|
- Timeline: 20 most recent events, max 3 evidence notes
|
||||||
|
- Related: top 5 entities
|
||||||
|
- Discussions: truncated after 5 threads
|
||||||
|
- Non-fatal: Ollama-dependent parts gracefully degrade
|
||||||
|
|
||||||
|
### Response Shape
|
||||||
|
|
||||||
|
Same as `issues <iid> --include timeline,related` but with the reduced defaults applied.
|
||||||
|
|
||||||
|
### Relationship to `--include`
|
||||||
|
|
||||||
|
`context` is sugar for the most common `--include` pattern. Both mechanisms can coexist:
|
||||||
|
- `context` for the 80% case (agents wanting full entity understanding)
|
||||||
|
- `--include` for custom combinations
|
||||||
|
|
||||||
|
**Effort:** Medium. Thin wrapper around detail + include pipeline.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E. `--max-tokens` response budget (P3)
|
||||||
|
|
||||||
|
**Problem:** Response sizes vary wildly (100 to 8000 tokens). Agents can't predict cost in advance.
|
||||||
|
|
||||||
|
**Proposal:** Let agents cap response size. Server truncates to fit.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J me --max-tokens 500
|
||||||
|
lore -J timeline "feature" --max-tokens 1000
|
||||||
|
lore -J context issues 42 --max-tokens 2000
|
||||||
|
```
|
||||||
|
|
||||||
|
### Truncation Strategy (priority order)
|
||||||
|
|
||||||
|
1. Apply `--fields minimal` if not already set
|
||||||
|
2. Reduce array lengths (newest/highest-score items survive)
|
||||||
|
3. Truncate string fields (descriptions, snippets) to 200 chars
|
||||||
|
4. Omit null/empty fields
|
||||||
|
5. Drop included sub-queries (if using `--include`)
|
||||||
|
|
||||||
|
### Meta Notice
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"elapsed_ms": 50,
|
||||||
|
"truncated": true,
|
||||||
|
"original_tokens": 3500,
|
||||||
|
"budget_tokens": 1000,
|
||||||
|
"dropped": ["_related", "discussions[5:]", "activity[10:]"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Notes
|
||||||
|
|
||||||
|
Token estimation: rough heuristic based on JSON character count / 4. Doesn't need to be exact — the goal is "roughly this size" not "exactly N tokens."
|
||||||
|
|
||||||
|
**Effort:** High. Requires token estimation, progressive truncation logic, and tracking what was dropped.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F. `--format tsv` for list commands (P3)
|
||||||
|
|
||||||
|
**Problem:** JSON is verbose for tabular data. List commands return arrays of objects with repeated key names.
|
||||||
|
|
||||||
|
**Proposal:** Add `--format tsv` for list commands.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J issues --format tsv --fields iid,title,state -n 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output
|
||||||
|
|
||||||
|
```
|
||||||
|
iid title state
|
||||||
|
42 Fix auth opened
|
||||||
|
55 Add SSO opened
|
||||||
|
71 Token refresh closed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Token Savings
|
||||||
|
|
||||||
|
| Command | JSON tokens | TSV tokens | Savings |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `issues -n 50 --fields minimal` | ~800 | ~250 | **69%** |
|
||||||
|
| `mrs -n 50 --fields minimal` | ~800 | ~250 | **69%** |
|
||||||
|
| `who expert -n 10` | ~300 | ~100 | **67%** |
|
||||||
|
| `notes -n 50 --fields minimal` | ~1000 | ~350 | **65%** |
|
||||||
|
|
||||||
|
### Applicable Commands
|
||||||
|
|
||||||
|
TSV works well for flat, tabular data:
|
||||||
|
- `issues` (list), `mrs` (list), `notes` (list)
|
||||||
|
- `who expert`, `who overlap`, `who reviews`
|
||||||
|
- `count`
|
||||||
|
|
||||||
|
TSV does NOT work for nested/complex data:
|
||||||
|
- Detail views (discussions are nested)
|
||||||
|
- Timeline (events have nested evidence)
|
||||||
|
- Search (nested explain, labels arrays)
|
||||||
|
- `me` (multiple sections)
|
||||||
|
|
||||||
|
### Agent Parsing
|
||||||
|
|
||||||
|
Most LLMs parse TSV naturally. Agents that need structured data can still use JSON.
|
||||||
|
|
||||||
|
**Effort:** Medium. Tab-separated serialization for flat structs is straightforward. Need to handle escaping for body text containing tabs/newlines.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Impact Summary
|
||||||
|
|
||||||
|
| Optimization | Priority | Effort | Round-Trip Savings | Token Savings |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `--include` | P0 | High | **50-75%** | Moderate |
|
||||||
|
| `--depth` on `me` | P0 | Low | None | **60-80%** |
|
||||||
|
| `--batch` | P1 | Medium | **N-1 per batch** | Moderate |
|
||||||
|
| `context` command | P2 | Medium | **67-75%** | Moderate |
|
||||||
|
| `--max-tokens` | P3 | High | None | **Variable** |
|
||||||
|
| `--format tsv` | P3 | Medium | None | **65-69% on lists** |
|
||||||
|
|
||||||
|
### Implementation Order
|
||||||
|
|
||||||
|
1. **`--depth` on `me`** — lowest effort, high value, no risk
|
||||||
|
2. **`--include` on `issues`/`mrs` detail** — highest impact, start with `timeline` include only
|
||||||
|
3. **`--batch`** — eliminates N+1 pattern
|
||||||
|
4. **`context` command** — sugar on top of `--include`
|
||||||
|
5. **`--format tsv`** — nice-to-have, easy to add incrementally
|
||||||
|
6. **`--max-tokens`** — complex, defer until demand is clear
|
||||||
181
docs/command-surface-analysis/09-appendices.md
Normal file
181
docs/command-surface-analysis/09-appendices.md
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# Appendices
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## A. Robot Output Envelope
|
||||||
|
|
||||||
|
All robot-mode responses follow this structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": { /* command-specific */ },
|
||||||
|
"meta": { "elapsed_ms": 42 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Errors (to stderr):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": {
|
||||||
|
"code": "CONFIG_NOT_FOUND",
|
||||||
|
"message": "Configuration file not found",
|
||||||
|
"suggestion": "Run 'lore init'",
|
||||||
|
"actions": ["lore init"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `actions` array contains copy-paste shell commands for automated recovery. Omitted when empty.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## B. Exit Codes
|
||||||
|
|
||||||
|
| Code | Meaning | Retryable |
|
||||||
|
|---|---|---|
|
||||||
|
| 0 | Success | N/A |
|
||||||
|
| 1 | Internal error / not implemented | Maybe |
|
||||||
|
| 2 | Usage error (invalid flags or arguments) | No (fix syntax) |
|
||||||
|
| 3 | Config invalid | No (fix config) |
|
||||||
|
| 4 | Token not set | No (set token) |
|
||||||
|
| 5 | GitLab auth failed | Maybe (token expired?) |
|
||||||
|
| 6 | Resource not found (HTTP 404) | No |
|
||||||
|
| 7 | Rate limited | Yes (wait) |
|
||||||
|
| 8 | Network error | Yes (retry) |
|
||||||
|
| 9 | Database locked | Yes (wait) |
|
||||||
|
| 10 | Database error | Maybe |
|
||||||
|
| 11 | Migration failed | No (investigate) |
|
||||||
|
| 12 | I/O error | Maybe |
|
||||||
|
| 13 | Transform error | No (bug) |
|
||||||
|
| 14 | Ollama unavailable | Yes (start Ollama) |
|
||||||
|
| 15 | Ollama model not found | No (pull model) |
|
||||||
|
| 16 | Embedding failed | Yes (retry) |
|
||||||
|
| 17 | Not found (entity does not exist) | No |
|
||||||
|
| 18 | Ambiguous match (use `-p` to specify project) | No (be specific) |
|
||||||
|
| 19 | Health check failed | Yes (fix issues first) |
|
||||||
|
| 20 | Config not found | No (run init) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## C. Field Selection Presets
|
||||||
|
|
||||||
|
The `--fields` flag supports both presets and custom field lists:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore -J issues --fields minimal # Preset
|
||||||
|
lore -J mrs --fields iid,title,state,draft # Custom comma-separated
|
||||||
|
```
|
||||||
|
|
||||||
|
| Command | Minimal Preset Fields |
|
||||||
|
|---|---|
|
||||||
|
| `issues` (list) | `iid`, `title`, `state`, `updated_at_iso` |
|
||||||
|
| `mrs` (list) | `iid`, `title`, `state`, `updated_at_iso` |
|
||||||
|
| `notes` (list) | `id`, `author_username`, `body`, `created_at_iso` |
|
||||||
|
| `search` | `document_id`, `title`, `source_type`, `score` |
|
||||||
|
| `timeline` | `timestamp`, `type`, `entity_iid`, `detail` |
|
||||||
|
| `who expert` | `username`, `score` |
|
||||||
|
| `who workload` | `iid`, `title`, `state` |
|
||||||
|
| `who reviews` | `name`, `count`, `percentage` |
|
||||||
|
| `who active` | `entity_type`, `iid`, `title`, `participants` |
|
||||||
|
| `who overlap` | `username`, `touch_count` |
|
||||||
|
| `me` (items) | `iid`, `title`, `attention_state`, `updated_at_iso` |
|
||||||
|
| `me` (activity) | `timestamp_iso`, `event_type`, `entity_iid`, `actor` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## D. Configuration Precedence
|
||||||
|
|
||||||
|
1. CLI flags (highest priority)
|
||||||
|
2. Environment variables (`LORE_ROBOT`, `GITLAB_TOKEN`, `LORE_CONFIG_PATH`)
|
||||||
|
3. Config file (`~/.config/lore/config.json`)
|
||||||
|
4. Built-in defaults (lowest priority)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## E. Time Parsing
|
||||||
|
|
||||||
|
All commands accepting `--since`, `--until`, `--as-of` support:
|
||||||
|
|
||||||
|
| Format | Example | Meaning |
|
||||||
|
|---|---|---|
|
||||||
|
| Relative days | `7d` | 7 days ago |
|
||||||
|
| Relative weeks | `2w` | 2 weeks ago |
|
||||||
|
| Relative months | `1m`, `6m` | 1/6 months ago |
|
||||||
|
| Absolute date | `2026-01-15` | Specific date |
|
||||||
|
|
||||||
|
Internally converted to Unix milliseconds for DB queries.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## F. Database Schema (28 migrations)
|
||||||
|
|
||||||
|
### Primary Entity Tables
|
||||||
|
|
||||||
|
| Table | Key Columns | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `projects` | `gitlab_project_id`, `path_with_namespace`, `web_url` | No `name` or `last_seen_at` |
|
||||||
|
| `issues` | `iid`, `title`, `state`, `author_username`, 5 status columns | Status columns nullable (migration 021) |
|
||||||
|
| `merge_requests` | `iid`, `title`, `state`, `draft`, `source_branch`, `target_branch` | `last_seen_at INTEGER NOT NULL` |
|
||||||
|
| `discussions` | `gitlab_discussion_id` (text), `issue_id`/`merge_request_id` | One FK must be set |
|
||||||
|
| `notes` | `gitlab_id`, `author_username`, `body`, DiffNote position columns | `type` column for DiffNote/DiscussionNote |
|
||||||
|
|
||||||
|
### Relationship Tables
|
||||||
|
|
||||||
|
| Table | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `issue_labels`, `mr_labels` | Label junction (DELETE+INSERT for stale removal) |
|
||||||
|
| `issue_assignees`, `mr_assignees` | Assignee junction |
|
||||||
|
| `mr_reviewers` | Reviewer junction |
|
||||||
|
| `entity_references` | Cross-refs: closes, mentioned, related (with `source_method`) |
|
||||||
|
| `mr_file_changes` | File diffs: old_path, new_path, change_type |
|
||||||
|
|
||||||
|
### Event Tables
|
||||||
|
|
||||||
|
| Table | Constraint |
|
||||||
|
|---|---|
|
||||||
|
| `resource_state_events` | CHECK: exactly one of issue_id/merge_request_id NOT NULL |
|
||||||
|
| `resource_label_events` | Same CHECK constraint; `label_name` nullable (migration 012) |
|
||||||
|
| `resource_milestone_events` | Same CHECK constraint; `milestone_title` nullable |
|
||||||
|
|
||||||
|
### Document/Search Pipeline
|
||||||
|
|
||||||
|
| Table | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `documents` | Unified searchable content (source_type: issue/merge_request/discussion) |
|
||||||
|
| `documents_fts` | FTS5 virtual table for text search |
|
||||||
|
| `documents_fts_docsize` | FTS5 shadow B-tree (19x faster for COUNT) |
|
||||||
|
| `document_labels` | Fast label filtering (indexed exact-match) |
|
||||||
|
| `document_paths` | File path association for DiffNote filtering |
|
||||||
|
| `embeddings` | vec0 virtual table; rowid = document_id * 1000 + chunk_index |
|
||||||
|
| `embedding_metadata` | Chunk provenance + staleness tracking (document_hash) |
|
||||||
|
| `dirty_sources` | Documents needing regeneration (with backoff via next_attempt_at) |
|
||||||
|
|
||||||
|
### Infrastructure
|
||||||
|
|
||||||
|
| Table | Purpose |
|
||||||
|
|---|---|
|
||||||
|
| `sync_runs` | Sync history with metrics |
|
||||||
|
| `sync_cursors` | Per-resource sync position (updated_at cursor + tie_breaker_id) |
|
||||||
|
| `app_locks` | Crash-safe single-flight lock |
|
||||||
|
| `raw_payloads` | Raw JSON storage for debugging |
|
||||||
|
| `pending_discussion_fetches` | Dependent discussion fetch queue |
|
||||||
|
| `pending_dependent_fetches` | Job queue for resource_events, mr_closes, mr_diffs |
|
||||||
|
| `schema_version` | Migration tracking |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## G. Glossary
|
||||||
|
|
||||||
|
| Term | Definition |
|
||||||
|
|---|---|
|
||||||
|
| **IID** | Issue/MR number within a project (not globally unique) |
|
||||||
|
| **FTS5** | SQLite full-text search extension (BM25 ranking) |
|
||||||
|
| **vec0** | SQLite extension for vector similarity search |
|
||||||
|
| **RRF** | Reciprocal Rank Fusion — combines FTS and vector rankings |
|
||||||
|
| **DiffNote** | Comment attached to a specific line in a merge request diff |
|
||||||
|
| **Entity reference** | Cross-reference between issues/MRs (closes, mentioned, related) |
|
||||||
|
| **Rename chain** | BFS traversal of mr_file_changes to follow file renames |
|
||||||
|
| **Attention state** | Computed field on `me` items: needs_attention, not_started, stale, etc. |
|
||||||
|
| **Surgical sync** | Fetching specific entities by IID instead of full incremental sync |
|
||||||
245
docs/diagrams/01-human-flow-map.excalidraw
Normal file
245
docs/diagrams/01-human-flow-map.excalidraw
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{ "type": "text", "id": "title", "x": 300, "y": 15, "text": "Human User Flow Map", "fontSize": 28 },
|
||||||
|
{ "type": "text", "id": "subtitle", "x": 220, "y": 53, "text": "15 human workflows mapped to lore commands. Arrows show data dependency.", "fontSize": 14, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "col-trigger", "x": 60, "y": 80, "text": "TRIGGER (Problem)", "fontSize": 16, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "col-flow", "x": 400, "y": 80, "text": "COMMAND FLOW", "fontSize": 16, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "col-gap", "x": 880, "y": 80, "text": "GAP", "fontSize": 16, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-daily", "x": 20, "y": 110, "width": 960, "height": 190,
|
||||||
|
"backgroundColor": "#dbe4ff", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#4a9eed", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-daily-label", "x": 30, "y": 115, "text": "Daily Operations", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h1-trigger", "x": 30, "y": 140, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H1: Standup prep\n\"What moved overnight?\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h1-a1", "x": 230, "y": 165, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h1-cmd1", "x": 280, "y": 145, "width": 90, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "sync -q", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h1-a2", "x": 370, "y": 165, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h1-cmd2", "x": 400, "y": 145, "width": 140, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues --since 1d", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h1-a3", "x": 540, "y": 165, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h1-cmd3", "x": 570, "y": 145, "width": 130, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "mrs --since 1d", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h1-a4", "x": 700, "y": 165, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h1-cmd4", "x": 730, "y": 145, "width": 100, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who @me", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h1-a5", "x": 830, "y": 165, "width": 40, "height": 0,
|
||||||
|
"points": [[0,0],[40,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h1-gap", "x": 870, "y": 140, "width": 100, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No @me\nNo feed", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h3-trigger", "x": 30, "y": 210, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H3: Incident\n\"Deploy broke prod\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h3-a1", "x": 230, "y": 235, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h3-cmd1", "x": 280, "y": 215, "width": 130, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "timeline deploy", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h3-a2", "x": 410, "y": 235, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h3-cmd2", "x": 440, "y": 215, "width": 160, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "search deploy --mr", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h3-a3", "x": 600, "y": 235, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h3-cmd3", "x": 630, "y": 215, "width": 110, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "mrs <iid>", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h3-a4", "x": 740, "y": 235, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h3-cmd4", "x": 770, "y": 215, "width": 100, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who --overlap", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-planning", "x": 20, "y": 310, "width": 960, "height": 190,
|
||||||
|
"backgroundColor": "#d3f9d8", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#22c55e", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-planning-label", "x": 30, "y": 315, "text": "Planning & Assignment", "fontSize": 14, "strokeColor": "#15803d" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h2-trigger", "x": 30, "y": 340, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H2: Sprint plan\n\"What's ready to pick?\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h2-a1", "x": 230, "y": 365, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h2-cmd1", "x": 280, "y": 345, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues -s opened -l ready", "fontSize": 13 } },
|
||||||
|
{ "type": "arrow", "id": "h2-a2", "x": 450, "y": 365, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h2-cmd2", "x": 480, "y": 345, "width": 150, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues --has-due", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h2-a3", "x": 630, "y": 365, "width": 230, "height": 0,
|
||||||
|
"points": [[0,0],[230,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h2-gap", "x": 860, "y": 340, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No\n--no-assignee", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h8-trigger", "x": 30, "y": 410, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H8: Assign work\n\"Who has bandwidth?\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h8-a1", "x": 230, "y": 435, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h8-cmd1", "x": 280, "y": 415, "width": 120, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who @alice", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h8-a2", "x": 400, "y": 435, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h8-cmd2", "x": 430, "y": 415, "width": 110, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who @bob", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h8-a3", "x": 540, "y": 435, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h8-cmd3", "x": 570, "y": 415, "width": 120, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who @carol...", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h8-a4", "x": 690, "y": 435, "width": 170, "height": 0,
|
||||||
|
"points": [[0,0],[170,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h8-gap", "x": 860, "y": 410, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No team\nworkload view", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-investigation", "x": 20, "y": 510, "width": 960, "height": 260,
|
||||||
|
"backgroundColor": "#fff3bf", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#f59e0b", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-invest-label", "x": 30, "y": 515, "text": "Investigation & Understanding", "fontSize": 14, "strokeColor": "#b45309" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h7-trigger", "x": 30, "y": 540, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H7: Why this way?\n\"Understand a decision\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h7-a1", "x": 230, "y": 565, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h7-cmd1", "x": 280, "y": 545, "width": 160, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "search \"rationale\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h7-a2", "x": 440, "y": 565, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h7-cmd2", "x": 470, "y": 545, "width": 140, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "timeline --depth 2", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h7-a3", "x": 610, "y": 565, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h7-cmd3", "x": 640, "y": 545, "width": 100, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues 234", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h7-a4", "x": 740, "y": 565, "width": 120, "height": 0,
|
||||||
|
"points": [[0,0],[120,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h7-gap", "x": 860, "y": 540, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No per-note\nsearch", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h11-trigger", "x": 30, "y": 610, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H11: Bug lifecycle\n\"Why does #321 reopen?\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h11-a1", "x": 230, "y": 635, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h11-cmd1", "x": 280, "y": 615, "width": 120, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues 321", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h11-a2", "x": 400, "y": 635, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h11-cmd2", "x": 430, "y": 615, "width": 130, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "timeline ???", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h11-a3", "x": 560, "y": 635, "width": 300, "height": 0,
|
||||||
|
"points": [[0,0],[300,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h11-gap", "x": 860, "y": 610, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No entity\ntimeline", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h14-trigger", "x": 30, "y": 680, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H14: Prior art?\n\"Was this tried before?\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h14-a1", "x": 230, "y": 705, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h14-cmd1", "x": 280, "y": 685, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "search \"memory leak\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h14-a2", "x": 450, "y": 705, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h14-cmd2", "x": 480, "y": 685, "width": 120, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "mrs --closed?", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h14-a3", "x": 600, "y": 705, "width": 260, "height": 0,
|
||||||
|
"points": [[0,0],[260,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h14-gap", "x": 860, "y": 680, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No --state\non search", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-people", "x": 20, "y": 780, "width": 960, "height": 190,
|
||||||
|
"backgroundColor": "#e5dbff", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#8b5cf6", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-people-label", "x": 30, "y": 785, "text": "People & Expertise", "fontSize": 14, "strokeColor": "#7048e8" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h4-trigger", "x": 30, "y": 810, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H4: Review prep\n\"Context for MR !789\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h4-a1", "x": 230, "y": 835, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h4-cmd1", "x": 280, "y": 815, "width": 100, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "mrs 789", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h4-a2", "x": 380, "y": 835, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h4-cmd2", "x": 410, "y": 815, "width": 120, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who src/auth/", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h4-a3", "x": 530, "y": 835, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h4-cmd3", "x": 560, "y": 815, "width": 130, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "search \"auth\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h4-a4", "x": 690, "y": 835, "width": 170, "height": 0,
|
||||||
|
"points": [[0,0],[170,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h4-gap", "x": 860, "y": 810, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No MR file\nlist output", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "h6-trigger", "x": 30, "y": 880, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "H6: Find reviewer\n\"Who should review?\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h6-a1", "x": 230, "y": 905, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h6-cmd1", "x": 280, "y": 885, "width": 130, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who src/auth/", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h6-a2", "x": 410, "y": 905, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h6-cmd2", "x": 440, "y": 885, "width": 140, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who src/pay/", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h6-a3", "x": 580, "y": 905, "width": 30, "height": 0,
|
||||||
|
"points": [[0,0],[30,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h6-cmd3", "x": 610, "y": 885, "width": 140, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who @candidate", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "h6-a4", "x": 750, "y": 905, "width": 110, "height": 0,
|
||||||
|
"points": [[0,0],[110,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "h6-gap", "x": 860, "y": 880, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No multi-\npath query", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "callout-1", "x": 30, "y": 990, "text": "Pattern: Most human flows require 3-5 serial commands. Average gap rate: 73% of flows have at least one.", "fontSize": 14, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "callout-2", "x": 30, "y": 1015, "text": "Top optimization: Composite commands (activity feed, team workload) would reduce multi-command flows by ~40%.", "fontSize": 14, "strokeColor": "#15803d" },
|
||||||
|
{ "type": "text", "id": "callout-3", "x": 30, "y": 1040, "text": "Top missing data: MR file changes and entity references are stored but invisible to CLI users.", "fontSize": 14, "strokeColor": "#ef4444" }
|
||||||
|
],
|
||||||
|
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/diagrams/01-human-flow-map.png
Normal file
BIN
docs/diagrams/01-human-flow-map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
204
docs/diagrams/02-agent-flow-map.excalidraw
Normal file
204
docs/diagrams/02-agent-flow-map.excalidraw
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{ "type": "text", "id": "title", "x": 320, "y": 15, "text": "AI Agent Flow Map", "fontSize": 28 },
|
||||||
|
{ "type": "text", "id": "subtitle", "x": 180, "y": 53, "text": "15 agent automation workflows. Agents need structured JSON (-J), exit codes, and field selection.", "fontSize": 14, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "col-trigger", "x": 60, "y": 80, "text": "TRIGGER (Agent Goal)", "fontSize": 16, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "col-flow", "x": 400, "y": 80, "text": "COMMAND PIPELINE", "fontSize": 16, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "col-gap", "x": 880, "y": 80, "text": "BLOCKED BY", "fontSize": 16, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-context", "x": 20, "y": 110, "width": 960, "height": 200,
|
||||||
|
"backgroundColor": "#e5dbff", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#8b5cf6", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-context-label", "x": 30, "y": 115, "text": "Context Gathering (pre-action)", "fontSize": 14, "strokeColor": "#7048e8" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a1-trigger", "x": 30, "y": 140, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A1: Pre-edit context\nAbout to modify files", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a1-a1", "x": 230, "y": 165, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a1-cmd1", "x": 280, "y": 145, "width": 80, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J health", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a1-a2", "x": 360, "y": 165, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a1-cmd2", "x": 380, "y": 145, "width": 140, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J who src/auth/", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a1-a3", "x": 520, "y": 165, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a1-cmd3", "x": 540, "y": 145, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J search \"auth\" -n 10", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a1-a4", "x": 710, "y": 165, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a1-cmd4", "x": 730, "y": 145, "width": 130, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J who --overlap", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a6-trigger", "x": 30, "y": 210, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A6: Auto-assign reviewers\nBased on file expertise", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a6-a1", "x": 230, "y": 235, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a6-cmd1", "x": 280, "y": 215, "width": 100, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J mrs 456", "fontSize": 14 } },
|
||||||
|
{ "type": "text", "id": "a6-block", "x": 390, "y": 218, "text": "file list not\nin response!", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
{ "type": "arrow", "id": "a6-a2", "x": 380, "y": 245, "width": 480, "height": -10,
|
||||||
|
"points": [[0,0],[480,-10]], "endArrowhead": "arrow", "strokeColor": "#ef4444", "strokeStyle": "dashed" },
|
||||||
|
{ "type": "rectangle", "id": "a6-gap", "x": 860, "y": 210, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "MR files\nnot exposed", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-report", "x": 20, "y": 320, "width": 960, "height": 200,
|
||||||
|
"backgroundColor": "#d3f9d8", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#22c55e", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-report-label", "x": 30, "y": 325, "text": "Reporting & Synthesis", "fontSize": 14, "strokeColor": "#15803d" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a3-trigger", "x": 30, "y": 350, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A3: Sprint status report\n7 queries for 1 report", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a3-a1", "x": 230, "y": 375, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a3-cmd1", "x": 280, "y": 352, "width": 100, "height": 36,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues -s closed", "fontSize": 12 } },
|
||||||
|
{ "type": "rectangle", "id": "a3-cmd2", "x": 390, "y": 352, "width": 100, "height": 36,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues --status", "fontSize": 12 } },
|
||||||
|
{ "type": "rectangle", "id": "a3-cmd3", "x": 500, "y": 352, "width": 100, "height": 36,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "mrs -s merged", "fontSize": 12 } },
|
||||||
|
{ "type": "rectangle", "id": "a3-cmd4", "x": 610, "y": 352, "width": 80, "height": 36,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "mrs -s open", "fontSize": 12 } },
|
||||||
|
{ "type": "rectangle", "id": "a3-cmd5", "x": 700, "y": 352, "width": 80, "height": 36,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "count x2", "fontSize": 12 } },
|
||||||
|
{ "type": "rectangle", "id": "a3-cmd6", "x": 790, "y": 352, "width": 60, "height": 36,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "who", "fontSize": 12 } },
|
||||||
|
{ "type": "arrow", "id": "a3-agap", "x": 850, "y": 370, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a3-gap", "x": 860, "y": 350, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No summary\ncommand", "fontSize": 14 } },
|
||||||
|
{ "type": "text", "id": "a3-note", "x": 280, "y": 395, "text": "7 sequential API calls for one report. A `lore summary` could reduce to 1.", "fontSize": 12, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a7-trigger", "x": 30, "y": 430, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A7: Incident timeline\nPostmortem reconstruction", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a7-a1", "x": 230, "y": 455, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a7-cmd1", "x": 280, "y": 435, "width": 190, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J timeline --depth 2", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a7-a2", "x": 470, "y": 455, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a7-cmd2", "x": 490, "y": 435, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J search --since 3d", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a7-a3", "x": 660, "y": 455, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a7-cmd3", "x": 680, "y": 435, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J mrs -s merged", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-discover", "x": 20, "y": 530, "width": 960, "height": 200,
|
||||||
|
"backgroundColor": "#fff3bf", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#f59e0b", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-discover-label", "x": 30, "y": 535, "text": "Discovery & Correlation", "fontSize": 14, "strokeColor": "#b45309" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a5-trigger", "x": 30, "y": 560, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A5: PR description\nFind related issues to link", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a5-a1", "x": 230, "y": 585, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a5-cmd1", "x": 280, "y": 565, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J search keywords", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a5-a2", "x": 450, "y": 585, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a5-cmd2", "x": 470, "y": 565, "width": 180, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J issues --fields iid,url", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a5-a3", "x": 650, "y": 585, "width": 210, "height": 0,
|
||||||
|
"points": [[0,0],[210,0]], "endArrowhead": "arrow", "strokeColor": "#ef4444", "strokeStyle": "dashed" },
|
||||||
|
{ "type": "rectangle", "id": "a5-gap", "x": 860, "y": 560, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No refs\nquery", "fontSize": 14 } },
|
||||||
|
{ "type": "text", "id": "a5-note", "x": 280, "y": 612, "text": "Agent can't ask \"which issues does MR !456 close?\" -- entity_references data exists but isn't queryable.", "fontSize": 12, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a11-trigger", "x": 30, "y": 640, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A11: Knowledge graph\nMap entity relationships", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a11-a1", "x": 230, "y": 665, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a11-cmd1", "x": 280, "y": 645, "width": 140, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J search -n 30", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a11-a2", "x": 420, "y": 665, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a11-cmd2", "x": 440, "y": 645, "width": 190, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J timeline --depth 2", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a11-a3", "x": 630, "y": 665, "width": 230, "height": 0,
|
||||||
|
"points": [[0,0],[230,0]], "endArrowhead": "arrow", "strokeColor": "#ef4444", "strokeStyle": "dashed" },
|
||||||
|
{ "type": "rectangle", "id": "a11-gap", "x": 860, "y": 640, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No refs\nquery", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-maint", "x": 20, "y": 740, "width": 960, "height": 140,
|
||||||
|
"backgroundColor": "#dbe4ff", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#4a9eed", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-maint-label", "x": 30, "y": 745, "text": "Maintenance & Cleanup", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a9-trigger", "x": 30, "y": 770, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A9: Stale issue cleanup\nWeekly backlog hygiene", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a9-a1", "x": 230, "y": 795, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a9-cmd1", "x": 280, "y": 775, "width": 200, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J issues --sort updated --asc", "fontSize": 12 } },
|
||||||
|
{ "type": "arrow", "id": "a9-a2", "x": 480, "y": 795, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a9-cmd2", "x": 500, "y": 775, "width": 120, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "filter client-side", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a9-a3", "x": 620, "y": 795, "width": 240, "height": 0,
|
||||||
|
"points": [[0,0],[240,0]], "endArrowhead": "arrow", "strokeColor": "#ef4444", "strokeStyle": "dashed" },
|
||||||
|
{ "type": "rectangle", "id": "a9-gap", "x": 860, "y": 770, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No --before\nNo offset", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "a15-trigger", "x": 30, "y": 840, "width": 200, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "A15: Conflict detect\n\"Safe to start work?\"", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a15-a1", "x": 230, "y": 865, "width": 50, "height": 0,
|
||||||
|
"points": [[0,0],[50,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a15-cmd1", "x": 280, "y": 845, "width": 110, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J issues 123", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a15-a2", "x": 390, "y": 865, "width": 20, "height": 0,
|
||||||
|
"points": [[0,0],[20,0]], "endArrowhead": "arrow" },
|
||||||
|
{ "type": "rectangle", "id": "a15-cmd2", "x": 410, "y": 845, "width": 130, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "-J who --overlap", "fontSize": 14 } },
|
||||||
|
{ "type": "arrow", "id": "a15-a3", "x": 540, "y": 865, "width": 320, "height": 0,
|
||||||
|
"points": [[0,0],[320,0]], "endArrowhead": "arrow", "strokeColor": "#ef4444", "strokeStyle": "dashed" },
|
||||||
|
{ "type": "rectangle", "id": "a15-gap", "x": 860, "y": 840, "width": 110, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "No refs +\n--state", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "callout-1", "x": 30, "y": 910, "text": "Agent-specific pain: Agents always use -J and --fields minimal for token efficiency. Every extra query burns tokens.", "fontSize": 14, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "callout-2", "x": 30, "y": 935, "text": "Biggest ROI: `lore refs` command would unblock A5, A11, A12, A15 instantly. Data already exists in entity_references table.", "fontSize": 14, "strokeColor": "#15803d" },
|
||||||
|
{ "type": "text", "id": "callout-3", "x": 30, "y": 960, "text": "Token waste: Sprint report (A3) requires 7 calls. A composite `lore summary` could save ~85% of tokens.", "fontSize": 14, "strokeColor": "#ef4444" }
|
||||||
|
],
|
||||||
|
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/diagrams/02-agent-flow-map.png
Normal file
BIN
docs/diagrams/02-agent-flow-map.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 269 KiB |
203
docs/diagrams/03-command-coverage.excalidraw
Normal file
203
docs/diagrams/03-command-coverage.excalidraw
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{ "type": "text", "id": "title", "x": 280, "y": 15, "text": "Command Coverage Heatmap", "fontSize": 28 },
|
||||||
|
{ "type": "text", "id": "subtitle", "x": 220, "y": 53, "text": "Which commands serve which workflows? Darker = more essential to that flow.", "fontSize": 14, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "col-issues", "x": 260, "y": 85, "text": "issues", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-mrs", "x": 330, "y": 85, "text": "mrs", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-search", "x": 390, "y": 85, "text": "search", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-who", "x": 465, "y": 85, "text": "who", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-timeline", "x": 520, "y": 85, "text": "timeline", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-sync", "x": 600, "y": 85, "text": "sync", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-count", "x": 660, "y": 85, "text": "count", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-status", "x": 720, "y": 85, "text": "status", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "col-missing", "x": 790, "y": 85, "text": "MISSING?", "fontSize": 14, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "grp-human", "x": 15, "y": 108, "text": "HUMAN FLOWS", "fontSize": 14, "strokeColor": "#15803d" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h1-label", "x": 15, "y": 135, "text": "H1 Standup prep", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h1-issues", "x": 255, "y": 130, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h1-mrs", "x": 325, "y": 130, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h1-who", "x": 460, "y": 130, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h1-sync", "x": 595, "y": 130, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h1-gap", "x": 780, "y": 135, "text": "activity feed", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h2-label", "x": 15, "y": 170, "text": "H2 Sprint planning", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h2-issues", "x": 255, "y": 165, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h2-count", "x": 655, "y": 165, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h2-gap", "x": 780, "y": 170, "text": "--no-assignee", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h3-label", "x": 15, "y": 205, "text": "H3 Incident response", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h3-mrs", "x": 325, "y": 200, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h3-search", "x": 390, "y": 200, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h3-who", "x": 460, "y": 200, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h3-timeline", "x": 525, "y": 200, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h3-sync", "x": 595, "y": 200, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h4-label", "x": 15, "y": 240, "text": "H4 Code review prep", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h4-mrs", "x": 325, "y": 235, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h4-search", "x": 390, "y": 235, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h4-who", "x": 460, "y": 235, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h4-timeline", "x": 525, "y": 235, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h4-gap", "x": 780, "y": 240, "text": "MR file list", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h5-label", "x": 15, "y": 275, "text": "H5 Onboarding", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h5-issues", "x": 255, "y": 270, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h5-mrs", "x": 325, "y": 270, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h5-search", "x": 390, "y": 270, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h5-who", "x": 460, "y": 270, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h5-timeline", "x": 525, "y": 270, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h6-label", "x": 15, "y": 310, "text": "H6 Find reviewer", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h6-who", "x": 460, "y": 305, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h6-gap", "x": 780, "y": 310, "text": "multi-path who", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h7-label", "x": 15, "y": 345, "text": "H7 Why was this built?", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h7-issues", "x": 255, "y": 340, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h7-mrs", "x": 325, "y": 340, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h7-search", "x": 390, "y": 340, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h7-timeline", "x": 525, "y": 340, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h7-gap", "x": 780, "y": 345, "text": "per-note search", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h8-label", "x": 15, "y": 380, "text": "H8 Team workload", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h8-who", "x": 460, "y": 375, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h8-gap", "x": 780, "y": 380, "text": "team view", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h9-label", "x": 15, "y": 415, "text": "H9 Release notes", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h9-issues", "x": 255, "y": 410, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h9-mrs", "x": 325, "y": 410, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h9-gap", "x": 780, "y": 415, "text": "mrs --milestone", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h10-label", "x": 15, "y": 450, "text": "H10 Stale issues", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h10-issues", "x": 255, "y": 445, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h10-gap", "x": 780, "y": 450, "text": "--updated-before", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h11-label", "x": 15, "y": 485, "text": "H11 Bug lifecycle", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h11-issues", "x": 255, "y": 480, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h11-timeline", "x": 525, "y": 480, "width": 50, "height": 28, "backgroundColor": "#ffd8a8", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h11-gap", "x": 780, "y": 485, "text": "entity timeline", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h12-label", "x": 15, "y": 520, "text": "H12 Who broke tests?", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h12-search", "x": 390, "y": 515, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h12-who", "x": 460, "y": 515, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h13-label", "x": 15, "y": 555, "text": "H13 Feature tracking", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h13-issues", "x": 255, "y": 550, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h13-mrs", "x": 325, "y": 550, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h13-timeline", "x": 525, "y": 550, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h14-label", "x": 15, "y": 590, "text": "H14 Prior art check", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h14-search", "x": 390, "y": 585, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "h14-timeline", "x": 525, "y": 585, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h14-gap", "x": 780, "y": 590, "text": "--state on search", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "h15-label", "x": 15, "y": 625, "text": "H15 My discussions", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "h15-who", "x": 460, "y": 620, "width": 50, "height": 28, "backgroundColor": "#ffd8a8", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "h15-gap", "x": 780, "y": 625, "text": "participant filter", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "divider", "x": 10, "y": 655, "width": 910, "height": 2, "backgroundColor": "#dee2e6", "fillStyle": "solid" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "grp-agent", "x": 15, "y": 668, "text": "AI AGENT FLOWS", "fontSize": 14, "strokeColor": "#7048e8" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a1-label", "x": 15, "y": 695, "text": "A1 Pre-edit context", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a1-mrs", "x": 325, "y": 690, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a1-search", "x": 390, "y": 690, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a1-who", "x": 460, "y": 690, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a2-label", "x": 15, "y": 730, "text": "A2 Auto-triage", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a2-issues", "x": 255, "y": 725, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a2-search", "x": 390, "y": 725, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a2-who", "x": 460, "y": 725, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a2-gap", "x": 780, "y": 730, "text": "detail --fields", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a3-label", "x": 15, "y": 765, "text": "A3 Sprint report", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a3-issues", "x": 255, "y": 760, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a3-mrs", "x": 325, "y": 760, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a3-who", "x": 460, "y": 760, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a3-count", "x": 655, "y": 760, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a3-gap", "x": 780, "y": 765, "text": "summary cmd", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a4-label", "x": 15, "y": 800, "text": "A4 Prior art", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a4-search", "x": 390, "y": 795, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a4-timeline", "x": 525, "y": 795, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a4-gap", "x": 780, "y": 800, "text": "per-note search", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a5-label", "x": 15, "y": 835, "text": "A5 PR description", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a5-issues", "x": 255, "y": 830, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a5-search", "x": 390, "y": 830, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a5-gap", "x": 780, "y": 835, "text": "entity refs query", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a6-label", "x": 15, "y": 870, "text": "A6 Reviewer assign", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a6-mrs", "x": 325, "y": 865, "width": 50, "height": 28, "backgroundColor": "#ffd8a8", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a6-who", "x": 460, "y": 865, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a6-gap", "x": 780, "y": 870, "text": "MR file list", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a7-label", "x": 15, "y": 905, "text": "A7 Incident timeline", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a7-mrs", "x": 325, "y": 900, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a7-search", "x": 390, "y": 900, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a7-timeline", "x": 525, "y": 900, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a8-label", "x": 15, "y": 940, "text": "A8 Cross-project", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a8-search", "x": 390, "y": 935, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a8-timeline", "x": 525, "y": 935, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a8-gap", "x": 780, "y": 940, "text": "group by project", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a9-label", "x": 15, "y": 975, "text": "A9 Stale cleanup", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a9-issues", "x": 255, "y": 970, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a9-search", "x": 390, "y": 970, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a9-gap", "x": 780, "y": 975, "text": "--updated-before", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a10-label", "x": 15, "y": 1010, "text": "A10 Review context", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a10-mrs", "x": 325, "y": 1005, "width": 50, "height": 28, "backgroundColor": "#ffd8a8", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a10-who", "x": 460, "y": 1005, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a10-gap", "x": 780, "y": 1010, "text": "MR file list", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a11-label", "x": 15, "y": 1045, "text": "A11 Knowledge graph", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a11-search", "x": 390, "y": 1040, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a11-timeline", "x": 525, "y": 1040, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a11-gap", "x": 780, "y": 1045, "text": "entity refs query", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a12-label", "x": 15, "y": 1080, "text": "A12 Release check", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a12-issues", "x": 255, "y": 1075, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a12-mrs", "x": 325, "y": 1075, "width": 50, "height": 28, "backgroundColor": "#ffd8a8", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a12-who", "x": 460, "y": 1075, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a12-gap", "x": 780, "y": 1080, "text": "mrs --milestone", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a13-label", "x": 15, "y": 1115, "text": "A13 What changed?", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a13-issues", "x": 255, "y": 1110, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a13-mrs", "x": 325, "y": 1110, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a13-gap", "x": 780, "y": 1115, "text": "state-change filter", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a14-label", "x": 15, "y": 1150, "text": "A14 Meeting prep", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a14-issues", "x": 255, "y": 1145, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a14-mrs", "x": 325, "y": 1145, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a14-who", "x": 460, "y": 1145, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a14-count", "x": 655, "y": 1145, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a14-gap", "x": 780, "y": 1150, "text": "summary cmd", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "a15-label", "x": 15, "y": 1185, "text": "A15 Conflict detect", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "a15-issues", "x": 255, "y": 1180, "width": 50, "height": 28, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a15-mrs", "x": 325, "y": 1180, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "rectangle", "id": "a15-who", "x": 460, "y": 1180, "width": 50, "height": 28, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "a15-gap", "x": 780, "y": 1185, "text": "entity refs, --state", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "legend-title", "x": 15, "y": 1230, "text": "Legend:", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "leg-essential", "x": 80, "y": 1228, "width": 20, "height": 20, "backgroundColor": "#22c55e", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "leg-essential-t", "x": 105, "y": 1230, "text": "Essential", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "leg-supporting", "x": 190, "y": 1228, "width": 20, "height": 20, "backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "leg-supporting-t", "x": 215, "y": 1230, "text": "Supporting", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "leg-partial", "x": 310, "y": 1228, "width": 20, "height": 20, "backgroundColor": "#ffd8a8", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "leg-partial-t", "x": 335, "y": 1230, "text": "Partially blocked", "fontSize": 14 },
|
||||||
|
{ "type": "text", "id": "leg-gap-t", "x": 470, "y": 1230, "text": "Red text = gap", "fontSize": 14, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "insight-1", "x": 15, "y": 1270, "text": "Key insight: `issues` and `search` are the workhorses (used in 20+ flows).", "fontSize": 14, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "insight-2", "x": 15, "y": 1295, "text": "`who` is critical for people questions but siloed from file-change data.", "fontSize": 14, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "insight-3", "x": 15, "y": 1320, "text": "`timeline` is powerful but keyword-only seeding limits entity-specific queries.", "fontSize": 14, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "insight-4", "x": 15, "y": 1345, "text": "22/30 flows have at least one gap. Most gaps are filter additions, not new commands.", "fontSize": 14, "strokeColor": "#ef4444" }
|
||||||
|
],
|
||||||
|
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/diagrams/03-command-coverage.png
Normal file
BIN
docs/diagrams/03-command-coverage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 217 KiB |
110
docs/diagrams/04-gap-priority-matrix.excalidraw
Normal file
110
docs/diagrams/04-gap-priority-matrix.excalidraw
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{ "type": "text", "id": "title", "x": 300, "y": 20, "text": "Lore CLI Gap Priority Matrix", "fontSize": 28 },
|
||||||
|
{ "type": "text", "id": "subtitle", "x": 310, "y": 58, "text": "20 identified gaps plotted by impact vs effort", "fontSize": 16, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "q1-zone", "x": 100, "y": 120, "width": 500, "height": 380,
|
||||||
|
"backgroundColor": "#d3f9d8", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#22c55e", "strokeWidth": 1, "opacity": 25 },
|
||||||
|
{ "type": "text", "id": "q1-label", "x": 110, "y": 126, "text": "QUICK WINS", "fontSize": 18, "strokeColor": "#15803d" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "q2-zone", "x": 620, "y": 120, "width": 500, "height": 380,
|
||||||
|
"backgroundColor": "#fff3bf", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#f59e0b", "strokeWidth": 1, "opacity": 25 },
|
||||||
|
{ "type": "text", "id": "q2-label", "x": 630, "y": 126, "text": "STRATEGIC", "fontSize": 18, "strokeColor": "#b45309" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "q3-zone", "x": 100, "y": 520, "width": 500, "height": 300,
|
||||||
|
"backgroundColor": "#dbe4ff", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#4a9eed", "strokeWidth": 1, "opacity": 25 },
|
||||||
|
{ "type": "text", "id": "q3-label", "x": 110, "y": 526, "text": "FILL-IN", "fontSize": 18, "strokeColor": "#1971c2" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "q4-zone", "x": 620, "y": 520, "width": 500, "height": 300,
|
||||||
|
"backgroundColor": "#ffc9c9", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#ef4444", "strokeWidth": 1, "opacity": 25 },
|
||||||
|
{ "type": "text", "id": "q4-label", "x": 630, "y": 526, "text": "DEPRIORITIZE", "fontSize": 18, "strokeColor": "#c92a2a" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "y-axis-hi", "x": 30, "y": 130, "text": "HIGH\nIMPACT", "fontSize": 16, "strokeColor": "#495057", "textAlign": "center" },
|
||||||
|
{ "type": "text", "id": "y-axis-lo", "x": 30, "y": 550, "text": "LOW\nIMPACT", "fontSize": 16, "strokeColor": "#495057", "textAlign": "center" },
|
||||||
|
{ "type": "text", "id": "x-axis-lo", "x": 280, "y": 840, "text": "LOW EFFORT", "fontSize": 16, "strokeColor": "#495057" },
|
||||||
|
{ "type": "text", "id": "x-axis-hi", "x": 800, "y": 840, "text": "HIGH EFFORT", "fontSize": 16, "strokeColor": "#495057" },
|
||||||
|
|
||||||
|
{ "type": "arrow", "id": "y-arrow", "x": 85, "y": 810, "width": 0, "height": -680,
|
||||||
|
"points": [[0,0],[0,-680]], "endArrowhead": "arrow", "strokeColor": "#495057", "strokeWidth": 1 },
|
||||||
|
{ "type": "arrow", "id": "x-arrow", "x": 85, "y": 810, "width": 1050, "height": 0,
|
||||||
|
"points": [[0,0],[1050,0]], "endArrowhead": "arrow", "strokeColor": "#495057", "strokeWidth": 1 },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "g5", "x": 120, "y": 160, "width": 210, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#5 @me alias", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g8", "x": 120, "y": 225, "width": 210, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#8 --state on search", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g9", "x": 120, "y": 290, "width": 210, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#9 mrs --milestone", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g10", "x": 120, "y": 355, "width": 210, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#10 --no-assignee", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g11", "x": 350, "y": 160, "width": 230, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#11 --updated-before", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g14", "x": 350, "y": 225, "width": 230, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#14 detail --fields", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g18", "x": 350, "y": 290, "width": 230, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#18 1y/12m duration", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g20", "x": 350, "y": 355, "width": 230, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#20 sort by due date", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "g1", "x": 640, "y": 160, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#1 MR file changes", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g2", "x": 640, "y": 225, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#2 entity refs query", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g3", "x": 640, "y": 290, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#3 per-note search", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g4", "x": 880, "y": 160, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#4 entity timeline", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g6", "x": 880, "y": 225, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#6 activity feed", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g12", "x": 880, "y": 290, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffd8a8", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#12 team workload", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "g13", "x": 120, "y": 570, "width": 210, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#13 pagination/offset", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g15", "x": 120, "y": 635, "width": 210, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#15 group by project", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g19", "x": 120, "y": 700, "width": 210, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#19 participant filter", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "g7", "x": 640, "y": 570, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#7 multi-path who", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g16", "x": 640, "y": 635, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#16 trend metrics", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "g17", "x": 640, "y": 700, "width": 220, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid",
|
||||||
|
"label": { "text": "#17 --for-issue on mrs", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "q1-count", "x": 180, "y": 430, "text": "8 gaps - lowest hanging fruit", "fontSize": 14, "strokeColor": "#15803d" },
|
||||||
|
{ "type": "text", "id": "q2-count", "x": 710, "y": 370, "text": "6 gaps - build deliberately", "fontSize": 14, "strokeColor": "#b45309" },
|
||||||
|
{ "type": "text", "id": "q3-count", "x": 160, "y": 770, "text": "3 gaps - fill as needed", "fontSize": 14, "strokeColor": "#1971c2" },
|
||||||
|
{ "type": "text", "id": "q4-count", "x": 680, "y": 770, "text": "3 gaps - defer or rethink", "fontSize": 14, "strokeColor": "#c92a2a" }
|
||||||
|
],
|
||||||
|
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/diagrams/04-gap-priority-matrix.png
Normal file
BIN
docs/diagrams/04-gap-priority-matrix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 179 KiB |
184
docs/diagrams/05-data-flow-architecture.excalidraw
Normal file
184
docs/diagrams/05-data-flow-architecture.excalidraw
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
{
|
||||||
|
"type": "excalidraw",
|
||||||
|
"version": 2,
|
||||||
|
"source": "https://excalidraw.com",
|
||||||
|
"elements": [
|
||||||
|
{ "type": "text", "id": "title", "x": 350, "y": 15, "text": "Lore Data Flow Architecture", "fontSize": 28 },
|
||||||
|
{ "type": "text", "id": "subtitle", "x": 280, "y": 53, "text": "Green = queryable via CLI | Red = stored but hidden | Gray = internal", "fontSize": 14, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-gitlab", "x": 30, "y": 90, "width": 200, "height": 300,
|
||||||
|
"backgroundColor": "#e5dbff", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#8b5cf6", "strokeWidth": 1, "opacity": 30 },
|
||||||
|
{ "type": "text", "id": "zone-gitlab-label", "x": 55, "y": 96, "text": "GitLab APIs", "fontSize": 16, "strokeColor": "#7048e8" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "rest-api", "x": 50, "y": 130, "width": 160, "height": 60,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "REST API\n(paginated)", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "graphql-api", "x": 50, "y": 210, "width": 160, "height": 60,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "GraphQL API\n(adaptive pages)", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "ollama-api", "x": 50, "y": 310, "width": 160, "height": 60,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#d0bfff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "Ollama\n(embeddings)", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-ingest", "x": 270, "y": 90, "width": 180, "height": 300,
|
||||||
|
"backgroundColor": "#dbe4ff", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#4a9eed", "strokeWidth": 1, "opacity": 30 },
|
||||||
|
{ "type": "text", "id": "zone-ingest-label", "x": 300, "y": 96, "text": "Ingestion", "fontSize": 16, "strokeColor": "#1971c2" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "ingest-issues", "x": 285, "y": 130, "width": 150, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "Issue Sync", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "ingest-mrs", "x": 285, "y": 195, "width": 150, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "MR Sync", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "ingest-disc", "x": 285, "y": 260, "width": 150, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "Discussion Sync", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "ingest-events", "x": 285, "y": 325, "width": 150, "height": 50,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#a5d8ff", "fillStyle": "solid",
|
||||||
|
"label": { "text": "Event Sync", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "arrow", "id": "a-rest-issues", "x": 210, "y": 155, "width": 75, "height": 0,
|
||||||
|
"points": [[0,0],[75,0]], "endArrowhead": "arrow", "strokeColor": "#495057" },
|
||||||
|
{ "type": "arrow", "id": "a-rest-mrs", "x": 210, "y": 165, "width": 75, "height": 50,
|
||||||
|
"points": [[0,0],[75,50]], "endArrowhead": "arrow", "strokeColor": "#495057" },
|
||||||
|
{ "type": "arrow", "id": "a-graphql-issues", "x": 210, "y": 240, "width": 75, "height": -80,
|
||||||
|
"points": [[0,0],[75,-80]], "endArrowhead": "arrow", "strokeColor": "#495057" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-sqlite", "x": 490, "y": 90, "width": 400, "height": 650,
|
||||||
|
"backgroundColor": "#d3f9d8", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#22c55e", "strokeWidth": 1, "opacity": 20 },
|
||||||
|
{ "type": "text", "id": "zone-sqlite-label", "x": 570, "y": 96, "text": "SQLite (WAL mode)", "fontSize": 16, "strokeColor": "#15803d" },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "grp-queryable", "x": 500, "y": 120, "text": "Queryable Tables", "fontSize": 14, "strokeColor": "#15803d" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "t-projects", "x": 500, "y": 145, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "projects", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-issues", "x": 500, "y": 195, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "issues + assignees", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-mrs", "x": 500, "y": 245, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "merge_requests", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-discussions", "x": 500, "y": 295, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "discussions + notes", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-events", "x": 500, "y": 345, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "resource_*_events", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-docs", "x": 500, "y": 395, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "documents + FTS5", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-embed", "x": 500, "y": 445, "width": 170, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#b2f2bb", "fillStyle": "solid",
|
||||||
|
"label": { "text": "embeddings (vec)", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "grp-hidden", "x": 700, "y": 120, "text": "Hidden Tables", "fontSize": 14, "strokeColor": "#c92a2a" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "t-file-changes", "x": 695, "y": 145, "width": 180, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "mr_file_changes", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-entity-refs", "x": 695, "y": 195, "width": 180, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "entity_references", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-raw", "x": 695, "y": 245, "width": 180, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444",
|
||||||
|
"label": { "text": "raw_payloads", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "grp-internal", "x": 700, "y": 310, "text": "Internal Only", "fontSize": 14, "strokeColor": "#868e96" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "t-sync", "x": 695, "y": 340, "width": 180, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#dee2e6", "fillStyle": "solid", "strokeColor": "#868e96",
|
||||||
|
"label": { "text": "sync_runs + cursors", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-dirty", "x": 695, "y": 390, "width": 180, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#dee2e6", "fillStyle": "solid", "strokeColor": "#868e96",
|
||||||
|
"label": { "text": "dirty_sources", "fontSize": 14 } },
|
||||||
|
{ "type": "rectangle", "id": "t-locks", "x": 695, "y": 440, "width": 180, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#dee2e6", "fillStyle": "solid", "strokeColor": "#868e96",
|
||||||
|
"label": { "text": "app_locks", "fontSize": 14 } },
|
||||||
|
|
||||||
|
{ "type": "arrow", "id": "a-ingest-tables", "x": 435, "y": 200, "width": 55, "height": 0,
|
||||||
|
"points": [[0,0],[55,0]], "endArrowhead": "arrow", "strokeColor": "#495057" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "zone-cli", "x": 930, "y": 90, "width": 250, "height": 650,
|
||||||
|
"backgroundColor": "#fff3bf", "fillStyle": "solid", "roundness": { "type": 3 },
|
||||||
|
"strokeColor": "#f59e0b", "strokeWidth": 1, "opacity": 25 },
|
||||||
|
{ "type": "text", "id": "zone-cli-label", "x": 990, "y": 96, "text": "CLI Commands", "fontSize": 16, "strokeColor": "#b45309" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "cmd-issues", "x": 950, "y": 130, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore issues", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-mrs", "x": 950, "y": 185, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore mrs", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-search", "x": 950, "y": 240, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore search", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-who", "x": 950, "y": 295, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore who", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-timeline", "x": 950, "y": 350, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore timeline", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-count", "x": 950, "y": 405, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore count", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-sync", "x": 950, "y": 460, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore sync", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-status", "x": 950, "y": 515, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#fff3bf", "fillStyle": "solid",
|
||||||
|
"label": { "text": "lore status", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "arrow", "id": "a-issues-cmd", "x": 670, "y": 215, "width": 270, "height": -65,
|
||||||
|
"points": [[0,0],[270,-65]], "endArrowhead": "arrow", "strokeColor": "#22c55e", "strokeWidth": 2 },
|
||||||
|
{ "type": "arrow", "id": "a-mrs-cmd", "x": 670, "y": 265, "width": 270, "height": -60,
|
||||||
|
"points": [[0,0],[270,-60]], "endArrowhead": "arrow", "strokeColor": "#22c55e", "strokeWidth": 2 },
|
||||||
|
{ "type": "arrow", "id": "a-docs-cmd", "x": 670, "y": 415, "width": 270, "height": -155,
|
||||||
|
"points": [[0,0],[270,-155]], "endArrowhead": "arrow", "strokeColor": "#22c55e", "strokeWidth": 2 },
|
||||||
|
{ "type": "arrow", "id": "a-embed-cmd", "x": 670, "y": 465, "width": 270, "height": -200,
|
||||||
|
"points": [[0,0],[270,-200]], "endArrowhead": "arrow", "strokeColor": "#22c55e", "strokeWidth": 2 },
|
||||||
|
{ "type": "arrow", "id": "a-events-cmd", "x": 670, "y": 365, "width": 270, "height": 5,
|
||||||
|
"points": [[0,0],[270,5]], "endArrowhead": "arrow", "strokeColor": "#22c55e", "strokeWidth": 2 },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "hidden-note-1", "x": 695, "y": 498, "text": "mr_file_changes: populated by\nMR sync but NOT queryable.\nBlocks H4, A6, A10 flows.", "fontSize": 14, "strokeColor": "#ef4444" },
|
||||||
|
{ "type": "text", "id": "hidden-note-2", "x": 695, "y": 568, "text": "entity_references: used by\ntimeline internally but NOT\nqueryable. Blocks A5, A11.", "fontSize": 14, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "arrow", "id": "a-hidden-who", "x": 875, "y": 165, "width": 65, "height": 148,
|
||||||
|
"points": [[0,0],[65,148]], "endArrowhead": "arrow", "strokeColor": "#ef4444", "strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed" },
|
||||||
|
{ "type": "text", "id": "hidden-who-label", "x": 880, "y": 240, "text": "who uses\nDiffNotes,\nnot file\nchanges", "fontSize": 12, "strokeColor": "#ef4444" },
|
||||||
|
|
||||||
|
{ "type": "arrow", "id": "a-hidden-timeline", "x": 875, "y": 215, "width": 65, "height": 155,
|
||||||
|
"points": [[0,0],[65,155]], "endArrowhead": "arrow", "strokeColor": "#ef4444", "strokeWidth": 2,
|
||||||
|
"strokeStyle": "dashed" },
|
||||||
|
|
||||||
|
{ "type": "rectangle", "id": "cmd-missing-refs", "x": 950, "y": 580, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444", "strokeStyle": "dashed",
|
||||||
|
"label": { "text": "lore refs (missing)", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-missing-files", "x": 950, "y": 635, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444", "strokeStyle": "dashed",
|
||||||
|
"label": { "text": "lore files (missing)", "fontSize": 16 } },
|
||||||
|
{ "type": "rectangle", "id": "cmd-missing-activity", "x": 950, "y": 690, "width": 210, "height": 40,
|
||||||
|
"roundness": { "type": 3 }, "backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444", "strokeStyle": "dashed",
|
||||||
|
"label": { "text": "lore activity (missing)", "fontSize": 16 } },
|
||||||
|
|
||||||
|
{ "type": "text", "id": "legend-title", "x": 30, "y": 430, "text": "Legend", "fontSize": 16 },
|
||||||
|
{ "type": "rectangle", "id": "leg-green", "x": 30, "y": 460, "width": 20, "height": 20,
|
||||||
|
"backgroundColor": "#b2f2bb", "fillStyle": "solid" },
|
||||||
|
{ "type": "text", "id": "leg-green-t", "x": 60, "y": 462, "text": "Queryable via CLI", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "leg-red", "x": 30, "y": 490, "width": 20, "height": 20,
|
||||||
|
"backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444" },
|
||||||
|
{ "type": "text", "id": "leg-red-t", "x": 60, "y": 492, "text": "Stored but hidden", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "leg-gray", "x": 30, "y": 520, "width": 20, "height": 20,
|
||||||
|
"backgroundColor": "#dee2e6", "fillStyle": "solid", "strokeColor": "#868e96" },
|
||||||
|
{ "type": "text", "id": "leg-gray-t", "x": 60, "y": 522, "text": "Internal bookkeeping", "fontSize": 14 },
|
||||||
|
{ "type": "rectangle", "id": "leg-dashed", "x": 30, "y": 550, "width": 20, "height": 20,
|
||||||
|
"backgroundColor": "#ffc9c9", "fillStyle": "solid", "strokeColor": "#ef4444", "strokeStyle": "dashed" },
|
||||||
|
{ "type": "text", "id": "leg-dashed-t", "x": 60, "y": 552, "text": "Missing command", "fontSize": 14 }
|
||||||
|
],
|
||||||
|
"appState": { "viewBackgroundColor": "#ffffff", "gridSize": null },
|
||||||
|
"files": {}
|
||||||
|
}
|
||||||
BIN
docs/diagrams/05-data-flow-architecture.png
Normal file
BIN
docs/diagrams/05-data-flow-architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 238 KiB |
308
docs/embedding-pipeline-hardening.md
Normal file
308
docs/embedding-pipeline-hardening.md
Normal file
@@ -0,0 +1,308 @@
|
|||||||
|
# Embedding Pipeline Hardening: Chunk Config Drift, Adaptive Dedup, Full Flag Wiring
|
||||||
|
|
||||||
|
> **Status:** Proposed
|
||||||
|
> **Date:** 2026-02-02
|
||||||
|
> **Context:** Reduced CHUNK_MAX_BYTES from 32KB to 6KB to prevent Ollama context window overflow. This plan addresses the downstream consequences of that change.
|
||||||
|
|
||||||
|
## Problem Statement
|
||||||
|
|
||||||
|
Three issues stem from the chunk size reduction:
|
||||||
|
|
||||||
|
1. **Broken `--full` wiring**: `handle_embed` in main.rs ignores `args.full` (calls `run_embed` instead of `run_embed_full`). `run_sync` hardcodes `false` for retry_failed and never passes `options.full` to embed. Users running `lore sync --full` or `lore embed --full` don't get a full re-embed.
|
||||||
|
|
||||||
|
2. **Mixed chunk sizes in vector space**: Existing embeddings (32KB chunks) coexist with new embeddings (6KB chunks). These are semantically incomparable -- different granularity vectors in the same KNN space degrade search quality. No mechanism detects this drift.
|
||||||
|
|
||||||
|
3. **Static dedup multiplier**: `search_vector` uses `limit * 8` to over-fetch for dedup. With smaller chunks producing 5-6 chunks per document, clustered search results can exhaust slots before reaching `limit` unique documents. The multiplier should adapt to actual data.
|
||||||
|
|
||||||
|
## Decision Record
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|----------|--------|-----------|
|
||||||
|
| Detect chunk config drift | Store `chunk_max_bytes` in `embedding_metadata` | Allows automatic invalidation without user intervention. Self-heals on next sync. |
|
||||||
|
| Dedup multiplier strategy | Adaptive from DB with static floor | One cheap aggregate query per search. Self-adjusts as data grows. No wasted KNN budget. |
|
||||||
|
| `--full` propagation | `sync --full` passes full to embed step | Matches user expectation: "start fresh" means everything, not just ingest+docs. |
|
||||||
|
| Migration strategy | New migration 010 for `chunk_max_bytes` column | Non-breaking additive change. NULL values = "unknown config" treated as needing re-embed. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### Change 1: Wire `--full` flag through to embed
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `src/main.rs` (line 1116)
|
||||||
|
- `src/cli/commands/sync.rs` (line 105)
|
||||||
|
|
||||||
|
**main.rs `handle_embed`** (line 1116):
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
let result = run_embed(&config, retry_failed).await?;
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
let result = run_embed_full(&config, args.full, retry_failed).await?;
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the import at top of main.rs from `run_embed` to `run_embed_full`.
|
||||||
|
|
||||||
|
**sync.rs `run_sync`** (line 105):
|
||||||
|
```rust
|
||||||
|
// BEFORE:
|
||||||
|
match run_embed(config, false).await {
|
||||||
|
|
||||||
|
// AFTER:
|
||||||
|
match run_embed_full(config, options.full, false).await {
|
||||||
|
```
|
||||||
|
|
||||||
|
Update the import at line 11 from `run_embed` to `run_embed_full`.
|
||||||
|
|
||||||
|
**Cleanup `embed.rs`**: Remove `run_embed` (the wrapper that hardcodes `full: false`). All callers should use `run_embed_full` directly. Rename `run_embed_full` to `run_embed` with the 3-arg signature `(config, full, retry_failed)`.
|
||||||
|
|
||||||
|
Final signature:
|
||||||
|
```rust
|
||||||
|
pub async fn run_embed(
|
||||||
|
config: &Config,
|
||||||
|
full: bool,
|
||||||
|
retry_failed: bool,
|
||||||
|
) -> Result<EmbedCommandResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 2: Migration 010 -- add `chunk_max_bytes` to `embedding_metadata`
|
||||||
|
|
||||||
|
**New file:** `migrations/010_chunk_config.sql`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Migration 010: Chunk config tracking
|
||||||
|
-- Schema version: 10
|
||||||
|
-- Adds chunk_max_bytes to embedding_metadata for drift detection.
|
||||||
|
-- Existing rows get NULL, which the change detector treats as "needs re-embed".
|
||||||
|
|
||||||
|
ALTER TABLE embedding_metadata ADD COLUMN chunk_max_bytes INTEGER;
|
||||||
|
|
||||||
|
UPDATE schema_version SET version = 10
|
||||||
|
WHERE version = (SELECT MAX(version) FROM schema_version);
|
||||||
|
-- Or if using INSERT pattern:
|
||||||
|
INSERT INTO schema_version (version, applied_at, description)
|
||||||
|
VALUES (10, strftime('%s', 'now') * 1000, 'Add chunk_max_bytes to embedding_metadata for config drift detection');
|
||||||
|
```
|
||||||
|
|
||||||
|
Check existing migration pattern in `src/core/db.rs` for how migrations are applied -- follow that exact pattern for consistency.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 3: Store `chunk_max_bytes` when writing embeddings
|
||||||
|
|
||||||
|
**File:** `src/embedding/pipeline.rs`
|
||||||
|
|
||||||
|
**`store_embedding`** (lines 238-266): Add `chunk_max_bytes` to the INSERT:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Add import at top:
|
||||||
|
use crate::embedding::chunking::CHUNK_MAX_BYTES;
|
||||||
|
|
||||||
|
// In store_embedding, update SQL:
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO embedding_metadata
|
||||||
|
(document_id, chunk_index, model, dims, document_hash, chunk_hash,
|
||||||
|
created_at, attempt_count, last_error, chunk_max_bytes)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 1, NULL, ?8)",
|
||||||
|
rusqlite::params![
|
||||||
|
doc_id, chunk_index as i64, model_name, EXPECTED_DIMS as i64,
|
||||||
|
doc_hash, chunk_hash, now, CHUNK_MAX_BYTES as i64
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
**`record_embedding_error`** (lines 269-291): Also store `chunk_max_bytes` so error rows track which config they failed under:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO embedding_metadata
|
||||||
|
(document_id, chunk_index, model, dims, document_hash, chunk_hash,
|
||||||
|
created_at, attempt_count, last_error, last_attempt_at, chunk_max_bytes)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 1, ?8, ?7, ?9)
|
||||||
|
ON CONFLICT(document_id, chunk_index) DO UPDATE SET
|
||||||
|
attempt_count = embedding_metadata.attempt_count + 1,
|
||||||
|
last_error = ?8,
|
||||||
|
last_attempt_at = ?7,
|
||||||
|
chunk_max_bytes = ?9",
|
||||||
|
rusqlite::params![
|
||||||
|
doc_id, chunk_index as i64, model_name, EXPECTED_DIMS as i64,
|
||||||
|
doc_hash, chunk_hash, now, error, CHUNK_MAX_BYTES as i64
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 4: Detect chunk config drift in change detector
|
||||||
|
|
||||||
|
**File:** `src/embedding/change_detector.rs`
|
||||||
|
|
||||||
|
Add a third condition to the pending detection: embeddings where `chunk_max_bytes` differs from the current `CHUNK_MAX_BYTES` constant (or is NULL, meaning pre-migration embeddings).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use crate::embedding::chunking::CHUNK_MAX_BYTES;
|
||||||
|
|
||||||
|
pub fn find_pending_documents(
|
||||||
|
conn: &Connection,
|
||||||
|
page_size: usize,
|
||||||
|
last_id: i64,
|
||||||
|
) -> Result<Vec<PendingDocument>> {
|
||||||
|
let sql = r#"
|
||||||
|
SELECT d.id, d.content_text, d.content_hash
|
||||||
|
FROM documents d
|
||||||
|
WHERE d.id > ?1
|
||||||
|
AND (
|
||||||
|
-- Case 1: No embedding metadata (new document)
|
||||||
|
NOT EXISTS (
|
||||||
|
SELECT 1 FROM embedding_metadata em
|
||||||
|
WHERE em.document_id = d.id AND em.chunk_index = 0
|
||||||
|
)
|
||||||
|
-- Case 2: Document content changed
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1 FROM embedding_metadata em
|
||||||
|
WHERE em.document_id = d.id AND em.chunk_index = 0
|
||||||
|
AND em.document_hash != d.content_hash
|
||||||
|
)
|
||||||
|
-- Case 3: Chunk config drift (different chunk size or pre-migration NULL)
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1 FROM embedding_metadata em
|
||||||
|
WHERE em.document_id = d.id AND em.chunk_index = 0
|
||||||
|
AND (em.chunk_max_bytes IS NULL OR em.chunk_max_bytes != ?3)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
ORDER BY d.id
|
||||||
|
LIMIT ?2
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let mut stmt = conn.prepare(sql)?;
|
||||||
|
let rows = stmt
|
||||||
|
.query_map(
|
||||||
|
rusqlite::params![last_id, page_size as i64, CHUNK_MAX_BYTES as i64],
|
||||||
|
|row| {
|
||||||
|
Ok(PendingDocument {
|
||||||
|
document_id: row.get(0)?,
|
||||||
|
content_text: row.get(1)?,
|
||||||
|
content_hash: row.get(2)?,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
Ok(rows)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Apply the same change to `count_pending_documents` -- add the third OR clause and the `?3` parameter.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 5: Adaptive dedup multiplier in vector search
|
||||||
|
|
||||||
|
**File:** `src/search/vector.rs`
|
||||||
|
|
||||||
|
Replace the static `limit * 8` with an adaptive multiplier based on the actual max chunks-per-document in the database.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Query the max chunks any single document has in the embedding table.
|
||||||
|
/// Returns the max chunk count, or a default floor if no data exists.
|
||||||
|
fn max_chunks_per_document(conn: &Connection) -> i64 {
|
||||||
|
conn.query_row(
|
||||||
|
"SELECT COALESCE(MAX(cnt), 1) FROM (
|
||||||
|
SELECT COUNT(*) as cnt FROM embedding_metadata
|
||||||
|
WHERE last_error IS NULL
|
||||||
|
GROUP BY document_id
|
||||||
|
)",
|
||||||
|
[],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.unwrap_or(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn search_vector(
|
||||||
|
conn: &Connection,
|
||||||
|
query_embedding: &[f32],
|
||||||
|
limit: usize,
|
||||||
|
) -> Result<Vec<VectorResult>> {
|
||||||
|
if query_embedding.is_empty() || limit == 0 {
|
||||||
|
return Ok(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
let embedding_bytes: Vec<u8> = query_embedding
|
||||||
|
.iter()
|
||||||
|
.flat_map(|f| f.to_le_bytes())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Adaptive over-fetch: use actual max chunks per doc, with floor of 8x
|
||||||
|
// The 1.5x safety margin handles clustering in KNN results
|
||||||
|
let max_chunks = max_chunks_per_document(conn);
|
||||||
|
let multiplier = (max_chunks as usize * 3 / 2).max(8);
|
||||||
|
let k = limit * multiplier;
|
||||||
|
|
||||||
|
// ... rest unchanged ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why `max_chunks * 1.5` with floor of 8**:
|
||||||
|
- `max_chunks` is the worst case for a single document dominating results
|
||||||
|
- `* 1.5` adds margin for multiple clustered documents
|
||||||
|
- Floor of `8` ensures reasonable over-fetch even with single-chunk documents
|
||||||
|
- This is a single aggregate query on an indexed column -- sub-millisecond
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Change 6: Update chunk_ids.rs comment
|
||||||
|
|
||||||
|
**File:** `src/embedding/chunk_ids.rs` (line 1-3)
|
||||||
|
|
||||||
|
Update the comment to reflect current reality:
|
||||||
|
```rust
|
||||||
|
/// Multiplier for encoding (document_id, chunk_index) into a single rowid.
|
||||||
|
/// Supports up to 1000 chunks per document. At CHUNK_MAX_BYTES=6000,
|
||||||
|
/// a 2MB document (MAX_DOCUMENT_BYTES_HARD) produces ~333 chunks.
|
||||||
|
pub const CHUNK_ROWID_MULTIPLIER: i64 = 1000;
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Modified (Summary)
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `migrations/010_chunk_config.sql` | **NEW** -- Add `chunk_max_bytes` column |
|
||||||
|
| `src/embedding/pipeline.rs` | Store `CHUNK_MAX_BYTES` in metadata writes |
|
||||||
|
| `src/embedding/change_detector.rs` | Detect chunk config drift (3rd OR clause) |
|
||||||
|
| `src/search/vector.rs` | Adaptive dedup multiplier from DB |
|
||||||
|
| `src/cli/commands/embed.rs` | Consolidate to single `run_embed(config, full, retry_failed)` |
|
||||||
|
| `src/cli/commands/sync.rs` | Pass `options.full` to embed, update import |
|
||||||
|
| `src/main.rs` | Call `run_embed` with `args.full`, update import |
|
||||||
|
| `src/embedding/chunk_ids.rs` | Comment update only |
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. **Compile check**: `cargo build` -- no errors
|
||||||
|
2. **Unit tests**: `cargo test` -- all existing tests pass
|
||||||
|
3. **Migration test**: Run `lore doctor` or `lore migrate` -- migration 010 applies cleanly
|
||||||
|
4. **Full flag wiring**: `lore embed --full` should clear all embeddings and re-embed. Verify by checking `lore --robot stats` before and after (embedded count should reset then rebuild).
|
||||||
|
5. **Chunk config drift**: After migration, existing embeddings have `chunk_max_bytes = NULL`. Running `lore embed` (without --full) should detect all existing embeddings as stale and re-embed them automatically.
|
||||||
|
6. **Sync propagation**: `lore sync --full` should produce the same embed behavior as `lore embed --full`
|
||||||
|
7. **Adaptive dedup**: Run `lore search "some query"` and verify the result count matches the requested limit (default 20). Check with `RUST_LOG=debug` that the computed `k` value scales with actual chunk distribution.
|
||||||
|
|
||||||
|
## Decision Record (for future reference)
|
||||||
|
|
||||||
|
**Date:** 2026-02-02
|
||||||
|
**Trigger:** Reduced CHUNK_MAX_BYTES from 32KB to 6KB to prevent Ollama nomic-embed-text context window overflow (8192 tokens).
|
||||||
|
|
||||||
|
**Downstream consequences identified:**
|
||||||
|
1. Chunk ID headroom reduced (1000 slots, now ~333 used for 2MB docs) -- acceptable, no action needed
|
||||||
|
2. Vector search dedup pressure increased 5x -- fixed with adaptive multiplier
|
||||||
|
3. Embedding DB grows ~5x -- acceptable at current scale (~7.5MB)
|
||||||
|
4. Mixed chunk sizes degrade search -- fixed with config drift detection
|
||||||
|
5. Ollama API call volume increases proportionally -- acceptable for local model
|
||||||
|
|
||||||
|
**Rejected alternatives:**
|
||||||
|
- Two-phase KNN fetch (fetch, check, re-fetch with higher k): adds code complexity for marginal improvement over adaptive. sqlite-vec doesn't support OFFSET in KNN queries, requiring full re-query.
|
||||||
|
- Generous static multiplier (15x): wastes KNN budget on datasets where documents are small. Over-allocates permanently instead of adapting.
|
||||||
|
- Manual `--full` as the only drift remedy: requires users to understand chunk config internals. Violates principle of least surprise.
|
||||||
66
docs/ideas/README.md
Normal file
66
docs/ideas/README.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Gitlore Feature Ideas
|
||||||
|
|
||||||
|
Central registry of potential features. Each idea leverages data already ingested
|
||||||
|
into the local SQLite database (issues, MRs, discussions, notes, resource events,
|
||||||
|
entity references, embeddings, file changes).
|
||||||
|
|
||||||
|
## Priority Tiers
|
||||||
|
|
||||||
|
**Tier 1 — High confidence, low effort, immediate value:**
|
||||||
|
|
||||||
|
| # | Idea | File | Confidence |
|
||||||
|
|---|------|------|------------|
|
||||||
|
| 9 | Similar Issues Finder | [similar-issues.md](similar-issues.md) | 95% |
|
||||||
|
| 17 | "What Changed?" Digest | [digest.md](digest.md) | 93% |
|
||||||
|
| 5 | Who Knows About X? | [experts.md](experts.md) | 92% |
|
||||||
|
| -- | Multi-Project Ergonomics | [project-ergonomics.md](project-ergonomics.md) | 90% |
|
||||||
|
| 27 | Weekly Digest Generator | [weekly-digest.md](weekly-digest.md) | 90% |
|
||||||
|
| 4 | Stale Discussion Finder | [stale-discussions.md](stale-discussions.md) | 90% |
|
||||||
|
|
||||||
|
**Tier 2 — Strong ideas, moderate effort:**
|
||||||
|
|
||||||
|
| # | Idea | File | Confidence |
|
||||||
|
|---|------|------|------------|
|
||||||
|
| 19 | MR-to-Issue Closure Gap | [closure-gaps.md](closure-gaps.md) | 88% |
|
||||||
|
| 1 | Contributor Heatmap | [contributors.md](contributors.md) | 88% |
|
||||||
|
| 21 | Knowledge Silo Detection | [silos.md](silos.md) | 87% |
|
||||||
|
| 2 | Review Bottleneck Detector | [bottlenecks.md](bottlenecks.md) | 85% |
|
||||||
|
| 14 | File Hotspot Report | [hotspots.md](hotspots.md) | 85% |
|
||||||
|
| 26 | Unlinked MR Finder | [unlinked.md](unlinked.md) | 83% |
|
||||||
|
| 6 | Decision Archaeology | [decisions.md](decisions.md) | 82% |
|
||||||
|
| 18 | Label Hygiene Audit | [label-audit.md](label-audit.md) | 82% |
|
||||||
|
|
||||||
|
**Tier 3 — Promising, needs more design work:**
|
||||||
|
|
||||||
|
| # | Idea | File | Confidence |
|
||||||
|
|---|------|------|------------|
|
||||||
|
| 29 | Entity Relationship Explorer | [graph.md](graph.md) | 80% |
|
||||||
|
| 12 | Milestone Risk Report | [milestone-risk.md](milestone-risk.md) | 78% |
|
||||||
|
| 3 | Label Velocity | [label-flow.md](label-flow.md) | 78% |
|
||||||
|
| 24 | Recurring Bug Patterns | [recurring-patterns.md](recurring-patterns.md) | 76% |
|
||||||
|
| 7 | Cross-Project Impact Graph | [impact-graph.md](impact-graph.md) | 75% |
|
||||||
|
| 16 | Idle Work Detector | [idle.md](idle.md) | 73% |
|
||||||
|
| 8 | MR Churn Analysis | [churn.md](churn.md) | 72% |
|
||||||
|
| 15 | Author Collaboration Network | [collaboration.md](collaboration.md) | 70% |
|
||||||
|
| 28 | DiffNote Coverage Map | [review-coverage.md](review-coverage.md) | 75% |
|
||||||
|
| 25 | MR Pipeline Efficiency | [mr-pipeline.md](mr-pipeline.md) | 78% |
|
||||||
|
|
||||||
|
## Rejected Ideas (with reasons)
|
||||||
|
|
||||||
|
| # | Idea | Reason |
|
||||||
|
|---|------|--------|
|
||||||
|
| 10 | Sprint Burndown from Labels | Too opinionated about label semantics |
|
||||||
|
| 11 | Code Review Quality Score | Subjective "quality" scoring creates perverse incentives |
|
||||||
|
| 13 | Discussion Sentiment Drift | Unreliable heuristic sentiment on technical text |
|
||||||
|
| 20 | Response Time Leaderboard | Toxic "leaderboard" framing; metric folded into #2 |
|
||||||
|
| 22 | Timeline Diff | Niche use case; timeline already interleaves events |
|
||||||
|
| 23 | Discussion Thread Summarizer | Requires LLM inference; out of scope for local-first tool |
|
||||||
|
| 30 | NL Query Interface | Over-engineered; existing filters cover this |
|
||||||
|
|
||||||
|
## How to use this list
|
||||||
|
|
||||||
|
1. Pick an idea from Tier 1 or Tier 2
|
||||||
|
2. Read its detail file for implementation plan and SQL sketches
|
||||||
|
3. Create a bead (`br create`) referencing the idea file
|
||||||
|
4. Implement following TDD (test first, then minimal impl)
|
||||||
|
5. Update the idea file with `status: implemented` when done
|
||||||
555
docs/ideas/SYSTEM-PROPOSAL.md
Normal file
555
docs/ideas/SYSTEM-PROPOSAL.md
Normal file
@@ -0,0 +1,555 @@
|
|||||||
|
# Project Manager System — Design Proposal
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
|
||||||
|
We have a growing backlog of ideas and issues in markdown files. Agents can ship
|
||||||
|
features in under an hour. The constraint isn't execution speed — it's knowing
|
||||||
|
WHAT to execute NEXT, in what ORDER, and detecting when the plan needs to change.
|
||||||
|
|
||||||
|
We need a system that:
|
||||||
|
1. Automatically scores and sequences work items
|
||||||
|
2. Detects when scope changes during spec generation
|
||||||
|
3. Tracks the full lifecycle: idea → spec → beads → shipped
|
||||||
|
4. Re-triages instantly when the dependency graph changes
|
||||||
|
5. Runs in seconds, not minutes
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ docs/ideas/*.md │
|
||||||
|
│ docs/issues/*.md │
|
||||||
|
│ (YAML frontmatter) │
|
||||||
|
└──────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ IDEA TRIAGE SKILL │
|
||||||
|
│ │
|
||||||
|
│ Phase 1: INGEST — parse all frontmatter │
|
||||||
|
│ Phase 2: VALIDATE — check refs, detect staleness │
|
||||||
|
│ Phase 3: EVALUATE — detect scope changes since last run │
|
||||||
|
│ Phase 4: SCORE — compute priority with unlock graph │
|
||||||
|
│ Phase 5: SEQUENCE — topological sort by dependency + score │
|
||||||
|
│ Phase 6: RECOMMEND — top 3 + unlock advisories + warnings │
|
||||||
|
└──────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ HUMAN DECIDES │
|
||||||
|
│ (picks from top 3, takes seconds) │
|
||||||
|
└──────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ SPEC GENERATION (Claude/GPT) │
|
||||||
|
│ Takes the idea doc, generates detailed implementation spec │
|
||||||
|
│ ALSO: re-evaluates frontmatter fields based on deeper │
|
||||||
|
│ understanding. Updates effort, blocked-by, components. │
|
||||||
|
│ This is the SCOPE CHANGE DETECTION point. │
|
||||||
|
└──────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ PLAN-TO-BEADS (existing skill) │
|
||||||
|
│ Spec → granular beads with dependencies via br CLI │
|
||||||
|
│ Links bead IDs back into the idea frontmatter │
|
||||||
|
└──────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ AGENT IMPLEMENTATION │
|
||||||
|
│ Works beads via br/bv workflow │
|
||||||
|
│ bv --robot-triage handles execution-phase prioritization │
|
||||||
|
└──────────────────────────┬──────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ COMPLETION & RE-TRIAGE │
|
||||||
|
│ Beads close → idea status updates to implemented │
|
||||||
|
│ Skill re-runs → newly unblocked ideas surface │
|
||||||
|
│ Loop back to top │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## The Two Systems and Their Boundary
|
||||||
|
|
||||||
|
| Concern | Ideas System (new) | Beads System (existing) |
|
||||||
|
|---------|-------------------|------------------------|
|
||||||
|
| Phase | Pre-commitment (what to build) | Execution (how to build) |
|
||||||
|
| Data | docs/ideas/*.md, docs/issues/*.md | .beads/issues.jsonl |
|
||||||
|
| Triage | Idea triage skill | bv --robot-triage |
|
||||||
|
| Tracking | YAML frontmatter | JSONL records |
|
||||||
|
| Granularity | Feature-level | Task-level |
|
||||||
|
| Lifecycle | proposed → specced → promoted | open → in_progress → closed |
|
||||||
|
|
||||||
|
**The handoff point is promotion.** An idea becomes one or more beads. After that,
|
||||||
|
the ideas system only tracks the idea's status (promoted/implemented). Beads owns
|
||||||
|
execution.
|
||||||
|
|
||||||
|
An idea file is NEVER deleted. It's a permanent design record. Even after
|
||||||
|
implementation, it documents WHY the feature was built and what tradeoffs were made.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Model
|
||||||
|
|
||||||
|
### Frontmatter Schema
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
# ── Identity ──
|
||||||
|
id: idea-009 # stable unique identifier
|
||||||
|
title: Similar Issues Finder
|
||||||
|
type: idea # idea | issue
|
||||||
|
status: proposed # see lifecycle below
|
||||||
|
|
||||||
|
# ── Timestamps ──
|
||||||
|
created: 2026-02-09
|
||||||
|
updated: 2026-02-09
|
||||||
|
eval-hash: null # SHA of scoring fields at last triage run
|
||||||
|
|
||||||
|
# ── Scoring Inputs ──
|
||||||
|
impact: high # high | medium | low
|
||||||
|
effort: small # small | medium | large | xlarge
|
||||||
|
severity: null # critical | high | medium | low (issues only)
|
||||||
|
autonomy: full # full | needs-design | needs-human
|
||||||
|
|
||||||
|
# ── Dependency Graph ──
|
||||||
|
blocked-by: [] # IDs of ideas/issues that must complete first
|
||||||
|
unlocks: # IDs that become possible/better after this ships
|
||||||
|
- idea-recurring-patterns
|
||||||
|
requires: [] # external prerequisites (gate names)
|
||||||
|
related: # soft links, not blocking
|
||||||
|
- issue-001
|
||||||
|
|
||||||
|
# ── Implementation Context ──
|
||||||
|
components: # source code paths this will touch
|
||||||
|
- src/search/
|
||||||
|
- src/embedding/
|
||||||
|
command: lore similar # proposed CLI command (null for issues)
|
||||||
|
has-spec: false # detailed spec has been generated
|
||||||
|
spec-path: null # path to spec doc if it exists
|
||||||
|
beads: [] # bead IDs after promotion
|
||||||
|
|
||||||
|
# ── Classification ──
|
||||||
|
tags:
|
||||||
|
- embeddings
|
||||||
|
- search
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Status Lifecycle
|
||||||
|
|
||||||
|
```
|
||||||
|
IDEA lifecycle:
|
||||||
|
proposed ──→ accepted ──→ specced ──→ promoted ──→ implemented
|
||||||
|
│ │
|
||||||
|
└──→ rejected └──→ (scope changed, back to accepted)
|
||||||
|
|
||||||
|
ISSUE lifecycle:
|
||||||
|
open ──→ accepted ──→ specced ──→ promoted ──→ resolved
|
||||||
|
│
|
||||||
|
└──→ wontfix
|
||||||
|
```
|
||||||
|
|
||||||
|
Transitions:
|
||||||
|
- `proposed → accepted`: Human confirms this is worth building
|
||||||
|
- `accepted → specced`: Detailed implementation spec has been generated
|
||||||
|
- `specced → promoted`: Beads created from the spec
|
||||||
|
- `promoted → implemented`: All beads closed
|
||||||
|
- Any → `rejected`/`wontfix`: Decided not to build (with reason in body)
|
||||||
|
- `specced → accepted`: Scope changed during spec, needs re-evaluation
|
||||||
|
|
||||||
|
### Effort Calibration (Agent-Executed)
|
||||||
|
|
||||||
|
| Level | Wall Clock | Autonomy | Example |
|
||||||
|
|-------|-----------|----------|---------|
|
||||||
|
| small | ~30 min | Agent ships end-to-end | stale-discussions, closure-gaps |
|
||||||
|
| medium | ~1 hour | Agent ships end-to-end | similar-issues, digest |
|
||||||
|
| large | 1-2 hours | May need one design decision | recurring-patterns, experts |
|
||||||
|
| xlarge | 2+ hours | Needs human architecture input | project groups |
|
||||||
|
|
||||||
|
### Gates Registry (docs/gates.yaml)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
gates:
|
||||||
|
gate-1:
|
||||||
|
title: Resource Events Ingestion
|
||||||
|
status: complete
|
||||||
|
completed: 2025-12-15
|
||||||
|
|
||||||
|
gate-2:
|
||||||
|
title: Cross-References & Entity Graph
|
||||||
|
status: complete
|
||||||
|
completed: 2026-01-10
|
||||||
|
|
||||||
|
gate-3:
|
||||||
|
title: Timeline Pipeline
|
||||||
|
status: complete
|
||||||
|
completed: 2026-01-25
|
||||||
|
|
||||||
|
gate-4:
|
||||||
|
title: MR File Changes Ingestion
|
||||||
|
status: partial
|
||||||
|
notes: Schema ready (migration 016), ingestion code exists but untested
|
||||||
|
tracks: mr_file_changes table population
|
||||||
|
|
||||||
|
gate-5:
|
||||||
|
title: Code Trace (file:line → commit → MR → issue)
|
||||||
|
status: not-started
|
||||||
|
blocked-by: gate-4
|
||||||
|
notes: Requires git log parsing + commit SHA matching
|
||||||
|
```
|
||||||
|
|
||||||
|
The skill reads this file to determine which `requires` entries are satisfied.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scoring Algorithm
|
||||||
|
|
||||||
|
### Priority Score
|
||||||
|
|
||||||
|
```
|
||||||
|
For ideas:
|
||||||
|
base = impact_weight # high=3, medium=2, low=1
|
||||||
|
unlock = 1 + (0.5 × count_of_unlocks) # items this directly enables
|
||||||
|
readiness = 0 if blocked, 1 if ready
|
||||||
|
priority = base × unlock × readiness
|
||||||
|
|
||||||
|
For issues:
|
||||||
|
base = severity_weight × 1.5 # critical=6, high=4.5, medium=3, low=1.5
|
||||||
|
unlock = 1 + (0.5 × count_of_unlocks) # (bugs rarely unlock, but can)
|
||||||
|
readiness = 0 if blocked, 1 if ready
|
||||||
|
priority = base × unlock × readiness
|
||||||
|
|
||||||
|
Tiebreak (among equal priority):
|
||||||
|
1. Prefer smaller effort (ships faster, starts next cycle sooner)
|
||||||
|
2. Prefer autonomy:full over needs-design over needs-human
|
||||||
|
3. Prefer older items (FIFO within same score)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Why This Works
|
||||||
|
|
||||||
|
- High-impact items that unlock other items float to the top
|
||||||
|
- Blocked items score 0 regardless of impact (can't be worked)
|
||||||
|
- Effort is a tiebreaker, not a primary factor (since execution is fast)
|
||||||
|
- Issues with severity get a 1.5× multiplier (bugs degrade existing value)
|
||||||
|
- Unlock multiplier captures the "do Gate 4 first" insight automatically
|
||||||
|
|
||||||
|
### Example Rankings
|
||||||
|
|
||||||
|
| Item | Impact | Unlocks | Readiness | Score |
|
||||||
|
|------|--------|---------|-----------|-------|
|
||||||
|
| project-ergonomics | high(3) | 10 | ready(1) | 3 × 6.0 = 18.0 |
|
||||||
|
| gate-4-completion | med(2) | 5 | ready(1) | 2 × 3.5 = 7.0 |
|
||||||
|
| similar-issues | high(3) | 1 | ready(1) | 3 × 1.5 = 4.5 |
|
||||||
|
| stale-discussions | high(3) | 0 | ready(1) | 3 × 1.0 = 3.0 |
|
||||||
|
| hotspots | high(3) | 1 | blocked(0) | 0.0 |
|
||||||
|
|
||||||
|
Project-ergonomics dominates because it unlocks 10 downstream items. This is the
|
||||||
|
correct recommendation — it's the highest-leverage work even though "stale-discussions"
|
||||||
|
is simpler.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scope Change Detection
|
||||||
|
|
||||||
|
This is the hardest problem. An idea's scope can change in three ways:
|
||||||
|
|
||||||
|
### 1. During Spec Generation (Primary Detection Point)
|
||||||
|
|
||||||
|
When Claude/GPT generates a detailed implementation spec from an idea doc, it
|
||||||
|
understands the idea more deeply than the original sketch. The spec process should
|
||||||
|
be instructed to:
|
||||||
|
|
||||||
|
- Re-evaluate effort (now that implementation is understood in detail)
|
||||||
|
- Discover new dependencies (need to change schema first, need a new config option)
|
||||||
|
- Identify component changes (touches more modules than originally thought)
|
||||||
|
- Assess impact more accurately (this is actually higher/lower value than estimated)
|
||||||
|
|
||||||
|
**Mechanism:** The spec generation prompt includes an explicit "re-evaluate frontmatter"
|
||||||
|
step. The spec output includes an updated frontmatter block. If scoring-relevant
|
||||||
|
fields changed, the skill flags it:
|
||||||
|
|
||||||
|
```
|
||||||
|
SCOPE CHANGE DETECTED:
|
||||||
|
idea-009 (Similar Issues Finder)
|
||||||
|
- effort: small → medium (needs embedding aggregation strategy)
|
||||||
|
- blocked-by: [] → [gate-embeddings-populated]
|
||||||
|
- components: +src/cli/commands/similar.rs (new file)
|
||||||
|
Previous score: 4.5 → New score: 3.0
|
||||||
|
Recommendation: Still top-3, but sequencing may change.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. During Implementation (Discovered Complexity)
|
||||||
|
|
||||||
|
An agent working on beads may discover the spec was wrong:
|
||||||
|
- "This requires a database migration I didn't anticipate"
|
||||||
|
- "This module doesn't expose the API I need"
|
||||||
|
|
||||||
|
**Mechanism:** When a bead is blocked or takes significantly longer than estimated,
|
||||||
|
the agent should update the idea's frontmatter. The skill detects the change on
|
||||||
|
next triage run via eval-hash comparison.
|
||||||
|
|
||||||
|
### 3. External Changes (Gate Completion, New Ideas)
|
||||||
|
|
||||||
|
When a gate completes or a new idea is added that changes the dependency graph:
|
||||||
|
- Gate 4 completes → 5 ideas become unblocked
|
||||||
|
- New idea added that's higher priority than current top-3
|
||||||
|
- Two ideas discovered to be duplicates
|
||||||
|
|
||||||
|
**Mechanism:** The skill detects these automatically by re-computing the full graph
|
||||||
|
on every run. The eval-hash tracks what the scoring fields looked like last time;
|
||||||
|
if they haven't changed but the SCORE changed (because a dependency was resolved),
|
||||||
|
the skill flags it as "newly unblocked."
|
||||||
|
|
||||||
|
### The eval-hash Field
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
eval-hash: "a1b2c3d4" # SHA-256 of: impact + effort + blocked-by + unlocks + requires
|
||||||
|
```
|
||||||
|
|
||||||
|
Computed by hashing the concatenation of all scoring-relevant fields. When the skill
|
||||||
|
runs, it compares:
|
||||||
|
- If eval-hash matches AND score is same → no change, skip
|
||||||
|
- If eval-hash matches BUT score changed → external change (dependency resolved)
|
||||||
|
- If eval-hash differs → item was modified, re-evaluate
|
||||||
|
|
||||||
|
This avoids re-announcing unchanged items on every run.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Skill Design
|
||||||
|
|
||||||
|
### Location
|
||||||
|
|
||||||
|
`.claude/skills/idea-triage/SKILL.md` (project-local)
|
||||||
|
|
||||||
|
### Trigger Phrases
|
||||||
|
|
||||||
|
- "triage ideas" / "what should I build next?"
|
||||||
|
- "idea triage" / "prioritize ideas"
|
||||||
|
- "what's the highest value work?"
|
||||||
|
- `/idea-triage`
|
||||||
|
|
||||||
|
### Workflow Phases
|
||||||
|
|
||||||
|
**Phase 1: INGEST**
|
||||||
|
- Glob docs/ideas/*.md and docs/issues/*.md
|
||||||
|
- Parse YAML frontmatter from each file
|
||||||
|
- Read docs/gates.yaml for capability status
|
||||||
|
- Collect: id, title, type, status, impact, effort, severity, autonomy,
|
||||||
|
blocked-by, unlocks, requires, has-spec, beads, eval-hash
|
||||||
|
|
||||||
|
**Phase 2: VALIDATE**
|
||||||
|
- Required fields present (id, title, type, status, impact, effort)
|
||||||
|
- All blocked-by IDs reference existing files
|
||||||
|
- All unlocks IDs reference existing files
|
||||||
|
- All requires entries exist in gates.yaml
|
||||||
|
- No dependency cycles (blocked-by graph is a DAG)
|
||||||
|
- Status transitions are valid (no "proposed" with beads linked)
|
||||||
|
- Output: list of validation errors/warnings
|
||||||
|
|
||||||
|
**Phase 3: EVALUATE (Scope Change Detection)**
|
||||||
|
- For each item, compute current eval-hash from scoring fields
|
||||||
|
- Compare against stored eval-hash in frontmatter
|
||||||
|
- If different: flag as SCOPE_CHANGED with field-level diff
|
||||||
|
- If same but score changed (due to external dep resolution): flag as NEWLY_UNBLOCKED
|
||||||
|
- If status is specced but has-spec is false: flag as INCONSISTENT
|
||||||
|
|
||||||
|
**Phase 4: SCORE**
|
||||||
|
- Resolve requires against gates.yaml (is the gate complete?)
|
||||||
|
- Resolve blocked-by against other items (is the blocker done?)
|
||||||
|
- Compute readiness: 0 if any hard blocker is unresolved, 1 otherwise
|
||||||
|
- Compute unlock count: count items whose blocked-by includes this ID
|
||||||
|
- Apply scoring formula:
|
||||||
|
- Ideas: impact_weight × (1 + 0.5 × unlock_count) × readiness
|
||||||
|
- Issues: severity_weight × 1.5 × (1 + 0.5 × unlock_count) × readiness
|
||||||
|
- Apply tiebreak: effort_weight, autonomy, created date
|
||||||
|
|
||||||
|
**Phase 5: SEQUENCE**
|
||||||
|
- Separate into: actionable (score > 0) vs blocked (score = 0)
|
||||||
|
- Among actionable: sort by score descending with tiebreak
|
||||||
|
- Among blocked: sort by "what-if score" (score if blockers were resolved)
|
||||||
|
- Compute unlock advisories: "completing X unblocks Y items worth Z total score"
|
||||||
|
|
||||||
|
**Phase 6: RECOMMEND**
|
||||||
|
Output structured report:
|
||||||
|
|
||||||
|
```
|
||||||
|
== IDEA TRIAGE ==
|
||||||
|
Run: 2026-02-09T14:30:00Z
|
||||||
|
Items: 22 (18 proposed, 2 accepted, 1 specced, 1 implemented)
|
||||||
|
|
||||||
|
RECOMMENDED SEQUENCE:
|
||||||
|
1. [idea-project-ergonomics] Multi-Project Ergonomics
|
||||||
|
impact:high effort:medium autonomy:full score:18.0
|
||||||
|
WHY FIRST: Unlocks 10 downstream ideas. Highest leverage.
|
||||||
|
COMPONENTS: src/core/config.rs, src/core/project.rs, src/cli/
|
||||||
|
|
||||||
|
2. [idea-009] Similar Issues Finder
|
||||||
|
impact:high effort:small autonomy:full score:4.5
|
||||||
|
WHY NEXT: Highest standalone impact. Ships in ~30 min.
|
||||||
|
UNLOCKS: idea-recurring-patterns
|
||||||
|
|
||||||
|
3. [idea-004] Stale Discussion Finder
|
||||||
|
impact:high effort:small autonomy:full score:3.0
|
||||||
|
WHY NEXT: Quick win, no dependencies, immediate user value.
|
||||||
|
|
||||||
|
BLOCKED (would rank high if unblocked):
|
||||||
|
idea-014 File Hotspots score-if-unblocked:4.5 BLOCKED BY: gate-4
|
||||||
|
idea-021 Knowledge Silos score-if-unblocked:3.0 BLOCKED BY: gate-4
|
||||||
|
UNLOCK ADVISORY: Completing gate-4 unblocks 5 items (combined: 15.0)
|
||||||
|
|
||||||
|
SCOPE CHANGES DETECTED:
|
||||||
|
idea-009: effort changed small→medium (eval-hash mismatch)
|
||||||
|
idea-017: now has spec (has-spec flipped to true)
|
||||||
|
|
||||||
|
NEWLY UNBLOCKED:
|
||||||
|
(none this run)
|
||||||
|
|
||||||
|
WARNINGS:
|
||||||
|
idea-016: status=proposed, unchanged for 30+ days
|
||||||
|
idea-008: blocked-by references "idea-gate4" which doesn't exist (typo?)
|
||||||
|
|
||||||
|
HEALTH:
|
||||||
|
Proposed: 18 | Accepted: 2 | Specced: 1 | Promoted: 0 | Implemented: 1
|
||||||
|
Blocked: 6 | Actionable: 16
|
||||||
|
Backlog runway at ~5/day: ~3 days
|
||||||
|
```
|
||||||
|
|
||||||
|
### What the Skill Does NOT Do
|
||||||
|
|
||||||
|
- **Never modifies files.** Read-only triage. The agent or human updates frontmatter.
|
||||||
|
Exception: the skill CAN update eval-hash after a triage run (opt-in).
|
||||||
|
- **Never creates beads.** That's plan-to-beads skill territory.
|
||||||
|
- **Never replaces bv.** Once work is in beads, bv --robot-triage handles execution
|
||||||
|
prioritization. This skill owns pre-commitment only.
|
||||||
|
- **Never generates specs.** That's a separate step with Claude/GPT.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### With Spec Generation
|
||||||
|
|
||||||
|
The spec generation prompt (separate from this skill) should include:
|
||||||
|
|
||||||
|
```
|
||||||
|
After generating the implementation spec, re-evaluate the idea's frontmatter:
|
||||||
|
1. Is the effort estimate still accurate? (small/medium/large/xlarge)
|
||||||
|
2. Did you discover new dependencies? (add to blocked-by)
|
||||||
|
3. Are there components not listed? (add to components)
|
||||||
|
4. Has the impact assessment changed?
|
||||||
|
5. Can an agent ship this autonomously? (autonomy: full/needs-design/needs-human)
|
||||||
|
|
||||||
|
Output an UPDATED frontmatter block at the end of the spec.
|
||||||
|
If any scoring field changed, explain what changed and why.
|
||||||
|
```
|
||||||
|
|
||||||
|
### With plan-to-beads
|
||||||
|
|
||||||
|
When promoting an idea to beads:
|
||||||
|
1. Run plan-to-beads on the spec
|
||||||
|
2. Capture the created bead IDs
|
||||||
|
3. Update the idea's frontmatter: status → promoted, beads → [bd-xxx, bd-yyy]
|
||||||
|
4. Run br sync --flush-only && git add .beads/
|
||||||
|
|
||||||
|
### With bv --robot-triage
|
||||||
|
|
||||||
|
These systems don't talk to each other directly. The boundary is:
|
||||||
|
- Idea triage skill → "build idea-009 next"
|
||||||
|
- Human/agent generates spec → plan-to-beads → beads created
|
||||||
|
- bv --robot-triage → "work on bd-xxx next"
|
||||||
|
- Beads close → human/agent updates idea frontmatter → idea triage re-runs
|
||||||
|
|
||||||
|
### With New Item Ingestion
|
||||||
|
|
||||||
|
When someone adds a new file to docs/ideas/ or docs/issues/:
|
||||||
|
- If it has valid frontmatter: picked up automatically on next triage run
|
||||||
|
- If it has no/invalid frontmatter: flagged in WARNINGS section
|
||||||
|
- Skill can suggest default frontmatter based on content analysis
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Failure Modes and Mitigations
|
||||||
|
|
||||||
|
### 1. Frontmatter Rot
|
||||||
|
**Risk:** Fields don't get updated. Status says "proposed" but it's actually shipped.
|
||||||
|
**Mitigation:** Cross-reference with beads. If an idea has beads and all beads are
|
||||||
|
closed, flag that the idea should be "implemented" even if frontmatter says otherwise.
|
||||||
|
The skill detects this inconsistency.
|
||||||
|
|
||||||
|
### 2. Score Gaming
|
||||||
|
**Risk:** Someone inflates impact or unlocks count to make their idea rank higher.
|
||||||
|
**Mitigation:** Unlocks are verified — the skill checks that the referenced items
|
||||||
|
actually have this idea in their blocked-by. Impact is subjective but reviewed during
|
||||||
|
spec generation (second opinion from a different model/session).
|
||||||
|
|
||||||
|
### 3. Stale Gates Registry
|
||||||
|
**Risk:** gate-4 is actually complete but gates.yaml wasn't updated.
|
||||||
|
**Mitigation:** Skill warns when a gate has been "partial" for a long time. Could
|
||||||
|
also probe the codebase (check if mr_file_changes ingestion code exists and has tests).
|
||||||
|
|
||||||
|
### 4. Circular Dependencies
|
||||||
|
**Risk:** A blocks B blocks A.
|
||||||
|
**Mitigation:** Phase 2 validation explicitly checks for cycles in the blocked-by
|
||||||
|
graph and reports them as errors.
|
||||||
|
|
||||||
|
### 5. Unlock Count Inflation
|
||||||
|
**Risk:** An item claims to unlock 20 things, making it score astronomically.
|
||||||
|
**Mitigation:** Unlock count is VERIFIED by checking reverse blocked-by references.
|
||||||
|
If idea-X says it unlocks idea-Y, but idea-Y's blocked-by doesn't include idea-X,
|
||||||
|
the claim is discounted. Both explicit unlocks and reverse blocked-by contribute to
|
||||||
|
the count, but unverified claims are flagged.
|
||||||
|
|
||||||
|
### 6. Scope Creep During Spec
|
||||||
|
**Risk:** Spec generation reveals the idea is actually 5× harder than estimated.
|
||||||
|
The score drops, but the human has already mentally committed.
|
||||||
|
**Mitigation:** The scope change detection makes this VISIBLE. The triage output
|
||||||
|
explicitly shows "effort changed small→xlarge, score dropped from 4.5 to 0.75."
|
||||||
|
Human can then decide: proceed anyway, or switch to a different top-3 pick.
|
||||||
|
|
||||||
|
### 7. Orphaned Ideas
|
||||||
|
**Risk:** Ideas get promoted to beads, beads get implemented, but the idea file
|
||||||
|
never gets updated. It sits in "promoted" forever.
|
||||||
|
**Mitigation:** Skill checks: for each idea with status=promoted, look up the
|
||||||
|
linked beads. If all beads are closed, flag: "idea-009 appears complete, update
|
||||||
|
status to implemented."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### Step 1: Create the Frontmatter Schema (this doc → applied to all files)
|
||||||
|
- Define the exact YAML schema (above)
|
||||||
|
- Create docs/gates.yaml
|
||||||
|
- Apply frontmatter to all 22 existing files in docs/ideas/ and docs/issues/
|
||||||
|
|
||||||
|
### Step 2: Build the Skill
|
||||||
|
- Create .claude/skills/idea-triage/SKILL.md
|
||||||
|
- Implement all 6 phases in the skill prompt
|
||||||
|
- The skill uses Glob, Read, and text processing — no external scripts needed
|
||||||
|
(25 files is small enough for Claude to process directly)
|
||||||
|
|
||||||
|
### Step 3: Test the System
|
||||||
|
- Run the skill against current files
|
||||||
|
- Verify scoring matches manual expectations
|
||||||
|
- Check that project-ergonomics ranks #1 (it should, due to unlock count)
|
||||||
|
- Verify blocked items score 0
|
||||||
|
- Check validation catches intentional errors
|
||||||
|
|
||||||
|
### Step 4: Run One Full Cycle
|
||||||
|
- Pick the top recommendation
|
||||||
|
- Generate a spec (separate session)
|
||||||
|
- Verify scope change detection works (spec should update frontmatter)
|
||||||
|
- Promote to beads via plan-to-beads
|
||||||
|
- Implement
|
||||||
|
- Verify completion detection works
|
||||||
|
|
||||||
|
### Step 5: Iterate
|
||||||
|
- Run triage again after implementation
|
||||||
|
- Verify newly unblocked items surface
|
||||||
|
- Adjust scoring weights if rankings feel wrong
|
||||||
|
- Add new ideas as they emerge
|
||||||
88
docs/ideas/bottlenecks.md
Normal file
88
docs/ideas/bottlenecks.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# Review Bottleneck Detector
|
||||||
|
|
||||||
|
- **Command:** `lore bottlenecks [--since <date>]`
|
||||||
|
- **Confidence:** 85%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — join MRs with first review note, compute percentiles
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
For MRs in a given time window, compute:
|
||||||
|
1. **Time to first review** — created_at to first non-author DiffNote
|
||||||
|
2. **Review cycles** — count of discussion resolution rounds
|
||||||
|
3. **Time to merge** — created_at to merged_at
|
||||||
|
|
||||||
|
Flag MRs above P90 thresholds as bottlenecks.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Review bottlenecks are the #1 developer productivity killer. Making them visible
|
||||||
|
and measurable is the first step to fixing them. This provides data for process
|
||||||
|
retrospectives.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `merge_requests` (created_at, merged_at, author_username)
|
||||||
|
- `notes` (note_type='DiffNote', author_username, created_at)
|
||||||
|
- `discussions` (resolved, resolvable)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Time to first review per MR
|
||||||
|
SELECT
|
||||||
|
mr.id,
|
||||||
|
mr.iid,
|
||||||
|
mr.title,
|
||||||
|
mr.author_username,
|
||||||
|
mr.created_at,
|
||||||
|
mr.merged_at,
|
||||||
|
p.path_with_namespace,
|
||||||
|
MIN(n.created_at) as first_review_at,
|
||||||
|
(MIN(n.created_at) - mr.created_at) / 3600000.0 as hours_to_first_review,
|
||||||
|
(mr.merged_at - mr.created_at) / 3600000.0 as hours_to_merge
|
||||||
|
FROM merge_requests mr
|
||||||
|
JOIN projects p ON mr.project_id = p.id
|
||||||
|
LEFT JOIN discussions d ON d.merge_request_id = mr.id
|
||||||
|
LEFT JOIN notes n ON n.discussion_id = d.id
|
||||||
|
AND n.note_type = 'DiffNote'
|
||||||
|
AND n.is_system = 0
|
||||||
|
AND n.author_username != mr.author_username
|
||||||
|
WHERE mr.created_at >= ?1
|
||||||
|
AND mr.state IN ('merged', 'opened')
|
||||||
|
GROUP BY mr.id
|
||||||
|
ORDER BY hours_to_first_review DESC NULLS FIRST;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Review Bottlenecks (last 30 days)
|
||||||
|
|
||||||
|
P50 time to first review: 4.2h
|
||||||
|
P90 time to first review: 28.1h
|
||||||
|
P50 time to merge: 2.1d
|
||||||
|
P90 time to merge: 8.3d
|
||||||
|
|
||||||
|
Slowest to review:
|
||||||
|
!234 Refactor auth 72h to first review (alice, still open)
|
||||||
|
!228 Database migration 48h to first review (bob, merged in 5d)
|
||||||
|
|
||||||
|
Most review cycles:
|
||||||
|
!234 Refactor auth 8 discussion threads, 4 resolved
|
||||||
|
!225 API versioning 6 discussion threads, 6 resolved
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Doesn't capture review done outside GitLab (Slack, in-person)
|
||||||
|
- DiffNote timestamp != when reviewer started reading
|
||||||
|
- Large MRs naturally take longer; no size normalization
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore bottlenecks --reviewer alice` — how fast does alice review?
|
||||||
|
- Per-project comparison: which project has the fastest review cycle?
|
||||||
|
- Trend line: is review speed improving or degrading over time?
|
||||||
77
docs/ideas/churn.md
Normal file
77
docs/ideas/churn.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# MR Churn Analysis
|
||||||
|
|
||||||
|
- **Command:** `lore churn [--since <date>]`
|
||||||
|
- **Confidence:** 72%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — multi-table aggregation with composite scoring
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
For merged MRs, compute a "contentiousness score" based on: number of review
|
||||||
|
discussions, number of DiffNotes, resolution cycles, file count. Flag high-churn
|
||||||
|
MRs as candidates for architectural review.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
High-churn MRs often indicate architectural disagreements, unclear requirements,
|
||||||
|
or code that's hard to review. Surfacing them post-merge enables retrospectives
|
||||||
|
and identifies areas that need better design upfront.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `merge_requests` (state='merged')
|
||||||
|
- `discussions` (merge_request_id, resolved, resolvable)
|
||||||
|
- `notes` (note_type='DiffNote', discussion_id)
|
||||||
|
- `mr_file_changes` (file count per MR)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
mr.iid,
|
||||||
|
mr.title,
|
||||||
|
mr.author_username,
|
||||||
|
p.path_with_namespace,
|
||||||
|
COUNT(DISTINCT d.id) as discussion_count,
|
||||||
|
COUNT(DISTINCT CASE WHEN n.note_type = 'DiffNote' THEN n.id END) as diffnote_count,
|
||||||
|
COUNT(DISTINCT CASE WHEN d.resolvable = 1 AND d.resolved = 1 THEN d.id END) as resolved_threads,
|
||||||
|
COUNT(DISTINCT mfc.id) as files_changed,
|
||||||
|
-- Composite score: normalize each metric and weight
|
||||||
|
(COUNT(DISTINCT d.id) * 2 + COUNT(DISTINCT n.id) + COUNT(DISTINCT mfc.id)) as churn_score
|
||||||
|
FROM merge_requests mr
|
||||||
|
JOIN projects p ON mr.project_id = p.id
|
||||||
|
LEFT JOIN discussions d ON d.merge_request_id = mr.id AND d.noteable_type = 'MergeRequest'
|
||||||
|
LEFT JOIN notes n ON n.discussion_id = d.id AND n.is_system = 0
|
||||||
|
LEFT JOIN mr_file_changes mfc ON mfc.merge_request_id = mr.id
|
||||||
|
WHERE mr.state = 'merged'
|
||||||
|
AND mr.merged_at >= ?1
|
||||||
|
GROUP BY mr.id
|
||||||
|
ORDER BY churn_score DESC
|
||||||
|
LIMIT ?2;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
High-Churn MRs (last 90 days)
|
||||||
|
|
||||||
|
MR Discussions DiffNotes Files Score Title
|
||||||
|
!234 12 28 8 60 Refactor auth middleware
|
||||||
|
!225 8 19 5 39 API versioning v2
|
||||||
|
!218 6 15 12 39 Database schema migration
|
||||||
|
!210 5 8 3 21 Update logging framework
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- High discussion count could mean thorough review, not contention
|
||||||
|
- Composite scoring weights are arbitrary; needs calibration per team
|
||||||
|
- Large MRs naturally score higher regardless of contention
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- Normalize by file count (discussions per file changed)
|
||||||
|
- Compare against team averages (flag outliers, not absolute values)
|
||||||
|
- `lore churn --author alice` — which of alice's MRs generate the most discussion?
|
||||||
73
docs/ideas/closure-gaps.md
Normal file
73
docs/ideas/closure-gaps.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# MR-to-Issue Closure Gap
|
||||||
|
|
||||||
|
- **Command:** `lore closure-gaps`
|
||||||
|
- **Confidence:** 88%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** low — single join query
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Find entity_references where reference_type='closes' AND the target issue is still
|
||||||
|
open AND the source MR is merged. These represent broken auto-close links where a
|
||||||
|
merge should have closed an issue but didn't.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Simple, definitive, actionable. If a merged MR says "closes #42" but #42 is still
|
||||||
|
open, something is wrong. Either auto-close failed (wrong target branch), the
|
||||||
|
reference was incorrect, or the issue needs manual attention.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `entity_references` (reference_type='closes')
|
||||||
|
- `merge_requests` (state='merged')
|
||||||
|
- `issues` (state='opened')
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
mr.iid as mr_iid,
|
||||||
|
mr.title as mr_title,
|
||||||
|
mr.merged_at,
|
||||||
|
mr.target_branch,
|
||||||
|
i.iid as issue_iid,
|
||||||
|
i.title as issue_title,
|
||||||
|
i.state as issue_state,
|
||||||
|
p.path_with_namespace
|
||||||
|
FROM entity_references er
|
||||||
|
JOIN merge_requests mr ON er.source_entity_type = 'merge_request'
|
||||||
|
AND er.source_entity_id = mr.id
|
||||||
|
JOIN issues i ON er.target_entity_type = 'issue'
|
||||||
|
AND er.target_entity_id = i.id
|
||||||
|
JOIN projects p ON er.project_id = p.id
|
||||||
|
WHERE er.reference_type = 'closes'
|
||||||
|
AND mr.state = 'merged'
|
||||||
|
AND i.state = 'opened';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Closure Gaps — merged MRs that didn't close their referenced issues
|
||||||
|
|
||||||
|
group/backend !234 merged 3d ago → #42 still OPEN
|
||||||
|
"Refactor auth middleware" should have closed "Login timeout bug"
|
||||||
|
Target branch: develop (default: main) — possible branch mismatch
|
||||||
|
|
||||||
|
group/frontend !45 merged 1w ago → #38 still OPEN
|
||||||
|
"Update dashboard" should have closed "Dashboard layout broken"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Could be intentional (MR merged to wrong branch, issue tracked across branches)
|
||||||
|
- Cross-project references may not be resolvable if target project not synced
|
||||||
|
- GitLab auto-close only works when merging to default branch
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- Flag likely cause: branch mismatch (target_branch != project.default_branch)
|
||||||
|
- `lore closure-gaps --auto-close` — actually close the issues via API (dangerous, needs confirmation)
|
||||||
101
docs/ideas/collaboration.md
Normal file
101
docs/ideas/collaboration.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Author Collaboration Network
|
||||||
|
|
||||||
|
- **Command:** `lore collaboration [--since <date>]`
|
||||||
|
- **Confidence:** 70%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — self-join on notes, graph construction
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Build a weighted graph of author pairs: (author_A, author_B, weight) where weight =
|
||||||
|
number of times A reviewed B's MR + B reviewed A's MR + they both commented on the
|
||||||
|
same entity.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Reveals team structure empirically. Shows who collaborates across team boundaries
|
||||||
|
and where knowledge transfer happens. Useful for re-orgs, onboarding planning,
|
||||||
|
and identifying isolated team members.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `merge_requests` (author_username)
|
||||||
|
- `notes` (author_username, note_type='DiffNote')
|
||||||
|
- `discussions` (for co-participation)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Review relationships: who reviews whose MRs
|
||||||
|
SELECT
|
||||||
|
mr.author_username as author,
|
||||||
|
n.author_username as reviewer,
|
||||||
|
COUNT(*) as review_count
|
||||||
|
FROM merge_requests mr
|
||||||
|
JOIN discussions d ON d.merge_request_id = mr.id
|
||||||
|
JOIN notes n ON n.discussion_id = d.id
|
||||||
|
WHERE n.note_type = 'DiffNote'
|
||||||
|
AND n.is_system = 0
|
||||||
|
AND n.author_username != mr.author_username
|
||||||
|
AND mr.created_at >= ?1
|
||||||
|
GROUP BY mr.author_username, n.author_username;
|
||||||
|
|
||||||
|
-- Co-participation: who comments on the same entities
|
||||||
|
WITH entity_participants AS (
|
||||||
|
SELECT
|
||||||
|
COALESCE(d.issue_id, d.merge_request_id) as entity_id,
|
||||||
|
d.noteable_type,
|
||||||
|
n.author_username
|
||||||
|
FROM discussions d
|
||||||
|
JOIN notes n ON n.discussion_id = d.id
|
||||||
|
WHERE n.is_system = 0
|
||||||
|
AND n.created_at >= ?1
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
a.author_username as person_a,
|
||||||
|
b.author_username as person_b,
|
||||||
|
COUNT(DISTINCT a.entity_id) as shared_entities
|
||||||
|
FROM entity_participants a
|
||||||
|
JOIN entity_participants b
|
||||||
|
ON a.entity_id = b.entity_id
|
||||||
|
AND a.noteable_type = b.noteable_type
|
||||||
|
AND a.author_username < b.author_username -- avoid duplicates
|
||||||
|
GROUP BY a.author_username, b.author_username;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Formats
|
||||||
|
|
||||||
|
### JSON (for further analysis)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nodes": ["alice", "bob", "charlie"],
|
||||||
|
"edges": [
|
||||||
|
{ "source": "alice", "target": "bob", "reviews": 15, "co_participated": 8 },
|
||||||
|
{ "source": "bob", "target": "charlie", "reviews": 3, "co_participated": 12 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Human
|
||||||
|
```
|
||||||
|
Collaboration Network (last 90 days)
|
||||||
|
|
||||||
|
alice <-> bob 15 reviews, 8 shared discussions [strong]
|
||||||
|
bob <-> charlie 3 reviews, 12 shared discussions [moderate]
|
||||||
|
alice <-> charlie 1 review, 2 shared discussions [weak]
|
||||||
|
dave <-> (none) 0 reviews, 0 shared discussions [isolated]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Interpretation requires context; high collaboration might mean dependency
|
||||||
|
- Doesn't capture collaboration outside GitLab
|
||||||
|
- Self-join can be slow with many notes
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore collaboration --format dot` — GraphViz network diagram
|
||||||
|
- `lore collaboration --isolated` — find team members with no collaboration edges
|
||||||
|
- Team boundary detection via graph clustering algorithms
|
||||||
86
docs/ideas/contributors.md
Normal file
86
docs/ideas/contributors.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
# Contributor Heatmap
|
||||||
|
|
||||||
|
- **Command:** `lore contributors [--since <date>]`
|
||||||
|
- **Confidence:** 88%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — multiple aggregation queries
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Rank team members by activity across configurable time windows (7d, 30d, 90d). Shows
|
||||||
|
issues authored, MRs authored, MRs merged, review comments made, discussions
|
||||||
|
participated in.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Team leads constantly ask "who's been active?" or "who's contributing to reviews?"
|
||||||
|
This answers it from local data without GitLab Premium analytics. Also useful for
|
||||||
|
identifying team members who may be overloaded or disengaged.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `issues` (author_username, created_at)
|
||||||
|
- `merge_requests` (author_username, created_at, merged_at)
|
||||||
|
- `notes` (author_username, created_at, note_type, is_system)
|
||||||
|
- `discussions` (for participation counting)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Combined activity per author
|
||||||
|
WITH activity AS (
|
||||||
|
SELECT author_username, 'issue_authored' as activity_type, created_at
|
||||||
|
FROM issues WHERE created_at >= ?1
|
||||||
|
UNION ALL
|
||||||
|
SELECT author_username, 'mr_authored', created_at
|
||||||
|
FROM merge_requests WHERE created_at >= ?1
|
||||||
|
UNION ALL
|
||||||
|
SELECT author_username, 'mr_merged', merged_at
|
||||||
|
FROM merge_requests WHERE merged_at >= ?1 AND state = 'merged'
|
||||||
|
UNION ALL
|
||||||
|
SELECT author_username, 'review_comment', created_at
|
||||||
|
FROM notes WHERE created_at >= ?1 AND note_type = 'DiffNote' AND is_system = 0
|
||||||
|
UNION ALL
|
||||||
|
SELECT author_username, 'discussion_comment', created_at
|
||||||
|
FROM notes WHERE created_at >= ?1 AND note_type != 'DiffNote' AND is_system = 0
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
author_username,
|
||||||
|
COUNT(*) FILTER (WHERE activity_type = 'issue_authored') as issues,
|
||||||
|
COUNT(*) FILTER (WHERE activity_type = 'mr_authored') as mrs_authored,
|
||||||
|
COUNT(*) FILTER (WHERE activity_type = 'mr_merged') as mrs_merged,
|
||||||
|
COUNT(*) FILTER (WHERE activity_type = 'review_comment') as reviews,
|
||||||
|
COUNT(*) FILTER (WHERE activity_type = 'discussion_comment') as comments,
|
||||||
|
COUNT(*) as total
|
||||||
|
FROM activity
|
||||||
|
GROUP BY author_username
|
||||||
|
ORDER BY total DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: SQLite doesn't support FILTER — use SUM(CASE WHEN ... THEN 1 ELSE 0 END).
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Contributors (last 30 days)
|
||||||
|
|
||||||
|
Username Issues MRs Merged Reviews Comments Total
|
||||||
|
alice 3 8 7 23 12 53
|
||||||
|
bob 1 5 4 31 8 49
|
||||||
|
charlie 5 3 2 4 15 29
|
||||||
|
dave 0 1 0 2 3 6
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Could be used for surveillance; frame as team health, not individual tracking
|
||||||
|
- Activity volume != productivity (one thoughtful review > ten "LGTM"s)
|
||||||
|
- Doesn't capture work done outside GitLab
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore contributors --project group/backend` — scoped to project
|
||||||
|
- `lore contributors --type reviews` — focus on review activity only
|
||||||
|
- Trend comparison: `--compare 30d,90d` shows velocity changes
|
||||||
94
docs/ideas/decisions.md
Normal file
94
docs/ideas/decisions.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
# Decision Archaeology
|
||||||
|
|
||||||
|
- **Command:** `lore decisions <query>`
|
||||||
|
- **Confidence:** 82%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — search pipeline + regex pattern matching on notes
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Search for discussion notes that contain decision-making language. Use the existing
|
||||||
|
search pipeline but boost notes containing patterns like "decided", "agreed",
|
||||||
|
"will go with", "tradeoff", "because we", "rationale", "the approach is", "we chose".
|
||||||
|
Return the surrounding discussion context.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
This is gitlore's unique value proposition — "why was this decision made?" is the
|
||||||
|
question that no other tool answers well. Architecture Decision Records are rarely
|
||||||
|
maintained; the real decisions live in discussion threads. This mines them.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `documents` + search pipeline (for finding relevant entities)
|
||||||
|
- `notes` (body text for pattern matching)
|
||||||
|
- `discussions` (for thread context)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Run existing hybrid search to find entities matching the query topic
|
||||||
|
2. For each result entity, query all discussion notes
|
||||||
|
3. Score each note against decision-language patterns:
|
||||||
|
- Strong signals (weight 3): "decided to", "agreed on", "the decision is",
|
||||||
|
"we will go with", "approved approach"
|
||||||
|
- Medium signals (weight 2): "tradeoff", "because", "rationale", "chosen",
|
||||||
|
"opted for", "rejected", "alternative"
|
||||||
|
- Weak signals (weight 1): "should we", "proposal", "option A", "option B",
|
||||||
|
"pros and cons"
|
||||||
|
4. Return notes scoring above threshold, with surrounding context (previous and
|
||||||
|
next note in discussion thread)
|
||||||
|
5. Sort by: search relevance * decision score
|
||||||
|
```
|
||||||
|
|
||||||
|
### Decision Patterns (regex)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
const STRONG_PATTERNS: &[&str] = &[
|
||||||
|
r"(?i)\b(decided|agreed|approved)\s+(to|on|that)\b",
|
||||||
|
r"(?i)\bthe\s+(decision|approach|plan)\s+is\b",
|
||||||
|
r"(?i)\bwe('ll| will| are going to)\s+(go with|use|implement)\b",
|
||||||
|
r"(?i)\blet'?s\s+(go with|use|do)\b",
|
||||||
|
];
|
||||||
|
|
||||||
|
const MEDIUM_PATTERNS: &[&str] = &[
|
||||||
|
r"(?i)\b(tradeoff|trade-off|rationale|because we|opted for)\b",
|
||||||
|
r"(?i)\b(rejected|ruled out|won't work|not viable)\b",
|
||||||
|
r"(?i)\b(chosen|selected|picked)\b.{0,20}\b(over|instead of)\b",
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Decisions related to "authentication"
|
||||||
|
|
||||||
|
group/backend !234 — "Refactor auth middleware"
|
||||||
|
Discussion #a1b2c3 (alice, 3w ago):
|
||||||
|
"We decided to use JWT with short-lived tokens instead of session cookies.
|
||||||
|
The tradeoff is more complexity in the refresh flow, but we get stateless
|
||||||
|
auth which scales better."
|
||||||
|
Decision confidence: HIGH (3 strong pattern matches)
|
||||||
|
|
||||||
|
group/backend #42 — "Auth architecture review"
|
||||||
|
Discussion #d4e5f6 (bob, 2mo ago):
|
||||||
|
"After discussing with the security team, we'll go with bcrypt for password
|
||||||
|
hashing. Argon2 is theoretically better but bcrypt has wider library support."
|
||||||
|
Decision confidence: HIGH (2 strong pattern matches)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Pattern matching is imperfect; may miss decisions phrased differently
|
||||||
|
- May surface "discussion about deciding" rather than actual decisions
|
||||||
|
- Non-English discussions won't match
|
||||||
|
- Requires good search results as input (garbage in, garbage out)
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore decisions --recent` — decisions made in last 30 days
|
||||||
|
- `lore decisions --author alice` — decisions made by specific person
|
||||||
|
- Export as ADR (Architecture Decision Record) format
|
||||||
|
- Combine with timeline for chronological decision history
|
||||||
131
docs/ideas/digest.md
Normal file
131
docs/ideas/digest.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# "What Changed?" Digest
|
||||||
|
|
||||||
|
- **Command:** `lore digest --since <date>`
|
||||||
|
- **Confidence:** 93%
|
||||||
|
- **Tier:** 1
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — multiple queries across event tables, formatting logic
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Generate a structured summary of all activity since a given date: issues
|
||||||
|
opened/closed, MRs merged, labels changed, milestones updated, key discussions.
|
||||||
|
Group by project and sort by significance (state changes > merges > label changes >
|
||||||
|
new comments).
|
||||||
|
|
||||||
|
Default `--since` is 1 day (last 24 hours). Supports `7d`, `2w`, `YYYY-MM-DD`.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
"What happened while I was on PTO?" is the most universal developer question. This
|
||||||
|
is a killer feature that leverages ALL the event data gitlore has ingested. No other
|
||||||
|
local tool provides this.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `resource_state_events` (opened/closed/merged/reopened)
|
||||||
|
- `resource_label_events` (label add/remove)
|
||||||
|
- `resource_milestone_events` (milestone add/remove)
|
||||||
|
- `merge_requests` (merged_at for merge events)
|
||||||
|
- `issues` (created_at for new issues)
|
||||||
|
- `discussions` (last_note_at for active discussions)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Parse --since into ms epoch timestamp
|
||||||
|
2. Query each event table WHERE created_at >= since
|
||||||
|
3. Query new issues WHERE created_at >= since
|
||||||
|
4. Query merged MRs WHERE merged_at >= since
|
||||||
|
5. Query active discussions WHERE last_note_at >= since
|
||||||
|
6. Group all events by project
|
||||||
|
7. Within each project, sort by: state changes first, then merges, then labels
|
||||||
|
8. Format as human-readable sections or robot JSON
|
||||||
|
```
|
||||||
|
|
||||||
|
### SQL Queries
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- State changes in window
|
||||||
|
SELECT rse.*, i.iid as issue_iid, mr.iid as mr_iid,
|
||||||
|
COALESCE(i.title, mr.title) as title,
|
||||||
|
p.path_with_namespace
|
||||||
|
FROM resource_state_events rse
|
||||||
|
LEFT JOIN issues i ON rse.issue_id = i.id
|
||||||
|
LEFT JOIN merge_requests mr ON rse.merge_request_id = mr.id
|
||||||
|
JOIN projects p ON rse.project_id = p.id
|
||||||
|
WHERE rse.created_at >= ?1
|
||||||
|
ORDER BY rse.created_at DESC;
|
||||||
|
|
||||||
|
-- Newly merged MRs
|
||||||
|
SELECT mr.iid, mr.title, mr.author_username, mr.merged_at,
|
||||||
|
p.path_with_namespace
|
||||||
|
FROM merge_requests mr
|
||||||
|
JOIN projects p ON mr.project_id = p.id
|
||||||
|
WHERE mr.merged_at >= ?1
|
||||||
|
ORDER BY mr.merged_at DESC;
|
||||||
|
|
||||||
|
-- New issues
|
||||||
|
SELECT i.iid, i.title, i.author_username, i.created_at,
|
||||||
|
p.path_with_namespace
|
||||||
|
FROM issues i
|
||||||
|
JOIN projects p ON i.project_id = p.id
|
||||||
|
WHERE i.created_at >= ?1
|
||||||
|
ORDER BY i.created_at DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
=== What Changed (last 7 days) ===
|
||||||
|
|
||||||
|
group/backend (12 events)
|
||||||
|
Merged:
|
||||||
|
!234 Refactor auth middleware (alice, 2d ago)
|
||||||
|
!231 Fix connection pool leak (bob, 5d ago)
|
||||||
|
Closed:
|
||||||
|
#89 Login timeout on slow networks (closed by alice, 3d ago)
|
||||||
|
Opened:
|
||||||
|
#95 Rate limiting returns 500 (charlie, 1d ago)
|
||||||
|
Labels:
|
||||||
|
#90 +priority::high (dave, 4d ago)
|
||||||
|
|
||||||
|
group/frontend (3 events)
|
||||||
|
Merged:
|
||||||
|
!45 Update dashboard layout (eve, 6d ago)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Robot Mode Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"since": "2025-01-20T00:00:00Z",
|
||||||
|
"projects": [
|
||||||
|
{
|
||||||
|
"path": "group/backend",
|
||||||
|
"merged": [ { "iid": 234, "title": "...", "author": "alice" } ],
|
||||||
|
"closed": [ { "iid": 89, "title": "...", "actor": "alice" } ],
|
||||||
|
"opened": [ { "iid": 95, "title": "...", "author": "charlie" } ],
|
||||||
|
"label_changes": [ { "iid": 90, "label": "priority::high", "action": "add" } ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"summary": { "total_events": 15, "projects_active": 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Can be overwhelming for very active repos; needs `--limit` per category
|
||||||
|
- Doesn't capture nuance (a 200-comment MR merge is more significant than a typo fix)
|
||||||
|
- Only shows what gitlore has synced; stale data = stale digest
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore digest --author alice` — personal activity digest
|
||||||
|
- `lore digest --project group/backend` — single project scope
|
||||||
|
- `lore digest --format markdown` — paste-ready for Slack/email
|
||||||
|
- Combine with weekly-digest for scheduled summaries
|
||||||
120
docs/ideas/experts.md
Normal file
120
docs/ideas/experts.md
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
# Who Knows About X?
|
||||||
|
|
||||||
|
- **Command:** `lore experts <path-or-topic>`
|
||||||
|
- **Confidence:** 92%
|
||||||
|
- **Tier:** 1
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — two query paths (file-based, topic-based)
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Given a file path, find people who have authored MRs touching that file, left
|
||||||
|
DiffNotes on that file, or discussed issues referencing that file. Given a topic
|
||||||
|
string, use search to find relevant entities then extract the active participants.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
"Who should I ask about the auth module?" is one of the most common questions in
|
||||||
|
large teams. This answers it empirically from actual contribution and review data.
|
||||||
|
No guessing, no out-of-date wiki pages.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `mr_file_changes` (new_path, merge_request_id) — who changed the file
|
||||||
|
- `notes` (position_new_path, author_username) — who reviewed the file
|
||||||
|
- `merge_requests` (author_username) — MR authorship
|
||||||
|
- `documents` + search pipeline — for topic-based queries
|
||||||
|
- `discussions` + `notes` — for participant extraction
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
### Path Mode: `lore experts src/auth/`
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Query mr_file_changes WHERE new_path LIKE 'src/auth/%'
|
||||||
|
2. Join merge_requests to get author_username for each MR
|
||||||
|
3. Query notes WHERE position_new_path LIKE 'src/auth/%'
|
||||||
|
4. Collect all usernames with activity counts
|
||||||
|
5. Rank by: MR authorship (weight 3) + DiffNote authorship (weight 2) + discussion participation (weight 1)
|
||||||
|
6. Apply recency decay (recent activity weighted higher)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Topic Mode: `lore experts "authentication timeout"`
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Run existing hybrid search for the topic
|
||||||
|
2. Collect top N document results
|
||||||
|
3. For each document, extract author_username
|
||||||
|
4. For each document's entity, query discussions and collect note authors
|
||||||
|
5. Rank by frequency and recency
|
||||||
|
```
|
||||||
|
|
||||||
|
### SQL (Path Mode)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Authors who changed files matching pattern
|
||||||
|
SELECT mr.author_username, COUNT(*) as changes, MAX(mr.merged_at) as last_active
|
||||||
|
FROM mr_file_changes mfc
|
||||||
|
JOIN merge_requests mr ON mfc.merge_request_id = mr.id
|
||||||
|
WHERE mfc.new_path LIKE ?1
|
||||||
|
AND mr.state = 'merged'
|
||||||
|
GROUP BY mr.author_username
|
||||||
|
ORDER BY changes DESC;
|
||||||
|
|
||||||
|
-- Reviewers who commented on files matching pattern
|
||||||
|
SELECT n.author_username, COUNT(*) as reviews, MAX(n.created_at) as last_active
|
||||||
|
FROM notes n
|
||||||
|
WHERE n.position_new_path LIKE ?1
|
||||||
|
AND n.note_type = 'DiffNote'
|
||||||
|
AND n.is_system = 0
|
||||||
|
GROUP BY n.author_username
|
||||||
|
ORDER BY reviews DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
Experts for: src/auth/
|
||||||
|
|
||||||
|
alice 12 changes, 8 reviews (last active 3d ago) [top contributor]
|
||||||
|
bob 3 changes, 15 reviews (last active 1d ago) [top reviewer]
|
||||||
|
charlie 5 changes, 2 reviews (last active 2w ago)
|
||||||
|
dave 1 change, 0 reviews (last active 3mo ago) [stale]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Robot Mode Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"query": "src/auth/",
|
||||||
|
"query_type": "path",
|
||||||
|
"experts": [
|
||||||
|
{
|
||||||
|
"username": "alice",
|
||||||
|
"changes": 12,
|
||||||
|
"reviews": 8,
|
||||||
|
"discussions": 3,
|
||||||
|
"score": 62,
|
||||||
|
"last_active": "2025-01-25T10:00:00Z",
|
||||||
|
"role": "top_contributor"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Historical data may be stale (people leave teams, change roles)
|
||||||
|
- Path mode requires `mr_file_changes` to be populated (Gate 4 ingestion)
|
||||||
|
- Topic mode quality depends on search quality
|
||||||
|
- Doesn't account for org chart / actual ownership
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore experts --since 90d` — recency filter
|
||||||
|
- `lore experts --min-activity 3` — noise filter
|
||||||
|
- Combine with `lore silos` to highlight when an expert is the ONLY expert
|
||||||
75
docs/ideas/graph.md
Normal file
75
docs/ideas/graph.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Entity Relationship Explorer
|
||||||
|
|
||||||
|
- **Command:** `lore graph <entity-type> <iid>`
|
||||||
|
- **Confidence:** 80%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — BFS traversal (similar to timeline expand), output formatting
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Given an issue or MR, traverse `entity_references` and display all connected
|
||||||
|
entities with relationship types and depths. Output as tree, JSON, or Mermaid diagram.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
The entity_references graph is already built (Gate 2) but has no dedicated
|
||||||
|
exploration command. Timeline shows events over time; this shows the relationship
|
||||||
|
structure. "What's connected to this issue?" is a different question from "what
|
||||||
|
happened to this issue?"
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `entity_references` (source/target entity, reference_type)
|
||||||
|
- `issues` / `merge_requests` (for entity context)
|
||||||
|
- Timeline expand stage already implements BFS over this graph
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Resolve entity type + iid to local ID
|
||||||
|
2. BFS over entity_references:
|
||||||
|
- Follow source→target AND target→source (bidirectional)
|
||||||
|
- Track depth (--depth flag, default 2)
|
||||||
|
- Track reference_type for edge labels
|
||||||
|
3. Hydrate each discovered entity with title, state, URL
|
||||||
|
4. Format as tree / JSON / Mermaid
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output (Tree)
|
||||||
|
|
||||||
|
```
|
||||||
|
#42 Login timeout bug (CLOSED)
|
||||||
|
├── closes ── !234 Refactor auth middleware (MERGED)
|
||||||
|
│ ├── mentioned ── #38 Connection timeout in auth flow (CLOSED)
|
||||||
|
│ └── mentioned ── #51 Token refresh improvements (OPEN)
|
||||||
|
├── related ── #45 Auth module documentation (OPEN)
|
||||||
|
└── mentioned ── !228 Database migration (MERGED)
|
||||||
|
└── closes ── #35 Schema version drift (CLOSED)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Mermaid Output
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
I42["#42 Login timeout"] -->|closes| MR234["!234 Refactor auth"]
|
||||||
|
MR234 -->|mentioned| I38["#38 Connection timeout"]
|
||||||
|
MR234 -->|mentioned| I51["#51 Token refresh"]
|
||||||
|
I42 -->|related| I45["#45 Auth docs"]
|
||||||
|
I42 -->|mentioned| MR228["!228 DB migration"]
|
||||||
|
MR228 -->|closes| I35["#35 Schema drift"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Overlaps somewhat with timeline (but different focus: structure vs chronology)
|
||||||
|
- High fan-out for popular entities (need depth + limit controls)
|
||||||
|
- Unresolved cross-project references appear as dead ends
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore graph --format dot` — GraphViz DOT output
|
||||||
|
- `lore graph --format mermaid` — Mermaid diagram
|
||||||
|
- `lore graph --include-discussions` — show discussion threads as nodes
|
||||||
|
- Interactive HTML visualization (future web UI)
|
||||||
70
docs/ideas/hotspots.md
Normal file
70
docs/ideas/hotspots.md
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
# File Hotspot Report
|
||||||
|
|
||||||
|
- **Command:** `lore hotspots [--since <date>]`
|
||||||
|
- **Confidence:** 85%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** low — single query on mr_file_changes (requires Gate 4 population)
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Rank files by frequency of appearance in merged MRs over a time window. Show
|
||||||
|
change_type breakdown (modified vs added vs deleted). Optionally filter by project.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Hot files are where bugs live. This is a proven engineering metric (see "Your Code
|
||||||
|
as a Crime Scene" by Adam Tornhill). High-churn files deserve extra test coverage,
|
||||||
|
better documentation, and architectural review.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
- `mr_file_changes` (new_path, change_type, merge_request_id) — needs Gate 4 population
|
||||||
|
- `merge_requests` (merged_at, state='merged')
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
mfc.new_path,
|
||||||
|
p.path_with_namespace,
|
||||||
|
COUNT(*) as total_changes,
|
||||||
|
SUM(CASE WHEN mfc.change_type = 'modified' THEN 1 ELSE 0 END) as modifications,
|
||||||
|
SUM(CASE WHEN mfc.change_type = 'added' THEN 1 ELSE 0 END) as additions,
|
||||||
|
SUM(CASE WHEN mfc.change_type = 'deleted' THEN 1 ELSE 0 END) as deletions,
|
||||||
|
SUM(CASE WHEN mfc.change_type = 'renamed' THEN 1 ELSE 0 END) as renames,
|
||||||
|
COUNT(DISTINCT mr.author_username) as unique_authors
|
||||||
|
FROM mr_file_changes mfc
|
||||||
|
JOIN merge_requests mr ON mfc.merge_request_id = mr.id
|
||||||
|
JOIN projects p ON mfc.project_id = p.id
|
||||||
|
WHERE mr.state = 'merged'
|
||||||
|
AND mr.merged_at >= ?1
|
||||||
|
GROUP BY mfc.new_path, p.path_with_namespace
|
||||||
|
ORDER BY total_changes DESC
|
||||||
|
LIMIT ?2;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
File Hotspots (last 90 days, top 20)
|
||||||
|
|
||||||
|
File Changes Authors Type Breakdown
|
||||||
|
src/auth/middleware.rs 18 4 14 mod, 3 add, 1 del
|
||||||
|
src/api/routes.rs 15 3 12 mod, 2 add, 1 rename
|
||||||
|
src/db/migrations.rs 12 2 8 mod, 4 add
|
||||||
|
tests/integration/auth_test.rs 11 3 9 mod, 2 add
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Requires `mr_file_changes` to be populated (Gate 4 ingestion)
|
||||||
|
- Doesn't distinguish meaningful changes from trivial ones (formatting, imports)
|
||||||
|
- Configuration files (CI, Cargo.toml) will rank high but aren't risky
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore hotspots --exclude "*.toml,*.yml"` — filter out config files
|
||||||
|
- `lore hotspots --dir src/auth/` — scope to directory
|
||||||
|
- Combine with `lore silos` for risk scoring: high churn + bus factor 1 = critical
|
||||||
|
- Complexity trend: correlate with discussion count (churn + many discussions = problematic)
|
||||||
69
docs/ideas/idle.md
Normal file
69
docs/ideas/idle.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Idle Work Detector
|
||||||
|
|
||||||
|
- **Command:** `lore idle [--days <N>] [--labels <pattern>]`
|
||||||
|
- **Confidence:** 73%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — label event querying with configurable patterns
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Find entities that received an "in progress" or similar label but have had no
|
||||||
|
discussion activity for N days. Cross-reference with assignee to show who might
|
||||||
|
have forgotten about something.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Forgotten WIP is invisible waste. Developers start work, get pulled to something
|
||||||
|
urgent, and the original task sits idle. This makes it visible before it becomes
|
||||||
|
a problem.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `resource_label_events` (label_name, action='add', created_at)
|
||||||
|
- `discussions` (last_note_at for entity activity)
|
||||||
|
- `issues` / `merge_requests` (state, assignees)
|
||||||
|
- `issue_assignees` / `mr_assignees`
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Query resource_label_events for labels matching "in progress" patterns
|
||||||
|
Default patterns: "in-progress", "in_progress", "doing", "wip",
|
||||||
|
"workflow::in-progress", "status::in-progress"
|
||||||
|
Configurable via --labels flag
|
||||||
|
2. For each entity with an "in progress" label still applied:
|
||||||
|
a. Check if the label was subsequently removed (if so, skip)
|
||||||
|
b. Get last_note_at from discussions for that entity
|
||||||
|
c. Flag if last_note_at is older than threshold
|
||||||
|
3. Join with assignees for attribution
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Idle Work (labeled "in progress" but no activity for 14+ days)
|
||||||
|
|
||||||
|
group/backend
|
||||||
|
#90 Rate limiting design assigned to: charlie idle 18 days
|
||||||
|
Last activity: label +priority::high by dave
|
||||||
|
#85 Cache invalidation fix assigned to: alice idle 21 days
|
||||||
|
Last activity: discussion comment by bob
|
||||||
|
|
||||||
|
group/frontend
|
||||||
|
!230 Dashboard redesign assigned to: eve idle 14 days
|
||||||
|
Last activity: DiffNote by dave
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Requires label naming conventions; no universal standard
|
||||||
|
- Work may be happening outside GitLab (local branch, design doc)
|
||||||
|
- "Idle" threshold is subjective; 14 days may be normal for large features
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore idle --assignee alice` — personal idle work check
|
||||||
|
- `lore idle --notify` — generate message templates for nudging owners
|
||||||
|
- Configurable label patterns in config.json for team-specific workflows
|
||||||
92
docs/ideas/impact-graph.md
Normal file
92
docs/ideas/impact-graph.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Cross-Project Impact Graph
|
||||||
|
|
||||||
|
- **Command:** `lore impact-graph [--format json|dot|mermaid]`
|
||||||
|
- **Confidence:** 75%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — aggregation over entity_references, graph output formatting
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Aggregate `entity_references` by project pair to produce a weighted adjacency matrix
|
||||||
|
showing how projects reference each other. Output as JSON, DOT, or Mermaid for
|
||||||
|
visualization.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Makes invisible architectural coupling visible. "Backend and frontend repos have
|
||||||
|
47 cross-references this quarter" tells you about tight coupling that may need
|
||||||
|
architectural attention.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `entity_references` (source/target entity IDs)
|
||||||
|
- `issues` / `merge_requests` (project_id for source/target)
|
||||||
|
- `projects` (path_with_namespace)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Project-to-project reference counts
|
||||||
|
WITH ref_projects AS (
|
||||||
|
SELECT
|
||||||
|
CASE er.source_entity_type
|
||||||
|
WHEN 'issue' THEN i_src.project_id
|
||||||
|
WHEN 'merge_request' THEN mr_src.project_id
|
||||||
|
END as source_project_id,
|
||||||
|
CASE er.target_entity_type
|
||||||
|
WHEN 'issue' THEN i_tgt.project_id
|
||||||
|
WHEN 'merge_request' THEN mr_tgt.project_id
|
||||||
|
END as target_project_id,
|
||||||
|
er.reference_type
|
||||||
|
FROM entity_references er
|
||||||
|
LEFT JOIN issues i_src ON er.source_entity_type = 'issue' AND er.source_entity_id = i_src.id
|
||||||
|
LEFT JOIN merge_requests mr_src ON er.source_entity_type = 'merge_request' AND er.source_entity_id = mr_src.id
|
||||||
|
LEFT JOIN issues i_tgt ON er.target_entity_type = 'issue' AND er.target_entity_id = i_tgt.id
|
||||||
|
LEFT JOIN merge_requests mr_tgt ON er.target_entity_type = 'merge_request' AND er.target_entity_id = mr_tgt.id
|
||||||
|
WHERE er.target_entity_id IS NOT NULL -- resolved references only
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
p_src.path_with_namespace as source_project,
|
||||||
|
p_tgt.path_with_namespace as target_project,
|
||||||
|
er.reference_type,
|
||||||
|
COUNT(*) as weight
|
||||||
|
FROM ref_projects rp
|
||||||
|
JOIN projects p_src ON rp.source_project_id = p_src.id
|
||||||
|
JOIN projects p_tgt ON rp.target_project_id = p_tgt.id
|
||||||
|
WHERE rp.source_project_id != rp.target_project_id -- cross-project only
|
||||||
|
GROUP BY p_src.path_with_namespace, p_tgt.path_with_namespace, er.reference_type
|
||||||
|
ORDER BY weight DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Formats
|
||||||
|
|
||||||
|
### Mermaid
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
Backend -->|closes 23| Frontend
|
||||||
|
Backend -->|mentioned 47| Infrastructure
|
||||||
|
Frontend -->|mentioned 12| Backend
|
||||||
|
```
|
||||||
|
|
||||||
|
### DOT
|
||||||
|
```dot
|
||||||
|
digraph impact {
|
||||||
|
"group/backend" -> "group/frontend" [label="closes: 23"];
|
||||||
|
"group/backend" -> "group/infra" [label="mentioned: 47"];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Requires multiple projects synced; limited value for single-project users
|
||||||
|
- "Mentioned" references are noisy (high volume, low signal)
|
||||||
|
- Doesn't capture coupling through shared libraries or APIs (code-level coupling)
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore impact-graph --since 90d` — time-scoped coupling analysis
|
||||||
|
- `lore impact-graph --type closes` — only meaningful reference types
|
||||||
|
- Include unresolved references to show dependencies on un-synced projects
|
||||||
|
- Coupling trend: is cross-project coupling increasing over time?
|
||||||
97
docs/ideas/label-audit.md
Normal file
97
docs/ideas/label-audit.md
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
# Label Hygiene Audit
|
||||||
|
|
||||||
|
- **Command:** `lore label-audit`
|
||||||
|
- **Confidence:** 82%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** low — straightforward aggregation queries
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Report on label health:
|
||||||
|
- Labels used only once (may be typos or abandoned experiments)
|
||||||
|
- Labels applied and removed within 1 hour (likely mistakes)
|
||||||
|
- Labels with no active issues/MRs (orphaned)
|
||||||
|
- Label name collisions across projects (same name, different meaning)
|
||||||
|
- Labels never used at all (defined but not applied)
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Label sprawl is real and makes filtering useless over time. Teams create labels
|
||||||
|
ad-hoc and never clean them up. This simple audit surfaces maintenance tasks.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `labels` (name, project_id)
|
||||||
|
- `issue_labels` / `mr_labels` (usage counts)
|
||||||
|
- `resource_label_events` (add/remove pairs for mistake detection)
|
||||||
|
- `issues` / `merge_requests` (state for "active" filtering)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Labels used only once
|
||||||
|
SELECT l.name, p.path_with_namespace, COUNT(*) as usage
|
||||||
|
FROM labels l
|
||||||
|
JOIN projects p ON l.project_id = p.id
|
||||||
|
LEFT JOIN issue_labels il ON il.label_id = l.id
|
||||||
|
LEFT JOIN mr_labels ml ON ml.label_id = l.id
|
||||||
|
GROUP BY l.id
|
||||||
|
HAVING COUNT(il.issue_id) + COUNT(ml.merge_request_id) = 1;
|
||||||
|
|
||||||
|
-- Flash labels (applied and removed within 1 hour)
|
||||||
|
SELECT
|
||||||
|
rle1.label_name,
|
||||||
|
rle1.created_at as added_at,
|
||||||
|
rle2.created_at as removed_at,
|
||||||
|
(rle2.created_at - rle1.created_at) / 60000 as minutes_active
|
||||||
|
FROM resource_label_events rle1
|
||||||
|
JOIN resource_label_events rle2
|
||||||
|
ON rle1.issue_id = rle2.issue_id
|
||||||
|
AND rle1.label_name = rle2.label_name
|
||||||
|
AND rle1.action = 'add'
|
||||||
|
AND rle2.action = 'remove'
|
||||||
|
AND rle2.created_at > rle1.created_at
|
||||||
|
AND (rle2.created_at - rle1.created_at) < 3600000;
|
||||||
|
|
||||||
|
-- Unused labels (defined but never applied)
|
||||||
|
SELECT l.name, p.path_with_namespace
|
||||||
|
FROM labels l
|
||||||
|
JOIN projects p ON l.project_id = p.id
|
||||||
|
LEFT JOIN issue_labels il ON il.label_id = l.id
|
||||||
|
LEFT JOIN mr_labels ml ON ml.label_id = l.id
|
||||||
|
WHERE il.issue_id IS NULL AND ml.merge_request_id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Label Audit
|
||||||
|
|
||||||
|
Unused Labels (4):
|
||||||
|
group/backend: deprecated-v1, needs-triage, wontfix-maybe
|
||||||
|
group/frontend: old-design
|
||||||
|
|
||||||
|
Single-Use Labels (3):
|
||||||
|
group/backend: perf-regression (1 issue)
|
||||||
|
group/frontend: ux-debt (1 MR), mobile-only (1 issue)
|
||||||
|
|
||||||
|
Flash Labels (applied < 1hr, 2):
|
||||||
|
group/backend #90: +priority::critical then -priority::critical (12 min)
|
||||||
|
group/backend #85: +blocked then -blocked (5 min)
|
||||||
|
|
||||||
|
Cross-Project Collisions (1):
|
||||||
|
"needs-review" used in group/backend (32 uses) AND group/frontend (8 uses)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Low glamour; this is janitorial work
|
||||||
|
- Single-use labels may be legitimate (one-off categorization)
|
||||||
|
- Cross-project collisions may be intentional (shared vocabulary)
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore label-audit --fix` — suggest deletions for unused labels
|
||||||
|
- Trend: label count over time (is sprawl increasing?)
|
||||||
74
docs/ideas/label-flow.md
Normal file
74
docs/ideas/label-flow.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Label Velocity
|
||||||
|
|
||||||
|
- **Command:** `lore label-flow <from-label> <to-label>`
|
||||||
|
- **Confidence:** 78%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — self-join on resource_label_events, percentile computation
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
For a given label pair (e.g., "needs-review" to "approved"), compute median and P90
|
||||||
|
transition times using `resource_label_events`. Shows how fast work moves through
|
||||||
|
your process labels.
|
||||||
|
|
||||||
|
Also supports: single label dwell time (how long does "in-progress" stay applied?).
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Process bottlenecks become quantifiable. "Our code review takes a median of 3 days"
|
||||||
|
is actionable data for retrospectives and process improvement.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `resource_label_events` (label_name, action, created_at, issue_id, merge_request_id)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Label A → Label B transition time
|
||||||
|
WITH add_a AS (
|
||||||
|
SELECT issue_id, merge_request_id, MIN(created_at) as added_at
|
||||||
|
FROM resource_label_events
|
||||||
|
WHERE label_name = ?1 AND action = 'add'
|
||||||
|
GROUP BY issue_id, merge_request_id
|
||||||
|
),
|
||||||
|
add_b AS (
|
||||||
|
SELECT issue_id, merge_request_id, MIN(created_at) as added_at
|
||||||
|
FROM resource_label_events
|
||||||
|
WHERE label_name = ?2 AND action = 'add'
|
||||||
|
GROUP BY issue_id, merge_request_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
(b.added_at - a.added_at) / 3600000.0 as hours_transition
|
||||||
|
FROM add_a a
|
||||||
|
JOIN add_b b ON a.issue_id = b.issue_id OR a.merge_request_id = b.merge_request_id
|
||||||
|
WHERE b.added_at > a.added_at;
|
||||||
|
```
|
||||||
|
|
||||||
|
Then compute percentiles in Rust (median, P75, P90).
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Label Flow: "needs-review" → "approved"
|
||||||
|
|
||||||
|
Transitions: 42 issues/MRs in last 90 days
|
||||||
|
Median: 18.5 hours
|
||||||
|
P75: 36.2 hours
|
||||||
|
P90: 72.8 hours
|
||||||
|
Slowest: !234 Refactor auth (168 hours)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Only works if teams use label-based workflows consistently
|
||||||
|
- Labels may be applied out of order or skipped
|
||||||
|
- Self-join performance could be slow with many events
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore label-flow --dwell "in-progress"` — how long does a label stay?
|
||||||
|
- `lore label-flow --all` — auto-discover common transitions from event data
|
||||||
|
- Visualization: label state machine with median transition times on edges
|
||||||
81
docs/ideas/milestone-risk.md
Normal file
81
docs/ideas/milestone-risk.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Milestone Risk Report
|
||||||
|
|
||||||
|
- **Command:** `lore milestone-risk [title]`
|
||||||
|
- **Confidence:** 78%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — milestone + issue aggregation with scope change detection
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
For each active milestone (or a specific one): show total issues, % closed, issues
|
||||||
|
added after milestone creation (scope creep), issues with no assignee, issues with
|
||||||
|
overdue due_date. Flag milestones where completion rate is below expected trajectory.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Milestone health is usually assessed by gut feel. This provides objective signals
|
||||||
|
from data already ingested. Project managers can spot risks early.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `milestones` (title, state, due_date)
|
||||||
|
- `issues` (milestone_id, state, created_at, due_date, assignee)
|
||||||
|
- `issue_assignees` (for unassigned detection)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
m.title,
|
||||||
|
m.state,
|
||||||
|
m.due_date,
|
||||||
|
COUNT(*) as total_issues,
|
||||||
|
SUM(CASE WHEN i.state = 'closed' THEN 1 ELSE 0 END) as closed,
|
||||||
|
SUM(CASE WHEN i.state = 'opened' THEN 1 ELSE 0 END) as open,
|
||||||
|
SUM(CASE WHEN i.created_at > m.created_at THEN 1 ELSE 0 END) as scope_creep,
|
||||||
|
SUM(CASE WHEN ia.username IS NULL AND i.state = 'opened' THEN 1 ELSE 0 END) as unassigned,
|
||||||
|
SUM(CASE WHEN i.due_date < DATE('now') AND i.state = 'opened' THEN 1 ELSE 0 END) as overdue
|
||||||
|
FROM milestones m
|
||||||
|
JOIN issues i ON i.milestone_id = m.id
|
||||||
|
LEFT JOIN issue_assignees ia ON ia.issue_id = i.id
|
||||||
|
WHERE m.state = 'active'
|
||||||
|
GROUP BY m.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `created_at` comparison for scope creep is approximate — GitLab doesn't
|
||||||
|
expose when an issue was added to a milestone via its milestone_events.
|
||||||
|
|
||||||
|
Actually we DO have `resource_milestone_events` — use those for precise scope change
|
||||||
|
detection.
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Milestone Risk Report
|
||||||
|
|
||||||
|
v2.0 (due Feb 15, 2025)
|
||||||
|
Progress: 14/20 closed (70%)
|
||||||
|
Scope: +3 issues added after milestone start
|
||||||
|
Risks: 2 issues overdue, 1 issue unassigned
|
||||||
|
Status: ON TRACK (70% complete, 60% time elapsed)
|
||||||
|
|
||||||
|
v2.1 (due Mar 30, 2025)
|
||||||
|
Progress: 2/15 closed (13%)
|
||||||
|
Scope: +8 issues added after milestone start
|
||||||
|
Risks: 5 issues unassigned
|
||||||
|
Status: AT RISK (13% complete, scope still growing)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Milestone semantics vary wildly between teams
|
||||||
|
- "Scope creep" detection is noisy if teams batch-add issues to milestones
|
||||||
|
- due_date comparison assumes consistent timezone handling
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore milestone-risk --history` — show scope changes over time
|
||||||
|
- Velocity estimation: at current closure rate, will the milestone finish on time?
|
||||||
|
- Combine with label-flow for "how fast are milestone issues moving through workflow"
|
||||||
67
docs/ideas/mr-pipeline.md
Normal file
67
docs/ideas/mr-pipeline.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# MR Pipeline Efficiency
|
||||||
|
|
||||||
|
- **Command:** `lore mr-pipeline [--since <date>]`
|
||||||
|
- **Confidence:** 78%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — builds on bottleneck detector with more stages
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Track the full MR lifecycle: creation, first review, all reviews complete (threads
|
||||||
|
resolved), approval, merge. Compute time spent in each stage across all MRs.
|
||||||
|
Identify which stage is the bottleneck.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
"Our merge process is slow" is vague. This breaks it into stages so teams can target
|
||||||
|
the actual bottleneck. Maybe creation-to-review is fast but review-to-merge is slow
|
||||||
|
(merge queue issues). Maybe first review is fast but resolution takes forever
|
||||||
|
(contentious code).
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `merge_requests` (created_at, merged_at)
|
||||||
|
- `notes` (note_type='DiffNote', created_at, author_username)
|
||||||
|
- `discussions` (resolved, resolvable, merge_request_id)
|
||||||
|
- `resource_state_events` (state changes with timestamps)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
For each merged MR, compute:
|
||||||
|
1. **Created → First Review**: MIN(DiffNote.created_at) - mr.created_at
|
||||||
|
2. **First Review → All Resolved**: MAX(discussion.resolved_at) - MIN(DiffNote.created_at)
|
||||||
|
3. **All Resolved → Merged**: mr.merged_at - MAX(discussion.resolved_at)
|
||||||
|
|
||||||
|
Note: "resolved_at" isn't directly stored but can be approximated from the last
|
||||||
|
note in resolved discussions, or from state events.
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
MR Pipeline (last 30 days, 24 merged MRs)
|
||||||
|
|
||||||
|
Stage Median P75 P90
|
||||||
|
Created → First Review 4.2h 12.1h 28.3h
|
||||||
|
First Review → Resolved 8.1h 24.5h 72.0h <-- BOTTLENECK
|
||||||
|
Resolved → Merged 0.5h 1.2h 3.1h
|
||||||
|
|
||||||
|
Total (Created → Merged) 18.4h 48.2h 96.1h
|
||||||
|
|
||||||
|
Biggest bottleneck: Review resolution (median 8.1h)
|
||||||
|
Suggestion: Consider breaking large MRs into smaller reviewable chunks
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- "Resolved" timestamp approximation may be inaccurate
|
||||||
|
- Pipeline assumes linear flow; real MRs have back-and-forth cycles
|
||||||
|
- Draft MRs skew metrics (created early, reviewed late intentionally)
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore mr-pipeline --exclude-drafts` — cleaner metrics
|
||||||
|
- Per-project comparison: which project has the fastest pipeline?
|
||||||
|
- Trend line: weekly pipeline speed over time
|
||||||
|
- Break down by MR size (files changed) to normalize
|
||||||
265
docs/ideas/project-ergonomics.md
Normal file
265
docs/ideas/project-ergonomics.md
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
# Multi-Project Ergonomics
|
||||||
|
|
||||||
|
- **Confidence:** 90%
|
||||||
|
- **Tier:** 1
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium (multiple small improvements that compound)
|
||||||
|
|
||||||
|
## The Problem
|
||||||
|
|
||||||
|
Every command that touches project-scoped data requires `-p group/subgroup/project`
|
||||||
|
to disambiguate. For users with 5+ projects synced, this is:
|
||||||
|
|
||||||
|
- Repetitive: typing `-p infra/platform/auth-service` on every query
|
||||||
|
- Error-prone: mistyping long paths
|
||||||
|
- Discoverable only by failure: you don't know you need `-p` until you hit an
|
||||||
|
ambiguous error
|
||||||
|
|
||||||
|
The fuzzy matching in `resolve_project` is already good (suffix, substring,
|
||||||
|
case-insensitive) but it only kicks in on the `-p` value itself. There's no way to
|
||||||
|
set a default, group projects, or scope a whole session.
|
||||||
|
|
||||||
|
## Proposed Improvements
|
||||||
|
|
||||||
|
### 1. Project Aliases in Config
|
||||||
|
|
||||||
|
Let users define short aliases for long project paths.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projects": [
|
||||||
|
{ "path": "infra/platform/auth-service", "alias": "auth" },
|
||||||
|
{ "path": "infra/platform/billing-service", "alias": "billing" },
|
||||||
|
{ "path": "frontend/customer-portal", "alias": "portal" },
|
||||||
|
{ "path": "frontend/admin-dashboard", "alias": "admin" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then: `lore issues -p auth` resolves via alias before falling through to fuzzy match.
|
||||||
|
|
||||||
|
**Implementation:** Add optional `alias` field to `ProjectConfig`. In
|
||||||
|
`resolve_project`, check aliases before the existing exact/suffix/substring cascade.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
pub struct ProjectConfig {
|
||||||
|
pub path: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub alias: Option<String>,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Resolution order becomes:
|
||||||
|
1. Exact alias match (new)
|
||||||
|
2. Exact path match
|
||||||
|
3. Case-insensitive path match
|
||||||
|
4. Suffix match
|
||||||
|
5. Substring match
|
||||||
|
|
||||||
|
### 2. Default Project (`LORE_PROJECT` env var)
|
||||||
|
|
||||||
|
Set a default project for your shell session so you don't need `-p` at all.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export LORE_PROJECT=auth
|
||||||
|
lore issues # scoped to auth-service
|
||||||
|
lore mrs --state opened # scoped to auth-service
|
||||||
|
lore search "timeout bug" # scoped to auth-service
|
||||||
|
lore issues -p billing # explicit -p overrides the env var
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation:** In every command that accepts `-p`, fall back to
|
||||||
|
`std::env::var("LORE_PROJECT")` when the flag is absent. The `-p` flag always wins.
|
||||||
|
|
||||||
|
Could also support a config-level default:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"defaultProject": "auth"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Precedence: CLI flag > env var > config default > (no filter).
|
||||||
|
|
||||||
|
### 3. `lore use <project>` — Session Context Switcher
|
||||||
|
|
||||||
|
A command that sets `LORE_PROJECT` for the current shell by writing to a dotfile.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lore use auth
|
||||||
|
# writes ~/.local/state/lore/current-project containing "auth"
|
||||||
|
|
||||||
|
lore issues # reads current-project file, scopes to auth
|
||||||
|
lore use --clear # removes the file, back to all-project mode
|
||||||
|
lore use # shows current project context
|
||||||
|
```
|
||||||
|
|
||||||
|
This is similar to `kubectl config use-context`, `nvm use`, or `tfenv use`.
|
||||||
|
|
||||||
|
**Implementation:** Write a one-line file at a known state path. Each command reads
|
||||||
|
it as the lowest-priority default (below env var and CLI flag).
|
||||||
|
|
||||||
|
Precedence: CLI flag > env var > `lore use` state file > config default > (no filter).
|
||||||
|
|
||||||
|
### 4. `lore projects` — Project Listing and Discovery
|
||||||
|
|
||||||
|
A dedicated command to see what's synced, with aliases and activity stats.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ lore projects
|
||||||
|
|
||||||
|
Alias Path Issues MRs Last Sync
|
||||||
|
auth infra/platform/auth-service 142 87 2h ago
|
||||||
|
billing infra/platform/billing-service 56 34 2h ago
|
||||||
|
portal frontend/customer-portal 203 112 2h ago
|
||||||
|
admin frontend/admin-dashboard 28 15 3d ago
|
||||||
|
- data/ml-pipeline 89 45 2h ago
|
||||||
|
```
|
||||||
|
|
||||||
|
Robot mode returns the same as JSON with alias, path, counts, and last sync time.
|
||||||
|
|
||||||
|
**Implementation:** Query `projects` joined with `COUNT(issues)`, `COUNT(mrs)`,
|
||||||
|
and `MAX(sync_runs.finished_at)`. Overlay aliases from config.
|
||||||
|
|
||||||
|
### 5. Project Groups in Config
|
||||||
|
|
||||||
|
Let users define named groups of projects for batch scoping.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"projectGroups": {
|
||||||
|
"backend": ["auth", "billing", "data/ml-pipeline"],
|
||||||
|
"frontend": ["portal", "admin"],
|
||||||
|
"all-infra": ["auth", "billing"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then: `lore issues -p @backend` (or `--group backend`) queries across all projects
|
||||||
|
in the group.
|
||||||
|
|
||||||
|
**Implementation:** When `-p` value starts with `@`, look up the group and resolve
|
||||||
|
each member project. Pass as a `Vec<i64>` of project IDs to the query layer.
|
||||||
|
|
||||||
|
This is especially powerful for:
|
||||||
|
- `lore search "auth bug" -p @backend` — search across related repos
|
||||||
|
- `lore digest --since 7d -p @frontend` — team-scoped activity digest
|
||||||
|
- `lore timeline "deployment" -p @all-infra` — cross-repo timeline
|
||||||
|
|
||||||
|
### 6. Git-Aware Project Detection
|
||||||
|
|
||||||
|
When running `lore` from inside a git repo that matches a synced project, auto-scope
|
||||||
|
to that project without any flags.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/code/auth-service
|
||||||
|
lore issues # auto-detects this is infra/platform/auth-service
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation:** Read `.git/config` for the remote URL, extract the project path,
|
||||||
|
check if it matches a synced project. Only activate when exactly one project matches.
|
||||||
|
|
||||||
|
Detection logic:
|
||||||
|
```
|
||||||
|
1. Check if cwd is inside a git repo (find .git)
|
||||||
|
2. Parse git remote origin URL
|
||||||
|
3. Extract path component (e.g., "infra/platform/auth-service.git" → "infra/platform/auth-service")
|
||||||
|
4. Match against synced projects
|
||||||
|
5. If exactly one match, use as implicit -p
|
||||||
|
6. If ambiguous or no match, do nothing (fall through to normal behavior)
|
||||||
|
```
|
||||||
|
|
||||||
|
Precedence: CLI flag > env var > `lore use` > config default > git detection > (no filter).
|
||||||
|
|
||||||
|
This is similar to how `gh` (GitHub CLI) auto-detects the repo you're in.
|
||||||
|
|
||||||
|
### 7. Prompt Integration / Shell Function
|
||||||
|
|
||||||
|
Provide a shell function that shows the current project context in the prompt.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# In .bashrc / .zshrc
|
||||||
|
eval "$(lore completions zsh)"
|
||||||
|
PROMPT='$(lore-prompt)%~ %# '
|
||||||
|
```
|
||||||
|
|
||||||
|
Output: `[lore:auth] ~/code/auth-service %`
|
||||||
|
|
||||||
|
Shows which project `lore` commands will scope to, using the same precedence chain.
|
||||||
|
Helps users understand what context they're in before running a query.
|
||||||
|
|
||||||
|
### 8. Short Project References in Output
|
||||||
|
|
||||||
|
Once aliases exist, use them everywhere in output for brevity:
|
||||||
|
|
||||||
|
**Before:**
|
||||||
|
```
|
||||||
|
infra/platform/auth-service#42 Login timeout bug
|
||||||
|
infra/platform/auth-service!234 Refactor auth middleware
|
||||||
|
```
|
||||||
|
|
||||||
|
**After:**
|
||||||
|
```
|
||||||
|
auth#42 Login timeout bug
|
||||||
|
auth!234 Refactor auth middleware
|
||||||
|
```
|
||||||
|
|
||||||
|
With `--full-paths` flag to get the verbose form when needed.
|
||||||
|
|
||||||
|
## Combined UX Flow
|
||||||
|
|
||||||
|
With all improvements, a typical session looks like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# One-time config
|
||||||
|
lore init # sets up aliases during interactive setup
|
||||||
|
|
||||||
|
# Daily use
|
||||||
|
lore use auth # set context
|
||||||
|
lore issues --state opened # no -p needed
|
||||||
|
lore search "timeout" # scoped to auth
|
||||||
|
lore timeline "login flow" # scoped to auth
|
||||||
|
lore issues -p @backend # cross-repo query via group
|
||||||
|
lore mrs -p billing # quick alias switch
|
||||||
|
lore use --clear # back to global
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for the power user who never wants to type `lore use`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/code/auth-service
|
||||||
|
lore issues # git-aware auto-detection
|
||||||
|
```
|
||||||
|
|
||||||
|
Or for the scripter:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LORE_PROJECT=auth lore --robot issues -n 50 # env var for automation
|
||||||
|
```
|
||||||
|
|
||||||
|
## Priority Order
|
||||||
|
|
||||||
|
Implement in this order for maximum incremental value:
|
||||||
|
|
||||||
|
1. **Project aliases** — smallest change, biggest daily friction reduction
|
||||||
|
2. **`LORE_PROJECT` env var** — trivial to implement, enables scripting
|
||||||
|
3. **`lore projects` command** — discoverability, completes the alias story
|
||||||
|
4. **`lore use` context** — nice-to-have for heavy users
|
||||||
|
5. **Project groups** — high value for multi-repo teams
|
||||||
|
6. **Git-aware detection** — polish, "it just works" feel
|
||||||
|
7. **Short refs in output** — ties into timeline issue #001
|
||||||
|
8. **Prompt integration** — extra polish
|
||||||
|
|
||||||
|
## Relationship to Issue #001
|
||||||
|
|
||||||
|
The timeline entity-ref ambiguity (issue #001) is solved naturally by items 7 and 8
|
||||||
|
here. Once aliases exist, `format_entity_ref` can use the alias as the short project
|
||||||
|
identifier in multi-project output:
|
||||||
|
|
||||||
|
```
|
||||||
|
auth#42 instead of infra/platform/auth-service#42
|
||||||
|
```
|
||||||
|
|
||||||
|
And in single-project timelines (detected via `lore use` or git-aware), the project
|
||||||
|
prefix is omitted entirely — matching the current behavior but now intentionally.
|
||||||
81
docs/ideas/recurring-patterns.md
Normal file
81
docs/ideas/recurring-patterns.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# Recurring Bug Pattern Detector
|
||||||
|
|
||||||
|
- **Command:** `lore recurring-patterns [--min-cluster <N>]`
|
||||||
|
- **Confidence:** 76%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** high — vector clustering, threshold tuning
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Cluster closed issues by embedding similarity. Identify clusters of 3+ issues that
|
||||||
|
are semantically similar — these represent recurring problems that need a systemic
|
||||||
|
fix rather than one-off patches.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Finding the same bug filed 5 different ways is one of the most impactful things you
|
||||||
|
can surface. This is a sophisticated use of the embedding pipeline that no competing
|
||||||
|
tool offers. It turns "we keep having auth issues" from a gut feeling into data.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `documents` (source_type='issue', content_text)
|
||||||
|
- `embeddings` (768-dim vectors)
|
||||||
|
- `issues` (state='closed' for filtering)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Collect all embeddings for closed issue documents
|
||||||
|
2. For each issue, find K nearest neighbors (K=10)
|
||||||
|
3. Build adjacency graph: edge exists if similarity > threshold (e.g., 0.80)
|
||||||
|
4. Find connected components (simple DFS/BFS)
|
||||||
|
5. Filter to components with >= min-cluster members (default 3)
|
||||||
|
6. For each cluster:
|
||||||
|
a. Extract common terms (TF-IDF or simple word frequency)
|
||||||
|
b. Sort by recency (most recent issue first)
|
||||||
|
c. Report cluster with: theme, member issues, time span
|
||||||
|
```
|
||||||
|
|
||||||
|
### Similarity Threshold Tuning
|
||||||
|
|
||||||
|
This is the critical parameter. Too low = noise, too high = misses.
|
||||||
|
- Start at 0.80 cosine similarity
|
||||||
|
- Expose as `--threshold` flag for user tuning
|
||||||
|
- Report cluster cohesion score for transparency
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Recurring Patterns (3+ similar closed issues)
|
||||||
|
|
||||||
|
Cluster 1: "Authentication timeout errors" (5 issues, spanning 6 months)
|
||||||
|
#89 Login timeout on slow networks (closed 3d ago)
|
||||||
|
#72 Auth flow hangs on cellular (closed 2mo ago)
|
||||||
|
#58 Token refresh timeout (closed 3mo ago)
|
||||||
|
#45 SSO login timeout for remote users (closed 5mo ago)
|
||||||
|
#31 Connection timeout in auth middleware (closed 6mo ago)
|
||||||
|
Avg similarity: 0.87 | Suggested: systemic fix for auth timeout handling
|
||||||
|
|
||||||
|
Cluster 2: "Cache invalidation issues" (3 issues, spanning 2 months)
|
||||||
|
#85 Stale cache after deploy (closed 2w ago)
|
||||||
|
#77 Cache headers not updated (closed 1mo ago)
|
||||||
|
#69 Dashboard shows old data after settings change (closed 2mo ago)
|
||||||
|
Avg similarity: 0.82 | Suggested: review cache invalidation strategy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Clustering quality depends on embedding quality and threshold tuning
|
||||||
|
- May produce false clusters (issues that mention similar terms but are different problems)
|
||||||
|
- Computationally expensive for large issue counts (N^2 comparisons)
|
||||||
|
- Need to handle multi-chunk documents (aggregate embeddings)
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore recurring-patterns --open` — find clusters in open issues (duplicates to merge)
|
||||||
|
- `lore recurring-patterns --cross-project` — patterns across repos
|
||||||
|
- Trend detection: are cluster sizes growing? (escalating problem)
|
||||||
|
- Export as report for engineering retrospectives
|
||||||
78
docs/ideas/review-coverage.md
Normal file
78
docs/ideas/review-coverage.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# DiffNote Coverage Map
|
||||||
|
|
||||||
|
- **Command:** `lore review-coverage <mr-iid>`
|
||||||
|
- **Confidence:** 75%
|
||||||
|
- **Tier:** 3
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — join DiffNote positions with mr_file_changes
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
For a specific MR, show which files received review comments (DiffNotes) vs. which
|
||||||
|
files were changed but received no review attention. Highlights blind spots in code
|
||||||
|
review.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Large MRs often have files that get reviewed thoroughly and files that slip through
|
||||||
|
with no comments. This makes the review coverage visible so teams can decide if
|
||||||
|
un-reviewed files need a second look.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `mr_file_changes` (new_path per MR)
|
||||||
|
- `notes` (position_new_path, note_type='DiffNote', discussion_id)
|
||||||
|
- `discussions` (merge_request_id)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
mfc.new_path,
|
||||||
|
mfc.change_type,
|
||||||
|
COUNT(DISTINCT n.id) as review_comments,
|
||||||
|
COUNT(DISTINCT d.id) as review_threads,
|
||||||
|
CASE WHEN COUNT(n.id) = 0 THEN 'NOT REVIEWED' ELSE 'REVIEWED' END as status
|
||||||
|
FROM mr_file_changes mfc
|
||||||
|
LEFT JOIN notes n ON n.position_new_path = mfc.new_path
|
||||||
|
AND n.note_type = 'DiffNote'
|
||||||
|
AND n.is_system = 0
|
||||||
|
LEFT JOIN discussions d ON n.discussion_id = d.id
|
||||||
|
AND d.merge_request_id = mfc.merge_request_id
|
||||||
|
WHERE mfc.merge_request_id = ?1
|
||||||
|
GROUP BY mfc.new_path
|
||||||
|
ORDER BY review_comments DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Review Coverage for !234 — Refactor auth middleware
|
||||||
|
|
||||||
|
REVIEWED (5 files, 23 comments)
|
||||||
|
src/auth/middleware.rs 12 comments, 4 threads
|
||||||
|
src/auth/jwt.rs 6 comments, 2 threads
|
||||||
|
src/auth/session.rs 3 comments, 1 thread
|
||||||
|
tests/auth/middleware_test.rs 1 comment, 1 thread
|
||||||
|
src/auth/mod.rs 1 comment, 1 thread
|
||||||
|
|
||||||
|
NOT REVIEWED (3 files)
|
||||||
|
src/auth/types.rs modified [no review comments]
|
||||||
|
src/api/routes.rs modified [no review comments]
|
||||||
|
Cargo.toml modified [no review comments]
|
||||||
|
|
||||||
|
Coverage: 5/8 files (62.5%)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Reviewers may have reviewed a file without leaving comments (approval by silence)
|
||||||
|
- position_new_path matching may not cover all DiffNote position formats
|
||||||
|
- Config files (Cargo.toml) not being reviewed is usually fine
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore review-coverage --all --since 30d` — aggregate coverage across all MRs
|
||||||
|
- Per-reviewer breakdown: which reviewers cover which files?
|
||||||
|
- Coverage heatmap: files that consistently escape review across multiple MRs
|
||||||
90
docs/ideas/silos.md
Normal file
90
docs/ideas/silos.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Knowledge Silo Detection
|
||||||
|
|
||||||
|
- **Command:** `lore silos [--min-changes <N>]`
|
||||||
|
- **Confidence:** 87%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — requires mr_file_changes population (Gate 4)
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
For each file path (or directory), count unique MR authors. Flag paths where only
|
||||||
|
1 person has ever authored changes (bus factor = 1). Aggregate by directory to show
|
||||||
|
silo areas.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Bus factor analysis is critical for team resilience. If only one person has ever
|
||||||
|
touched the auth module, that's a risk. This uses data already ingested to surface
|
||||||
|
knowledge concentration that's otherwise invisible.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
- `mr_file_changes` (new_path, merge_request_id) — needs Gate 4 ingestion
|
||||||
|
- `merge_requests` (author_username, state='merged')
|
||||||
|
- `projects` (path_with_namespace)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Find directories with bus factor = 1
|
||||||
|
WITH file_authors AS (
|
||||||
|
SELECT
|
||||||
|
mfc.new_path,
|
||||||
|
mr.author_username,
|
||||||
|
p.path_with_namespace,
|
||||||
|
mfc.project_id
|
||||||
|
FROM mr_file_changes mfc
|
||||||
|
JOIN merge_requests mr ON mfc.merge_request_id = mr.id
|
||||||
|
JOIN projects p ON mfc.project_id = p.id
|
||||||
|
WHERE mr.state = 'merged'
|
||||||
|
),
|
||||||
|
directory_authors AS (
|
||||||
|
SELECT
|
||||||
|
project_id,
|
||||||
|
path_with_namespace,
|
||||||
|
-- Extract directory: everything before last '/'
|
||||||
|
CASE
|
||||||
|
WHEN INSTR(new_path, '/') > 0
|
||||||
|
THEN SUBSTR(new_path, 1, LENGTH(new_path) - LENGTH(REPLACE(RTRIM(new_path, REPLACE(new_path, '/', '')), '', '')))
|
||||||
|
ELSE '.'
|
||||||
|
END as directory,
|
||||||
|
COUNT(DISTINCT author_username) as unique_authors,
|
||||||
|
COUNT(*) as total_changes,
|
||||||
|
GROUP_CONCAT(DISTINCT author_username) as authors
|
||||||
|
FROM file_authors
|
||||||
|
GROUP BY project_id, directory
|
||||||
|
)
|
||||||
|
SELECT * FROM directory_authors
|
||||||
|
WHERE unique_authors = 1
|
||||||
|
AND total_changes >= ?1 -- min-changes threshold
|
||||||
|
ORDER BY total_changes DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Knowledge Silos (bus factor = 1, min 3 changes)
|
||||||
|
|
||||||
|
group/backend
|
||||||
|
src/auth/ alice (8 changes) HIGH RISK
|
||||||
|
src/billing/ bob (5 changes) HIGH RISK
|
||||||
|
src/utils/cache/ charlie (3 changes) MODERATE RISK
|
||||||
|
|
||||||
|
group/frontend
|
||||||
|
src/admin/ dave (12 changes) HIGH RISK
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Historical authors may have left the team; needs recency weighting
|
||||||
|
- Requires `mr_file_changes` to be populated (Gate 4)
|
||||||
|
- Single-author directories may be intentional (ownership model)
|
||||||
|
- Directory aggregation heuristic is imperfect for deep nesting
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore silos --since 180d` — only count recent activity
|
||||||
|
- `lore silos --depth 2` — aggregate at directory depth N
|
||||||
|
- Combine with `lore experts` to show both silos and experts in one view
|
||||||
|
- Risk scoring: weight by directory size, change frequency, recency
|
||||||
95
docs/ideas/similar-issues.md
Normal file
95
docs/ideas/similar-issues.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# Similar Issues Finder
|
||||||
|
|
||||||
|
- **Command:** `lore similar <iid>`
|
||||||
|
- **Confidence:** 95%
|
||||||
|
- **Tier:** 1
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** low — infrastructure exists, needs one new query path
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Given an issue IID, find the N most semantically similar issues using the existing
|
||||||
|
vector embeddings. Show similarity score and overlapping keywords.
|
||||||
|
|
||||||
|
Can also work with MRs: `lore similar --mr <iid>`.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Duplicate detection is a constant problem on active projects. "Is this bug already
|
||||||
|
filed?" becomes a one-liner. This is the most natural use of the embedding pipeline
|
||||||
|
and the feature people expect when they hear "semantic search."
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `documents` table (source_type, source_id, content_text)
|
||||||
|
- `embeddings` virtual table (768-dim vectors via sqlite-vec)
|
||||||
|
- `embedding_metadata` (document_hash for staleness check)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Resolve IID → issue.id → document.id (via source_type='issue', source_id)
|
||||||
|
2. Look up embedding vector(s) for that document
|
||||||
|
3. Query sqlite-vec for K nearest neighbors (K = limit * 2 for headroom)
|
||||||
|
4. Filter to source_type='issue' (or 'merge_request' if --include-mrs)
|
||||||
|
5. Exclude self
|
||||||
|
6. Rank by cosine similarity
|
||||||
|
7. Return top N with: iid, title, project, similarity_score, url
|
||||||
|
```
|
||||||
|
|
||||||
|
### SQL Core
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Get the embedding for target document (chunk 0 = representative)
|
||||||
|
SELECT embedding FROM embeddings WHERE rowid = ?1 * 1000;
|
||||||
|
|
||||||
|
-- Find nearest neighbors
|
||||||
|
SELECT
|
||||||
|
rowid,
|
||||||
|
distance
|
||||||
|
FROM embeddings
|
||||||
|
WHERE embedding MATCH ?1
|
||||||
|
AND k = ?2
|
||||||
|
ORDER BY distance;
|
||||||
|
|
||||||
|
-- Resolve back to entities
|
||||||
|
SELECT d.source_type, d.source_id, d.title, d.url, i.iid, i.state
|
||||||
|
FROM documents d
|
||||||
|
JOIN issues i ON d.source_id = i.id AND d.source_type = 'issue'
|
||||||
|
WHERE d.id = ?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Robot Mode Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"query_issue": { "iid": 42, "title": "Login timeout on slow networks" },
|
||||||
|
"similar": [
|
||||||
|
{
|
||||||
|
"iid": 38,
|
||||||
|
"title": "Connection timeout in auth flow",
|
||||||
|
"project": "group/backend",
|
||||||
|
"similarity": 0.87,
|
||||||
|
"state": "closed",
|
||||||
|
"url": "https://gitlab.com/group/backend/-/issues/38"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"meta": { "elapsed_ms": 45, "candidates_scanned": 200 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Embedding quality depends on description quality; short issues may not match well
|
||||||
|
- Multi-chunk documents need aggregation strategy (use chunk 0 or average?)
|
||||||
|
- Requires embeddings to be generated first (`lore embed`)
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore similar --open-only` to filter to unresolved issues (duplicate triage)
|
||||||
|
- `lore similar --text "free text query"` to find issues similar to arbitrary text
|
||||||
|
- Batch mode: find all potential duplicate clusters across the entire database
|
||||||
100
docs/ideas/stale-discussions.md
Normal file
100
docs/ideas/stale-discussions.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Stale Discussion Finder
|
||||||
|
|
||||||
|
- **Command:** `lore stale-discussions [--days <N>]`
|
||||||
|
- **Confidence:** 90%
|
||||||
|
- **Tier:** 1
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** low — single query, minimal formatting
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
List unresolved, resolvable discussions where `last_note_at` is older than a
|
||||||
|
threshold (default 14 days), grouped by parent entity. Prioritize by discussion
|
||||||
|
count per entity (more stale threads = more urgent).
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Unresolved discussions are silent blockers. They prevent MR merges, stall
|
||||||
|
decision-making, and represent forgotten conversations. This surfaces them so teams
|
||||||
|
can take action: resolve, respond, or explicitly mark as won't-fix.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `discussions` (resolved, resolvable, last_note_at)
|
||||||
|
- `issues` / `merge_requests` (for parent entity context)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT
|
||||||
|
d.id,
|
||||||
|
d.noteable_type,
|
||||||
|
CASE WHEN d.issue_id IS NOT NULL THEN i.iid ELSE mr.iid END as entity_iid,
|
||||||
|
CASE WHEN d.issue_id IS NOT NULL THEN i.title ELSE mr.title END as entity_title,
|
||||||
|
p.path_with_namespace,
|
||||||
|
d.last_note_at,
|
||||||
|
((?1 - d.last_note_at) / 86400000) as days_stale,
|
||||||
|
COUNT(*) OVER (PARTITION BY COALESCE(d.issue_id, d.merge_request_id), d.noteable_type) as stale_count_for_entity
|
||||||
|
FROM discussions d
|
||||||
|
JOIN projects p ON d.project_id = p.id
|
||||||
|
LEFT JOIN issues i ON d.issue_id = i.id
|
||||||
|
LEFT JOIN merge_requests mr ON d.merge_request_id = mr.id
|
||||||
|
WHERE d.resolved = 0
|
||||||
|
AND d.resolvable = 1
|
||||||
|
AND d.last_note_at < ?1
|
||||||
|
ORDER BY days_stale DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output Format
|
||||||
|
|
||||||
|
```
|
||||||
|
Stale Discussions (14+ days without activity)
|
||||||
|
|
||||||
|
group/backend !234 — Refactor auth middleware (3 stale threads)
|
||||||
|
Discussion #a1b2c3 (28d stale) "Should we use JWT or session tokens?"
|
||||||
|
Discussion #d4e5f6 (21d stale) "Error handling for expired tokens"
|
||||||
|
Discussion #g7h8i9 (14d stale) "Performance implications of per-request validation"
|
||||||
|
|
||||||
|
group/backend #90 — Rate limiting design (1 stale thread)
|
||||||
|
Discussion #j0k1l2 (18d stale) "Redis vs in-memory rate counter"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Robot Mode Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"threshold_days": 14,
|
||||||
|
"total_stale": 4,
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"type": "merge_request",
|
||||||
|
"iid": 234,
|
||||||
|
"title": "Refactor auth middleware",
|
||||||
|
"project": "group/backend",
|
||||||
|
"stale_discussions": [
|
||||||
|
{
|
||||||
|
"discussion_id": "a1b2c3",
|
||||||
|
"days_stale": 28,
|
||||||
|
"first_note_preview": "Should we use JWT or session tokens?"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Some discussions are intentionally left open (design docs, long-running threads)
|
||||||
|
- Could produce noise in repos with loose discussion hygiene
|
||||||
|
- Doesn't distinguish "stale and blocking" from "stale and irrelevant"
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore stale-discussions --mr-only` — focus on MR review threads (most actionable)
|
||||||
|
- `lore stale-discussions --author alice` — "threads I started that went quiet"
|
||||||
|
- `lore stale-discussions --assignee bob` — "threads on my MRs that need attention"
|
||||||
82
docs/ideas/unlinked.md
Normal file
82
docs/ideas/unlinked.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Unlinked MR Finder
|
||||||
|
|
||||||
|
- **Command:** `lore unlinked [--since <date>]`
|
||||||
|
- **Confidence:** 83%
|
||||||
|
- **Tier:** 2
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** low — LEFT JOIN queries
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Two reports:
|
||||||
|
1. Merged MRs with no entity_references at all (no "closes", no "mentioned",
|
||||||
|
no "related") — orphan MRs with no issue traceability
|
||||||
|
2. Closed issues with no MR reference — issues closed manually without code change
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Process compliance metric. Unlinked MRs mean lost traceability — you can't trace
|
||||||
|
a code change back to a requirement. Manually closed issues might mean work was done
|
||||||
|
outside the tracked process, or issues were closed prematurely.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
All exists today:
|
||||||
|
- `merge_requests` (state, merged_at)
|
||||||
|
- `issues` (state, closed/updated_at)
|
||||||
|
- `entity_references` (for join/anti-join)
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Orphan merged MRs (no references at all)
|
||||||
|
SELECT mr.iid, mr.title, mr.author_username, mr.merged_at,
|
||||||
|
p.path_with_namespace
|
||||||
|
FROM merge_requests mr
|
||||||
|
JOIN projects p ON mr.project_id = p.id
|
||||||
|
LEFT JOIN entity_references er
|
||||||
|
ON er.source_entity_type = 'merge_request' AND er.source_entity_id = mr.id
|
||||||
|
WHERE mr.state = 'merged'
|
||||||
|
AND mr.merged_at >= ?1
|
||||||
|
AND er.id IS NULL
|
||||||
|
ORDER BY mr.merged_at DESC;
|
||||||
|
|
||||||
|
-- Closed issues with no MR reference
|
||||||
|
SELECT i.iid, i.title, i.author_username, i.updated_at,
|
||||||
|
p.path_with_namespace
|
||||||
|
FROM issues i
|
||||||
|
JOIN projects p ON i.project_id = p.id
|
||||||
|
LEFT JOIN entity_references er
|
||||||
|
ON er.target_entity_type = 'issue' AND er.target_entity_id = i.id
|
||||||
|
AND er.source_entity_type = 'merge_request'
|
||||||
|
WHERE i.state = 'closed'
|
||||||
|
AND i.updated_at >= ?1
|
||||||
|
AND er.id IS NULL
|
||||||
|
ORDER BY i.updated_at DESC;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Human Output
|
||||||
|
|
||||||
|
```
|
||||||
|
Unlinked MRs (merged with no issue reference, last 30 days)
|
||||||
|
|
||||||
|
!245 Fix typo in README (alice, merged 2d ago)
|
||||||
|
!239 Update CI pipeline (bob, merged 1w ago)
|
||||||
|
!236 Bump dependency versions (charlie, merged 2w ago)
|
||||||
|
|
||||||
|
Orphan Closed Issues (closed without any MR, last 30 days)
|
||||||
|
|
||||||
|
#92 Update documentation for v2 (closed by dave, 3d ago)
|
||||||
|
#88 Investigate memory usage (closed by eve, 2w ago)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Some MRs legitimately don't reference issues (chores, CI fixes, dependency bumps)
|
||||||
|
- Some issues are legitimately closed without code (questions, duplicates, won't-fix)
|
||||||
|
- Noise level depends on team discipline
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore unlinked --ignore-labels "chore,ci"` — filter out expected orphans
|
||||||
|
- Compliance score: % of MRs with issue links over time (trend metric)
|
||||||
102
docs/ideas/weekly-digest.md
Normal file
102
docs/ideas/weekly-digest.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Weekly Digest Generator
|
||||||
|
|
||||||
|
- **Command:** `lore weekly [--since <date>]`
|
||||||
|
- **Confidence:** 90%
|
||||||
|
- **Tier:** 1
|
||||||
|
- **Status:** proposed
|
||||||
|
- **Effort:** medium — builds on digest infrastructure, adds markdown formatting
|
||||||
|
|
||||||
|
## What
|
||||||
|
|
||||||
|
Auto-generate a markdown document summarizing the week: MRs merged (grouped by
|
||||||
|
project), issues closed, new issues opened, ongoing discussions, milestone progress.
|
||||||
|
Formatted for pasting into Slack, email, or team standup notes.
|
||||||
|
|
||||||
|
Default window is 7 days. `--since` overrides.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
|
||||||
|
Every team lead writes a weekly status update. This writes itself from the data.
|
||||||
|
Leverages everything gitlore has ingested. Saves 30-60 minutes of manual summarization
|
||||||
|
per week.
|
||||||
|
|
||||||
|
## Data Required
|
||||||
|
|
||||||
|
Same as digest (all exists today):
|
||||||
|
- `resource_state_events`, `merge_requests`, `issues`, `discussions`
|
||||||
|
- `milestones` for progress tracking
|
||||||
|
|
||||||
|
## Implementation Sketch
|
||||||
|
|
||||||
|
This is essentially `lore digest --since 7d --format markdown` with:
|
||||||
|
1. Section headers for each category
|
||||||
|
2. Milestone progress bars (X/Y issues closed)
|
||||||
|
3. "Highlights" section with the most-discussed items
|
||||||
|
4. "Risks" section with overdue issues and stale MRs
|
||||||
|
|
||||||
|
### Markdown Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Weekly Summary — Jan 20-27, 2025
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
- **!234** Refactor auth middleware merged (12 discussions, 4 reviewers)
|
||||||
|
- **#95** New critical bug: Rate limiting returns 500
|
||||||
|
|
||||||
|
## Merged (3)
|
||||||
|
| MR | Title | Author | Reviewers |
|
||||||
|
|----|-------|--------|-----------|
|
||||||
|
| !234 | Refactor auth middleware | alice | bob, charlie |
|
||||||
|
| !231 | Fix connection pool leak | bob | alice |
|
||||||
|
| !45 | Update dashboard layout | eve | dave |
|
||||||
|
|
||||||
|
## Closed Issues (2)
|
||||||
|
- **#89** Login timeout on slow networks (closed by alice)
|
||||||
|
- **#87** Stale cache headers (closed by bob)
|
||||||
|
|
||||||
|
## New Issues (3)
|
||||||
|
- **#95** Rate limiting returns 500 (priority::high, assigned to charlie)
|
||||||
|
- **#94** Add rate limit documentation (priority::low)
|
||||||
|
- **#93** Flaky test in CI pipeline (assigned to dave)
|
||||||
|
|
||||||
|
## Milestone Progress
|
||||||
|
- **v2.0** — 14/20 issues closed (70%) — due Feb 15
|
||||||
|
- **v1.9-hotfix** — 3/3 issues closed (100%) — COMPLETE
|
||||||
|
|
||||||
|
## Active Discussions
|
||||||
|
- **#90** 8 new comments this week (needs-review)
|
||||||
|
- **!230** 5 review threads unresolved
|
||||||
|
```
|
||||||
|
|
||||||
|
## Robot Mode Output
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ok": true,
|
||||||
|
"data": {
|
||||||
|
"period": { "from": "2025-01-20", "to": "2025-01-27" },
|
||||||
|
"merged_count": 3,
|
||||||
|
"closed_count": 2,
|
||||||
|
"opened_count": 3,
|
||||||
|
"highlights": [...],
|
||||||
|
"merged": [...],
|
||||||
|
"closed": [...],
|
||||||
|
"opened": [...],
|
||||||
|
"milestones": [...],
|
||||||
|
"active_discussions": [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downsides
|
||||||
|
|
||||||
|
- Formatting preferences vary by team; hard to please everyone
|
||||||
|
- "Highlights" ranking is heuristic (discussion count as proxy for importance)
|
||||||
|
- Doesn't capture work done outside GitLab
|
||||||
|
|
||||||
|
## Extensions
|
||||||
|
|
||||||
|
- `lore weekly --project group/backend` — single project scope
|
||||||
|
- `lore weekly --author alice` — personal weekly summary
|
||||||
|
- `lore weekly --output weekly.md` — write to file
|
||||||
|
- Scheduled generation via cron + robot mode
|
||||||
140
docs/issues/001-timeline-missing-project-in-entity-ref.md
Normal file
140
docs/issues/001-timeline-missing-project-in-entity-ref.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# 001: Timeline human output omits project path from entity references
|
||||||
|
|
||||||
|
- **Severity:** medium
|
||||||
|
- **Component:** `src/cli/commands/timeline.rs`
|
||||||
|
- **Status:** open
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The `lore timeline` human-readable output renders entity references as bare `#42` or
|
||||||
|
`!234` without the project path. When multiple projects are synced, this makes the
|
||||||
|
output ambiguous — issue `#42` in `group/backend` and `#42` in `group/frontend` are
|
||||||
|
indistinguishable.
|
||||||
|
|
||||||
|
### Affected code
|
||||||
|
|
||||||
|
`format_entity_ref` at `src/cli/commands/timeline.rs:201-207`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn format_entity_ref(entity_type: &str, iid: i64) -> String {
|
||||||
|
match entity_type {
|
||||||
|
"issue" => format!("#{iid}"),
|
||||||
|
"merge_request" => format!("!{iid}"),
|
||||||
|
_ => format!("{entity_type}:{iid}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is called in three places:
|
||||||
|
|
||||||
|
1. **Event lines** (`print_timeline_event`, line 130) — each event row shows `#42`
|
||||||
|
with no project context
|
||||||
|
2. **Footer seed list** (`print_timeline_footer`, line 161) — seed entities listed as
|
||||||
|
`#42, !234` with no project disambiguation
|
||||||
|
3. **Collect stage summaries** (`timeline_collect.rs:107`) — the `summary` field itself
|
||||||
|
bakes in `"Issue #42 created: ..."` without project
|
||||||
|
|
||||||
|
### Current output (ambiguous)
|
||||||
|
|
||||||
|
```
|
||||||
|
2025-01-20 CREATED #42 Issue #42 created: Login timeout bug @alice
|
||||||
|
2025-01-21 LABEL+ #42 Label added: priority::high @dave
|
||||||
|
2025-01-22 CREATED !234 MR !234 created: Refactor auth middleware @alice
|
||||||
|
2025-01-25 MERGED !234 MR !234 merged @bob
|
||||||
|
|
||||||
|
Seed entities: #42, !234
|
||||||
|
```
|
||||||
|
|
||||||
|
When multiple projects are synced, a reader cannot tell which project `#42` belongs to.
|
||||||
|
|
||||||
|
## Robot mode is partially affected
|
||||||
|
|
||||||
|
The robot JSON output (`EventJson`, line 387-416) DOES include a `project` field per
|
||||||
|
event, so programmatic consumers can disambiguate. However, the `summary` string field
|
||||||
|
still bakes in bare `#42` without project context, which is misleading if an agent uses
|
||||||
|
the summary for display.
|
||||||
|
|
||||||
|
## Proposed fix
|
||||||
|
|
||||||
|
### 1. Add project to `format_entity_ref`
|
||||||
|
|
||||||
|
Pass `project_path` into `format_entity_ref` and use GitLab's full reference format:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn format_entity_ref(entity_type: &str, iid: i64, project_path: &str) -> String {
|
||||||
|
match entity_type {
|
||||||
|
"issue" => format!("{project_path}#{iid}"),
|
||||||
|
"merge_request" => format!("{project_path}!{iid}"),
|
||||||
|
_ => format!("{project_path}/{entity_type}:{iid}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Smart elision for single-project timelines
|
||||||
|
|
||||||
|
When all events belong to the same project, the full path is visual noise. Detect
|
||||||
|
this and fall back to bare `#42` / `!234`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn should_show_project(events: &[TimelineEvent]) -> bool {
|
||||||
|
let mut projects = events.iter().map(|e| &e.project_path).collect::<HashSet<_>>();
|
||||||
|
projects.len() > 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then conditionally format:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let entity_ref = if show_project {
|
||||||
|
format_entity_ref(&event.entity_type, event.entity_iid, &event.project_path)
|
||||||
|
} else {
|
||||||
|
format_entity_ref_short(&event.entity_type, event.entity_iid)
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Fix summary strings in collect stage
|
||||||
|
|
||||||
|
`timeline_collect.rs:107` bakes the summary as `"Issue #42 created: title"`. This
|
||||||
|
should include the project when multi-project:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let prefix = if multi_project {
|
||||||
|
format!("{type_label} {project_path}#{iid}")
|
||||||
|
} else {
|
||||||
|
format!("{type_label} #{iid}")
|
||||||
|
};
|
||||||
|
summary = format!("{prefix} created: {title_str}");
|
||||||
|
```
|
||||||
|
|
||||||
|
Same pattern for the merge summary at lines 317 and 347.
|
||||||
|
|
||||||
|
### 4. Update footer seed list
|
||||||
|
|
||||||
|
`print_timeline_footer` (line 155-164) should also use the project-aware format:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
result.seed_entities.iter()
|
||||||
|
.map(|e| format_entity_ref(&e.entity_type, e.entity_iid, &e.project_path))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Expected output after fix
|
||||||
|
|
||||||
|
### Single project (no change)
|
||||||
|
|
||||||
|
```
|
||||||
|
2025-01-20 CREATED #42 Issue #42 created: Login timeout bug @alice
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-project (project path added)
|
||||||
|
|
||||||
|
```
|
||||||
|
2025-01-20 CREATED group/backend#42 Issue group/backend#42 created: Login timeout @alice
|
||||||
|
2025-01-22 CREATED group/frontend#42 Issue group/frontend#42 created: Broken layout @eve
|
||||||
|
```
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Human output: ambiguous for multi-project users (the primary use case for gitlore)
|
||||||
|
- Robot output: summary field misleading, but `project` field provides workaround
|
||||||
|
- Timeline footer: seed entity list ambiguous
|
||||||
|
- Collect-stage summaries: baked-in bare references propagate to both renderers
|
||||||
290
docs/lore-me-spec.md
Normal file
290
docs/lore-me-spec.md
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
# `lore me` — Personal Work Dashboard
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A personal dashboard command that shows everything relevant to the configured user: open issues, authored MRs, MRs under review, and recent activity. Attention state is computed from GitLab interaction data (comments) with no local state tracking.
|
||||||
|
|
||||||
|
## Command Interface
|
||||||
|
|
||||||
|
```
|
||||||
|
lore me # Full dashboard (default project or all)
|
||||||
|
lore me --issues # Issues section only
|
||||||
|
lore me --mrs # MRs section only (authored + reviewing)
|
||||||
|
lore me --activity # Activity feed only
|
||||||
|
lore me --issues --mrs # Multiple sections (combinable)
|
||||||
|
lore me --all # All synced projects (overrides default_project)
|
||||||
|
lore me --since 2d # Activity window (default: 30d)
|
||||||
|
lore me --project group/repo # Scope to one project
|
||||||
|
lore me --user jdoe # Override configured username
|
||||||
|
```
|
||||||
|
|
||||||
|
Standard global flags: `--robot`/`-J`, `--fields`, `--color`, `--icons`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
### AC-1: Configuration
|
||||||
|
|
||||||
|
- **AC-1.1**: New optional field `gitlab.username` (string) in config.json
|
||||||
|
- **AC-1.2**: Resolution order: `--user` CLI flag > `config.gitlab.username` > exit code 2 with actionable error message suggesting how to set it
|
||||||
|
- **AC-1.3**: Username is case-sensitive (matches GitLab usernames exactly)
|
||||||
|
|
||||||
|
### AC-2: Command Interface
|
||||||
|
|
||||||
|
- **AC-2.1**: New command `lore me` — single command with flags (matches `who` pattern)
|
||||||
|
- **AC-2.2**: Section filter flags: `--issues`, `--mrs`, `--activity` — combinable. Passing multiple shows those sections. No flags = full dashboard (all sections).
|
||||||
|
- **AC-2.3**: `--since <duration>` controls activity feed window, default 30 days. Only affects the activity section; work item sections always show all open items regardless of `--since`.
|
||||||
|
- **AC-2.4**: `--project <path>` scopes to a single project
|
||||||
|
- **AC-2.5**: `--user <username>` overrides configured username
|
||||||
|
- **AC-2.6**: `--all` flag shows all synced projects (overrides default_project)
|
||||||
|
- **AC-2.7**: `--project` and `--all` are mutually exclusive — passing both is exit code 2
|
||||||
|
- **AC-2.8**: Standard global flags: `--robot`/`-J`, `--fields`, `--color`, `--icons`
|
||||||
|
|
||||||
|
### AC-3: "My Items" Definition
|
||||||
|
|
||||||
|
- **AC-3.1**: Issues assigned to me (`issue_assignees.username`). Authorship alone does NOT qualify an issue.
|
||||||
|
- **AC-3.2**: MRs authored by me (`merge_requests.author_username`)
|
||||||
|
- **AC-3.3**: MRs where I'm a reviewer (`mr_reviewers.username`)
|
||||||
|
- **AC-3.4**: Scope is **Assigned (issues) + Authored/Reviewing (MRs)** — no participation/mention expansion
|
||||||
|
- **AC-3.5**: MR assignees (`mr_assignees`) are NOT used — in Pattern 1 workflows (author = assignee), this is redundant with authorship
|
||||||
|
- **AC-3.6**: Activity feed uses CURRENT association only — if you've been unassigned from an issue, activity on it no longer appears. This keeps the query simple and the feed relevant.
|
||||||
|
|
||||||
|
### AC-4: Attention State Model
|
||||||
|
|
||||||
|
- **AC-4.1**: Computed per-item from synced GitLab data, no local state tracking
|
||||||
|
- **AC-4.2**: Interaction signal: notes authored by the user (`notes.author_username = me` where `is_system = 0`)
|
||||||
|
- **AC-4.3**: Future: award emoji will extend interaction signals (separate bead)
|
||||||
|
- **AC-4.4**: States (evaluated in this order — first match wins):
|
||||||
|
1. `not_ready`: MR only — `draft=1` AND zero entries in `mr_reviewers`
|
||||||
|
2. `needs_attention`: Others' latest non-system note > user's latest non-system note
|
||||||
|
3. `stale`: Entity has at least one non-system note from someone, but the most recent note from anyone is older than 30 days. Items with ZERO notes are NOT stale — they're `not_started`.
|
||||||
|
4. `not_started`: User has zero non-system notes on this entity (regardless of whether others have commented)
|
||||||
|
5. `awaiting_response`: User's latest non-system note timestamp >= all others' latest non-system note timestamps (including when user is the only commenter)
|
||||||
|
- **AC-4.5**: Applied to all item types (issues, authored MRs, reviewing MRs)
|
||||||
|
|
||||||
|
### AC-5: Dashboard Sections
|
||||||
|
|
||||||
|
**AC-5.1: Open Issues**
|
||||||
|
- Source: `issue_assignees.username = me`, state = opened
|
||||||
|
- Fields: project path, iid, title, status_name (work item status), attention state, relative time since updated
|
||||||
|
- Sort: attention-first (needs_attention > not_started > awaiting_response > stale), then most recently updated within same state
|
||||||
|
- No limit, no truncation — show all
|
||||||
|
|
||||||
|
**AC-5.2: Open MRs — Authored**
|
||||||
|
- Source: `merge_requests.author_username = me`, state = opened
|
||||||
|
- Fields: project path, iid, title, draft indicator, detailed_merge_status, attention state, relative time
|
||||||
|
- Sort: same as issues
|
||||||
|
|
||||||
|
**AC-5.3: Open MRs — Reviewing**
|
||||||
|
- Source: `mr_reviewers.username = me`, state = opened
|
||||||
|
- Fields: project path, iid, title, MR author username, draft indicator, attention state, relative time
|
||||||
|
- Sort: same as issues
|
||||||
|
|
||||||
|
**AC-5.4: Activity Feed**
|
||||||
|
- Sources (all within `--since` window, default 30d):
|
||||||
|
- Human comments (`notes.is_system = 0`) on my items
|
||||||
|
- State events (`resource_state_events`) on my items
|
||||||
|
- Label events (`resource_label_events`) on my items
|
||||||
|
- Milestone events (`resource_milestone_events`) on my items
|
||||||
|
- Assignment/reviewer system notes (see AC-12 for patterns) on my items
|
||||||
|
- "My items" for the activity feed = items I'm CURRENTLY associated with per AC-3 (current assignment state, not historical)
|
||||||
|
- Includes activity on items regardless of open/closed state
|
||||||
|
- Own actions included but flagged (`is_own: true` in robot, `(you)` suffix + dimmed in human)
|
||||||
|
- Sort: newest first (chronological descending)
|
||||||
|
- No limit, no truncation — show all events
|
||||||
|
|
||||||
|
**AC-5.5: Summary Header**
|
||||||
|
- Counts: projects, open issues, authored MRs, reviewing MRs, needs_attention count
|
||||||
|
- Attention legend (human mode): icon + label for each state
|
||||||
|
|
||||||
|
### AC-6: Human Output — Visual Design
|
||||||
|
|
||||||
|
**AC-6.1: Layout**
|
||||||
|
- Section card style with `section_divider` headers
|
||||||
|
- Legend at top explains attention icons
|
||||||
|
- Two-line per item: main data on line 1, project path on line 2 (indented)
|
||||||
|
- When scoped to single project (`--project`), suppress project path line (redundant)
|
||||||
|
|
||||||
|
**AC-6.2: Attention Icons (three tiers)**
|
||||||
|
|
||||||
|
| State | Nerd Font | Unicode | ASCII | Color |
|
||||||
|
|-------|-----------|---------|-------|-------|
|
||||||
|
| needs_attention | `\uf0f3` bell | `◆` | `[!]` | amber (warning) |
|
||||||
|
| not_started | `\uf005` star | `★` | `[*]` | cyan (info) |
|
||||||
|
| awaiting_response | `\uf017` clock | `◷` | `[~]` | dim (muted) |
|
||||||
|
| stale | `\uf54c` skull | `☠` | `[x]` | dim (muted) |
|
||||||
|
|
||||||
|
**AC-6.3: Color Vocabulary** (matches existing lore palette)
|
||||||
|
- Issue refs (#N): cyan
|
||||||
|
- MR refs (!N): purple
|
||||||
|
- Usernames (@name): cyan
|
||||||
|
- Opened state: green
|
||||||
|
- Merged state: purple
|
||||||
|
- Closed state: dim
|
||||||
|
- Draft indicator: gray
|
||||||
|
- Own actions: dimmed + `(you)` suffix
|
||||||
|
- Timestamps: dim (relative time)
|
||||||
|
|
||||||
|
**AC-6.4: Activity Event Badges**
|
||||||
|
|
||||||
|
| Event | Nerd/Unicode (colored bg) | ASCII fallback |
|
||||||
|
|-------|--------------------------|----------------|
|
||||||
|
| note | cyan bg, dark text | `[note]` cyan text |
|
||||||
|
| status | amber bg, dark text | `[status]` amber text |
|
||||||
|
| label | purple bg, white text | `[label]` purple text |
|
||||||
|
| assign | green bg, dark text | `[assign]` green text |
|
||||||
|
| milestone | magenta bg, white text | `[milestone]` magenta text |
|
||||||
|
|
||||||
|
Fallback: when background colors aren't available (ASCII mode), use colored text with brackets instead of background pills.
|
||||||
|
|
||||||
|
**AC-6.5: Labels**
|
||||||
|
- Human mode: not shown
|
||||||
|
- Robot mode: included in JSON
|
||||||
|
|
||||||
|
### AC-7: Robot Output
|
||||||
|
|
||||||
|
- **AC-7.1**: Standard `{ok, data, meta}` envelope
|
||||||
|
- **AC-7.2**: `data` contains: `username`, `since_iso`, `summary` (counts + `needs_attention_count`), `open_issues[]`, `open_mrs_authored[]`, `reviewing_mrs[]`, `activity[]`
|
||||||
|
- **AC-7.3**: Each item includes: project, iid, title, state, attention_state (programmatic: `needs_attention`, `not_started`, `awaiting_response`, `stale`, `not_ready`), labels, updated_at_iso, web_url
|
||||||
|
- **AC-7.4**: Issues include `status_name` (work item status)
|
||||||
|
- **AC-7.5**: MRs include `draft`, `detailed_merge_status`, `author_username` (reviewing section)
|
||||||
|
- **AC-7.6**: Activity items include: `timestamp_iso`, `event_type`, `entity_type`, `entity_iid`, `project`, `actor`, `is_own`, `summary`, `body_preview` (for notes, truncated to 200 chars)
|
||||||
|
- **AC-7.7**: `--fields minimal` preset: `iid`, `title`, `attention_state`, `updated_at_iso` (work items); `timestamp_iso`, `event_type`, `entity_iid`, `actor` (activity)
|
||||||
|
- **AC-7.8**: Metadata-only depth — agents drill into specific items with `timeline`, `issues`, `mrs` for full context
|
||||||
|
- **AC-7.9**: No limits, no truncation on any array
|
||||||
|
|
||||||
|
### AC-8: Cross-Project Behavior
|
||||||
|
|
||||||
|
- **AC-8.1**: If `config.default_project` is set, scope to that project by default. If no default project, show all synced projects.
|
||||||
|
- **AC-8.2**: `--all` flag overrides default project and shows all synced projects
|
||||||
|
- **AC-8.3**: `--project` flag narrows to a specific project (supports fuzzy match like other commands)
|
||||||
|
- **AC-8.4**: `--project` and `--all` are mutually exclusive (exit 2 if both passed)
|
||||||
|
- **AC-8.5**: Project path shown per-item in both human and robot output (suppressed in human when single-project scoped per AC-6.1)
|
||||||
|
|
||||||
|
### AC-9: Sort Order
|
||||||
|
|
||||||
|
- **AC-9.1**: Work item sections: attention-first, then most recently updated
|
||||||
|
- **AC-9.2**: Attention priority: `needs_attention` > `not_started` > `awaiting_response` > `stale` > `not_ready`
|
||||||
|
- **AC-9.3**: Activity feed: chronological descending (newest first)
|
||||||
|
|
||||||
|
### AC-10: Error Handling
|
||||||
|
|
||||||
|
- **AC-10.1**: No username configured and no `--user` flag → exit 2 with suggestion
|
||||||
|
- **AC-10.2**: No synced data → exit 17 with suggestion to run `lore sync`
|
||||||
|
- **AC-10.3**: Username found but no matching items → empty sections with summary showing zeros
|
||||||
|
- **AC-10.4**: `--project` and `--all` both passed → exit 2 with message
|
||||||
|
|
||||||
|
### AC-11: Relationship to Existing Commands
|
||||||
|
|
||||||
|
- **AC-11.1**: `who @username` remains for looking at anyone's workload
|
||||||
|
- **AC-11.2**: `lore me` is the self-view with attention intelligence
|
||||||
|
- **AC-11.3**: No deprecation of `who` — they serve different purposes
|
||||||
|
|
||||||
|
### AC-12: New Assignments Detection
|
||||||
|
|
||||||
|
- **AC-12.1**: Detect from system notes (`notes.is_system = 1`) matching these body patterns:
|
||||||
|
- `"assigned to @username"` — issue/MR assignment
|
||||||
|
- `"unassigned @username"` — removal (shown as `unassign` event type)
|
||||||
|
- `"requested review from @username"` — reviewer assignment (shown as `review_request` event type)
|
||||||
|
- **AC-12.2**: These appear in the activity feed with appropriate event types
|
||||||
|
- **AC-12.3**: Shows who performed the action (note author from the associated non-system context, or "system" if unavailable) and when (note created_at)
|
||||||
|
- **AC-12.4**: Pattern matching is case-insensitive and matches username at word boundary
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Out of Scope (Follow-Up Work)
|
||||||
|
|
||||||
|
- **Award emoji sync**: Extends attention signal with reaction timestamps. Requires new table + GitLab REST API integration. Note-level emoji sync has N+1 concern requiring smart batching.
|
||||||
|
- **Participation/mention expansion**: Broadening "my items" beyond assigned+authored.
|
||||||
|
- **Label filtering**: `--label` flag to scope dashboard by label.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Design Notes
|
||||||
|
|
||||||
|
### Why No High-Water Mark
|
||||||
|
|
||||||
|
GitLab itself is the source of truth for "what I've engaged with." The attention state is computed by comparing the user's latest comment timestamp against others' latest comment timestamps on each item. No local cursor or mark is needed.
|
||||||
|
|
||||||
|
### Why Comments-Only (For Now)
|
||||||
|
|
||||||
|
Award emoji (reactions) are a valid "I've engaged" signal but aren't currently synced. The attention model is designed to incorporate emoji timestamps when available — adding them later requires no model changes.
|
||||||
|
|
||||||
|
### Why MR Assignees Are Excluded
|
||||||
|
|
||||||
|
GitLab MR workflows have three role fields: Author, Assignee, and Reviewer. In Pattern 1 workflows (the most common post-2020), the author assigns themselves — making assignee redundant with authorship. The Reviewing section uses `mr_reviewers` as the review signal.
|
||||||
|
|
||||||
|
### Attention State Evaluation Order
|
||||||
|
|
||||||
|
States are evaluated in priority order (first match wins):
|
||||||
|
|
||||||
|
```
|
||||||
|
1. not_ready — MR-only: draft=1 AND no reviewers
|
||||||
|
2. needs_attention — others commented after me
|
||||||
|
3. stale — had activity, but nothing in 30d (NOT for zero-comment items)
|
||||||
|
4. not_started — I have zero comments (may or may not have others' comments)
|
||||||
|
5. awaiting_response — I commented last (or I'm the only commenter)
|
||||||
|
```
|
||||||
|
|
||||||
|
Edge cases:
|
||||||
|
- Zero comments from anyone → `not_started` (NOT stale)
|
||||||
|
- Only my comments, none from others → `awaiting_response`
|
||||||
|
- Only others' comments, none from me → `not_started` (I haven't engaged)
|
||||||
|
- Wait: this conflicts with `needs_attention` (step 2). If others have commented and I haven't, then others' latest > my latest (NULL). This should be `needs_attention`, not `not_started`.
|
||||||
|
|
||||||
|
Corrected logic:
|
||||||
|
- `needs_attention` takes priority over `not_started` when others HAVE commented but I haven't. The distinction: `not_started` only applies when NOBODY has commented.
|
||||||
|
|
||||||
|
```
|
||||||
|
1. not_ready — MR-only: draft=1 AND no reviewers
|
||||||
|
2. needs_attention — others have non-system notes AND (I have none OR others' latest > my latest)
|
||||||
|
3. stale — latest note from anyone is older than 30 days
|
||||||
|
4. awaiting_response — my latest >= others' latest (I'm caught up)
|
||||||
|
5. not_started — zero non-system notes from anyone
|
||||||
|
```
|
||||||
|
|
||||||
|
### Attention State Computation (SQL Sketch)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
WITH my_latest AS (
|
||||||
|
SELECT d.issue_id, d.merge_request_id, MAX(n.created_at) AS ts
|
||||||
|
FROM notes n
|
||||||
|
JOIN discussions d ON n.discussion_id = d.id
|
||||||
|
WHERE n.author_username = ?me AND n.is_system = 0
|
||||||
|
GROUP BY d.issue_id, d.merge_request_id
|
||||||
|
),
|
||||||
|
others_latest AS (
|
||||||
|
SELECT d.issue_id, d.merge_request_id, MAX(n.created_at) AS ts
|
||||||
|
FROM notes n
|
||||||
|
JOIN discussions d ON n.discussion_id = d.id
|
||||||
|
WHERE n.author_username != ?me AND n.is_system = 0
|
||||||
|
GROUP BY d.issue_id, d.merge_request_id
|
||||||
|
),
|
||||||
|
any_latest AS (
|
||||||
|
SELECT d.issue_id, d.merge_request_id, MAX(n.created_at) AS ts
|
||||||
|
FROM notes n
|
||||||
|
JOIN discussions d ON n.discussion_id = d.id
|
||||||
|
WHERE n.is_system = 0
|
||||||
|
GROUP BY d.issue_id, d.merge_request_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
CASE
|
||||||
|
-- MR-only: draft with no reviewers
|
||||||
|
WHEN entity_type = 'mr' AND draft = 1
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM mr_reviewers WHERE merge_request_id = entity_id)
|
||||||
|
THEN 'not_ready'
|
||||||
|
-- Others commented and I haven't caught up (or never engaged)
|
||||||
|
WHEN others.ts IS NOT NULL AND (my.ts IS NULL OR others.ts > my.ts)
|
||||||
|
THEN 'needs_attention'
|
||||||
|
-- Had activity but gone quiet for 30d
|
||||||
|
WHEN any.ts IS NOT NULL AND any.ts < ?now_minus_30d
|
||||||
|
THEN 'stale'
|
||||||
|
-- I've responded and I'm caught up
|
||||||
|
WHEN my.ts IS NOT NULL AND my.ts >= COALESCE(others.ts, 0)
|
||||||
|
THEN 'awaiting_response'
|
||||||
|
-- Nobody has commented at all
|
||||||
|
ELSE 'not_started'
|
||||||
|
END AS attention_state
|
||||||
|
FROM ...
|
||||||
|
```
|
||||||
179
docs/performance-audit-2026-02-12.md
Normal file
179
docs/performance-audit-2026-02-12.md
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# Deep Performance Audit Report
|
||||||
|
|
||||||
|
**Date:** 2026-02-12
|
||||||
|
**Branch:** `perf-audit` (e9bacc94)
|
||||||
|
**Parent:** `039ab1c2` (master, v0.6.1)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Methodology
|
||||||
|
|
||||||
|
1. **Baseline** — measured p50/p95 latency for all major commands with warm cache
|
||||||
|
2. **Profile** — used macOS `sample` profiler and `EXPLAIN QUERY PLAN` to identify hotspots
|
||||||
|
3. **Golden output** — captured exact numeric outputs before changes as equivalence oracle
|
||||||
|
4. **One lever per change** — each optimization isolated and independently benchmarked
|
||||||
|
5. **Revert threshold** — any optimization <1.1x speedup reverted per audit rules
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Baseline Measurements (warm cache, release build)
|
||||||
|
|
||||||
|
| Command | Latency | Notes |
|
||||||
|
|---------|---------|-------|
|
||||||
|
| `who --path src/core/db.rs` (expert) | 2200ms | **Hotspot** |
|
||||||
|
| `who --active` | 83-93ms | Acceptable |
|
||||||
|
| `who workload` | 22ms | Fast |
|
||||||
|
| `stats` | 107-112ms | **Hotspot** |
|
||||||
|
| `search "authentication"` | 1030ms | **Hotspot** (library-level) |
|
||||||
|
| `list issues -n 50` | ~40ms | Fast |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Optimization 1: INDEXED BY for DiffNote Queries
|
||||||
|
|
||||||
|
**Target:** `src/cli/commands/who.rs` — expert and reviews query paths
|
||||||
|
|
||||||
|
**Problem:** SQLite query planner chose `idx_notes_system` (38% selectivity, 106K rows) over `idx_notes_diffnote_path_created` (9.3% selectivity, 26K rows) for path-filtered DiffNote queries. The partial index `WHERE noteable_type = 'MergeRequest' AND type = 'DiffNote'` is far more selective but the planner's cost model didn't pick it.
|
||||||
|
|
||||||
|
**Change:** Added `INDEXED BY idx_notes_diffnote_path_created` to all 8 SQL queries across `query_expert`, `query_expert_details`, `query_reviews`, `build_path_query` (probes 1 & 2), and `suffix_probe`.
|
||||||
|
|
||||||
|
**Results:**
|
||||||
|
|
||||||
|
| Query | Before | After | Speedup |
|
||||||
|
|-------|--------|-------|---------|
|
||||||
|
| expert (specific path) | 2200ms | 56-58ms | **38x** |
|
||||||
|
| expert (broad path) | 2200ms | 83ms | **26x** |
|
||||||
|
| reviews | 1800ms | 24ms | **75x** |
|
||||||
|
|
||||||
|
**Isomorphism proof:** `INDEXED BY` only changes which index the planner uses, not the query semantics. Same rows matched, same ordering, same output. Verified by golden output comparison across 5+ runs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Optimization 2: Conditional Aggregates in Stats
|
||||||
|
|
||||||
|
**Target:** `src/cli/commands/stats.rs`
|
||||||
|
|
||||||
|
**Problem:** 12+ sequential `COUNT(*)` queries each requiring a full table scan of `documents` (61K rows). Each scan touched the same pages but couldn't share work.
|
||||||
|
|
||||||
|
**Changes:**
|
||||||
|
- Documents: 5 sequential COUNTs -> 1 query with `SUM(CASE WHEN ... THEN 1 END)`
|
||||||
|
- FTS count: `SELECT COUNT(*) FROM documents_fts` (virtual table, slow) -> `SELECT COUNT(*) FROM documents_fts_docsize` (shadow B-tree table, 19x faster)
|
||||||
|
- Embeddings: 2 queries -> 1 with `COUNT(DISTINCT document_id), COUNT(*)`
|
||||||
|
- Dirty sources: 2 queries -> 1 with conditional aggregates
|
||||||
|
- Pending fetches: 2 queries -> 1 each (discussions, dependents)
|
||||||
|
|
||||||
|
**Results:**
|
||||||
|
|
||||||
|
| Metric | Before | After | Speedup |
|
||||||
|
|--------|--------|-------|---------|
|
||||||
|
| Warm median | 112ms | 66ms | **1.70x** |
|
||||||
|
| Cold | 1220ms | ~700ms | ~1.7x |
|
||||||
|
|
||||||
|
**Golden output verified:**
|
||||||
|
|
||||||
|
```
|
||||||
|
total:61652, issues:8241, mrs:10018, discussions:43393, truncated:63
|
||||||
|
fts:61652, embedded:61652, chunks:88161
|
||||||
|
```
|
||||||
|
|
||||||
|
All values match exactly across before/after runs.
|
||||||
|
|
||||||
|
**Isomorphism proof:** `SUM(CASE WHEN x THEN 1 END)` is algebraically identical to `COUNT(*) WHERE x`. The FTS5 shadow table `documents_fts_docsize` has exactly one row per FTS document by SQLite specification, so `COUNT(*)` on it equals the virtual table count.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Investigation: Two-Phase FTS Search (REVERTED)
|
||||||
|
|
||||||
|
**Target:** `src/search/fts.rs`, `src/cli/commands/search.rs`
|
||||||
|
|
||||||
|
**Hypothesis:** FTS5 `snippet()` generation is expensive. Splitting search into Phase 1 (score-only MATCH+bm25) and Phase 2 (snippet for filtered results only) should reduce work.
|
||||||
|
|
||||||
|
**Implementation:** Created `fetch_fts_snippets()` that retrieves snippets only for post-filter document IDs via `json_each()` join.
|
||||||
|
|
||||||
|
**Results:**
|
||||||
|
|
||||||
|
| Metric | Before | After | Improvement |
|
||||||
|
|--------|--------|-------|-------------|
|
||||||
|
| search (limit 20) | 1030ms | 995ms | 3.5% |
|
||||||
|
|
||||||
|
**Decision:** Reverted. Per audit rules, <1.1x speedup does not justify added code complexity.
|
||||||
|
|
||||||
|
**Root cause:** The bottleneck is not snippet generation but `MATCH` + `bm25()` scoring itself. Profiling showed `strspn` (FTS5 tokenizer) and `memmove` as the top CPU consumers. The same query runs in 30ms on system sqlite3 but 1030ms in rusqlite's bundled SQLite — a ~125x gap despite both being SQLite 3.51.x compiled at -O3.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Library-Level Finding: Bundled SQLite FTS5 Performance
|
||||||
|
|
||||||
|
**Observation:** FTS5 MATCH+bm25 queries are ~125x slower in rusqlite's bundled SQLite vs system sqlite3.
|
||||||
|
|
||||||
|
| Environment | Query Time | Notes |
|
||||||
|
|-------------|-----------|-------|
|
||||||
|
| System sqlite3 (macOS) | 30ms (with snippet), 8ms (without) | Same .db file |
|
||||||
|
| rusqlite bundled | 1030ms | `features = ["bundled"]`, OPT_LEVEL=3 |
|
||||||
|
|
||||||
|
**Profiler data (macOS `sample`):**
|
||||||
|
- Top hotspot: `strspn` in FTS5 tokenizer
|
||||||
|
- Secondary: `memmove` in FTS5 internals
|
||||||
|
- Scaling: ~5ms per result (limit 5 = 497ms, limit 20 = 995ms)
|
||||||
|
|
||||||
|
**Possible causes:**
|
||||||
|
- Bundled SQLite compiled without platform-specific optimizations (SIMD, etc.)
|
||||||
|
- Different memory allocator behavior
|
||||||
|
- Missing compile-time tuning flags
|
||||||
|
|
||||||
|
**Recommendation for future:** Investigate switching from `features = ["bundled"]` to system SQLite linkage, or audit the bundled compile flags in the `libsqlite3-sys` build script.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exploration Agent Findings (Informational)
|
||||||
|
|
||||||
|
Four parallel exploration agents surveyed the entire codebase. Key findings beyond what was already addressed:
|
||||||
|
|
||||||
|
### Ingestion Pipeline
|
||||||
|
- Serial DB writes in async context (acceptable — rusqlite is synchronous)
|
||||||
|
- Label ingestion uses individual inserts (potential batch optimization, low priority)
|
||||||
|
|
||||||
|
### CLI / GitLab Client
|
||||||
|
- GraphQL client recreated per call (`client.rs:98-100`) — caches connection pool, minor
|
||||||
|
- Double JSON deserialization in GraphQL responses — medium priority
|
||||||
|
- N+1 subqueries in `list` command (`list.rs:408-423`) — 4 correlated subqueries per row
|
||||||
|
|
||||||
|
### Search / Embedding
|
||||||
|
- No N+1 patterns, no O(n^2) algorithms
|
||||||
|
- Chunking is O(n) single-pass with proper UTF-8 safety
|
||||||
|
- Ollama concurrency model is sound (parallel HTTP, serial DB writes)
|
||||||
|
|
||||||
|
### Database / Documents
|
||||||
|
- O(n^2) prefix sum in `truncation.rs` — low traffic path
|
||||||
|
- String allocation patterns in extractors — micro-optimization territory
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opportunity Matrix
|
||||||
|
|
||||||
|
| Candidate | Impact | Confidence | Effort | Score | Status |
|
||||||
|
|-----------|--------|------------|--------|-------|--------|
|
||||||
|
| INDEXED BY for DiffNote | Very High | High | Low | **9.0** | Shipped |
|
||||||
|
| Stats conditional aggregates | Medium | High | Low | **7.0** | Shipped |
|
||||||
|
| Bundled SQLite FTS5 | Very High | Medium | High | 5.0 | Documented |
|
||||||
|
| List N+1 subqueries | Medium | Medium | Medium | 4.0 | Backlog |
|
||||||
|
| GraphQL double deser | Low | Medium | Low | 3.5 | Backlog |
|
||||||
|
| Truncation O(n^2) | Low | High | Low | 3.0 | Backlog |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `src/cli/commands/who.rs` | INDEXED BY hints on 8 SQL queries |
|
||||||
|
| `src/cli/commands/stats.rs` | Conditional aggregates, FTS5 shadow table, merged queries |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quality Gates
|
||||||
|
|
||||||
|
- All 603 tests pass
|
||||||
|
- `cargo clippy --all-targets -- -D warnings` clean
|
||||||
|
- `cargo fmt --check` clean
|
||||||
|
- Golden output verified for both optimizations
|
||||||
456
docs/phase-a-spec.md
Normal file
456
docs/phase-a-spec.md
Normal file
@@ -0,0 +1,456 @@
|
|||||||
|
# Phase A: Complete API Field Capture
|
||||||
|
|
||||||
|
> **Status:** Draft
|
||||||
|
> **Guiding principle:** Mirror everything GitLab gives us.
|
||||||
|
> - **Lossless mirror:** the raw API JSON stored behind `raw_payload_id`. This is the true complete representation of every API response.
|
||||||
|
> - **Relational projection:** a stable, query-optimized subset of fields we commit to keeping current on every re-sync.
|
||||||
|
> This preserves maximum context for processing and analysis while avoiding unbounded schema growth.
|
||||||
|
> **Migration:** 007_complete_field_capture.sql
|
||||||
|
> **Prerequisite:** None (independent of CP3)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
One migration. Three categories of work:
|
||||||
|
|
||||||
|
1. **New columns** on `issues` and `merge_requests` for fields currently dropped by serde or dropped during transform
|
||||||
|
2. **New serde fields** on `GitLabIssue` and `GitLabMergeRequest` to deserialize currently-silently-dropped JSON fields
|
||||||
|
3. **Transformer + insert updates** to pass the new fields through to the DB
|
||||||
|
|
||||||
|
No new tables. No new API calls. No new endpoints. All data comes from responses we already receive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issues: Field Gap Inventory
|
||||||
|
|
||||||
|
### Currently stored
|
||||||
|
id, iid, project_id, title, description, state, author_username, created_at, updated_at, web_url, due_date, milestone_id, milestone_title, raw_payload_id, last_seen_at, discussions_synced_for_updated_at, labels (junction), assignees (junction)
|
||||||
|
|
||||||
|
### Currently deserialized but dropped during transform
|
||||||
|
| API Field | Status | Action |
|
||||||
|
|-----------|--------|--------|
|
||||||
|
| `closed_at` | Deserialized in serde struct, but no DB column exists and transformer never populates it | Add column in migration 007, wire up in IssueRow + transform + INSERT |
|
||||||
|
| `author.id` | Deserialized | Store as `author_id` column |
|
||||||
|
| `author.name` | Deserialized | Store as `author_name` column |
|
||||||
|
|
||||||
|
### Currently silently dropped by serde (not in GitLabIssue struct)
|
||||||
|
| API Field | Type | DB Column | Notes |
|
||||||
|
|-----------|------|-----------|-------|
|
||||||
|
| `issue_type` | Option\<String\> | `issue_type` | Canonical field (lowercase, e.g. "issue"); preferred for DB storage |
|
||||||
|
| `upvotes` | i64 | `upvotes` | |
|
||||||
|
| `downvotes` | i64 | `downvotes` | |
|
||||||
|
| `user_notes_count` | i64 | `user_notes_count` | Useful for discussion sync optimization |
|
||||||
|
| `merge_requests_count` | i64 | `merge_requests_count` | Count of linked MRs |
|
||||||
|
| `confidential` | bool | `confidential` | 0/1 |
|
||||||
|
| `discussion_locked` | bool | `discussion_locked` | 0/1 |
|
||||||
|
| `weight` | Option\<i64\> | `weight` | Premium/Ultimate, null on Free |
|
||||||
|
| `time_stats.time_estimate` | i64 | `time_estimate` | Seconds |
|
||||||
|
| `time_stats.total_time_spent` | i64 | `time_spent` | Seconds |
|
||||||
|
| `time_stats.human_time_estimate` | Option\<String\> | `human_time_estimate` | e.g. "3h 30m" |
|
||||||
|
| `time_stats.human_total_time_spent` | Option\<String\> | `human_time_spent` | e.g. "1h 15m" |
|
||||||
|
| `task_completion_status.count` | i64 | `task_count` | Checkbox total |
|
||||||
|
| `task_completion_status.completed_count` | i64 | `task_completed_count` | Checkboxes checked |
|
||||||
|
| `has_tasks` | bool | `has_tasks` | 0/1 |
|
||||||
|
| `severity` | Option\<String\> | `severity` | Incident severity |
|
||||||
|
| `closed_by` | Option\<object\> | `closed_by_username` | Who closed it (username only, consistent with author pattern) |
|
||||||
|
| `imported` | bool | `imported` | 0/1 |
|
||||||
|
| `imported_from` | Option\<String\> | `imported_from` | Import source |
|
||||||
|
| `moved_to_id` | Option\<i64\> | `moved_to_id` | Target issue if moved |
|
||||||
|
| `references.short` | String | `references_short` | e.g. "#42" |
|
||||||
|
| `references.relative` | String | `references_relative` | e.g. "#42" or "group/proj#42" |
|
||||||
|
| `references.full` | String | `references_full` | e.g. "group/project#42" |
|
||||||
|
| `health_status` | Option\<String\> | `health_status` | Ultimate only |
|
||||||
|
| `type` | Option\<String\> | (transform-only) | Uppercase category (e.g. "ISSUE"); fallback for `issue_type` -- lowercased before storage. Not stored as separate column; raw JSON remains lossless. |
|
||||||
|
| `epic.id` | Option\<i64\> | `epic_id` | Premium/Ultimate, null on Free |
|
||||||
|
| `epic.iid` | Option\<i64\> | `epic_iid` | |
|
||||||
|
| `epic.title` | Option\<String\> | `epic_title` | |
|
||||||
|
| `epic.url` | Option\<String\> | `epic_url` | |
|
||||||
|
| `epic.group_id` | Option\<i64\> | `epic_group_id` | |
|
||||||
|
| `iteration.id` | Option\<i64\> | `iteration_id` | Premium/Ultimate, null on Free |
|
||||||
|
| `iteration.iid` | Option\<i64\> | `iteration_iid` | |
|
||||||
|
| `iteration.title` | Option\<String\> | `iteration_title` | |
|
||||||
|
| `iteration.state` | Option\<i64\> | `iteration_state` | Enum: 1=upcoming, 2=current, 3=closed |
|
||||||
|
| `iteration.start_date` | Option\<String\> | `iteration_start_date` | ISO date |
|
||||||
|
| `iteration.due_date` | Option\<String\> | `iteration_due_date` | ISO date |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Merge Requests: Field Gap Inventory
|
||||||
|
|
||||||
|
### Currently stored
|
||||||
|
id, iid, project_id, title, description, state, draft, author_username, source_branch, target_branch, head_sha, references_short, references_full, detailed_merge_status, merge_user_username, created_at, updated_at, merged_at, closed_at, last_seen_at, web_url, raw_payload_id, discussions_synced_for_updated_at, discussions_sync_last_attempt_at, discussions_sync_attempts, discussions_sync_last_error, labels (junction), assignees (junction), reviewers (junction)
|
||||||
|
|
||||||
|
### Currently deserialized but dropped during transform
|
||||||
|
| API Field | Status | Action |
|
||||||
|
|-----------|--------|--------|
|
||||||
|
| `author.id` | Deserialized | Store as `author_id` column |
|
||||||
|
| `author.name` | Deserialized | Store as `author_name` column |
|
||||||
|
| `work_in_progress` | Used transiently for `draft` fallback | Already handled, no change needed |
|
||||||
|
| `merge_status` (legacy) | Used transiently for `detailed_merge_status` fallback | Already handled, no change needed |
|
||||||
|
| `merged_by` | Used transiently for `merge_user` fallback | Already handled, no change needed |
|
||||||
|
|
||||||
|
### Currently silently dropped by serde (not in GitLabMergeRequest struct)
|
||||||
|
| API Field | Type | DB Column | Notes |
|
||||||
|
|-----------|------|-----------|-------|
|
||||||
|
| `upvotes` | i64 | `upvotes` | |
|
||||||
|
| `downvotes` | i64 | `downvotes` | |
|
||||||
|
| `user_notes_count` | i64 | `user_notes_count` | |
|
||||||
|
| `source_project_id` | i64 | `source_project_id` | Fork source |
|
||||||
|
| `target_project_id` | i64 | `target_project_id` | Fork target |
|
||||||
|
| `milestone` | Option\<object\> | `milestone_id`, `milestone_title` | Reuse issue milestone pattern |
|
||||||
|
| `merge_when_pipeline_succeeds` | bool | `merge_when_pipeline_succeeds` | 0/1, auto-merge flag |
|
||||||
|
| `merge_commit_sha` | Option\<String\> | `merge_commit_sha` | Commit ref after merge |
|
||||||
|
| `squash_commit_sha` | Option\<String\> | `squash_commit_sha` | Commit ref after squash |
|
||||||
|
| `discussion_locked` | bool | `discussion_locked` | 0/1 |
|
||||||
|
| `should_remove_source_branch` | Option\<bool\> | `should_remove_source_branch` | 0/1 |
|
||||||
|
| `force_remove_source_branch` | Option\<bool\> | `force_remove_source_branch` | 0/1 |
|
||||||
|
| `squash` | bool | `squash` | 0/1 |
|
||||||
|
| `squash_on_merge` | bool | `squash_on_merge` | 0/1 |
|
||||||
|
| `has_conflicts` | bool | `has_conflicts` | 0/1 |
|
||||||
|
| `blocking_discussions_resolved` | bool | `blocking_discussions_resolved` | 0/1 |
|
||||||
|
| `time_stats.time_estimate` | i64 | `time_estimate` | Seconds |
|
||||||
|
| `time_stats.total_time_spent` | i64 | `time_spent` | Seconds |
|
||||||
|
| `time_stats.human_time_estimate` | Option\<String\> | `human_time_estimate` | |
|
||||||
|
| `time_stats.human_total_time_spent` | Option\<String\> | `human_time_spent` | |
|
||||||
|
| `task_completion_status.count` | i64 | `task_count` | |
|
||||||
|
| `task_completion_status.completed_count` | i64 | `task_completed_count` | |
|
||||||
|
| `closed_by` | Option\<object\> | `closed_by_username` | |
|
||||||
|
| `prepared_at` | Option\<String\> | `prepared_at` | ISO datetime in API; store as ms epoch via `iso_to_ms()`, nullable |
|
||||||
|
| `merge_after` | Option\<String\> | `merge_after` | ISO datetime in API; store as ms epoch via `iso_to_ms()`, nullable (scheduled merge) |
|
||||||
|
| `imported` | bool | `imported` | 0/1 |
|
||||||
|
| `imported_from` | Option\<String\> | `imported_from` | |
|
||||||
|
| `approvals_before_merge` | Option\<i64\> | `approvals_before_merge` | Deprecated, scheduled for removal in GitLab API v5; store best-effort, keep nullable |
|
||||||
|
| `references.relative` | String | `references_relative` | Currently only short + full stored |
|
||||||
|
| `confidential` | bool | `confidential` | 0/1 (MRs can be confidential too) |
|
||||||
|
| `iteration.id` | Option\<i64\> | `iteration_id` | Premium/Ultimate, null on Free |
|
||||||
|
| `iteration.iid` | Option\<i64\> | `iteration_iid` | |
|
||||||
|
| `iteration.title` | Option\<String\> | `iteration_title` | |
|
||||||
|
| `iteration.state` | Option\<i64\> | `iteration_state` | |
|
||||||
|
| `iteration.start_date` | Option\<String\> | `iteration_start_date` | ISO date |
|
||||||
|
| `iteration.due_date` | Option\<String\> | `iteration_due_date` | ISO date |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migration 007: complete_field_capture.sql
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Migration 007: Capture all remaining GitLab API response fields.
|
||||||
|
-- Principle: mirror everything GitLab returns. No field left behind.
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- ISSUES: new columns
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Fields currently deserialized but not stored
|
||||||
|
ALTER TABLE issues ADD COLUMN closed_at INTEGER; -- ms epoch, deserialized but never stored until now
|
||||||
|
ALTER TABLE issues ADD COLUMN author_id INTEGER; -- GitLab user ID
|
||||||
|
ALTER TABLE issues ADD COLUMN author_name TEXT; -- Display name
|
||||||
|
|
||||||
|
-- Issue metadata
|
||||||
|
ALTER TABLE issues ADD COLUMN issue_type TEXT; -- 'issue' | 'incident' | 'test_case'
|
||||||
|
ALTER TABLE issues ADD COLUMN confidential INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE issues ADD COLUMN discussion_locked INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Engagement
|
||||||
|
ALTER TABLE issues ADD COLUMN upvotes INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE issues ADD COLUMN downvotes INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE issues ADD COLUMN user_notes_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE issues ADD COLUMN merge_requests_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Time tracking
|
||||||
|
ALTER TABLE issues ADD COLUMN time_estimate INTEGER NOT NULL DEFAULT 0; -- seconds
|
||||||
|
ALTER TABLE issues ADD COLUMN time_spent INTEGER NOT NULL DEFAULT 0; -- seconds
|
||||||
|
ALTER TABLE issues ADD COLUMN human_time_estimate TEXT;
|
||||||
|
ALTER TABLE issues ADD COLUMN human_time_spent TEXT;
|
||||||
|
|
||||||
|
-- Task lists
|
||||||
|
ALTER TABLE issues ADD COLUMN task_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE issues ADD COLUMN task_completed_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE issues ADD COLUMN has_tasks INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- References (MRs already have short + full)
|
||||||
|
ALTER TABLE issues ADD COLUMN references_short TEXT; -- e.g. "#42"
|
||||||
|
ALTER TABLE issues ADD COLUMN references_relative TEXT; -- context-dependent
|
||||||
|
ALTER TABLE issues ADD COLUMN references_full TEXT; -- e.g. "group/project#42"
|
||||||
|
|
||||||
|
-- Close/move tracking
|
||||||
|
ALTER TABLE issues ADD COLUMN closed_by_username TEXT;
|
||||||
|
|
||||||
|
-- Premium/Ultimate fields (nullable, null on Free tier)
|
||||||
|
ALTER TABLE issues ADD COLUMN weight INTEGER;
|
||||||
|
ALTER TABLE issues ADD COLUMN severity TEXT;
|
||||||
|
ALTER TABLE issues ADD COLUMN health_status TEXT;
|
||||||
|
|
||||||
|
-- Import tracking
|
||||||
|
ALTER TABLE issues ADD COLUMN imported INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE issues ADD COLUMN imported_from TEXT;
|
||||||
|
ALTER TABLE issues ADD COLUMN moved_to_id INTEGER;
|
||||||
|
|
||||||
|
-- Epic (Premium/Ultimate, null on Free)
|
||||||
|
ALTER TABLE issues ADD COLUMN epic_id INTEGER;
|
||||||
|
ALTER TABLE issues ADD COLUMN epic_iid INTEGER;
|
||||||
|
ALTER TABLE issues ADD COLUMN epic_title TEXT;
|
||||||
|
ALTER TABLE issues ADD COLUMN epic_url TEXT;
|
||||||
|
ALTER TABLE issues ADD COLUMN epic_group_id INTEGER;
|
||||||
|
|
||||||
|
-- Iteration (Premium/Ultimate, null on Free)
|
||||||
|
ALTER TABLE issues ADD COLUMN iteration_id INTEGER;
|
||||||
|
ALTER TABLE issues ADD COLUMN iteration_iid INTEGER;
|
||||||
|
ALTER TABLE issues ADD COLUMN iteration_title TEXT;
|
||||||
|
ALTER TABLE issues ADD COLUMN iteration_state INTEGER;
|
||||||
|
ALTER TABLE issues ADD COLUMN iteration_start_date TEXT;
|
||||||
|
ALTER TABLE issues ADD COLUMN iteration_due_date TEXT;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- MERGE REQUESTS: new columns
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- Author enrichment
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN author_id INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN author_name TEXT;
|
||||||
|
|
||||||
|
-- Engagement
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN upvotes INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN downvotes INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN user_notes_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Fork tracking
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN source_project_id INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN target_project_id INTEGER;
|
||||||
|
|
||||||
|
-- Milestone (parity with issues)
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN milestone_id INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN milestone_title TEXT;
|
||||||
|
|
||||||
|
-- Merge behavior
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN merge_when_pipeline_succeeds INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN merge_commit_sha TEXT;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN squash_commit_sha TEXT;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN squash INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN squash_on_merge INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Merge readiness
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN has_conflicts INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN blocking_discussions_resolved INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Branch cleanup
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN should_remove_source_branch INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN force_remove_source_branch INTEGER;
|
||||||
|
|
||||||
|
-- Discussion lock
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN discussion_locked INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Time tracking
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN time_estimate INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN time_spent INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN human_time_estimate TEXT;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN human_time_spent TEXT;
|
||||||
|
|
||||||
|
-- Task lists
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN task_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN task_completed_count INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Close tracking
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN closed_by_username TEXT;
|
||||||
|
|
||||||
|
-- Scheduling (API returns ISO datetimes; we store ms epoch for consistency)
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN prepared_at INTEGER; -- ms epoch after iso_to_ms()
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN merge_after INTEGER; -- ms epoch after iso_to_ms()
|
||||||
|
|
||||||
|
-- References (add relative, short + full already exist)
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN references_relative TEXT;
|
||||||
|
|
||||||
|
-- Import tracking
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN imported INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN imported_from TEXT;
|
||||||
|
|
||||||
|
-- Premium/Ultimate
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN approvals_before_merge INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN confidential INTEGER NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- Iteration (Premium/Ultimate, null on Free)
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN iteration_id INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN iteration_iid INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN iteration_title TEXT;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN iteration_state INTEGER;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN iteration_start_date TEXT;
|
||||||
|
ALTER TABLE merge_requests ADD COLUMN iteration_due_date TEXT;
|
||||||
|
|
||||||
|
-- Record migration version
|
||||||
|
INSERT INTO schema_version (version, applied_at, description)
|
||||||
|
VALUES (7, strftime('%s', 'now') * 1000, 'Complete API field capture for issues and merge requests');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Serde Struct Changes
|
||||||
|
|
||||||
|
### Existing type changes
|
||||||
|
|
||||||
|
```
|
||||||
|
GitLabReferences // Add: relative: Option<String> (with #[serde(default)])
|
||||||
|
// Existing fields short + full remain unchanged
|
||||||
|
GitLabIssue // Add #[derive(Default)] for test ergonomics
|
||||||
|
GitLabMergeRequest // Add #[derive(Default)] for test ergonomics
|
||||||
|
```
|
||||||
|
|
||||||
|
### New helper types needed
|
||||||
|
|
||||||
|
```
|
||||||
|
GitLabTimeStats { time_estimate, total_time_spent, human_time_estimate, human_total_time_spent }
|
||||||
|
GitLabTaskCompletionStatus { count, completed_count }
|
||||||
|
GitLabClosedBy (reuse GitLabAuthor shape: id, username, name)
|
||||||
|
GitLabEpic { id, iid, title, url, group_id }
|
||||||
|
GitLabIteration { id, iid, title, state, start_date, due_date }
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitLabIssue: add fields
|
||||||
|
|
||||||
|
```
|
||||||
|
type: Option<String> // #[serde(rename = "type")] -- fallback-only (uppercase category); "type" is reserved in Rust
|
||||||
|
upvotes: i64 // #[serde(default)]
|
||||||
|
downvotes: i64 // #[serde(default)]
|
||||||
|
user_notes_count: i64 // #[serde(default)]
|
||||||
|
merge_requests_count: i64 // #[serde(default)]
|
||||||
|
confidential: bool // #[serde(default)]
|
||||||
|
discussion_locked: bool // #[serde(default)]
|
||||||
|
weight: Option<i64>
|
||||||
|
time_stats: Option<GitLabTimeStats>
|
||||||
|
task_completion_status: Option<GitLabTaskCompletionStatus>
|
||||||
|
has_tasks: bool // #[serde(default)]
|
||||||
|
references: Option<GitLabReferences>
|
||||||
|
closed_by: Option<GitLabAuthor>
|
||||||
|
severity: Option<String>
|
||||||
|
health_status: Option<String>
|
||||||
|
imported: bool // #[serde(default)]
|
||||||
|
imported_from: Option<String>
|
||||||
|
moved_to_id: Option<i64>
|
||||||
|
issue_type: Option<String> // canonical field (lowercase); preferred for DB storage over `type`
|
||||||
|
epic: Option<GitLabEpic>
|
||||||
|
iteration: Option<GitLabIteration>
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitLabMergeRequest: add fields
|
||||||
|
|
||||||
|
```
|
||||||
|
upvotes: i64 // #[serde(default)]
|
||||||
|
downvotes: i64 // #[serde(default)]
|
||||||
|
user_notes_count: i64 // #[serde(default)]
|
||||||
|
source_project_id: Option<i64>
|
||||||
|
target_project_id: Option<i64>
|
||||||
|
milestone: Option<GitLabMilestone> // reuse existing type
|
||||||
|
merge_when_pipeline_succeeds: bool // #[serde(default)]
|
||||||
|
merge_commit_sha: Option<String>
|
||||||
|
squash_commit_sha: Option<String>
|
||||||
|
squash: bool // #[serde(default)]
|
||||||
|
squash_on_merge: bool // #[serde(default)]
|
||||||
|
has_conflicts: bool // #[serde(default)]
|
||||||
|
blocking_discussions_resolved: bool // #[serde(default)]
|
||||||
|
should_remove_source_branch: Option<bool>
|
||||||
|
force_remove_source_branch: Option<bool>
|
||||||
|
discussion_locked: bool // #[serde(default)]
|
||||||
|
time_stats: Option<GitLabTimeStats>
|
||||||
|
task_completion_status: Option<GitLabTaskCompletionStatus>
|
||||||
|
closed_by: Option<GitLabAuthor>
|
||||||
|
prepared_at: Option<String>
|
||||||
|
merge_after: Option<String>
|
||||||
|
imported: bool // #[serde(default)]
|
||||||
|
imported_from: Option<String>
|
||||||
|
approvals_before_merge: Option<i64>
|
||||||
|
confidential: bool // #[serde(default)]
|
||||||
|
iteration: Option<GitLabIteration>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Transformer Changes
|
||||||
|
|
||||||
|
### IssueRow: add fields
|
||||||
|
|
||||||
|
All new fields map 1:1 from the serde struct except:
|
||||||
|
- `closed_at` -> `iso_to_ms()` conversion (already in serde struct, just not passed through)
|
||||||
|
- `time_stats` -> flatten to 4 individual fields
|
||||||
|
- `task_completion_status` -> flatten to 2 individual fields
|
||||||
|
- `references` -> flatten to 3 individual fields
|
||||||
|
- `closed_by` -> extract `username` only (consistent with author pattern)
|
||||||
|
- `author` -> additionally extract `id` and `name` (currently only `username`)
|
||||||
|
- `issue_type` -> store as-is (canonical, lowercase); fallback to lowercased `type` field if `issue_type` absent
|
||||||
|
- `epic` -> flatten to 5 individual fields (id, iid, title, url, group_id)
|
||||||
|
- `iteration` -> flatten to 6 individual fields (id, iid, title, state, start_date, due_date)
|
||||||
|
|
||||||
|
### NormalizedMergeRequest: add fields
|
||||||
|
|
||||||
|
Same patterns as issues, plus:
|
||||||
|
- `milestone` -> reuse `upsert_milestone_tx` from issue pipeline, add `milestone_id` + `milestone_title`
|
||||||
|
- `prepared_at`, `merge_after` -> `iso_to_ms()` conversion (API provides ISO datetimes)
|
||||||
|
- `source_project_id`, `target_project_id` -> direct pass-through
|
||||||
|
- `iteration` -> flatten to 6 individual fields (same as issues)
|
||||||
|
|
||||||
|
### Insert statement changes
|
||||||
|
|
||||||
|
Both `process_issue_in_transaction` and `process_mr_in_transaction` need their INSERT and ON CONFLICT DO UPDATE statements extended with all new columns. The ON CONFLICT clause should update all new fields on re-sync.
|
||||||
|
|
||||||
|
**Implementation note (reliability):** Define a single authoritative list of persisted columns per entity and generate/compose both SQL fragments from it:
|
||||||
|
- INSERT column list + VALUES placeholders
|
||||||
|
- ON CONFLICT DO UPDATE assignments
|
||||||
|
|
||||||
|
This prevents drift where a new field is added to one clause but not the other -- the most likely bug class with 40+ new columns.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisite refactors (prep commits before main Phase A work)
|
||||||
|
|
||||||
|
### 1. Align issue transformer on `core::time`
|
||||||
|
|
||||||
|
The issue transformer (`transformers/issue.rs`) has a local `parse_timestamp()` that duplicates `iso_to_ms_strict()` from `core::time`. The MR transformer already uses the shared module. Before adding Phase A's optional timestamp fields (especially `closed_at` as `Option<String>`), migrate the issue transformer to use `iso_to_ms_strict()` and `iso_to_ms_opt_strict()` from `core::time`. This avoids duplicating the `opt` variant locally and establishes one timestamp parsing path across the codebase.
|
||||||
|
|
||||||
|
**Changes:** Replace `parse_timestamp()` calls with `iso_to_ms_strict()`, adapt or remove `TransformError::TimestampParse` (MR transformer uses `String` errors; align on that or on a shared error type).
|
||||||
|
|
||||||
|
### 2. Extract shared ingestion helpers
|
||||||
|
|
||||||
|
`upsert_milestone_tx` (in `ingestion/issues.rs`) and `upsert_label_tx` (duplicated in both `ingestion/issues.rs` and `ingestion/merge_requests.rs`) should be moved to a shared module (e.g., `src/ingestion/shared.rs`). MR ingestion needs `upsert_milestone_tx` for Phase A milestone support, and the label helper is already copy-pasted between files.
|
||||||
|
|
||||||
|
**Changes:** Create `src/ingestion/shared.rs`, move `upsert_milestone_tx`, `upsert_label_tx`, and `MilestoneRow` there. Update imports in both issue and MR ingestion modules.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files touched
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `migrations/007_complete_field_capture.sql` | New file |
|
||||||
|
| `src/gitlab/types.rs` | Add `#[derive(Default)]` to `GitLabIssue` and `GitLabMergeRequest`; add `relative: Option<String>` to `GitLabReferences`; add fields to both structs; add `GitLabTimeStats`, `GitLabTaskCompletionStatus`, `GitLabEpic`, `GitLabIteration` |
|
||||||
|
| `src/gitlab/transformers/issue.rs` | Remove local `parse_timestamp()`, switch to `core::time`; extend IssueRow, IssueWithMetadata, transform_issue() |
|
||||||
|
| `src/gitlab/transformers/merge_request.rs` | Extend NormalizedMergeRequest, MergeRequestWithMetadata, transform_merge_request(); extract `references_relative` |
|
||||||
|
| `src/ingestion/shared.rs` | New file: shared `upsert_milestone_tx`, `upsert_label_tx`, `MilestoneRow` |
|
||||||
|
| `src/ingestion/issues.rs` | Extend INSERT/UPSERT SQL; import from shared module |
|
||||||
|
| `src/ingestion/merge_requests.rs` | Extend INSERT/UPSERT SQL; import from shared module; add milestone upsert |
|
||||||
|
| `src/core/db.rs` | Register migration 007 in `MIGRATIONS` array |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What this does NOT include
|
||||||
|
|
||||||
|
- No new API endpoints called
|
||||||
|
- No new tables (except reusing existing `milestones` for MRs)
|
||||||
|
- No CLI changes (new fields are stored but not yet surfaced in `lore issues` / `lore mrs` output)
|
||||||
|
- No changes to discussion/note ingestion (Phase A is issues + MRs only)
|
||||||
|
- No observability instrumentation (that's Phase B)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rollout / Backfill Note
|
||||||
|
|
||||||
|
After applying Migration 007 and shipping transformer + UPSERT updates, **existing rows will not have the new columns populated** until issues/MRs are reprocessed. Plan on a **one-time full re-sync** (`lore ingest --type issues --full` and `lore ingest --type mrs --full`) to backfill the new fields. Until then, queries on new columns will return NULL/default values for previously-synced entities.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved decisions
|
||||||
|
|
||||||
|
| Field | Decision | Rationale |
|
||||||
|
|-------|----------|-----------|
|
||||||
|
| `subscribed` | **Excluded** | User-relative field (reflects token holder's subscription state, not an entity property). Changes meaning if the token is rotated to a different user. Not entity data. |
|
||||||
|
| `_links` | **Excluded** | HATEOAS API navigation metadata, not entity data. Every URL is deterministically constructable from `project_id` + `iid` + GitLab base URL. Note: `closed_as_duplicate_of` inside `_links` contains a real entity reference -- extracting that is deferred to a future phase. |
|
||||||
|
| `epic` / `iteration` | **Flatten to columns** | Same denormalization pattern as milestones. Epic gets 5 columns (`epic_id`, `epic_iid`, `epic_title`, `epic_url`, `epic_group_id`). Iteration gets 6 columns (`iteration_id`, `iteration_iid`, `iteration_title`, `iteration_state`, `iteration_start_date`, `iteration_due_date`). Both nullable (null on Free tier). |
|
||||||
|
| `approvals_before_merge` | **Store best-effort** | Deprecated and scheduled for removal in GitLab API v5. Keep as `Option<i64>` / nullable column. Never depend on it for correctness -- it may disappear in a future GitLab release. |
|
||||||
1009
docs/phase-b-temporal-intelligence.md
Normal file
1009
docs/phase-b-temporal-intelligence.md
Normal file
File diff suppressed because it is too large
Load Diff
202
docs/plan-expose-discussion-ids.feedback-1.md
Normal file
202
docs/plan-expose-discussion-ids.feedback-1.md
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
No `## Rejected Recommendations` section appears in the plan you pasted, so the revisions below are all net-new.
|
||||||
|
|
||||||
|
1. **Add an explicit “Bridge Contract” and fix scope inconsistency**
|
||||||
|
Analysis: The plan says “Three changes” but defines four. More importantly, identifier requirements are scattered. A single contract section prevents drift and makes every new read surface prove it can drive a write call.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@
|
||||||
|
-**Scope**: Three changes, delivered in order:
|
||||||
|
+**Scope**: Four workstreams, delivered in order:
|
||||||
|
1. Add `gitlab_discussion_id` to notes output
|
||||||
|
2. Add `gitlab_discussion_id` to show command discussion groups
|
||||||
|
3. Add a standalone `discussions` list command
|
||||||
|
4. Fix robot-docs to list actual field names instead of opaque type references
|
||||||
|
+
|
||||||
|
+## Bridge Contract (Cross-Cutting)
|
||||||
|
+Every read payload that surfaces notes/discussions MUST include:
|
||||||
|
+- `project_path`
|
||||||
|
+- `noteable_type`
|
||||||
|
+- `parent_iid`
|
||||||
|
+- `gitlab_discussion_id`
|
||||||
|
+- `gitlab_note_id` (when note-level data is returned)
|
||||||
|
+This contract is required so agents can deterministically construct `glab api` write calls.
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Normalize identifier naming now (break ambiguous names)**
|
||||||
|
Analysis: Current `id`/`gitlab_id` naming is ambiguous in mixed payloads. Rename to explicit `note_id` and `gitlab_note_id` now (you explicitly don’t care about backward compatibility). This reduces automation mistakes.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 1b. Add field to `NoteListRow`
|
||||||
|
-pub struct NoteListRow {
|
||||||
|
- pub id: i64,
|
||||||
|
- pub gitlab_id: i64,
|
||||||
|
+pub struct NoteListRow {
|
||||||
|
+ pub note_id: i64, // local DB id
|
||||||
|
+ pub gitlab_note_id: i64, // GitLab note id
|
||||||
|
@@
|
||||||
|
@@ 1c. Add field to `NoteListRowJson`
|
||||||
|
-pub struct NoteListRowJson {
|
||||||
|
- pub id: i64,
|
||||||
|
- pub gitlab_id: i64,
|
||||||
|
+pub struct NoteListRowJson {
|
||||||
|
+ pub note_id: i64,
|
||||||
|
+ pub gitlab_note_id: i64,
|
||||||
|
@@
|
||||||
|
-#### 2f. Add `gitlab_note_id` to note detail structs in show
|
||||||
|
-While we're here, add `gitlab_id` to `NoteDetail`, `MrNoteDetail`, and their JSON
|
||||||
|
+#### 2f. Add `gitlab_note_id` to note detail structs in show
|
||||||
|
+While we're here, add `gitlab_note_id` to `NoteDetail`, `MrNoteDetail`, and their JSON
|
||||||
|
counterparts.
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Stop positional column indexing for these changes**
|
||||||
|
Analysis: In `list.rs`, row extraction is positional (`row.get(18)`, etc.). Adding fields is fragile and easy to break silently. Use named aliases and named lookup for robustness.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 1a/1b SQL + query_map
|
||||||
|
- p.path_with_namespace AS project_path
|
||||||
|
+ p.path_with_namespace AS project_path,
|
||||||
|
+ d.gitlab_discussion_id AS gitlab_discussion_id
|
||||||
|
@@
|
||||||
|
- project_path: row.get(18)?,
|
||||||
|
- gitlab_discussion_id: row.get(19)?,
|
||||||
|
+ project_path: row.get("project_path")?,
|
||||||
|
+ gitlab_discussion_id: row.get("gitlab_discussion_id")?,
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Redesign `discussions` query to avoid correlated subquery fanout**
|
||||||
|
Analysis: Proposed query uses many correlated subqueries per row. That’s acceptable for tiny MR-scoped sets, but degrades for project-wide scans. Use a base CTE + one rollup pass over notes.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 3c. SQL Query
|
||||||
|
-SELECT
|
||||||
|
- d.id,
|
||||||
|
- ...
|
||||||
|
- (SELECT COUNT(*) FROM notes n2 WHERE n2.discussion_id = d.id AND n2.is_system = 0) AS note_count,
|
||||||
|
- (SELECT n3.author_username FROM notes n3 WHERE n3.discussion_id = d.id ORDER BY n3.position LIMIT 1) AS first_author,
|
||||||
|
- ...
|
||||||
|
-FROM discussions d
|
||||||
|
+WITH base AS (
|
||||||
|
+ SELECT d.id, d.gitlab_discussion_id, d.noteable_type, d.project_id, d.issue_id, d.merge_request_id,
|
||||||
|
+ d.individual_note, d.first_note_at, d.last_note_at, d.resolvable, d.resolved
|
||||||
|
+ FROM discussions d
|
||||||
|
+ {where_sql}
|
||||||
|
+),
|
||||||
|
+note_rollup AS (
|
||||||
|
+ SELECT n.discussion_id,
|
||||||
|
+ COUNT(*) FILTER (WHERE n.is_system = 0) AS user_note_count,
|
||||||
|
+ COUNT(*) AS total_note_count,
|
||||||
|
+ MIN(CASE WHEN n.is_system = 0 THEN n.position END) AS first_user_pos
|
||||||
|
+ FROM notes n
|
||||||
|
+ JOIN base b ON b.id = n.discussion_id
|
||||||
|
+ GROUP BY n.discussion_id
|
||||||
|
+)
|
||||||
|
+SELECT ...
|
||||||
|
+FROM base b
|
||||||
|
+LEFT JOIN note_rollup r ON r.discussion_id = b.id
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Add explicit index work for new access patterns**
|
||||||
|
Analysis: Existing indexes are good but not ideal for new list patterns (`project + last_note`, note position ordering inside discussion). Add migration entries to keep latency stable.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## 3. Add Standalone `discussions` List Command
|
||||||
|
+#### 3h. Add migration for discussion-list performance
|
||||||
|
+**File**: `migrations/027_discussions_list_indexes.sql`
|
||||||
|
+```sql
|
||||||
|
+CREATE INDEX IF NOT EXISTS idx_discussions_project_last_note
|
||||||
|
+ ON discussions(project_id, last_note_at DESC, id DESC);
|
||||||
|
+CREATE INDEX IF NOT EXISTS idx_discussions_project_first_note
|
||||||
|
+ ON discussions(project_id, first_note_at DESC, id DESC);
|
||||||
|
+CREATE INDEX IF NOT EXISTS idx_notes_discussion_position
|
||||||
|
+ ON notes(discussion_id, position);
|
||||||
|
+```
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Add keyset pagination (critical for agent workflows)**
|
||||||
|
Analysis: `--limit` alone is not enough for automation over large datasets. Add cursor-based pagination with deterministic sort keys and `next_cursor` in JSON.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 3a. CLI Args
|
||||||
|
+ /// Keyset cursor from previous response
|
||||||
|
+ #[arg(long, help_heading = "Output")]
|
||||||
|
+ pub cursor: Option<String>,
|
||||||
|
@@
|
||||||
|
@@ Response Schema
|
||||||
|
- "total_count": 15,
|
||||||
|
- "showing": 15
|
||||||
|
+ "total_count": 15,
|
||||||
|
+ "showing": 15,
|
||||||
|
+ "next_cursor": "eyJsYXN0X25vdGVfYXQiOjE3MDAwMDAwMDAwMDAsImlkIjoxMjN9"
|
||||||
|
@@
|
||||||
|
@@ Validation Criteria
|
||||||
|
+7. `lore -J discussions ... --cursor <token>` returns the next stable page without duplicates/skips
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Fix semantic ambiguities in discussion summary fields**
|
||||||
|
Analysis: `note_count` is ambiguous, and `first_author` can accidentally be a system note author. Make fields explicit and consistent with non-system default behavior.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Response Schema
|
||||||
|
- "note_count": 3,
|
||||||
|
- "first_author": "elovegrove",
|
||||||
|
+ "user_note_count": 3,
|
||||||
|
+ "total_note_count": 4,
|
||||||
|
+ "first_user_author": "elovegrove",
|
||||||
|
@@
|
||||||
|
@@ 3d. Filters struct / path behavior
|
||||||
|
-- `path` → `EXISTS (SELECT 1 FROM notes n WHERE n.discussion_id = d.id AND n.position_new_path LIKE ?)`
|
||||||
|
+- `path` → match on BOTH `position_new_path` and `position_old_path` (exact/prefix)
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **Enrich show outputs with actionable thread metadata**
|
||||||
|
Analysis: Adding only discussion id helps, but agents still need thread state and note ids to pick targets correctly. Add `resolvable`, `resolved`, `last_note_at_iso`, and `gitlab_note_id` in show discussion payloads.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 2a/2b show discussion structs
|
||||||
|
pub struct DiscussionDetailJson {
|
||||||
|
pub gitlab_discussion_id: String,
|
||||||
|
+ pub resolvable: bool,
|
||||||
|
+ pub resolved: bool,
|
||||||
|
+ pub last_note_at_iso: String,
|
||||||
|
pub notes: Vec<NoteDetailJson>,
|
||||||
|
@@
|
||||||
|
pub struct NoteDetailJson {
|
||||||
|
+ pub gitlab_note_id: i64,
|
||||||
|
pub author_username: String,
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **Harden robot-docs against schema drift with tests**
|
||||||
|
Analysis: Static JSON in `main.rs` will drift again. Add a lightweight contract test that asserts docs include required fields for `notes`, `discussions`, and show payloads.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 4. Fix Robot-Docs Response Schemas
|
||||||
|
+#### 4f. Add robot-docs contract tests
|
||||||
|
+**File**: `src/main.rs` (or dedicated test module)
|
||||||
|
+- Assert `robot-docs` contains `gitlab_discussion_id` and `gitlab_note_id` in:
|
||||||
|
+ - `notes.response_schema`
|
||||||
|
+ - `issues.response_schema.show`
|
||||||
|
+ - `mrs.response_schema.show`
|
||||||
|
+ - `discussions.response_schema`
|
||||||
|
```
|
||||||
|
|
||||||
|
10. **Adjust delivery order to reduce rework and include missing CSV path**
|
||||||
|
Analysis: In your sample `handle_discussions`, `csv` is declared in args but not handled. Also, robot-docs should land after all payload changes. Sequence should minimize churn.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Delivery Order
|
||||||
|
-3. **Change 4** (robot-docs) — depends on 1 and 2 being done so schemas are accurate.
|
||||||
|
-4. **Change 3** (discussions command) — largest change, depends on 1 for design consistency.
|
||||||
|
+3. **Change 3** (discussions command + indexes + pagination) — largest change.
|
||||||
|
+4. **Change 4** (robot-docs + contract tests) — last, after payloads are final.
|
||||||
|
@@ 3e. Handler wiring
|
||||||
|
- match format {
|
||||||
|
+ match format {
|
||||||
|
"json" => ...
|
||||||
|
"jsonl" => ...
|
||||||
|
+ "csv" => print_list_discussions_csv(&result),
|
||||||
|
_ => ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can produce a single consolidated revised plan markdown with these edits applied so you can drop it in directly.
|
||||||
162
docs/plan-expose-discussion-ids.feedback-2.md
Normal file
162
docs/plan-expose-discussion-ids.feedback-2.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
Best non-rejected upgrades I’d make to this plan are below. They focus on reducing schema drift, making robot output safer to consume, and improving performance behavior at scale.
|
||||||
|
|
||||||
|
1. Add a shared contract model and field constants first (before workstreams 1-4)
|
||||||
|
Rationale: Right now each command has its own structs and ad-hoc mapping. That is exactly how drift happens. A single contract definition reused by `notes`, `show`, `discussions`, and robot-docs gives compile-time coupling between output payloads and docs. It also makes future fields cheaper and safer to add.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Scope: Four workstreams, delivered in order:
|
||||||
|
-1. Add `gitlab_discussion_id` to notes output
|
||||||
|
-2. Add `gitlab_discussion_id` to show command discussion groups
|
||||||
|
-3. Add a standalone `discussions` list command
|
||||||
|
-4. Fix robot-docs to list actual field names instead of opaque type references
|
||||||
|
+0. Introduce shared Bridge Contract model/constants used by notes/show/discussions/robot-docs
|
||||||
|
+1. Add `gitlab_discussion_id` to notes output
|
||||||
|
+2. Add `gitlab_discussion_id` to show command discussion groups
|
||||||
|
+3. Add a standalone `discussions` list command
|
||||||
|
+4. Fix robot-docs to list actual field names instead of opaque type references
|
||||||
|
|
||||||
|
+## 0. Shared Contract Model (Cross-Cutting)
|
||||||
|
+Define canonical required-field constants and shared mapping helpers, then consume them in:
|
||||||
|
+- `src/cli/commands/list.rs`
|
||||||
|
+- `src/cli/commands/show.rs`
|
||||||
|
+- `src/cli/robot.rs`
|
||||||
|
+- `src/main.rs` robot-docs builder
|
||||||
|
+This removes duplicated field-name strings and prevents docs/output mismatch.
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Make bridge fields “non-droppable” in robot mode
|
||||||
|
Rationale: The current plan adds fields, but `--fields` can still remove them. That breaks the core read/write bridge contract in exactly the workflows this change is trying to fix. In robot mode, contract fields should always be force-included.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Bridge Contract (Cross-Cutting)
|
||||||
|
Every read payload that surfaces notes or discussions **MUST** include:
|
||||||
|
- `project_path`
|
||||||
|
- `noteable_type`
|
||||||
|
- `parent_iid`
|
||||||
|
- `gitlab_discussion_id`
|
||||||
|
- `gitlab_note_id` (when note-level data is returned — i.e., in notes list and show detail)
|
||||||
|
|
||||||
|
+### Field Filtering Guardrail
|
||||||
|
+In robot mode, `filter_fields` must force-include Bridge Contract fields even when users pass a narrower `--fields` list.
|
||||||
|
+Human/table mode keeps existing behavior.
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Replace correlated subqueries in `discussions` rollup with a single-pass window/aggregate pattern
|
||||||
|
Rationale: Your CTE is better than naive fanout, but it still uses multiple correlated sub-selects per discussion for first author/body/path. At 200K+ discussions this can regress badly depending on cache/index state. A window-ranked `notes` CTE with grouped aggregates is usually faster and more predictable in SQLite.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ #### 3c. SQL Query
|
||||||
|
-Core query uses a CTE + rollup to avoid correlated subquery fanout on larger result sets:
|
||||||
|
+Core query uses a CTE + ranked-notes rollup (window function) to avoid per-row correlated subqueries:
|
||||||
|
|
||||||
|
-WITH filtered_discussions AS (...),
|
||||||
|
-note_rollup AS (
|
||||||
|
- SELECT
|
||||||
|
- n.discussion_id,
|
||||||
|
- SUM(...) AS note_count,
|
||||||
|
- (SELECT ... LIMIT 1) AS first_author,
|
||||||
|
- (SELECT ... LIMIT 1) AS first_note_body,
|
||||||
|
- (SELECT ... LIMIT 1) AS position_new_path,
|
||||||
|
- (SELECT ... LIMIT 1) AS position_new_line
|
||||||
|
- FROM notes n
|
||||||
|
- ...
|
||||||
|
-)
|
||||||
|
+WITH filtered_discussions AS (...),
|
||||||
|
+ranked_notes AS (
|
||||||
|
+ SELECT
|
||||||
|
+ n.*,
|
||||||
|
+ ROW_NUMBER() OVER (PARTITION BY n.discussion_id ORDER BY n.position, n.id) AS rn
|
||||||
|
+ FROM notes n
|
||||||
|
+ WHERE n.discussion_id IN (SELECT id FROM filtered_discussions)
|
||||||
|
+),
|
||||||
|
+note_rollup AS (
|
||||||
|
+ SELECT
|
||||||
|
+ discussion_id,
|
||||||
|
+ SUM(CASE WHEN is_system = 0 THEN 1 ELSE 0 END) AS note_count,
|
||||||
|
+ MAX(CASE WHEN rn = 1 AND is_system = 0 THEN author_username END) AS first_author,
|
||||||
|
+ MAX(CASE WHEN rn = 1 AND is_system = 0 THEN body END) AS first_note_body,
|
||||||
|
+ MAX(CASE WHEN position_new_path IS NOT NULL THEN position_new_path END) AS position_new_path,
|
||||||
|
+ MAX(CASE WHEN position_new_line IS NOT NULL THEN position_new_line END) AS position_new_line
|
||||||
|
+ FROM ranked_notes
|
||||||
|
+ GROUP BY discussion_id
|
||||||
|
+)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Add direct GitLab ID filters for deterministic bridging
|
||||||
|
Rationale: Bridge workflows often start from one known ID. You already have `gitlab_note_id` in notes filters, but discussion filtering still looks internal-ID-centric. Add explicit GitLab-ID filters so agents do not need extra translation calls.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ #### 3a. CLI Args
|
||||||
|
pub struct DiscussionsArgs {
|
||||||
|
+ /// Filter by GitLab discussion ID
|
||||||
|
+ #[arg(long, help_heading = "Filters")]
|
||||||
|
+ pub gitlab_discussion_id: Option<String>,
|
||||||
|
@@
|
||||||
|
|
||||||
|
@@ #### 3d. Filters struct
|
||||||
|
pub struct DiscussionListFilters {
|
||||||
|
+ pub gitlab_discussion_id: Option<String>,
|
||||||
|
@@
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## 1. Add `gitlab_discussion_id` to Notes Output
|
||||||
|
+#### 1g. Add `--gitlab-discussion-id` filter to notes
|
||||||
|
+Allow filtering notes directly by GitLab thread ID (not only internal discussion ID).
|
||||||
|
+This enables one-hop note retrieval from external references.
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Add optional note expansion to `discussions` for fewer round-trips
|
||||||
|
Rationale: Today the agent flow is often `discussions -> show`. Optional embedded notes (`--include-notes N`) gives a fast path for “list unresolved threads with latest context” without forcing full show payloads.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ### Design
|
||||||
|
lore -J discussions --for-mr 99 --resolution unresolved
|
||||||
|
+lore -J discussions --for-mr 99 --resolution unresolved --include-notes 2
|
||||||
|
|
||||||
|
@@ #### 3a. CLI Args
|
||||||
|
+ /// Include up to N latest notes per discussion (0 = none)
|
||||||
|
+ #[arg(long, default_value = "0", help_heading = "Output")]
|
||||||
|
+ pub include_notes: usize,
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Upgrade robot-docs from string blobs to structured schema + explicit contract block
|
||||||
|
Rationale: `contains("gitlab_discussion_id")` tests on schema strings are brittle. A structured schema object gives machine-checked docs and reliable test assertions. Add a contract section for agent consumers.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## 4. Fix Robot-Docs Response Schemas
|
||||||
|
-#### 4a. Notes response_schema
|
||||||
|
-Replace stringly-typed schema snippets...
|
||||||
|
+#### 4a. Notes response_schema (structured)
|
||||||
|
+Represent response fields as JSON objects (field -> type/nullable), not freeform strings.
|
||||||
|
|
||||||
|
+#### 4g. Add `bridge_contract` section in robot-docs
|
||||||
|
+Publish canonical required fields per entity:
|
||||||
|
+- notes
|
||||||
|
+- discussions
|
||||||
|
+- show.discussions
|
||||||
|
+- show.notes
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Strengthen validation: add CLI-level contract tests and perf guardrails
|
||||||
|
Rationale: Most current tests are unit-level struct/query checks. Add end-to-end JSON contract tests via command handlers, plus a benchmark-style regression test (ignored by default) so performance work stays intentional.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Validation Criteria
|
||||||
|
8. Bridge Contract fields (...) are present in every applicable read payload
|
||||||
|
+9. Contract fields remain present even with `--fields` in robot mode
|
||||||
|
+10. `discussions` query meets performance guardrail on representative fixture (documented threshold)
|
||||||
|
|
||||||
|
@@ ### Tests
|
||||||
|
+#### Test: robot-mode fields cannot drop bridge contract keys
|
||||||
|
+Run notes/discussions JSON output through `filter_fields` path and assert required keys remain.
|
||||||
|
+
|
||||||
|
+#### Test: CLI contract integration
|
||||||
|
+Invoke command handlers for `notes`, `discussions`, `mrs <iid>`, parse JSON, assert required keys and types.
|
||||||
|
+
|
||||||
|
+#### Test (ignored): large-fixture performance regression
|
||||||
|
+Generate representative fixture and assert `query_discussions` stays under target elapsed time.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can now produce a full “v2 plan” document that applies these diffs end-to-end (including revised delivery order and complete updated sections).
|
||||||
147
docs/plan-expose-discussion-ids.feedback-3.md
Normal file
147
docs/plan-expose-discussion-ids.feedback-3.md
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
1. **Make `gitlab_note_id` explicit in all note-level payloads without breaking existing consumers**
|
||||||
|
Rationale: Your Bridge Contract already requires `gitlab_note_id`, but current plan keeps `gitlab_id` only in `notes` list while adding `gitlab_note_id` only in detail views. That forces agents to special-case commands. Add `gitlab_note_id` as an alias field everywhere note-level data appears, while keeping `gitlab_id` for compatibility.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Bridge Contract (Cross-Cutting)
|
||||||
|
-Every read payload that surfaces notes or discussions MUST include:
|
||||||
|
+Every read payload that surfaces notes or discussions MUST include:
|
||||||
|
- project_path
|
||||||
|
- noteable_type
|
||||||
|
- parent_iid
|
||||||
|
- gitlab_discussion_id
|
||||||
|
- gitlab_note_id (when note-level data is returned — i.e., in notes list and show detail)
|
||||||
|
+ - Back-compat rule: note payloads may continue exposing `gitlab_id`, but MUST also expose `gitlab_note_id` with the same value.
|
||||||
|
|
||||||
|
@@ 1. Add `gitlab_discussion_id` to Notes Output
|
||||||
|
-#### 1c. Add field to `NoteListRowJson`
|
||||||
|
+#### 1c. Add fields to `NoteListRowJson`
|
||||||
|
+Add `gitlab_note_id` alias in addition to existing `gitlab_id` (no rename, no breakage).
|
||||||
|
|
||||||
|
@@ 1f. Update `--fields minimal` preset
|
||||||
|
-"notes" => ["id", "author_username", "body", "created_at_iso", "gitlab_discussion_id"]
|
||||||
|
+"notes" => ["id", "gitlab_note_id", "author_username", "body", "created_at_iso", "gitlab_discussion_id"]
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Avoid duplicate flag semantics for discussion filtering**
|
||||||
|
Rationale: `notes` already has `--discussion-id` and it already maps to `d.gitlab_discussion_id`. Adding a second independent flag/field (`--gitlab-discussion-id`) increases complexity and precedence bugs. Keep one backing filter field and make the new flag an alias.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 1g. Add `--gitlab-discussion-id` filter to notes
|
||||||
|
-Allow filtering notes directly by GitLab discussion thread ID...
|
||||||
|
+Normalize discussion ID flags:
|
||||||
|
+- Keep one backing filter field (`discussion_id`)
|
||||||
|
+- Support both `--discussion-id` (existing) and `--gitlab-discussion-id` (alias)
|
||||||
|
+- If both are provided, clap should reject as duplicate/alias conflict
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add ambiguity guardrails for cross-project discussion IDs**
|
||||||
|
Rationale: `gitlab_discussion_id` is unique per project, not globally. Filtering by discussion ID without project can return multiple rows across repos, which breaks deterministic write bridging. Fail fast with an `Ambiguous` error and actionable fix (`--project`).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Bridge Contract (Cross-Cutting)
|
||||||
|
+### Ambiguity Guardrail
|
||||||
|
+When filtering by `gitlab_discussion_id` without `--project`, if multiple projects match:
|
||||||
|
+- return `Ambiguous` error
|
||||||
|
+- include matching project paths in message
|
||||||
|
+- suggest retry with `--project <path>`
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Replace `--include-notes` N+1 retrieval with one batched top-N query**
|
||||||
|
Rationale: The current plan’s per-discussion follow-up query scales poorly and creates latency spikes. Use a single window-function query over selected discussion IDs and group rows in Rust. This is both faster and more predictable.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 3c-ii. Note expansion query (--include-notes)
|
||||||
|
-When `include_notes > 0`, after the main discussion query, run a follow-up query per discussion...
|
||||||
|
+When `include_notes > 0`, run one batched query:
|
||||||
|
+WITH ranked_notes AS (
|
||||||
|
+ SELECT
|
||||||
|
+ n.*,
|
||||||
|
+ d.gitlab_discussion_id,
|
||||||
|
+ ROW_NUMBER() OVER (
|
||||||
|
+ PARTITION BY n.discussion_id
|
||||||
|
+ ORDER BY n.created_at DESC, n.id DESC
|
||||||
|
+ ) AS rn
|
||||||
|
+ FROM notes n
|
||||||
|
+ JOIN discussions d ON d.id = n.discussion_id
|
||||||
|
+ WHERE n.discussion_id IN ( ...selected discussion ids... )
|
||||||
|
+)
|
||||||
|
+SELECT ... FROM ranked_notes WHERE rn <= ?
|
||||||
|
+ORDER BY discussion_id, rn;
|
||||||
|
+
|
||||||
|
+Group by `discussion_id` in Rust and attach notes arrays without per-thread round-trips.
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Add hard output guardrails and explicit truncation metadata**
|
||||||
|
Rationale: `--limit` and `--include-notes` are unbounded today. For robot workflows this can accidentally generate huge payloads. Cap values and surface effective limits plus truncation state in `meta`.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 3a. CLI Args
|
||||||
|
- pub limit: usize,
|
||||||
|
+ pub limit: usize, // clamp to max (e.g., 500)
|
||||||
|
|
||||||
|
- pub include_notes: usize,
|
||||||
|
+ pub include_notes: usize, // clamp to max (e.g., 20)
|
||||||
|
|
||||||
|
@@ Response Schema
|
||||||
|
- "meta": { "elapsed_ms": 12 }
|
||||||
|
+ "meta": {
|
||||||
|
+ "elapsed_ms": 12,
|
||||||
|
+ "effective_limit": 50,
|
||||||
|
+ "effective_include_notes": 2,
|
||||||
|
+ "has_more": true
|
||||||
|
+ }
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Strengthen deterministic ordering and null handling**
|
||||||
|
Rationale: `first_note_at`, `last_note_at`, and note `position` can be null/incomplete during partial sync states. Add null-safe ordering to avoid unstable output and flaky automation.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 2c. Update queries to SELECT new fields
|
||||||
|
-... ORDER BY first_note_at
|
||||||
|
+... ORDER BY COALESCE(first_note_at, last_note_at, 0), id
|
||||||
|
|
||||||
|
@@ show note query
|
||||||
|
-ORDER BY position
|
||||||
|
+ORDER BY COALESCE(position, 9223372036854775807), created_at, id
|
||||||
|
|
||||||
|
@@ 3c. SQL Query
|
||||||
|
-ORDER BY {sort_column} {order}
|
||||||
|
+ORDER BY COALESCE({sort_column}, 0) {order}, fd.id {order}
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Make write-bridging more useful with optional command hints**
|
||||||
|
Rationale: Exposing IDs is necessary but not sufficient; agents still need to assemble endpoints repeatedly. Add optional `--with-write-hints` that injects compact endpoint templates (`reply`, `resolve`) derived from row context. This improves usability without bloating default output.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 3a. CLI Args
|
||||||
|
+ /// Include machine-actionable glab write hints per row
|
||||||
|
+ #[arg(long, help_heading = "Output")]
|
||||||
|
+ pub with_write_hints: bool,
|
||||||
|
|
||||||
|
@@ Response Schema (notes/discussions/show)
|
||||||
|
+ "write_hints?": {
|
||||||
|
+ "reply_endpoint": "string",
|
||||||
|
+ "resolve_endpoint?": "string"
|
||||||
|
+ }
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **Upgrade robot-docs/contract validation from string-contains to parity checks**
|
||||||
|
Rationale: `contains("gitlab_discussion_id")` catches very little and allows schema drift. Build field-set parity tests that compare actual serialized JSON keys to robot-docs declared fields for `notes`, `discussions`, and `show` discussion nodes.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ 4f. Add robot-docs contract tests
|
||||||
|
-assert!(notes_schema.contains("gitlab_discussion_id"));
|
||||||
|
+let declared = parse_schema_field_list(notes_schema);
|
||||||
|
+let sample = sample_notes_row_json_keys();
|
||||||
|
+assert_required_subset(&declared, &["project_path","noteable_type","parent_iid","gitlab_discussion_id","gitlab_note_id"]);
|
||||||
|
+assert_schema_matches_payload(&declared, &sample);
|
||||||
|
|
||||||
|
@@ 4g. Add CLI-level contract integration tests
|
||||||
|
+Add parity tests for:
|
||||||
|
+- notes list JSON
|
||||||
|
+- discussions list JSON
|
||||||
|
+- issues show discussions[*]
|
||||||
|
+- mrs show discussions[*]
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can produce a full revised v3 plan text with these edits merged end-to-end so it’s ready to execute directly.
|
||||||
207
docs/plan-expose-discussion-ids.feedback-4.md
Normal file
207
docs/plan-expose-discussion-ids.feedback-4.md
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
Below are the highest-impact revisions I’d make to this plan. I excluded everything listed in your `## Rejected Recommendations` section.
|
||||||
|
|
||||||
|
**1. Fix a correctness bug in the ambiguity guardrail (must run before `LIMIT`)**
|
||||||
|
|
||||||
|
The current post-query ambiguity check can silently fail when `--limit` truncates results to one project even though multiple projects match the same `gitlab_discussion_id`. That creates non-deterministic write targeting risk.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Ambiguity Guardrail
|
||||||
|
-**Implementation**: After the main query, if `gitlab_discussion_id` is set and no `--project`
|
||||||
|
-was provided, check if the result set spans multiple `project_path` values.
|
||||||
|
+**Implementation**: Run a preflight distinct-project check when `gitlab_discussion_id` is set
|
||||||
|
+and `--project` was not provided, before the main list query applies `LIMIT`.
|
||||||
|
+Use:
|
||||||
|
+```sql
|
||||||
|
+SELECT DISTINCT p.path_with_namespace
|
||||||
|
+FROM discussions d
|
||||||
|
+JOIN projects p ON p.id = d.project_id
|
||||||
|
+WHERE d.gitlab_discussion_id = ?
|
||||||
|
+LIMIT 3
|
||||||
|
+```
|
||||||
|
+If more than one project is found, return `LoreError::Ambiguous` (exit code 18) with project
|
||||||
|
+paths and suggestion to retry with `--project <path>`.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**2. Add `gitlab_project_id` to the Bridge Contract**
|
||||||
|
|
||||||
|
`project_path` is human-friendly but mutable (renames/transfers). `gitlab_project_id` gives a stable write target and avoids path re-resolution failures.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Bridge Contract (Cross-Cutting)
|
||||||
|
Every read payload that surfaces notes or discussions **MUST** include:
|
||||||
|
- `project_path`
|
||||||
|
+- `gitlab_project_id`
|
||||||
|
- `noteable_type`
|
||||||
|
- `parent_iid`
|
||||||
|
- `gitlab_discussion_id`
|
||||||
|
- `gitlab_note_id`
|
||||||
|
@@
|
||||||
|
const BRIDGE_FIELDS_NOTES: &[&str] = &[
|
||||||
|
- "project_path", "noteable_type", "parent_iid",
|
||||||
|
+ "project_path", "gitlab_project_id", "noteable_type", "parent_iid",
|
||||||
|
"gitlab_discussion_id", "gitlab_note_id",
|
||||||
|
];
|
||||||
|
const BRIDGE_FIELDS_DISCUSSIONS: &[&str] = &[
|
||||||
|
- "project_path", "noteable_type", "parent_iid",
|
||||||
|
+ "project_path", "gitlab_project_id", "noteable_type", "parent_iid",
|
||||||
|
"gitlab_discussion_id",
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**3. Replace stringly-typed filter/sort fields with enums end-to-end**
|
||||||
|
|
||||||
|
Right now `sort`, `order`, `resolution`, `noteable_type` are mostly `String`. This is fragile and risks unsafe SQL interpolation drift over time. Typed enums make invalid states unrepresentable.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## 3a. CLI Args
|
||||||
|
- pub resolution: Option<String>,
|
||||||
|
+ pub resolution: Option<ResolutionFilter>,
|
||||||
|
@@
|
||||||
|
- pub noteable_type: Option<String>,
|
||||||
|
+ pub noteable_type: Option<NoteableTypeFilter>,
|
||||||
|
@@
|
||||||
|
- pub sort: String,
|
||||||
|
+ pub sort: DiscussionSortField,
|
||||||
|
@@
|
||||||
|
- pub asc: bool,
|
||||||
|
+ pub order: SortDirection,
|
||||||
|
@@ ## 3d. Filters struct
|
||||||
|
- pub resolution: Option<String>,
|
||||||
|
- pub noteable_type: Option<String>,
|
||||||
|
- pub sort: String,
|
||||||
|
- pub order: String,
|
||||||
|
+ pub resolution: Option<ResolutionFilter>,
|
||||||
|
+ pub noteable_type: Option<NoteableTypeFilter>,
|
||||||
|
+ pub sort: DiscussionSortField,
|
||||||
|
+ pub order: SortDirection,
|
||||||
|
@@
|
||||||
|
+Map enum -> SQL fragment via `match` in query builder; never interpolate raw strings.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**4. Enforce snapshot consistency for multi-query commands**
|
||||||
|
|
||||||
|
`discussions` with `--include-notes` does multiple reads. Without a single read transaction, concurrent ingest can produce mismatched `total_count`, row set, and expanded notes.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## 3c. SQL Query
|
||||||
|
-pub fn query_discussions(...)
|
||||||
|
+pub fn query_discussions(...)
|
||||||
|
{
|
||||||
|
+ // Run count query + page query + note expansion under one deferred read transaction
|
||||||
|
+ // so output is a single consistent snapshot.
|
||||||
|
+ let tx = conn.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
|
||||||
|
...
|
||||||
|
+ tx.commit()?;
|
||||||
|
}
|
||||||
|
@@ ## 1. Add `gitlab_discussion_id` to Notes Output
|
||||||
|
+Apply the same snapshot rule to `query_notes` when returning `total_count` + paged rows.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**5. Correct first-note rollup semantics (current CTE can return null/incorrect `first_author`)**
|
||||||
|
|
||||||
|
In the proposed SQL, `rn=1` is computed over all notes but then filtered with `is_system=0`, so threads with a leading system note may incorrectly lose `first_author`/snippet. Also path rollup uses non-deterministic `MAX(...)`.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## 3c. SQL Query
|
||||||
|
-ranked_notes AS (
|
||||||
|
+ranked_notes AS (
|
||||||
|
SELECT
|
||||||
|
n.discussion_id,
|
||||||
|
n.author_username,
|
||||||
|
n.body,
|
||||||
|
n.is_system,
|
||||||
|
n.position_new_path,
|
||||||
|
n.position_new_line,
|
||||||
|
- ROW_NUMBER() OVER (
|
||||||
|
- PARTITION BY n.discussion_id
|
||||||
|
- ORDER BY n.position, n.id
|
||||||
|
- ) AS rn
|
||||||
|
+ ROW_NUMBER() OVER (
|
||||||
|
+ PARTITION BY n.discussion_id
|
||||||
|
+ ORDER BY CASE WHEN n.is_system = 0 THEN 0 ELSE 1 END, n.created_at, n.id
|
||||||
|
+ ) AS rn_first_note,
|
||||||
|
+ ROW_NUMBER() OVER (
|
||||||
|
+ PARTITION BY n.discussion_id
|
||||||
|
+ ORDER BY CASE WHEN n.position_new_path IS NULL THEN 1 ELSE 0 END, n.created_at, n.id
|
||||||
|
+ ) AS rn_first_position
|
||||||
|
@@
|
||||||
|
- MAX(CASE WHEN rn = 1 AND is_system = 0 THEN author_username END) AS first_author,
|
||||||
|
- MAX(CASE WHEN rn = 1 AND is_system = 0 THEN body END) AS first_note_body,
|
||||||
|
- MAX(CASE WHEN position_new_path IS NOT NULL THEN position_new_path END) AS position_new_path,
|
||||||
|
- MAX(CASE WHEN position_new_line IS NOT NULL THEN position_new_line END) AS position_new_line
|
||||||
|
+ MAX(CASE WHEN rn_first_note = 1 AND is_system = 0 THEN author_username END) AS first_author,
|
||||||
|
+ MAX(CASE WHEN rn_first_note = 1 AND is_system = 0 THEN body END) AS first_note_body,
|
||||||
|
+ MAX(CASE WHEN rn_first_position = 1 THEN position_new_path END) AS position_new_path,
|
||||||
|
+ MAX(CASE WHEN rn_first_position = 1 THEN position_new_line END) AS position_new_line
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**6. Add per-discussion truncation signals for `--include-notes`**
|
||||||
|
|
||||||
|
Top-level `has_more` is useful, but agents also need to know if an individual thread’s notes were truncated. Otherwise they can’t tell if a thread is complete.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Response Schema
|
||||||
|
{
|
||||||
|
"gitlab_discussion_id": "...",
|
||||||
|
...
|
||||||
|
- "notes": []
|
||||||
|
+ "included_note_count": 0,
|
||||||
|
+ "has_more_notes": false,
|
||||||
|
+ "notes": []
|
||||||
|
}
|
||||||
|
@@ ## 3b. Domain Structs
|
||||||
|
pub struct DiscussionListRowJson {
|
||||||
|
@@
|
||||||
|
+ pub included_note_count: usize,
|
||||||
|
+ pub has_more_notes: bool,
|
||||||
|
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub notes: Vec<NoteListRowJson>,
|
||||||
|
}
|
||||||
|
@@ ## 3c-ii. Note expansion query (--include-notes)
|
||||||
|
-Group by `discussion_id` in Rust and attach notes arrays...
|
||||||
|
+Group by `discussion_id` in Rust, attach notes arrays, and set:
|
||||||
|
+`included_note_count = notes.len()`,
|
||||||
|
+`has_more_notes = note_count > included_note_count`.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**7. Add explicit query-plan gate and targeted index workstream (measured, not speculative)**
|
||||||
|
|
||||||
|
This plan introduces heavy discussion-centric reads. You should bake in deterministic performance validation with `EXPLAIN QUERY PLAN` and only then add indexes if missing.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Scope: Four workstreams, delivered in order:
|
||||||
|
-4. Fix robot-docs to list actual field names instead of opaque type references
|
||||||
|
+4. Add query-plan validation + targeted index updates for new discussion queries
|
||||||
|
+5. Fix robot-docs to list actual field names instead of opaque type references
|
||||||
|
@@
|
||||||
|
+## 4. Query-Plan Validation and Targeted Indexes
|
||||||
|
+
|
||||||
|
+Before and after implementing `query_discussions`, capture `EXPLAIN QUERY PLAN` for:
|
||||||
|
+- `--for-mr <iid> --resolution unresolved`
|
||||||
|
+- `--project <path> --since 7d --sort last_note`
|
||||||
|
+- `--gitlab-discussion-id <id>`
|
||||||
|
+
|
||||||
|
+If plans show table scans on `notes`/`discussions`, add indexes in `MIGRATIONS` array:
|
||||||
|
+- `discussions(project_id, gitlab_discussion_id)`
|
||||||
|
+- `discussions(merge_request_id, last_note_at, id)`
|
||||||
|
+- `notes(discussion_id, created_at DESC, id DESC)`
|
||||||
|
+- `notes(discussion_id, position, id)`
|
||||||
|
+
|
||||||
|
+Tests: assert the new query paths return expected rows under indexed schema and no regressions.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want, I can produce a single consolidated “iteration 4” version of the plan text with all seven revisions merged in place.
|
||||||
160
docs/plan-expose-discussion-ids.feedback-4.md.bak
Normal file
160
docs/plan-expose-discussion-ids.feedback-4.md.bak
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
I reviewed the plan end-to-end and focused only on new improvements (none of the items in `## Rejected Recommendations` are re-proposed).
|
||||||
|
|
||||||
|
1. Add direct `--discussion-id` retrieval paths
|
||||||
|
Rationale: This removes a full discovery hop for the exact workflow that failed (replying to a known thread). It also reduces ambiguity and query cost when an agent already has the thread ID.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Core Changes
|
||||||
|
| 7 | Fix robot-docs to list actual field names | Docs | Small |
|
||||||
|
+| 8 | Add direct `--discussion-id` filter to notes/discussions/show | Core | Small |
|
||||||
|
|
||||||
|
@@ Change 3: Add Standalone `discussions` List Command
|
||||||
|
lore -J discussions --for-mr 99 --cursor <token> # keyset pagination
|
||||||
|
+lore -J discussions --discussion-id 6a9c1750b37d... # direct lookup
|
||||||
|
|
||||||
|
@@ 3a. CLI Args
|
||||||
|
+ #[arg(long, conflicts_with_all = ["for_issue", "for_mr"], help_heading = "Filters")]
|
||||||
|
+ pub discussion_id: Option<String>,
|
||||||
|
|
||||||
|
@@ Change 1: Add `gitlab_discussion_id` to Notes Output
|
||||||
|
+Add `--discussion-id <hex>` filter to `notes` for direct note retrieval within one thread.
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Add a shared filter compiler to eliminate count/query drift
|
||||||
|
Rationale: The plan currently repeats filters across data query, `total_count`, and `incomplete_rows` count queries. That is a classic reliability bug source. A single compiled filter object makes count semantics provably consistent.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Count Semantics (Cross-Cutting Convention)
|
||||||
|
+## Filter Compiler (NEW, Cross-Cutting Convention)
|
||||||
|
+All list commands must build predicates via a shared `CompiledFilters` object that emits:
|
||||||
|
+- SQL predicate fragment
|
||||||
|
+- bind parameters
|
||||||
|
+- canonical filter string (for cursor hash)
|
||||||
|
+The same compiled object is reused by:
|
||||||
|
+- page data query
|
||||||
|
+- `total_count` query
|
||||||
|
+- `incomplete_rows` query
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Harden keyset pagination semantics for `DESC`, limits, and client ergonomics
|
||||||
|
Rationale: `(sort_value, id) > (?, ?)` is only correct for ascending order. Descending sort needs `<`. Also add explicit `has_more` so clients don’t infer from cursor nullability.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Keyset Pagination (Cross-Cutting, Change B)
|
||||||
|
-```sql
|
||||||
|
-WHERE (sort_value, id) > (?, ?)
|
||||||
|
-```
|
||||||
|
+Use comparator by order:
|
||||||
|
+- ASC: `(sort_value, id) > (?, ?)`
|
||||||
|
+- DESC: `(sort_value, id) < (?, ?)`
|
||||||
|
|
||||||
|
@@ 3a. CLI Args
|
||||||
|
+ #[arg(short = 'n', long = "limit", default_value = "50", value_parser = clap::value_parser!(usize).range(1..=500), help_heading = "Output")]
|
||||||
|
+ pub limit: usize,
|
||||||
|
|
||||||
|
@@ Response Schema
|
||||||
|
- "next_cursor": "aW...xyz=="
|
||||||
|
+ "next_cursor": "aW...xyz==",
|
||||||
|
+ "has_more": true
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Add DB-level entity integrity invariants (not just response invariants)
|
||||||
|
Rationale: Response-side filtering is good, but DB correctness should also be guarded. This prevents silent corruption and bad joins from ingestion or future migrations.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Contract Invariants (NEW)
|
||||||
|
+### Entity Integrity Invariants (DB + Ingest)
|
||||||
|
+1. `discussions` must belong to exactly one parent (`issue_id XOR merge_request_id`).
|
||||||
|
+2. `discussions.noteable_type` must match the populated parent column.
|
||||||
|
+3. Natural-key uniqueness is enforced where valid:
|
||||||
|
+ - `(project_id, gitlab_discussion_id)` unique for discussions.
|
||||||
|
+4. Ingestion must reject/quarantine rows violating invariants and report counts.
|
||||||
|
|
||||||
|
@@ Supporting Indexes (Cross-Cutting, Change D)
|
||||||
|
+CREATE UNIQUE INDEX IF NOT EXISTS idx_discussions_project_gitlab_discussion_id
|
||||||
|
+ ON discussions(project_id, gitlab_discussion_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Switch bulk note loading to streaming grouping (avoid large intermediate vecs)
|
||||||
|
Rationale: Current bulk strategy still materializes all notes before grouping. Streaming into the map cuts peak memory and improves large-MR stability.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Change 2e. Constructor — use bulk notes map
|
||||||
|
-let all_note_rows: Vec<MrNoteDetail> = ... // From bulk query above
|
||||||
|
-let notes_by_discussion: HashMap<i64, Vec<MrNoteDetail>> =
|
||||||
|
- all_note_rows.into_iter().fold(HashMap::new(), |mut map, note| {
|
||||||
|
- map.entry(note.discussion_id).or_insert_with(Vec::new).push(note);
|
||||||
|
- map
|
||||||
|
- });
|
||||||
|
+let mut notes_by_discussion: HashMap<i64, Vec<MrNoteDetail>> = HashMap::new();
|
||||||
|
+for row in bulk_note_stmt.query_map(params, map_note_row)? {
|
||||||
|
+ let note = row?;
|
||||||
|
+ notes_by_discussion.entry(note.discussion_id).or_default().push(note);
|
||||||
|
+}
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Make freshness tri-state (`fresh|stale|unknown`) and fail closed on unknown with `--require-fresh`
|
||||||
|
Rationale: `stale: bool` alone cannot represent “never synced / unknown project freshness.” For write safety, unknown freshness should be explicit and reject under freshness constraints.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Freshness Metadata & Staleness Guards
|
||||||
|
pub struct ResponseMeta {
|
||||||
|
pub elapsed_ms: i64,
|
||||||
|
pub data_as_of_iso: String,
|
||||||
|
pub sync_lag_seconds: i64,
|
||||||
|
pub stale: bool,
|
||||||
|
+ pub freshness_state: String, // "fresh" | "stale" | "unknown"
|
||||||
|
+ #[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
+ pub freshness_reason: Option<String>,
|
||||||
|
pub incomplete_rows: i64,
|
||||||
|
@@
|
||||||
|
-if sync_lag_seconds > max_age_secs {
|
||||||
|
+if freshness_state == "unknown" || sync_lag_seconds > max_age_secs {
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Tune indexes to match actual ORDER BY paths in window queries
|
||||||
|
Rationale: `idx_notes_discussion_position` is likely insufficient for the two window orderings. A covering-style index aligned with partition/order keys reduces random table lookups.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Supporting Indexes (Cross-Cutting, Change D)
|
||||||
|
--- Notes: window function ORDER BY (discussion_id, position) for ROW_NUMBER()
|
||||||
|
-CREATE INDEX IF NOT EXISTS idx_notes_discussion_position
|
||||||
|
- ON notes(discussion_id, position);
|
||||||
|
+-- Notes: support dual ROW_NUMBER() orderings and reduce table lookups
|
||||||
|
+CREATE INDEX IF NOT EXISTS idx_notes_discussion_window
|
||||||
|
+ ON notes(discussion_id, is_system, position, created_at, gitlab_id);
|
||||||
|
```
|
||||||
|
|
||||||
|
8. Add a phased rollout gate before strict exclusion becomes default
|
||||||
|
Rationale: Enforcing `gitlab_* IS NOT NULL` immediately can hide data if existing rows are incomplete. A short observation gate prevents sudden regressions while preserving the end-state contract.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Delivery Order
|
||||||
|
+Batch 0: Observability gate (NEW)
|
||||||
|
+- Ship `incomplete_rows` and freshness meta first
|
||||||
|
+- Measure incomplete rate across real datasets
|
||||||
|
+- If incomplete ratio <= threshold, enable strict exclusion defaults
|
||||||
|
+- If above threshold, block rollout and fix ingestion quality first
|
||||||
|
+
|
||||||
|
Change 1 (notes output) ──┐
|
||||||
|
```
|
||||||
|
|
||||||
|
9. Add property-based invariants for pagination/count correctness
|
||||||
|
Rationale: Your current tests are scenario-based and good, but randomized property tests are much better at catching edge-case cursor/count bugs.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Tests (Change 3 / Change B)
|
||||||
|
+**Test 12**: Property-based pagination invariants (`proptest`)
|
||||||
|
+```rust
|
||||||
|
+#[test]
|
||||||
|
+fn prop_discussion_cursor_no_overlap_no_gap_under_random_data() { /* ... */ }
|
||||||
|
+```
|
||||||
|
+
|
||||||
|
+**Test 13**: Property-based count invariants
|
||||||
|
+```rust
|
||||||
|
+#[test]
|
||||||
|
+fn prop_total_count_and_incomplete_rows_match_filter_partition() { /* ... */ }
|
||||||
|
+```
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can now produce a fully consolidated “Plan v4” that applies these diffs cleanly into your original document so it reads as a single coherent spec.
|
||||||
140
docs/plan-expose-discussion-ids.feedback-5.md
Normal file
140
docs/plan-expose-discussion-ids.feedback-5.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
Your iteration 4 plan is already strong. The highest-impact revisions are around query shape, transaction boundaries, and contract stability for agents.
|
||||||
|
|
||||||
|
1. **Switch discussions query to a two-phase page-first architecture**
|
||||||
|
Analysis: Current `ranked_notes` runs over every filtered discussion before `LIMIT`, which can explode on project-wide queries. A page-first plan keeps complexity proportional to `limit`, improves tail latency, and reduces memory churn.
|
||||||
|
```diff
|
||||||
|
@@ ## 3c. SQL Query
|
||||||
|
-Core query uses a CTE + ranked-notes rollup (window function) to avoid per-row correlated
|
||||||
|
-subqueries.
|
||||||
|
+Core query is split into two phases for scalability:
|
||||||
|
+1) `paged_discussions` applies filters/sort/LIMIT and returns only page IDs.
|
||||||
|
+2) Note rollups and optional `--include-notes` expansion run only for those page IDs.
|
||||||
|
+This bounds note scanning to visible results and stabilizes latency on large projects.
|
||||||
|
|
||||||
|
-WITH filtered_discussions AS (
|
||||||
|
+WITH filtered_discussions AS (
|
||||||
|
...
|
||||||
|
),
|
||||||
|
-ranked_notes AS (
|
||||||
|
+paged_discussions AS (
|
||||||
|
+ SELECT id
|
||||||
|
+ FROM filtered_discussions
|
||||||
|
+ ORDER BY COALESCE({sort_column}, 0) {order}, id {order}
|
||||||
|
+ LIMIT ?
|
||||||
|
+),
|
||||||
|
+ranked_notes AS (
|
||||||
|
...
|
||||||
|
- WHERE n.discussion_id IN (SELECT id FROM filtered_discussions)
|
||||||
|
+ WHERE n.discussion_id IN (SELECT id FROM paged_discussions)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Move snapshot transaction ownership to handlers (not query helpers)**
|
||||||
|
Analysis: This avoids nested transaction edge cases, keeps function signatures clean, and guarantees one snapshot across count + page + include-notes + serialization metadata.
|
||||||
|
```diff
|
||||||
|
@@ ## Cross-cutting: snapshot consistency
|
||||||
|
-Wrap `query_notes` and `query_discussions` in a deferred read transaction.
|
||||||
|
+Open one deferred read transaction in each handler (`handle_notes`, `handle_discussions`)
|
||||||
|
+and pass `&Transaction` into query helpers. Query helpers do not open/commit transactions.
|
||||||
|
+This guarantees a single snapshot across all subqueries and avoids nested tx pitfalls.
|
||||||
|
|
||||||
|
-pub fn query_discussions(conn: &Connection, ...)
|
||||||
|
+pub fn query_discussions(tx: &rusqlite::Transaction<'_>, ...)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Add immutable input filter `--project-id` across notes/discussions/show**
|
||||||
|
Analysis: You already expose `gitlab_project_id` because paths are mutable; input should support the same immutable selector. This removes failure modes after project renames/transfers.
|
||||||
|
```diff
|
||||||
|
@@ ## 3a. CLI Args
|
||||||
|
+ /// Filter by immutable GitLab project ID
|
||||||
|
+ #[arg(long, help_heading = "Filters", conflicts_with = "project")]
|
||||||
|
+ pub project_id: Option<i64>,
|
||||||
|
@@ ## Bridge Contract
|
||||||
|
+Input symmetry rule: commands that accept `--project` should also accept `--project-id`.
|
||||||
|
+If both are present, return usage error (exit code 2).
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Enforce bridge fields for nested notes in `discussions --include-notes`**
|
||||||
|
Analysis: Current guardrail is entity-level; nested notes can still lose required IDs under aggressive filtering. This is a contract hole for write-bridging.
|
||||||
|
```diff
|
||||||
|
@@ ### Field Filtering Guardrail
|
||||||
|
-In robot mode, `filter_fields` MUST force-include Bridge Contract fields...
|
||||||
|
+In robot mode, `filter_fields` MUST force-include Bridge Contract fields at all returned levels:
|
||||||
|
+- discussion row fields
|
||||||
|
+- nested note fields when `discussions --include-notes` is used
|
||||||
|
|
||||||
|
+const BRIDGE_FIELDS_DISCUSSION_NOTES: &[&str] = &[
|
||||||
|
+ "project_path", "gitlab_project_id", "noteable_type", "parent_iid",
|
||||||
|
+ "gitlab_discussion_id", "gitlab_note_id",
|
||||||
|
+];
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Make ambiguity preflight scope-aware and machine-actionable**
|
||||||
|
Analysis: Current preflight checks only `gitlab_discussion_id`, which can produce false ambiguity when additional filters already narrow to one project. Also, agents need structured candidates, not only free-text.
|
||||||
|
```diff
|
||||||
|
@@ ### Ambiguity Guardrail
|
||||||
|
-SELECT DISTINCT p.path_with_namespace
|
||||||
|
+SELECT DISTINCT p.path_with_namespace, p.gitlab_project_id
|
||||||
|
FROM discussions d
|
||||||
|
JOIN projects p ON p.id = d.project_id
|
||||||
|
-WHERE d.gitlab_discussion_id = ?
|
||||||
|
+WHERE d.gitlab_discussion_id = ?
|
||||||
|
+ /* plus active scope filters: noteable_type, for_issue/for_mr, since/path when present */
|
||||||
|
LIMIT 3
|
||||||
|
|
||||||
|
-Return LoreError::Ambiguous with message
|
||||||
|
+Return LoreError::Ambiguous with structured details:
|
||||||
|
+`{ code, message, candidates:[{project_path, gitlab_project_id}], suggestion }`
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Add `--contains` filter to `discussions`**
|
||||||
|
Analysis: This is a high-utility agent workflow gap. Agents frequently need “find thread by text then reply”; forcing a separate `notes` search round-trip is unnecessary.
|
||||||
|
```diff
|
||||||
|
@@ ## 3a. CLI Args
|
||||||
|
+ /// Filter discussions whose notes contain text
|
||||||
|
+ #[arg(long, help_heading = "Filters")]
|
||||||
|
+ pub contains: Option<String>,
|
||||||
|
@@ ## 3d. Filters struct
|
||||||
|
+ pub contains: Option<String>,
|
||||||
|
@@ ## 3d. Where-clause construction
|
||||||
|
+- `path` -> EXISTS (...)
|
||||||
|
+- `path` -> EXISTS (...)
|
||||||
|
+- `contains` -> EXISTS (
|
||||||
|
+ SELECT 1 FROM notes n
|
||||||
|
+ WHERE n.discussion_id = d.id
|
||||||
|
+ AND n.body LIKE ?
|
||||||
|
+ )
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Promote two baseline indexes from “candidate” to “required”**
|
||||||
|
Analysis: These are directly hit by new primary paths; waiting for post-merge profiling risks immediate perf cliffs in real usage.
|
||||||
|
```diff
|
||||||
|
@@ ## 3h. Query-plan validation
|
||||||
|
-Candidate indexes (add only if EXPLAIN QUERY PLAN shows they're needed):
|
||||||
|
-- discussions(project_id, gitlab_discussion_id)
|
||||||
|
-- notes(discussion_id, created_at DESC, id DESC)
|
||||||
|
+Required baseline indexes for this feature:
|
||||||
|
+- discussions(project_id, gitlab_discussion_id)
|
||||||
|
+- notes(discussion_id, created_at DESC, id DESC)
|
||||||
|
+Keep other indexes conditional on EXPLAIN QUERY PLAN.
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **Add schema versioning and remove contradictory rejected items**
|
||||||
|
Analysis: `robot-docs` contract drift is a long-term agent risk; explicit schema versions let clients fail safely. Also, rejected items currently contradict active sections, which creates implementation ambiguity.
|
||||||
|
```diff
|
||||||
|
@@ ## 4. Fix Robot-Docs Response Schemas
|
||||||
|
"meta": {"elapsed_ms": "int", ...}
|
||||||
|
+"meta": {"elapsed_ms":"int", ..., "schema_version":"string"}
|
||||||
|
+
|
||||||
|
+Schema version policy:
|
||||||
|
+- bump minor on additive fields
|
||||||
|
+- bump major on removals/renames
|
||||||
|
+- expose per-command versions in `robot-docs`
|
||||||
|
@@ ## Rejected Recommendations
|
||||||
|
-- Add `gitlab_note_id` to show-command note detail structs ... rejected ...
|
||||||
|
-- Add `gitlab_discussion_id` to show-command discussion detail structs ... rejected ...
|
||||||
|
-- Add `gitlab_project_id` to show-command discussion detail structs ... rejected ...
|
||||||
|
+Remove stale rejected entries that conflict with accepted workstreams in this plan iteration.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can produce a fully rewritten iteration 5 plan document that applies all of the above edits cleanly end-to-end.
|
||||||
158
docs/plan-expose-discussion-ids.feedback-5.md.bak
Normal file
158
docs/plan-expose-discussion-ids.feedback-5.md.bak
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
I reviewed the whole plan and only proposed changes that are not in your `## Rejected Recommendations`.
|
||||||
|
|
||||||
|
1. **Fix plan-internal inconsistencies first**
|
||||||
|
Analysis: The plan currently has a few self-contradictions (`8` vs `9` cross-cutting improvements, `stale` still referenced after moving to tri-state freshness). Cleaning this prevents implementation drift and bad AC validation.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@
|
||||||
|
-**Scope**: 8 core changes + 8 cross-cutting architectural improvements across 3 tiers:
|
||||||
|
+**Scope**: 8 core changes + 9 cross-cutting architectural improvements across 3 tiers:
|
||||||
|
@@ AC-7: Freshness Metadata Present & Staleness Guards Work
|
||||||
|
-lore -J notes -n 1 | jq '.meta | {data_as_of_iso, sync_lag_seconds, stale}'
|
||||||
|
-# All fields present, stale=false if recently synced
|
||||||
|
+lore -J notes -n 1 | jq '.meta | {data_as_of_iso, sync_lag_seconds, freshness_state}'
|
||||||
|
+# All fields present, freshness_state is one of fresh|stale|unknown
|
||||||
|
@@ Change 6 Response Schema example
|
||||||
|
- "stale": false,
|
||||||
|
+ "freshness_state": "fresh",
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Require snapshot-consistent list responses (page + counts)**
|
||||||
|
Analysis: `total_count`, `incomplete_rows`, and page rows can drift if sync writes between queries. Enforcing a single read snapshot for all list commands makes pagination and counts deterministic.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ Count Semantics (Cross-Cutting Convention)
|
||||||
|
All list commands use consistent count fields:
|
||||||
|
+All three queries (`page`, `total_count`, `incomplete_rows`) MUST execute inside one read transaction/snapshot.
|
||||||
|
+This guarantees count/page consistency under concurrent sync writes.
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Use RAII transactions instead of manual `BEGIN/COMMIT`**
|
||||||
|
Analysis: Manual `execute_batch("BEGIN...")` is fragile on early returns. `rusqlite::Transaction` guarantees rollback on error and removes transaction-leak risk.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ Change 2: Consistency guarantee
|
||||||
|
-conn.execute_batch("BEGIN DEFERRED")?;
|
||||||
|
-// ... discussion query ...
|
||||||
|
-// ... bulk note query ...
|
||||||
|
-conn.execute_batch("COMMIT")?;
|
||||||
|
+let tx = conn.transaction_with_behavior(rusqlite::TransactionBehavior::Deferred)?;
|
||||||
|
+// ... discussion query ...
|
||||||
|
+// ... bulk note query ...
|
||||||
|
+tx.commit()?;
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Allow small focused new modules for query infrastructure**
|
||||||
|
Analysis: Keeping everything in `list.rs`/`show.rs` will become a maintenance hotspot as filters/cursors/freshness expand. A small module split reduces coupling and regression risk.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ Change 3: File Architecture
|
||||||
|
-**No new files.** Follow existing patterns:
|
||||||
|
+Allow focused infra modules for shared logic:
|
||||||
|
+- `src/cli/query/filters.rs` (CompiledFilters + builders)
|
||||||
|
+- `src/cli/query/cursor.rs` (encode/decode/validate v2 cursors)
|
||||||
|
+- `src/cli/query/freshness.rs` (freshness computation + guards)
|
||||||
|
+Command handlers remain in existing files.
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Add ingest-time `discussion_rollups` to avoid repeated heavy window scans**
|
||||||
|
Analysis: Window functions are good, but doing them on every read over large note volumes is still expensive. Precomputing rollups during ingest gives lower and more predictable p95 latency while keeping read paths simpler.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ Architectural Improvements (Cross-Cutting)
|
||||||
|
+| J | Ingest-time discussion rollups (`discussion_rollups`) | Performance | Medium |
|
||||||
|
@@ Change 3 SQL strategy
|
||||||
|
-Use `ROW_NUMBER()` window function instead of correlated subqueries...
|
||||||
|
+Primary path: join precomputed `discussion_rollups` for `note_count`, `first_author`,
|
||||||
|
+`first_note_body`, `position_new_path`, `position_new_line`.
|
||||||
|
+Fallback path: window-function recompute if rollup row is missing (defensive correctness).
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Add deterministic numeric project selector `--project-id`**
|
||||||
|
Analysis: `-p group/repo` is human-friendly, but numeric project IDs are safer for robots and avoid fuzzy/project-path ambiguity. This reduces false ambiguity failures and lookup overhead.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ DiscussionsArgs
|
||||||
|
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||||
|
pub project: Option<String>,
|
||||||
|
+ #[arg(long, conflicts_with = "project", help_heading = "Filters")]
|
||||||
|
+ pub project_id: Option<i64>,
|
||||||
|
@@ Ambiguity handling
|
||||||
|
+If `--project-id` is provided, IID resolution is scoped directly to that project.
|
||||||
|
+`--project-id` takes precedence over path-based project matching.
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Make path filtering rename-aware (`old` + `new`)**
|
||||||
|
Analysis: Current `--path` strategy only using `position_new_path` misses deleted/renamed-file discussions. Supporting side selection makes the feature materially more useful for review workflows.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ DiscussionsArgs
|
||||||
|
#[arg(long, help_heading = "Filters")]
|
||||||
|
pub path: Option<String>,
|
||||||
|
+ #[arg(long, value_parser = ["either", "new", "old"], default_value = "either", help_heading = "Filters")]
|
||||||
|
+ pub path_side: String,
|
||||||
|
@@ Change 3 filtering
|
||||||
|
-Path filter matches `position_new_path`.
|
||||||
|
+Path filter semantics:
|
||||||
|
+- `either` (default): match `position_new_path` OR `position_old_path`
|
||||||
|
+- `new`: match only `position_new_path`
|
||||||
|
+- `old`: match only `position_old_path`
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **Add explicit freshness behavior for empty-result queries + bootstrap backfill**
|
||||||
|
Analysis: Freshness based only on “participating rows” is undefined when results are empty. Define deterministic behavior and backfill `project_sync_state` on migration so `unknown` doesn’t spike unexpectedly after deploy.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ Freshness state logic
|
||||||
|
+Empty-result rules:
|
||||||
|
+- If query is project-scoped (`-p` or `--project-id`), freshness is computed from that project even when no rows match.
|
||||||
|
+- If query is unscoped and returns zero rows, freshness is computed from all tracked projects.
|
||||||
|
@@ A1. Track per-project sync timestamp
|
||||||
|
+Migration step: seed `project_sync_state` from latest known sync metadata where available
|
||||||
|
+to avoid mass `unknown` freshness immediately after rollout.
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **Upgrade `--discussion-id` from filter-only to first-class thread retrieval**
|
||||||
|
Analysis: Filtering list output by discussion ID still returns list-shaped data and partial note context. A direct thread retrieval mode is faster for agent workflows and avoids extra commands.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ Core Changes
|
||||||
|
-| 8 | Add direct `--discussion-id` filter to notes/discussions/show | Core | Small |
|
||||||
|
+| 8 | Add direct `--discussion-id` filter + single-thread retrieval mode | Core | Medium |
|
||||||
|
@@ Change 8
|
||||||
|
+lore -J discussions --discussion-id <id> --full-thread
|
||||||
|
+# Returns one discussion with full notes payload (same note schema as show command).
|
||||||
|
```
|
||||||
|
|
||||||
|
10. **Replace ad-hoc AC performance timing with repeatable perf harness**
|
||||||
|
Analysis: `time lore ...` is noisy and machine-dependent. A reproducible seeded benchmark test gives stable guardrails and catches regressions earlier.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
--- a/plan.md
|
||||||
|
+++ b/plan.md
|
||||||
|
@@ AC-10: Performance Budget
|
||||||
|
-time lore -J discussions --for-mr <iid> -n 100
|
||||||
|
-# real 0m0.100s (p95 < 150ms)
|
||||||
|
+cargo test --test perf_discussions -- --ignored --nocapture
|
||||||
|
+# Uses seeded fixture DB and N repeated runs; asserts p95 < 150ms for target query shape.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can also produce a fully merged “iteration 5” rewritten plan document with these edits applied end-to-end so it’s directly executable by an implementation agent.
|
||||||
143
docs/plan-expose-discussion-ids.feedback-6.md.bak
Normal file
143
docs/plan-expose-discussion-ids.feedback-6.md.bak
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
Strong plan overall. The biggest gaps I’d fix are around sync-health correctness, idempotency/integrity under repeated ingests, deleted-entity lifecycle, and reducing schema drift risk without heavy reflection machinery.
|
||||||
|
|
||||||
|
I avoided everything in your `## Rejected Recommendations` section.
|
||||||
|
|
||||||
|
**1. Add Sync Health Semantics (not just age)**
|
||||||
|
Time freshness alone can mislead after partial/failed syncs. Agents need to know whether data is both recent and complete.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Freshness Metadata & Staleness Guards (Cross-Cutting, Change A/F/G)
|
||||||
|
- pub freshness_state: String, // "fresh" | "stale" | "unknown"
|
||||||
|
+ pub freshness_state: String, // "fresh" | "stale" | "unknown"
|
||||||
|
+ pub sync_status: String, // "ok" | "partial" | "failed" | "never"
|
||||||
|
+ pub last_successful_sync_run_id: Option<i64>,
|
||||||
|
+ pub last_attempted_sync_run_id: Option<i64>,
|
||||||
|
@@
|
||||||
|
-#[arg(long, help_heading = "Freshness")]
|
||||||
|
-pub require_fresh: Option<String>,
|
||||||
|
+#[arg(long, help_heading = "Freshness")]
|
||||||
|
+pub require_fresh: Option<String>,
|
||||||
|
+#[arg(long, help_heading = "Freshness")]
|
||||||
|
+pub require_sync_ok: bool,
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: this prevents false confidence when one project is fresh-by-time but latest sync actually failed or was partial.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**2. Add `--require-complete` Guard for Missing Required IDs**
|
||||||
|
You already expose `meta.incomplete_rows`; add a hard gate for automation.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Count Semantics (Cross-Cutting Convention)
|
||||||
|
`incomplete_rows` is computed via a dedicated COUNT query...
|
||||||
|
+Add CLI guard:
|
||||||
|
+`--require-complete` fails with exit code 19 when `meta.incomplete_rows > 0`.
|
||||||
|
+Suggested action: `lore sync --full`.
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: agents can fail fast instead of silently acting on partial datasets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**3. Strengthen Ingestion Idempotency + Referential Integrity for Notes**
|
||||||
|
You added natural-key uniqueness for discussions; do the same for notes and enforce parent integrity at DB level.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Supporting Indexes (Cross-Cutting, Change D)
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_discussions_project_gitlab_discussion_id
|
||||||
|
ON discussions(project_id, gitlab_discussion_id);
|
||||||
|
+CREATE UNIQUE INDEX IF NOT EXISTS idx_notes_project_gitlab_id
|
||||||
|
+ ON notes(project_id, gitlab_id);
|
||||||
|
+
|
||||||
|
+-- Referential integrity
|
||||||
|
+-- notes.discussion_id REFERENCES discussions(id)
|
||||||
|
+-- notes.project_id REFERENCES projects(id)
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: repeated syncs and retries won’t duplicate notes, and orphaned rows can’t accumulate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**4. Add Deleted/Tombstoned Entity Lifecycle**
|
||||||
|
Current plan excludes null IDs but doesn’t define behavior when GitLab entities are deleted after sync.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Contract Invariants (NEW)
|
||||||
|
+### Deletion Lifecycle Invariant
|
||||||
|
+1. Notes/discussions deleted upstream are tombstoned locally (`deleted_at`), not hard-deleted.
|
||||||
|
+2. All list/show commands exclude tombstoned rows by default.
|
||||||
|
+3. Optional flag `--include-deleted` exposes tombstoned rows for audit/debug.
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: preserves auditability, prevents ghost actions on deleted objects, and avoids destructive resync behavior.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**5. Expand Discussions Payload for Rename Accuracy + Better Triage**
|
||||||
|
`--path-side old` is great, but output currently only returns `position_new_*`.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Change 3: Add Standalone `discussions` List Command
|
||||||
|
pub position_new_path: Option<String>,
|
||||||
|
pub position_new_line: Option<i64>,
|
||||||
|
+ pub position_old_path: Option<String>,
|
||||||
|
+ pub position_old_line: Option<i64>,
|
||||||
|
+ pub last_author: Option<String>,
|
||||||
|
+ pub participant_usernames: Vec<String>,
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: for renamed/deleted files, agents need old and new coordinates to act confidently; participants/last_author improve thread routing and prioritization.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**6. Add SQLite Busy Handling + Retry Policy**
|
||||||
|
Read transactions + concurrent sync writes can still produce `SQLITE_BUSY` under load.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Count Semantics (Cross-Cutting Convention)
|
||||||
|
**Snapshot consistency**: All three queries ... inside a single read transaction ...
|
||||||
|
+**Busy handling**: set `PRAGMA busy_timeout` (e.g. 5000ms) and retry transient
|
||||||
|
+`SQLITE_BUSY` errors up to 3 times with jittered backoff for read commands.
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: improves reliability in real multi-agent usage without changing semantics.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**7. Make Field Definitions Single-Source (Lightweight Drift Prevention)**
|
||||||
|
You rejected full schema generation from code; a lower-cost middle ground is shared field manifests used by both docs and `--fields` validation.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Change 7: Fix Robot-Docs Response Schemas
|
||||||
|
+#### 7h. Single-source field manifests (no reflection)
|
||||||
|
+Define per-command field constants (e.g. `NOTES_FIELDS`, `DISCUSSIONS_FIELDS`)
|
||||||
|
+used by:
|
||||||
|
+1) `--fields` validation/filtering
|
||||||
|
+2) `--fields minimal` expansion
|
||||||
|
+3) `robot-docs` schema rendering
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: cuts drift risk materially while staying much simpler than reflection/snapshot infra.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**8. De-duplicate and Upgrade Test Strategy Around Concurrency**
|
||||||
|
There are duplicated tests across Change 2 and Change 3; add explicit race tests where sync writes happen between list subqueries to prove tx consistency.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Tests
|
||||||
|
-**Test 6**: `--project-id` scopes IID resolution directly
|
||||||
|
-**Test 7**: `--path-side old` matches renamed file discussions
|
||||||
|
-**Test 8**: `--path-side either` matches both old and new paths
|
||||||
|
+Move shared discussion-filter tests to a single section under Change 3.
|
||||||
|
+Add concurrency tests:
|
||||||
|
+1) count/page/incomplete consistency under concurrent sync writes
|
||||||
|
+2) show discussion+notes snapshot consistency under concurrent writes
|
||||||
|
```
|
||||||
|
|
||||||
|
Rationale: less maintenance noise, better coverage of your highest-risk correctness path.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If you want, I can also produce a single consolidated patch block that rewrites your plan text end-to-end with these edits applied in-place.
|
||||||
2128
docs/plan-expose-discussion-ids.md
Normal file
2128
docs/plan-expose-discussion-ids.md
Normal file
File diff suppressed because it is too large
Load Diff
169
docs/plan-surgical-sync.feedback-3.md
Normal file
169
docs/plan-surgical-sync.feedback-3.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
Below are the strongest **new** revisions I’d make (excluding everything in your rejected list), with rationale and plan-level diffs.
|
||||||
|
|
||||||
|
### 1. Add a durable run ledger (`sync_runs`) with phase state
|
||||||
|
This makes surgical sync crash-resumable, auditable, and safer under Ctrl+C. Right now `run_id` is mostly ephemeral; persisting phase state removes ambiguity about what completed.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
+9. **Durable run state**: Surgical sync MUST persist a `sync_runs` row keyed by `run_id`
|
||||||
|
+ with phase transitions (`preflight`, `ingest`, `dependents`, `docs`, `embed`, `done`, `failed`).
|
||||||
|
+ This is required for crash recovery, observability, and deterministic retries.
|
||||||
|
|
||||||
|
@@ Step 9: Create `run_sync_surgical`
|
||||||
|
+Before Stage 0, insert `sync_runs(run_id, project_id, mode='surgical', requested_counts, started_at)`.
|
||||||
|
+After each stage, update `sync_runs.phase`, counters, and `last_error` if present.
|
||||||
|
+On success/failure, set terminal state (`done`/`failed`) and `finished_at`.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Add `--preflight-only` (network validation without writes)
|
||||||
|
`--dry-run` is intentionally zero-network, so it cannot validate IIDs. `--preflight-only` is high-value for agents: verifies existence/permissions quickly with no DB mutation.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ CLI Interface
|
||||||
|
lore sync --dry-run --issue 123 -p myproject
|
||||||
|
+lore sync --preflight-only --issue 123 -p myproject
|
||||||
|
|
||||||
|
@@ Step 2: Add `--issue`, `--mr`, `-p` to `SyncArgs`
|
||||||
|
+ /// Validate remote entities and auth without any DB writes
|
||||||
|
+ #[arg(long, default_value_t = false)]
|
||||||
|
+ pub preflight_only: bool,
|
||||||
|
|
||||||
|
@@ Step 10: Add branch in `run_sync`
|
||||||
|
+if options.preflight_only && options.is_surgical() {
|
||||||
|
+ return run_sync_surgical_preflight_only(config, &options, run_id, signal).await;
|
||||||
|
+}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Preflight should aggregate all missing/failed IIDs, not fail-fast
|
||||||
|
Fail-fast causes repeated reruns. Aggregating errors gives one-shot correction and better robot automation.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Step 7: Create `src/ingestion/surgical.rs`
|
||||||
|
-/// Returns the fetched payloads. If ANY fetch fails, the entire operation should abort.
|
||||||
|
+/// Returns fetched payloads plus per-IID failures; caller aborts writes if failures exist.
|
||||||
|
pub async fn preflight_fetch(...) -> Result<PreflightResult> {
|
||||||
|
|
||||||
|
@@
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct PreflightResult {
|
||||||
|
pub issues: Vec<GitLabIssue>,
|
||||||
|
pub merge_requests: Vec<GitLabMergeRequest>,
|
||||||
|
+ pub failures: Vec<EntityFailure>, // stage="fetch"
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ Step 9: Create `run_sync_surgical`
|
||||||
|
-let preflight = preflight_fetch(...).await?;
|
||||||
|
+let preflight = preflight_fetch(...).await?;
|
||||||
|
+if !preflight.failures.is_empty() {
|
||||||
|
+ result.entity_failures = preflight.failures;
|
||||||
|
+ return Err(LoreError::Other("Surgical preflight failed for one or more IIDs".into()).into());
|
||||||
|
+}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Stop filtering scoped queue drains with raw `json_extract` scans
|
||||||
|
`json_extract(payload_json, '$.scope_run_id')` in hot drain queries will degrade as queue grows. Use indexed scope metadata.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Step 9b: Implement scoped drain helpers
|
||||||
|
-// claim query adds:
|
||||||
|
-// AND json_extract(payload_json, '$.scope_run_id') = ?
|
||||||
|
+// Add migration:
|
||||||
|
+// 1) Add `scope_run_id` generated/stored column derived from payload_json (or explicit column)
|
||||||
|
+// 2) Create index on (project_id, job_type, scope_run_id, status, id)
|
||||||
|
+// Scoped drains filter by indexed `scope_run_id`, not full-table JSON extraction.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Replace `dirty_source_ids` collection-by-query with explicit run scoping
|
||||||
|
Current approach can accidentally include prior dirty rows for same source and can duplicate work. Tag dirty rows with `origin_run_id` and consume by run.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
-2. **Dirty queue scoping**: ... MUST call ... `run_generate_docs_for_dirty_ids`
|
||||||
|
+2. **Dirty queue scoping**: Surgical sync MUST scope docs by `origin_run_id` on `dirty_sources`
|
||||||
|
+ (or equivalent exact run marker) and MUST NOT drain unrelated dirty rows.
|
||||||
|
|
||||||
|
@@ Step 7: `SurgicalIngestResult`
|
||||||
|
- pub dirty_source_ids: Vec<i64>,
|
||||||
|
+ pub origin_run_id: String,
|
||||||
|
|
||||||
|
@@ Step 9a: Implement `run_generate_docs_for_dirty_ids`
|
||||||
|
-pub fn run_generate_docs_for_dirty_ids(config: &Config, dirty_source_ids: &[i64]) -> Result<...>
|
||||||
|
+pub fn run_generate_docs_for_run_id(config: &Config, run_id: &str) -> Result<...>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Enforce transaction safety at the type boundary
|
||||||
|
`unchecked_transaction()` + `&Connection` signatures is fragile. Accept `&Transaction` for ingest internals and use `TransactionBehavior::Immediate` for deterministic lock behavior.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Step 7: Create `src/ingestion/surgical.rs`
|
||||||
|
-pub fn ingest_issue_by_iid_from_payload(conn: &Connection, ...)
|
||||||
|
+pub fn ingest_issue_by_iid_from_payload(tx: &rusqlite::Transaction<'_>, ...)
|
||||||
|
|
||||||
|
-pub fn ingest_mr_by_iid_from_payload(conn: &Connection, ...)
|
||||||
|
+pub fn ingest_mr_by_iid_from_payload(tx: &rusqlite::Transaction<'_>, ...)
|
||||||
|
|
||||||
|
-let tx = conn.unchecked_transaction()?;
|
||||||
|
+let tx = conn.transaction_with_behavior(rusqlite::TransactionBehavior::Immediate)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Acquire sync lock only for mutation phases, not remote preflight
|
||||||
|
This materially reduces lock contention and keeps normal sync throughput higher, while still guaranteeing mutation serialization.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
+10. **Lock window minimization**: Preflight fetch runs without sync lock; lock is acquired immediately
|
||||||
|
+ before first DB mutation and held through all mutation stages.
|
||||||
|
|
||||||
|
@@ Step 9: Create `run_sync_surgical`
|
||||||
|
-// ── Acquire sync lock ──
|
||||||
|
-...
|
||||||
|
-// ── Stage 0: Preflight fetch ──
|
||||||
|
+// ── Stage 0: Preflight fetch (no lock, no writes) ──
|
||||||
|
...
|
||||||
|
+// ── Acquire sync lock just before Stage 1 mutation ──
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Add explicit transient retry policy beyond 429
|
||||||
|
Client already handles rate limits; surgical reliability improves a lot if 5xx/timeouts are retried with bounded backoff.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
+11. **Transient retry policy**: Preflight and dependent remote fetches MUST retry boundedly on
|
||||||
|
+ timeout/5xx with jittered backoff; permanent errors (404/401/403) fail immediately.
|
||||||
|
|
||||||
|
@@ Step 5: Add `get_issue_by_iid` / `get_mr_by_iid`
|
||||||
|
+Document retry behavior for transient transport/server failures.
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Tighten automated tests around scoping invariants
|
||||||
|
You already list manual checks; these should be enforced in unit/integration tests to prevent regressions.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Step 1: TDD — Write Failing Tests First
|
||||||
|
+### 1d. New invariants tests
|
||||||
|
+- `surgical_docs_scope_ignores_preexisting_dirty_rows`
|
||||||
|
+- `scoped_queue_drain_ignores_orphaned_jobs`
|
||||||
|
+- `preflight_aggregates_multiple_missing_iids`
|
||||||
|
+- `preflight_only_performs_zero_writes`
|
||||||
|
+- `dry_run_performs_zero_network_calls`
|
||||||
|
+- `lock_window_does_not_block_during_preflight`
|
||||||
|
|
||||||
|
@@ Acceptance Criteria
|
||||||
|
+32. Scoped queue/docs invariants are covered by automated tests (not manual-only verification).
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. Make robot-mode surgical output first-class
|
||||||
|
For agent workflows, include full stage telemetry and actionable recovery commands.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Step 15: Update `SyncResult` for robot mode structured output
|
||||||
|
+ /// Per-stage elapsed ms for deterministic performance tracking
|
||||||
|
+ pub stage_timings_ms: std::collections::BTreeMap<String, u64>,
|
||||||
|
+ /// Suggested recovery commands (robot ergonomics)
|
||||||
|
+ pub recovery_actions: Vec<String>,
|
||||||
|
|
||||||
|
@@ Step 14: Update `robot-docs` manifest
|
||||||
|
+Document surgical-specific error codes and `actions` schema for automated recovery.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can now produce a fully rewritten **iteration 3** plan that merges these into your current structure end-to-end.
|
||||||
212
docs/plan-surgical-sync.feedback-4.md
Normal file
212
docs/plan-surgical-sync.feedback-4.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
1. **Resolve the current contract contradictions (`preflight-only`, `dry-run`, `sync_runs`)**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- Right now constraints conflict: “zero DB writes before commit” vs inserting `sync_runs` during preflight.
|
||||||
|
- This ambiguity will cause implementation drift and flaky acceptance tests.
|
||||||
|
- Splitting control-plane writes from content-plane writes keeps safety guarantees strict while preserving observability.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Design Constraints
|
||||||
|
-6. **Preflight-then-commit**: All remote fetches happen BEFORE any DB writes. If any IID fetch fails (404, network error), the entire operation aborts with zero DB mutations.
|
||||||
|
+6. **Preflight-then-commit (content-plane)**: All remote fetches happen BEFORE any writes to content tables (`issues`, `merge_requests`, `discussions`, `resource_events`, `documents`, `embeddings`).
|
||||||
|
+7. **Control-plane exception**: `sync_runs` / `sync_run_entities` writes are allowed during preflight for observability and crash diagnostics.
|
||||||
|
@@
|
||||||
|
-11. **Preflight-only mode**: `--preflight-only` validates remote entity existence and permissions with zero DB writes.
|
||||||
|
+11. **Preflight-only mode**: `--preflight-only` performs zero content writes; control-plane run-ledger writes are allowed.
|
||||||
|
@@ ### For me to evaluate (functional):
|
||||||
|
-24. **Preflight-only mode** ... no DB mutations beyond the sync_runs ledger entry
|
||||||
|
+24. **Preflight-only mode** ... no content DB mutations; only run-ledger rows may be written
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
2. **Add stale-write protection to avoid TOCTOU regressions during unlocked preflight**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- You intentionally preflight without lock; that’s good for throughput but introduces race risk.
|
||||||
|
- Without a guard, a slower surgical run can overwrite newer data ingested by a concurrent normal sync.
|
||||||
|
- This is a correctness bug under contention, not a nice-to-have.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Design Constraints
|
||||||
|
+12. **Stale-write protection**: Surgical ingest MUST NOT overwrite fresher local rows. If local `updated_at` is newer than the preflight payload’s `updated_at`, skip that entity and record `skipped_stale`.
|
||||||
|
@@ ## Step 7: Create `src/ingestion/surgical.rs`
|
||||||
|
- let labels_created = process_single_issue(conn, config, project_id, issue)?;
|
||||||
|
+ // Skip stale payloads to avoid TOCTOU overwrite after unlocked preflight.
|
||||||
|
+ if is_local_newer_issue(conn, project_id, issue.iid, issue.updated_at)? {
|
||||||
|
+ result.skipped_stale += 1;
|
||||||
|
+ return Ok(result);
|
||||||
|
+ }
|
||||||
|
+ let labels_created = process_single_issue(conn, config, project_id, issue)?;
|
||||||
|
@@
|
||||||
|
+// same guard for MR path
|
||||||
|
@@ ## Step 15: Update `SyncResult`
|
||||||
|
+ /// Entities skipped because local row was newer than preflight payload
|
||||||
|
+ pub skipped_stale: usize,
|
||||||
|
@@ ### Edge cases to verify:
|
||||||
|
+38. **TOCTOU safety**: if a normal sync updates entity after preflight but before ingest, surgical run skips stale payload (no overwrite)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
3. **Make dirty-source scoping exact (do not capture pre-existing rows for same entity)**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- Current “query dirty rows by `source_id` after ingest” can accidentally include older dirty rows for the same entity.
|
||||||
|
- That silently violates strict run scoping and can delete unrelated backlog rows.
|
||||||
|
- You can fix this without adding `origin_run_id` to `dirty_sources` (which you already rejected).
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Step 7: Create `src/ingestion/surgical.rs`
|
||||||
|
- // Collect dirty_source rows for this entity
|
||||||
|
- let mut stmt = conn.prepare(
|
||||||
|
- "SELECT id FROM dirty_sources WHERE source_type = 'issue' AND source_id = ?1"
|
||||||
|
- )?;
|
||||||
|
+ // Capture only rows inserted by THIS call using high-water mark.
|
||||||
|
+ let before_dirty_id: i64 = conn.query_row(
|
||||||
|
+ "SELECT COALESCE(MAX(id), 0) FROM dirty_sources",
|
||||||
|
+ [], |r| r.get(0),
|
||||||
|
+ )?;
|
||||||
|
+ // ... call process_single_issue ...
|
||||||
|
+ let mut stmt = conn.prepare(
|
||||||
|
+ "SELECT id FROM dirty_sources
|
||||||
|
+ WHERE id > ?1 AND source_type = 'issue' AND source_id = ?2"
|
||||||
|
+ )?;
|
||||||
|
@@
|
||||||
|
+ // same pattern for MR
|
||||||
|
@@ ### 1d. Scoping invariant tests
|
||||||
|
+#[test]
|
||||||
|
+fn surgical_docs_scope_ignores_preexisting_dirty_rows_for_same_entity() {
|
||||||
|
+ // pre-insert dirty row for iid=7, then surgical ingest iid=7
|
||||||
|
+ // assert result.dirty_source_ids only contains newly inserted rows
|
||||||
|
+}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
4. **Fix embed-stage leakage when `--no-docs` is used in surgical mode**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- Current design can run global embed even when docs stage is skipped, which may embed unrelated backlog docs.
|
||||||
|
- That breaks the surgical “scope only this run” promise.
|
||||||
|
- This is both correctness and operator-trust critical.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Step 9: Create `run_sync_surgical`
|
||||||
|
- if !options.no_embed {
|
||||||
|
+ // Surgical embed only runs when surgical docs actually regenerated docs in this run.
|
||||||
|
+ if !options.no_embed && !options.no_docs && result.documents_regenerated > 0 {
|
||||||
|
@@ ## Step 4: Wire new fields in `handle_sync_cmd`
|
||||||
|
+ if options.is_surgical() && options.no_docs && !options.no_embed {
|
||||||
|
+ return Err(Box::new(LoreError::Other(
|
||||||
|
+ "In surgical mode, --no-docs requires --no-embed (to preserve scoping guarantees)".to_string()
|
||||||
|
+ )));
|
||||||
|
+ }
|
||||||
|
@@ ### For me to evaluate
|
||||||
|
+39. **No embed leakage**: `sync --issue X --no-docs` never embeds unrelated unembedded docs
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
5. **Add queue-failure hygiene so scoped jobs do not leak forever**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- Scoped drains prevent accidental processing, but failed runs can strand pending jobs permanently.
|
||||||
|
- You need explicit terminalization (`aborted`) and optional replay mechanics.
|
||||||
|
- Otherwise queue bloat and confusing diagnostics accumulate.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Step 8a: Add `sync_runs` table migration
|
||||||
|
+ALTER TABLE dependent_queue ADD COLUMN aborted_reason TEXT;
|
||||||
|
+-- status domain now includes: pending, claimed, done, failed, aborted
|
||||||
|
@@ ## Step 9: run_sync_surgical failure paths
|
||||||
|
+// On run failure/cancel:
|
||||||
|
+conn.execute(
|
||||||
|
+ "UPDATE dependent_queue
|
||||||
|
+ SET status='aborted', aborted_reason=?1
|
||||||
|
+ WHERE project_id=?2 AND scope_run_id=?3 AND status='pending'",
|
||||||
|
+ rusqlite::params![failure_summary, project_id, run_id],
|
||||||
|
+)?;
|
||||||
|
@@ ## Acceptance Criteria
|
||||||
|
+40. **No stranded scoped jobs**: failed surgical runs leave no `pending` rows for their `scope_run_id`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
6. **Persist per-entity lifecycle (`sync_run_entities`) for real observability and deterministic retry**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- `sync_runs` alone gives aggregate counters but not which IID failed at which stage.
|
||||||
|
- Per-entity records make retries deterministic and robot output far more useful.
|
||||||
|
- This is the missing piece for your stated “deterministic retry decisions.”
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Step 8a: Add `sync_runs` table migration
|
||||||
|
+CREATE TABLE IF NOT EXISTS sync_run_entities (
|
||||||
|
+ id INTEGER PRIMARY KEY,
|
||||||
|
+ run_id TEXT NOT NULL REFERENCES sync_runs(run_id),
|
||||||
|
+ entity_type TEXT NOT NULL CHECK(entity_type IN ('issue','merge_request')),
|
||||||
|
+ iid INTEGER NOT NULL,
|
||||||
|
+ stage TEXT NOT NULL,
|
||||||
|
+ status TEXT NOT NULL CHECK(status IN ('ok','failed','skipped_stale')),
|
||||||
|
+ error_code TEXT,
|
||||||
|
+ error_message TEXT,
|
||||||
|
+ updated_at INTEGER NOT NULL
|
||||||
|
+);
|
||||||
|
+CREATE INDEX IF NOT EXISTS idx_sync_run_entities_run ON sync_run_entities(run_id, entity_type, iid);
|
||||||
|
@@ ## Step 15: Update `SyncResult`
|
||||||
|
+ pub failed_iids: Vec<(String, u64)>,
|
||||||
|
+ pub skipped_stale_iids: Vec<(String, u64)>,
|
||||||
|
@@ ## CLI Interface
|
||||||
|
+lore --robot sync-runs --run-id <id>
|
||||||
|
+lore --robot sync-runs --run-id <id> --retry-failed
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
7. **Use explicit error type for surgical preflight failures (not `LoreError::Other`)**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- `Other(String)` loses machine semantics, weakens robot mode, and leads to bad exit-code behavior.
|
||||||
|
- A typed error preserves structured failures and enables actionable recovery commands.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Step 9: run_sync_surgical
|
||||||
|
- return Err(LoreError::Other(
|
||||||
|
- format!("Surgical preflight failed for {} of {} IIDs: {}", ...)
|
||||||
|
- ).into());
|
||||||
|
+ return Err(LoreError::SurgicalPreflightFailed {
|
||||||
|
+ run_id: run_id.to_string(),
|
||||||
|
+ total: total_items,
|
||||||
|
+ failures: preflight.failures.clone(),
|
||||||
|
+ }.into());
|
||||||
|
@@ ## Step 15: Update `SyncResult`
|
||||||
|
+ /// Machine-actionable error summary for robot mode
|
||||||
|
+ pub error_code: Option<String>,
|
||||||
|
@@ ## Acceptance Criteria
|
||||||
|
+41. **Typed failure**: preflight failures serialize structured errors (not generic `Other`) with machine-usable codes/actions
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
8. **Strengthen tests for rollback, contention, and stale-skip guarantees**
|
||||||
|
|
||||||
|
Why this improves the plan:
|
||||||
|
- Current tests cover many happy-paths and scoping invariants, but key race/rollback behaviors are still under-tested.
|
||||||
|
- These are exactly where regressions will appear first in production.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ ## Step 1: TDD — Write Failing Tests First
|
||||||
|
+### 1f. Transactional rollback + TOCTOU tests
|
||||||
|
+1. `preflight_success_then_ingest_failure_rolls_back_all_content_writes`
|
||||||
|
+2. `stale_payload_is_skipped_when_local_updated_at_is_newer`
|
||||||
|
+3. `failed_run_aborts_pending_scoped_jobs`
|
||||||
|
+4. `surgical_no_docs_requires_no_embed`
|
||||||
|
@@ ### Automated scoping invariants
|
||||||
|
-38. **Scoped queue/docs invariants are enforced by automated tests**
|
||||||
|
+42. **Rollback and race invariants are enforced by automated tests** (no partial writes on ingest failure, no stale overwrite)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
These eight revisions keep your core approach intact, avoid your explicitly rejected ideas, and close the biggest correctness/operability gaps before implementation.
|
||||||
130
docs/plan-surgical-sync.feedback-5.md
Normal file
130
docs/plan-surgical-sync.feedback-5.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
**Critical Gaps In Current Plan**
|
||||||
|
1. `dirty_sources` scoping is based on `id`, but `dirty_sources` has no `id` column and uses `(source_type, source_id)` UPSERT semantics.
|
||||||
|
2. Plan assumes a new `dependent_queue` with `status`, but current code uses `pending_dependent_fetches` (delete-on-complete), so queue-scoping design conflicts with existing invariants.
|
||||||
|
3. Constraint 6 says all remote fetches happen before any content writes, but the proposed surgical flow fetches discussions/events/diffs after ingest writes.
|
||||||
|
4. `sync_runs` is already an existing table and already used by `SyncRunRecorder`; the plan currently treats it like a new table.
|
||||||
|
|
||||||
|
**Best Revisions**
|
||||||
|
|
||||||
|
1. **Fix dirty-source scoping to match real schema (queued-at watermark, not `id` high-water).**
|
||||||
|
Why this is better: This removes a correctness bug and makes same-entity re-ingest deterministic under UPSERT behavior.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
-2. Dirty queue scoping: ... capture MAX(id) FROM dirty_sources ... run_generate_docs_for_dirty_ids ...
|
||||||
|
+2. Dirty queue scoping: `dirty_sources` is keyed by `(source_type, source_id)` and updated via UPSERT.
|
||||||
|
+ Surgical scoping MUST use:
|
||||||
|
+ 1) a run-level `run_dirty_floor_ms` captured before surgical ingest, and
|
||||||
|
+ 2) explicit touched source keys from ingest (`(source_type, source_id)`).
|
||||||
|
+ Surgical docs MUST call a scoped API (e.g. `run_generate_docs_for_sources`) and MUST NOT drain global dirty queue.
|
||||||
|
@@ Step 9a
|
||||||
|
-pub fn run_generate_docs_for_dirty_ids(config: &Config, dirty_source_ids: &[i64]) -> Result<GenerateDocsResult>
|
||||||
|
+pub fn run_generate_docs_for_sources(config: &Config, sources: &[(SourceType, i64)]) -> Result<GenerateDocsResult>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Bypass shared dependent queue in surgical mode; run dependents inline per target.**
|
||||||
|
Why this is better: Avoids queue migration churn, avoids run-scope conflicts with existing unique constraints, and removes orphan-job hygiene complexity entirely.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
-4. Dependent queue scoping: ... scope_run_id indexed column on dependent_queue ...
|
||||||
|
+4. Surgical dependent execution: surgical mode MUST bypass `pending_dependent_fetches`.
|
||||||
|
+ Dependents (resource_events, mr_closes_issues, mr_diffs) run inline for targeted entities only.
|
||||||
|
+ Global queue remains for normal sync only.
|
||||||
|
@@ Design Constraints
|
||||||
|
-14. Queue failure hygiene: ... pending scoped jobs ... terminalized to aborted ...
|
||||||
|
+14. Surgical failure hygiene: surgical mode MUST leave no queue artifacts because it does not enqueue dependent jobs.
|
||||||
|
@@ Step 9b / 9c / Step 13
|
||||||
|
-Implement scoped drain helpers and enqueue_job scope_run_id plumbing
|
||||||
|
+Replace with direct per-entity helpers in ingestion layer:
|
||||||
|
+ - sync_issue_resource_events_direct(...)
|
||||||
|
+ - sync_mr_resource_events_direct(...)
|
||||||
|
+ - sync_mr_closes_issues_direct(...)
|
||||||
|
+ - sync_mr_diffs_direct(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Clarify atomicity contract to “primary-entity atomicity” (remove contradiction).**
|
||||||
|
Why this is better: Keeps strong zero-write guarantees for missing IIDs while matching practical staged pipeline behavior.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
-6. Preflight-then-commit (content-plane): All remote fetches happen BEFORE any writes to content tables ...
|
||||||
|
+6. Primary-entity atomicity: all requested issue/MR payload fetches complete before first content write.
|
||||||
|
+ If any primary IID fetch fails, primary ingest does zero content writes.
|
||||||
|
+ Dependent stages (discussions/events/diffs/closes) are post-ingest and best-effort, with structured per-stage failure reporting.
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Extend existing `sync_runs` schema instead of redefining it.**
|
||||||
|
Why this is better: Preserves compatibility with current `SyncRunRecorder`, `sync_status`, and existing historical data.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Step 8a
|
||||||
|
-Add `sync_runs` table migration (CREATE TABLE sync_runs ...)
|
||||||
|
+Add migration 027 to extend existing `sync_runs` table:
|
||||||
|
+ - ADD COLUMN mode TEXT NULL -- 'standard' | 'surgical'
|
||||||
|
+ - ADD COLUMN phase TEXT NULL -- preflight|ingest|dependents|docs|embed|done|failed
|
||||||
|
+ - ADD COLUMN surgical_summary_json TEXT NULL
|
||||||
|
+Reuse `SyncRunRecorder` row lifecycle; do not introduce a parallel run-ledger model.
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Strengthen TOCTOU stale protection for equal timestamps.**
|
||||||
|
Why this is better: Prevents regressions when `updated_at` is equal but a fresher local fetch already happened.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
-13. ... If local `updated_at` is newer than preflight payload `updated_at`, skip ...
|
||||||
|
+13. ... Skip stale when:
|
||||||
|
+ a) local.updated_at > payload.updated_at, OR
|
||||||
|
+ b) local.updated_at == payload.updated_at AND local.last_seen_at > preflight_started_at_ms.
|
||||||
|
+ This prevents equal-timestamp regressions under concurrent sync.
|
||||||
|
@@ Step 1f tests
|
||||||
|
+Add test: `equal_updated_at_but_newer_last_seen_is_skipped`.
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Shrink lock window further: release `sync` lock before embed; use dedicated embed lock.**
|
||||||
|
Why this is better: Prevents long embedding from blocking unrelated syncs and avoids concurrent embed writers.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Design Constraints
|
||||||
|
-11. Lock ... held through all mutation stages.
|
||||||
|
+11. Lock ... held through ingest/dependents/docs only.
|
||||||
|
+ Release `AppLock("sync")` before embed.
|
||||||
|
+ Embed stage uses `AppLock("embed")` for single-flight embedding writes.
|
||||||
|
@@ Step 9
|
||||||
|
-Embed runs inside the same sync lock window
|
||||||
|
+Embed runs after sync lock release, under dedicated embed lock
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Add the missing `sync-runs` robot read path (the plan references it but doesn’t define it).**
|
||||||
|
Why this is better: Makes durable run-state actually useful for recovery automation and observability.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ Step 14 (new)
|
||||||
|
+## Step 14a: Add `sync-runs` read command
|
||||||
|
+
|
||||||
|
+CLI:
|
||||||
|
+ lore --robot sync-runs --limit 20
|
||||||
|
+ lore --robot sync-runs --run-id <id>
|
||||||
|
+ lore --robot sync-runs --state failed
|
||||||
|
+
|
||||||
|
+Robot response fields:
|
||||||
|
+ run_id, mode, phase, status, started_at, finished_at, counters, failures, suggested_retry_command
|
||||||
|
```
|
||||||
|
|
||||||
|
8. **Add URL-native surgical targets (`--issue-url`, `--mr-url`) with project inference.**
|
||||||
|
Why this is better: Much more agent-friendly and reduces project-resolution errors from copy/paste workflows.
|
||||||
|
|
||||||
|
```diff
|
||||||
|
@@ CLI Interface
|
||||||
|
lore sync --issue 123 --issue 456 -p myproject
|
||||||
|
+lore sync --issue-url https://gitlab.example.com/group/proj/-/issues/123
|
||||||
|
+lore sync --mr-url https://gitlab.example.com/group/proj/-/merge_requests/789
|
||||||
|
@@ Step 2
|
||||||
|
+Add repeatable flags:
|
||||||
|
+ --issue-url <url>
|
||||||
|
+ --mr-url <url>
|
||||||
|
+Parse URL into (project_path, iid). If all targets are URL-derived and same project, `-p` is optional.
|
||||||
|
+If mixed projects are provided in one command, reject with clear error.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want, I can produce a single consolidated patched version of your plan (iteration 5 draft) with these revisions already merged.
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user