@planu/cli 4.11.4 → 4.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## [4.11.5] - 2026-07-18
2
+
3
+ ### Bug Fixes
4
+ - fix: close weekly technical debt backlog
5
+
6
+
1
7
  ## [4.11.4] - 2026-07-18
2
8
 
3
9
  ### Bug Fixes
@@ -58,12 +58,19 @@ function buildSddSection(options) {
58
58
  '',
59
59
  '### Architecture Rules',
60
60
  '',
61
+ '- Primary implementation stays in `src/`; Rust is an optional acceleration layer, not the default destination for every feature',
61
62
  '- `types/` → no imports from other layers',
62
63
  '- `engine/` → imports only from `types/`',
63
64
  '- `storage/` → imports only from `types/`',
64
65
  '- `tools/` → imports from `engine/` + `storage/` + `types/`',
65
66
  '- Cross-layer violations → ESLint error',
66
67
  '',
68
+ '### Release Rules',
69
+ '',
70
+ '- `main` is the authoritative release branch',
71
+ '- `develop` and `release` are optional mirrors that must not drift from `main`',
72
+ '- Run `pnpm check` and `pnpm test` before local release publishing',
73
+ '',
67
74
  PLANU_SECTION_END,
68
75
  ].join('\n');
69
76
  }
@@ -1,4 +1,4 @@
1
- import type { OutboundWebhookPayload, OutboundWebhookEventType, OutboundWebhookConfig, OutboundWebhookDelivery } from '../types/index.js';
2
- export declare function dispatchToWebhooks(webhooks: readonly OutboundWebhookConfig[], payload: OutboundWebhookPayload, onDelivery: (delivery: OutboundWebhookDelivery) => Promise<void>): Promise<void>;
1
+ import type { OutboundWebhookPayload, OutboundWebhookEventType, OutboundWebhookConfig, OutboundWebhookDelivery, WebhookDispatchOptions } from '../types/index.js';
2
+ export declare function dispatchToWebhooks(webhooks: readonly OutboundWebhookConfig[], payload: OutboundWebhookPayload, onDelivery: (delivery: OutboundWebhookDelivery) => Promise<void>, options?: WebhookDispatchOptions): Promise<void>;
3
3
  export declare function buildSpecLifecyclePayload(event: OutboundWebhookEventType, specId: string, specTitle: string, status: string, projectPath: string): OutboundWebhookPayload;
4
4
  //# sourceMappingURL=outbound-webhook-dispatcher.d.ts.map
