Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@ run = "actionlint"
[tasks.check]
description = "Run the pull-request quality gate"
depends = ["ci", "docs", "fmt", "lint", "test"]

[tasks.demo]
description = "Run the offline Milestone 1 import-to-digest demonstration"
run = "scripts/demo-milestone-1.sh"
48 changes: 48 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"crates/autophagy-core",
"crates/autophagy-events",
"crates/autophagy-patterns",
"crates/autophagy-redaction",
"crates/autophagy-store",
]

Expand All @@ -21,6 +22,8 @@ repository = "https://github.com/karnstack/autophagy"
[workspace.dependencies]
clap = { version = "4.6.2", features = ["derive", "env"] }
directories = "6.0"
globset = "0.4"
regex = "1.12"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sha2 = "0.11"
Expand Down
36 changes: 31 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,10 @@ change because of what happened?”

## Status

Autophagy is in foundation development. Agent Event Protocol (AEP) v0.1, the
transactional local SQLite event store, generic JSONL CLI vertical slice, and
incremental Claude Code and Codex history adapters are implemented. No daemon
or background capture ships yet. Deterministic repeated-failure and explicit
user-correction detectors now emit versioned, evidence-linked packets.
The local-only Milestone 1 engine is implemented: AEP v0.1, transactional
SQLite storage, generic JSONL plus Claude Code and Codex adapters, deterministic
evidence-linked findings, ingestion redaction, retention, export, and deletion.
No daemon, mutation generation, replay, or background capture ships yet.

## Principles

