pm-graph 2026.10.23 → 2026.10.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 2026.10.24 - 2026-10-24
4
+
5
+ ### Added
6
+
7
+ - Enumerate minimal blocker frontiers for a goal with bounded completeness receipts ([pm-graph-8z44](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/features/pm-graph-8z44.toon))
8
+ - Compare snapshot graph impact for resuming agents ([pm-graph-snapshot-impact](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/features/pm-graph-snapshot-impact.toon))
9
+
10
+ ### Other
11
+
12
+ - Correct review evidence in tracker reconciliation PR 155 ([pm-graph-review155](https://github.com/unbraind/pm-graph/blob/main/.agents/pm/chores/pm-graph-review155.toon))
13
+
3
14
  ## 2026.10.23 - 2026-10-23
4
15
 
5
16
  ### Changed
package/README.md CHANGED
@@ -613,7 +613,7 @@ See the [SDK and CLI example](docs/snapshot-impact-example.md) and the
613
613
 
614
614
  ## Without git
615
615
 
616
- Graph projection, export, offline analysis, path/impact/explain and snapshot
616
+ Graph projection, consent-scoped `project`, export, offline analysis, path/impact/explain and snapshot
617
617
  comparison work in plain trackers. Snapshot comparison reads only the two
618
618
  explicit JSON files and also works without a tracker. Neo4j operations require
619
619
  their configured database connection. None of these graph operations needs a
@@ -720,3 +720,15 @@ form paper, carbon ink, ruled item sheets and explicit status fills. Portable fo
720
720
  stacks put Archivo, Public Sans and Martian Mono first and fetch no external fonts.
721
721
  [Design, themes, token parity and reproducible review renders](docs/triplicate-visuals.md).
722
722
  The README banner embeds pm-brand's committed output at an immutable SHA.
723
+
724
+ ## Consent-scoped cross-workspace projection
725
+
726
+ `pm pm-graph project projection.json --limit 100 --json` reads only explicitly
727
+ consented trackers into a read-only item graph. The typed `projectWorkspaces`
728
+ SDK also accepts database readers bound to existing core authorization. Qualified
729
+ IDs preserve workspace identity; cross-workspace evidence needs separate consent
730
+ and two visible endpoints. Denied or incomplete sources yield opaque boundaries,
731
+ with no hidden node IDs, labels or source exception text. Source completeness
732
+ and output omission receipts remain separate. JSON and compact TOON use the
733
+ host's standard output controls. See the [manifest and SDK example](docs/cross-workspace-example.md)
734
+ for scope, authorization, pagination, refusal remedies and limits.
@@ -1,4 +1,4 @@
1
- import { listAllComplete, readSettings, resolveRuntimeStatusRegistry } from "@unbrained/pm-cli/sdk";
1
+ import { defineCommand, defineFlag, listAllComplete, readSettings, resolveRuntimeStatusRegistry } from "@unbrained/pm-cli/sdk";
2
2
  import { minimalBlockerCuts } from "./blocker-cuts.js";
3
3
  /** Preserve refusal evidence in CLI JSON/TOON instead of losing it in error formatting. */
4
4
  function commandRefusal(code, remedy, receipt, source) {
@@ -7,44 +7,30 @@ function commandRefusal(code, remedy, receipt, source) {
7
7
  }
8
8
  /** Register the offline command; source reads are strict, complete, and extension-free. */
9
9
  export function registerBlockerCuts(api, resolveRoot, project) {
10
- api.registerCommand({
10
+ api.registerCommand(defineCommand({
11
11
  name: "pm-graph blocker-cuts",
12
12
  description: "Enumerate alternative minimal blocker frontiers for a goal with bounded completeness receipts.",
13
13
  read_output: { row_keys: ["cuts"] },
14
14
  arguments: [{ name: "goal", required: true, description: "Exact goal item id" }],
15
+ flags: [
16
+ defineFlag({ long: "--max-states", value_name: "integer", value_type: "number", description: "Positive integer search-state budget (default 20000)." }),
17
+ defineFlag({ long: "--max-work", value_name: "integer", value_type: "number", description: "Positive integer work budget (default 2000000)." }),
18
+ defineFlag({ long: "--max-cuts", value_name: "integer", value_type: "number", description: "Positive integer cut limit (default 32)." }),
19
+ defineFlag({ long: "--max-size", value_name: "integer", value_type: "number", description: "Positive integer frontier size limit (default 8)." }),
20
+ ],
15
21
  examples: ["pm pm-graph blocker-cuts goal-id --max-cuts 16 --json"],
16
22
  failure_hints: ["Reload the full tracker for missing nodes; break cycles; increase search limits only when the receipt is partial."],
17
23
  run: async (context) => {
18
- const args = context.args ?? [];
19
- if (args.includes("--help") || args.includes("-h")) {
20
- return {
21
- cuts: [],
22
- usage: "pm pm-graph blocker-cuts <goal> [--max-states 20000] [--max-work 2000000] [--max-cuts 32] [--max-size 8] [--json]",
23
- semantics: "All upstream prerequisite paths must intersect a cut. Intermediate tasks still require completion; immediatelyUnblocked reports direct eligibility.",
24
- completeness: "Graph/source and search receipts are separate. Partial cuts are minimal; a partial list is not exhaustive.",
25
- };
26
- }
24
+ const positionals = context.args ?? [];
27
25
  const options = {};
28
- const positionals = [];
29
- const flags = new Map([
30
- ["--max-states", "maxStates"], ["--max-work", "maxWork"], ["--max-cuts", "maxCuts"], ["--max-size", "maxSize"],
31
- ]);
32
- for (let i = 0; i < args.length; i++) {
33
- const arg = args[i];
34
- if (arg === "--json")
26
+ for (const key of ["maxStates", "maxWork", "maxCuts", "maxSize"]) {
27
+ const value = context.options?.[key];
28
+ if (value === undefined)
35
29
  continue;
36
- const key = flags.get(arg);
37
- if (key) {
38
- const value = args[++i];
39
- if (!value || !/^\d+$/.test(value))
40
- return commandRefusal("INVALID_OPTIONS", `Supply a positive integer after ${arg}.`);
41
- options[key] = Number(value);
42
- }
43
- else if (arg.startsWith("-")) {
44
- return commandRefusal("INVALID_OPTIONS", `Remove unsupported flag ${arg}; use blocker-cuts --help.`);
30
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
31
+ return commandRefusal("INVALID_OPTIONS", `Supply a positive integer for ${key}.`);
45
32
  }
46
- else
47
- positionals.push(arg);
33
+ options[key] = value;
48
34
  }
49
35
  if (positionals.length !== 1)
50
36
  return commandRefusal("INVALID_OPTIONS", "Use pm pm-graph blocker-cuts <exact-goal-id>; see --help for budgets.");
@@ -69,6 +55,6 @@ export function registerBlockerCuts(api, resolveRoot, project) {
69
55
  return { ...commandRefusal(result.refusal.code, result.refusal.remedy, result.receipt, source), goal: result.goal };
70
56
  return { ...result, source };
71
57
  },
72
- });
58
+ }));
73
59
  }
74
60
  //# sourceMappingURL=blocker-cuts-command.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"blocker-cuts-command.js","sourceRoot":"","sources":["../src/blocker-cuts-command.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"blocker-cuts-command.js","sourceRoot":"","sources":["../src/blocker-cuts-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { ExtensionApi } from "./index.ts";
2
+ /** Register a read-only directory transport; database consumers bind core-authorized SDK readers directly. */
3
+ export declare function registerCrossWorkspace(api: ExtensionApi): void;
@@ -0,0 +1,96 @@
1
+ import { constants } from "node:fs";
2
+ import { open } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { projectWorkspaces, projectionRefusal } from "./cross-workspace.js";
5
+ /** Parse an explicit bounded regular manifest without discovering workspaces, git, extensions or credentials. */
6
+ async function readManifest(file) {
7
+ const handle = await open(file, constants.O_RDONLY | constants.O_NONBLOCK);
8
+ try {
9
+ if (!(await handle.stat()).isFile())
10
+ return null;
11
+ const buffer = Buffer.alloc(1024 * 1024 + 1);
12
+ let length = 0;
13
+ while (length < buffer.length) {
14
+ const read = await handle.read(buffer, length, buffer.length - length, null);
15
+ if (read.bytesRead === 0)
16
+ break;
17
+ length += read.bytesRead;
18
+ }
19
+ return length === buffer.length ? null : JSON.parse(buffer.subarray(0, length).toString("utf8"));
20
+ }
21
+ finally {
22
+ await handle.close();
23
+ }
24
+ }
25
+ /** Validate transport input before constructing private directory reader capabilities. */
26
+ function manifestScopes(value, base) {
27
+ if (value === null || typeof value !== "object" || !("scopes" in value) || !Array.isArray(value.scopes)
28
+ || !("consent" in value) || !Array.isArray(value.consent) || !value.consent.every((key) => typeof key === "string"))
29
+ return null;
30
+ const scopes = [];
31
+ for (const scope of value.scopes) {
32
+ if (scope === null || typeof scope !== "object" || !("key" in scope) || typeof scope.key !== "string"
33
+ || !("pmRoot" in scope) || typeof scope.pmRoot !== "string" || scope.pmRoot.length === 0)
34
+ return null;
35
+ scopes.push({ key: scope.key, source: { kind: "directory", pmRoot: path.resolve(base, scope.pmRoot) } });
36
+ }
37
+ const evidence = "evidence" in value ? value.evidence : [];
38
+ if (!Array.isArray(evidence))
39
+ return null;
40
+ // The SDK validates every evidence field before any source capability runs.
41
+ return { scopes, consent: value.consent, evidence: evidence };
42
+ }
43
+ /** Register a read-only directory transport; database consumers bind core-authorized SDK readers directly. */
44
+ export function registerCrossWorkspace(api) {
45
+ api.registerCommand({
46
+ name: "pm-graph project",
47
+ description: "Project explicitly consented workspaces into a qualified read-only graph with opaque boundary receipts.",
48
+ arguments: [{ name: "manifest", required: true, description: "Explicit consent manifest JSON" }],
49
+ read_output: { row_keys: ["rows", "boundaries"] },
50
+ examples: ["pm pm-graph project projection.json --limit 100 --json"],
51
+ failure_hints: ["Grant consent only to intended scopes; recover omitted rows with --offset; see boundary remedies for incomplete sources."],
52
+ run: async (context) => {
53
+ const args = context.args ?? [];
54
+ if (args.includes("--help") || args.includes("-h"))
55
+ return {
56
+ rows: [], boundaries: [], usage: "pm pm-graph project <manifest.json> [--limit 100] [--offset 0] [--json]",
57
+ manifest: { scopes: [{ key: "public", pmRoot: "tracker/.agents/pm" }], consent: ["public"], evidence: [] },
58
+ semantics: "Paths are relative to the manifest. Explicit local consent plus OS/SDK read permissions; hosted callers must bind their current authorization via projectWorkspaces. No traversal of denied scopes. Item nodes and local relationships only; cross-workspace SUPPORTS/EVIDENCE_FOR requires per-edge consent. Read sequentially, not a global atomic snapshot.",
59
+ };
60
+ let manifest;
61
+ let limit = 100;
62
+ let offset = 0;
63
+ for (let index = 0; index < args.length; index++) {
64
+ const arg = args[index];
65
+ if (arg === "--json")
66
+ continue;
67
+ if (arg === "--limit" || arg === "--offset") {
68
+ const raw = args[++index];
69
+ if (!raw || !/^\d+$/.test(raw))
70
+ return projectionRefusal();
71
+ if (arg === "--limit")
72
+ limit = Number(raw);
73
+ else
74
+ offset = Number(raw);
75
+ }
76
+ else if (arg.startsWith("-") || manifest !== undefined)
77
+ return projectionRefusal();
78
+ else
79
+ manifest = arg;
80
+ }
81
+ if (manifest === undefined)
82
+ return projectionRefusal();
83
+ const file = path.resolve(context.cwd ?? process.cwd(), manifest);
84
+ try {
85
+ const parsed = manifestScopes(await readManifest(file), path.dirname(file));
86
+ if (!parsed)
87
+ return projectionRefusal();
88
+ return await projectWorkspaces(parsed.scopes, { consent: parsed.consent, evidence: parsed.evidence, authorize: () => true, limit, offset });
89
+ }
90
+ catch {
91
+ return projectionRefusal();
92
+ }
93
+ },
94
+ });
95
+ }
96
+ //# sourceMappingURL=cross-workspace-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-workspace-command.js","sourceRoot":"","sources":["../src/cross-workspace-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ import type { PmCompleteListResult } from "@unbrained/pm-cli/sdk";
2
+ /** Explicit source capability. Database readers must be bound to the caller's core-authorized tenant scope. */
3
+ export type ProjectionSource = {
4
+ kind: "directory";
5
+ pmRoot: string;
6
+ } | {
7
+ kind: "database";
8
+ read: () => Promise<PmCompleteListResult>;
9
+ };
10
+ /** Public alias and private source capability; neither paths nor database identifiers enter output. */
11
+ export type ProjectionScope = {
12
+ key: string;
13
+ source: ProjectionSource;
14
+ };
15
+ /** Cross-workspace evidence is opt-in per edge and requires two readable item endpoints. */
16
+ export type ProjectionEvidence = {
17
+ from: {
18
+ workspace: string;
19
+ item: string;
20
+ };
21
+ to: {
22
+ workspace: string;
23
+ item: string;
24
+ };
25
+ type: "SUPPORTS" | "EVIDENCE_FOR";
26
+ consent: boolean;
27
+ };
28
+ /** Explicit consent plus the host's current authorization policy; consent never overrides denial. */
29
+ export type ProjectionOptions = {
30
+ consent: readonly string[];
31
+ authorize: (scope: ProjectionScope) => boolean | Promise<boolean>;
32
+ evidence?: readonly ProjectionEvidence[];
33
+ limit?: number;
34
+ offset?: number;
35
+ };
36
+ /** Compact item-only graph rows, intentionally excluding bodies, raw dependencies and external placeholders. */
37
+ export type ProjectionRow = {
38
+ kind: "node";
39
+ id: string;
40
+ workspace: string;
41
+ item: string;
42
+ title: string;
43
+ type: string;
44
+ status: string;
45
+ } | {
46
+ kind: "edge";
47
+ from: string;
48
+ to: string;
49
+ type: string;
50
+ };
51
+ /** Opaque boundary references only the caller's input slot; it carries no source errors or endpoint identities. */
52
+ export type ProjectionBoundary = {
53
+ slot: number;
54
+ kind: "scope" | "evidence";
55
+ code: "CONSENT_REQUIRED" | "UNREADABLE" | "SOURCE_INCOMPLETE" | "ENDPOINT_UNAVAILABLE" | "SOURCE_LIMIT" | "DANGLING_RELATION";
56
+ remedy: string;
57
+ };
58
+ /** Source completeness and output pagination are independent, explicit receipts. No cross-source atomic snapshot is claimed. */
59
+ export type ProjectionResult = {
60
+ ok: boolean;
61
+ exit_code?: number;
62
+ rows: ProjectionRow[];
63
+ boundaries: ProjectionBoundary[];
64
+ complete: boolean;
65
+ receipt: {
66
+ scopes: number;
67
+ readable: number;
68
+ nodes: number;
69
+ edges: number;
70
+ total: number;
71
+ offset: number;
72
+ returned: number;
73
+ omitted: number;
74
+ nextOffset: number | null;
75
+ };
76
+ refusal?: {
77
+ code: "INVALID_OPTIONS" | "NO_READABLE_SCOPE";
78
+ remedy: string;
79
+ };
80
+ };
81
+ /** Encode each tuple component separately so separators and repeated local IDs cannot collide. */
82
+ export declare function qualifyProjectionId(workspace: string, item: string): string;
83
+ /** Produce a stable, path-free refusal that remains machine-readable through the CLI host. */
84
+ export declare function projectionRefusal(): ProjectionResult;
85
+ /**
86
+ * Project only explicitly consented, currently authorized sources. SDK certificates are
87
+ * revalidated even for database adapters. Failed reads are atomic per source; no partial
88
+ * source rows, dangling identities, source errors, or denied endpoint labels are exposed.
89
+ * Readers run sequentially and without extension hooks; this function performs no writes.
90
+ */
91
+ export declare function projectWorkspaces(scopes: readonly ProjectionScope[], options: ProjectionOptions): Promise<ProjectionResult>;
@@ -0,0 +1,120 @@
1
+ import { certifyCompleteListResult, listAllComplete } from "@unbrained/pm-cli/sdk";
2
+ import { assertPmTracker, graphFromItems } from "./index.js";
3
+ /** Encode each tuple component separately so separators and repeated local IDs cannot collide. */
4
+ export function qualifyProjectionId(workspace, item) {
5
+ return `${encodeURIComponent(workspace)}::${encodeURIComponent(item)}`;
6
+ }
7
+ /** Produce a stable, path-free refusal that remains machine-readable through the CLI host. */
8
+ export function projectionRefusal() {
9
+ return { ok: false, exit_code: 2, rows: [], boundaries: [], complete: false,
10
+ receipt: { scopes: 0, readable: 0, nodes: 0, edges: 0, total: 0, offset: 0, returned: 0, omitted: 0, nextOffset: null },
11
+ refusal: { code: "INVALID_OPTIONS", remedy: "Supply 1-32 unique nonempty scope aliases and well-formed Unicode identity components, explicit consent and an authorization function; limit 1-1000, offset 0 or greater, and at most 10000 typed evidence edges. See project --help." } };
12
+ }
13
+ /** Accept nonempty UTF-16 identities only when every surrogate belongs to a valid pair. */
14
+ function validIdentityComponent(value) {
15
+ return typeof value === "string" && value.length > 0 && !/[\uD800-\uDFFF]/u.test(value);
16
+ }
17
+ /** Check caller-controlled edge shapes without inspecting any source or echoing invalid identities. */
18
+ function validEvidence(edge) {
19
+ return edge !== null && typeof edge === "object" && ["SUPPORTS", "EVIDENCE_FOR"].includes(edge.type)
20
+ && typeof edge.consent === "boolean" && [edge.from, edge.to].every((end) => end !== null && typeof end === "object"
21
+ && validIdentityComponent(end.workspace) && validIdentityComponent(end.item));
22
+ }
23
+ /**
24
+ * Project only explicitly consented, currently authorized sources. SDK certificates are
25
+ * revalidated even for database adapters. Failed reads are atomic per source; no partial
26
+ * source rows, dangling identities, source errors, or denied endpoint labels are exposed.
27
+ * Readers run sequentially and without extension hooks; this function performs no writes.
28
+ */
29
+ export async function projectWorkspaces(scopes, options) {
30
+ const limit = options.limit ?? 100;
31
+ const offset = options.offset ?? 0;
32
+ const evidence = options.evidence ?? [];
33
+ if (!Array.isArray(scopes) || scopes.length === 0 || scopes.length > 32 || !Array.isArray(options.consent)
34
+ || typeof options.authorize !== "function" || !Number.isSafeInteger(limit) || limit < 1 || limit > 1000
35
+ || !Number.isSafeInteger(offset) || offset < 0 || !Array.isArray(evidence) || evidence.length > 10000
36
+ || !evidence.every(validEvidence) || !scopes.every((scope) => scope && validIdentityComponent(scope.key) && scope.key.length <= 128
37
+ && scope.source && (scope.source.kind === "directory" ? typeof scope.source.pmRoot === "string" && scope.source.pmRoot.length > 0 : scope.source.kind === "database" && typeof scope.source.read === "function"))
38
+ || new Set(scopes.map((scope) => scope.key)).size !== scopes.length
39
+ || options.consent.some((key) => !scopes.some((scope) => scope.key === key)))
40
+ return projectionRefusal();
41
+ const rows = [];
42
+ const boundaries = [];
43
+ const visible = new Set();
44
+ const edgeKeys = new Set();
45
+ let readable = 0;
46
+ for (const [slot, scope] of scopes.entries()) {
47
+ if (!options.consent.includes(scope.key)) {
48
+ boundaries.push({ slot, kind: "scope", code: "CONSENT_REQUIRED", remedy: "Obtain explicit read consent for this input scope." });
49
+ continue;
50
+ }
51
+ let allowed = false;
52
+ try {
53
+ allowed = await options.authorize(scope) === true;
54
+ }
55
+ catch { /* Authorization failures are indistinguishable from denial. */ }
56
+ if (!allowed) {
57
+ boundaries.push({ slot, kind: "scope", code: "UNREADABLE", remedy: "Ask the source owner to grant read access, then retry with current authorization." });
58
+ continue;
59
+ }
60
+ let snapshot;
61
+ try {
62
+ snapshot = certifyCompleteListResult(scope.source.kind === "directory"
63
+ ? await listAllComplete({}, { pmRoot: assertPmTracker(scope.source.pmRoot), noExtensions: true }) : await scope.source.read());
64
+ }
65
+ catch {
66
+ boundaries.push({ slot, kind: "scope", code: "SOURCE_INCOMPLETE", remedy: "Restore a readable source and use the SDK listAllComplete reader without filters or output limits." });
67
+ continue;
68
+ }
69
+ if (snapshot.items.length > 100000) {
70
+ boundaries.push({ slot, kind: "scope", code: "SOURCE_LIMIT", remedy: "Project a separately authorized tracker containing at most 100000 items." });
71
+ continue;
72
+ }
73
+ const graph = graphFromItems(snapshot.items, scope.key, new Map(), { includeFacets: false });
74
+ const local = new Set(snapshot.items.map((item) => item.id));
75
+ for (const item of snapshot.items) {
76
+ const id = qualifyProjectionId(scope.key, item.id);
77
+ visible.add(id);
78
+ rows.push({ kind: "node", id, workspace: scope.key, item: item.id, title: item.title, type: item.type, status: item.status });
79
+ }
80
+ let dangling = false;
81
+ for (const edge of graph.relationships) {
82
+ if (!local.has(edge.from) || !local.has(edge.to)) {
83
+ dangling = true;
84
+ continue;
85
+ }
86
+ const from = qualifyProjectionId(scope.key, edge.from);
87
+ const to = qualifyProjectionId(scope.key, edge.to);
88
+ rows.push({ kind: "edge", from, to, type: edge.type });
89
+ edgeKeys.add(JSON.stringify([from, to, edge.type]));
90
+ }
91
+ if (dangling)
92
+ boundaries.push({ slot, kind: "scope", code: "DANGLING_RELATION", remedy: "Resolve missing local relationships in the source; cross-workspace evidence requires a separate consented edge." });
93
+ readable++;
94
+ }
95
+ for (const [slot, edge] of evidence.entries()) {
96
+ if (!edge.consent) {
97
+ boundaries.push({ slot, kind: "evidence", code: "CONSENT_REQUIRED", remedy: "Obtain explicit consent for this evidence edge." });
98
+ continue;
99
+ }
100
+ const from = qualifyProjectionId(edge.from.workspace, edge.from.item);
101
+ const to = qualifyProjectionId(edge.to.workspace, edge.to.item);
102
+ if (!visible.has(from) || !visible.has(to)) {
103
+ boundaries.push({ slot, kind: "evidence", code: "ENDPOINT_UNAVAILABLE", remedy: "Ensure both endpoints are in readable consented sources; no boundary is traversed automatically." });
104
+ continue;
105
+ }
106
+ const key = JSON.stringify([from, to, edge.type]);
107
+ if (edgeKeys.has(key))
108
+ continue;
109
+ edgeKeys.add(key);
110
+ rows.push({ kind: "edge", from, to, type: edge.type });
111
+ }
112
+ const page = rows.slice(offset, offset + limit);
113
+ const nodes = visible.size;
114
+ const nextOffset = offset + page.length < rows.length ? offset + page.length : null;
115
+ return { ok: readable > 0, rows: page, boundaries,
116
+ complete: boundaries.length === 0,
117
+ receipt: { scopes: scopes.length, readable, nodes, edges: rows.length - nodes, total: rows.length, offset, returned: page.length, omitted: rows.length - page.length, nextOffset },
118
+ ...(readable === 0 ? { exit_code: 2, refusal: { code: "NO_READABLE_SCOPE", remedy: "Provide at least one consented source with current read authorization and a certified complete read." } } : {}) };
119
+ }
120
+ //# sourceMappingURL=cross-workspace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cross-workspace.js","sourceRoot":"","sources":["../src/cross-workspace.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
+ export { projectWorkspaces, qualifyProjectionId } from "./cross-workspace.ts";
2
+ export type { ProjectionSource, ProjectionScope, ProjectionOptions, ProjectionEvidence, ProjectionRow, ProjectionBoundary, ProjectionResult } from "./cross-workspace.ts";
1
3
  export { compareGraphSnapshots } from "./snapshot-impact.ts";
2
4
  export type { SnapshotImpactOptions, SnapshotNodeContext, SnapshotImpactChange, SnapshotImpactRefusalCode, SnapshotImpactRefusal, SnapshotImpactResult } from "./snapshot-impact.ts";
3
5
  import type { BrandTheme } from "./brand.ts";
4
- import { type Exporter, type ItemMetadata } from "@unbrained/pm-cli/sdk";
6
+ import { type FlagDefinition, type Exporter, type GlobalOptions, type ItemMetadata } from "@unbrained/pm-cli/sdk";
5
7
  import type { ExtensionCommandReadOutput } from "@unbrained/pm-cli/sdk/authoring";
6
8
  export { minimalBlockerCuts } from "./blocker-cuts.ts";
7
9
  export type { BlockerCut, BlockerCutOptions, BlockerCutReceipt, BlockerCutRefusalCode, BlockerCutResult } from "./blocker-cuts.ts";
@@ -15,7 +17,7 @@ export type CommandContext = {
15
17
  /** Resolved tracker storage path the CLI passes to extension commands (honours --pm-path/--path). */
16
18
  pm_root?: string;
17
19
  options?: Record<string, unknown>;
18
- global?: Record<string, unknown>;
20
+ global?: Partial<GlobalOptions> | Record<string, unknown>;
19
21
  };
20
22
  /** Definition of a positional argument accepted by an extension command. */
21
23
  export type ExtensionCommandArgumentDefinition = {
@@ -31,6 +33,7 @@ export type RegisterCommand = {
31
33
  read_output?: ExtensionCommandReadOutput;
32
34
  run: (context: CommandContext) => Promise<unknown>;
33
35
  arguments?: ExtensionCommandArgumentDefinition[];
36
+ flags?: FlagDefinition[];
34
37
  intent?: string;
35
38
  examples?: string[];
36
39
  failure_hints?: string[];
@@ -72,6 +75,31 @@ export type Graph = {
72
75
  nodes: GraphNode[];
73
76
  relationships: GraphRelationship[];
74
77
  };
78
+ /**
79
+ * Verify that a path is a usable pm tracker root, throwing a `USAGE`
80
+ * {@link PmCliExpectedError} when it is not.
81
+ *
82
+ * This exists because the SDK reader is tolerant where the shell-out it replaced
83
+ * was strict. `listAllItemMetadata` resolves with an empty array for a path that
84
+ * does not exist, for a path that is a regular file (swallowing `ENOTDIR`), and
85
+ * for a directory that is not a tracker — all indistinguishable from a tracker
86
+ * that legitimately has no items. Without this check a mistyped `--path` /
87
+ * `--pm-path` would make `analyze`, `cycles`, `critical-path`, `cypher` and the
88
+ * exporters report a confident empty graph and exit 0, where the previous
89
+ * `pm list-all` invocation exited non-zero. Reporting an empty answer for a bad
90
+ * input is worse than failing, because nothing downstream can detect it.
91
+ *
92
+ * A tracker is identified by the artefacts `pm init` always writes: `settings.json`
93
+ * and the `schema/` directory. Requiring either (rather than both) keeps older
94
+ * trackers readable while still rejecting an arbitrary directory.
95
+ *
96
+ * Filed upstream as unbraind/pm-cli#814; this guard restores the previous failure
97
+ * semantics locally and independently of that fix.
98
+ *
99
+ * @param pmRoot Candidate tracker root.
100
+ * @returns The same path, once validated, so callers can wrap in place.
101
+ */
102
+ export declare function assertPmTracker(pmRoot: string): string;
75
103
  /**
76
104
  * Build a workspace graph (nodes + relationships) from pm item metadata.
77
105
  *
@@ -87,9 +115,12 @@ export type Graph = {
87
115
  * @param items - pm item metadata to project.
88
116
  * @param workspace - Workspace path, recorded on the returned graph.
89
117
  * @param depsByItem - Extra dependency records keyed by item id.
118
+ * @param options - Set includeFacets to false for an item-only graph with unambiguous missing-item endpoints.
90
119
  * @returns The shaped graph with project metadata.
91
120
  */
92
- export declare function graphFromItems(items: readonly ItemMetadata[], workspace: string, depsByItem: Map<string, Array<Record<string, unknown>>>): Graph;
121
+ export declare function graphFromItems(items: readonly ItemMetadata[], workspace: string, depsByItem: Map<string, Array<Record<string, unknown>>>, options?: {
122
+ includeFacets?: boolean;
123
+ }): Graph;
93
124
  type AnalyticsFlags = {
94
125
  json: boolean;
95
126
  includeClosed: boolean;
@@ -106,7 +137,7 @@ type AnalyticsFlags = {
106
137
  /**
107
138
  * Parse the shared analytics flags (--json, --include-closed, --root, --depth,
108
139
  * --format, --filter) and collect remaining positional arguments. Throws a USAGE
109
- * CommandError on a malformed --depth, an invalid --format, a malformed
140
+ * An expected error on a malformed --depth, an invalid --format, a malformed
110
141
  * --filter, or a value-less --root/--depth/--format/--filter.
111
142
  */
112
143
  export declare function parseAnalyticsFlags(args: string[]): AnalyticsFlags;
@@ -123,7 +154,7 @@ export type NodeFilterEntry = {
123
154
  export type NodeFilter = NodeFilterEntry[];
124
155
  /**
125
156
  * Parse one or more `key=value[,value]` filter terms into a NodeFilter.
126
- * Throws a USAGE CommandError on a missing `=`, an unsupported key, or an
157
+ * Throws a USAGE expected error on a missing `=`, an unsupported key, or an
127
158
  * empty value list. Values are matched case-insensitively.
128
159
  */
129
160
  export declare function parseNodeFilter(raw: string[]): NodeFilter;
@@ -259,7 +290,7 @@ export declare function cyclesSubgraph(graph: Graph, cycles: string[][]): Graph;
259
290
  * `--direction` value. The canonical engine uses edge-orientation terms:
260
291
  * `incoming` = downstream dependents (items that break if <id> changes —
261
292
  * exactly the legacy `reverseReachable` semantics), `outgoing` = upstream
262
- * prerequisites/blockers, `both` = union. Throws a USAGE `CommandError` on an
293
+ * prerequisites/blockers, `both` = union. Throws a USAGE `PmCliExpectedError` on an
263
294
  * unknown logical direction.
264
295
  */
265
296
  export declare function mapImpactDirection(logical: string): "incoming" | "outgoing" | "both";