Planning measurements
Tracked by pm-6olc95, pm-398z0u, and pm-d2wfig. Distribution work is tracked by pm-fx0rcb.
These SDK primitives make three planning questions reproducible: which recorded populations have relationship context, how many items belong to each category, and whether estimated prerequisite work fits an authored deadline. The CLI and MCP use the same SDK implementations.
Relationship coverage across lifecycle states
pm graph audit --summary
pm graph audit --save-baseline --summary
--summary retains recorded population totals, acyclicity, and lifecycle
connectivity counts. Use --full for degree histograms, status/type distributions,
edge-kind shares, and reachability detail. Baselines always persist the full census.
The summary omission receipt declares --full as the recovery path.
profile.coverage_by_lifecycle reports all, active, and terminal populations.
profile.coverage_by_status reports the actual schema statuses, including
closed, canceled, and custom terminal states. Every population reports:
nodes: recorded items, excluding missing and external placeholders.isolatedanddegree_leq_one: items with zero or at most one incident edge.semantic_nodesandwithout_semantic_edges: presence or absence of incident provenance or verification edges, using the audit's semantic-kind definition.degree_histogram: exact incident directed-edge counts and their populations.
The existing active-only counters and type profiles retain their meanings. Population measurements include policy-exempt isolates; exemptions affect findings, not census denominators. Incident edges use the assembled graph's normalized, deduplicated directed basis. Reciprocal spellings can still be reported separately by duplicate-edge findings.
ordering_acyclic states whether the complete ordering graph is acyclic.
active_ordering_acyclic states whether any detected ordering cycle touches
active or unknown-lifecycle work. A historical cycle remains a structural cycle
even when the historical-debt policy gives its finding informational severity.
Missing-reference findings retain their separate active and terminal populations.
After a saved baseline, signed lifecycle and status deltas measure backfill
progress. An older baseline that never measured these populations reports
lifecycle_comparable: false; it does not imply that the historical population
was empty. Save a new baseline to begin comparable measurements. Malformed new
census fields invalidate a persisted baseline rather than producing misleading
comparisons. Rebuildable query-cache entries from the older format are discarded.
Group membership and bounded aggregation
pm aggregate --group-by tags --limit 20
pm aggregate --group-by tags,type --sum estimated_minutes --completion
pm aggregate --group-by tags --set-mode tuple
Set-valued dimensions group by distinct normalized members by default. An item
with alpha and beta contributes once to each group, even if a member appears
more than once or differs only in case or surrounding whitespace. The same
expansion rule applies to every array-valued grouping dimension; scalar dimensions
compose with those memberships. Empty sets retain a null group displayed as
(untagged) for tags.
--set-mode tuple selects the previous behavior: the complete normalized set is
one comma-joined key. This is useful when comparing exact category combinations.
It is a distinct operation, and filters.set_mode records the interpretation.
Counts, completion rates, sums, and averages are computed independently in each
membership group. Consequently, summing group counts or numeric sums can count
one item more than once. totals.items_grouped counts distinct input items;
totals.group_memberships counts all group contributions.
Aggregation reads the complete matching item population before paging groups.
An unreadable input fails the strict read rather than becoming a partial count.
The default page contains at most 50 groups. count is the total group count,
returned_count is the page size, and truncated indicates further groups.
Pass the returned next_after value as --after to continue. Keep the grouping
and filters unchanged between pages. A cursor absent from the current result is
rejected with restart guidance; pages are not an immutable snapshot of a tracker
that is being concurrently edited.
Derived deadline constraints
pm graph slack --limit 10
pm graph slack --summary
The existing unit-weighted rows, makespan, and hop-based slack remain intact.
The additional deadline_schedule uses estimated_minutes and authored
deadline finish constraints. Its basis is derived_elapsed_minutes, and
as_of records the clock used. This computation writes no item fields.
A forward pass computes earliest estimated starts from the invocation clock. A
backward pass transfers each dated finish constraint through the ordering graph,
retaining the tightest successor constraint. A derived row identifies the
controlling deadline_id, its latest_start, and slack_minutes relative to the
earliest possible start. Negative slack indicates a time shortfall; this can
coexist with positive hop-based slack.
overcommitted findings name the authored deadline, the shortfall in minutes,
and a bounded controlling prerequisite path. path_truncated identifies a path
whose leading steps were omitted; the retained suffix still ends at the dated
item. Moving the authored deadline later recomputes the finding.
Missing, negative, or nonfinite estimates are explicit unknown_estimate
residuals. Unknown predecessor duration produces unknown_predecessor_estimate
and null temporal slack, never zero-cost feasibility. Cycles and work gated by
cycles report cycle_or_gated_by_cycle. Invalid dates and dates outside the
representable range are also residuals. complete: false means feasibility
cannot be established for the entire input graph; counts remain separate from
bounded samples.
Estimates represent elapsed duration with independent work allowed in parallel.
There is no staffing, resource-contention, working-hours, holiday, or progress
model. The workspace adapter schedules active work and reports population: active;
completed work no longer consumes future duration or contributes overdue deadlines.
The pure SDK function schedules the supplied graph, so callers can select other
populations explicitly. ISO
date-only deadlines denote UTC midnight. Undated components have no latest-start
constraint. Topology cache hits do not reuse time-sensitive deadline results;
they are derived from the current metadata on every invocation.
The pure derivation defaults to a work bound of 100,000 input items plus graph
nodes and edges, checked before graph passes. Larger inputs return
work_limit_exceeded: true and complete: false with no manufactured schedule.
Rows, residual samples, findings, and path identifiers obey the supplied limit.
All retained paths also share a total identifier budget of maxWork, so large
finding limits cannot create quadratic path evidence. Exhaustion preserves exact
counts and sets both path_truncated and the result's truncated flag.
The workspace CLI still pays its existing metadata-read and graph-assembly cost;
the derivation bound is not a claim that loading a million-item workspace is free.
Summary mode suppresses the evidence arrays and retains their counts.
Public SDK
import {
assembleWorkspaceRelationshipGraph,
auditWorkspaceRelationshipGraph,
deriveRelationshipDeadlines,
} from "@unbrained/pm-cli/sdk/graph";
import { runAggregate } from "@unbrained/pm-cli/sdk/query";
const items = [
{ id: "work", title: "Prepare", status: "open", estimated_minutes: 60 },
{
id: "milestone", title: "Deliver", status: "open", blocked_by: "work",
estimated_minutes: 30, deadline: "2026-10-01T12:00:00Z",
},
];
const assembly = assembleWorkspaceRelationshipGraph(items);
const coverage = auditWorkspaceRelationshipGraph(assembly).profile;
const schedule = deriveRelationshipDeadlines(assembly.graph, items, {
now: "2026-10-01T10:00:00Z",
maxWork: 100_000,
limit: 20,
});
const categories = await runAggregate(
{ groupBy: "tags", setMode: "element", limit: 20 },
{ path: "/path/to/project/.agents/pm" },
);
The derivation accepts an AbortSignal. Relationship orientation comes from the
supplied graph registry, so custom ordering kinds and inverse spellings use the
same execution semantics. MCP pm_run with action: "aggregate" accepts
options.setMode, options.limit, and options.after. MCP pm_graph with
subcommand: "slack" returns the same deadline evidence.
Distribution
Build finalization compacts whitespace in retained runtime JavaScript modules. It preserves module paths and identifiers, keeps public declaration documentation, and composes source maps back to the original TypeScript for release symbolication. Existing bundled outputs remain unchanged by this pass. Source maps remain local release artifacts and are excluded from the npm packlist. Artifact size and file count ceilings are checked against the actual npm pack projection.