@planu/cli 4.11.9 → 4.12.2

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 (91) hide show
  1. package/CHANGELOG.md +129 -22
  2. package/dist/engine/abortable-process-runner.d.ts +8 -0
  3. package/dist/engine/abortable-process-runner.js +107 -0
  4. package/dist/engine/cascade-hooks/core/append-releases.d.ts +1 -0
  5. package/dist/engine/cascade-hooks/core/append-releases.js +51 -14
  6. package/dist/engine/cascade-hooks/hooks/orchestration-plan.hook.js +16 -21
  7. package/dist/engine/ci-generator/yaml-builder.js +5 -1
  8. package/dist/engine/context-md-generator.js +3 -1
  9. package/dist/engine/criterion-identity.d.ts +6 -0
  10. package/dist/engine/criterion-identity.js +63 -0
  11. package/dist/engine/evidence-gates/lifecycle-gate.js +22 -44
  12. package/dist/engine/evidence-index/index-builder.js +7 -19
  13. package/dist/engine/frontmatter-parser.d.ts +5 -0
  14. package/dist/engine/frontmatter-parser.js +24 -0
  15. package/dist/engine/project-command-runtime.d.ts +5 -0
  16. package/dist/{tools/validate-runtime.js → engine/project-command-runtime.js} +8 -21
  17. package/dist/engine/project-graph/builder.js +1 -35
  18. package/dist/engine/project-graph/cache.js +37 -1
  19. package/dist/engine/qa-gate.d.ts +4 -1
  20. package/dist/engine/qa-gate.js +72 -59
  21. package/dist/engine/safety/cross-process-lock.d.ts +1 -6
  22. package/dist/engine/safety/cross-process-lock.js +18 -16
  23. package/dist/engine/safety/lock-scavenger.d.ts +9 -0
  24. package/dist/engine/safety/lock-scavenger.js +114 -0
  25. package/dist/engine/session-context-generator.js +24 -2
  26. package/dist/engine/session-safeguard/learnings-buffer.js +7 -2
  27. package/dist/engine/session-state/writer.js +15 -8
  28. package/dist/engine/skill-registry/installer.js +1 -1
  29. package/dist/engine/spec-format/acceptance-criteria.js +8 -26
  30. package/dist/engine/spec-grounding/contract.d.ts +3 -1
  31. package/dist/engine/spec-grounding/contract.js +39 -14
  32. package/dist/engine/universal-rules/installer.js +2 -2
  33. package/dist/engine/validation-evidence-digest.d.ts +4 -0
  34. package/dist/engine/validation-evidence-digest.js +83 -0
  35. package/dist/engine/validation-evidence-ledger.d.ts +30 -0
  36. package/dist/engine/validation-evidence-ledger.js +274 -0
  37. package/dist/engine/validation-impact-planner.d.ts +4 -0
  38. package/dist/engine/validation-impact-planner.js +71 -0
  39. package/dist/engine/validator/spec-compliance-runner.d.ts +1 -1
  40. package/dist/engine/validator/spec-compliance-runner.js +37 -40
  41. package/dist/engine/validator/validation-report-writer.d.ts +1 -0
  42. package/dist/engine/validator/validation-report-writer.js +1 -1
  43. package/dist/engine/validator.js +7 -11
  44. package/dist/storage/convention-baseline.js +5 -0
  45. package/dist/storage/session-state-store.js +13 -6
  46. package/dist/storage/spec-store.d.ts +5 -0
  47. package/dist/storage/spec-store.js +7 -0
  48. package/dist/tools/git/git-helpers.d.ts +5 -1
  49. package/dist/tools/git/git-helpers.js +3 -2
  50. package/dist/tools/init-project/conventions-writer.js +1 -1
  51. package/dist/tools/init-project/scaffold-writer.js +1 -1
  52. package/dist/tools/safe-handler.d.ts +5 -1
  53. package/dist/tools/safe-handler.js +73 -12
  54. package/dist/tools/status-handler.d.ts +1 -1
  55. package/dist/tools/status-handler.js +27 -66
  56. package/dist/tools/update-status/dod-gates.js +7 -31
  57. package/dist/tools/update-status/evidence-gate.js +2 -4
  58. package/dist/tools/update-status/force-done-audit.d.ts +2 -0
  59. package/dist/tools/update-status/force-done-audit.js +23 -0
  60. package/dist/tools/update-status/qa-gate.d.ts +1 -0
  61. package/dist/tools/update-status/qa-gate.js +7 -51
  62. package/dist/tools/update-status-convention-gate.js +132 -97
  63. package/dist/tools/validate-lint.d.ts +1 -1
  64. package/dist/tools/validate-lint.js +78 -165
  65. package/dist/tools/validate.js +3 -1
  66. package/dist/transports/http-transport.js +22 -8
  67. package/dist/transports/mcp-types.d.ts +2 -2
  68. package/dist/transports/transport-factory.js +110 -12
  69. package/dist/types/abortable-process.d.ts +26 -0
  70. package/dist/types/abortable-process.js +2 -0
  71. package/dist/types/criterion-identity.d.ts +12 -0
  72. package/dist/types/criterion-identity.js +2 -0
  73. package/dist/types/index.d.ts +2 -0
  74. package/dist/types/index.js +2 -0
  75. package/dist/types/qa-gate.d.ts +0 -5
  76. package/dist/types/spec-format.d.ts +2 -0
  77. package/dist/types/spec-lock-v2.d.ts +7 -0
  78. package/dist/types/validation-evidence.d.ts +62 -0
  79. package/dist/types/validation-evidence.js +2 -0
  80. package/package.json +43 -23
  81. package/planu-native.json +1 -1
  82. package/planu-plugin.json +1 -1
  83. package/dist/storage/qa-gate-store.d.ts +0 -4
  84. package/dist/storage/qa-gate-store.js +0 -24
  85. package/dist/tools/validate-runtime.d.ts +0 -14
  86. package/dist/types/data/estimation.d.ts +0 -147
  87. package/dist/types/data/estimation.js +0 -2
  88. package/dist/types/data/index.d.ts +0 -5
  89. package/dist/types/data/index.js +0 -6
  90. package/dist/types/data/velocity.d.ts +0 -168
  91. package/dist/types/data/velocity.js +0 -4
