@unbrained/pm-cli 2026.9.23 → 2026.9.26

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.
Files changed (136) hide show
  1. package/.agents/skills/README.md +1 -0
  2. package/.agents/skills/pm-developer/SKILL.md +3 -0
  3. package/.agents/skills/pm-test-audit/LICENSE.txt +21 -0
  4. package/.agents/skills/pm-test-audit/SKILL.md +103 -0
  5. package/.agents/skills/pm-test-audit/references/AUDIT.md +54 -0
  6. package/.agents/skills/pm-test-audit/references/CAMPAIGN.md +47 -0
  7. package/.claude-plugin/marketplace.json +2 -2
  8. package/AGENTS.md +6 -20
  9. package/CHANGELOG.md +49 -1
  10. package/dist/cli/error-guidance.d.ts +9 -0
  11. package/dist/cli/error-guidance.js +1 -1
  12. package/dist/cli/help-content.js +1 -1
  13. package/dist/cli/register-list-query.js +2 -2
  14. package/dist/cli/schema/registration.js +2 -2
  15. package/dist/cli-bundle/bundle-manifest.json +149 -149
  16. package/dist/cli-bundle/chunks/{chunk-NBGSRFZE.js → chunk-4ODHIX7N.js} +2 -2
  17. package/dist/cli-bundle/chunks/{chunk-VO7DHENB.js → chunk-FOTXRNY6.js} +39 -39
  18. package/dist/cli-bundle/chunks/chunk-GRWPZR7F.js +29 -0
  19. package/dist/cli-bundle/chunks/{chunk-E6ZES2X7.js → chunk-HTHTX6E6.js} +6 -6
  20. package/dist/cli-bundle/chunks/chunk-IYN4MJ2C.js +238 -0
  21. package/dist/cli-bundle/chunks/{chunk-6WBR6GMW.js → chunk-MJWKR7YA.js} +63 -62
  22. package/dist/cli-bundle/chunks/{chunk-ZSVINTXT.js → chunk-QQD3VRMZ.js} +2 -2
  23. package/dist/cli-bundle/chunks/{chunk-SXRMM72R.js → chunk-SZQCHJI4.js} +2 -2
  24. package/dist/cli-bundle/chunks/{chunk-ZWSWR2GX.js → chunk-TC74K7SS.js} +2 -2
  25. package/dist/cli-bundle/chunks/{chunk-PFWCAI4X.js → chunk-TYJXMT3Q.js} +2 -2
  26. package/dist/cli-bundle/chunks/{chunk-KOUT5SAA.js → chunk-U55RYT3I.js} +2 -2
  27. package/dist/cli-bundle/chunks/{chunk-WBB5B7G5.js → chunk-VMO4P5MX.js} +2 -2
  28. package/dist/cli-bundle/chunks/{register-list-query-IFPQ4YRD.js → register-list-query-426QTRET.js} +4 -4
  29. package/dist/cli-bundle/chunks/{register-mutation-C3O7OSXW.js → register-mutation-FLI3HHMP.js} +4 -4
  30. package/dist/cli-bundle/chunks/{register-operations-LGO7DBZ7.js → register-operations-TP7ZG4MK.js} +2 -2
  31. package/dist/cli-bundle/chunks/{register-setup-4EW2PPZD.js → register-setup-574SIVQZ.js} +2 -2
  32. package/dist/cli-bundle/focused-chunks/chunk-25BBHWMH.js +20 -0
  33. package/dist/cli-bundle/focused-chunks/{chunk-YE6F5HGF.js → chunk-ABA3KRPY.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/chunk-H3BI4EDO.js +75 -0
  35. package/dist/cli-bundle/focused-chunks/chunk-HNHMK5JI.js +2 -0
  36. package/dist/cli-bundle/focused-chunks/{chunk-S5JYIFFS.js → chunk-HQPTDJLF.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/{chunk-LZOEUNHR.js → chunk-MK5AKKHK.js} +2 -2
  38. package/dist/cli-bundle/focused-chunks/{chunk-6CWR2QFM.js → chunk-NRHTXBTI.js} +2 -2
  39. package/dist/cli-bundle/focused-chunks/{chunk-L554BKFH.js → chunk-NVVB2HJC.js} +2 -2
  40. package/dist/cli-bundle/focused-chunks/{chunk-PIHQNL32.js → chunk-O7MIBEMK.js} +2 -2
  41. package/dist/cli-bundle/focused-chunks/{chunk-BKXYQQDT.js → chunk-OY3VHCTX.js} +2 -2
  42. package/dist/cli-bundle/focused-chunks/{chunk-N2JYYEHM.js → chunk-PMPFLA6X.js} +2 -2
  43. package/dist/cli-bundle/focused-chunks/{chunk-HJA6OHWT.js → chunk-Q355NPLH.js} +28 -28
  44. package/dist/cli-bundle/focused-chunks/{chunk-BQSKFRL2.js → chunk-TLF6N56J.js} +2 -2
  45. package/dist/cli-bundle/focused-chunks/{chunk-QREDJ77O.js → chunk-UIFVIE5F.js} +2 -2
  46. package/dist/cli-bundle/focused-chunks/{chunk-E5AOEXF5.js → chunk-XKH2FC5D.js} +2 -2
  47. package/dist/cli-bundle/focused-chunks/{chunk-FIQYVDLL.js → chunk-XVBWZTU2.js} +11 -11
  48. package/dist/cli-bundle/main.js +22 -22
  49. package/dist/cli-bundle/sdk-authoring.js +1 -1
  50. package/dist/cli-bundle/sdk-contracts.js +1 -1
  51. package/dist/cli-bundle/sdk-core.js +30 -45
  52. package/dist/cli-bundle/sdk-governance.js +1 -1
  53. package/dist/cli-bundle/sdk-graph.js +1 -1
  54. package/dist/cli-bundle/sdk-merge.js +58 -42
  55. package/dist/cli-bundle/sdk-query.js +1 -1
  56. package/dist/cli-bundle/sdk-runtime.js +1 -1
  57. package/dist/cli-bundle/sdk-testing.js +1 -1
  58. package/dist/cli-bundle/sdk.js +9 -10
  59. package/dist/core/policy/workflow-policy-store.js +1 -1
  60. package/dist/core/search/embedding-batches.d.ts +47 -15
  61. package/dist/core/search/embedding-batches.js +1 -1
  62. package/dist/core/search/eval-thresholds.d.ts +14 -0
  63. package/dist/core/search/eval-thresholds.js +2 -0
  64. package/dist/core/search/eval.d.ts +4 -1
  65. package/dist/core/search/eval.js +1 -1
  66. package/dist/core/search/http-client.d.ts +7 -0
  67. package/dist/core/search/http-client.js +1 -1
  68. package/dist/core/shared/errors.d.ts +13 -0
  69. package/dist/core/shared/errors.js +1 -1
  70. package/dist/sdk/completion/bash.js +1 -1
  71. package/dist/sdk/eval.d.ts +19 -17
  72. package/dist/sdk/eval.js +1 -1
  73. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +1 -1
  74. package/dist/sdk/governance/health.d.ts +1 -1
  75. package/dist/sdk/governance/health.js +1 -1
  76. package/dist/sdk/governance/reindex.d.ts +4 -1
  77. package/dist/sdk/governance/reindex.js +2 -2
  78. package/dist/sdk/init-agent-guidance.d.ts +2 -1
  79. package/dist/sdk/init-agent-guidance.js +2 -2
  80. package/dist/sdk/merge/install.js +2 -2
  81. package/dist/sdk/merge/worktree-config.d.ts +9 -0
  82. package/dist/sdk/merge/worktree-config.js +2 -0
  83. package/dist/sdk/public-surface.json +1 -1
  84. package/dist/sdk/query.d.ts +3 -0
  85. package/dist/sdk/query.js +1 -1
  86. package/dist/sdk/runtime/schema-preview.d.ts +2 -0
  87. package/dist/sdk/runtime/schema-preview.js +2 -0
  88. package/dist/sdk/runtime/schema.js +1 -1
  89. package/dist/sdk/runtime-primitives.d.ts +2 -1
  90. package/dist/sdk/runtime-primitives.js +1 -1
  91. package/docs/AGENT_RECOVERY_AND_GUIDANCE.md +49 -0
  92. package/docs/CHATGPT_CODEX_PLUGIN_IMPLEMENTATION.md +2 -0
  93. package/docs/CLAUDE_CODE_PLUGIN.md +18 -7
  94. package/docs/CODEX_PLUGIN.md +5 -12
  95. package/docs/DEFECT_RECURRENCE.md +8 -1
  96. package/docs/DEPENDENCY_KIND_CONTRACT.md +27 -0
  97. package/docs/MERGE_SAFETY.md +28 -5
  98. package/docs/README.md +2 -0
  99. package/docs/RELEASE_RELIABILITY.md +70 -0
  100. package/docs/RELEASING.md +9 -1
  101. package/docs/SEARCH_QUALITY.md +116 -0
  102. package/docs/TESTING.md +17 -0
  103. package/docs/VALIDATION_PORTABILITY.md +20 -11
  104. package/marketplace.json +2 -2
  105. package/package.json +6 -5
  106. package/packages/pm-beads/package.json +1 -1
  107. package/packages/pm-calendar/package.json +1 -1
  108. package/packages/pm-command-kit/package.json +1 -1
  109. package/packages/pm-digital-twin/package.json +1 -1
  110. package/packages/pm-governance-audit/package.json +1 -1
  111. package/packages/pm-guide-shell/package.json +1 -1
  112. package/packages/pm-kanban/package.json +1 -1
  113. package/packages/pm-lifecycle-hooks/package.json +1 -1
  114. package/packages/pm-linked-test-adapters/package.json +1 -1
  115. package/packages/pm-search-advanced/package.json +1 -1
  116. package/packages/pm-templates/package.json +1 -1
  117. package/packages/pm-todos/package.json +1 -1
  118. package/packages/pm-vcs/package.json +1 -1
  119. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  120. package/plugins/pm-claude/README.md +6 -5
  121. package/plugins/pm-claude/hooks/hooks.json +1 -1
  122. package/plugins/pm-claude/hooks/session-start.mjs +9 -6
  123. package/plugins/pm-claude/package.json +9 -0
  124. package/plugins/pm-claude/scripts/plugin-runtime.mjs +84 -0
  125. package/plugins/pm-claude/scripts/pm-mcp-server.mjs +39 -96
  126. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  127. package/plugins/pm-codex/.mcp.json +0 -3
  128. package/plugins/pm-codex/README.md +1 -1
  129. package/plugins/pm-codex/package.json +9 -0
  130. package/plugins/pm-codex/scripts/plugin-runtime.mjs +84 -0
  131. package/plugins/pm-codex/scripts/pm-mcp-server.mjs +38 -99
  132. package/dist/cli-bundle/chunks/chunk-7IABMUB6.js +0 -238
  133. package/dist/cli-bundle/chunks/chunk-OBTHQ6UX.js +0 -29
  134. package/dist/cli-bundle/focused-chunks/chunk-3JAEZ7WI.js +0 -59
  135. package/dist/cli-bundle/focused-chunks/chunk-BULUJEOL.js +0 -20
  136. package/dist/cli-bundle/focused-chunks/chunk-KMNH4G6Y.js +0 -2
@@ -0,0 +1,116 @@
1
+ # Semantic Batching and Retrieval Quality
2
+
3
+ Tracked by [pm-vjgqj5](../.agents/pm/issues/pm-vjgqj5.toon) and
4
+ [pm-p1g43q](../.agents/pm/features/pm-p1g43q.toon).
5
+
6
+ ## Agent Quick Context
7
+
8
+ Use `pm reindex --mode semantic --progress` to refresh stale embeddings and
9
+ `pm eval --queries ./queries.json --k 10` to measure retrieval. A successful
10
+ index refresh proves that vectors were written, not that relevant items rank
11
+ well. Evaluate quality separately.
12
+
13
+ ## Independent Input and Request Limits
14
+
15
+ The built-in scheduler preserves `search.embedding_batch_size` as the maximum
16
+ number of input positions per planned batch. Per-input text truncation still
17
+ uses `search.embedding_corpus_max_characters` or the provider default.
18
+
19
+ Requests additionally have a 256 KiB encoded JSON byte ceiling. This counts
20
+ UTF-8, JSON escaping, array separators, and model metadata; it is independent of
21
+ the per-input character limit. OpenAI's single-string encoding and duplicate
22
+ input elimination can make the actual payload smaller than this conservative
23
+ planning bound. An individual input that cannot fit is refused before any
24
+ request is sent. Empty inputs are also refused rather than silently shifting
25
+ vector positions.
26
+
27
+ SDK hosts may supply `maxRequestBytes` to
28
+ `executeEmbeddingBatchesWithRetry`, exported by
29
+ `@unbrained/pm-cli/sdk/query`. The request ceiling is a transport
30
+ bound, not a model tokenizer or context-window claim. Model input limits remain
31
+ provider-owned. [Ollama's embedding API](https://docs.ollama.com/api/embed)
32
+ accepts arrays of inputs and describes its own context truncation behavior.
33
+
34
+ HTTP 413 and timeout failures split a multi-input batch in half. Both halves
35
+ complete in source order. Other failures use the configured bounded retry
36
+ policy. A singleton failure terminates the operation; failed execution never
37
+ returns a partial vector array. Sequential execution avoids creating a burst
38
+ of concurrent requests against a local embedding model.
39
+
40
+ HTTP status takes precedence over response text: a non-413 error containing
41
+ the word "timeout" does not trigger splitting. For a planned batch of `n`
42
+ positions, binary subdivision creates at most `2n - 1` request nodes, each
43
+ with at most `scanner_max_batch_retries + 1` attempts. SDK hosts accepting
44
+ untrusted workloads should constrain corpus size, retry settings and the
45
+ caller-supplied byte ceiling to their provider budget; this primitive does not
46
+ impose a universal account-level quota.
47
+
48
+ `pm reindex` exposes `semantic.batching` for built-in providers:
49
+
50
+ - `planned_batches` counts batches before adaptation;
51
+ - `requests`, `successful_batches`, `split_batches`, and `retries` describe
52
+ actual execution;
53
+ - `batch_size_histogram` counts successful requests by input positions;
54
+ - `elapsed_ms` and `inputs_per_second` include retry backoff;
55
+ - `provider`, `model`, `max_request_bytes`, and `limit_source` explain the run.
56
+
57
+ These receipts contain no input text, credentials, or endpoint addresses.
58
+ Empty work retains the existing empty result; extension providers do not
59
+ receive a fabricated built-in batching receipt.
60
+
61
+ ## Per-query Quality Floors
62
+
63
+ A golden query may declare a `minimum` object containing any of `ndcg`, `mrr`,
64
+ `precision`, and `recall`. Each value must be a finite number in `[0, 1]`.
65
+ Unknown metric names fail validation so a spelling error cannot remove a gate.
66
+
67
+ ```json
68
+ [
69
+ {
70
+ "query": "snapshot restore durability",
71
+ "relevant_ids": ["pm-snapshot", "pm-restore"],
72
+ "mode": "keyword",
73
+ "minimum": { "recall": 0.5, "mrr": 0.5 }
74
+ }
75
+ ]
76
+ ```
77
+
78
+ Replace the example IDs with judgments from your workspace. `pm eval` compares
79
+ unrounded scores to these floors and reports `violations` on the affected query.
80
+ It exits nonzero if any floor fails, even when no `--fail-under` is supplied or
81
+ the aggregate nDCG passes. Existing query sets without floors retain their
82
+ aggregate-only behavior. The public query SDK exports `parseEvalMetricFloors`
83
+ and `evaluateMetricFloors` for package-owned evaluations.
84
+
85
+ The repository's `quality:retrieval-eval` gate also pins floors by query text
86
+ and retrieval mode. Missing, unexpected or duplicated identities and mismatched
87
+ declared row counts fail. Explicit
88
+ `--update` refreshes never lower a prior floor or discard a missing required
89
+ query. A baseline update is a reviewed policy change; it is not permission to
90
+ change relevance judgments until a score turns green.
91
+
92
+ ## Verification Boundaries
93
+
94
+ Real loopback HTTP tests exercise batching, Unicode payloads, 413 recovery,
95
+ vector ordering, and pre-dispatch refusal. Evaluator tests exercise the case
96
+ where the macro average passes while one query fails. These are transport and
97
+ quality-contract proofs, not measured million-item model throughput, semantic
98
+ ranking improvements, parallel scheduling, or durable partial-run resume.
99
+ Those broader acceptance criteria remain tracked by the linked items.
100
+
101
+ ## Portable Delivery Evidence
102
+
103
+ Workspace-owned custom types used by delivery records must be declared in the
104
+ tracked project schema. An ignored local extension installation does not make
105
+ its types available to a fresh CI checkout. This repository declares `Changeset`
106
+ in `schema/types.json` through `pm schema add-type`, and verifies changelog output
107
+ in a clean checkout with only the published changelog package installed.
108
+
109
+ Schema `--dry-run` applies to rename/remap migrations and workflow-policy
110
+ actions. Other schema operations refuse the flag before writing, through the
111
+ shared SDK guard used by CLI and SDK/MCP dispatch. For example,
112
+ `pm schema rename-type Spike --to Experiment --dry-run` previews a migration;
113
+ `pm schema add-type Spike --dry-run` is refused. Adding a type is an explicit
114
+ write operation. Tracked by [pm-s74dca](../.agents/pm/issues/pm-s74dca.toon).
115
+ SDK hosts can reuse `assertSchemaPreviewSupported(subcommand, dryRun)` from
116
+ `@unbrained/pm-cli/sdk` when dispatching built-in schema operations.
package/docs/TESTING.md CHANGED
@@ -88,6 +88,23 @@ so PR-local CodeFactor maintainability annotations fail locally before commit or
88
88
  push. Existing legacy high-complexity test fixtures are tracked separately and
89
89
  must not be used as precedent for new changed production/script code.
90
90
 
91
+ The same required static gate validates an explicit seven-day Dependabot
92
+ cooldown for every updater and requires both npm and GitHub Actions entries.
93
+ Missing values, semver overrides outside 7–90 days, invalid YAML, removed
94
+ updaters, and include/exclude bypasses fail the gate. The maximum follows
95
+ [GitHub's cooldown limits](https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/optimizing-pr-creation-version-updates).
96
+ Security updates retain GitHub's separate security-update behavior.
97
+ The negative controls are tracked by [pm-gkut](../.agents/pm/issues/pm-gkut.toon)
98
+ and [pm-dmo5](../.agents/pm/issues/pm-dmo5.toon).
99
+
100
+ Release synchronization and commit staging share one distribution manifest
101
+ inventory. The [pm-t4prek regression](../tests/unit/scripts/release/version-manifests.spec.ts)
102
+ bumps versions in a temporary Git repository and reads the resulting commit to
103
+ verify workspace versions and exact plugin runtime pins. The pipeline checks
104
+ version consistency immediately before staging, including required string
105
+ versions and runtime pins in both plugin package manifests; tests of a prepared working
106
+ tree alone cannot establish that the release commit contains those changes.
107
+
91
108
  `lint:duplicates` runs three zero-clone profiles: the broad repository profile,
92
109
  the production cross-format profile, and a supplemental long-table profile at
93
110
  28 lines / 80 tokens. The last profile detects the historical GH-508 normalizer
@@ -1,7 +1,8 @@
1
1
  # Completion portability and bounded replication checks
2
2
 
3
- Tracked by [pm-t6jl1f](../.agents/pm/issues/pm-t6jl1f.toon) and
4
- [pm-26y269](../.agents/pm/tasks/pm-26y269.toon).
3
+ Tracked by [pm-t6jl1f](../.agents/pm/issues/pm-t6jl1f.toon),
4
+ [pm-26y269](../.agents/pm/tasks/pm-26y269.toon), and
5
+ [pm-eswzn9](../.agents/pm/tasks/pm-eswzn9.toon).
5
6
 
6
7
  ## Bash completion data boundary
7
8
 
@@ -13,10 +14,15 @@ elements to `COMPREPLY`. Candidates containing shell metacharacters or Unicode
13
14
  use portable single-quote syntax, including embedded quote escaping, so accepting
14
15
  a candidate and pressing Enter also preserves its literal argument value.
15
16
 
16
- Already quoted or backslash-escaped prefixes return no suggestions before optional
17
- completion initialization can remove their quoting. This conservative boundary
18
- avoids inserting an unquoted-context escape inside a different shell quoting
19
- context. Normal unquoted prefixes retain literal matching.
17
+ The matcher decodes the current Readline word without evaluation. It recognizes
18
+ open single quotes, open double quotes, and backslash-escaped prefixes. Each
19
+ completion is encoded for its insertion context: apostrophes are split safely
20
+ inside single quotes; dollar signs, backticks, backslashes, and double quotes
21
+ are escaped inside double quotes; exclamation marks use a single-quoted segment
22
+ so history expansion cannot change the accepted bytes. Unquoted prefixes keep
23
+ the existing literal single-quote encoding for unsafe candidates. Mixed quoted
24
+ segments whose replacement span is ambiguous, and prefixes containing typed
25
+ backticks, still return no suggestions.
20
26
 
21
27
  The matcher never evaluates choices as shell programs. Dollar signs, backticks,
22
28
  quotes, backslashes, and wildcard characters remain literal. Unicode survives
@@ -37,9 +43,10 @@ and cache policy retain their existing contracts.
37
43
  `compgen -W` cannot safely serve as the data transport here: it performs another
38
44
  expansion pass, and Bash 3.2 does not round-trip the ANSI-C quoting emitted by
39
45
  `printf %q` for Unicode. Unquoted command-substitution arrays can additionally
40
- expand literal wildcard candidates into filenames. The regression executes the
41
- generated function and parses its accepted words with the real Bash parser,
42
- rather than inferring safety from source substrings.
46
+ expand literal wildcard candidates into filenames. The regression checks the
47
+ generated function and uses an interactive pseudo terminal to press Tab and
48
+ Enter in each supported insertion context. It checks the accepted argument
49
+ bytes and verifies that a command-substitution sentinel was not created.
43
50
 
44
51
  Run the focused native regression after building:
45
52
 
@@ -51,8 +58,10 @@ To test another installed Bash version, set `PM_COMPLETION_TEST_BASH` to its
51
58
  executable path. This is a test harness input, not a CLI configuration setting.
52
59
  The existing macOS runtime-smoke job pins `/bin/bash` so PATH cannot silently
53
60
  substitute a newer Homebrew Bash. It runs this regression before merge; Linux
54
- coverage runs the same matrix. Native Fish and Zsh acceptance remains in the
55
- required smoke gate.
61
+ coverage runs the same matrix. The macOS test drives Bash through a Python 3
62
+ pseudo terminal because BSD `script` requires terminal stdin; Linux uses
63
+ `script`. Set `PM_COMPLETION_TEST_PTY=python` to exercise the macOS driver on
64
+ Linux. Native Fish and Zsh acceptance remains in the required smoke gate.
56
65
 
57
66
  ## Replication diff collection
58
67
 
package/marketplace.json CHANGED
@@ -6,14 +6,14 @@
6
6
  },
7
7
  "metadata": {
8
8
  "description": "Official marketplace for pm CLI — native git-based project management for Claude Code and AI coding agents.",
9
- "version": "2026.9.23"
9
+ "version": "2026.9.26"
10
10
  },
11
11
  "plugins": [
12
12
  {
13
13
  "name": "pm-claude",
14
14
  "source": "./plugins/pm-claude",
15
15
  "description": "Native pm CLI integration for Claude Code — 28 MCP tools, 5 workflow skills, 14 slash commands, 4 subagents, hybrid TUI task tracking, session context injection, and coordination subagents for git-based project management without leaving Claude Code.",
16
- "version": "2026.9.23",
16
+ "version": "2026.9.26",
17
17
  "author": {
18
18
  "name": "unbrained",
19
19
  "url": "https://github.com/unbraind/pm-cli"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-cli",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "description": "Git-native project management CLI for humans and agents.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.10.0",
@@ -123,7 +123,7 @@
123
123
  "lint:complexity:baseline": "eslint . --suppress-rule complexity --suppress-rule sonarjs/cognitive-complexity",
124
124
  "lint:duplicates": "jscpd --config .jscpd.json && jscpd --config .jscpd.source.json && jscpd --config .jscpd.tables.json",
125
125
  "lint:codefactor": "pnpm quality:static",
126
- "quality:static": "pnpm quality:docstrings && pnpm quality:exports && pnpm lint:eslint && pnpm lint:duplicates && pnpm build && node scripts/contracts-snapshot.mjs --check && node scripts/generate-agent-capability-surfaces.mjs --check && node scripts/generate-error-code-catalog.mjs --check && node scripts/release/repository-assurance.mjs repository-static-quality --trigger ci --json && node scripts/release/audit-package-boundary.mjs && node scripts/release/package-sdk-contract-parity.mjs && node scripts/release/surface-replication-gate.mjs && node scripts/release/command-grammar-gate.mjs && node scripts/release/flag-invocation-parity.mjs && node scripts/release/flag-lexicon-gate.mjs && node scripts/release/refusal-closure-gate.mjs && node scripts/release/agent-task-token-gate.mjs && pnpm quality:mcp-deprecations && node dist/cli.js assurance run tracker-context-quality --trigger ci --dry-run --json --output-budget unbounded && node scripts/release/gate-registry.mjs && node scripts/sdk-surface-snapshot.mjs --check && node scripts/bench/sdk-entrypoint-costs.mjs --check && node scripts/bench/cli-transport-floor.mjs --check && node dist/cli.js assurance run graph-composition --trigger ci --dry-run --json --output-budget unbounded && node dist/cli.js assurance run record-integrity --trigger ci --dry-run --json --output-budget unbounded && pnpm quality:mutation -- --prebuilt",
126
+ "quality:static": "pnpm quality:docstrings && pnpm quality:exports && pnpm lint:eslint && pnpm lint:duplicates && node scripts/check-workflow-permissions.mjs && node scripts/check-dependency-cooldown.mjs && node scripts/gen-plugin-mcp-wrappers.mjs --check && pnpm build && node scripts/contracts-snapshot.mjs --check && node scripts/generate-agent-capability-surfaces.mjs --check && node scripts/generate-error-code-catalog.mjs --check && node scripts/release/repository-assurance.mjs repository-static-quality --trigger ci --json && node scripts/release/audit-package-boundary.mjs && node scripts/release/package-sdk-contract-parity.mjs && node scripts/release/surface-replication-gate.mjs && node scripts/release/command-grammar-gate.mjs && node scripts/release/flag-invocation-parity.mjs && node scripts/release/flag-lexicon-gate.mjs && node scripts/release/refusal-closure-gate.mjs && node scripts/release/agent-task-token-gate.mjs && pnpm quality:mcp-deprecations && node dist/cli.js assurance run tracker-context-quality --trigger ci --dry-run --json --output-budget unbounded && node scripts/release/gate-registry.mjs && node scripts/sdk-surface-snapshot.mjs --check && node scripts/bench/sdk-entrypoint-costs.mjs --check && node scripts/bench/cli-transport-floor.mjs --check && node dist/cli.js assurance run graph-composition --trigger ci --dry-run --json --output-budget unbounded && node dist/cli.js assurance run record-integrity --trigger ci --dry-run --json --output-budget unbounded && pnpm quality:mutation -- --prebuilt",
127
127
  "quality:exports": "knip --no-config-hints && node scripts/run-tests.mjs test -- tests/unit/scripts/release/dead-exports.spec.ts",
128
128
  "quality:command-grammar": "pnpm build && node scripts/release/command-grammar-gate.mjs && node scripts/release/flag-invocation-parity.mjs && node scripts/release/flag-lexicon-gate.mjs",
129
129
  "quality:recovery-closure": "pnpm build && node scripts/release/refusal-closure-gate.mjs",
@@ -167,6 +167,7 @@
167
167
  "smoke:external-packages": "node scripts/smoke-external-packages.mjs",
168
168
  "smoke:codex-plugin": "node scripts/smoke-codex-plugin-mcp.mjs",
169
169
  "smoke:claude-plugin": "node scripts/smoke-claude-plugin.mjs",
170
+ "smoke:plugin-cache": "node tests/acceptance/plugin-cache-smoke.mjs",
170
171
  "version:check": "node scripts/release-version.mjs check && node scripts/sync-versions.mjs check",
171
172
  "version:next": "node scripts/release-version.mjs next",
172
173
  "version:sync": "node scripts/sync-versions.mjs apply",
@@ -211,7 +212,7 @@
211
212
  "node": ">=22.18.0"
212
213
  },
213
214
  "dependencies": {
214
- "@sentry/node": "10.75.0",
215
+ "@sentry/node": "10.75.1",
215
216
  "@toon-format/toon": "^4.1.1",
216
217
  "@types/node": ">=22",
217
218
  "commander": "^15.0.0",
@@ -238,13 +239,13 @@
238
239
  "fast-check": "^4.10.2",
239
240
  "github-slugger": "2.0.0",
240
241
  "greptile": "^3.5.3",
241
- "jscpd": "^5.3.0",
242
+ "jscpd": "^5.3.1",
242
243
  "knip": "^6.37.0",
243
244
  "marked": "18.0.13",
244
245
  "parse5": "8.0.1",
245
246
  "tsx": "^4.23.15",
246
247
  "typescript": "^6.0.3",
247
- "typescript-eslint": "^8.70.0",
248
+ "typescript-eslint": "^8.70.1",
248
249
  "vitest": "^4.1.11"
249
250
  }
250
251
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-beads",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for importing Beads JSONL records.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-calendar",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for calendar and agenda views.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-command-kit",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm exemplar package for the commands capability: registerCommand, registerFlags, and registerParser.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-digital-twin",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Beyond-PM exemplar: a temporal production-facility digital twin built entirely on public pm SDK and extension primitives.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-governance-audit",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for governance audit and normalization workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-guide-shell",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for guide and shell completion workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-kanban",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm archetype package: a complete Kanban continuous-flow profile built on public SDK primitives.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-lifecycle-hooks",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package demonstrating safe lifecycle hook registration.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-linked-test-adapters",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for optional linked-test run management adapters and reporters.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-search-advanced",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for semantic, hybrid, and reindex search workflows.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-templates",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for reusable create templates.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-todos",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "First-party pm package for Todo markdown import and export.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unbrained/pm-vcs-sdk-exemplar",
3
- "version": "2026.9.23",
3
+ "version": "2026.9.26",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Beyond-PM exemplar: a VCS-style changeset workflow built entirely on public pm SDK and extension primitives.",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pm-claude",
3
3
  "description": "Native pm CLI integration for Claude Code — 28 MCP tools, 5 workflow skills, 14 slash commands, 4 subagents (coordinator, delivery-chain, triage, verification), hybrid TUI task tracking (pm as persistent store + Claude Code task panel as live view), session context injection, and full git-based project management without leaving Claude Code.",
4
- "version": "2026.9.23",
4
+ "version": "2026.9.26",
5
5
  "author": {
6
6
  "name": "unbrained",
7
7
  "url": "https://github.com/unbraind/pm-cli"
@@ -11,7 +11,7 @@ Native pm CLI integration for Claude Code. Use pm project management tools direc
11
11
  | **14 slash commands** | Full lifecycle coverage — status, start, close, triage, audit, search, new, list, calendar, developer, planner, release, workflow, init |
12
12
  | **4 subagents** | `pm-coordinator` (batch/multi-item), `pm-triage-agent` (duplicate-safe item creation), `pm-verification-agent` (evidence + close readiness), and `pm-delivery-chain` (orchestrated delivery) |
13
13
  | **Hybrid TUI tracking** | pm items sync to Claude Code's task panel — pm is the persistent store, the task panel is the live session view |
14
- | **Session hook** | Injects active pm item summary at session start when pm is initialized (uses native modules, no CLI required) |
14
+ | **Session hook** | Injects active pm item summary at session start using the same pinned pm-cli runtime as the MCP server |
15
15
 
16
16
  ## Installation
17
17
 
@@ -192,8 +192,9 @@ All skills and commands implement this pattern for every claimed item:
192
192
 
193
193
  ## Session Context Injection
194
194
 
195
- At session start, the hook runs the published `pm` CLI without requiring a global install:
196
- - Uses `npx --yes --package=@unbrained/pm-cli@latest pm context --limit 5 --json`
195
+ At session start, the hook runs the plugin's exact-version `pm` CLI without requiring a global install:
196
+ - Uses the version pinned in `plugins/pm-claude/package.json` and persisted in the plugin data directory
197
+ - Reuses the installed runtime offline on later sessions
197
198
  - Injects a compact summary of in-progress/open/blocked items
198
199
 
199
200
  Example output:
@@ -215,8 +216,8 @@ Use pm_context tool or /pm-status for full details.
215
216
 
216
217
  ## Requirements
217
218
 
218
- - Node.js ≥ 20
219
- - pm CLI resolved automatically via local dist (in repo) or `npx --yes @unbrained/pm-cli@latest` (no global install needed)
219
+ - Node.js ≥ 22.18.0
220
+ - pm CLI resolved automatically from the plugin's pinned package installation (no global install needed)
220
221
  - Project initialized with `pm init` (or use `/pm-init`)
221
222
 
222
223
  ## Links
@@ -7,7 +7,7 @@
7
7
  {
8
8
  "type": "command",
9
9
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.mjs\"",
10
- "timeout": 8,
10
+ "timeout": 130,
11
11
  "async": true
12
12
  }
13
13
  ]
@@ -3,12 +3,13 @@
3
3
  * pm-cli Claude Code session-start hook.
4
4
  *
5
5
  * Injects a brief pm context summary into the session when pm is initialized
6
- * in the current workspace. Uses the published pm CLI through npx without
6
+ * in the current workspace. Uses the plugin's exact-version runtime without
7
7
  * requiring a global install. Exits silently if pm is not set up.
8
8
  */
9
9
  import { existsSync } from "node:fs";
10
10
  import { join } from "node:path";
11
- import { execSync } from "node:child_process";
11
+ import { execFileSync } from "node:child_process";
12
+ import { resolvePluginRuntime } from "../scripts/plugin-runtime.mjs";
12
13
 
13
14
  const workspace = process.cwd();
14
15
  const pmSettingsPath = join(workspace, ".agents", "pm", "settings.json");
@@ -40,10 +41,12 @@ function formatSummary(ctx) {
40
41
  );
41
42
  }
42
43
 
43
- function tryNpxContext() {
44
+ async function readContext() {
44
45
  try {
45
- const raw = execSync(
46
- "npx -y --package=@unbrained/pm-cli@latest pm context --limit 5 --json",
46
+ const runtime = await resolvePluginRuntime();
47
+ const raw = execFileSync(
48
+ process.execPath,
49
+ [runtime.cli, "context", "--limit", "5", "--json"],
47
50
  {
48
51
  cwd: workspace,
49
52
  encoding: "utf-8",
@@ -58,7 +61,7 @@ function tryNpxContext() {
58
61
  }
59
62
 
60
63
  try {
61
- const ctx = tryNpxContext();
64
+ const ctx = await readContext();
62
65
  if (!ctx) process.exit(0);
63
66
 
64
67
  const message = formatSummary(ctx);
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "pm-claude-runtime",
3
+ "private": true,
4
+ "type": "module",
5
+ "version": "2026.9.26",
6
+ "dependencies": {
7
+ "@unbrained/pm-cli": "2026.9.26"
8
+ }
9
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Resolve the exact pm-cli runtime declared by a copied agent plugin.
3
+ * This file is copied into each plugin by gen-plugin-mcp-wrappers.mjs so a
4
+ * marketplace cache never needs to reach back into the repository checkout.
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import { access, mkdir, mkdtemp, readFile, rename, rm } from "node:fs/promises";
8
+ import os from "node:os";
9
+ import path from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const pluginRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
13
+ const packageName = "@unbrained/pm-cli";
14
+ const versionPattern = /^[1-9]\d{3}\.[1-9]\d*\.[1-9]\d*(?:-[1-9]\d*)?$/;
15
+
16
+ /** Require both the declared version and the executable shipped by that package. */
17
+ async function installedRuntime(root, version) {
18
+ const packageRoot = path.join(root, "node_modules", "@unbrained", "pm-cli");
19
+ try {
20
+ const manifest = JSON.parse(await readFile(path.join(packageRoot, "package.json"), "utf8"));
21
+ if (manifest.version !== version) return null;
22
+ const server = path.join(packageRoot, "dist", "mcp", "server.js");
23
+ const cli = path.join(packageRoot, "dist", "cli.js");
24
+ await access(server);
25
+ await access(cli);
26
+ return { server, cli, version };
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ /** Install into a private staging directory and publish only a complete tree. */
33
+ async function installRuntime(dataRoot, version, installer) {
34
+ await mkdir(dataRoot, { recursive: true });
35
+ const finalRoot = path.join(dataRoot, `v${version}`);
36
+ const existing = await installedRuntime(finalRoot, version);
37
+ if (existing) return existing;
38
+ const stagingRoot = await mkdtemp(path.join(dataRoot, `.install-${version}-`));
39
+ try {
40
+ const args = ["install", "--prefix", stagingRoot, "--no-save", "--ignore-scripts", "--no-audit", "--no-fund", "--package-lock=false", `${packageName}@${version}`];
41
+ const windows = process.platform === "win32";
42
+ // The command text contains only fixed tokens and a validated version.
43
+ // Put the caller-selected path in the child environment so cmd.exe never
44
+ // parses path metacharacters as command syntax.
45
+ const command = windows ? process.env.ComSpec || "cmd.exe" : "npm";
46
+ const commandArgs = windows
47
+ ? ["/d", "/v:off", "/s", "/c", `"npm.cmd ${args.map((arg) => arg === stagingRoot ? '"%PM_PLUGIN_STAGING_ROOT%"' : arg).join(" ")}"`]
48
+ : args;
49
+ const result = installer(command, commandArgs, {
50
+ encoding: "utf8", timeout: 120000, stdio: ["ignore", "pipe", "pipe"],
51
+ ...(windows ? { windowsVerbatimArguments: true, env: { ...process.env, PM_PLUGIN_STAGING_ROOT: stagingRoot } } : {}),
52
+ });
53
+ if (result.error || result.status !== 0 || !(await installedRuntime(stagingRoot, version))) {
54
+ throw new Error(`Cannot install ${packageName}@${version}: ${result.error?.message || result.stderr?.trim() || "incomplete package"}`);
55
+ }
56
+ try {
57
+ await rename(stagingRoot, finalRoot);
58
+ } catch (error) {
59
+ if (!(await installedRuntime(finalRoot, version))) {
60
+ throw new Error(`Plugin runtime at ${finalRoot} is incomplete; remove that directory and retry.`, { cause: error });
61
+ }
62
+ }
63
+ const runtime = await installedRuntime(finalRoot, version);
64
+ if (!runtime) throw new Error(`Plugin runtime at ${finalRoot} is incomplete; remove that directory and retry.`);
65
+ return runtime;
66
+ } finally {
67
+ await rm(stagingRoot, { recursive: true, force: true });
68
+ }
69
+ }
70
+
71
+ /** Resolve a copied plugin's runtime without ever consulting npm's latest tag. */
72
+ export async function resolvePluginRuntime(options = {}) {
73
+ const root = options.pluginRoot ?? pluginRoot;
74
+ const plugin = JSON.parse(await readFile(path.join(root, "package.json"), "utf8"));
75
+ const version = plugin.dependencies?.[packageName];
76
+ if (typeof version !== "string" || !versionPattern.test(version) || plugin.version !== version) {
77
+ throw new Error(`Plugin ${plugin.name ?? root} needs an exact pm-cli dependency matching its version.`);
78
+ }
79
+ const bundled = await installedRuntime(root, version);
80
+ if (bundled) return bundled;
81
+ const dataRoot = process.env.PLUGIN_DATA || process.env.CLAUDE_PLUGIN_DATA ||
82
+ path.join(os.homedir(), ".cache", "pm-cli", "plugins", path.basename(root));
83
+ return installRuntime(dataRoot, version, options.installer ?? spawnSync);
84
+ }