@planu/cli 5.7.5 → 5.7.6

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 (196) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/README.md +3 -3
  3. package/dist/.planu-build.json +1 -1
  4. package/dist/cli/commands/package-handoff.js +30 -5
  5. package/dist/config/compliance-profiles.json +36 -3
  6. package/dist/config/environment-schema.json +21 -0
  7. package/dist/config/hook-templates/planu-session-safeguard.sh +22 -15
  8. package/dist/config/official-sdd-tools.d.ts +1 -1
  9. package/dist/config/official-sdd-tools.js +11 -24
  10. package/dist/config/registries/hosts/codex.json +14 -30
  11. package/dist/config/registries/hosts/opencode.json +1 -6
  12. package/dist/config/server-instructions.js +0 -21
  13. package/dist/config/skill-templates/planu-context-assets.md +3 -6
  14. package/dist/config/skill-templates/planu-implement.md +2 -2
  15. package/dist/config/skill-templates/planu-multi-teammate-review.md +1 -1
  16. package/dist/config/skill-templates/planu-release.md +2 -7
  17. package/dist/config/skill-templates/planu-validate.md +3 -3
  18. package/dist/config/spec-templates/crud-rest-api/template.json +5 -1
  19. package/dist/config/spec-templates/file-upload-s3/template.json +5 -1
  20. package/dist/config/spec-templates/stripe-payments/template.json +5 -1
  21. package/dist/config/spec-templates/webhook-system/template.json +5 -1
  22. package/dist/config/subagent-templates/planu-challenger.md +4 -9
  23. package/dist/config/subagent-templates/planu-spec-implementer.md +2 -4
  24. package/dist/config/subagent-templates/planu-validator.md +5 -8
  25. package/dist/config/tool-groups.json +6 -144
  26. package/dist/engine/ambiguity-scorer.js +40 -1
  27. package/dist/engine/autopilot/bootstrap.js +1 -1
  28. package/dist/engine/code-impact-analyzer.js +2 -6
  29. package/dist/engine/context-orchestrator/index.js +2 -2
  30. package/dist/engine/convention-scanner/codebase-scanner.js +2 -2
  31. package/dist/engine/dogfooding/runtime-gap-detector.js +2 -2
  32. package/dist/engine/evidence-gates/artifact-reader.d.ts +1 -0
  33. package/dist/engine/evidence-gates/artifact-reader.js +1 -1
  34. package/dist/engine/evidence-gates/evidence-autofill.d.ts +13 -5
  35. package/dist/engine/evidence-gates/evidence-autofill.js +106 -43
  36. package/dist/engine/evidence-gates/lifecycle-gate.js +7 -4
  37. package/dist/engine/evidence-index/index-builder.js +21 -4
  38. package/dist/engine/execution/job-runtime.js +1 -1
  39. package/dist/engine/execution/package-handoff-job-executor.d.ts +22 -0
  40. package/dist/engine/execution/package-handoff-job-executor.js +490 -0
  41. package/dist/engine/execution/validate-job-executor.js +20 -8
  42. package/dist/engine/git/exec-git.d.ts +4 -0
  43. package/dist/engine/git/exec-git.js +14 -0
  44. package/dist/engine/handoff-artifacts/implementation-review-reader.js +15 -2
  45. package/dist/engine/handoff-artifacts/schemas.d.ts +2 -0
  46. package/dist/engine/handoff-artifacts/schemas.js +1 -0
  47. package/dist/engine/handoff-format.d.ts +36 -0
  48. package/dist/engine/handoff-format.js +314 -0
  49. package/dist/engine/handoff-packager.d.ts +5 -3
  50. package/dist/engine/handoff-packager.js +89 -32
  51. package/dist/engine/host-tool-filter.js +20 -49
  52. package/dist/engine/human-summary.js +14 -3
  53. package/dist/engine/implementation-contract/common.d.ts +8 -3
  54. package/dist/engine/implementation-contract/common.js +9 -6
  55. package/dist/engine/implementation-contract/evaluator.js +4 -6
  56. package/dist/engine/implementation-contract/renderer.js +18 -23
  57. package/dist/engine/project-health-checker.js +2 -2
  58. package/dist/engine/readiness-checker.js +27 -0
  59. package/dist/engine/reconcile/apply-changes.js +4 -0
  60. package/dist/engine/reconcile/propagate-mirrors.d.ts +3 -0
  61. package/dist/engine/reconcile/propagate-mirrors.js +311 -0
  62. package/dist/engine/self-healing/healer.js +12 -5
  63. package/dist/engine/self-healing/strategies/lint-fix.d.ts +1 -1
  64. package/dist/engine/self-healing/strategies/lint-fix.js +4 -1
  65. package/dist/engine/self-healing/strategies/test-fix.d.ts +4 -3
  66. package/dist/engine/self-healing/strategies/test-fix.js +32 -14
  67. package/dist/engine/self-healing/strategies/typescript-fix.d.ts +1 -1
  68. package/dist/engine/self-healing/strategies/typescript-fix.js +28 -10
  69. package/dist/engine/session/checkpoint-writer.js +8 -4
  70. package/dist/engine/session/session-tracker.d.ts +5 -16
  71. package/dist/engine/session/session-tracker.js +29 -26
  72. package/dist/engine/session-state/writer.js +3 -11
  73. package/dist/engine/skill-generator/workflow-skill-generator.d.ts +4 -2
  74. package/dist/engine/skill-generator/workflow-skill-generator.js +113 -38
  75. package/dist/engine/spec-format/bdd-parser.d.ts +1 -11
  76. package/dist/engine/spec-format/bdd-parser.js +40 -11
  77. package/dist/engine/spec-format/lean-spec-generator.js +30 -10
  78. package/dist/engine/spec-format/retired-scaffold.d.ts +3 -0
  79. package/dist/engine/spec-format/retired-scaffold.js +5 -0
  80. package/dist/engine/spec-format/technical-md-populator.d.ts +1 -0
  81. package/dist/engine/spec-format/technical-md-populator.js +27 -0
  82. package/dist/engine/spec-format/unified-spec-builder.d.ts +1 -0
  83. package/dist/engine/spec-format/unified-spec-builder.js +30 -20
  84. package/dist/engine/spec-grounding/contract.js +115 -1
  85. package/dist/engine/spec-quality/generic-output-gate.js +37 -3
  86. package/dist/engine/technical-enricher/index.js +4 -1
  87. package/dist/engine/type-safety-gate.js +3 -11
  88. package/dist/engine/universal-rules/catalog.js +0 -2
  89. package/dist/engine/universal-rules/rules/planu-workflow.js +1 -1
  90. package/dist/engine/validation/durable-validation.d.ts +3 -2
  91. package/dist/engine/validation/durable-validation.js +106 -40
  92. package/dist/engine/validation/validation-freshness.d.ts +3 -2
  93. package/dist/engine/validation/validation-freshness.js +22 -16
  94. package/dist/engine/validation/validation-worktree.js +34 -0
  95. package/dist/hosts/claude-code/ux/mcp-prompts.js +8 -8
  96. package/dist/index.js +3 -17
  97. package/dist/resources/process.js +38 -65
  98. package/dist/storage/session-state-store.js +2 -0
  99. package/dist/tools/challenge-spec/scenarios-utils.js +2 -2
  100. package/dist/tools/configure-checkpoint-policy.js +3 -3
  101. package/dist/tools/create-spec-helpers.js +2 -5
  102. package/dist/tools/create-spec.js +21 -4
  103. package/dist/tools/facilitate.js +3 -6
  104. package/dist/tools/git/branch-ops.js +16 -4
  105. package/dist/tools/github-release-handler.js +1 -20
  106. package/dist/tools/init-project/agents-md-writer.js +6 -6
  107. package/dist/tools/init-project/claude-md-generator.d.ts +0 -1
  108. package/dist/tools/init-project/claude-md-generator.js +0 -35
  109. package/dist/tools/init-project/handler.js +7 -5
  110. package/dist/tools/init-project/per-client-files-writer.js +5 -5
  111. package/dist/tools/init-project/planu-workflow-generator.js +2 -13
  112. package/dist/tools/init-project/portable-index-reconciler.d.ts +2 -1
  113. package/dist/tools/init-project/portable-index-reconciler.js +7 -2
  114. package/dist/tools/init-project/rules-generator.d.ts +0 -11
  115. package/dist/tools/init-project/rules-generator.js +4 -85
  116. package/dist/tools/init-project/scaffold-writer.d.ts +0 -1
  117. package/dist/tools/init-project/scaffold-writer.js +1 -12
  118. package/dist/tools/jobs/handlers.d.ts +0 -6
  119. package/dist/tools/jobs/handlers.js +13 -40
  120. package/dist/tools/list-specs.js +6 -21
  121. package/dist/tools/package-handoff.d.ts +3 -2
  122. package/dist/tools/package-handoff.js +153 -427
  123. package/dist/tools/reconcile-session-safeguard-hook.js +22 -15
  124. package/dist/tools/register-sdd-tools.js +0 -23
  125. package/dist/tools/register-spec-tools/core-spec-tools.js +18 -27
  126. package/dist/tools/schemas/index.d.ts +1 -1
  127. package/dist/tools/schemas/index.js +1 -1
  128. package/dist/tools/schemas/output-schemas.d.ts +1 -18
  129. package/dist/tools/schemas/output-schemas.js +1 -11
  130. package/dist/tools/schemas/package-handoff-output-schema.d.ts +40 -0
  131. package/dist/tools/schemas/package-handoff-output-schema.js +68 -0
  132. package/dist/tools/schemas/validate-output-schema.d.ts +1 -2
  133. package/dist/tools/schemas/validate-output-schema.js +3 -2
  134. package/dist/tools/session-checkpoint.js +2 -2
  135. package/dist/tools/skill-registry/index.d.ts +0 -1
  136. package/dist/tools/skill-registry/index.js +0 -1
  137. package/dist/tools/sync-spec-state-handler.d.ts +2 -2
  138. package/dist/tools/sync-spec-state-handler.js +23 -3
  139. package/dist/tools/tool-registry/core-tools.js +1 -140
  140. package/dist/tools/tool-registry/group-infra.js +2 -39
  141. package/dist/tools/tool-registry/group-quality-compliance.js +9 -1
  142. package/dist/tools/update-status/batch.js +17 -0
  143. package/dist/tools/update-status/dod-gates.js +14 -1
  144. package/dist/tools/update-status/done-receipt-verifier.d.ts +22 -0
  145. package/dist/tools/update-status/done-receipt-verifier.js +56 -26
  146. package/dist/tools/update-status/evidence-gate.js +10 -21
  147. package/dist/tools/update-status/file-sync.d.ts +1 -0
  148. package/dist/tools/update-status/file-sync.js +6 -0
  149. package/dist/tools/update-status/index.d.ts +6 -0
  150. package/dist/tools/update-status/index.js +39 -44
  151. package/dist/tools/update-status-actions.js +2 -7
  152. package/dist/tools/validate.js +34 -28
  153. package/dist/transports/oauth-validator.js +12 -1
  154. package/dist/transports/transport-factory.d.ts +2 -1
  155. package/dist/transports/transport-factory.js +19 -0
  156. package/dist/types/common/primitives.d.ts +2 -0
  157. package/dist/types/durable-job.d.ts +45 -0
  158. package/dist/types/durable-validation.d.ts +1 -1
  159. package/dist/types/evidence-autofill.d.ts +13 -1
  160. package/dist/types/handoff-artifacts.d.ts +2 -1
  161. package/dist/types/readiness.d.ts +8 -1
  162. package/dist/types/reconcile.d.ts +6 -0
  163. package/dist/types/skill-registry.d.ts +26 -1
  164. package/dist/types/spec/core.d.ts +5 -0
  165. package/dist/types/spec/inputs.d.ts +2 -2
  166. package/dist/types/spec-format.d.ts +11 -1
  167. package/dist/types/spec-grounding.d.ts +12 -0
  168. package/dist/types/transport.d.ts +1 -0
  169. package/dist/types/validation-receipt.d.ts +27 -0
  170. package/package.json +3 -2
  171. package/planu-plugin.json +13 -26
  172. package/src/i18n/messages/en.json +1 -1
  173. package/src/i18n/messages/es.json +1 -1
  174. package/src/i18n/messages/pt.json +1 -1
  175. package/dist/engine/skill-generator/conventions-hasher.d.ts +0 -7
  176. package/dist/engine/skill-generator/conventions-hasher.js +0 -24
  177. package/dist/engine/universal-rules/rules/agent-teams.d.ts +0 -3
  178. package/dist/engine/universal-rules/rules/agent-teams.js +0 -63
  179. package/dist/tools/clarify-requirements/multiple-choice.d.ts +0 -30
  180. package/dist/tools/clarify-requirements/multiple-choice.js +0 -306
  181. package/dist/tools/clarify-requirements/questions-context.d.ts +0 -9
  182. package/dist/tools/clarify-requirements/questions-context.js +0 -74
  183. package/dist/tools/clarify-requirements/questions.d.ts +0 -16
  184. package/dist/tools/clarify-requirements/questions.js +0 -96
  185. package/dist/tools/clarify-requirements.d.ts +0 -4
  186. package/dist/tools/clarify-requirements.js +0 -314
  187. package/dist/tools/code-graph-handler.d.ts +0 -6
  188. package/dist/tools/code-graph-handler.js +0 -72
  189. package/dist/tools/create-rule.d.ts +0 -4
  190. package/dist/tools/create-rule.js +0 -107
  191. package/dist/tools/feedback-handler.d.ts +0 -7
  192. package/dist/tools/feedback-handler.js +0 -150
  193. package/dist/tools/semantic-search-handler.d.ts +0 -7
  194. package/dist/tools/semantic-search-handler.js +0 -71
  195. package/dist/tools/skill-registry/search.d.ts +0 -11
  196. package/dist/tools/skill-registry/search.js +0 -159