@@ -1,7 +1,8 @@
1
- // http-transport.ts — HTTP transport using SDK's StreamableHTTPServerTransport.
1
+ // http-transport.ts — HTTP transport using the SDK's Web Standard transport and Hono 2.
2
2
  import { createServer } from 'node:http';
3
3
  import { randomUUID } from 'node:crypto';
4
- import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
4
+ import { getRequestListener } from '@hono/node-server';
5
+ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
5
6
  import { PLANU_VERSION } from '../config/version.js';
6
7
  import { runWithSessionContext } from './session-context.js';
7
8
  import { checkHttpRateLimit } from './http-rate-limiter.js';
@@ -10,6 +11,13 @@ import { compactToolsListMessage, isToolsListResponse, } from '../engine/compact
10
11
  const MAX_BODY_BYTES = 5 * 1024 * 1024; // 5MB
11
12
  const SHUTDOWN_GRACE_MS = 5_000;
12
13
  const sessions = new Map();
14
+ async function handleNodeTransportRequest(transport, req, res, parsedBody) {
15
+ // The SDK's Node wrapper pins @hono/node-server 1.x. Adapt the Web Standard
16
+ // transport here so the published package executes against our direct Hono 2 dependency.
17
+ const authInfo = req.auth;
18
+ const listener = getRequestListener((webRequest) => transport.handleRequest(webRequest, { authInfo, parsedBody }), { overrideGlobalObjects: false });
19
+ await listener(req, res);
20
+ }
13
21
  function setSecurityHeaders(res) {
14
22
  res.setHeader('X-Content-Type-Options', 'nosniff');
15
23
  }
