@planu/cli 5.3.2 → 5.3.4

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 (58) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/config/runtime-policy.json +9 -0
  3. package/dist/engine/autopilot/action-registry.js +20 -2
  4. package/dist/engine/autopilot/complete-loop.js +4 -1
  5. package/dist/engine/constitution/sdd-rules-registry.js +8 -4
  6. package/dist/engine/drift/violation-resolver.js +30 -8
  7. package/dist/engine/evidence-gates/evidence-skeletons.js +9 -4
  8. package/dist/engine/execution/outbox-worker.d.ts +8 -0
  9. package/dist/engine/execution/outbox-worker.js +102 -31
  10. package/dist/engine/handoff-packager.js +10 -3
  11. package/dist/engine/implementation-contract/common.d.ts +13 -1
  12. package/dist/engine/implementation-contract/common.js +19 -3
  13. package/dist/engine/implementation-contract/evaluator.js +116 -35
  14. package/dist/engine/implementation-contract/renderer.js +62 -31
  15. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
  16. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +4 -4
  17. package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
  18. package/dist/engine/planu-core.darwin-x64.node.sbom.json +4 -4
  19. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
  20. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +4 -4
  21. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
  22. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +4 -4
  23. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
  24. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +4 -4
  25. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
  26. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +4 -4
  27. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
  28. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +4 -4
  29. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
  30. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +4 -4
  31. package/dist/engine/runtime-policy.js +14 -0
  32. package/dist/engine/spec-format/unified-spec-builder.js +32 -8
  33. package/dist/engine/spec-generator/fallback-generator.js +5 -3
  34. package/dist/engine/spec-quality/generic-output-gate.d.ts +7 -1
  35. package/dist/engine/spec-quality/generic-output-gate.js +199 -8
  36. package/dist/engine/spec-quality-scorer.js +9 -5
  37. package/dist/engine/workers/handlers/auto-drift.js +22 -8
  38. package/dist/index.js +3 -1
  39. package/dist/storage/runtime-db.d.ts +23 -1
  40. package/dist/storage/runtime-db.js +79 -9
  41. package/dist/tools/create-spec/post-creation.js +7 -2
  42. package/dist/tools/create-spec/spec-created-outbox-consumer.d.ts +2 -0
  43. package/dist/tools/create-spec/spec-created-outbox-consumer.js +94 -6
  44. package/dist/tools/create-spec-helpers.d.ts +2 -2
  45. package/dist/tools/create-spec-helpers.js +2 -2
  46. package/dist/tools/create-spec.js +5 -0
  47. package/dist/tools/git/branch-ops.d.ts +7 -3
  48. package/dist/tools/git/branch-ops.js +68 -18
  49. package/dist/tools/update-status/evidence-gate.js +10 -17
  50. package/dist/tools/update-status/transition-guard.js +26 -14
  51. package/dist/tools/update-status-actions.js +4 -2
  52. package/dist/types/git.d.ts +4 -0
  53. package/dist/types/outbox-worker.d.ts +2 -0
  54. package/dist/types/runtime-policy.d.ts +9 -0
  55. package/dist/types/spec-quality.d.ts +6 -1
  56. package/package.json +9 -9
  57. package/planu-native.json +1 -1
  58. package/planu-plugin.json +1 -1
@@ -7,7 +7,7 @@ import { scoreAmbiguityFromPath } from '../../engine/ambiguity-scorer.js';
7
7
  import { checkSpecReadiness } from '../../engine/readiness-checker.js';
8
8
  import { validateEnglishOnlySpecText } from '../../engine/spec-language/english-only.js';
9
9
  import { checkGroundedSpecContract } from '../../engine/spec-grounding/contract.js';
10
- import { checkGenericSpecOutput } from '../../engine/spec-quality/generic-output-gate.js';
10
+ import { checkGenericSpecOutput, STRUCTURAL_INTERPOLATION_KIND, } from '../../engine/spec-quality/generic-output-gate.js';
11
11
  import { formatKeyValue } from '../output-formatter.js';
