diff --git a/README.md b/README.md
index fafe74d..b8c36a8 100755
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
๐ง NeuroSploit v3.6.2
+๐ง NeuroSploit v3.6.3
@@ -12,7 +12,7 @@
-
+
@@ -28,14 +28,15 @@
>
> ๐ **New here? Read the [full Tutorial & User Guide โ](TUTORIAL.md)** โ every mode, flag, config and example explained.
-> ๐ **New in v3.6.2 โ live Codex streaming + full activity feed:**
-> Codex runs now **stream tool-by-tool** (`codex exec --json`) exactly like
-> Claude Code โ every command, file edit, MCP call and token count appears live
-> instead of a silent black box, so a long intense recon never looks frozen. The
-> **`/logs`** feed and **`/status`** sign-of-line now capture the *actual
-> commands each agent runs* (subfinder, httpx, nmap, curlโฆ), not just pipeline
-> phases. *(v3.6.1 added GPT-5.6 sol/terra/luna, the Codex exit-1 fix, `/logs`
-> and richer `/status`; v3.6.0 added AI-agent/LLM/MCP/Skills/n8n testing +
+> ๐ **New in v3.6.3 โ resumable interrupted runs + crash-proof browsing:**
+> If a run is interrupted (terminal closed, Ctrl-C, crash), **`/continue` now
+> relaunches it** on the same target and **carries the recovered findings
+> forward** โ the offer appears right at launch. And opening **`/results`**,
+> **`/finding`** or **`/report`** while a run streams no longer corrupts the
+> terminal: live output is paused for the picker (still captured in `/logs`) and
+> restored on exit. *(v3.6.2 added live Codex tool-by-tool streaming + the full
+> activity feed in `/logs`/`/status`; v3.6.1 added GPT-5.6 sol/terra/luna and
+> the Codex exit-1 fix; v3.6.0 added AI-agent/LLM/MCP/Skills/n8n testing +
> onboarding wizard + Cloud scope.)*
> *(v3.5.4 added robust attack chaining + false-positive reduction; v3.5.3
> GitHub/GitLab/Jira **[integrations](TUTORIAL-INTEGRATION.md)**; v3.5.2 the DEPTH
diff --git a/RELEASE.md b/RELEASE.md
index 635aff8..b0b9d85 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,7 +1,7 @@
-# NeuroSploit v3.6.2 โ Release Notes
+# NeuroSploit v3.6.3 โ Release Notes
**Release Date:** July 2026
-**Codename:** Live Codex
+**Codename:** Resume & Recover
**License:** MIT
**Credits:** Joas A Santos & Red Team Leaders
@@ -9,6 +9,26 @@
## Highlights
+- **Interrupted runs are resumable.** When a run is cut off (terminal closed,
+ Ctrl-C, crash, SSH drop), its findings were already checkpointed live and
+ recovered as a run on the next launch. Now `/continue` (or `/resume`) also
+ **relaunches the engagement** on the same target and **carries those findings
+ forward** โ steering agents to widen coverage and chain from what was already
+ found instead of re-reporting it. The offer is shown at launch right under the
+ recovery line. A fresh `/run` supersedes the pending resume.
+- **Browsing no longer kills a live run.** Opening `/results`, `/finding` or
+ `/report` while a run streams used to let the background printer and the
+ full-screen picker fight over the terminal โ pressing Ctrl-C to escape could
+ take the whole process down. Live output is now paused while any picker is
+ open (still captured in `/logs`) and restored when you exit, so browsing
+ findings mid-run is safe.
+- Findings merge (dedup by title + endpoint) across the interrupted and
+ continued runs, and the merged report is rewritten to include everything.
+
+---
+
+## Previously in v3.6.2
+
- **Codex now streams live, tool-by-tool.** `codex exec` is driven with `--json`
and its JSONL event stream is parsed into the same categorized activity feed
as Claude Code: every shell command it runs (`exec:`), file edit (`edit:`),
diff --git a/TUTORIAL.md b/TUTORIAL.md
index 537b482..222c87e 100644
--- a/TUTORIAL.md
+++ b/TUTORIAL.md
@@ -1,4 +1,4 @@
-# NeuroSploit โ Tutorial & User Guide (v3.6.2)
+# NeuroSploit โ Tutorial & User Guide (v3.6.3)
A complete, hands-on guide to installing, configuring and running NeuroSploit โ
the autonomous, multi-model penetration-testing harness.
@@ -98,7 +98,7 @@ Agents **degrade gracefully**: if `rustscan` is absent they use `nmap`; if neith
### Verify
```bash
-neurosploit --version # neurosploit 3.6.2
+neurosploit --version # neurosploit 3.6.3
neurosploit agents # {"vulns":196,...,"chains":12,"total":417}
neurosploit models # all providers & models
```
diff --git a/neurosploit-rs/Cargo.lock b/neurosploit-rs/Cargo.lock
index a5b13b5..2510894 100644
--- a/neurosploit-rs/Cargo.lock
+++ b/neurosploit-rs/Cargo.lock
@@ -871,7 +871,7 @@ dependencies = [
[[package]]
name = "neurosploit"
-version = "3.6.2"
+version = "3.6.3"
dependencies = [
"anyhow",
"clap",
@@ -888,7 +888,7 @@ dependencies = [
[[package]]
name = "neurosploit-harness"
-version = "3.6.2"
+version = "3.6.3"
dependencies = [
"anyhow",
"futures",
diff --git a/neurosploit-rs/Cargo.toml b/neurosploit-rs/Cargo.toml
index f583193..2ef74dc 100644
--- a/neurosploit-rs/Cargo.toml
+++ b/neurosploit-rs/Cargo.toml
@@ -3,7 +3,7 @@ members = ["crates/harness", "app"]
resolver = "2"
[workspace.package]
-version = "3.6.2"
+version = "3.6.3"
edition = "2021"
license = "MIT"
repository = "https://github.com/JoasASantos/NeuroSploit"
diff --git a/neurosploit-rs/app/src/main.rs b/neurosploit-rs/app/src/main.rs
index 40fc0bc..f21e6e1 100644
--- a/neurosploit-rs/app/src/main.rs
+++ b/neurosploit-rs/app/src/main.rs
@@ -1,4 +1,4 @@
-//! NeuroSploit v3.6.2 โ interactive harness + CLI (`run` / `whitebox` / `agents` / `models`).
+//! NeuroSploit v3.6.3 โ interactive harness + CLI (`run` / `whitebox` / `agents` / `models`).
mod repl;
mod tui;
@@ -11,8 +11,8 @@ use std::path::{Path, PathBuf};
#[command(
name = "neurosploit",
version,
- about = "NeuroSploit v3.6.2 โ multi-model autonomous pentest harness",
- long_about = "NeuroSploit v3.6.2 โ a Rust multi-model harness that drives a pool of LLMs \
+ about = "NeuroSploit v3.6.3 โ multi-model autonomous pentest harness",
+ long_about = "NeuroSploit v3.6.3 โ a Rust multi-model harness that drives a pool of LLMs \
(API key or local subscription: Claude/Codex/Gemini/Grok) to autonomously test a target. \
After recon it INTELLIGENTLY selects only the agents matching the discovered surface, runs \
them in parallel, then validates every finding by cross-model voting before reporting.\n\n\
@@ -721,7 +721,7 @@ pub(crate) fn spawn_engagement(base: &Path, mut cfg: RunConfig, mcp: bool, mode:
println!(" โ ua : {ua}");
write_status(&workdir, "running", &format!("\"target\":{:?}", cfg.target));
- println!(" โโ NeuroSploit v3.6.2 ยท by Joas A Santos & Red Team Leaders");
+ println!(" โโ NeuroSploit v3.6.3 ยท by Joas A Santos & Red Team Leaders");
println!(" โ run id : {run_id}");
println!(" โ target : {}", cfg.target);
println!(" โ models : {}", cfg.models.join(", "));
diff --git a/neurosploit-rs/app/src/repl.rs b/neurosploit-rs/app/src/repl.rs
index 6787279..8f346a0 100644
--- a/neurosploit-rs/app/src/repl.rs
+++ b/neurosploit-rs/app/src/repl.rs
@@ -1,4 +1,4 @@
-//! NeuroSploit v3.6.2 โ interactive session (Claude-Code / Codex / Cursor-CLI style).
+//! NeuroSploit v3.6.3 โ interactive session (Claude-Code / Codex / Cursor-CLI style).
//!
//! Launched when `neurosploit` runs with no subcommand. A persistent REPL with
//! real line editing (arrow-key history recall, Ctrl-A/E/K, paste), model
@@ -120,6 +120,10 @@ struct ActiveRun {
resume: Arc,
/// Fallback models to try first, pushed by /continue .
fallback: Arc>>,
+ /// Suppress live background printing while a full-screen picker (dialoguer)
+ /// is open, so the two don't fight over the terminal and corrupt it. The
+ /// stream is still ingested (feed/checkpoint), just not printed meanwhile.
+ quiet: Arc,
}
/// On-disk checkpoint of an in-flight run's findings/commands, written live so a
@@ -353,7 +357,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
let backends = harness::installed_cli_backends();
println!("\x1b[1m");
println!(" โโโโ โโโโโโโโโโโโโโ โโโโโโโโโโ โโโโโโโ");
- println!(" โโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ NeuroSploit v3.6.2");
+ println!(" โโโโโ โโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโ NeuroSploit v3.6.3");
println!(" โโโโโโ โโโโโโโโโ โโโ โโโโโโโโโโโโโโ โโโ interactive harness");
println!(" โโโโโโโโโโโโโโโโ โโโ โโโโโโโโโโโโโโ โโโ by Joas A Santos");
println!(" โโโ โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโ & Red Team Leaders");
@@ -370,6 +374,9 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
if resumed || past > 0 {
println!(" โป resumed project session from {} โ {} past run(s)", proj_dir().display(), past);
}
+ // A recovered interrupted run, carried in memory so `/continue` can relaunch
+ // the engagement on the same target with these findings folded forward.
+ let mut resumable: Option<(String, Vec)> = None;
// Recover an interrupted run (REPL was quit/crashed mid-engagement): its
// live findings were checkpointed to disk โ fold them into /runs so
// /results, /finding and /report still work.
@@ -384,6 +391,8 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
save_runs(base, &h);
println!(" \x1b[1;33mโป recovered interrupted run on {} โ {} finding(s) saved as run #{}\x1b[0m (/results {id} ยท /report {id})",
cp.target, cp.findings.len(), id);
+ println!(" \x1b[36m โณ /continue to keep testing this target โ the {} finding(s) carry forward\x1b[0m", cp.findings.len());
+ resumable = Some((cp.target.clone(), cp.findings.clone()));
}
clear_checkpoint();
}
@@ -402,7 +411,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
if !queue.is_empty() && active.as_ref().map(|a| a.done.load(Ordering::Relaxed)).unwrap_or(true) {
let next = queue.remove(0);
println!("\n \x1b[1;35mโถ next target\x1b[0m ({} left): {next}", queue.len());
- active = start_background(base, &s, &mut reader, history.clone(), Some(&next)).await;
+ active = start_background(base, &s, &mut reader, history.clone(), Some(&next), vec![]).await;
}
println!("{}", context_prompt(&s)); // dim context line above the prompt
let Some(line) = reader.read(PROMPT) else { println!("\n bye."); break };
@@ -610,6 +619,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
if active.as_ref().map(|a| !a.done.load(Ordering::Relaxed)).unwrap_or(false) {
println!(" a run is already active โ /status to check, /stop to halt it.");
} else {
+ resumable = None; // a fresh /run supersedes any recovered interrupted run
save_session(&s);
// Multiple comma-separated targets โ run sequentially (queue the rest).
let targets = session_targets(&s);
@@ -620,7 +630,7 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
if !queue.is_empty() {
println!(" \x1b[1;35mโถ multi-target\x1b[0m: {} URLs โ running sequentially", targets.len());
}
- match start_background(base, &s, &mut reader, history.clone(), first.as_deref()).await {
+ match start_background(base, &s, &mut reader, history.clone(), first.as_deref(), vec![]).await {
Some(a) => { active = Some(a); println!(" \x1b[1;35mโถ running in background\x1b[0m โ keep typing ยท \x1b[36m/status\x1b[0m ยท \x1b[36m/stop\x1b[0m"); }
None => { // no external printer (piped) โ blocking fallback
let mut h = history.lock().unwrap();
@@ -651,20 +661,46 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
}
}
"/continue" | "/resume" => {
- match &active {
- Some(a) if a.paused.load(Ordering::Relaxed) => {
- if !arg.is_empty() {
- let m = ModelRef::parse(arg);
- println!(" \x1b[1;35mโถ resuming with fallback model\x1b[0m {}:{}", m.provider, m.model);
- a.fallback.lock().unwrap().push(m);
- } else {
- println!(" \x1b[1;35mโถ resuming\x1b[0m โ retrying with the current model(s).");
- }
- a.paused.store(false, Ordering::Relaxed);
- a.resume.notify_waiters();
+ let paused = active.as_ref().map(|a| a.paused.load(Ordering::Relaxed)).unwrap_or(false);
+ let working = active.as_ref().map(|a| !a.done.load(Ordering::Relaxed)).unwrap_or(false);
+ if paused {
+ let a = active.as_ref().unwrap();
+ if !arg.is_empty() {
+ let m = ModelRef::parse(arg);
+ println!(" \x1b[1;35mโถ resuming with fallback model\x1b[0m {}:{}", m.provider, m.model);
+ a.fallback.lock().unwrap().push(m);
+ } else {
+ println!(" \x1b[1;35mโถ resuming\x1b[0m โ retrying with the current model(s).");
}
- Some(a) if !a.done.load(Ordering::Relaxed) => println!(" run is not paused โ it's still working. /status to check."),
- _ => println!(" no paused run. (a run pauses automatically if your tokens/quota run out)"),
+ a.paused.store(false, Ordering::Relaxed);
+ a.resume.notify_waiters();
+ } else if working {
+ println!(" run is not paused โ it's still working. /status to check.");
+ } else if let Some((tgt, prior)) = resumable.take() {
+ // Continue an interrupted run: relaunch on the same target, carry
+ // the prior findings forward, and steer agents to extend coverage
+ // rather than re-report what was already found.
+ if s.target.is_none() && s.repo.is_none() { s.target = Some(tgt.clone()); }
+ let titles: Vec = prior.iter().map(|f| format!("[{}] {}", f.severity, f.title)).collect();
+ let carry = format!(
+ "CONTINUE a prior interrupted engagement on this same target. These {} finding(s) are \
+ ALREADY confirmed โ do NOT re-report them; instead widen coverage: chase untested \
+ endpoints/params/methods, try new agent classes, and chain from these where possible: {}",
+ prior.len(), titles.join("; "));
+ s.instructions = Some(match &s.instructions {
+ Some(prev) if !prev.trim().is_empty() => format!("{prev}\n\n{carry}"),
+ _ => carry,
+ });
+ println!(" \x1b[1;35mโถ continuing interrupted run\x1b[0m on {tgt} โ {} prior finding(s) carried forward", prior.len());
+ match start_background(base, &s, &mut reader, history.clone(), None, prior).await {
+ Some(a) => { active = Some(a); println!(" \x1b[1;35mโถ running in background\x1b[0m โ keep typing ยท \x1b[36m/status\x1b[0m ยท \x1b[36m/stop\x1b[0m"); }
+ None => {
+ let mut h = history.lock().unwrap();
+ run(base, &s, &mut h).await; save_runs(base, &h);
+ }
+ }
+ } else {
+ println!(" no paused or interrupted run. (a run pauses on token/quota exhaustion; an interrupted run is offered for /continue at launch)");
}
}
"/runs" | "/history" => list_runs(&history.lock().unwrap()),
@@ -735,7 +771,12 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
}
}
runs.extend(history.lock().unwrap().iter().rev().cloned()); // newest-first
+ // Silence live background output while the full-screen picker is
+ // open (they'd corrupt each other); restore + point to /logs after.
+ let live_now = active.as_ref().map(|a| { a.quiet.store(true, Ordering::Relaxed); !a.done.load(Ordering::Relaxed) }).unwrap_or(false);
browse_results(&runs);
+ if let Some(a) = &active { a.quiet.store(false, Ordering::Relaxed); }
+ if live_now { println!(" \x1b[2m(run still streaming in background โ /logs for what happened while browsing)\x1b[0m"); }
}
}
"/finding" | "/findings" => {
@@ -744,7 +785,9 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
Some(a) if arg.is_empty() && !a.done.load(Ordering::Relaxed) => a.live.lock().unwrap().full.clone(),
_ => { let h = history.lock().unwrap(); pick(&h, arg).map(|r| r.findings.clone()).unwrap_or_default() }
};
+ if let Some(a) = &active { a.quiet.store(true, Ordering::Relaxed); }
finding_detail(&pool);
+ if let Some(a) = &active { a.quiet.store(false, Ordering::Relaxed); }
}
"/expand" | "/full" => {
// Show full untruncated commands from the active run.
@@ -762,7 +805,11 @@ pub async fn repl(base: &Path) -> anyhow::Result<()> {
None => println!(" no active run โ /expand shows full commands while a run streams."),
}
}
- "/report" => open_report(&history.lock().unwrap(), arg),
+ "/report" => {
+ if let Some(a) = &active { a.quiet.store(true, Ordering::Relaxed); }
+ open_report(&history.lock().unwrap(), arg);
+ if let Some(a) = &active { a.quiet.store(false, Ordering::Relaxed); }
+ }
"/status" => {
// Live status if a run is active, else a past run's status.json.
match &active {
@@ -1026,7 +1073,8 @@ async fn run(base: &Path, s: &Session, history: &mut Vec) {
/// external printer while the REPL keeps accepting commands (/status, /stop).
/// Returns None when no external printer is available (piped) โ caller blocks.
async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
- history: Arc>>, target_override: Option<&str>) -> Option {
+ history: Arc>>, target_override: Option<&str>,
+ seed: Vec) -> Option {
// `target_override` runs one specific URL (used by the multi-target queue).
let ov = target_override.map(|t| t.to_string());
// The onboarding scope steers infra/cloud/ai/skills; otherwise web black/white/grey.
@@ -1084,8 +1132,10 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
let fallback = sp.fallback.clone();
let done = Arc::new(AtomicBool::new(false));
let choice = Arc::new(Mutex::new(StopMode::Run));
+ let quiet = Arc::new(AtomicBool::new(false));
let soft_task = soft.clone(); // idle guardrail triggers a soft-stop (validate)
let cancel_task = cancel.clone();
+ let quiet_task = quiet.clone();
let sub_mcp = s.subscription && mcp; // for the "browser/tools never engaged" diagnostic
let (live2, done2, hist2, choice2) = (live.clone(), done.clone(), history, choice.clone());
@@ -1111,7 +1161,12 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
// Exploitation has begun once agents launch / vote โ only then arm the guardrail.
if low.contains("launching agent") || low.starts_with("exploit ") || low.starts_with("test ")
|| low.starts_with("ai ") || low.starts_with("skill ") || low.starts_with("vote") { exploiting = true; }
- if let Some(out) = crate::render_compact(&line) { let _ = printer.print(out); }
+ // Don't print into the terminal while a full-screen picker is
+ // open (it would corrupt the picker); the line is still in the
+ // feed for /logs once the picker closes.
+ if !quiet_task.load(Ordering::Relaxed) {
+ if let Some(out) = crate::render_compact(&line) { let _ = printer.print(out); }
+ }
// Checkpoint on each new finding.
let snap = {
let l = live2.lock().unwrap();
@@ -1161,7 +1216,7 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
}
// Raw โ report from the unvalidated candidates we captured live.
- let (findings, validated_word) = if mode_choice == StopMode::Raw {
+ let (mut findings, validated_word) = if mode_choice == StopMode::Raw {
let raw = live2.lock().unwrap().full.clone();
crate::report_raw(&target, &raw, &workdir);
(raw, "unvalidated")
@@ -1169,6 +1224,13 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
let out = crate::finalize_run(task_out, &workdir);
(out.findings, "validated")
};
+ // Continued run (/continue on an interrupted run): fold the carried-forward
+ // prior findings back in (dedup by title+endpoint) and rewrite the report
+ // so the merged run shows everything found across both sessions.
+ if !seed.is_empty() {
+ findings = merge_findings(seed.clone(), findings);
+ crate::report_raw(&target, &findings, &workdir);
+ }
let id = {
let mut h = hist2.lock().unwrap();
@@ -1184,7 +1246,19 @@ async fn start_background(base: &Path, s: &Session, reader: &mut Reader,
let _ = printer.print(format!("\x1b[36m report: {}\x1b[0m", crate::report_url(&workdir)));
done2.store(true, Ordering::Relaxed);
});
- Some(ActiveRun { live, cancel, soft, done, choice, paused, resume, fallback })
+ Some(ActiveRun { live, cancel, soft, done, choice, paused, resume, fallback, quiet })
+}
+
+/// Merge two finding sets, deduping by (title, endpoint) โ used to carry a prior
+/// interrupted run's findings forward into a continued run without duplicating.
+fn merge_findings(prior: Vec, mut fresh: Vec) -> Vec {
+ use std::collections::HashSet;
+ let key = |f: &Finding| format!("{}|{}", f.title.trim().to_lowercase(), f.endpoint.trim().to_lowercase());
+ let seen: HashSet = fresh.iter().map(key).collect();
+ for p in prior {
+ if !seen.contains(&key(&p)) { fresh.push(p); }
+ }
+ fresh
}
/// Project-local store: `/.neurosploit/` so each project keeps its own
@@ -1577,7 +1651,7 @@ fn help() {
h("/status [n]", "live progress + findings while running (or a past run #)");
h("/logs [n]", "recent activity feed of the running test (recon/tools/findings)");
h("/stop", "stop: [1] validate+report [2] raw report now [3] discard");
- h("/continue", "resume a run paused on token/quota (change /model first to switch)");
+ h("/continue", "resume a paused (token/quota) OR a recovered interrupted run โ carries findings forward");
h("/results [n]", "browse findings (target โ vuln โ detail; Esc = back)");
h("/finding [n]", "pick a finding and see its command + PoC + evidence");
h("/report [n]", "open a run's report (menu if several)");
diff --git a/neurosploit-rs/app/src/tui.rs b/neurosploit-rs/app/src/tui.rs
index 2086288..762fcea 100644
--- a/neurosploit-rs/app/src/tui.rs
+++ b/neurosploit-rs/app/src/tui.rs
@@ -1,4 +1,4 @@
-//! NeuroSploit v3.6.2 โ TUI "Mission Control" mode.
+//! NeuroSploit v3.6.3 โ TUI "Mission Control" mode.
//!
//! Concurrent panels that update live while the engagement runs in the
//! background, with a composer input that stays active during execution:
diff --git a/neurosploit-rs/crates/harness/src/belief.rs b/neurosploit-rs/crates/harness/src/belief.rs
index 38a8c06..4da71c6 100644
--- a/neurosploit-rs/crates/harness/src/belief.rs
+++ b/neurosploit-rs/crates/harness/src/belief.rs
@@ -1,4 +1,4 @@
-//! POMDP belief-state world model (v3.6.2).
+//! POMDP belief-state world model (v3.6.3).
//!
//! The target is only partially observable, so we don't track booleans โ we
//! track a **belief**: a property graph whose nodes (host / service / vuln /
diff --git a/neurosploit-rs/crates/harness/src/grounding.rs b/neurosploit-rs/crates/harness/src/grounding.rs
index c1febba..288e408 100644
--- a/neurosploit-rs/crates/harness/src/grounding.rs
+++ b/neurosploit-rs/crates/harness/src/grounding.rs
@@ -1,4 +1,4 @@
-//! Verification / grounding engine (v3.6.2).
+//! Verification / grounding engine (v3.6.3).
//!
//! Hard rule: **no claim enters the world model without a tool receipt** โ raw
//! tool output, not the LLM's paraphrase. This is the empirical anti-hallucination
diff --git a/neurosploit-rs/crates/harness/src/lib.rs b/neurosploit-rs/crates/harness/src/lib.rs
index e64fbe1..1380af7 100644
--- a/neurosploit-rs/crates/harness/src/lib.rs
+++ b/neurosploit-rs/crates/harness/src/lib.rs
@@ -1,4 +1,4 @@
-//! NeuroSploit v3.6.2 harness โ a robust multi-model runtime for the
+//! NeuroSploit v3.6.3 harness โ a robust multi-model runtime for the
//! markdown-driven autonomous pentest engine.
//!
//! The harness loads the `agents_md/` library, drives a *pool* of LLM models
diff --git a/neurosploit-rs/crates/harness/src/pomdp.rs b/neurosploit-rs/crates/harness/src/pomdp.rs
index a69f2e4..f290b1f 100644
--- a/neurosploit-rs/crates/harness/src/pomdp.rs
+++ b/neurosploit-rs/crates/harness/src/pomdp.rs
@@ -1,4 +1,4 @@
-//! POMDP decision layer (v3.6.2): value-of-information planning + the
+//! POMDP decision layer (v3.6.3): value-of-information planning + the
//! anti-hallucination gate.
//!
//! The choice "scan more vs exploit now" is **not** a heuristic here โ it falls
diff --git a/neurosploit-rs/crates/harness/src/probe.rs b/neurosploit-rs/crates/harness/src/probe.rs
index 2a2d119..ae3d3de 100644
--- a/neurosploit-rs/crates/harness/src/probe.rs
+++ b/neurosploit-rs/crates/harness/src/probe.rs
@@ -1,4 +1,4 @@
-//! Deterministic HTTP request/response analysis (v3.6.2).
+//! Deterministic HTTP request/response analysis (v3.6.3).
//!
//! Before the LLM recon runs, the harness performs a **real** probe of the
//! target and captures observed facts โ status, headers, security headers,
diff --git a/neurosploit-rs/crates/harness/src/report.rs b/neurosploit-rs/crates/harness/src/report.rs
index 6186a49..3bbce24 100644
--- a/neurosploit-rs/crates/harness/src/report.rs
+++ b/neurosploit-rs/crates/harness/src/report.rs
@@ -97,9 +97,9 @@ pub fn html(target: &str, findings: &[Finding]) -> String {
h4{{margin:12px 0 3px;font-size:12px;text-transform:uppercase;letter-spacing:.5px;color:#8b5cf6}}\
.b{{color:#8b5cf6;font-weight:800}}\
NeuroSploit Penetration Test Report
\
- Target: {t} ยท v3.6.2 Rust harness ยท multi-model validated
\
+ Target: {t} ยท v3.6.3 Rust harness ยท multi-model validated
\
{chips}
{graph_block}Findings ({n})
{body}\
- Authorized testing only. Findings confirmed by multi-model adversarial voting.
NeuroSploit v3.6.2 ยท by Joas A Santos & Red Team Leaders