@planu/cli 5.6.0 → 5.7.1

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 (52) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/.planu-build.json +1 -1
  3. package/dist/cli/commands/telemetry.d.ts +3 -0
  4. package/dist/cli/commands/telemetry.js +118 -0
  5. package/dist/cli/router.js +3 -1
  6. package/dist/config/environment-schema.json +14 -0
  7. package/dist/engine/contradiction-detector.d.ts +2 -1
  8. package/dist/engine/contradiction-detector.js +215 -0
  9. package/dist/engine/detectors/cache-db-detector.js +2 -2
  10. package/dist/engine/detectors/newsql-db-detector.js +2 -2
  11. package/dist/engine/detectors/search-engine-detector.js +2 -2
  12. package/dist/engine/detectors/vector-db-detector.js +2 -2
  13. package/dist/engine/detectors/widecolumn-db-detector.js +2 -2
  14. package/dist/engine/handoff-artifacts/schemas.js +4 -0
  15. package/dist/engine/housekeeping/legacy-planu-demolisher.d.ts +3 -0
  16. package/dist/engine/housekeeping/legacy-planu-demolisher.js +164 -0
  17. package/dist/engine/lifecycle-reconciliation.js +87 -40
  18. package/dist/engine/readiness-checker.js +13 -1
  19. package/dist/engine/telemetry/error-reporter.d.ts +9 -9
  20. package/dist/engine/telemetry/error-reporter.js +15 -34
  21. package/dist/engine/telemetry/event-envelope.d.ts +11 -0
  22. package/dist/engine/telemetry/event-envelope.js +124 -0
  23. package/dist/engine/telemetry/telemetry-client.d.ts +8 -1
  24. package/dist/engine/telemetry/telemetry-client.js +38 -20
  25. package/dist/engine/telemetry/telemetry-store.d.ts +15 -2
  26. package/dist/engine/telemetry/telemetry-store.js +73 -2
  27. package/dist/engine/validator/reliability-gate.d.ts +4 -0
  28. package/dist/engine/validator/reliability-gate.js +93 -0
  29. package/dist/engine/validator/spec-compliance-runner.d.ts +2 -1
  30. package/dist/engine/validator/spec-compliance-runner.js +78 -1
  31. package/dist/index.js +26 -0
  32. package/dist/storage/migrations/canonical-storage.js +22 -9
  33. package/dist/tools/challenge-spec.js +25 -10
  34. package/dist/tools/init-project/handler.js +2 -2
  35. package/dist/tools/init-project/legacy-planu.d.ts +2 -0
  36. package/dist/tools/init-project/legacy-planu.js +18 -0
  37. package/dist/tools/init-project/schedule-housekeeping.d.ts +2 -0
  38. package/dist/tools/init-project/schedule-housekeeping.js +8 -0
  39. package/dist/tools/reconcile-spec.js +29 -2
  40. package/dist/tools/register-spec-tools/analysis-tools.d.ts +7 -0
  41. package/dist/tools/register-spec-tools/analysis-tools.js +13 -1
  42. package/dist/tools/safe-handler.js +6 -12
  43. package/dist/tools/validate.js +36 -1
  44. package/dist/types/handoff-artifacts.d.ts +1 -0
  45. package/dist/types/housekeeping.d.ts +34 -0
  46. package/dist/types/housekeeping.js +0 -1
  47. package/dist/types/scope.d.ts +23 -0
  48. package/dist/types/spec/inputs.d.ts +9 -0
  49. package/dist/types/telemetry.d.ts +39 -1
  50. package/dist/types/validation-evidence.d.ts +18 -0
  51. package/package.json +1 -1
  52. package/planu-plugin.json +1 -1
@@ -0,0 +1,8 @@
1
+ // tools/init-project/schedule-housekeeping.ts — SPEC-1709
2
+ import { scheduleRuntimeResidueSweep } from './runtime-residue.js';
3
+ import { scheduleLegacyPlanuDemolition } from './legacy-planu.js';
4
+ export function scheduleHousekeepingSweeps(projectPath) {
5
+ scheduleRuntimeResidueSweep(projectPath);
6
+ scheduleLegacyPlanuDemolition(projectPath);
7
+ }
8
+ //# sourceMappingURL=schedule-housekeeping.js.map
@@ -1,5 +1,5 @@
1
1
  import { SECTIONS_WITHOUT_LITERAL_BODY_TEXT, } from '../types/index.js';