12
12
  import { getResolvedChallengeEvidence, requiredResolvedChallenges, } from '../challenge-spec/challenge-report.js';
13
13
  import { evaluateSpecDependencies } from '../../engine/dependency-evaluator.js';
@@ -319,14 +319,22 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
319
319
  };
320
320
  }
321
321
  const genericOutputGate = checkGenericSpecOutput(body);
322
- if (!genericOutputGate.passed) {
322
+ // SPEC-1406: structural-interpolation findings warn instead of block here — the
323
+ // `create_spec` call site is where this new detector is blocking; ~1100 already-persisted
324
+ // specs must stay transitionable at update_status even if their rendered body still
325
+ // contains an interpolated-list pattern.
326
+ const blockingGenericOutputIssues = genericOutputGate.issues.filter((issue) => issue.kind !== STRUCTURAL_INTERPOLATION_KIND);
327
+ const structuralInterpolationWarnings = genericOutputGate.issues
328
+ .filter((issue) => issue.kind === STRUCTURAL_INTERPOLATION_KIND)
329
+ .map((issue) => `${issue.phrase}: ${issue.reason}`);
330
+ if (blockingGenericOutputIssues.length > 0) {
323
331
  return {
324
332
  blockResult: {
325
333
  content: [
326
334
  {
327
335
  type: 'text',
328
336
  text: `Spec quality gate blocked transition to ${newStatus}. ` +
329
- genericOutputGate.issues
337
+ blockingGenericOutputIssues
330
338
  .slice(0, 3)
331
339
  .map((issue) => `${issue.phrase}: ${issue.reason}`)
332
340
  .join('; '),
@@ -336,11 +344,15 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
336
344
  structuredContent: {
337
345
  error: 'GENERIC_SPEC_OUTPUT_BLOCKED',
338
346
  sddConstitutionRuleId: 'sdd.no-generic-output',
339
- issues: genericOutputGate.issues,
347
+ issues: blockingGenericOutputIssues,
340
348
  fixHint: 'Replace generic criteria or placeholder references with grounded, testable behavior before review or approval.',
341
349
  },
342
350
  },
343
- qualityWarnings: [],
351
+ // SPEC-1406 (AC3, round 2): the offending section must be named in the output on
352
+ // every return path, not only the success path — this blockResult already fired
353
+ // for a different reason, but any structural-interpolation warnings computed above
354
+ // are still real findings and must not be dropped.
355
+ qualityWarnings: structuralInterpolationWarnings,
344
356
  };
345
357
  }
346
358
  let readiness;
@@ -350,7 +362,7 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
350
362
  catch (error) {
351
363
  /* reliability-optional: READINESS_EVALUATION_FAILED — transition fails closed below */
352
364
  reportClassifiedDegradation('READINESS_EVALUATION_FAILED', error);
353
- return readinessUnavailable('READINESS_EVALUATION_FAILED', error instanceof Error ? error.message : 'readiness evaluation failed');
365
+ return readinessUnavailable('READINESS_EVALUATION_FAILED', error instanceof Error ? error.message : 'readiness evaluation failed', structuralInterpolationWarnings);
354
366
  }
355
367
  const { score } = readiness;
356
368
  const blockers = [...readiness.issues.blockers];
@@ -377,7 +389,7 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
377
389
  fixHint: 'Add at least 3 testable acceptance criteria before moving to review.',
378
390
  },
379
391
  },
380
- qualityWarnings: [],
392
+ qualityWarnings: structuralInterpolationWarnings,
381
393
  };
382
394
  }
383
395
  const question = {
@@ -412,11 +424,11 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
412
424
  interactiveQuestions: [question],
413
425
  },
414
426
  },
415
- qualityWarnings: [],
427
+ qualityWarnings: structuralInterpolationWarnings,
416
428
  };