@@ -86,14 +94,14 @@ async function handlePost(req, res, sessionId, serverFactory) {
86
94
  if (sessionId) {
87
95
  const entry = sessions.get(sessionId);
88
96
  if (entry) {
89
- await runWithSessionContext({ sessionId, licenseKey: entry.licenseKey }, () => entry.transport.handleRequest(req, res, parsedBody));
97
+ await runWithSessionContext({ sessionId, licenseKey: entry.licenseKey }, () => handleNodeTransportRequest(entry.transport, req, res, parsedBody));
90
98
  return;
91
99
  }
92
100
  }
93
101
  // Extract per-session license key from HTTP header (hosted multi-tenant mode)
94
102
  const licenseKey = req.headers['x-planu-license-key']?.trim() ?? undefined;
95
103
  // New session — create transport and connect a new server instance
96
- const transport = new StreamableHTTPServerTransport({
104
+ const transport = new WebStandardStreamableHTTPServerTransport({
97
105
  sessionIdGenerator: () => randomUUID(),
98
106
  });
99
107
  installToolsListCompaction(transport);
@@ -105,7 +113,7 @@ async function handlePost(req, res, sessionId, serverFactory) {
105
113
  };
106
114
  const sessionServer = serverFactory();
107
115
  await sessionServer.connect(transport);
108
- await runWithSessionContext({ sessionId: transport.sessionId ?? sessionId ?? 'unknown', licenseKey }, () => transport.handleRequest(req, res, parsedBody));
116
+ await runWithSessionContext({ sessionId: transport.sessionId ?? sessionId ?? 'unknown', licenseKey }, () => handleNodeTransportRequest(transport, req, res, parsedBody));
109
117
  const newSessionId = transport.sessionId;
110
118
  if (newSessionId) {
111
119
  sessions.set(newSessionId, { transport, server: sessionServer, licenseKey });
@@ -149,7 +157,7 @@ async function handleMcpRequest(req, res, serverFactory, corsOrigin) {
149
157
  if (sessionId) {
150
158
  const entry = sessions.get(sessionId);
151
159
  if (entry) {
152
- await runWithSessionContext({ sessionId, licenseKey: entry.licenseKey }, () => entry.transport.handleRequest(req, res));
160
+ await runWithSessionContext({ sessionId, licenseKey: entry.licenseKey }, () => handleNodeTransportRequest(entry.transport, req, res));
153
161
  return;
154
162
  }
155
163
  }
@@ -238,8 +246,14 @@ export async function createHttpTransport(serverFactory, config) {
238
246
  console.error('[Planu] Shutdown complete');
239
247
  process.exit(0);
240
248
  };
241
- process.on('SIGINT', () => void shutdownGracefully());
242
- process.on('SIGTERM', () => void shutdownGracefully());
249
+ const handleSigint = () => void shutdownGracefully();
250
+ const handleSigterm = () => void shutdownGracefully();
251
+ process.on('SIGINT', handleSigint);
252
+ process.on('SIGTERM', handleSigterm);
253
+ httpServer.once('close', () => {
254
+ process.off('SIGINT', handleSigint);
255
+ process.off('SIGTERM', handleSigterm);
256
+ });
243
257
  return new Promise((resolve) => {
244
258
  httpServer.listen(config.port, config.host, () => {
245
259
  console.error(`Planu MCP server listening on http://${config.host}:${config.port}`);
@@ -1,8 +1,8 @@
1
- import type { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
1
+ import type { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
2
2
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  export type McpServerFactory = () => McpServer;
4
4
  export interface SessionEntry {
5
- transport: StreamableHTTPServerTransport;
5
+ transport: WebStandardStreamableHTTPServerTransport;
6
6
  server: McpServer;
7
7
  /** Per-session license key from HTTP header (hosted multi-tenant mode) */
8
8
  licenseKey?: string;
@@ -1,10 +1,57 @@
1
1
  // transport-factory.ts — Parses CLI flags and selects the appropriate MCP transport.
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { execFileSync } from 'node:child_process';
3
4
  import { createHttpTransport } from './http-transport.js';
4
5
  import { compactToolsListMessage, isToolsListResponse, } from '../engine/compact/tool-list-compactor.js';
5
6
  const VALID_TRANSPORTS = ['stdio', 'http'];
6
7
  const DEFAULT_PORT = 3100;
7
8
  const DEFAULT_HOST = '127.0.0.1';
9
+ const SHUTDOWN_GRACE_MS = 100;
10
+ function descendantPids(parentPid) {
11
+ if (process.platform === 'win32') {
12
+ return [];
13
+ }
14
+ try {
15
+ const rows = execFileSync('ps', ['-A', '-o', 'pid=', '-o', 'ppid='], {
16
+ encoding: 'utf8',
17
+ timeout: 250,
18
+ });
19
+ const children = new Map();
20
+ for (const row of rows.split('\n')) {
21
+ const [pidText, ppidText] = row.trim().split(/\s+/);
22
+ const pid = Number(pidText);
23
+ const ppid = Number(ppidText);
24
+ if (!Number.isInteger(pid) || !Number.isInteger(ppid)) {
25
+ continue;
26
+ }
27
+ children.set(ppid, [...(children.get(ppid) ?? []), pid]);
28
+ }
29
+ const descendants = [];
30
+ const pending = [...(children.get(parentPid) ?? [])];
31
+ while (pending.length > 0) {
32
+ const pid = pending.pop();
33
+ if (pid === undefined) {
34
+ continue;
35
+ }
36
+ descendants.push(pid);
37
+ pending.push(...(children.get(pid) ?? []));
38
+ }
39
+ return descendants.reverse();
40
+ }
41
+ catch {
42
+ return [];
43
+ }
44
+ }
45
+ function signalDescendants(signal) {
46
+ for (const pid of descendantPids(process.pid)) {
47
+ try {
48
+ process.kill(pid, signal);
49
+ }
50
+ catch {
51
+ // The child may already have exited.
52
+ }
53
+ }
54
+ }
8
55
  function getFlag(args, name) {
9
56
  const prefix = `--${name}=`;
10
57
  for (let i = 0; i < args.length; i++) {
@@ -42,8 +89,14 @@ export async function selectTransport(server, args, serverFactory) {
42
89
  if (config.transport === 'stdio') {
43
90
  const transport = new StdioServerTransport();
44
91
  installToolsListCompaction(transport);
45
- await server.connect(transport);
46
- installStdioShutdownHandlers(server);
92
+ const removeShutdownHandlers = installStdioShutdownHandlers(server);
93
+ try {
94
+ await server.connect(transport);
95
+ }
96
+ catch (error) {
97
+ removeShutdownHandlers();
98
+ throw error;
99
+ }
47
100
  return;
48
101
  }
49
102
  // HTTP mode: each session needs its own McpServer instance
@@ -63,32 +116,77 @@ function installToolsListCompaction(transport) {
63
116
  }
64
117
  function installStdioShutdownHandlers(server) {
65
118
  let shuttingDown = false;
119
+ const handlers = new Map();
120
+ const removeHandlers = () => {
121
+ const end = handlers.get('end');
122
+ const close = handlers.get('close');
123
+ const sigterm = handlers.get('SIGTERM');
124
+ const sigint = handlers.get('SIGINT');
125
+ if (end) {
126
+ process.stdin.off('end', end);
127
+ }
128
+ if (close) {
129
+ process.stdin.off('close', close);
130
+ }
131
+ if (sigterm) {
132
+ process.off('SIGTERM', sigterm);
133
+ }
134
+ if (sigint) {
135
+ process.off('SIGINT', sigint);
136
+ }
137
+ };
66
138
  const shutdown = (reason) => {
67
139
  if (shuttingDown) {
68
140
  return;
69
141
  }
70
142
  shuttingDown = true;
143
+ removeHandlers();
71
144
  console.error(`[Planu] MCP stdio ${reason}; shutting down.`);
72
- void server
73
- .close()
145
+ const hardStop = setTimeout(() => {
146
+ signalDescendants('SIGKILL');
147
+ process.exit(0);
148
+ }, 1_500);
149
+ void import('../tools/safe-handler.js')
150
+ .then(({ abortActiveToolExecutions }) => {
151
+ abortActiveToolExecutions(`MCP stdio ${reason}`);
152
+ signalDescendants('SIGTERM');
153
+ })
154
+ .catch(() => {
155
+ /* shutdown remains best-effort */
156
+ })
157
+ .then(() => Promise.all([
158
+ server.close(),
159
+ new Promise((resolveGrace) => setTimeout(resolveGrace, SHUTDOWN_GRACE_MS)),
160
+ ]))
74
161
  .catch((err) => {
75
162
  console.error('[Planu] Error while closing MCP server:', err);
76
163
  })
77
164
  .finally(() => {
165
+ clearTimeout(hardStop);
166
+ signalDescendants('SIGKILL');
78
167
  process.exit(0);
79
168
  });
80
169
  };
81
- process.stdin.once('end', () => {
170
+ const end = () => {
82
171
  shutdown('stdin ended');
83
- });
84
- process.stdin.once('close', () => {
172
+ };
173
+ const close = () => {
85
174
  shutdown('stdin closed');
86
- });
87
- process.once('SIGTERM', () => {
175
+ };
176
+ const sigterm = () => {
88
177
  shutdown('received SIGTERM');
89
- });
90
- process.once('SIGINT', () => {
178
+ };
179
+ const sigint = () => {
91
180
  shutdown('received SIGINT');
92
- });
181
+ };
182
+ handlers.set('end', end);
183
+ handlers.set('close', close);
184
+ handlers.set('SIGTERM', sigterm);
185
+ handlers.set('SIGINT', sigint);
186
+ process.stdin.once('end', end);
187
+ process.stdin.once('close', close);
188
+ process.once('SIGTERM', sigterm);
189
+ process.once('SIGINT', sigint);
190
+ return removeHandlers;
93
191
  }
94
192
  //# sourceMappingURL=transport-factory.js.map
@@ -0,0 +1,26 @@
1
+ export interface AbortableProcessResult {
2
+ stdout: string;
3
+ stderr: string;
4
+ status: number | null;
5
+ signal: NodeJS.Signals | null;
6
+ error?: Error;
7
+ timedOut: boolean;
8
+ aborted: boolean;
9
+ }
10
+ export interface AbortableProcessOptions {
11
+ cwd: string;
12
+ timeoutMs: number;
13
+ maxBufferBytes: number;
14
+ signal?: AbortSignal;
15
+ }
16
+ export interface ProjectCommandPlan {
17
+ requestedCommand: string;
18
+ executedCommand: string;
19
+ executable: string;
20
+ args: string[];
21
+ projectPackageManager: string | null;
22
+ source: 'project-package-manager' | 'host-path';
23
+ runtimeNode: string;
24
+ hint?: string;
25
+ }
26
+ //# sourceMappingURL=abortable-process.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=abortable-process.js.map
@@ -0,0 +1,12 @@
1
+ export interface CriterionIdentity {
2
+ id: string;
3
+ text: string;
4
+ normalizedText: string;
5
+ stableKey: string;
6
+ aliases: string[];
7
+ }
8
+ export interface CriterionIdentityOptions {
9
+ index?: number;
10
+ defaultKind?: 'AC' | 'AB';
11
+ }
12
+ //# sourceMappingURL=criterion-identity.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=criterion-identity.js.map
@@ -68,6 +68,7 @@ export * from './ci.js';
68
68
  export * from './spec-templates.js';
69
69
  export * from './spec-generator.js';
70
70
  export * from './spec-grounding.js';
71
+ export * from './criterion-identity.js';
71
72
  export * from './registry.js';
72
73
  export * from './tool-groups.js';
73
74
  export * from './code-transforms.js';
@@ -267,6 +268,7 @@ export * from './agent-telemetry.js';
267
268
  export * from './agent-memory.js';
268
269
  export * from './agent-spawner.js';
269
270
  export * from './qa-gate.js';
271
+ export * from './validation-evidence.js';
270
272
  export * from './project-drift.js';
271
273
  export * from './technical-enrichment.js';
272
274
  export * from './agent-team.js';
@@ -69,6 +69,7 @@ export * from './ci.js';
69
69
  export * from './spec-templates.js';
70
70
  export * from './spec-generator.js';
71
71
  export * from './spec-grounding.js';
72
+ export * from './criterion-identity.js';
72
73
  export * from './registry.js';
73
74
  export * from './tool-groups.js';
74
75
  export * from './code-transforms.js';
@@ -264,6 +265,7 @@ export * from './agent-telemetry.js';
264
265
  export * from './agent-memory.js';
265
266
  export * from './agent-spawner.js';
266
267
  export * from './qa-gate.js';
268
+ export * from './validation-evidence.js';
267
269
  export * from './project-drift.js';
268
270
  export * from './technical-enrichment.js';
269
271
  export * from './agent-team.js';
@@ -16,9 +16,4 @@ export interface QaGateResult {
16
16
  coverageThreshold: number | null;
17
17
  fingerprint?: string;
18
18
  }
19
- export interface QaGateState {
20
- specId: string;
21
- lastResult: QaGateResult | null;
22
- fingerprint?: string;
23
- }
24
19
  //# sourceMappingURL=qa-gate.d.ts.map
@@ -30,6 +30,8 @@ export interface BddScenario {
30
30
  export interface NormalizedAcceptanceCriterion {
31
31
  id: string;
32
32
  text: string;
33
+ normalizedText: string;
34
+ stableKey: string;
33
35
  source: 'body' | 'frontmatter';
34
36
  aliases: string[];
35
37
  }
@@ -50,4 +50,11 @@ export interface LockInfo extends CrossProcessLockMetadata {
50
50
  stale: boolean;
51
51
  staleReason?: 'heartbeat-expired' | 'ttl-expired' | 'pid-dead';
52
52
  }
53
+ /** Observable totals and per-run results from stale lock cleanup. */
54
+ export interface LockScavengeMetrics {
55
+ scavengedLocks: number;
56
+ scavengedBackups: number;
57
+ preservedLive: number;
58
+ preservedCorrupt: number;
59
+ }
53
60
  //# sourceMappingURL=spec-lock-v2.d.ts.map
@@ -0,0 +1,62 @@
1
+ export declare const VALIDATION_EVIDENCE_SCHEMA_VERSION: '1.0.0';
2
+ export type ValidationGateCapability = 'typecheck' | 'lint' | 'test-related' | 'test-full' | 'security' | 'native' | 'website' | 'reproducibility';
3
+ export type ValidationEvidenceStatus = 'passed' | 'failed' | 'cancelled' | 'invalidated';
4
+ export interface ValidationEvidenceKeyInput {
5
+ projectPath: string;
6
+ capability: ValidationGateCapability;
7
+ command: string;
8
+ scope: readonly string[];
9
+ relevantPaths?: readonly string[];
10
+ platformDependent?: boolean;
11
+ toolchain?: Partial<ValidationToolchain>;
12
+ }
13
+ export interface ValidationToolchain {
14
+ node: string;
15
+ packageManager: string;
16
+ platform: string;
17
+ architecture: string;
18
+ }
19
+ export interface ValidationEvidenceIdentity {
20
+ schemaVersion: typeof VALIDATION_EVIDENCE_SCHEMA_VERSION;
21
+ capability: ValidationGateCapability;
22
+ normalizedCommand: string;
23
+ sourceTreeDigest: string;
24
+ lockDigest: string;
25
+ configDigest: string;
26
+ toolchainDigest: string;
27
+ selectedScopeDigest: string;
28
+ }
29
+ export interface ValidationEvidenceReceipt {
30
+ schemaVersion: typeof VALIDATION_EVIDENCE_SCHEMA_VERSION;
31
+ key: string;
32
+ identity: ValidationEvidenceIdentity;
33
+ producer: string;
34
+ status: ValidationEvidenceStatus;
35
+ durationMs: number;
36
+ outputDigest: string;
37
+ reason: string;
38
+ source: string;
39
+ completedAt: string;
40
+ timeSavedMs: number;
41
+ }
42
+ export interface ValidationEvidenceAudit {
43
+ key: string;
44
+ capability: ValidationGateCapability;
45
+ action: 'executed' | 'reused' | 'skipped' | 'invalidated' | 'aborted';
46
+ producer: string;
47
+ status: ValidationEvidenceStatus;
48
+ durationMs: number;
49
+ outputDigest: string;
50
+ reason: string;
51
+ source: string;
52
+ completedAt: string;
53
+ timeSavedMs: number;
54
+ }
55
+ export interface ValidationImpactPlan {
56
+ kind: 'none' | 'lifecycle' | 'source' | 'dependency' | 'native' | 'website' | 'full';
57
+ gates: ValidationGateCapability[];
58
+ heavy: boolean;
59
+ reason: string;
60
+ changedPaths: string[];
61
+ }
62
+ //# sourceMappingURL=validation-evidence.d.ts.map
@@ -0,0 +1,2 @@
1
+ export const VALIDATION_EVIDENCE_SCHEMA_VERSION = '1.0.0';
2
+ //# sourceMappingURL=validation-evidence.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.11.9",
3
+ "version": "4.12.2",
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.9",
38
- "@planu/core-darwin-x64": "4.11.9",
39
- "@planu/core-linux-arm64-gnu": "4.11.9",
40
- "@planu/core-linux-arm64-musl": "4.11.9",
41
- "@planu/core-linux-x64-gnu": "4.11.9",
42
- "@planu/core-linux-x64-musl": "4.11.9",
43
- "@planu/core-win32-arm64-msvc": "4.11.9",
44
- "@planu/core-win32-x64-msvc": "4.11.9"
37
+ "@planu/core-darwin-arm64": "4.12.2",
38
+ "@planu/core-darwin-x64": "4.12.2",
39
+ "@planu/core-linux-arm64-gnu": "4.12.2",
40
+ "@planu/core-linux-arm64-musl": "4.12.2",
41
+ "@planu/core-linux-x64-gnu": "4.12.2",
42
+ "@planu/core-linux-x64-musl": "4.12.2",
43
+ "@planu/core-win32-arm64-msvc": "4.12.2",
44
+ "@planu/core-win32-x64-msvc": "4.12.2"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=24.0.0"
@@ -72,10 +72,21 @@
72
72
  "test:watch": "vitest",
73
73
  "test:coverage": "vitest run --coverage --exclude 'tests/integration/**'",
74
74
  "test:integration": "vitest run tests/integration",
75
+ "test:website:unit": "vitest run tests/website tests/scripts/generate-website-proof.test.ts tests/scripts/website-tool-counts.test.ts",
76
+ "test:website:browser": "playwright test --config playwright.website.config.ts",
77
+ "test:website:lighthouse": "lhci autorun --config=.lighthouserc.cjs",
78
+ "test:website:lighthouse:production": "PLANU_LIGHTHOUSE_PRODUCTION=1 lhci autorun --config=.lighthouserc.cjs",
75
79
  "native:project-graph:e2e": "PLANU_NATIVE_E2E=1 vitest run tests/integration/native-project-graph-e2e.test.ts",
76
80
  "native:project-graph:benchmark": "node scripts/benchmark-native-performance.mjs --operation project_graph_query --samples 5 --project-path . --spec-id SPEC-1119 --json",
77
- "check": "pnpm typecheck && pnpm lint && pnpm format:check",
78
- "check:strict": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm audit:deadcode && pnpm audit:circular && pnpm audit:types && pnpm audit:security && pnpm audit:licenses && pnpm audit:i18n",
81
+ "check": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm check:public-privacy && pnpm check:website-proof && pnpm check:donation-assets && pnpm check:website-public-assets",
82
+ "check:strict": "pnpm check && pnpm audit:deadcode && pnpm audit:circular && pnpm audit:types && pnpm audit:security && pnpm audit:licenses && pnpm audit:i18n",
83
+ "check:website-public-assets": "node scripts/check-website-public-assets.mjs",
84
+ "check:public-privacy": "node scripts/check-public-privacy.mjs",
85
+ "generate:website-proof": "node scripts/generate-website-proof.mjs",
86
+ "generate:donation-assets": "node scripts/generate-crypto-donation-assets.mjs",
87
+ "check:donation-assets": "node scripts/generate-crypto-donation-assets.mjs --check",
88
+ "check:website-proof": "node scripts/generate-website-proof.mjs --check",
89
+ "check:website": "pnpm check:public-privacy && pnpm check:website-proof && pnpm check:donation-assets && pnpm check:website-public-assets && pnpm test:website:unit && pnpm test:website:browser",
79
90
  "check:deps:fresh": "bash scripts/check-dependency-freshness.sh",
80
91
  "audit:deadcode": "knip",
81
92
  "audit:circular": "madge --circular --extensions ts src/",
@@ -133,43 +144,52 @@
133
144
  ],
134
145
  "license": "SEE LICENSE IN LICENSE",
135
146
  "dependencies": {
136
- "@anthropic-ai/sdk": "^0.112.3",
147
+ "@anthropic-ai/sdk": "^0.115.0",
148
+ "@hono/node-server": "2.0.11",
137
149
  "@modelcontextprotocol/sdk": "^1.29.0",
138
150
  "glob": "^13.0.6",
139
151
  "yaml": "^2.9.0",
140
152
  "zod": "^4.4.3"
141
153
  },
142
154
  "devDependencies": {
155
+ "@axe-core/playwright": "4.12.1",
143
156
  "@commitlint/cli": "^21.2.1",
144
157
  "@commitlint/config-conventional": "^21.2.0",
145
158
  "@eslint/js": "^10.0.1",
146
- "@napi-rs/cli": "^3.7.3",
147
- "@secretlint/secretlint-rule-no-homedir": "^13.0.2",
148
- "@secretlint/secretlint-rule-preset-recommend": "^13.0.2",
159
+ "@lhci/cli": "0.15.1",
160
+ "@napi-rs/cli": "^3.7.4",
161
+ "@noble/hashes": "2.2.0",
162
+ "@playwright/test": "1.62.0",
163
+ "@scure/base": "2.2.0",
164
+ "@secretlint/secretlint-rule-no-homedir": "^13.0.4",
165
+ "@secretlint/secretlint-rule-preset-recommend": "^13.0.4",
149
166
  "@stryker-mutator/core": "^9.6.1",
150
167
  "@stryker-mutator/vitest-runner": "^9.6.1",
151
- "@supabase/supabase-js": "^2.110.7",
168
+ "@supabase/supabase-js": "^2.110.8",
152
169
  "@types/node": "^26.1.1",
170
+ "@types/qrcode": "1.5.6",
153
171
  "@typescript/native": "npm:typescript@^7.0.2",
154
172
  "@vitejs/plugin-vue": "^6.0.8",
155
173
  "@vitest/coverage-v8": "^4.1.10",
156
174
  "@vue/test-utils": "^2.4.11",
157
- "eslint": "^10.7.0",
175
+ "eslint": "10.8.0",
158
176
  "eslint-config-prettier": "^10.1.8",
159
177
  "eslint-import-resolver-typescript": "^4.4.5",
160
178
  "eslint-plugin-import": "^2.32.0",
161
- "happy-dom": "^20.11.0",
179
+ "happy-dom": "^20.11.1",
162
180
  "husky": "^9.1.7",
163
181
  "javascript-obfuscator": "^5.5.0",
164
- "knip": "^6.27.0",
165
- "lint-staged": "^17.1.0",
182
+ "jiti": "2.7.0",
183
+ "knip": "^6.29.0",
184
+ "lint-staged": "^17.2.0",
166
185
  "madge": "^8.0.0",
167
- "prettier": "^3.9.5",
168
- "secretlint": "^13.0.2",
186
+ "prettier": "^3.9.6",
187
+ "qrcode": "1.5.4",
188
+ "secretlint": "^13.0.4",
169
189
  "tsc-alias": "^1.9.1",
170
190
  "type-coverage": "^2.29.7",
171
191
  "typescript": "npm:@typescript/typescript6@^6.0.2",
172
- "typescript-eslint": "^8.64.0",
192
+ "typescript-eslint": "^8.65.0",
173
193
  "vite": "^8.1.5",
174
194
  "vitest": "^4.1.10",
175
195
  "vue": "^3.5.40"
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.9",
4
+ "version": "4.12.2",
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.9",
5
+ "version": "4.12.2",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": [
8
8
  "npx",
@@ -1,4 +0,0 @@
1
- import type { QaGateState, QaGateResult } from '../types/qa-gate.js';
2
- export declare function saveGateState(projectPath: string, specId: string, result: QaGateResult): Promise<void>;
3
- export declare function getGateState(projectPath: string, specId: string): Promise<QaGateState | null>;
4
- //# sourceMappingURL=qa-gate-store.d.ts.map
@@ -1,24 +0,0 @@
1
- // storage/qa-gate-store.ts — SPEC-642
2
- import { readFile, writeFile, mkdir } from 'node:fs/promises';
3
- import { join, dirname } from 'node:path';
4
- import { safeJsonParse } from './base-store.js';
5
- function gatePath(projectPath, specId) {
6
- return join(projectPath, '.planu', 'qa-gate', `${specId}.json`);
7
- }
8
- export async function saveGateState(projectPath, specId, result) {
9
- const path = gatePath(projectPath, specId);
10
- await mkdir(dirname(path), { recursive: true });
11
- const state = { specId, lastResult: result, fingerprint: result.fingerprint };
12
- await writeFile(path, JSON.stringify(state, null, 2), 'utf-8');
13
- }
14
- export async function getGateState(projectPath, specId) {
15
- try {
16
- const raw = await readFile(gatePath(projectPath, specId), 'utf-8');
17
- const parsed = safeJsonParse(raw, null);
18
- return parsed && typeof parsed === 'object' && 'specId' in parsed ? parsed : null;
19
- }
20
- catch {
21
- return null;
22
- }
23
- }
24
- //# sourceMappingURL=qa-gate-store.js.map
@@ -1,14 +0,0 @@
1
- export interface ProjectCommandPlan {
2
- requestedCommand: string;
3
- executedCommand: string;
4
- executable: string;
5
- args: string[];
6
- projectPackageManager: string | null;
7
- source: 'project-package-manager' | 'host-path';
8
- runtimeNode: string;
9
- hint?: string;
10
- }
11
- export declare function resolveProjectCommandPlan(projectPath: string, requestedCommand: string, corepackAvailable?: boolean): ProjectCommandPlan;
12
- export declare function formatLintFailureDiagnostics(plan: ProjectCommandPlan, output: string): string;
13
- export declare function runProjectCommandPlan(plan: ProjectCommandPlan, projectPath: string, timeoutMs: number): void;
14
- //# sourceMappingURL=validate-runtime.d.ts.map