@@ -162,10 +162,15 @@ export async function handleValidate(args) {
162
162
  },
163
163
  };
164
164
  }
165
- const [spec, knowledge] = await Promise.all([
166
- specStore.getSpec(projectId, args.specId),
167
- knowledgeStore.getKnowledge(projectId),
168
- ]);
165
+ const knowledge = await knowledgeStore.getKnowledge(projectId);
166
+ if (!knowledge) {
167
+ return {
168
+ content: [{ type: 'text', text: t('project.notFound') }],
169
+ isError: true,
170
+ structuredContent: { error: 'project_not_found', code: 404, context: { projectId } },
171
+ };
172
+ }
173
+ const spec = await specStore.getSpec(projectId, args.specId, knowledge.projectPath);
169
174
  if (!spec) {
170
175
  return {
171
176
  content: [{ type: 'text', text: ti('spec.notFound', { id: args.specId }) }],
@@ -177,13 +182,6 @@ export async function handleValidate(args) {
177
182
  },
178
183
  };
179
184
  }
180
- if (!knowledge) {
181
- return {
182
- content: [{ type: 'text', text: t('project.notFound') }],
183
- isError: true,
184
- structuredContent: { error: 'project_not_found', code: 404, context: { projectId } },
185
- };
186
- }
187
185
  let validationWorktree;