417
429
  }
418
430
  if (readiness.ready) {
419
- return { blockResult: null, qualityWarnings: [] };
431
+ return { blockResult: null, qualityWarnings: structuralInterpolationWarnings };
420
432
  }
421
433
  const diagnostic = score < 70
422
434
  ? `score ${String(score)}/100 is below the 70 threshold`
@@ -442,13 +454,13 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
442
454
  fixHint: 'Complete the spec before moving from draft to review.',
443
455
  },
444
456
  },
445
- qualityWarnings: [],
457
+ qualityWarnings: structuralInterpolationWarnings,
446
458
  };
447
459
  }
448
460
  // Score < 70
449
461
  if (forceApprove) {
450
462
  // Caller wants to force through — proceed with warnings attached
451
- return { blockResult: null, qualityWarnings: issues };
463
+ return { blockResult: null, qualityWarnings: [...issues, ...structuralInterpolationWarnings] };
452
464
  }
453
465
  const question = {
454
466
  header: 'Readiness gate',
@@ -486,7 +498,7 @@ export async function checkReadinessGate(spec, newStatus, forceApprove) {
486
498
  interactiveQuestions: [question],
487
499
  },
488
500
  },
489
- qualityWarnings: [],
501
+ qualityWarnings: structuralInterpolationWarnings,
490
502
  };
491
503
  }
492
504
  function gateUnavailableResult(code, message) {
@@ -496,8 +508,8 @@ function gateUnavailableResult(code, message) {
496
508
  structuredContent: { error: code, message, retryable: true },
497
509
  };
498
510
  }
499
- function readinessUnavailable(code, message) {
500
- return { blockResult: gateUnavailableResult(code, message), qualityWarnings: [] };
511
+ function readinessUnavailable(code, message, qualityWarnings = []) {
512
+ return { blockResult: gateUnavailableResult(code, message), qualityWarnings };
501
513
  }
502
514
  /**
503
515
  * SPEC-964: Challenge gate — block 'review' if challenge_spec was never run
@@ -54,11 +54,13 @@ export async function runImplementingSideEffects(projectId, specId, transitionPr
54
54
  let branchResult;
55
55
  try {
56
56
  const { handleCreateBranch } = await import('./git/branch-ops.js');
57
- branchResult = await withAudit(projectPath, 'update_status(implementing)', 'handleCreateBranch', () => handleCreateBranch(projectId, specId, undefined, projectPath), (r) => ({ isError: r.isError }));
57
+ branchResult = await withAudit(projectPath, 'update_status(implementing)', 'handleCreateBranch', () => handleCreateBranch(projectId, specId, undefined, projectPath, { mutateCheckout: false }), (r) => ({ isError: r.isError }));
58
58
  }
59
59
  catch {
60
60
  const { handleCreateBranch } = await import('./git/branch-ops.js');
61
- branchResult = await handleCreateBranch(projectId, specId, undefined, projectPath);
61
+ branchResult = await handleCreateBranch(projectId, specId, undefined, projectPath, {
62
+ mutateCheckout: false,
63
+ });
62
64
  }
63
65
  if (!branchResult.isError) {
64
66
  try {
@@ -144,6 +144,10 @@ export interface GitSetupResult {
144
144
  data: Record<string, unknown>;
145
145
  branch: string;
146
146
  }
147
+ /** SPEC-1396: automatic callers set `mutateCheckout: false` so the shared checkout never moves. */
148
+ export interface GitAutoSetupOptions {
149
+ mutateCheckout?: boolean;
150
+ }
147
151
  export interface AutoSetupResult {
148
152
  action: 'auto-setup';
149
153
  gitFlowType: GitFlowType;
@@ -12,6 +12,8 @@ export interface OutboxFenceRecord {
12
12
  readonly state: 'leased' | 'failed' | 'dead-letter' | 'delivered';
13
13
  readonly attempts?: number;
14
14
  readonly lastError?: string;
15
+ /** Durable retry deadline (ISO timestamp). Only set for `failed` fences; optional for schemaVersion 1 compatibility. */
16
+ readonly nextAttemptAt?: string;
15
17
  }
16
18
  export declare const SPEC_CREATED_POST_COMMIT_TASKS: readonly ['git-setup', 'analysis', 'quality-score', 'duplicate-detection', 'complexity-advice', 'prior-decisions', 'suggestions', 'challenge-readiness'];
17
19
  export type SpecCreatedPostCommitTask = (typeof SPEC_CREATED_POST_COMMIT_TASKS)[number];
@@ -47,5 +47,14 @@ export interface RuntimePolicy {
47
47
  maximumRememberedTransitions: number;
48
48
  minimumHeartbeatMs: number;
49
49
  };
50
+ outbox: {
51
+ sliceRecordBudget: number;
52
+ sliceTimeBudgetMs: number;
53
+ backoffBaseMs: number;
54
+ backoffCapMs: number;
55
+ maxAttempts: number;
56
+ diagnosticMaxLength: number;
57
+ continuationDelayMs: number;
58
+ };
50
59
  }
51
60
  //# sourceMappingURL=runtime-policy.d.ts.map
@@ -35,7 +35,12 @@ export interface SpecQualityReport {
35
35
  risk: QualityDimensionDetail;
36
36
  };
37
37
  }
