@unbrained/pm-cli 2026.8.10 → 2026.8.12
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +153 -100
- package/dist/cli/commander-usage.js +113 -15
- package/dist/cli/error-guidance.d.ts +12 -0
- package/dist/cli/error-guidance.js +147 -29
- package/dist/cli/main.js +38 -19
- package/dist/cli/register-annotations.js +56 -3
- package/dist/cli/register-assurance.d.ts +8 -0
- package/dist/cli/register-assurance.js +52 -0
- package/dist/cli/register-files-lookup.js +14 -3
- package/dist/cli/register-list-query.js +10 -7
- package/dist/cli/register-mutation.js +30 -9
- package/dist/cli/register-operations.js +30 -15
- package/dist/cli-bundle/bundle-manifest.json +155 -155
- package/dist/cli-bundle/chunks/{chunk-TRWXF476.js → chunk-3PS7X6GS.js} +2 -2
- package/dist/cli-bundle/chunks/chunk-7KL6KMWF.js +13 -0
- package/dist/cli-bundle/chunks/{chunk-EBO7OUUE.js → chunk-BM5YJ2UW.js} +69 -55
- package/dist/cli-bundle/chunks/chunk-D55A5AEN.js +33 -0
- package/dist/cli-bundle/chunks/chunk-KIKDWECH.js +196 -0
- package/dist/cli-bundle/chunks/{chunk-ETML5C6H.js → chunk-KOQMGCTA.js} +47 -47
- package/dist/cli-bundle/chunks/chunk-R76342NV.js +2 -0
- package/dist/cli-bundle/chunks/chunk-RBTK5EZV.js +5 -0
- package/dist/cli-bundle/chunks/chunk-VLUQOIDT.js +2 -0
- package/dist/cli-bundle/chunks/chunk-WLAHPSOX.js +3 -0
- package/dist/cli-bundle/chunks/register-list-query-XPCJRUUD.js +10 -0
- package/dist/cli-bundle/chunks/register-mutation-4RJCBKRS.js +20 -0
- package/dist/cli-bundle/chunks/register-operations-2OGISOVR.js +2 -0
- package/dist/cli-bundle/chunks/{register-setup-S3KT2LTH.js → register-setup-LLHAKP2V.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-2DALMVF7.js +5 -0
- package/dist/cli-bundle/focused-chunks/chunk-5OQIMELN.js +155 -0
- package/dist/cli-bundle/focused-chunks/chunk-5TAGTDRE.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-ECADZMVC.js +18 -0
- package/dist/cli-bundle/focused-chunks/chunk-EJGXRA6B.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-FFGSZCPK.js +16 -0
- package/dist/cli-bundle/focused-chunks/{chunk-IW43E4XL.js → chunk-GQW4BPUT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ISKOBJCM.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-75774JWP.js → chunk-JAFRIWVF.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-2COZ67XT.js → chunk-KHM2CS2C.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-M6RPIXKC.js → chunk-KPSXVBDV.js} +2 -2
- package/dist/cli-bundle/focused-chunks/{chunk-34SX654S.js → chunk-KTLOKKCQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-LGYK3NUF.js +8 -0
- package/dist/cli-bundle/focused-chunks/{chunk-2EXI2DKR.js → chunk-NBLUKITQ.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-NYLOUPOB.js +2 -0
- package/dist/cli-bundle/focused-chunks/chunk-Q4T463SD.js +31 -0
- package/dist/cli-bundle/focused-chunks/chunk-QVZVY53N.js +2 -0
- package/dist/cli-bundle/focused-chunks/{chunk-DZGAHZZV.js → chunk-RWPAWUOT.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-UTEWXDEQ.js +17 -0
- package/dist/cli-bundle/focused-chunks/{chunk-7UN6BHHO.js → chunk-YIAXMC3T.js} +2 -2
- package/dist/cli-bundle/focused-chunks/chunk-ZMGWAYH7.js +2 -0
- package/dist/cli-bundle/main.js +13 -13
- package/dist/cli-bundle/sdk-authoring.js +1 -1
- package/dist/cli-bundle/sdk-contracts.js +1 -1
- package/dist/cli-bundle/sdk-core.js +41 -39
- package/dist/cli-bundle/sdk-governance.js +1 -1
- package/dist/cli-bundle/sdk-graph.js +1 -1
- package/dist/cli-bundle/sdk-merge.js +1 -1
- package/dist/cli-bundle/sdk-query.js +1 -1
- package/dist/cli-bundle/sdk-runtime.js +1 -1
- package/dist/cli-bundle/sdk-testing.js +1 -1
- package/dist/cli-bundle/sdk.js +2 -2
- package/dist/core/extensions/reserved-host-flags.js +3 -2
- package/dist/core/history/drift-scan.js +13 -8
- package/dist/core/history/history.d.ts +8 -0
- package/dist/core/history/history.js +57 -7
- package/dist/core/history/replay.d.ts +9 -1
- package/dist/core/history/replay.js +48 -13
- package/dist/core/output/output.d.ts +2 -0
- package/dist/core/output/output.js +46 -14
- package/dist/core/session/session-state.d.ts +47 -0
- package/dist/core/session/session-state.js +207 -6
- package/dist/core/shared/author.d.ts +7 -1
- package/dist/core/shared/author.js +66 -46
- package/dist/core/shared/command-types.d.ts +2 -0
- package/dist/core/shared/command-types.js +2 -2
- package/dist/core/shared/errors.d.ts +13 -0
- package/dist/core/shared/errors.js +2 -2
- package/dist/core/store/item-metadata-cache.d.ts +1 -1
- package/dist/core/store/item-metadata-cache.js +14 -7
- package/dist/sdk/agent/refusal-reachability.d.ts +40 -0
- package/dist/sdk/agent/refusal-reachability.js +72 -0
- package/dist/sdk/agent/subcommand-recovery.d.ts +40 -0
- package/dist/sdk/agent/subcommand-recovery.js +84 -0
- package/dist/sdk/cli-bootstrap.d.ts +10 -0
- package/dist/sdk/cli-bootstrap.js +11 -7
- package/dist/sdk/cli-contracts/completeness.js +14 -3
- package/dist/sdk/cli-contracts/enum-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts/enum-contracts.js +3 -2
- package/dist/sdk/cli-contracts/flag-contracts.d.ts +2 -0
- package/dist/sdk/cli-contracts/flag-contracts.js +20 -2
- package/dist/sdk/cli-contracts/registration-helpers.d.ts +20 -3
- package/dist/sdk/cli-contracts/registration-helpers.js +18 -12
- package/dist/sdk/cli-contracts/runtime-contracts.js +4 -3
- package/dist/sdk/cli-contracts/schemas/assurance-definition.d.ts +515 -0
- package/dist/sdk/cli-contracts/schemas/assurance-definition.js +252 -0
- package/dist/sdk/cli-contracts/tool-option-contracts.js +3 -2
- package/dist/sdk/cli-contracts/tool-parameter-tables.js +99 -2
- package/dist/sdk/cli-contracts/tool-schema.d.ts +3 -3
- package/dist/sdk/cli-contracts/tool-schema.js +38 -5
- package/dist/sdk/cli-contracts.d.ts +1 -1
- package/dist/sdk/cli-contracts.js +3 -3
- package/dist/sdk/cli-program.js +3 -2
- package/dist/sdk/completion.js +23 -8
- package/dist/sdk/config.js +9 -3
- package/dist/sdk/context/semantic-session-attribution.d.ts +32 -0
- package/dist/sdk/context/semantic-session-attribution.js +83 -0
- package/dist/sdk/context-intent-contracts.js +45 -4
- package/dist/sdk/core-governance.d.ts +3 -1
- package/dist/sdk/core-governance.js +5 -3
- package/dist/sdk/core.d.ts +2 -1
- package/dist/sdk/core.js +3 -2
- package/dist/sdk/error-code-catalog.d.ts +13 -0
- package/dist/sdk/error-code-catalog.js +35 -2
- package/dist/sdk/extension.js +18 -17
- package/dist/sdk/files.d.ts +11 -0
- package/dist/sdk/files.js +82 -24
- package/dist/sdk/generated/generated-error-code-catalog-part-1.d.ts +8 -0
- package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1585 -0
- package/dist/sdk/generated/generated-error-code-catalog-part-2.d.ts +8 -0
- package/dist/sdk/generated/generated-error-code-catalog-part-2.js +1608 -0
- package/dist/sdk/generated-error-code-catalog.js +6 -3121
- package/dist/sdk/governance/assurance-action-contracts.d.ts +7 -0
- package/dist/sdk/governance/assurance-action-contracts.js +5 -0
- package/dist/sdk/governance/assurance-action.d.ts +70 -0
- package/dist/sdk/governance/assurance-action.js +200 -0
- package/dist/sdk/governance/assurance-limits.d.ts +7 -0
- package/dist/sdk/governance/assurance-limits.js +11 -0
- package/dist/sdk/governance/assurance-mutation-error.d.ts +7 -0
- package/dist/sdk/governance/assurance-mutation-error.js +35 -0
- package/dist/sdk/governance/assurance-runtime.d.ts +20 -0
- package/dist/sdk/governance/assurance-runtime.js +134 -0
- package/dist/sdk/governance/assurance.d.ts +438 -0
- package/dist/sdk/governance/assurance.js +804 -0
- package/dist/sdk/governance/health.d.ts +3 -1
- package/dist/sdk/governance/health.js +21 -4
- package/dist/sdk/governance/validate-item-reader.js +3 -3
- package/dist/sdk/governance/validate.d.ts +2 -0
- package/dist/sdk/governance/validate.js +13 -3
- package/dist/sdk/graph/durable-cache.d.ts +1 -1
- package/dist/sdk/graph/durable-cache.js +98 -29
- package/dist/sdk/graph/governance.d.ts +14 -0
- package/dist/sdk/graph/governance.js +34 -9
- package/dist/sdk/graph/run.js +8 -3
- package/dist/sdk/index.d.ts +7 -2
- package/dist/sdk/index.js +9 -4
- package/dist/sdk/lifecycle/claim.js +26 -4
- package/dist/sdk/lifecycle/focus.js +13 -3
- package/dist/sdk/lifecycle/plan.js +8 -3
- package/dist/sdk/merge/driver.d.ts +2 -2
- package/dist/sdk/merge/driver.js +3 -3
- package/dist/sdk/merge/receipts.d.ts +6 -4
- package/dist/sdk/merge/receipts.js +7 -5
- package/dist/sdk/merge/three-way.d.ts +2 -2
- package/dist/sdk/merge/three-way.js +3 -3
- package/dist/sdk/output-projection.d.ts +9 -1
- package/dist/sdk/output-projection.js +45 -9
- package/dist/sdk/provenance.d.ts +38 -0
- package/dist/sdk/provenance.js +64 -2
- package/dist/sdk/query/activity.d.ts +49 -2
- package/dist/sdk/query/activity.js +183 -37
- package/dist/sdk/query/context.js +19 -8
- package/dist/sdk/query/get.js +19 -2
- package/dist/sdk/read-output-contracts.d.ts +5 -1
- package/dist/sdk/read-output-contracts.js +17 -2
- package/dist/sdk/read-output-session.d.ts +2 -0
- package/dist/sdk/read-output-session.js +12 -5
- package/dist/sdk/runtime-extended-actions.js +22 -5
- package/dist/sdk/runtime-primitives.d.ts +5 -3
- package/dist/sdk/runtime-primitives.js +7 -5
- package/dist/sdk/runtime-stats-options.js +3 -2
- package/dist/sdk/runtime.d.ts +4 -0
- package/dist/sdk/runtime.js +34 -34
- package/dist/sdk/stats.d.ts +36 -3
- package/dist/sdk/stats.js +55 -15
- package/dist/sdk/telemetry.js +7 -4
- package/dist/sdk/test/execution.d.ts +4 -0
- package/dist/sdk/test/execution.js +86 -21
- package/dist/sdk/traceability/runtime-files-lookup.d.ts +8 -0
- package/dist/sdk/traceability/runtime-files-lookup.js +33 -0
- package/dist/sdk/traceability/source-traceability.d.ts +102 -0
- package/dist/sdk/traceability/source-traceability.js +293 -0
- package/dist/types.d.ts +10 -2
- package/dist/types.js +2 -2
- package/docs/ASSURANCE.md +137 -0
- package/docs/COMMANDS.md +29 -8
- package/docs/CONFIGURATION.md +7 -0
- package/docs/DEPENDENCY_KIND_CONTRACT.md +10 -8
- package/docs/MERGE_SAFETY.md +3 -1
- package/docs/OUTPUT_PROJECTION_CONTRACTS.md +26 -13
- package/docs/README.md +3 -0
- package/docs/READ_OUTPUT_CONTRACTS.md +17 -2
- package/docs/RELEASING.md +9 -5
- package/docs/SDK.md +12 -1
- package/docs/SDK_AGENT_SESSION_CONTEXT.md +52 -5
- package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +53 -0
- package/docs/SDK_CONTEXT_INTEGRITY.md +28 -1
- package/docs/SDK_EVIDENCE_TRACEABILITY.md +39 -1
- package/docs/SDK_RUNTIME_BOUNDARIES.md +36 -1
- package/docs/TESTING.md +12 -9
- package/docs/TRUSTWORTHY_CONTEXT_EVIDENCE.md +106 -0
- package/docs/generated/AGENT_COMMAND_SURFACE.md +1 -0
- package/docs/performance/sdk-entrypoint-import-costs.md +6 -2
- package/marketplace.json +2 -2
- package/package.json +7 -7
- package/packages/pm-beads/package.json +1 -1
- package/packages/pm-calendar/package.json +1 -1
- package/packages/pm-command-kit/package.json +1 -1
- package/packages/pm-digital-twin/package.json +1 -1
- package/packages/pm-governance-audit/package.json +1 -1
- package/packages/pm-guide-shell/package.json +1 -1
- package/packages/pm-kanban/package.json +1 -1
- package/packages/pm-lifecycle-hooks/package.json +1 -1
- package/packages/pm-linked-test-adapters/package.json +1 -1
- package/packages/pm-search-advanced/package.json +1 -1
- package/packages/pm-templates/extensions/templates/index.ts +9 -3
- package/packages/pm-templates/package.json +1 -1
- package/packages/pm-todos/package.json +1 -1
- package/packages/pm-vcs/package.json +1 -1
- package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
- package/sdk/public-surface.json +1030 -67
- package/dist/cli-bundle/chunks/chunk-2QYH4QMI.js +0 -30
- package/dist/cli-bundle/chunks/chunk-JUDU4RO4.js +0 -194
- package/dist/cli-bundle/chunks/chunk-JZSNADRR.js +0 -5
- package/dist/cli-bundle/chunks/chunk-MENWVGJW.js +0 -2
- package/dist/cli-bundle/chunks/chunk-S4EITGLA.js +0 -3
- package/dist/cli-bundle/chunks/chunk-TIGKWVLA.js +0 -13
- package/dist/cli-bundle/chunks/chunk-TSLDTJLQ.js +0 -2
- package/dist/cli-bundle/chunks/register-list-query-OJ6ZXKV4.js +0 -10
- package/dist/cli-bundle/chunks/register-mutation-DKI7QC7O.js +0 -20
- package/dist/cli-bundle/chunks/register-operations-P5OGMT6I.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-36FTZ2NT.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-3CQECMKR.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-CHIBMEZR.js +0 -8
- package/dist/cli-bundle/focused-chunks/chunk-FF4MNAYB.js +0 -153
- package/dist/cli-bundle/focused-chunks/chunk-GHTN2ILR.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-I4YM2I7S.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-IMDTEN7K.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-JMVA4HKR.js +0 -5
- package/dist/cli-bundle/focused-chunks/chunk-KR5RVXLQ.js +0 -31
- package/dist/cli-bundle/focused-chunks/chunk-KSNJG4KI.js +0 -16
- package/dist/cli-bundle/focused-chunks/chunk-MXYSIQ56.js +0 -18
- package/dist/cli-bundle/focused-chunks/chunk-RIIN5V4N.js +0 -2
- package/dist/cli-bundle/focused-chunks/chunk-RKWSCYYN.js +0 -2
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module sdk/source-traceability
|
|
3
|
+
*
|
|
4
|
+
* Composes linked-file evidence, bounded Git line attribution, item rationale,
|
|
5
|
+
* and typed relationship paths into an explainable source-to-work projection.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b8b3f729-be32-5a77-b08f-f616ee068e0f")}catch(e){}}();
|
|
9
|
+
import { execFile } from "node:child_process";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { assembleWorkspaceRelationshipGraph } from "../graph/assembly.js";
|
|
12
|
+
function runGit(workspaceRoot, args) {
|
|
13
|
+
return new Promise((resolve, reject) => {
|
|
14
|
+
execFile("git", [...args], {
|
|
15
|
+
cwd: workspaceRoot,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
18
|
+
}, (error, stdout) => {
|
|
19
|
+
if (error)
|
|
20
|
+
reject(error);
|
|
21
|
+
else
|
|
22
|
+
resolve(stdout);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function validateLineRange(range) {
|
|
27
|
+
if (range &&
|
|
28
|
+
(!Number.isSafeInteger(range.start) ||
|
|
29
|
+
!Number.isSafeInteger(range.end) ||
|
|
30
|
+
range.start < 1 ||
|
|
31
|
+
range.end < range.start)) {
|
|
32
|
+
throw new RangeError("Source line range must be inclusive positive integers with end >= start.");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** Parse an inclusive `start:end` source-line selector. */
|
|
36
|
+
export function parseSourceLineRange(value) {
|
|
37
|
+
const match = /^(\d+):(\d+)$/u.exec(value.trim());
|
|
38
|
+
const range = match
|
|
39
|
+
? { start: Number(match[1]), end: Number(match[2]) }
|
|
40
|
+
: undefined;
|
|
41
|
+
validateLineRange(range);
|
|
42
|
+
if (!range) {
|
|
43
|
+
throw new RangeError("Source line range must use start:end.");
|
|
44
|
+
}
|
|
45
|
+
return range;
|
|
46
|
+
}
|
|
47
|
+
function parseBlameCommits(output) {
|
|
48
|
+
const lines = new Map();
|
|
49
|
+
for (const row of output.split("\n")) {
|
|
50
|
+
const commit = /^([0-9a-f^]{40,64}) \d+ \d+(?: \d+)?$/u.exec(row)?.[1];
|
|
51
|
+
if (commit)
|
|
52
|
+
lines.set(commit, (lines.get(commit) ?? 0) + 1);
|
|
53
|
+
}
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
56
|
+
function parseCommitItemReferences(output) {
|
|
57
|
+
const fields = output.split("\0");
|
|
58
|
+
const references = new Map();
|
|
59
|
+
for (let index = 0; index + 1 < fields.length; index += 2) {
|
|
60
|
+
const commit = fields[index]?.trim();
|
|
61
|
+
if (!commit)
|
|
62
|
+
continue;
|
|
63
|
+
const ids = [
|
|
64
|
+
...new Set(fields[index + 1]?.match(/\bpm-[a-z0-9][a-z0-9-]{2,63}\b/gu) ?? []),
|
|
65
|
+
].sort((left, right) => left.localeCompare(right));
|
|
66
|
+
references.set(commit, ids);
|
|
67
|
+
}
|
|
68
|
+
return references;
|
|
69
|
+
}
|
|
70
|
+
async function readGitAttribution(params) {
|
|
71
|
+
const relativePath = path.relative(params.workspaceRoot, path.resolve(params.workspaceRoot, params.sourcePath));
|
|
72
|
+
const blame = await runGit(params.workspaceRoot, [
|
|
73
|
+
"blame",
|
|
74
|
+
"--line-porcelain",
|
|
75
|
+
"-L",
|
|
76
|
+
`${params.lineRange.start},${params.lineRange.end}`,
|
|
77
|
+
"--",
|
|
78
|
+
relativePath,
|
|
79
|
+
]);
|
|
80
|
+
const log = await runGit(params.workspaceRoot, [
|
|
81
|
+
"log",
|
|
82
|
+
"-n",
|
|
83
|
+
"256",
|
|
84
|
+
"--format=%H%x00%B%x00",
|
|
85
|
+
"--",
|
|
86
|
+
relativePath,
|
|
87
|
+
]);
|
|
88
|
+
return {
|
|
89
|
+
commitLines: parseBlameCommits(blame),
|
|
90
|
+
commitItems: parseCommitItemReferences(log),
|
|
91
|
+
available: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function expandDecisionPath(params) {
|
|
95
|
+
const tail = params.current.nodes.at(-1);
|
|
96
|
+
const nextDepth = params.current.kinds.length + 1;
|
|
97
|
+
let foundDepth;
|
|
98
|
+
for (const edge of params.graph.incidentEdges(tail)) {
|
|
99
|
+
const outgoing = edge.source === tail;
|
|
100
|
+
const next = outgoing ? edge.target : edge.source;
|
|
101
|
+
if ((params.visitedDepth.get(next) ?? Number.POSITIVE_INFINITY) < nextDepth)
|
|
102
|
+
continue;
|
|
103
|
+
params.visitedDepth.set(next, nextDepth);
|
|
104
|
+
const definition = params.graph.registry().require(edge.kind);
|
|
105
|
+
const nextPath = {
|
|
106
|
+
nodes: [...params.current.nodes, next],
|
|
107
|
+
kinds: [
|
|
108
|
+
...params.current.kinds,
|
|
109
|
+
outgoing ? edge.kind : (definition.inverse ?? edge.kind),
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
if (params.details.get(next)?.type?.toLowerCase() === "decision") {
|
|
113
|
+
foundDepth = nextDepth;
|
|
114
|
+
params.found.push(nextPath);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
params.queue.push(nextPath);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return foundDepth;
|
|
121
|
+
}
|
|
122
|
+
function collectDecisionPaths(params) {
|
|
123
|
+
const queue = [
|
|
124
|
+
{ nodes: [params.itemId], kinds: [] },
|
|
125
|
+
];
|
|
126
|
+
const visitedDepth = new Map([[params.itemId, 0]]);
|
|
127
|
+
const found = [];
|
|
128
|
+
let foundDepth;
|
|
129
|
+
while (queue.length > 0) {
|
|
130
|
+
const current = queue.shift();
|
|
131
|
+
const depth = current.kinds.length;
|
|
132
|
+
if (foundDepth !== undefined && depth >= foundDepth)
|
|
133
|
+
continue;
|
|
134
|
+
if (depth >= params.maxDepth)
|
|
135
|
+
continue;
|
|
136
|
+
foundDepth =
|
|
137
|
+
expandDecisionPath({
|
|
138
|
+
current,
|
|
139
|
+
graph: params.graph,
|
|
140
|
+
details: params.details,
|
|
141
|
+
visitedDepth,
|
|
142
|
+
queue,
|
|
143
|
+
found,
|
|
144
|
+
}) ?? foundDepth;
|
|
145
|
+
}
|
|
146
|
+
found.sort((left, right) => left.nodes.join("\0").localeCompare(right.nodes.join("\0")));
|
|
147
|
+
return found;
|
|
148
|
+
}
|
|
149
|
+
function shortestDecisionPath(itemId, corpus, maxDepth) {
|
|
150
|
+
const assembly = assembleWorkspaceRelationshipGraph(corpus);
|
|
151
|
+
if (!assembly.graph.hasNode(itemId)) {
|
|
152
|
+
return {
|
|
153
|
+
status: "not_found",
|
|
154
|
+
nodes: [],
|
|
155
|
+
kinds: [],
|
|
156
|
+
alternative_decision_ids: [],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
const found = collectDecisionPaths({
|
|
160
|
+
itemId,
|
|
161
|
+
maxDepth,
|
|
162
|
+
graph: assembly.graph,
|
|
163
|
+
details: new Map(assembly.details.map((detail) => [detail.id, detail])),
|
|
164
|
+
});
|
|
165
|
+
const selected = found[0];
|
|
166
|
+
if (!selected) {
|
|
167
|
+
return {
|
|
168
|
+
status: "not_found",
|
|
169
|
+
nodes: [],
|
|
170
|
+
kinds: [],
|
|
171
|
+
alternative_decision_ids: [],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
const decisionIds = [...new Set(found.map((entry) => entry.nodes.at(-1)))];
|
|
175
|
+
return {
|
|
176
|
+
status: decisionIds.length === 1 ? "found" : "ambiguous",
|
|
177
|
+
nodes: selected.nodes,
|
|
178
|
+
kinds: selected.kinds,
|
|
179
|
+
alternative_decision_ids: decisionIds.slice(1),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
async function resolveGitAttribution(params) {
|
|
183
|
+
if (!params.lineRange) {
|
|
184
|
+
return {
|
|
185
|
+
commitLines: new Map(),
|
|
186
|
+
commitItems: new Map(),
|
|
187
|
+
available: true,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
return await readGitAttribution({
|
|
192
|
+
workspaceRoot: params.workspaceRoot,
|
|
193
|
+
sourcePath: params.paths[0],
|
|
194
|
+
lineRange: params.lineRange,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return {
|
|
199
|
+
commitLines: new Map(),
|
|
200
|
+
commitItems: new Map(),
|
|
201
|
+
available: false,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function sourceAmbiguities(params) {
|
|
206
|
+
const ambiguities = [];
|
|
207
|
+
if (params.hasLineRange && !params.attributionAvailable) {
|
|
208
|
+
ambiguities.push("git_attribution_unavailable");
|
|
209
|
+
}
|
|
210
|
+
if (params.hasLineRange && params.contributionLines === 0) {
|
|
211
|
+
ambiguities.push("line_attribution_unmapped");
|
|
212
|
+
}
|
|
213
|
+
if (params.decisionStatus === "ambiguous") {
|
|
214
|
+
ambiguities.push("multiple_governing_decisions");
|
|
215
|
+
}
|
|
216
|
+
if (params.decisionStatus === "not_found") {
|
|
217
|
+
ambiguities.push("governing_decision_not_found");
|
|
218
|
+
}
|
|
219
|
+
return ambiguities;
|
|
220
|
+
}
|
|
221
|
+
function mappedBlamedCommits(attribution) {
|
|
222
|
+
return [...attribution.commitLines.keys()].filter((commit) => (attribution.commitItems.get(commit)?.length ?? 0) > 0);
|
|
223
|
+
}
|
|
224
|
+
/** Enrich reverse linked-file candidates with bounded source rationale. */
|
|
225
|
+
export async function explainSourceTraceability(params) {
|
|
226
|
+
validateLineRange(params.lineRange);
|
|
227
|
+
const decisionDepth = params.decisionDepth ?? 8;
|
|
228
|
+
if (!Number.isSafeInteger(decisionDepth) ||
|
|
229
|
+
decisionDepth < 1 ||
|
|
230
|
+
decisionDepth > 32) {
|
|
231
|
+
throw new RangeError("Decision path depth must be an integer from 1 to 32.");
|
|
232
|
+
}
|
|
233
|
+
const attribution = await resolveGitAttribution(params);
|
|
234
|
+
const mappedCommits = mappedBlamedCommits(attribution);
|
|
235
|
+
const explanations = new Map();
|
|
236
|
+
for (const candidate of params.candidates) {
|
|
237
|
+
const attributedCommits = [...attribution.commitLines.entries()].filter(([commit]) => attribution.commitItems.get(commit)?.includes(candidate.item.id));
|
|
238
|
+
const contributionLines = attributedCommits.reduce((total, [, lines]) => total + lines, 0);
|
|
239
|
+
const decisionPath = shortestDecisionPath(candidate.item.id, params.corpus, decisionDepth);
|
|
240
|
+
const ambiguities = sourceAmbiguities({
|
|
241
|
+
hasLineRange: params.lineRange !== undefined,
|
|
242
|
+
attributionAvailable: attribution.available,
|
|
243
|
+
contributionLines,
|
|
244
|
+
decisionStatus: decisionPath.status,
|
|
245
|
+
});
|
|
246
|
+
explanations.set(candidate.item.id, {
|
|
247
|
+
score: contributionLines * 100 +
|
|
248
|
+
candidate.files.length * 10 +
|
|
249
|
+
Math.max(0, 9 -
|
|
250
|
+
Math.floor((Date.now() - Date.parse(candidate.item.updated_at)) /
|
|
251
|
+
(30 * 24 * 60 * 60 * 1000))),
|
|
252
|
+
contribution_lines: contributionLines,
|
|
253
|
+
evidence: [
|
|
254
|
+
...candidate.files.map((file) => ({
|
|
255
|
+
kind: "linked_file",
|
|
256
|
+
reference: file.path,
|
|
257
|
+
})),
|
|
258
|
+
...attributedCommits.map(([commit, lines]) => ({
|
|
259
|
+
kind: "git_commit",
|
|
260
|
+
reference: commit.slice(0, 12),
|
|
261
|
+
contribution_lines: lines,
|
|
262
|
+
})),
|
|
263
|
+
],
|
|
264
|
+
rationale: {
|
|
265
|
+
value: candidate.item.value?.trim() || null,
|
|
266
|
+
why_now: candidate.item.why_now?.trim() || null,
|
|
267
|
+
outcome: candidate.item.outcome?.trim() || null,
|
|
268
|
+
objective: candidate.item.objective?.trim() || null,
|
|
269
|
+
},
|
|
270
|
+
decision_path: decisionPath,
|
|
271
|
+
ambiguities,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
explanations,
|
|
276
|
+
receipt: {
|
|
277
|
+
line_range: params.lineRange ?? null,
|
|
278
|
+
blamed_commit_count: attribution.commitLines.size,
|
|
279
|
+
mapped_commit_count: mappedCommits.length,
|
|
280
|
+
unmapped_commit_count: attribution.commitLines.size - mappedCommits.length,
|
|
281
|
+
decision_depth: decisionDepth,
|
|
282
|
+
},
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** Internal parser and traversal seams for deterministic boundary tests. */
|
|
286
|
+
export const _testOnlySourceTraceability = {
|
|
287
|
+
mappedBlamedCommits,
|
|
288
|
+
parseBlameCommits,
|
|
289
|
+
parseCommitItemReferences,
|
|
290
|
+
shortestDecisionPath,
|
|
291
|
+
};
|
|
292
|
+
//# sourceMappingURL=source-traceability.js.map
|
|
293
|
+
//# debugId=b8b3f729-be32-5a77-b08f-f616ee068e0f
|
package/dist/types.d.ts
CHANGED
|
@@ -743,7 +743,7 @@ export interface HistoryEntry {
|
|
|
743
743
|
/** Observed agent model identifier, retained in local history. */
|
|
744
744
|
agent_model?: string;
|
|
745
745
|
/** Bounded signal class that supplied `agent_model`. */
|
|
746
|
-
agent_model_source?: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe";
|
|
746
|
+
agent_model_source?: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe" | "inferred";
|
|
747
747
|
/** Privacy-safe fingerprint distinguishing concurrent harness invocations. */
|
|
748
748
|
agent_instance?: string;
|
|
749
749
|
/** Extensible local-only descriptive agent provenance; null means declared but unavailable. */
|
|
@@ -751,7 +751,13 @@ export interface HistoryEntry {
|
|
|
751
751
|
/** Bounded descriptive value observed for this dimension. */
|
|
752
752
|
value: string;
|
|
753
753
|
/** Bounded signal class that supplied the value. */
|
|
754
|
-
source: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe";
|
|
754
|
+
source: "override" | "environment" | "mcp_client" | "argv" | "host" | "session" | "probe" | "inferred";
|
|
755
|
+
/** Confidence attached to a semantic inference. */
|
|
756
|
+
confidence?: "high" | "medium" | "low";
|
|
757
|
+
/** Versioned semantic inference rule. */
|
|
758
|
+
rule_version?: "v2";
|
|
759
|
+
/** Bounded item and lineage references supporting the inference. */
|
|
760
|
+
evidence?: readonly string[];
|
|
755
761
|
} | null>>;
|
|
756
762
|
/** Stable declared episode identity; absent on legacy or undeclared events. */
|
|
757
763
|
agent_episode?: {
|
|
@@ -770,6 +776,8 @@ export interface HistoryEntry {
|
|
|
770
776
|
before_hash: string;
|
|
771
777
|
/** Value that configures or reports after hash for this contract. */
|
|
772
778
|
after_hash: string;
|
|
779
|
+
/** Version of the item canonicalization used by before_hash and after_hash. Absent on legacy streams. */
|
|
780
|
+
item_hash_version?: number;
|
|
773
781
|
/** Human-readable explanation suitable for logs and agent-facing output. */
|
|
774
782
|
message?: string;
|
|
775
783
|
/** Structured audit metadata that does not alter replayed item state. */
|
package/dist/types.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
/** Supported values accepted by the builtin item type contract. */
|
|
7
7
|
|
|
8
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
8
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="8b39bc01-0325-5dc6-8492-585c98608076")}catch(e){}}();
|
|
9
9
|
export const BUILTIN_ITEM_TYPE_VALUES = [
|
|
10
10
|
"Epic",
|
|
11
11
|
"Feature",
|
|
@@ -241,4 +241,4 @@ export const CONTEXT_SECTION_VALUES = [
|
|
|
241
241
|
"tests",
|
|
242
242
|
];
|
|
243
243
|
//# sourceMappingURL=types.js.map
|
|
244
|
-
//# debugId=
|
|
244
|
+
//# debugId=8b39bc01-0325-5dc6-8492-585c98608076
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Project Assurance Primitives
|
|
2
|
+
|
|
3
|
+
Tracker: [pm-2lex4r](../.agents/pm/features/pm-2lex4r.toon), [pm-lyfu7b](../.agents/pm/features/pm-lyfu7b.toon), [pm-wn6wot](../.agents/pm/features/pm-wn6wot.toon), [pm-91xeam](../.agents/pm/features/pm-91xeam.toon), [pm-py7qv2](../.agents/pm/issues/pm-py7qv2.toon), [pm-33mjrw](../.agents/pm/issues/pm-33mjrw.toon), [pm-q6n8sj](../.agents/pm/issues/pm-q6n8sj.toon)
|
|
4
|
+
|
|
5
|
+
## Agent Quick Context
|
|
6
|
+
|
|
7
|
+
Assurance turns project policy into three reusable SDK-owned declarations:
|
|
8
|
+
|
|
9
|
+
1. A **measurement** selects authoritative project data and produces a number or labelled set plus population, contributor, and compute-cost receipts.
|
|
10
|
+
2. An **assertion** applies exactly one explicit bound, scope, lifetime, and enforcement level to a measurement. Required negative controls prove that the bound can both pass and fail.
|
|
11
|
+
3. A **gate** evaluates named assertions at declared lifecycle triggers and returns one structured verdict shared by CLI, SDK, MCP, and CI callers.
|
|
12
|
+
|
|
13
|
+
Declarations live in `.agents/pm/assurance.json`. Every registry mutation and non-dry gate verdict is appended through the verified workspace history stream; never edit either file directly.
|
|
14
|
+
|
|
15
|
+
## Why Assurance Exists
|
|
16
|
+
|
|
17
|
+
Project management is context management. A useful quality gate therefore needs more than a shell exit code: it must preserve what was measured, which population was judged, why a bound exists, who owns it, what changed the result, how expensive the evaluation was, and which immutable tree received the verdict.
|
|
18
|
+
|
|
19
|
+
The assurance SDK keeps those semantics independent from presentation. Commander and MCP only normalize inputs. Package authors and CI hosts can use the same public functions without reconstructing policy in scripts.
|
|
20
|
+
|
|
21
|
+
## Declaration Vocabulary
|
|
22
|
+
|
|
23
|
+
Measurements support these built-in sources:
|
|
24
|
+
|
|
25
|
+
| Source | Purpose |
|
|
26
|
+
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
27
|
+
| `items` | Count authoritative full item records matching status, type, tags, an exact metadata field, or a field `state` of `present`/`missing`. |
|
|
28
|
+
| `dependency_kind` | Count typed relationship edges such as `blocked_by` or `verifies`; accepted aliases and canonical spellings measure the same population. |
|
|
29
|
+
| `graph` | Select a numeric or labelled-set field from a public graph SDK result. |
|
|
30
|
+
| `validate` | Select a validator check status or numeric detail. |
|
|
31
|
+
| `health` | Select a health check status, numeric detail, or labelled set. |
|
|
32
|
+
| `history` | Count immutable events by operation, author, harness, or model. |
|
|
33
|
+
| `links` | Count items with present or missing file, test, or documentation evidence. |
|
|
34
|
+
| `derived` | Combine numeric measurements with deterministic arithmetic and cycle detection. |
|
|
35
|
+
| `provider` | Delegate a measurement to an explicitly supplied host/package resolver. |
|
|
36
|
+
|
|
37
|
+
Each measurement may declare `max_cost`. Evaluation fails closed when the total abstract compute units exceed that ceiling. Every result reports units, scanned items, scanned history rows, provider calls, duration, population size, and contributors.
|
|
38
|
+
|
|
39
|
+
An `items` source with `field` must declare exactly one predicate: `equals` (including an explicit `null`) or `state`. `state: missing` treats an absent property, `null`, an empty string, or an empty array as missing; `state: present` selects the complement. Workspace evaluation loads full item metadata, so `files`, `tests`, and `docs` selectors measure stored evidence rather than a light projection that omitted those collections.
|
|
40
|
+
|
|
41
|
+
Assertions require exactly one polarity:
|
|
42
|
+
|
|
43
|
+
- `ceiling`, `floor`, `equals`, or `zero`
|
|
44
|
+
- `monotone_nondecreasing` or `monotone_nonincreasing`
|
|
45
|
+
- `subset_of` for labelled sets
|
|
46
|
+
|
|
47
|
+
Scopes are `all`, `active`, or `filter`. A filter names another measurement whose contributors define the item population. `lifetime: hold` keeps the guarantee after its owner item becomes terminal. `lifetime: retire` retires it only after owner termination and requires `retire_reason`.
|
|
48
|
+
|
|
49
|
+
Enforcement is `block`, `warn`, or `observe`. Weakening a bound, scope, lifetime, owner, source measurement, or enforcement requires `authorization_decision` naming a terminal Decision item verified by the host. The transport verifies only that explicitly named item; it never treats unrelated workspace Decisions as authorization. Tightening does not require authorization.
|
|
50
|
+
|
|
51
|
+
## CLI Workflow
|
|
52
|
+
|
|
53
|
+
Create a measurement:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pm assurance put measurement active-issues \
|
|
57
|
+
--definition '{"id":"active-issues","source":{"kind":"items","statuses":["open","in_progress"],"types":["Issue"]},"max_cost":5000}' \
|
|
58
|
+
--message "Track the active issue population"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Create an assertion with executable negative controls:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pm assurance put assertion active-issues-ceiling \
|
|
65
|
+
--definition '{"id":"active-issues-ceiling","measurement_id":"active-issues","owner_item_id":"pm-example","scope":{"kind":"active"},"ceiling":25,"lifetime":"hold","enforcement":"block","negative_control":{"cases":[{"observed":25,"expected":"pass"},{"observed":26,"expected":"fail"}]}}'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Create and evaluate a gate:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pm assurance put gate release-readiness \
|
|
72
|
+
--definition '{"id":"release-readiness","assertion_ids":["active-issues-ceiling"],"triggers":["ci","pre-release"]}'
|
|
73
|
+
|
|
74
|
+
pm assurance run release-readiness --trigger ci --dry-run --json
|
|
75
|
+
pm assurance run release-readiness --trigger pre-release --tree "$(git rev-parse HEAD)" --json
|
|
76
|
+
pm assurance verdicts release-readiness --limit 20 --json
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Registry reads and removals use the same nouns:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pm assurance list measurement --json
|
|
83
|
+
pm assurance show assertion active-issues-ceiling --json
|
|
84
|
+
pm assurance remove gate release-readiness
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Referenced measurements and assertions cannot be removed. Remove the consuming gate or assertion first.
|
|
88
|
+
|
|
89
|
+
## SDK and MCP
|
|
90
|
+
|
|
91
|
+
The reusable client exposes the same action grammar:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { PmClient } from "@unbrained/pm-cli/sdk";
|
|
95
|
+
|
|
96
|
+
const pm = new PmClient({ pmRoot: ".agents/pm" });
|
|
97
|
+
|
|
98
|
+
await pm.assurance({
|
|
99
|
+
action: "run",
|
|
100
|
+
id: "release-readiness",
|
|
101
|
+
trigger: "ci",
|
|
102
|
+
dry_run: true,
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
For direct host composition, use `evaluateMeasurement`, `evaluateAssuranceGate`, `createAssuranceWorkspaceContext`, and the audited declaration/verdict helpers exported from `@unbrained/pm-cli/sdk`. A host contributes provider measurements by passing stable resolver ids to `createAssuranceWorkspaceContext`; an absent resolver fails loudly. External adapters must enforce an appropriate timeout. The core evaluator bounds concurrent assertions and expression operands, and workspace history loading uses bounded concurrency; item-only callers can explicitly skip history and Git identity resolution.
|
|
107
|
+
|
|
108
|
+
Generic SDK and MCP dispatch use `action: "assurance"` with `subcommand` set to `list`, `show`, `put`, `remove`, `run`, or `verdicts`. Discover the current machine contract instead of copying parameter lists:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
pm contracts --action assurance --schema-only --json
|
|
112
|
+
pm contracts --command assurance --flags-only --json
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Verdict Contract
|
|
116
|
+
|
|
117
|
+
A gate emits one object containing:
|
|
118
|
+
|
|
119
|
+
- gate id, evaluated tree, trigger, timestamp, and dry-run status;
|
|
120
|
+
- overall `pass`, `warn`, or `block` plus stable exit code;
|
|
121
|
+
- every assertion's measurement, scope, population, observed value, structured bound, signed distance, enforcement, negative-control proof, cost, and contributors;
|
|
122
|
+
- every assertion's `measurement_definition_fingerprint`, a SHA-256 identity for the exact declaration that produced the observation;
|
|
123
|
+
- an aggregate compute receipt.
|
|
124
|
+
|
|
125
|
+
Dry runs never write history. Non-dry verdicts are immutable workspace audit events and remain queryable after ordinary registry changes. Verdict reads return newest entries first and default to a bounded result; use `--limit` to select up to 1,000 matching records. A blocking verdict exits non-zero; warnings and observations remain successful while preserving their failed assertion rows.
|
|
126
|
+
|
|
127
|
+
Verdicts persisted before definition fingerprints were introduced remain readable and are identifiable by the absence of `measurement_definition_fingerprint`. Registry mutations and verdict writes use the same explicit-author, configured-author, and detected-harness precedence as other SDK mutations; they do not manufacture an `unknown` author when a harness identity is available.
|
|
128
|
+
|
|
129
|
+
## Safety and Evolution
|
|
130
|
+
|
|
131
|
+
- Use stable lowercase ids; prose belongs in descriptions and mutation messages.
|
|
132
|
+
- Prefer saved measurements and derived arithmetic over duplicating queries in scripts.
|
|
133
|
+
- Give expensive graph, health, validate, or provider measurements explicit cost ceilings.
|
|
134
|
+
- Keep owner items and authorization Decisions linked into the project graph.
|
|
135
|
+
- Treat negative controls as part of the policy, not test decoration.
|
|
136
|
+
- Use `hold` unless a time-bounded guarantee has an explicit retirement rationale.
|
|
137
|
+
- Use `--dry-run` while authoring or tightening a gate, then persist a verdict against an immutable tree.
|
package/docs/COMMANDS.md
CHANGED
|
@@ -19,7 +19,9 @@ pm contracts --command <command> --flags-only --json
|
|
|
19
19
|
- Use `pm contracts --summary --json` for the cheapest command map, then narrow with command-scoped contracts.
|
|
20
20
|
- Every mutation writes history.
|
|
21
21
|
|
|
22
|
-
Tracked documentation work: [pm-u9d0](../.agents/pm/epics/pm-u9d0.toon)
|
|
22
|
+
Tracked documentation work: [pm-u9d0](../.agents/pm/epics/pm-u9d0.toon),
|
|
23
|
+
[pm-7nqo6b](../.agents/pm/issues/pm-7nqo6b.toon), and
|
|
24
|
+
[pm-j1r8gl](../.agents/pm/issues/pm-j1r8gl.toon).
|
|
23
25
|
|
|
24
26
|
## Command Families
|
|
25
27
|
|
|
@@ -941,17 +943,25 @@ pm history-repair <id> --dry-run
|
|
|
941
943
|
pm history-repair <id> --message "re-anchor legacy drift"
|
|
942
944
|
pm history-repair --all --dry-run
|
|
943
945
|
pm history-repair --all --message "bulk re-anchor drifted streams"
|
|
944
|
-
pm activity
|
|
946
|
+
pm activity # item digest for the last 24 hours
|
|
947
|
+
pm activity --raw --id <id> --limit 50
|
|
945
948
|
pm activity --full --id <id> --limit 50
|
|
946
949
|
pm activity --full --unbounded
|
|
947
950
|
pm restore <id> <timestamp-or-version>
|
|
948
951
|
```
|
|
949
952
|
|
|
950
953
|
History is append-only. Restore appends a new restore event instead of rewriting old history.
|
|
951
|
-
Bare `pm activity` is
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
954
|
+
Bare `pm activity` is an item-centric digest for the last 24 hours, bounded to
|
|
955
|
+
15 most-recently-touched items. Each row joins current `id`, `type`, `status`,
|
|
956
|
+
and title with the matching event count, first/last timestamps, and a bounded
|
|
957
|
+
operation histogram. `activity_summary` states the effective window plus event,
|
|
958
|
+
item, author, and operation counts, so an empty digest is distinguishable from
|
|
959
|
+
a bounded one. Use `--raw` for the legacy compact per-event stream, `--compact`
|
|
960
|
+
as its compatibility spelling, `--full` for events with patch payloads, or
|
|
961
|
+
`--provenance` for patch-free provenance rows. Every mode reports total and
|
|
962
|
+
omitted row counts plus the applied bound. Use `--limit` for a deliberate cap,
|
|
963
|
+
or `--unbounded` for an explicitly unlimited read; the two flags are mutually
|
|
964
|
+
exclusive.
|
|
955
965
|
Workspace-scoped mutations to settings, schema, profiles, init state, and
|
|
956
966
|
extension/package activation are recorded in
|
|
957
967
|
`.agents/pm/history/_workspace.jsonl` using the same patch/hash format.
|
|
@@ -962,10 +972,21 @@ workspace auditing and is created on the first audited singleton mutation.
|
|
|
962
972
|
|
|
963
973
|
`--diff` replays the history chain and emits, per entry, a `changes` array of `{ field, before, after }` field-level value transitions (alongside the `changed_fields` name list) — so you can see exactly what each field changed from and to without comparing snapshots. It is independent of the compact/full projection. `--field <name>` narrows the diff to a single field's transitions (implying `--diff`), answering "when did `<field>` change?" — e.g. `pm history <id> --diff --field status`.
|
|
964
974
|
|
|
965
|
-
`pm stats` reports item and history totals plus
|
|
975
|
+
`pm stats` reports item and history totals plus a screen-sized lifecycle table.
|
|
976
|
+
Every non-empty item type is one row with `total`, `open`, `in_progress`,
|
|
977
|
+
`blocked`, `draft`, `closed`, `canceled`, and `other` counts. Empty registered
|
|
978
|
+
types and statuses are suppressed and their combined count is retained in the
|
|
979
|
+
`omitted_zero_buckets` scalar. The default TOON projection is gated at no more
|
|
980
|
+
than 22 lines on the representative release fixture. Add `--include-empty` to
|
|
981
|
+
restore every zero-filled type and status for schema-governance dashboards.
|
|
982
|
+
Add `--storage` for aggregate history-stream metrics — `total_streams`,
|
|
983
|
+
`total_lines`, `total_bytes`, the top streams by size (`largest_by_bytes`) and
|
|
984
|
+
by depth (`deepest_by_lines`), and the global `oldest_entry`/`newest_entry` — to
|
|
985
|
+
decide when to compact or redact streams and to plan storage:
|
|
966
986
|
|
|
967
987
|
```bash
|
|
968
988
|
pm stats
|
|
989
|
+
pm stats --include-empty
|
|
969
990
|
pm stats --storage --json
|
|
970
991
|
pm stats --metadata-coverage --json
|
|
971
992
|
pm stats --field-utilization --json
|
|
@@ -973,7 +994,7 @@ pm stats --by-assignee --by-priority
|
|
|
973
994
|
pm stats --by-tag --tag-prefix domain: --json
|
|
974
995
|
```
|
|
975
996
|
|
|
976
|
-
For governance dashboards, `--metadata-coverage` adds a `metadata_coverage` block reporting per-field `present`/`applicable`/`percent` for `acceptance_criteria`, `estimated_minutes`, `resolution`, `tags`, and `parent` — overall and `by_type` (resolution coverage is scoped to terminal items, its only applicable population). `--field-utilization` adds a `field_utilization` block reporting `present`/`total`/`percent` for each content field (`notes`, `learnings`, `files`, `docs`, `tests`, `comments`, `deps`, `body`, `linked_command`) across all items, so under-documented content dimensions are visible at a glance and pair naturally with the `--has-*`/`--no-*` list filters for drill-down. `--by-assignee`, `--by-tag`, and `--by-priority` add a `breakdowns` block with lifecycle-bucketed rows (`open`/`in_progress`/`blocked`/`draft`/`closed`/`canceled`/`other` + `total`) per group; blank keys render an explicit `(unassigned)`/`(untagged)` label. `--by-tag` accepts `--tag-prefix` to restrict counting to a tag namespace (for example `domain:`). All of these sections are gated behind their flags so the default `pm stats`
|
|
997
|
+
For governance dashboards, `--metadata-coverage` adds a `metadata_coverage` block reporting per-field `present`/`applicable`/`percent` for `acceptance_criteria`, `estimated_minutes`, `resolution`, `tags`, and `parent` — overall and `by_type` (resolution coverage is scoped to terminal items, its only applicable population). `--field-utilization` adds a `field_utilization` block reporting `present`/`total`/`percent` for each content field (`notes`, `learnings`, `files`, `docs`, `tests`, `comments`, `deps`, `body`, `linked_command`) across all items, so under-documented content dimensions are visible at a glance and pair naturally with the `--has-*`/`--no-*` list filters for drill-down. `--by-assignee`, `--by-tag`, and `--by-priority` add a `breakdowns` block with lifecycle-bucketed rows (`open`/`in_progress`/`blocked`/`draft`/`closed`/`canceled`/`other` + `total`) per group; blank keys render an explicit `(unassigned)`/`(untagged)` label. `--by-tag` accepts `--tag-prefix` to restrict counting to a tag namespace (for example `domain:`). All of these sections are gated behind their flags so the default `pm stats` remains readable at first glance.
|
|
977
998
|
`history-redact` rewrites matching history payloads deterministically, recomputes hash chains, and appends an auditable `history_redact` marker entry when changes are applied. Its result reports only `literal_count`, `regex_count`, `total_count`, and whether the default replacement was selected; literal values, regex source text, and replacement text are never echoed in CLI, SDK, MCP, recovery, profile, or telemetry output. Treat regex text as sensitive input because it can contain the exact material being removed.
|
|
978
999
|
`history-compact` rewrites long streams into a synthetic checkpoint baseline plus a retained tail (`--before` accepts a 1-based version or ISO timestamp), re-anchors hashes, verifies integrity, and appends an auditable `history_compact` marker when applied.
|
|
979
1000
|
`history-compact` bulk mode (mutually exclusive with a positional `<id>`) compacts many streams in one audited pass. Select with `--ids <a,b,c>` (an explicit list — used on its own, not combined with the scan selectors below), or a scan: `--all-over <N>` (every stream with more than N entries) and/or a lifecycle filter `--closed` (terminal items only) or `--all-streams` (every stream). `--closed` and `--all-streams` are mutually exclusive. `--min-entries <N>` (default 3) skips already-compact streams; when `history.compact_policy` is enabled and `--all-over` is omitted, the policy's `max_entries` becomes the default threshold. `--before` is single-id only and is rejected in bulk mode. Each selected stream runs the same single-item compaction; one failing stream never aborts the rest — the result reports `totals` (`streams_considered`/`selected`/`items_compacted`/`items_skipped`/`items_errored`) plus one row per stream (`compacted`/`skipped` with a `skip_reason`/`errored`), and the command exits non-zero only if any stream errored.
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -183,6 +183,13 @@ Crash and error diagnostics are reported to Sentry only when telemetry is enable
|
|
|
183
183
|
|
|
184
184
|
> Sentry is hard-disabled under Vitest (`VITEST` / `VITEST_WORKER_ID`), so these knobs are no-ops inside the test suite.
|
|
185
185
|
|
|
186
|
+
Linked commands launched by `pm test --run` default
|
|
187
|
+
`SENTRY_ENVIRONMENT=test`, even when the parent process is classified as
|
|
188
|
+
production. This keeps sandbox failures out of production diagnostics. An
|
|
189
|
+
explicit run-level or per-test `env_set=SENTRY_ENVIRONMENT=<name>` directive
|
|
190
|
+
still overrides the default when a test intentionally targets another
|
|
191
|
+
environment.
|
|
192
|
+
|
|
186
193
|
## Item Storage Format
|
|
187
194
|
|
|
188
195
|
TOON is the default:
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
# Dependency-kind contract
|
|
2
2
|
|
|
3
|
-
Tracker: [pm-4020c5](../.agents/pm/issues/pm-4020c5.toon), [pm-jkbqt8](../.agents/pm/issues/pm-jkbqt8.toon)
|
|
3
|
+
Tracker: [pm-4020c5](../.agents/pm/issues/pm-4020c5.toon), [pm-jkbqt8](../.agents/pm/issues/pm-jkbqt8.toon), [pm-q6n8sj](../.agents/pm/issues/pm-q6n8sj.toon)
|
|
4
4
|
|
|
5
5
|
Dependency rows have one canonical stored spelling per relationship meaning. Command inputs remain compatibility-friendly: hyphens normalize to underscores and the aliases below are accepted, but `pm create` and `pm update` persist the canonical kind. Existing historical rows are never rewritten implicitly.
|
|
6
6
|
|
|
7
|
-
| Canonical kind | Accepted legacy aliases
|
|
8
|
-
|
|
|
9
|
-
| `blocked_by`
|
|
10
|
-
| `related`
|
|
11
|
-
| `parent`
|
|
12
|
-
| `child`
|
|
7
|
+
| Canonical kind | Accepted legacy aliases |
|
|
8
|
+
| -------------- | -------------------------------------- |
|
|
9
|
+
| `blocked_by` | `depends_on`, `depends-on` |
|
|
10
|
+
| `related` | `related_to`, `related-to` |
|
|
11
|
+
| `parent` | `child_of`, `child-of`, `epic` |
|
|
12
|
+
| `child` | `parent_child`, `parent-child`, `task` |
|
|
13
13
|
|
|
14
14
|
`epic` and `task` are compatibility aliases, not item types embedded in the relationship ontology. New integrations should use `parent` or `child` and express the work classification through the item `type` field.
|
|
15
15
|
|
|
@@ -26,4 +26,6 @@ Readiness, `pm next`, context blocker summaries, `pm list-blocked`, downstream `
|
|
|
26
26
|
|
|
27
27
|
## Legacy observability
|
|
28
28
|
|
|
29
|
-
`pm deps` returns `legacy_alias_counts` for the workspace. `pm graph audit` returns the same field beside
|
|
29
|
+
`pm deps` returns `legacy_alias_counts` for the workspace. `pm graph audit` returns the same field beside canonical `profile.edges_by_kind` counts, `profile.edge_share_by_kind` composition ratios, and the `semantic_edges`/`semantic_edge_share` context-preservation census. The semantic census counts `discovered_from`, `incident_from`, `supersedes`, and `verifies` over all deduplicated directed edges. Empty objects and zero shares are explicit, not omitted. These diagnostics are read-only; terminal history remains untouched until an explicitly governed migration is requested.
|
|
30
|
+
|
|
31
|
+
Assurance `dependency_kind` measurements canonicalize both the declaration and stored row before comparing. A declaration using `related` and one using the accepted `related_to` alias therefore measure the same edge population; alias debt remains separately observable through `legacy_alias_counts`.
|
package/docs/MERGE_SAFETY.md
CHANGED
|
@@ -61,7 +61,7 @@ pm merge install --dry-run --json
|
|
|
61
61
|
| tracker `**/*.jsonl` except the later `history/*.jsonl` override | `pm-relationship` | Covers default and package-owned custom relationship event paths, unions divergent suffixes by `eventId` (timestamp-ordered, ours-first on ties), and renumbers `sequence` consecutively so the strict-sequence store loader accepts the merged stream. |
|
|
62
62
|
| root `settings.json` and nested `**/*.json` | `pm-json` | Recursively merges objects per key. Arrays compose when both branches preserve the base and add distinct entries, so independent extension installs and evaluation additions merge without weakening edit/removal conflict detection. |
|
|
63
63
|
|
|
64
|
-
When both sides change the same item scalar differently, the driver writes the same stable value regardless of which branch Git labels ours or theirs, but exits nonzero. JSON leaf conflicts retain the explicit preferred-side policy. Git keeps either path conflicted so a human or coordinating agent must review the discarded value and explicitly `git add` the resolution.
|
|
64
|
+
When both sides change the same item scalar differently, the driver writes the same stable value regardless of which branch Git labels ours or theirs, but exits nonzero. Item results and receipts expose the caller's `requested_preference`; the per-decision `retained` and `discarded` values or hashes are authoritative because stable value order can retain either side. Readers normalize the legacy receipt key `preferred`, while new receipts no longer emit it. JSON leaf conflicts retain the explicit preferred-side policy. Git keeps either path conflicted so a human or coordinating agent must review the discarded value and explicitly `git add` the resolution. This correction is tracked by [pm-qckpnq](../.agents/pm/issues/pm-qckpnq.toon).
|
|
65
65
|
|
|
66
66
|
The driver result's `guidance` always points unresolved conflicts to `pm merge report`. When a clone-local receipt exists, guidance includes its privacy-safe receipt and item ids for exact correlation; discarded values remain confined to the local receipt and never appear in generic logs or tracker history. Tracked by [pm-fbrz7p](../.agents/pm/issues/pm-fbrz7p.toon).
|
|
67
67
|
|
|
@@ -133,6 +133,8 @@ pm merge reconcile --dry-run --json
|
|
|
133
133
|
|
|
134
134
|
`history-repair` records the reconciliation patch and classifies its changed fields against the final item. Append-only collection unions and deterministic reordering are reported as preserved context without a data-loss warning. Fields whose replayed values are actually removed or replaced remain loud with discarded event authors/operations and recovery guidance. Re-apply any intended losing mutation as a normal `pm update` so it remains explicit and auditable.
|
|
135
135
|
|
|
136
|
+
History events now declare an item-hash epoch. The current epoch preserves linked-test insertion order; unversioned streams are verified against both the legacy sorted-test canonicalization and the order-preserving canonicalization. An unknown explicit epoch is reported as `unsupported_item_hash_version` and repair refuses to guess. This keeps version incompatibility distinct from item corruption and is tracked by [pm-2htk4p](../.agents/pm/issues/pm-2htk4p.toon).
|
|
137
|
+
|
|
136
138
|
## Delete versus modify policy
|
|
137
139
|
|
|
138
140
|
A delete on one branch and an edit on another is not safely resolvable by a generic file driver. The merged workspace must not silently resurrect the item. `storage_integrity` reports the live document plus delete-terminated history as a hard finding. The coordinator chooses one policy explicitly:
|