188
186
  try {
189
187
  validationWorktree = await resolveValidationWorktree({
@@ -259,6 +257,11 @@ function validationWorktreeErrorResult(error, specId) {
259
257
  specId: failure.specId,
260
258
  ...(failure.relativeSpecPath ? { path: failure.relativeSpecPath } : {}),
261
259
  },
260
+ ...(failure.code === 'VALIDATION_STALE_SPEC_PATH'
261
+ ? {
262
+ fixHint: `The spec registry still points at a stale checkout for ${failure.relativeSpecPath ?? 'this spec'}. Re-run validate from the canonical checkout (or call list_specs against the canonical projectPath) to self-heal the registry entry, then retry.`,
263
+ }
264
+ : {}),
262
265
  },
263
266
  };
264
267
  }
@@ -281,27 +284,30 @@ async function acquireValidationSubmissionLock(projectId, specId) {
281
284
  }
282
285
  function validationAcceptedResult(job, projectId, runtime, { justCreated }) {
283
286
  const observation = runtime.observe(job);
287
+ const reused = !justCreated;
284
288
  const accepted = {
285
289
  schemaVersion: 1,
286
290
  operationId: job.id,
287
291
  projectId,
288
292
  workspaceId: job.workspaceId,
289
293
  state: job.state,
290
- submittedAt: job.createdAt,
291
- attachedAt: justCreated ? job.createdAt : new Date().toISOString(),
294
+ submittedAt: justCreated ? job.updatedAt : job.createdAt,
295
+ attachedAt: justCreated ? job.updatedAt : new Date().toISOString(),
296
+ reused,
292
297
  cursor: publicJobCursor(observation),
293
298
  queue: observation.queue,
294
299
  terminalStates: ['completed', 'failed', 'cancelled', 'dead-letter'],
295
300
  resultSchema: 'validate-completion/v1',
296
301
  observation: {
297
302
  get: 'get_job',
298
- watch: 'watch_job',
299
- cancel: 'cancel_job',
300
303
  restart: 'restart_job',
301
304
  },
302
305
  };
306
+ const text = reused
307
+ ? `Attached to already-running validation job ${job.id}; no new work was scheduled. Follow it with get_job. To force re-execution after it reaches a terminal state, run validate again.`
308
+ : `Validation accepted as durable job ${job.id}.`;
303
309
  return {
304
- content: [{ type: 'text', text: `Validation accepted as durable job ${job.id}.` }],
310
+ content: [{ type: 'text', text }],
305
311
  structuredContent: accepted,
306
312
  };
307
313
  }