38
- export type GenericSpecOutputIssueKind = 'generic-criterion' | 'unsupported-verification' | 'placeholder-reference' | 'generic-technical-reference';
38
+ export type GenericSpecOutputIssueKind = 'generic-criterion' | 'unsupported-verification' | 'placeholder-reference' | 'generic-technical-reference'
39
+ /** SPEC-1406: dedicated kind for structural-interpolation findings (checkInterpolatedSections).
40
+ * Kept distinct from 'generic-technical-reference' — severity routing in transition-guard.ts
41
+ * and sdd-rules-registry.ts filters BY KIND, so aliasing the two would silently downgrade
42
+ * whichever kind collides the day something else emits the shared value. */
43
+ | 'structural-interpolation';
39
44
  export interface GenericSpecOutputIssue {
40
45
  kind: GenericSpecOutputIssueKind;
41
46
  phrase: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "5.3.2",
3
+ "version": "5.3.4",
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",
@@ -35,14 +35,14 @@
35
35
  "packageName": "@planu/core"
36
36
  },
37
37
  "optionalDependencies": {
38
- "@planu/core-darwin-arm64": "5.3.2",
39
- "@planu/core-darwin-x64": "5.3.2",
40
- "@planu/core-linux-arm64-gnu": "5.3.2",
41
- "@planu/core-linux-arm64-musl": "5.3.2",
42
- "@planu/core-linux-x64-gnu": "5.3.2",
43
- "@planu/core-linux-x64-musl": "5.3.2",
44
- "@planu/core-win32-arm64-msvc": "5.3.2",
45
- "@planu/core-win32-x64-msvc": "5.3.2"
38
+ "@planu/core-darwin-arm64": "5.3.4",
39
+ "@planu/core-darwin-x64": "5.3.4",
40
+ "@planu/core-linux-arm64-gnu": "5.3.4",
41
+ "@planu/core-linux-arm64-musl": "5.3.4",
42
+ "@planu/core-linux-x64-gnu": "5.3.4",
43
+ "@planu/core-linux-x64-musl": "5.3.4",
44
+ "@planu/core-win32-arm64-msvc": "5.3.4",
45
+ "@planu/core-win32-x64-msvc": "5.3.4"
46
46
  },
47
47
  "engines": {
48
48
  "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": "5.3.2",
4
+ "version": "5.3.4",
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": "5.3.2",
5
+ "version": "5.3.4",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": ["npx", "@planu/cli@latest"],
8
8
  "packageName": "@planu/cli",