@@ -40,13 +40,15 @@ async function attemptDelivery(url, body, signature) {
40
40
  // Retry logic: 3 attempts with exponential backoff (1s, 5s, 30s)
41
41
  // ---------------------------------------------------------------------------
42
42
  const RETRY_DELAYS_MS = [1_000, 5_000, 30_000];
43
- async function deliverWithRetry(webhook, body, payload, onDelivery) {
43
+ async function deliverWithRetry(webhook, body, payload, onDelivery, options) {
44
44
  let lastResult = null;
45
45
  const signature = signPayload(webhook.secret, body);
46
- for (let attempt = 0; attempt < 3; attempt++) {
46
+ const retryDelays = options.retryDelaysMs ?? RETRY_DELAYS_MS;
47
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
48
+ for (let attempt = 0; attempt < retryDelays.length + 1; attempt++) {
47
49
  if (attempt > 0) {
48
- const delay = RETRY_DELAYS_MS[attempt - 1] ?? 1_000;
49
- await new Promise((resolve) => setTimeout(resolve, delay));
50
+ const delay = retryDelays[attempt - 1] ?? 0;
51
+ await sleep(delay);
50
52
  }
51
53
  lastResult = await attemptDelivery(webhook.url, body, signature);
52
54
  if (lastResult.success) {
@@ -71,12 +73,12 @@ async function deliverWithRetry(webhook, body, payload, onDelivery) {
71
73
  // ---------------------------------------------------------------------------
72
74
  // Core dispatch (pure — no storage deps)
73
75
  // ---------------------------------------------------------------------------
74
- export async function dispatchToWebhooks(webhooks, payload, onDelivery) {
76
+ export async function dispatchToWebhooks(webhooks, payload, onDelivery, options = {}) {
75
77
  if (webhooks.length === 0) {
76
78
  return;
77
79
  }
78
80
  const body = JSON.stringify(payload);
79
- await Promise.allSettled(webhooks.map((webhook) => deliverWithRetry(webhook, body, payload, onDelivery)));
81
+ await Promise.allSettled(webhooks.map((webhook) => deliverWithRetry(webhook, body, payload, onDelivery, options)));
80
82
  }
81
83
  // ---------------------------------------------------------------------------
82
84
  // Helper to build spec lifecycle payloads
@@ -1,4 +1,4 @@
1
- import type { DiscoveredDocsEntry, DocsRegistryHealth } from '../../types/index.js';
1
+ import type { DiscoveredDocsEntry, DocsRegistryHealth, DocsDiscoveryOptions } from '../../types/index.js';
2
2
  /**
3
3
  * Verify that a URL is alive with a HEAD request (timeout 3s).
4
4
  * Returns true if status is 200, 301, or 302.
@@ -9,7 +9,7 @@ export declare function validateRegistryUrl(url: string): Promise<boolean>;
9
9
  * Respects a 1 req/s rate limit per registry via simple delay.
10
10
  * Returns null if not found anywhere.
11
11
  */
12
- export declare function discoverDocsUrl(tech: string): Promise<DiscoveredDocsEntry | null>;
12
+ export declare function discoverDocsUrl(tech: string, options?: DocsDiscoveryOptions): Promise<DiscoveredDocsEntry | null>;
13
13
  /**
14
14
  * Batch validate all entries in the docs registry.
15
15
  * Categorizes each entry as healthy (<=2s), slow (2-5s), or broken (timeout/error).
@@ -113,10 +113,14 @@ export async function validateRegistryUrl(url) {
113
113
  * Respects a 1 req/s rate limit per registry via simple delay.
114
114
  * Returns null if not found anywhere.
115
115
  */
116
- export async function discoverDocsUrl(tech) {
117
- const tryWithDelay = async (fn) => {
116
+ export async function discoverDocsUrl(tech, options = {}) {
117
+ const rateLimitDelayMs = options.rateLimitDelayMs ?? RATE_LIMIT_DELAY_MS;
118
+ const sleepFn = options.sleep ?? sleep;
119
+ const tryWithDelay = async (fn, shouldDelay) => {
118
120
  const result = await fn();
119
- await sleep(RATE_LIMIT_DELAY_MS);
121
+ if (shouldDelay && rateLimitDelayMs > 0) {
122
+ await sleepFn(rateLimitDelayMs);
123
+ }
120
124
  return result;
121
125
  };
122
126
  const registries = [
@@ -124,8 +128,8 @@ export async function discoverDocsUrl(tech) {
124
128
  { source: 'pypi', fn: () => tryPypi(tech) },
125
129
  { source: 'crates', fn: () => tryCrates(tech) },
126
130
  ];
127
- for (const { source, fn } of registries) {
128
- const url = await tryWithDelay(fn);
131
+ for (const [index, { source, fn }] of registries.entries()) {
132
+ const url = await tryWithDelay(fn, index < registries.length - 1);
129
133
  if (url) {
130
134
  const verified = await validateRegistryUrl(url);
131
135
  return {
@@ -285,6 +285,10 @@ export interface DocsRegistryHealth {
285
285
  broken: string[];
286
286
  slow: string[];
287
287
  }
288
+ export interface DocsDiscoveryOptions {
289
+ rateLimitDelayMs?: number;
290
+ sleep?: (ms: number) => Promise<void>;
291
+ }
288
292
  /** Input for validate_docs_registry tool. */
289
293
  export interface ValidateDocsRegistryInput {
290
294
  projectPath: string;
@@ -48,4 +48,8 @@ export interface WebhookAttemptResult {
48
48
  readonly success: boolean;
49
49
  readonly error?: string;
50
50
  }
51
+ export interface WebhookDispatchOptions {
52
+ readonly retryDelaysMs?: readonly number[];
53
+ readonly sleep?: (ms: number) => Promise<void>;
54
+ }
51
55
  //# sourceMappingURL=outbound-webhook.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.11.4",
3
+ "version": "4.11.5",
4
4
  "description": "Planu — MCP Server for Spec Driven Development with native Rust acceleration for hot paths. Cross-platform (Linux/macOS/Windows, x64/arm64, glibc/musl).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -34,14 +34,14 @@
34
34
  "packageName": "@planu/core"
35
35
  },
36
36
  "optionalDependencies": {
37
- "@planu/core-darwin-arm64": "4.11.4",
38
- "@planu/core-darwin-x64": "4.11.4",
39
- "@planu/core-linux-arm64-gnu": "4.11.4",
40
- "@planu/core-linux-arm64-musl": "4.11.4",
41
- "@planu/core-linux-x64-gnu": "4.11.4",
42
- "@planu/core-linux-x64-musl": "4.11.4",
43
- "@planu/core-win32-arm64-msvc": "4.11.4",
44
- "@planu/core-win32-x64-msvc": "4.11.4"
37
+ "@planu/core-darwin-arm64": "4.11.5",
38
+ "@planu/core-darwin-x64": "4.11.5",
39
+ "@planu/core-linux-arm64-gnu": "4.11.5",
40
+ "@planu/core-linux-arm64-musl": "4.11.5",
41
+ "@planu/core-linux-x64-gnu": "4.11.5",
42
+ "@planu/core-linux-x64-musl": "4.11.5",
43
+ "@planu/core-win32-arm64-msvc": "4.11.5",
44
+ "@planu/core-win32-x64-msvc": "4.11.5"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.0.0"
package/planu-native.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dev.planu.native",
3
3
  "displayName": "Planu Native Lightweight Surface",
4
- "version": "4.11.4",
4
+ "version": "4.11.5",
5
5
  "packageName": "@planu/cli",
6
6
  "modes": {
7
7
  "lightweight": {
package/planu-plugin.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "dev.planu.cli",
3
3
  "displayName": "Planu — Spec Driven Development",
4
4
  "description": "Manage software specs, estimations, and autonomous SDD workflows. Language-agnostic MCP server for Claude Code.",
5
- "version": "4.11.4",
5
+ "version": "4.11.5",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": [
8
8
  "npx",