Expand All @@ -35,12 +34,14 @@ crates/autophagy-cli/ User-facing import, sessions, and search commands
crates/autophagy-core/ Reusable streaming import application services
crates/autophagy-events/ AEP Rust types, parsing, and validation
crates/autophagy-patterns/ Model-free recurrence detectors and evidence packets
crates/autophagy-redaction/ Secret rules and project/artifact path policy
crates/autophagy-store/ SQLite migrations, idempotency, FTS, and deletion
docs/architecture/ Planned component and storage boundaries
docs/blueprint/ Complete normalized product and implementation brief
docs/decisions/ Architecture decision records
docs/roadmap/ Small pull-request delivery sequence
docs/specs/aep/0.1/ Versioned AEP JSON Schema and examples
docs/specs/evidence/0.1/ Versioned deterministic finding contract
```

The intended repository structure is documented in
Expand Down Expand Up @@ -94,6 +95,31 @@ The [deterministic findings guide](docs/guides/deterministic-findings.md)
documents recurrence thresholds, signature normalization, counterexamples, and
the versioned Evidence Packet contract.

## Run the offline milestone demo

```sh
mise run demo
```

The demo imports anonymized evidence, emits two deterministic patterns with
exact evidence IDs, produces a digest that confirms no model or network was
used, and previews retention deletion. Its temporary database is removed on
exit.

Useful privacy and lifecycle commands:

```sh
autophagy import history.jsonl --exclude-path '**/private/**'
autophagy export > autophagy-export.jsonl
autophagy prune --older-than-days 30 --dry-run
autophagy prune --older-than-days 30
autophagy delete session ses_example
autophagy delete all --confirm delete-all
```

See the [privacy and lifecycle guide](docs/guides/privacy-and-lifecycle.md) and
[threat model](docs/security/threat-model.md) for guarantees and limitations.

## Try the contract

Install [mise](https://mise.jdx.dev/), then run:
Expand Down
4 changes: 4 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ repository. Include a minimal, redacted reproduction and the affected version.
- Generated mutations are inspectable, permission-scoped, and reversible.
- Generated scripts never become executable without explicit approval.

The implemented Milestone 1 trust boundaries, residual risks, deletion
semantics, and disclosure guidance are documented in the
[threat model](docs/security/threat-model.md).

The project is pre-alpha and does not yet make production security guarantees.
1 change: 1 addition & 0 deletions adapters/claude-code/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ publish = false

[dependencies]
autophagy-events = { path = "../../crates/autophagy-events" }
autophagy-redaction = { path = "../../crates/autophagy-redaction" }
autophagy-store = { path = "../../crates/autophagy-store" }
directories.workspace = true
serde.workspace = true
Expand Down
33 changes: 30 additions & 3 deletions adapters/claude-code/src/importer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use std::{
};

use autophagy_events::Event;
use autophagy_redaction::{PrivacyError, PrivacyPolicy};
use autophagy_store::{
EventStore, InsertOutcome, SearchProjection, SourceCursor, SourceIdentity, StoreError,
};
Expand All @@ -31,6 +32,8 @@ pub struct ClaudeImportOptions {
pub display_name: Option<String>,
/// Exact working directories to include; empty includes all.
pub projects: Vec<String>,
/// Glob patterns that exclude matching project or artifact paths.
pub exclude_paths: Vec<String>,
/// Include nested `agent-*.jsonl` transcripts.
pub include_subagents: bool,
/// Persist prompt, assistant, and result text in namespaced metadata.
Expand All @@ -54,6 +57,7 @@ impl ClaudeImportOptions {
instance_key: instance_key.into(),
display_name: None,
projects: Vec::new(),
exclude_paths: Vec::new(),
include_subagents: false,
include_content: false,
index_tool_input: false,
Expand Down Expand Up @@ -98,6 +102,10 @@ pub struct ClaudeImportSummary {
pub conflicts: u64,
/// Events excluded by project selection.
pub skipped: u64,
/// Events excluded by path privacy policy.
pub privacy_skipped: u64,
/// String fields changed by default secret redaction.
pub redacted_fields: u64,
/// Structurally valid but unsupported Claude records.
pub unsupported: u64,
/// Invalid records or normalization failures.
Expand Down Expand Up @@ -135,6 +143,7 @@ pub fn import_claude_code(
options: &ClaudeImportOptions,
) -> Result<ClaudeImportSummary, ClaudeImportError> {
validate_options(options)?;
let privacy = PrivacyPolicy::new(&options.exclude_paths)?;
if !options.dry_run && store.is_none() {
return Err(ClaudeImportError::MissingStore);
}
Expand All @@ -157,6 +166,8 @@ pub fn import_claude_code(
duplicates: 0,
conflicts: 0,
skipped: 0,
privacy_skipped: 0,
redacted_fields: 0,
unsupported: 0,
rejected: 0,
cursor_resets: 0,
Expand All @@ -166,7 +177,7 @@ pub fn import_claude_code(
diagnostics_suppressed: 0,
dry_run: options.dry_run,
};
let scope = project_scope(&options.projects);
let scope = import_scope(&options.projects, &options.exclude_paths);

for discovered in &discovery.files {
summary.files_read += 1;
Expand Down Expand Up @@ -263,6 +274,12 @@ pub fn import_claude_code(
summary.skipped += 1;
continue;
}
let outcome = privacy.apply(&event);
let Some(event) = outcome.event else {
summary.privacy_skipped += 1;
continue;
};
summary.redacted_fields += outcome.redacted_fields;
if options.dry_run {
continue;
}
Expand Down Expand Up @@ -318,10 +335,17 @@ fn validate_options(options: &ClaudeImportOptions) -> Result<(), ClaudeImportErr
Ok(())
}

fn project_scope(projects: &[String]) -> String {
fn import_scope(projects: &[String], exclude_paths: &[String]) -> String {
let mut selected = projects.to_vec();
selected.sort();
let digest = Sha256::digest(selected.join("\0").as_bytes());
let mut excluded = exclude_paths.to_vec();
excluded.sort();
let scope = format!(
"privacy/v1\0projects\0{}\0exclusions\0{}",
selected.join("\0"),
excluded.join("\0")
);
let digest = Sha256::digest(scope.as_bytes());
let mut encoded = String::with_capacity(digest.len() * 2);
for byte in digest {
write!(&mut encoded, "{byte:02x}").expect("writing to String cannot fail");
Expand Down Expand Up @@ -407,6 +431,9 @@ pub enum ClaudeImportError {
/// Database operation failed.
#[error(transparent)]
Store(#[from] StoreError),
/// Privacy policy could not be compiled.
#[error(transparent)]
Privacy(#[from] PrivacyError),
/// Cursor JSON could not be encoded.
#[error("could not serialize Claude Code cursor state: {0}")]
Json(#[from] serde_json::Error),
Expand Down
18 changes: 18 additions & 0 deletions adapters/claude-code/tests/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,24 @@ fn orphaned_tool_results_are_skipped_without_guessing() {
assert_eq!(summary.inserted, 1);
}

#[test]
fn changing_path_exclusions_uses_an_independent_cursor_scope() {
let directory = tempfile::tempdir().expect("temp directory");
copy_tree(&fixture_root(), directory.path());
let mut store = EventStore::open_in_memory().expect("store");
let mut options =
ClaudeImportOptions::new(directory.path().to_path_buf(), "fixture:policy-scope");
options.exclude_paths = vec!["/workspace/**".to_owned()];
let excluded = import_claude_code(Some(&mut store), &options).expect("excluded import");
assert_eq!(excluded.inserted, 0);
assert_eq!(excluded.privacy_skipped, 8);

options.exclude_paths.clear();
let included = import_claude_code(Some(&mut store), &options).expect("included import");
assert_eq!(included.records_seen, 7);
assert_eq!(included.inserted, 8);
}

fn append(path: &Path, value: &str) {
use std::io::Write;
let mut file = fs::OpenOptions::new()
Expand Down
1 change: 1 addition & 0 deletions adapters/codex/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ publish = false

[dependencies]
autophagy-events = { path = "../../crates/autophagy-events" }
autophagy-redaction = { path = "../../crates/autophagy-redaction" }
autophagy-store = { path = "../../crates/autophagy-store" }
directories.workspace = true
serde.workspace = true
Expand Down
Loading