2
- import { createHash } from 'node:crypto';
2
+ import { createHash, randomUUID } from 'node:crypto';
3
3
  import { elicitOrFallback, buildEnumSchema } from '../engine/elicitation/elicit-helper.js';
4
4
  import { ti, t } from '../i18n/index.js';
5
5
  import { formatSuccess, addNextSteps } from './response-helpers.js';
@@ -14,6 +14,9 @@ import { analyzeLivingSpec } from '../engine/living-spec-analyzer.js';
14
14
  import { notifyStoreChange } from '../engine/doc-generator/portal/regen-hook.js';
15
15
  import { applyChangesToSpec } from '../engine/reconcile/apply-changes.js';
16
16
  import { verifyWriteSucceeded } from '../engine/reconcile/verify-write.js';
17
+ import { reconcileImplementingSpec, currentReconciliationInvocationContext, } from '../engine/lifecycle-reconciliation.js';
18
+ import { reconciliationDigest } from '../engine/lifecycle-reconciliation-io.js';
19
+ import { syncSpecFiles } from './update-status/file-sync.js';
17
20
  function detectEstimationDrift(spec) {
18
21
  if (!spec.actuals) {
19
22
  return null;
@@ -322,8 +325,29 @@ function applyConflictResolution(resolution, allChanges, pendingChanges) {
322
325
  }
323
326
  }
324
327
  }
