diff --git a/strix/interface/utils.py b/strix/interface/utils.py index 12a013b..c267b77 100644 --- a/strix/interface/utils.py +++ b/strix/interface/utils.py @@ -738,7 +738,7 @@ def _parse_name_status_z(raw_output: bytes) -> list[DiffEntry]: # Backward-compat fallback if output is tab-delimited unexpectedly. status_fallback, has_tab, first_path = token.partition("\t") if not has_tab: - break + break fallback_code = status_fallback[:1] fallback_similarity: int | None = None if len(status_fallback) > 1 and status_fallback[1:].isdigit(): diff --git a/strix/llm/llm.py b/strix/llm/llm.py index 7c56051..4f62495 100644 --- a/strix/llm/llm.py +++ b/strix/llm/llm.py @@ -113,7 +113,7 @@ class LLM: ordered_skills.append(f"scan_modes/{self.config.scan_mode}") if self.config.is_whitebox: ordered_skills.append("coordination/source_aware_whitebox") - ordered_skills.append("source_aware_sast") + ordered_skills.append("custom/source_aware_sast") deduped: list[str] = [] seen: set[str] = set() diff --git a/strix/skills/custom/source_aware_sast.md b/strix/skills/custom/source_aware_sast.md index 9fcaf3b..0104605 100644 --- a/strix/skills/custom/source_aware_sast.md +++ b/strix/skills/custom/source_aware_sast.md @@ -36,10 +36,13 @@ mkdir -p "$ART" semgrep scan --config p/default --config p/golang --config p/secrets \ --metrics=off --json --output "$ART/semgrep.json" . -sg scan --json . > "$ART/ast-grep.json" +# Ruleless AST pass (works without sgconfig.yml/rules project setup) +sg run --pattern '$F($$$ARGS)' --json=stream . > "$ART/ast-grep.json" 2> "$ART/ast-grep.log" || true gitleaks detect --source . --report-format json --report-path "$ART/gitleaks.json" || true trufflehog filesystem --no-update --json --no-verification . > "$ART/trufflehog.json" || true -trivy fs --format json --output "$ART/trivy-fs.json" . +# Keep trivy focused on vuln/misconfig (secrets already covered above) and increase timeout for large repos +trivy fs --scanners vuln,misconfig --timeout 30m --offline-scan \ + --format json --output "$ART/trivy-fs.json" . || true ``` If one tool is skipped or fails, record that in the shared wiki note along with the reason. @@ -64,7 +67,8 @@ If diff scope is active, restrict to changed files first, then expand only when Use `sg` for structure-aware code hunting: ```bash -sg scan --json . > /workspace/.strix-source-aware/ast-grep.json +# Ruleless one-off structural pass (no sgconfig.yml required) +sg run --pattern '$F($$$ARGS)' --json=stream . > /workspace/.strix-source-aware/ast-grep.json 2> /workspace/.strix-source-aware/ast-grep.log || true ``` Target high-value patterns such as: @@ -95,7 +99,8 @@ trufflehog filesystem --json . > /workspace/.strix-source-aware/trufflehog.json Run repository-wide dependency and config checks: ```bash -trivy fs --format json --output /workspace/.strix-source-aware/trivy-fs.json . +trivy fs --scanners vuln,misconfig --timeout 30m --offline-scan \ + --format json --output /workspace/.strix-source-aware/trivy-fs.json . || true ``` ## Converting Static Signals Into Exploits diff --git a/strix/tools/agents_graph/agents_graph_actions.py b/strix/tools/agents_graph/agents_graph_actions.py index cfe6ed0..76313d7 100644 --- a/strix/tools/agents_graph/agents_graph_actions.py +++ b/strix/tools/agents_graph/agents_graph_actions.py @@ -123,7 +123,7 @@ def _append_wiki_update_on_finish( return try: - from strix.tools.notes.notes_actions import update_note + from strix.tools.notes.notes_actions import append_note_content note = _load_primary_wiki_note(agent_state) if not note: @@ -133,7 +133,6 @@ def _append_wiki_update_on_finish( if not isinstance(note_id, str) or not note_id: return - existing_content = str(note.get("content") or "") timestamp = datetime.now(UTC).isoformat() summary = " ".join(str(result_summary).split()) if len(summary) > 1200: @@ -151,8 +150,7 @@ def _append_wiki_update_on_finish( "Recommendations:\n" f"{recommendation_lines}\n" ) - updated_content = f"{existing_content.rstrip()}{delta}" - update_note(note_id=note_id, content=updated_content) + append_note_content(note_id=note_id, delta=delta) except Exception: # Best-effort update; never block agent completion on note persistence. return diff --git a/strix/tools/notes/notes_actions.py b/strix/tools/notes/notes_actions.py index 2622cc2..bcbdf58 100644 --- a/strix/tools/notes/notes_actions.py +++ b/strix/tools/notes/notes_actions.py @@ -364,6 +364,26 @@ def get_note(note_id: str) -> dict[str, Any]: return {"success": True, "note": note_with_id} +def append_note_content(note_id: str, delta: str) -> dict[str, Any]: + with _notes_lock: + try: + _ensure_notes_loaded() + + if note_id not in _notes_storage: + return {"success": False, "error": f"Note with ID '{note_id}' not found"} + + if not isinstance(delta, str): + return {"success": False, "error": "Delta must be a string"} + + note = _notes_storage[note_id] + existing_content = str(note.get("content") or "") + updated_content = f"{existing_content.rstrip()}{delta}" + return update_note(note_id=note_id, content=updated_content) + + except (ValueError, TypeError) as e: + return {"success": False, "error": f"Failed to append note content: {e}"} + + @register_tool(sandbox_execution=False) def update_note( note_id: str, diff --git a/tests/tools/test_agents_graph_whitebox.py b/tests/tools/test_agents_graph_whitebox.py index a8205cb..de89332 100644 --- a/tests/tools/test_agents_graph_whitebox.py +++ b/tests/tools/test_agents_graph_whitebox.py @@ -164,14 +164,14 @@ def test_agent_finish_appends_wiki_update_for_whitebox(monkeypatch) -> None: }, } - def fake_update_note(note_id: str, content: str): + def fake_append_note_content(note_id: str, delta: str): captured["note_id"] = note_id - captured["content"] = content + captured["delta"] = delta return {"success": True, "note_id": note_id} monkeypatch.setattr("strix.tools.notes.notes_actions.list_notes", fake_list_notes) monkeypatch.setattr("strix.tools.notes.notes_actions.get_note", fake_get_note) - monkeypatch.setattr("strix.tools.notes.notes_actions.update_note", fake_update_note) + monkeypatch.setattr("strix.tools.notes.notes_actions.append_note_content", fake_append_note_content) state = SimpleNamespace(agent_id=child_id, parent_id=parent_id) result = agents_graph_actions.agent_finish( @@ -185,8 +185,8 @@ def test_agent_finish_appends_wiki_update_for_whitebox(monkeypatch) -> None: assert result["agent_completed"] is True assert captured_get["note_id"] == "wiki-note-1" assert captured["note_id"] == "wiki-note-1" - assert "Agent Update: Child" in captured["content"] - assert "AST pass completed" in captured["content"] + assert "Agent Update: Child" in captured["delta"] + assert "AST pass completed" in captured["delta"] def test_run_agent_in_thread_injects_shared_wiki_context_in_whitebox(monkeypatch) -> None: diff --git a/tests/tools/test_notes_wiki.py b/tests/tools/test_notes_wiki.py index 381c064..e27ce58 100644 --- a/tests/tools/test_notes_wiki.py +++ b/tests/tools/test_notes_wiki.py @@ -137,3 +137,36 @@ def test_get_note_returns_full_note(tmp_path: Path, monkeypatch) -> None: finally: _reset_notes_state() set_global_tracer(previous_tracer) # type: ignore[arg-type] + + +def test_append_note_content_appends_delta(tmp_path: Path, monkeypatch) -> None: + monkeypatch.chdir(tmp_path) + _reset_notes_state() + + previous_tracer = get_global_tracer() + tracer = Tracer("append-note-run") + set_global_tracer(tracer) + + try: + created = notes_actions.create_note( + title="Repo wiki", + content="base", + category="wiki", + tags=["repo:demo"], + ) + assert created["success"] is True + note_id = created["note_id"] + assert isinstance(note_id, str) + + appended = notes_actions.append_note_content( + note_id=note_id, + delta="\n\n## Agent Update: worker\nSummary: done", + ) + assert appended["success"] is True + + loaded = notes_actions.get_note(note_id=note_id) + assert loaded["success"] is True + assert loaded["note"]["content"] == "base\n\n## Agent Update: worker\nSummary: done" + finally: + _reset_notes_state() + set_global_tracer(previous_tracer) # type: ignore[arg-type]