@@ -313,29 +319,29 @@ export async function executeValidate(args, server, onProgress) {
313
319
  if (!projectId) {
314
320
  return missingProjectIdError;
315
321
  }
316
- const spec = await specStore.getSpec(projectId, specId);
317
- if (!spec) {
322
+ const knowledge = await knowledgeStore.getKnowledge(projectId);
323
+ if (!knowledge) {
318
324
  return {
319
- content: [{ type: 'text', text: ti('spec.notFound', { id: specId }) }],
325
+ content: [{ type: 'text', text: t('project.notFound') }],
320
326
  isError: true,
321
327
  structuredContent: {
322
- error: 'spec_not_found',
328
+ error: 'project_not_found',
323
329
  code: 404,
324
- context: { specId, projectId },
325
- fixHint: `Verify the specId is correct and the spec exists in project ${projectId}.`,
330
+ context: { projectId },
331
+ fixHint: 'Run init_project first to initialize the project knowledge.',
326
332
  },
327
333
  };
328
334
  }
329
- const knowledge = await knowledgeStore.getKnowledge(projectId);
330
- if (!knowledge) {
335
+ const spec = await specStore.getSpec(projectId, specId, knowledge.projectPath);
336
+ if (!spec) {
331
337
  return {
332
- content: [{ type: 'text', text: t('project.notFound') }],
338
+ content: [{ type: 'text', text: ti('spec.notFound', { id: specId }) }],
333
339
  isError: true,
334
340
  structuredContent: {
335
- error: 'project_not_found',
341
+ error: 'spec_not_found',
336
342
  code: 404,
337
- context: { projectId },
338
- fixHint: 'Run init_project first to initialize the project knowledge.',
343
+ context: { specId, projectId },
344
+ fixHint: `Verify the specId is correct and the spec exists in project ${projectId}.`,
339
345
  },
340
346
  };
341
347
  }
@@ -3,6 +3,7 @@
3
3
  // 1. API key (SDD_OAUTH_API_KEY) — simplest: token must match env var
4
4
  // 2. HMAC-signed JWT (SDD_OAUTH_JWT_SECRET) — validates HS256 JWT signature
5
5
  // Configure via environment variables — no accounts or external services needed.
6
+ import { timingSafeEqual } from 'node:crypto';
6
7
  export function loadOAuthConfig() {
7
8
  const apiKey = process.env.SDD_OAUTH_API_KEY;
8
9
  const jwtSecret = process.env.SDD_OAUTH_JWT_SECRET;
@@ -32,13 +33,23 @@ export async function validateToken(token, config) {
32
33
  return { valid: true };
33
34
  }
34
35
  if (config.mode === 'api-key') {
35
- return token === config.apiKey ? { valid: true } : { valid: false, reason: 'Invalid API key' };
36
+ return isMatchingApiKey(token, config.apiKey ?? '')
37
+ ? { valid: true }
38
+ : { valid: false, reason: 'Invalid API key' };
36
39
  }
37
40
  if (config.mode === 'jwt-hmac') {
38
41
  return validateHmacJwt(token, config);
39
42
  }
40
43
  return { valid: false, reason: 'Unknown auth mode' };
41
44
  }
45
+ function isMatchingApiKey(token, apiKey) {
46
+ const tokenBuffer = Buffer.from(token);
47
+ const apiKeyBuffer = Buffer.from(apiKey);
48
+ if (tokenBuffer.byteLength !== apiKeyBuffer.byteLength) {
49
+ return false;
50
+ }
51
+ return timingSafeEqual(tokenBuffer, apiKeyBuffer);
52
+ }
42
53
  // Validate HS256 JWT using Node.js built-in crypto (no external deps).
43
54
  async function validateHmacJwt(token, config) {
44
55
  const parts = token.split('.');
@@ -1,5 +1,6 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import type { TransportConfig } from '../types/index.js';
2
+ import type { TransportConfig, OAuthConfig } from '../types/index.js';
3
3
  export declare function parseTransportConfig(args: string[]): TransportConfig;
4
+ export declare function assertHttpExposureAllowed(config: TransportConfig, oauth: OAuthConfig): void;
4
5
  export declare function selectTransport(server: McpServer, args: string[], serverFactory?: () => McpServer): Promise<void>;
5
6
  //# sourceMappingURL=transport-factory.d.ts.map
@@ -2,6 +2,7 @@
2
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import { createHttpTransport } from './http-transport.js';
5
+ import { loadOAuthConfig } from './oauth-validator.js';
5
6
  import { compactToolsListMessage, isToolsListResponse, } from '../engine/compact/tool-list-compactor.js';
6
7
  import { reportClassifiedDegradation } from '../errors/classified-degradation.js';
7
8
  const VALID_TRANSPORTS = ['stdio', 'http'];
@@ -84,8 +85,25 @@ export function parseTransportConfig(args) {
84
85
  port,
85
86
  host: getFlag(args, 'host') ?? process.env.HOST ?? DEFAULT_HOST,
86
87
  corsOrigin: getFlag(args, 'cors-origin') ?? process.env.CORS_ORIGIN,
88
+ insecure: args.includes('--insecure'),
87
89
  };
88
90
  }
91
+ const LOOPBACK_HOSTS = new Set(['127.0.0.1', '::1', 'localhost']);
92
+ function isLoopbackHost(host) {
93
+ return LOOPBACK_HOSTS.has(host);
94
+ }
95
+ export function assertHttpExposureAllowed(config, oauth) {
96
+ if (config.transport !== 'http' || isLoopbackHost(config.host) || oauth.enabled) {
97
+ return;
98
+ }
99
+ if (config.insecure) {
100
+ console.error(`[Planu] WARNING: running UNAUTHENTICATED on non-loopback host "${config.host}" because --insecure was passed.`);
101
+ return;
102
+ }
103
+ console.error(`[Planu] Refusing to start: HTTP transport on non-loopback host "${config.host}" has no authentication configured. ` +
104
+ 'Set SDD_OAUTH_API_KEY or SDD_OAUTH_JWT_SECRET, or pass --insecure to bypass this at your own risk.');
105
+ process.exit(1);
106
+ }
89
107
  export async function selectTransport(server, args, serverFactory) {
90
108
  const config = parseTransportConfig(args);
91
109
  if (config.transport === 'stdio') {
@@ -102,6 +120,7 @@ export async function selectTransport(server, args, serverFactory) {
102
120
  return;
103
121
  }
104
122
  // HTTP mode: each session needs its own McpServer instance
123
+ assertHttpExposureAllowed(config, loadOAuthConfig());
105
124
  const factory = serverFactory ?? (() => server);
106
125
  await createHttpTransport(factory, config);
107
126
  }
@@ -3,6 +3,8 @@ export type Difficulty = 1 | 2 | 3 | 4 | 5;
3
3
  export type RiskLevel = 'low' | 'medium' | 'high' | 'critical';
4
4
  export type SupportedLocale = 'en' | 'es' | 'pt';
5
5
  export type ExperienceLevel = 'beginner' | 'intermediate' | 'expert';
6
+ /** SDD model tier evidence for lifecycle transitions. */
7
+ export type ModelTierUsed = 'max' | 'implementation' | 'review';
6
8
  /** SPEC-313: Preferred interaction style with Planu (stored in planu.json) */
7
9
  export type WorkMode = 'guided' | 'standard' | 'expert';
8
10
  export type SpecType = 'feature' | 'refactor' | 'bugfix' | 'infra' | 'docs' | 'project' | 'agent' | 'tech-debt';
@@ -1,4 +1,5 @@
1
1
  import type { ValidationArtifactDigests, ValidationFreshnessLease } from './validation-receipt.js';
2
+ import type { HandoffPackage } from './readiness.js';
2
3
  export interface DurableJobIdentity {
3
4
  readonly operationId: string;
4
5
  readonly projectId: string;
@@ -35,6 +36,50 @@ export interface ValidateDurableJobPayload {
35
36
  readonly freshnessLease?: ValidationFreshnessLease;
36
37
  readonly specPath: string;
37
38
  }
39
+ export interface PackageHandoffDurableJobPayload {
40
+ readonly kind: 'package-handoff';
41
+ readonly schemaVersion: 1;
42
+ readonly projectId: string;
43
+ readonly specId: string;
44
+ readonly specVersion: string;
45
+ }
46
+ /** Outcome of one deadline-bounded package-handoff stage execution. */
47
+ export interface PackageHandoffStageOutcome<T> {
48
+ readonly ok: boolean;
49
+ readonly value?: T;
50
+ readonly timedOut: boolean;
51
+ readonly error?: unknown;
52
+ }
53
+ export interface PackageHandoffStageResult<T> {
54
+ readonly stage: string;
55
+ readonly durationMs: number;
56
+ readonly outcome: PackageHandoffStageOutcome<T>;
57
+ }
58
+ export interface PackageHandoffDegradation {
59
+ readonly stage: string;
60
+ readonly code: 'HANDOFF_ENRICHMENT_TIMEOUT' | 'HANDOFF_ENRICHMENT_FAILURE' | 'HANDOFF_STALE_VERSION_SKIPPED';
61
+ readonly remediation: string;
62
+ }
63
+ export interface PackageHandoffCoreStageOutput {
64
+ readonly readinessScore: number;
65
+ readonly pkg: HandoffPackage & {
66
+ readonly staleVersionSkipped?: boolean;
67
+ };
68
+ readonly stageTimings: Record<string, number>;
69
+ }
70
+ export interface PackageHandoffEnrichmentOutput {
71
+ readonly pkgWithConstraints: HandoffPackage;
72
+ readonly graphContext: {
73
+ text: string;
74
+ structured: unknown;
75
+ } | null;
76
+ readonly minimalityBlocked: boolean | null;
77
+ readonly minimalitySummary: Record<string, unknown> | undefined;
78
+ readonly tokenWasteRiskCount: number | null;
79
+ readonly tokenWasteSummary: Record<string, unknown> | undefined;
80
+ readonly stageTimings: Record<string, number>;
81
+ readonly degradations: readonly PackageHandoffDegradation[];
82
+ }
38
83
  export declare const DURABLE_JOB_STATES: readonly ['accepted', 'running', 'checkpointed', 'cancelled', 'failed', 'dead-letter', 'completed'];
39
84
  export type DurableJobState = (typeof DURABLE_JOB_STATES)[number];
40
85
  export interface DurableJobRecord {
@@ -9,7 +9,7 @@ export interface DurableValidationBindings {
9
9
  readonly policyHash: string;
10
10
  readonly toolchainHash: string;
11
11
  }
12
- export type ValidationWorktreeErrorCode = 'VALIDATION_PAYLOAD_VERSION_UNSUPPORTED' | 'VALIDATION_ROOT_ID_MALFORMED' | 'VALIDATION_ROOT_ID_MISMATCH' | 'VALIDATION_ROOT_BUSY' | 'VALIDATION_ROOT_NOT_GIT_WORKTREE' | 'VALIDATION_COMMON_DIR_MISMATCH' | 'VALIDATION_SPEC_PATH_TRAVERSAL' | 'VALIDATION_SPEC_PATH_SYMLINK' | 'VALIDATION_SPEC_NOT_FOUND' | 'VALIDATION_SPEC_ID_MISMATCH';
12
+ export type ValidationWorktreeErrorCode = 'VALIDATION_PAYLOAD_VERSION_UNSUPPORTED' | 'VALIDATION_ROOT_ID_MALFORMED' | 'VALIDATION_ROOT_ID_MISMATCH' | 'VALIDATION_ROOT_BUSY' | 'VALIDATION_ROOT_NOT_GIT_WORKTREE' | 'VALIDATION_COMMON_DIR_MISMATCH' | 'VALIDATION_SPEC_PATH_TRAVERSAL' | 'VALIDATION_STALE_SPEC_PATH' | 'VALIDATION_SPEC_PATH_SYMLINK' | 'VALIDATION_SPEC_NOT_FOUND' | 'VALIDATION_SPEC_ID_MISMATCH';
13
13
  export interface ResolvedValidationWorktree {
14
14
  readonly projectPath: string;
15
15
  readonly gitCommonDir: string;
@@ -28,9 +28,21 @@ export type AutofillTraceabilityMatrixResult = {
28
28
  written: true;
29
29
  path: string;
30
30
  rowCount: number;
31
+ reason?: 'regenerated-stale';
31
32
  } | {
32
33
  written: false;
33
- reason: 'already-exists' | 'no-criteria' | 'write-failed';
34
+ reason: 'already-exists-manual' | 'already-exists-current' | 'no-criteria' | 'write-failed';
34
35
  error?: string;
35
36
  };
37
+ /** Classification of an on-disk traceability-matrix.json relative to the
38
+ * generator's own prose templates: absent, hand-written/mixed/unparseable
39
+ * ("manual" — never touched), or autofill-shaped and safe to regenerate. */
40
+ export type ExistingMatrixLookup = {
41
+ kind: 'absent';
42
+ } | {
43
+ kind: 'manual';
44
+ } | {
45
+ kind: 'autofill-shaped';
46
+ raw: string;
47
+ };
36
48
  //# sourceMappingURL=evidence-autofill.d.ts.map
@@ -48,6 +48,7 @@ export interface ImplementationReviewV1 {
48
48
  kind: 'implementation-review-agent' | 'human';
49
49
  agent: string;
50
50
  verdict: 'approved' | 'changes-requested';
51
+ sessionId?: string;
51
52
  };
52
53
  blockers: string[];
53
54
  suggestions: string[];
@@ -147,7 +148,7 @@ export interface ArtifactPayloadMap {
147
148
  }
148
149
  export type ArtifactPayload<K extends ArtifactKind> = ArtifactPayloadMap[K];
149
150
  import type { MinimalImplementationReport } from './minimal-implementation-gate.js';
150
- export type ImplementationReviewRejectionReason = 'missing' | 'unreadable' | 'schema_invalid' | 'stub_body' | 'wrong_agent_or_kind' | 'not_approved' | 'self_review' | 'actor_unreadable' | 'wrong_spec';
151
+ export type ImplementationReviewRejectionReason = 'missing' | 'unreadable' | 'schema_invalid' | 'stub_body' | 'wrong_agent_or_kind' | 'not_approved' | 'self_review' | 'actor_unreadable' | 'wrong_spec' | 'identity_unverifiable';
151
152
  export interface VerifiedImplementationReviewOk {
152
153
  ok: true;
153
154
  review: ImplementationReviewV1;
@@ -63,8 +63,15 @@ export interface AmbiguityReport {
63
63
  violations: AmbiguityViolation[];
64
64
  blockers: string[];
65
65
  }
66
+ /** Version fence for the durable executor: skip publishing a stale-version artifact. */
67
+ export interface HandoffVersionFence {
68
+ readonly specVersion: string;
69
+ readonly getCurrentSpecVersion: () => Promise<string | undefined>;
70
+ readonly signal?: AbortSignal;
71
+ }
66
72
  export interface PackageHandoffInput {
67
- projectId: string;
73
+ projectId?: string;
74
+ projectPath?: string;
68
75
  specId: string;
69
76
  }
70
77
  /**
@@ -26,6 +26,12 @@ export interface SectionBounds {
26
26
  start: number;
27
27
  end: number;
28
28
  }
29
+ export type AppliedSections = ReadonlySet<string>;
30
+ export interface ParsedFilesSection {
31
+ create: string[];
32
+ modify: string[];
33
+ test: string[];
34
+ }
29
35
  export interface CurrentStatus {
30
36
  totalSpecs: number;
31
37
  content: Record<string, unknown>;
@@ -398,9 +398,34 @@ export interface WorkflowSkillGenerationOptions {
398
398
  export interface WorkflowSkillGenerationResult {
399
399
  /** Skill names that were written (new or updated). */
400
400
  written: string[];
401
- /** Skill names that were skipped (conventions unchanged). */
401
+ /** Skill names that were skipped (content unchanged). */
402
402
  skipped: string[];
403
+ /** Skill names left untouched because the on-disk file was user-authored or user-edited. */
404
+ userModified: string[];
403
405
  /** Any non-fatal errors (skill name → error message). */
404
406
  errors: Record<string, string>;
405
407
  }
408
+ /** Fail-closed result of reading the hash marker from an on-disk skill file. */
409
+ export type MarkerReadResult = {
410
+ kind: 'absent';
411
+ } | {
412
+ kind: 'invalid';
413
+ } | {
414
+ kind: 'legacy';
415
+ } | {
416
+ kind: 'current';
417
+ hash: string;
418
+ lineIndex: number;
419
+ };
420
+ /** Per-file write decision made by generateWorkflowSkills for one skill definition. */
421
+ export type SkillFileDecision = {
422
+ action: 'write';
423
+ } | {
424
+ action: 'skip-unchanged';
425
+ } | {
426
+ action: 'skip-user-modified';
427
+ } | {
428
+ action: 'skip-error';
429
+ message: string;
430
+ };
406
431
  //# sourceMappingURL=skill-registry.d.ts.map
@@ -435,6 +435,11 @@ export interface SyncDivergence {
435
435
  yamlStatus: string;
436
436
  dataStatus: string;
437
437
  }
438
+ /** Options controlling how syncSpecState reconciles divergences. */
439
+ export interface SyncSpecStateOptions {
440
+ /** When false, non-terminal drift is reported read-only instead of applied via handleUpdateStatus. */
441
+ applyTransitions?: boolean;
442
+ }
438
443
  /** Report produced by spec state synchronisation. */
439
444
  export interface SyncReport {
440
445
  synced: number;
@@ -1,4 +1,4 @@
1
- import type { SpecStatus, SpecType, SpecScope, SpecTarget, ToolResult } from '../common/index.js';
1
+ import type { ModelTierUsed, SpecStatus, SpecType, SpecScope, SpecTarget, ToolResult } from '../common/index.js';
2
2
  import type { Actuals } from '../estimation.js';
3
3
  import type { GlobalConfig } from '../project/config-metrics.js';
4
4
  import type { ProjectKnowledge } from '../project.js';
@@ -58,7 +58,7 @@ export interface UpdateStatusInput {
58
58
  * - implementation: lower/intermediate implementer tier
59
59
  * - review: reviewer tier
60
60
  */
61
- modelTierUsed?: 'max' | 'implementation' | 'review';
61
+ modelTierUsed?: ModelTierUsed;
62
62
  /** SHA-256 hash of the persisted context package used for this transition. */
63
63
  contextHash?: string;
64
64
  /** Path to the persisted handoff artifact generated by package_handoff. */
@@ -1,6 +1,6 @@
1
1
  import type { Spec } from './spec/core.js';
2
2
  import type { Estimation } from './estimation.js';
3
- import type { SpecStatus } from './common/index.js';
3
+ import type { ModelTierUsed, SpecStatus } from './common/index.js';
4
4
  import type { CriterionGroundingRecord, TechnicalReferenceGroundingRecord } from './spec-grounding.js';
5
5
  /** A criterion in the lean spec frontmatter. */
6
6
  export interface LeanCriterion {
@@ -110,6 +110,15 @@ export interface LegacyMigrationDirective {
110
110
  legacyFiles: LegacyArtifactHit[];
111
111
  directive: 'migrate_legacy_spec';
112
112
  }
113
+ export interface UpdateStatusBatchEvidence {
114
+ modelTierUsed?: ModelTierUsed;
115
+ modelId?: string;
116
+ contextHash?: string;
117
+ handoffPath?: string;
118
+ handoffArtifactId?: string;
119
+ reviewedBy?: string;
120
+ arbitratedBy?: string;
121
+ }
113
122
  export interface UpdateStatusBatchInput {
114
123
  specIds: string[];
115
124
  status: Exclude<SpecStatus, 'done'>;
@@ -117,6 +126,7 @@ export interface UpdateStatusBatchInput {
117
126
  projectPath?: string;
118
127
  dryRun?: boolean;
119
128
  reviewNotes?: string;
129
+ evidence?: Record<string, UpdateStatusBatchEvidence>;
120
130
  }
121
131
  /** Report of detected migration drift (SPEC-715). Read-only — no FS mutations. */
122
132
  export interface MigrationDriftReport {
@@ -19,4 +19,16 @@ export interface GroundingGateResult {
19
19
  issues: string[];
20
20
  records: CriterionGroundingRecord[];
21
21
  }
22
+ export interface RawGroundingFrontmatter {
23
+ grounding?: {
24
+ criteria?: unknown;
25
+ technicalReferences?: unknown;
26
+ };
27
+ }
28
+ export type FrontmatterYamlOutcome = {
29
+ parsed: true;
30
+ document: Record<string, unknown> | undefined;
31
+ } | {
32
+ parsed: false;
33
+ };
22
34
  //# sourceMappingURL=spec-grounding.d.ts.map
@@ -10,5 +10,6 @@ export interface TransportConfig {
10
10
  port: number;
11
11
  host: string;
12
12
  corsOrigin?: string;
13
+ insecure?: boolean;
13
14
  }
14
15
  //# sourceMappingURL=transport.d.ts.map
@@ -15,6 +15,16 @@ export interface ValidationArtifactDigests {
15
15
  readonly traceability: string;
16
16
  readonly contractValidation: string;
17
17
  }
18
+ export interface ValidationArtifactProvenanceWriteResult {
19
+ readonly writtenPaths: readonly string[];
20
+ readonly absentArtifacts: readonly string[];
21
+ }
22
+ export interface PerFileArtifactSentinel {
23
+ readonly name: string;
24
+ readonly digestValue: string;
25
+ readonly sentinel: string;
26
+ readonly payloadFile?: string;
27
+ }
18
28
  export interface ValidationArtifactProvenance {
19
29
  readonly receiptId: string;
20
30
  /** Digest of canonical artifact content excluding this provenance envelope. */
@@ -28,6 +38,23 @@ export interface ValidationFreshnessLease {
28
38
  readonly epoch: string;
29
39
  readonly generation: number;
30
40
  }
41
+ export type ValidationFreshnessBarrierReason = 'lease-superseded' | 'invalidation-drain-failed' | 'evidence-check-failed' | 'evidence-changed' | 'source-changed' | 'source-check-failed' | 'state-lost' | 'lease-expired' | 'bindings-mismatch';
42
+ export type ValidationFreshnessBarrierResult = {
43
+ readonly fresh: true;
44
+ } | {
45
+ readonly fresh: false;
46
+ readonly reason: ValidationFreshnessBarrierReason;
47
+ readonly changedInput?: string;
48
+ };
49
+ export type ValidationFreshnessRebaselineReason = 'lease-expired' | 'recompute-failed' | 'bindings-mismatch' | 'lease-unstable';
50
+ export type ValidationFreshnessRebaselineResult = {
51
+ readonly lease: ValidationFreshnessLease;
52
+ } | {
53
+ readonly rejection: {
54
+ readonly reason: ValidationFreshnessRebaselineReason;
55
+ readonly changedInput?: string;
56
+ };
57
+ };
31
58
  /**
32
59
  * Classification of one raw filesystem watcher event feeding the freshness
33
60
  * barrier: a direct relevant/ignored child path, a synthetic root event (the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "5.7.5",
3
+ "version": "5.7.6",
4
4
  "description": "Planu — MCP Server for Spec Driven Development. Cross-platform (Linux/macOS/Windows, x64/arm64).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@
20
20
  "LICENSE"
21
21
  ],
22
22
  "engines": {
23
- "node": ">=24.0.0"
23
+ "node": ">=22.13.0"
24
24
  },
25
25
  "packageManager": "pnpm@11.9.0+sha512.bd682d5d03fe525ef7c9fd6780c6884d1e756ac4c9c9fe00c538782824310dcf90e3ddc4f53835f06dfaebd5085e41855e0bcbb3b60de2ac5bbab89e5036f03b",
26
26
  "scripts": {
@@ -48,6 +48,7 @@
48
48
  "test:watch": "vitest",
49
49
  "test:coverage": "vitest run --coverage --testTimeout=60000 --maxWorkers=4",
50
50
  "test:release-gate": "vitest run --exclude 'tests/integration/**'",
51
+ "test:pre-push-scripts": "PLANU_PRE_PUSH_SCRIPTS_LANE=1 vitest run tests/scripts --reporter=dot",
51
52
  "test:integration": "vitest run tests/integration",
52
53
  "test:website:unit": "vitest run tests/website tests/scripts/generate-website-proof.test.ts tests/scripts/website-tool-counts.test.ts",
53
54
  "test:website:browser": "playwright test --config playwright.website.config.ts",
package/planu-plugin.json CHANGED
@@ -2,44 +2,31 @@
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": "5.7.5",
5
+ "version": "5.7.6",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": ["npx", "@planu/cli@latest"],
8
8
  "packageName": "@planu/cli",
9
9
  "capabilities": {
10
10
  "tools": [
11
- "planu_status",
12
- "session_checkpoint",
13
- "facilitate",
14
11
  "init_project",
15
- "clarify_requirements",
12
+ "facilitate",
16
13
  "create_spec",
17
14
  "list_specs",
18
- "challenge_spec",
19
- "check_readiness",
20
15
  "update_status",
21
16
  "update_status_batch",
22
- "request_changes",
23
- "estimate",
24
- "package_handoff",
25
17
  "validate",
26
- "get_job",
27
- "watch_job",
28
- "cancel_job",
29
- "restart_job",
30
- "reconcile_spec",
31
- "create_rule",
32
- "create_skill",
33
- "skill_search",
34
- "configure_code_graph",
35
- "code_graph_status",
36
- "submit_feedback",
37
- "triage_feedback",
38
- "resolve_feedback",
39
- "sync_feedback",
40
- "feedback_status",
18
+ "challenge_spec",
19
+ "check_readiness",
41
20
  "bump_spec_version",
42
- "migrate_legacy_spec"
21
+ "reconcile_spec",
22
+ "migrate_legacy_spec",
23
+ "package_handoff",
24
+ "planu_status",
25
+ "session_checkpoint",
26
+ "estimate",
27
+ "request_changes",
28
+ "get_job",
29
+ "restart_job"
43
30
  ],
44
31
  "resources": ["planu://specs/list", "planu://specs/{id}", "planu://project/status", "planu://roadmap"],
45
32
  "prompts": ["create-spec-from-idea", "review-spec-readiness", "generate-implementation-plan"],
@@ -307,7 +307,7 @@
307
307
  "description": "Check if a plan has enough detail to start building. Verifies that the description is complete, acceptance criteria are clear, and all dependencies are met. Tells you if the plan is ready or what is missing."
308
308
  },
309
309
  "package_handoff": {
310
- "description": "Create a complete instruction package so an AI agent can implement a feature. Includes the objective, acceptance criteria, file list, what is out of scope, and any constraints — everything the agent needs to get started."
310
+ "description": "Submit a durable job that builds a complete instruction package so an AI agent can implement a feature. Returns an acknowledgment immediately; poll get_job for the completed package (objective, acceptance criteria, file list, what is out of scope, and any constraints)."
311
311
  },
312
312
  "context_budget": {
313
313
  "description": "Manage context window budget and optimization — configure limits, save checkpoints, and get suggestions to reduce token usage.",
@@ -307,7 +307,7 @@
307
307
  "description": "Verificar si un plan tiene suficiente detalle para empezar a construir. Verifica que la descripción esté completa, los criterios de aceptación sean claros y todas las dependencias estén cumplidas. Te dice si el plan está listo o qué falta."
308
308
  },
309
309
  "package_handoff": {
310
- "description": "Crear un paquete completo de instrucciones para que un agente de IA implemente una feature. Incluye el objetivo, criterios de aceptación, lista de archivos, qué está fuera de scope y cualquier restricción — todo lo que el agente necesita para empezar."
310
+ "description": "Enviar un job durable que construye un paquete completo de instrucciones para que un agente de IA implemente una feature. Devuelve un acuse de recibo de inmediato; usa get_job para obtener el paquete completado (objetivo, criterios de aceptación, lista de archivos, qué está fuera de scope y cualquier restricción)."
311
311
  },
312
312
  "context_budget": {
313
313
  "description": "Gestionar el presupuesto de ventana de contexto y optimización — configurar límites, guardar checkpoints y obtener sugerencias para reducir uso de tokens.",
@@ -307,7 +307,7 @@
307
307
  "description": "Verificar se um plano tem detalhes suficientes para começar a construir. Verifica se a descrição está completa, se os critérios de aceitação estão claros e se todas as dependências estão atendidas. Te diz se o plano está pronto ou o que está faltando."
308
308
  },
309
309
  "package_handoff": {
310
- "description": "Criar um pacote completo de instruções para um agente de IA implementar uma feature. Inclui o objetivo, critérios de aceitação, lista de arquivos, o que está fora do escopo e quaisquer restrições — tudo que o agente precisa para começar."
310
+ "description": "Enviar um job durável que constrói um pacote completo de instruções para um agente de IA implementar uma feature. Retorna uma confirmação imediatamente; use get_job para obter o pacote concluído (objetivo, critérios de aceitação, lista de arquivos, o que está fora do escopo e quaisquer restrições)."
311
311
  },
312
312
  "context_budget": {
313
313
  "description": "Gerenciar o orçamento da janela de contexto e otimização — configurar limites, salvar checkpoints e obter sugestões para reduzir uso de tokens.",
@@ -1,7 +0,0 @@
1
- import type { ProjectKnowledge } from '../../types/index.js';
2
- /**
3
- * Hash the subset of ProjectKnowledge fields that affect skill content.
4
- * Returns a short hex string suitable for embedding in a comment marker.
5
- */
6
- export declare function hashConventions(knowledge: ProjectKnowledge): string;
7
- //# sourceMappingURL=conventions-hasher.d.ts.map