feat(protocol): expand Obsidian wikilinks in attach_context
When attaching Obsidian notes, extract [[wikilinks]] from the note content and resolve them to actual vault files. Linked notes are attached as "obsidian_wikilink" type, bounded by remaining_tokens. Supports both [[Note]] and [[Note|Display]] syntax. Closes #102 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
92c551f1b7
commit
263adf87cd
6 changed files with 103 additions and 16 deletions
|
|
@ -3,13 +3,13 @@
|
|||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write|NotebookEdit",
|
||||
"hook": "bash /Users/shunsukehayashi/dev/platform/miyabi-cli-standalone/scripts/hook-check-lock.sh \"$TOOL_INPUT\""
|
||||
"hook": "bash scripts/hook-check-lock.sh \"$TOOL_INPUT\""
|
||||
}
|
||||
],
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hook": "bash /Users/shunsukehayashi/dev/platform/miyabi-cli-standalone/scripts/hook-post-bash.sh \"$TOOL_INPUT\""
|
||||
"hook": "bash scripts/hook-post-bash.sh \"$TOOL_INPUT\""
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
15
AGENTS.md
15
AGENTS.md
|
|
@ -1,26 +1,23 @@
|
|||
# Repository Guidelines
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- Rust workspace lives under `crates/`: `miyabi-cli` (CLI entry), `miyabi-tui` (UI), and `miyabi-core` (shared logic, config, sessions). Shared workspace config in `Cargo.toml`.
|
||||
- TypeScript side sits in `src/` with `index.ts` as the entry point; Vitest specs live in `tests/`. Build artifacts output to `dist/`.
|
||||
- Supporting assets: `.claude/` agent configs, `docs/` for design notes, `.miyabi/` for local runtime data, and `.github/` for CI workflows.
|
||||
- Rust workspace lives under `crates/`: `miyabi-cli` (CLI entry), `miyabi-tui` (UI), and `miyabi-core` (shared logic, config, sessions, Polaris/DTP: `gate`, `store`, `lock`, `protocol`). Shared workspace config in `Cargo.toml`.
|
||||
- ルートに `package.json` はない(過去テンプレート由来の記述は削除済み)。フロント系ツールが別ディレクトリにあれば、その README に従う。
|
||||
- Supporting assets: `.claude/` agent configs, `docs/` for design notes, `project_memory/` for Polaris タスク台帳(`tasks.json`)、`.github/` for CI workflows。
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- Rust: `cargo build --workspace --release` (release build), `cargo test --all` (unit/integration), `cargo clippy --all-targets -- -D warnings` (lint), `cargo fmt --all` (format).
|
||||
- TypeScript: `npm run dev` (tsx watch), `npm run build` (tsc emit to `dist/`), `npm run typecheck` (tsc no emit), `npm run lint` (eslint per `.eslintrc.json`), `npm test` (vitest).
|
||||
- Install toolchains: `npm install` for JS deps; Rust uses stable 1.75+ per workspace metadata.
|
||||
- Toolchain: `rust-version` は `Cargo.toml` の workspace メタデータに準拠(現状 1.75+)。
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Rust: run `cargo fmt` before commits; clippy must be clean with `-D warnings`. Modules/files use `snake_case`, types/traits `PascalCase`, constants `SCREAMING_SNAKE_CASE`. Prefer `anyhow::Result` and `thiserror` for errors; avoid `unwrap` in non-test code.
|
||||
- TypeScript: strict mode on; avoid `any` (warned), silence unused via `_` prefix. Follow ESLint rules and keep imports sorted logically. Prefer async/await over callbacks.
|
||||
|
||||
## Testing Guidelines
|
||||
- Rust tests colocated in each crate (`mod tests` blocks or `tests/` directories); write integration tests when touching cross-crate behavior. Run `cargo test --all` before PRs.
|
||||
- TypeScript tests follow `*.test.ts` in `tests/`; structure with `describe/it` and keep fast. Aim to cover error paths and CLI flags.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Use Conventional Commits (`feat:`, `fix:`, `refactor:`, `docs:`, `test:`, `chore:`). Keep subject under ~72 chars; include scope when helpful (`feat(tui): add diff renderer`).
|
||||
- PRs should describe the change, linked issue, and test evidence (`cargo test --all`, `npm test`, etc.). Add screenshots or terminal recordings for TUI changes when relevant. Keep diffs focused; separate refactors from feature work.
|
||||
- PRs should describe the change, linked issue, and test evidence (`cargo test --all`). Add screenshots or terminal recordings for TUI changes when relevant. Keep diffs focused; separate refactors from feature work.
|
||||
|
||||
## Environment & Configuration
|
||||
- Environment defaults from `.env.example`; typical variables: `GITHUB_TOKEN`, `ANTHROPIC_API_KEY`, `REPOSITORY`, optional `RUST_LOG`/`RUST_BACKTRACE` for debugging.
|
||||
|
|
@ -29,7 +26,7 @@
|
|||
<!-- gitnexus:start -->
|
||||
# GitNexus — Code Intelligence
|
||||
|
||||
This project is indexed by GitNexus as **miyabi-cli-standalone** (5691 symbols, 12441 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
This project is indexed by GitNexus as **miyabi-cli-standalone** (5866 symbols, 12893 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
|
||||
|
||||
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
|
||||
|
||||
|
|
|
|||
83
crates/miyabi-core/src/protocol.rs
generated
83
crates/miyabi-core/src/protocol.rs
generated
|
|
@ -821,12 +821,16 @@ impl DeterministicExecutionProtocol {
|
|||
}
|
||||
|
||||
if let Some(vault_path) = obsidian_vault_path() {
|
||||
let mut attached_notes: std::collections::HashSet<PathBuf> =
|
||||
std::collections::HashSet::new();
|
||||
for note in find_obsidian_notes(&vault_path, &task.title, 3)? {
|
||||
if remaining_tokens == 0 {
|
||||
break;
|
||||
}
|
||||
let content = read_file_snippet(¬e, FILE_SNIPPET_LINE_LIMIT)
|
||||
.map_err(ProtocolError::from)?;
|
||||
// Expand wikilinks from this note
|
||||
let linked = extract_wikilinks(&content);
|
||||
push_attachment(
|
||||
&mut attachments,
|
||||
&mut remaining_tokens,
|
||||
|
|
@ -834,6 +838,29 @@ impl DeterministicExecutionProtocol {
|
|||
¬e.display().to_string(),
|
||||
&content,
|
||||
);
|
||||
attached_notes.insert(note);
|
||||
|
||||
for link_name in linked {
|
||||
if remaining_tokens == 0 {
|
||||
break;
|
||||
}
|
||||
if let Some(linked_path) = resolve_wikilink(&vault_path, &link_name) {
|
||||
if attached_notes.contains(&linked_path) {
|
||||
continue;
|
||||
}
|
||||
let linked_content =
|
||||
read_file_snippet(&linked_path, FILE_SNIPPET_LINE_LIMIT)
|
||||
.map_err(ProtocolError::from)?;
|
||||
push_attachment(
|
||||
&mut attachments,
|
||||
&mut remaining_tokens,
|
||||
"obsidian_wikilink",
|
||||
&linked_path.display().to_string(),
|
||||
&linked_content,
|
||||
);
|
||||
attached_notes.insert(linked_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1469,6 +1496,62 @@ fn collect_obsidian_matches(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_wikilinks(content: &str) -> Vec<String> {
|
||||
let mut links = Vec::new();
|
||||
let mut chars = content.chars().peekable();
|
||||
while let Some(ch) = chars.next() {
|
||||
if ch == '[' && chars.peek() == Some(&'[') {
|
||||
chars.next(); // consume second '['
|
||||
let mut name = String::new();
|
||||
for inner in chars.by_ref() {
|
||||
if inner == ']' {
|
||||
break;
|
||||
}
|
||||
if inner == '|' {
|
||||
// [[Note|Display]] — take the note part before |
|
||||
break;
|
||||
}
|
||||
name.push(inner);
|
||||
}
|
||||
let trimmed = name.trim().to_string();
|
||||
if !trimmed.is_empty() {
|
||||
links.push(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
links
|
||||
}
|
||||
|
||||
fn resolve_wikilink(vault_path: &Path, link_name: &str) -> Option<PathBuf> {
|
||||
// Try exact match first
|
||||
let exact = vault_path.join(format!("{link_name}.md"));
|
||||
if exact.exists() {
|
||||
return Some(exact);
|
||||
}
|
||||
// Search recursively for the note
|
||||
find_note_by_name(vault_path, link_name)
|
||||
}
|
||||
|
||||
fn find_note_by_name(directory: &Path, name: &str) -> Option<PathBuf> {
|
||||
let target = format!("{}.md", name.to_ascii_lowercase());
|
||||
for entry in fs::read_dir(directory).ok()? {
|
||||
let entry = entry.ok()?;
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
if let Some(found) = find_note_by_name(&path, name) {
|
||||
return Some(found);
|
||||
}
|
||||
} else if path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.is_some_and(|n| n.to_ascii_lowercase() == target)
|
||||
{
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn title_keywords(title: &str) -> Vec<String> {
|
||||
let normalized: String = title
|
||||
.chars()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
# Round 2 Code Review: Deterministic Task Protocol Rust Codebase
|
||||
|
||||
> **アーカイブ・スコープ注意(2026-04-10)**
|
||||
> 本文は **当時の作業ディレクトリ**(`openclaw-workspace`)向けの調査記録であり、**本リポジトリ `miyabi-cli-standalone` の `crates/miyabi-core` / `crates/miyabi-cli` に対するコードレビュー結果ではありません**。ここに書かれた「Rust が無い」等の記述は **そのワークスペース限定**です。現行実装・レビューは `docs/dtp/PLAYBOOK-v2.md` および `crates/**/*.rs` を正としてください。
|
||||
|
||||
対象依頼:
|
||||
- Read all Rust source files in `src/`: `lib.rs`, `types.rs`, `state.rs`, `dag.rs`, `lock.rs`, `store.rs`, `protocol.rs`, `gate.rs`, `main.rs`
|
||||
- Check:
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@
|
|||
# ロックされていないファイルへの書き込みをブロックする。
|
||||
|
||||
TOOL_INPUT="$1"
|
||||
MIYABI_BIN="/Users/shunsukehayashi/dev/platform/miyabi-cli-standalone/target/release/miyabi"
|
||||
STORE="/Users/shunsukehayashi/dev/platform/miyabi-cli-standalone/project_memory/tasks.json"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
# 上書き可: 別ビルドパスやインストール済み miyabi を指す
|
||||
MIYABI_BIN="${MIYABI_BIN:-$REPO_ROOT/target/release/miyabi}"
|
||||
STORE="${POLARIS_TASK_STORE:-$REPO_ROOT/project_memory/tasks.json}"
|
||||
|
||||
# miyabi バイナリがなければスキップ
|
||||
if [ ! -x "$MIYABI_BIN" ]; then
|
||||
|
|
@ -33,7 +36,6 @@ if [ -z "$TARGET_FILE" ]; then
|
|||
fi
|
||||
|
||||
# リポルートからの相対パスに変換
|
||||
REPO_ROOT="/Users/shunsukehayashi/dev/platform/miyabi-cli-standalone"
|
||||
REL_PATH="${TARGET_FILE#$REPO_ROOT/}"
|
||||
|
||||
# ロック一覧を取得
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@
|
|||
# ここでは軽い状態表示のみ。
|
||||
|
||||
TOOL_INPUT="$1"
|
||||
MIYABI_BIN="/Users/shunsukehayashi/dev/platform/miyabi-cli-standalone/target/release/miyabi"
|
||||
STORE="/Users/shunsukehayashi/dev/platform/miyabi-cli-standalone/project_memory/tasks.json"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MIYABI_BIN="${MIYABI_BIN:-$REPO_ROOT/target/release/miyabi}"
|
||||
STORE="${POLARIS_TASK_STORE:-$REPO_ROOT/project_memory/tasks.json}"
|
||||
|
||||
# miyabi バイナリがなければスキップ
|
||||
if [ ! -x "$MIYABI_BIN" ]; then
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue