1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// T26 — restriction-category lint on production code only.
// `#[cfg(test)]` modules inside production sources DO see this when
// invoked via `cargo build`, but `cargo test` evaluates `not(test)`
// as false (test cfg is on) and silences the warning everywhere —
// which is what we want: tests panic deliberately via unwrap, prod
// code should `.expect("invariant: ...")` or propagate.
//! cpm-planner: a Critical Path Method (CPM) planner for agent work, exposed
//! as a standalone MCP server and usable as a plain Rust library.
//!
//! The CPM kernel does the forward pass (earliest start/finish), backward pass
//! (latest start/finish), slack computation, critical-path identification,
//! parallel batch grouping, and bottleneck (ROI) analysis.
//!
//! On top of that kernel, [`BasicCpmPlanner`] implements the lock-aware
//! [`Planner`](ports::Planner) trait: callers submit a [`PlanGraph`](plan::PlanGraph),
//! then acquire / heartbeat / release locks on disjoint cohorts of deliverables
//! so that multiple workers can run in parallel without stepping on each other.
//! The same planner keeps named plan lines and variants (plan-as-code files
//! under `.cpm-planner/plans/`), frozen earned-value baselines and snapshots,
//! and durable state in SQLite ([`SqlitePlanStore`]). [`PlanServer`] surfaces
//! all of it as MCP tools (`plan.submit`, `plan.acquire_cohort`, `plan.ev`, …)
//! over stdio, so any MCP-speaking client — Claude Code, Cursor, a custom
//! orchestrator, or a praxec workflow `connection` — can drive it.
//!
//! # Example
//!
//! Schedule a graph, then lease and complete its first ready cohort:
//!
//! ```
//! use cpm_planner::plan::{AcquireRequest, CallerId, DeliverableStatus, PlanGraph};
//! use cpm_planner::ports::Planner;
//! use cpm_planner::{BasicCpmPlanner, MarkStatusRequest};
//!
//! # fn main() -> anyhow::Result<()> {
//! let graph: PlanGraph = serde_json::from_value(serde_json::json!({
//! "deliverables": [
//! {"id": "design", "owned_files": ["docs/design.md"], "prerequisites": [],
//! "estimated_effort_hours": 4.0},
//! {"id": "build", "owned_files": ["src/app.rs"], "prerequisites": ["design"],
//! "estimated_effort_hours": 8.0},
//! {"id": "docs", "owned_files": ["README.md"], "prerequisites": ["design"],
//! "estimated_effort_hours": 2.0}
//! ]
//! }))?;
//!
//! // Pure CPM: `__start__` and `__finish__` are synthetic zero-length endpoints.
//! let cpm = cpm_planner::schedule::compute_cpm(&graph)?;
//! assert_eq!(cpm.critical_path, ["__start__", "design", "build", "__finish__"]);
//! assert_eq!(cpm.critical_path_duration, 12.0);
//!
//! // Lock-aware execution on a private in-memory store.
//! let runtime = tokio::runtime::Runtime::new()?;
//! runtime.block_on(async {
//! let planner = BasicCpmPlanner::new();
//! let plan_id = planner.submit_plan(graph).await?;
//! let caller = CallerId("worker-1".into());
//!
//! let cohort = planner
//! .acquire_cohort(AcquireRequest::new(plan_id.clone(), caller.clone(), 4))
//! .await?;
//! let leased: Vec<&str> = cohort.rows.iter().map(|r| r.deliverable.id.as_str()).collect();
//! assert_eq!(leased, ["design"]); // `build` and `docs` wait on it
//!
//! planner
//! .mark_status(MarkStatusRequest::new(
//! plan_id.clone(),
//! "design",
//! caller.clone(),
//! DeliverableStatus::Complete,
//! ))
//! .await?;
//!
//! // Completing `design` releases its lock and readies both dependents.
//! let next = planner
//! .acquire_cohort(AcquireRequest::new(plan_id, caller, 4))
//! .await?;
//! let mut leased: Vec<&str> = next.rows.iter().map(|r| r.deliverable.id.as_str()).collect();
//! leased.sort();
//! assert_eq!(leased, ["build", "docs"]);
//! Ok::<_, anyhow::Error>(())
//! })?;
//! # Ok(())
//! # }
//! ```
//!
//! # Layout
//!
//! - [`plan`] — the wire/domain model (deliverables, cohorts, locks, errors).
//! - [`ports`] — the [`Planner`](ports::Planner) trait.
//! - [`algorithm`] / [`task`] — the pure CPM kernel and its internal model
//! (the `Task` types carry ES/EF/LS/LF/slack/batching state the wire model
//! doesn't need to expose).
//! - [`schedule`] — [`schedule::compute_cpm`], the CPM run shared by submit,
//! status and the analysis tools.
//! - [`planner`] — [`BasicCpmPlanner`], the lock-aware implementation
//! (including the earned-value operations).
//! - [`plan_store`] — durable SQLite persistence (plans, statuses, cohort
//! locks, submit dedup, portfolio lines/variants/revisions, earned value)
//! and the cross-process atomicity mechanism.
//! - [`project`] — project-root discovery and confined plan-file I/O.
//! - [`edits`], [`revise`] — structured graph edits and progress-preserving
//! revision.
//! - [`lint`], [`simulate`], [`resource_schedule`], [`monte_carlo`],
//! [`compare`], [`metrics`], [`drag`](mod@drag), [`risk`], [`network_health`] — the
//! read-only analysis tools and the plan scorecard.
//! - [`earned_value`] — the pure PV / EV / AC engine.
//! - [`review`], [`llm`] — the optional `plan.review` and its OpenRouter /
//! Jev judge.
//! - [`server`] — the MCP tool façade.
//! - [`skills`] — the embedded agent skills and `cpm-planner skills install`.
//! - [`audit`] — the lock-lifecycle audit surface.
//!
//! See `docs/architecture.md` in the repository for the request flow, the
//! store schema and the concurrency model.
//!
//! This crate has no dependency on praxec; it is consumed purely over the
//! MCP protocol.
pub
pub use CpmAlgorithm;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;