328
+ async function routeDeclaredArchitecturalDrift(spec, projectId, projectPath, declaredDrift) {
329
+ const reason = declaredDrift.reason;
330
+ const input = {
331
+ specId: spec.id,
332
+ projectId,
333
+ projectPath,
334
+ status: 'review',
335
+ reconciliationRequestId: randomUUID(),
336
+ expectedImplementingTransitionId: spec.statusHistory?.at(-1)?.transitionId,
337
+ implementationReviewDigest: reconciliationDigest(reason.trim()),
338
+ reason,
339
+ declaredDriftKind: declaredDrift.kind,
340
+ };
341
+ return reconcileImplementingSpec({
342
+ input,
343
+ projectId,
344
+ projectPath,
345
+ context: currentReconciliationInvocationContext(),
346
+ syncSpecFiles,
347
+ });
348
+ }
325
349
  export async function handleReconcileSpec(params, server) {
326
- const { specId, projectId, autoDetect = true, livingSpec = false, changes: manualChanges, } = params;
350
+ const { specId, projectId, autoDetect = true, livingSpec = false, changes: manualChanges, declaredDrift, } = params;
327
351
  try {
328
352
  const spec = await specStore.getSpec(projectId, specId);
329
353
  if (!spec) {
@@ -333,6 +357,9 @@ export async function handleReconcileSpec(params, server) {
333
357
  };
334
358
  }
335
359
  const knowledge = await knowledgeStore.getKnowledge(projectId);
360
+ if (declaredDrift) {
361
+ return await routeDeclaredArchitecturalDrift(spec, projectId, knowledge?.projectPath, declaredDrift);
362
+ }
336
363
  const allChanges = [];
337
364
  if (autoDetect) {
338
365
  const autoChanges = await autoDetectChanges(spec, knowledge);
@@ -1,4 +1,11 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ export declare const DeclaredDriftInputSchema: z.ZodObject<{
4
+ kind: z.ZodEnum<{
5
+ "architectural-premise": "architectural-premise";
6
+ }>;
7
+ reason: z.ZodString;
8
+ }, z.core.$strip>;
2
9
  /**
3
10
  * Registers drift detection, analysis, and discovery tools (tools 11–19) on the MCP server.
4
11
  */
@@ -7,6 +7,7 @@ import { handleDetectDrift } from '../detect-drift.js';
7
7
  import { handleSummarizeSpec } from '../summarize-spec.js';
8
8
  import { handleGenerateChecklist } from '../generate-checklist.js';
9
9
  import { handleReconcileSpec } from '../reconcile-spec.js';
10
+ import { runWithTrustedLocalMcpContext } from '../../engine/lifecycle-reconciliation.js';
10
11
  import { handleLearn } from '../learn.js';
11
12
  import { handleAudit } from '../audit.js';
12
13
  import { handleConsultDocs } from '../consult-docs.js';
@@ -17,6 +18,16 @@ import { handleSuggestMcpServer } from '../suggest-mcp-server.js';
17
18
  import { handleScanOrphanSpecRefs, ScanOrphanSpecRefsInputSchema, } from '../scan-orphan-spec-refs.js';
18
19
  import { registerGraphSpecsTool } from '../graph-specs.js';
19
20
  import { registerAuditSpecsDriftTool } from '../audit-specs-drift.js';
21
+ export const DeclaredDriftInputSchema = z.object({
22
+ kind: z
23
+ .enum(['architectural-premise'])
24
+ .describe('Drift kind. Only "architectural-premise" is supported: an orchestrator-declared premise contradiction that produces no auto-detectable file/scope drift.'),
25
+ reason: z
26
+ .string()
27
+ .min(100)
28
+ .max(10_000)
29
+ .describe('Why the implementation drifted from the approved architectural premise. Minimum 100 characters — becomes the reconciliation transition reason.'),
30
+ });
20
31
  import { handleSsrBackMigration } from '../ssr-back-migration.js';
21
32
  import { registerMigrateLegacySpecTool } from '../migrate-legacy-spec.js';
22
33
  /**
@@ -130,13 +141,14 @@ export function registerAnalysisTools(server) {
130
141
  .max(1000)
131
142
  .optional()
132
143
  .describe('Manual changes to apply'),
144
+ declaredDrift: DeclaredDriftInputSchema.optional().describe('When the spec is implementing, routes reconcile_spec into the existing implementing→review reconciliation demotion instead of auto-detected drift.'),
133
145
  },
134
146
  }, safeGoverned('reconcile_spec', async (args) => {
135
147
  const pid = resolveProjectId(args);
136
148
  if (!pid) {
137
149
  return missingProjectIdError;
138
150
  }
139
- return handleReconcileSpec({ ...args, projectId: pid });
151
+ return runWithTrustedLocalMcpContext(() => handleReconcileSpec({ ...args, projectId: pid }));
140
152
  }));
141
153
  // 15. learn_pattern
142
154
  server.registerTool('learn_pattern', {
@@ -7,8 +7,7 @@ import { ensureWorkersStarted } from '../engine/workers/index.js';
7
7
  import { recordError } from '../storage/error-telemetry-store.js';
8
8
  import { hashProjectPath } from '../storage/base-store.js';
9
9
  import { reportToolError, reportToolValidationError } from '../engine/telemetry/error-reporter.js';
10
- import { sendTelemetryEvent } from '../engine/telemetry/telemetry-client.js';
11
- import { PLANU_VERSION } from '../config/version.js';
10
+ import { sendTelemetryEnvelopeEvent } from '../engine/telemetry/telemetry-client.js';
12
11
  import { recordToolTokens, extractOutputText } from './token-recording.js';
13
12
  import { DriftCacheStore } from '../storage/drift-cache-store.js';
14
13
  import { consumeUpdateBanner } from '../engine/update-notifier.js';
@@ -509,18 +508,13 @@ function safeWithTelemetry(toolName, handler) {
509
508
  const outputText = extractOutputText(processedResult);
510
509
  recordLlmTokens(toolName, inputText, outputText);
511
510
  }
512
- // Emit tool_used telemetry for successful calls (fire-and-forget)
511
+ // Emit mcp_tool_completed envelope for successful calls (fire-and-forget)
513
512
  /* v8 ignore start */
514
513
  if (toolName !== undefined && processedResult.isError !== true) {
515
- sendTelemetryEvent({
516
- event: 'tool_used',
517
- properties: {
518
- tool: toolName,
519
- planVersion: PLANU_VERSION,
520
- nodeVersion: process.version,
521
- platform: process.platform,
522
- durationMs: Date.now() - startTime,
523
- },
514
+ sendTelemetryEnvelopeEvent('mcp_tool_completed', {
515
+ toolName,
516
+ result: 'success',
517
+ durationMs: Date.now() - startTime,
524
518
  });
525
519
  }
526
520
  /* v8 ignore stop */
@@ -58,6 +58,7 @@ import { compactObj } from '../engine/compact-obj.js';
58
58
  import { resolveProjectId, missingProjectIdError } from './resolve-project-id.js';
59
59
  import { specStore, knowledgeStore } from '../storage/index.js';
60
60
  import { validateSpec, generateDoR, generateDoD } from '../engine/validator.js';
61
+ import { scanCodeForSpec } from '../engine/validator/analyzer.js';
61
62
  import { isBlockingQualitySeverity } from '../engine/validator/dor-dod.js';
62
63
  import { readVerifiedImplementationReview } from '../engine/handoff-artifacts/implementation-review-reader.js';
63
64
  import { calcQualityScore } from '../engine/auditor-scoring.js';
@@ -69,6 +70,7 @@ import { parseConventions, scanConventions } from '../engine/convention-scanner/
69
70
  import { validateScopeCompliance } from '../engine/scope-boundaries/index.js';
70
71
  import { compareWithBaseline } from '../storage/convention-baseline.js';
71
72
  import { writeImplementationReviewReport } from '../engine/validator/validation-report-writer.js';
73
+ import { runReliabilityGate } from '../engine/validator/reliability-gate.js';
72
74
  import { buildGraphCoverageReport, formatGraphCoverageText } from './validate-graph-coverage.js';
73
75
  import { buildMinimalityReport } from './validate-minimality.js';
74
76
  import { buildValidateFailureFallback, validateStrictLayoutOrError } from './validate-helpers.js';
@@ -85,7 +87,7 @@ import { getRuntimePolicy } from '../engine/runtime-policy.js';
85
87
  import { resolveValidationWorktree, ValidationWorktreeError, } from '../engine/validation/validation-worktree.js';
86
88
  // Re-export for external use (SPEC-018)
87
89
  export { validateContractCompliance };
88
- const VALIDATION_GATE_TOTAL = 8;
90
+ const VALIDATION_GATE_TOTAL = 9;
89
91
  const GATE_DEADLINE_EXEMPT_PHASES = new Set(['lint', 'compliance']);
90
92
  async function runWithGateDeadline(phase, command, work) {
91
93
  if (GATE_DEADLINE_EXEMPT_PHASES.has(phase)) {
@@ -360,6 +362,10 @@ export async function executeValidate(args, server, onProgress) {
360
362
  });
361
363
  const implementationQualityScore = calcQualityScore(result.qualityIssues);
362
364
  const auditedFiles = [...new Set(result.qualityIssues.map((i) => i.file))];
365
+ const reliabilityGate = await runStage('reliability', 'scripts/check-reliability-policies.mjs --json', async () => {
366
+ const codeState = await scanCodeForSpec(executionSpec, projectPath);
367
+ return runReliabilityGate(projectPath, codeState.affectedFiles);
368
+ });
363
369
  const { conventionViolations, regressionDetected } = await runStage('conventions', 'validate project conventions', () => scanProjectConventions(projectId, projectPath));
364
370
  const lintCheck = await runStage('lint', knowledge.lintCommand ?? 'pnpm lint', () => runLintCheck(projectPath, knowledge.lintCommand ?? null, {
365
371
  projectId,
@@ -397,6 +403,7 @@ export async function executeValidate(args, server, onProgress) {
397
403
  lintPassed: lintCheck.passed,
398
404
  assurancePassed: assuranceGates.newCode.passed,
399
405
  minimalityBlocked: minimalityReport?.blocked === true,
406
+ reliabilityPassed: reliabilityGate.passed,
400
407
  score: effectiveResult.score,
401
408
  });
402
409
  const output = {
@@ -492,6 +499,26 @@ export async function executeValidate(args, server, onProgress) {
492
499
  });
493
500
  }
494
501
  }
502
+ for (const failure of reliabilityGate.failures) {
503
+ output.qualityIssues.push({
504
+ file: failure.file,
505
+ line: failure.line,
506
+ severity: 'critical',
507
+ rule: failure.rule,
508
+ message: `Reliability gate violation: ${failure.rule}`,
509
+ suggestion: 'Fix the release-gate reliability violation before marking this spec done.',
510
+ });
511
+ }
512
+ for (const warning of reliabilityGate.warnings) {
513
+ output.qualityIssues.push({
514
+ file: warning.file,
515
+ line: warning.line,
516
+ severity: 'warning',
517
+ rule: warning.rule,
518
+ message: `Reliability gate violation in an unrelated file: ${warning.rule}`,
519
+ suggestion: 'Not blocking this spec — file is outside the spec Files section.',
520
+ });
521
+ }
495
522
  // Auto-suggest corrective actions based on validation results
496
523
  // score is null when no criteria can be extracted — treat as 0 for comparisons.
497
524
  const scoreValue = effectiveResult.score ?? 0;
@@ -523,6 +550,12 @@ export async function executeValidate(args, server, onProgress) {
523
550
  if (!lintCheck.passed) {
524
551
  suggestions.push(`Lint check failed: ${lintCheck.issueCount} issue(s) found via \`${lintCheck.command}\`. Fix before marking as done.`);
525
552
  }
553
+ if (reliabilityGate.executionError !== undefined) {
554
+ suggestions.push(`Reliability gate could not run: ${reliabilityGate.executionError}. Fix the gate before marking as done.`);
555
+ }
556
+ else if (!reliabilityGate.passed) {
557
+ suggestions.push(`Reliability gate failed: ${String(reliabilityGate.failures.length)} violation(s) in this spec's files. Fix before marking as done.`);
558
+ }
526
559
  if (!assuranceGates.newCode.passed) {
527
560
  suggestions.push(`Assurance gate failed: ${String(assuranceGates.newCode.findings.length)} new-code domain inference issue(s) found.`);
528
561
  }
@@ -673,6 +706,7 @@ function buildQualityGateSummary(input) {
673
706
  ...(!input.lintPassed ? ['lint'] : []),
674
707
  ...(!input.assurancePassed ? ['assurance'] : []),
675
708
  ...(input.minimalityBlocked ? ['minimality'] : []),
709
+ ...(!input.reliabilityPassed ? ['reliability'] : []),
676
710
  ];
677
711
  return {
678
712
  passed: failures.length === 0,
@@ -683,6 +717,7 @@ function buildQualityGateSummary(input) {
683
717
  lintPassed: input.lintPassed,
684
718
  assurancePassed: input.assurancePassed,
685
719
  minimalityPassed: !input.minimalityBlocked,
720
+ reliabilityPassed: input.reliabilityPassed,
686
721
  };
687
722
  }
688
723
  function buildExecutableCoverage(specCompliance) {
@@ -124,6 +124,7 @@ export interface ReconciliationReceiptV1 {
124
124
  transitionId?: string;
125
125
  phase?: 'prepared' | 'status-committed' | 'spec-synced' | 'audit-appended';
126
126
  auditPending?: boolean;
127
+ driftSource?: 'declared-architectural-premise';
127
128
  }
128
129
  /** Injectable receipt persistence boundary used only for reconciliation crash tests. */
129
130
  export interface ReconciliationIo {
@@ -1,3 +1,4 @@
1
+ import type { GlobalProjectsRegistry, RegisteredProject } from './cross-repo-search.js';
1
2
  export interface StaleBranchInfo {
2
3
  /** Short ref name, e.g. 'tmp-foo' or 'feat/spec-100-bar'. */
3
4
  name: string;
@@ -150,4 +151,37 @@ export interface RuntimeResidueSweepResult {
150
151
  /** Absolute paths of every legacy runtime artifact removed from planu/. */
151
152
  removed: string[];
152
153
  }
154
+ export interface LegacyPlanuDemolitionFailure {
155
+ /** Directory name under <legacyRoot>/data/projects. */
156
+ dir: string;
157
+ /** Human-readable reason the directory could not be migrated. */
158
+ reason: string;
159
+ }
160
+ export type LegacyPlanuDemolitionStatus = 'demolished' | 'retained' | 'absent';
161
+ export interface LegacyPlanuDemolitionReport {
162
+ status: LegacyPlanuDemolitionStatus;
163
+ /** Directory names successfully migrated into their canonical destination. */
164
+ migrated: string[];
165
+ /** Count of unmappable directories deleted outright. */
166
+ demolishedUnmappable: number;
167
+ /** Per-directory migration failures; the root is retained when this is non-empty. */
168
+ failures: LegacyPlanuDemolitionFailure[];
169
+ /** Bytes freed by deleting the whole legacy root (0 unless status is 'demolished'). */
170
+ freedBytes: number;
171
+ }
172
+ export interface LegacyPlanuDemolisherOptions {
173
+ /** Legacy root to sweep. Defaults to join(homedir(), '.planu'). */
174
+ legacyRoot?: string;
175
+ /** Global cross-repo registry. Defaults to a live read via getRegistry(). */
176
+ registry?: GlobalProjectsRegistry;
177
+ /** Resolves a registered project's canonical storage destination. */
178
+ resolveDestination?: (project: RegisteredProject) => Promise<string>;
179
+ }
180
+ /** Internal accumulator returned by the data/projects sweep pass. */
181
+ export interface LegacyPlanuSweepResult {
182
+ migrated: string[];
183
+ demolishedUnmappable: number;
184
+ failures: LegacyPlanuDemolitionFailure[];
185
+ freedBytes: number;
186
+ }
153
187
  //# sourceMappingURL=housekeeping.d.ts.map
@@ -1,3 +1,2 @@
1
- // types/housekeeping.ts — SPEC-751: Housekeeping sweep types
2
1
  export {};
3
2
  //# sourceMappingURL=housekeeping.js.map
@@ -126,4 +126,27 @@ export interface ContradictionPatternDef {
126
126
  /** Recommended resolution action. */
127
127
  recommendation: string;
128
128
  }
129
+ /** A pair of antonymic direction phrases used to detect cross-spec premise drift (SPEC-1702). */
130
+ export interface DirectionalPhrasePair {
131
+ /** One side of the directional claim, e.g. "re-roots at". */
132
+ phraseA: string;
133
+ /** The opposite side of the directional claim, e.g. "relocates to". */
134
+ phraseB: string;
135
+ }
136
+ /**
137
+ * A contradiction between a spec's body claim about a done/approved sibling spec
138
+ * and that sibling's own delivered contract (SPEC-1702).
139
+ */
140
+ export interface CrossSpecPremiseFinding {
141
+ /** ID of the spec containing the premise claim (e.g. "SPEC-1698"). */
142
+ targetSpecId: string;
143
+ /** ID of the referenced sibling spec whose contract contradicts the claim (e.g. "SPEC-1695"). */
144
+ siblingSpecId: string;
145
+ /** The exact sentence in the target spec asserting the contradicted claim. */
146
+ targetSentence: string;
147
+ /** The exact sentence in the sibling spec's own body stating the opposite direction. */
148
+ siblingSentence: string;
149
+ /** File path shared between both specs' ## Files sections, evidencing the same subject. */
150
+ sharedFilePath: string;
151
+ }
129
152
  //# sourceMappingURL=scope.d.ts.map
@@ -109,6 +109,8 @@ export interface UpdateStatusInput {
109
109
  * When true, the transition proceeds and qualityWarnings[] is written to spec.md frontmatter.
110
110
  */
111
111
  forceApprove?: boolean;
112
+ /** Marker set by reconcile_spec's declared-drift route; accepts the declared reason as reconciliation evidence in place of validation-report.json. */
113
+ declaredDriftKind?: 'architectural-premise';
112
114
  }
113
115
  /**
114
116
  * SPEC-769: Output from the readiness gate check in update_status(approved).
@@ -159,12 +161,19 @@ export interface GenerateChecklistInput {
159
161
  focus?: ChecklistCategory[];
160
162
  experienceLevel?: 'beginner' | 'expert';
161
163
  }
164
+ /** An orchestrator-declared drift that produces no auto-detectable file/scope change. */
165
+ export interface DeclaredSpecDrift {
166
+ kind: 'architectural-premise';
167
+ /** Why the implementation drifted from the approved premise. Minimum 100 characters. */
168
+ reason: string;
169
+ }
162
170
  export interface ReconcileSpecInput {
163
171
  specId: string;
164
172
  projectId: string;
165
173
  autoDetect?: boolean;
166
174
  livingSpec?: boolean;
167
175
  changes?: ReconcileChange[];
176
+ declaredDrift?: DeclaredSpecDrift;
168
177
  }
169
178
  export interface ClarifyRequirementsInput {
170
179
  projectId: string;
@@ -4,11 +4,47 @@ export interface GeoData {
4
4
  city: string;
5
5
  timezone: string;
6
6
  }
7
+ /** SPEC-1704: the six allowlisted schemaVersion-1 envelope event names. */
8
+ export type TelemetryEnvelopeEventName = 'installation_activated' | 'mcp_server_started' | 'mcp_tool_completed' | 'mcp_tool_failed' | 'spec_lifecycle_transitioned' | 'release_check_completed';
7
9
  export type TelemetryEventName = 'init_project' | 'create_spec' | 'tool_used' | 'tool_error' | 'tool_blocked'
8
10
  /** SPEC-705: Emitted when agent parallelism is downgraded due to host RAM constraints. */
9
11
  | 'ram_guardrail_downgrade'
10
12
  /** SPEC-727: Emitted when a panel is aborted because two specialists share the same (modelId, promptHash). */
11
- | 'panel_heterogeneity_violation';
13
+ | 'panel_heterogeneity_violation' | TelemetryEnvelopeEventName;
14
+ export type TelemetryEnvelopeResult = 'success' | 'error' | 'blocked' | 'cancelled';
15
+ export type TelemetryDurationBucket = 'lt_100ms' | '100ms_1s' | '1s_5s' | 'gte_5s';
16
+ export type TelemetryOperatingSystem = 'darwin' | 'linux' | 'win32' | 'other';
17
+ export type TelemetryMcpHost = 'claude-code' | 'claude-desktop' | 'cursor' | 'codex' | 'other' | 'unknown';
18
+ /** SPEC-1704: the exact, closed shape of a schemaVersion-1 telemetry event. */
19
+ export interface TelemetryEnvelopeV1 {
20
+ schemaVersion: 1;
21
+ eventId: string;
22
+ eventName: TelemetryEnvelopeEventName;
23
+ occurredAt: string;
24
+ anonymousInstallationId: string;
25
+ sessionId: string;
26
+ planuVersion: string;
27
+ toolName?: string;
28
+ result?: TelemetryEnvelopeResult;
29
+ durationBucket?: TelemetryDurationBucket;
30
+ operatingSystem: TelemetryOperatingSystem;
31
+ nodeMajor: number;
32
+ mcpHost: TelemetryMcpHost;
33
+ }
34
+ /** Caller-supplied fields for buildTelemetryEnvelope; unknown keys are rejected. */
35
+ export interface TelemetryEnvelopeInput {
36
+ anonymousInstallationId: string;
37
+ sessionId: string;
38
+ toolName?: string;
39
+ result?: TelemetryEnvelopeResult;
40
+ durationMs?: number;
41
+ mcpHost?: TelemetryMcpHost;
42
+ }
43
+ /** Injectable determinism for tests and the `planu telemetry show` command. */
44
+ export interface TelemetryEnvelopeBuildOptions {
45
+ uuid?: () => string;
46
+ clock?: () => Date;
47
+ }
12
48
  export interface TelemetryConfig {
13
49
  enabled: boolean;
14
50
  /** ISO timestamp of when the user was first shown the opt-in prompt */
@@ -17,6 +53,8 @@ export interface TelemetryConfig {
17
53
  consentVersion?: number;
18
54
  /** SPEC-572: Opt-in to cross-project Observatory aggregated insights (requires telemetry enabled) */
19
55
  enableObservatory?: boolean;
56
+ /** SPEC-1704: generated on enable, deleted on disable, regenerated fresh on the next enable. */
57
+ anonymousInstallationId?: string;
20
58
  }
21
59
  export interface TelemetryEvent {
22
60
  event: TelemetryEventName;
@@ -83,12 +83,18 @@ export interface PerScenarioResult {
83
83
  evidence: string[];
84
84
  unverifiable?: true;
85
85
  }
86
+ export interface DoneDriftMissingFileFinding {
87
+ kind: 'done-drift-missing-files';
88
+ specId: string;
89
+ missingPath: string;
90
+ }
86
91
  export interface SpecComplianceResult {
87
92
  dimensionScore: number;
88
93
  perScenario: PerScenarioResult[];
89
94
  command: string;
90
95
  evidenceSource?: 'frontmatter' | 'criteria' | 'none';
91
96
  ignoredCriteriaTitles?: string[];
97
+ doneDriftFindings?: DoneDriftMissingFileFinding[];
92
98
  }
93
99
  export interface EvidenceTestLink {
94
100
  path: string;
@@ -104,4 +110,16 @@ export interface ExecutableEvidence {
104
110
  unmappedCriteria: string[];
105
111
  ignoredCriteriaTitles: string[];
106
112
  }
113
+ export interface ReliabilityGateViolation {
114
+ file: string;
115
+ line: number;
116
+ rule: string;
117
+ }
118
+ export interface ReliabilityGateResult {
119
+ skipped: boolean;
120
+ passed: boolean;
121
+ failures: ReliabilityGateViolation[];
122
+ warnings: ReliabilityGateViolation[];
123
+ executionError?: string;
124
+ }
107
125
  //# sourceMappingURL=validation-evidence.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "5.6.0",
3
+ "version": "5.7.1",
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",
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.6.0",
5
+ "version": "5.7.1",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": ["npx", "@planu/cli@latest"],
8
8
  "packageName": "@planu/cli",