@planu/cli 5.3.4 → 5.3.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 (76) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/engine/execution/validate-job-executor.js +25 -10
  3. package/dist/engine/hooks/handlers/on-impl-change.d.ts +17 -4
  4. package/dist/engine/hooks/handlers/on-impl-change.js +43 -44
  5. package/dist/engine/living-spec-analyzer.d.ts +5 -2
  6. package/dist/engine/living-spec-analyzer.js +5 -2
  7. package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
  8. package/dist/engine/planu-core.darwin-arm64.node.sbom.json +4 -4
  9. package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
  10. package/dist/engine/planu-core.darwin-x64.node.sbom.json +4 -4
  11. package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
  12. package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +4 -4
  13. package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
  14. package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +4 -4
  15. package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
  16. package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +4 -4
  17. package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
  18. package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +4 -4
  19. package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
  20. package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +4 -4
  21. package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
  22. package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +4 -4
  23. package/dist/engine/reconcile/apply-changes.d.ts +0 -12
  24. package/dist/engine/reconcile/apply-changes.js +11 -33
  25. package/dist/engine/reconcile/verify-write.js +2 -3
  26. package/dist/engine/rules-reconciler.js +21 -2
  27. package/dist/engine/scope-boundaries/contradiction-checker.js +20 -13
  28. package/dist/engine/spec-format/acceptance-criteria.js +11 -2
  29. package/dist/engine/spec-migrator/filesystem-import.js +113 -73
  30. package/dist/engine/spec-migrator/frontmatter-parser.js +15 -13
  31. package/dist/engine/universal-rules/catalog.js +6 -0
  32. package/dist/engine/universal-rules/rules/agent-teams.js +0 -2
  33. package/dist/engine/universal-rules/rules/planu-approval-gates.js +0 -1
  34. package/dist/engine/universal-rules/rules/planu-bdd-criteria.js +0 -1
  35. package/dist/engine/universal-rules/rules/planu-clean-code-no-comments.d.ts +3 -0
  36. package/dist/engine/universal-rules/rules/planu-clean-code-no-comments.js +30 -0
  37. package/dist/engine/universal-rules/rules/planu-debate-review.d.ts +3 -0
  38. package/dist/engine/universal-rules/rules/planu-debate-review.js +38 -0
  39. package/dist/engine/universal-rules/rules/planu-dogfood-bugs.js +0 -2
  40. package/dist/engine/universal-rules/rules/planu-english-specs.js +0 -1
  41. package/dist/engine/universal-rules/rules/planu-minimal-change.js +0 -2
  42. package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
  43. package/dist/engine/universal-rules/rules/planu-release-policy.js +0 -1
  44. package/dist/engine/universal-rules/rules/planu-revert-proof-tests.d.ts +3 -0
  45. package/dist/engine/universal-rules/rules/planu-revert-proof-tests.js +34 -0
  46. package/dist/engine/universal-rules/rules/planu-sdd-model-routing.js +0 -1
  47. package/dist/engine/universal-rules/rules/planu-workflow.js +0 -2
  48. package/dist/engine/validator/validation-report-writer.js +1 -1
  49. package/dist/tools/auto-reconcile.js +1 -1
  50. package/dist/tools/challenge-spec/challenge-report.js +1 -1
  51. package/dist/tools/init-project/portable-index-reconciler.js +2 -1
  52. package/dist/tools/reconcile-spec.d.ts +1 -1
  53. package/dist/tools/reconcile-spec.js +306 -179
  54. package/dist/tools/safe-handler.js +0 -2
  55. package/dist/tools/start-hooks/engine.d.ts +2 -3
  56. package/dist/tools/start-hooks/engine.js +50 -33
  57. package/dist/tools/start-hooks.d.ts +1 -1
  58. package/dist/tools/start-hooks.js +1 -2
  59. package/dist/tools/tool-registry/group-infra.js +36 -0
  60. package/dist/tools/update-status/dod-gates.js +3 -3
  61. package/dist/tools/update-status/transition-guard.js +17 -4
  62. package/dist/types/file-hooks.d.ts +4 -6
  63. package/dist/types/index.d.ts +0 -1
  64. package/dist/types/index.js +0 -1
  65. package/dist/types/mcp.d.ts +0 -19
  66. package/dist/types/reconcile.d.ts +5 -17
  67. package/dist/types/reconcile.js +1 -7
  68. package/dist/types/spec/core.d.ts +5 -0
  69. package/dist/types/spec/inputs.d.ts +16 -0
  70. package/package.json +9 -9
  71. package/planu-native.json +1 -1
  72. package/planu-plugin.json +1 -1
  73. package/dist/engine/hooks/agent-hook-migrator.d.ts +0 -9
  74. package/dist/engine/hooks/agent-hook-migrator.js +0 -140
  75. package/dist/types/hook-status-update.d.ts +0 -10
  76. package/dist/types/hook-status-update.js +0 -3
@@ -1,13 +1,12 @@
1
1
  import type { ToolResult } from '../../types/index.js';
2
- import type { StartHooksInput, StopHooksInput } from '../../types/file-hooks.js';
2
+ import type { StartHooksInput, StopHooksInput, HookStatusInput } from '../../types/file-hooks.js';
3
3
  import { HookEngine } from '../../engine/hooks/hook-engine.js';
4
4
  import { FileWatcher } from '../../engine/hooks/file-watcher.js';
5
5
  import { ConfigLoader } from '../../engine/hooks/config-loader.js';
6
6
  export declare function getEngineInstance(): HookEngine | null;
7
7
  export declare function getWatcherInstance(): FileWatcher | null;
8
8
  export declare function getConfigLoaderInstance(): ConfigLoader | null;
9
- export declare function ensureHooksStarted(projectPath: string): void;
10
9
  export declare function handleStartHooks(args: StartHooksInput): Promise<ToolResult>;
11
10
  export declare function handleStopHooks(_args: StopHooksInput): Promise<ToolResult>;
12
- export declare function handleHookStatus(): Promise<ToolResult>;
11
+ export declare function handleHookStatus(args?: HookStatusInput): Promise<ToolResult>;
13
12
  //# sourceMappingURL=engine.d.ts.map
@@ -4,7 +4,7 @@ import { EventBus } from '../../engine/hooks/event-bus.js';
4
4
  import { FileWatcher } from '../../engine/hooks/file-watcher.js';
5
5
  import { ConfigLoader } from '../../engine/hooks/config-loader.js';
6
6
  import { Debouncer } from '../../engine/hooks/debouncer.js';
7
- import { handleOnImplChange } from '../../engine/hooks/handlers/on-impl-change.js';
7
+ import { handleOnImplChange, getLastImplChangeResult, } from '../../engine/hooks/handlers/on-impl-change.js';
8
8
  import { handleOnSpecChange } from '../../engine/hooks/handlers/on-spec-change.js';
9
9
  import { handleOnPushCheck, getLastPushCheckResult, } from '../../engine/hooks/handlers/on-push-check.js';
10
10
  import { handleOnSecurityCheck, isSecuritySensitiveFile, } from '../../engine/hooks/handlers/on-security-check.js';
@@ -13,7 +13,6 @@ import { listSpecs } from '../../storage/spec-store.js';
13
13
  import { hashProjectPath } from '../../storage/base-store.js';
14
14
  import { analyzeLivingSpec } from '../../engine/living-spec-analyzer.js';
15
15
  import { compactResult, formatKeyValue } from '../output-formatter.js';
16
- import { migrateAgentHooksIfNeeded } from '../../engine/hooks/agent-hook-migrator.js';
17
16
  // ---------------------------------------------------------------------------
18
17
  // Singleton state
19
18
  // ---------------------------------------------------------------------------
@@ -30,26 +29,27 @@ export function getWatcherInstance() {
30
29
  export function getConfigLoaderInstance() {
31
30
  return configLoaderInstance;
32
31
  }
33
- export function ensureHooksStarted(projectPath) {
34
- migrateAgentHooksIfNeeded(projectPath);
35
- if (engineInstance?.status().running) {
36
- return;
32
+ /**
33
+ * Resolve whether a hook type should run: it must be among the hooks the
34
+ * engine was explicitly started with, and the live ConfigLoader config must
35
+ * not disable it. Reads getConfig() live (not a start-time snapshot) so a
36
+ * hot config reload takes effect without restarting the engine (SPEC-1404).
37
+ * A hook type absent from config.hooks (e.g. on-impl-change in the defaults
38
+ * file) is treated as enabled, matching pre-existing behaviour.
39
+ */
40
+ function isHookEnabled(hookType, requestedHooks) {
41
+ if (!requestedHooks.includes(hookType)) {
42
+ return false;
37
43
  }
38
- void handleStartHooks({
39
- projectPath,
40
- hooks: [
41
- 'on-save',
42
- 'on-create',
43
- 'on-delete',
44
- 'on-commit',
45
- 'on-impl-change',
46
- 'on-spec-change',
47
- 'on-push-check',
48
- 'on-security-check',
49
- ],
50
- }).catch(() => {
51
- // Silently swallow — hooks are best-effort
52
- });
44
+ const config = configLoaderInstance?.getConfig();
45
+ /* v8 ignore next 3 -- defensive: unreachable while handleStartHooks owns configLoaderInstance's lifecycle */
46
+ if (!config) {
47
+ return false;
48
+ }
49
+ if (!config.enabled) {
50
+ return false;
51
+ }
52
+ return config.hooks[hookType]?.enabled !== false;
53
53
  }
54
54
  // ---------------------------------------------------------------------------
55
55
  // Start hooks
@@ -84,7 +84,7 @@ export async function handleStartHooks(args) {
84
84
  debouncerInstance = new Debouncer((event) => {
85
85
  const hookTypes = getApplicableHookTypes(event);
86
86
  for (const hookType of hookTypes) {
87
- if (enabledHooks.includes(hookType)) {
87
+ if (isHookEnabled(hookType, enabledHooks)) {
88
88
  void engineInstance?.dispatch(event, hookType);
89
89
  }
90
90
  }
@@ -100,7 +100,7 @@ export async function handleStartHooks(args) {
100
100
  configLoaderInstance.startWatching();
101
101
  engineInstance.start();
102
102
  watcherInstance.start();
103
- registerAdvancedHandlers(engineInstance, enabledHooks, projectPath, config);
103
+ registerAdvancedHandlers(engineInstance, enabledHooks, projectPath);
104
104
  const eventBusFromEngine = engineInstance.getEventBus();
105
105
  subscribeStatusChangeHandler(eventBusFromEngine,
106
106
  /* v8 ignore next 3 */ (_projectId) => Promise.resolve(true));
@@ -133,23 +133,40 @@ export async function handleStopHooks(_args) {
133
133
  // ---------------------------------------------------------------------------
134
134
  // Hook status
135
135
  // ---------------------------------------------------------------------------
136
- export async function handleHookStatus() {
136
+ export async function handleHookStatus(args = {}) {
137
137
  await Promise.resolve();
138
138
  if (!engineInstance) {
139
139
  return compactResult(formatKeyValue({ running: false, activeHooks: 0, pendingEvents: 0, eventHistory: 0 }, 'Hook Status'));
140
140
  }
141
141
  const status = engineInstance.status();
142
142
  const eventBus = engineInstance.getEventBus();
143
- return compactResult(formatKeyValue({
143
+ const summary = formatKeyValue({
144
144
  running: status.running,
145
145
  activeHooks: status.activeHooks.length,
146
146
  pendingEvents: status.pendingEvents,
147
147
  eventHistory: eventBus.getFullHistory().length,
148
- }, 'Hook Status'));
148
+ }, 'Hook Status');
149
+ const diagnostic = getLastImplChangeResult(args.projectPath);
150
+ if (!diagnostic) {
151
+ return compactResult(summary);
152
+ }
153
+ return compactResult(summary + '\n\n' + formatImplChangeDiagnostic(diagnostic));
149
154
  }
150
155
  // ---------------------------------------------------------------------------
151
156
  // Private helpers
152
157
  // ---------------------------------------------------------------------------
158
+ function formatImplChangeDiagnostic(diagnostic) {
159
+ const specLines = diagnostic.specsAnalyzed.map((s) => ` - ${s.specId}: ${String(s.completionPct)}%`);
160
+ return [
161
+ '### Last impl-change diagnostic',
162
+ `**changedFile**: ${diagnostic.changedFile}`,
163
+ `**anyAt100Percent**: ${String(diagnostic.autoReconciled)}`,
164
+ ...specLines,
165
+ '',
166
+ 'Process-local observation, never applied automatically. ' +
167
+ 'Apply it with reconcile_spec(livingSpec: true), or change lifecycle status with update_status.',
168
+ ].join('\n');
169
+ }
153
170
  function getApplicableHookTypes(event) {
154
171
  switch (event.type) {
155
172
  case 'modify':
@@ -178,7 +195,7 @@ function makeAnalyzeSpecFn() {
178
195
  status: minimal.status,
179
196
  progressPath: minimal.progressPath,
180
197
  };
181
- const report = await analyzeLivingSpec(specLike, projectPath);
198
+ const report = await analyzeLivingSpec(specLike, projectPath, { writeProgress: false });
182
199
  return {
183
200
  completionPct: report.completionPercent,
184
201
  progressUpdated: report.progressUpdated,
@@ -190,8 +207,8 @@ function makeAnalyzeSpecFn() {
190
207
  };
191
208
  };
192
209
  }
193
- function registerAdvancedHandlers(engine, enabledHooks, projectPath, config) {
194
- if (enabledHooks.includes('on-impl-change')) {
210
+ function registerAdvancedHandlers(engine, enabledHooks, projectPath) {
211
+ if (isHookEnabled('on-impl-change', enabledHooks)) {
195
212
  engine.register({
196
213
  name: 'on-impl-change',
197
214
  hookType: 'on-impl-change',
@@ -206,7 +223,7 @@ function registerAdvancedHandlers(engine, enabledHooks, projectPath, config) {
206
223
  catch {
207
224
  // Spec load failure — proceed without analysis
208
225
  }
209
- const analysisResult = await handleOnImplChange(event, projectPath, specs, makeAnalyzeSpecFn(), config.autoMarkDone !== false);
226
+ const analysisResult = await handleOnImplChange(event, projectPath, specs, makeAnalyzeSpecFn());
210
227
  const firstSpecId = analysisResult
211
228
  ? (analysisResult.specsAnalyzed[0]?.specId ?? null)
212
229
  : null;
@@ -220,7 +237,7 @@ function registerAdvancedHandlers(engine, enabledHooks, projectPath, config) {
220
237
  },
221
238
  });
222
239
  }
223
- if (enabledHooks.includes('on-spec-change')) {
240
+ if (isHookEnabled('on-spec-change', enabledHooks)) {
224
241
  engine.register({
225
242
  name: 'on-spec-change',
226
243
  hookType: 'on-spec-change',
@@ -237,7 +254,7 @@ function registerAdvancedHandlers(engine, enabledHooks, projectPath, config) {
237
254
  },
238
255
  });
239
256
  }
240
- if (enabledHooks.includes('on-push-check')) {
257
+ if (isHookEnabled('on-push-check', enabledHooks)) {
241
258
  engine.register({
242
259
  name: 'on-push-check',
243
260
  hookType: 'on-push-check',
@@ -265,7 +282,7 @@ function registerAdvancedHandlers(engine, enabledHooks, projectPath, config) {
265
282
  },
266
283
  });
267
284
  }
268
- if (enabledHooks.includes('on-security-check')) {
285
+ if (isHookEnabled('on-security-check', enabledHooks)) {
269
286
  engine.register({
270
287
  name: 'on-security-check',
271
288
  hookType: 'on-security-check',
@@ -1,3 +1,3 @@
1
- export { getEngineInstance, getWatcherInstance, getConfigLoaderInstance, ensureHooksStarted, handleStartHooks, handleStopHooks, handleHookStatus, } from './start-hooks/engine.js';
1
+ export { getEngineInstance, getWatcherInstance, getConfigLoaderInstance, handleStartHooks, handleStopHooks, handleHookStatus, } from './start-hooks/engine.js';
2
2
  export { handleConfigureHooks } from './start-hooks/configure.js';
3
3
  //# sourceMappingURL=start-hooks.d.ts.map
@@ -1,5 +1,4 @@
1
1
  // tools/start-hooks.ts — Barrel re-export. Implementation split into start-hooks/ subdirectory.
2
- // Existing imports (register-hooks-tools.ts, safe-handler.ts) continue to work unchanged.
3
- export { getEngineInstance, getWatcherInstance, getConfigLoaderInstance, ensureHooksStarted, handleStartHooks, handleStopHooks, handleHookStatus, } from './start-hooks/engine.js';
2
+ export { getEngineInstance, getWatcherInstance, getConfigLoaderInstance, handleStartHooks, handleStopHooks, handleHookStatus, } from './start-hooks/engine.js';
4
3
  export { handleConfigureHooks } from './start-hooks/configure.js';
5
4
  //# sourceMappingURL=start-hooks.js.map
@@ -28,6 +28,8 @@ import { handleValidateBrowser } from '../browser-validate-handler.js';
28
28
  import { WorkerStatusInputSchema, ConfigureWorkersInputSchema } from '../schemas/workers-schema.js';
29
29
  import { handleWorkerStatus } from '../worker-status-handler.js';
30
30
  import { handleConfigureWorkers } from '../configure-workers-handler.js';
31
+ // ── Hook tools (SPEC-1404: explicit start, no longer auto-started) ──────────
32
+ import { handleStartHooks, handleStopHooks, handleHookStatus } from '../start-hooks.js';
31
33
  // ── Plugin tools (register-plugin-tools.ts) ──────────────────────────────────
32
34
  import { handleManagePlugins } from '../manage-plugins-handler.js';
33
35
  import { handleScaffoldPlugin } from '../scaffold-plugin-handler.js';
@@ -434,6 +436,40 @@ export function registerInfraGroupTools(server) {
434
436
  },
435
437
  annotations: { title: 'Configure Workers', readOnlyHint: false },
436
438
  }, safeGoverned('configure_workers', (args) => handleConfigureWorkers(args)));
439
+ // ── Hook tools (SPEC-1404: explicit start, watcher never mutates spec state) ─
440
+ server.registerTool('start_hooks', {
441
+ description: 'Start the filesystem hook engine for a project: watches source and spec files and runs ' +
442
+ 'diagnostic-only analysis on change (never mutates spec lifecycle status). Must be called ' +
443
+ 'explicitly — hooks no longer start implicitly on other tool calls.',
444
+ inputSchema: {
445
+ projectPath: z.string().describe('Absolute path to the project root directory to watch'),
446
+ hooks: z
447
+ .array(z.string())
448
+ .optional()
449
+ .describe('Hook types to enable. Valid values: on-save, on-create, on-delete, on-test-pass, ' +
450
+ 'on-commit, on-impl-change, on-spec-change, on-push-check, on-security-check. ' +
451
+ 'Defaults to on-save, on-create, on-delete, on-impl-change, on-spec-change, on-security-check.'),
452
+ },
453
+ annotations: { title: 'Start Hooks', readOnlyHint: false },
454
+ }, safe(async (args) => handleStartHooks(args)));
455
+ server.registerTool('stop_hooks', {
456
+ description: 'Stop the running filesystem hook engine and release its watcher, debouncer, and config-loader resources.',
457
+ inputSchema: {
458
+ reason: z.string().optional().describe('Optional reason for stopping the hook engine'),
459
+ },
460
+ annotations: { title: 'Stop Hooks', readOnlyHint: false },
461
+ }, safe(async (args) => handleStopHooks(args)));
462
+ server.registerTool('hook_status', {
463
+ description: 'Show whether the filesystem hook engine is running, its active hooks and pending events, ' +
464
+ 'and the last diagnostic-only impl-change analysis (if any). Never applies changes.',
465
+ inputSchema: {
466
+ projectPath: z
467
+ .string()
468
+ .optional()
469
+ .describe('Absolute path to the project root, used to look up its diagnostic snapshot'),
470
+ },
471
+ annotations: { title: 'Hook Status', readOnlyHint: true },
472
+ }, safe(async (args) => handleHookStatus(args)));
437
473
  // ── Plugin tools ───────────────────────────────────────────────────────────
438
474
  server.registerTool('manage_plugins', {
439
475
  description: 'Manage Planu plugin lifecycle: search, list, activate, deactivate, uninstall, info. ' +
@@ -285,7 +285,7 @@ function validationReceiptGateError(specId, reason) {
285
285
  },
286
286
  };
287
287
  }
288
- function doneReviewDigestError(specId, code, message) {
288
+ function doneReviewDigestError(specId, code, message, digests) {
289
289
  const artifactPath = `external Planu project data: handoffs/${specId}/validation-report.json`;
290
290
  const fixHint = 'Run validate, then recompute the sha256 digest from the exact report bytes.';
291
291
  return {
@@ -294,7 +294,7 @@ function doneReviewDigestError(specId, code, message) {
294
294
  structuredContent: {
295
295
  error: code,
296
296
  code: 422,
297
- context: { specId, artifactPath },
297
+ context: { specId, artifactPath, ...digests },
298
298
  fixHint,
299
299
  },
300
300
  };
@@ -430,7 +430,7 @@ async function checkDoneReviewDigest(specId, projectId, implementationReviewDige
430
430
  }
431
431
  const observed = `sha256:${createHash('sha256').update(reportResult).digest('hex')}`;
432
432
  if (observed !== implementationReviewDigest) {
433
- return doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_MISMATCH', 'The implementation review digest does not match the persisted validation report.');
433
+ return doneReviewDigestError(specId, 'DONE_REVIEW_DIGEST_MISMATCH', 'The implementation review digest does not match the persisted validation report.', { received: implementationReviewDigest, expected: observed });
434
434
  }
435
435
  let decoded;
436
436
  try {
@@ -540,12 +540,25 @@ export function checkChallengeGate(spec, newStatus) {
540
540
  },
541
541
  };
542
542
  }
543
+ if (!Array.isArray(report.findings)) {
544
+ return {
545
+ content: [
546
+ {
547
+ type: 'text',
548
+ text: `Challenge gate blocked: the stored challenge report predates finding-level tracking, so its findings cannot be resolved. Run challenge_spec(specId="${spec.id}") to regenerate it before transitioning to review.`,
549
+ },
550
+ ],
551
+ isError: true,
552
+ structuredContent: {
553
+ error: 'CHALLENGE_GATE_BLOCKED',
554
+ code: 'STALE_CHALLENGE_REPORT',
555
+ fixHint: `Run challenge_spec(specId="${spec.id}") to regenerate the report with findings, then record resolution evidence.`,
556
+ },
557
+ };
558
+ }
543
559
  const resolutionEvidence = getResolvedChallengeEvidence(report);
544
560
  const addressedCount = resolutionEvidence.length;
545
- const findingsCount = Array.isArray(report.findings)
546
- ? report.findings.length
547
- : report.totalScenarios;
548
- const requiredCount = requiredResolvedChallenges(findingsCount);
561
+ const requiredCount = requiredResolvedChallenges(new Set(report.findings.map((finding) => finding.scenario)).size);
549
562
  if (addressedCount < requiredCount) {
550
563
  return {
551
564
  content: [
@@ -26,12 +26,6 @@ export interface FileHookConfig {
26
26
  rateLimitPerFileMs: number;
27
27
  /** Per-hook-type configuration */
28
28
  hooks: Record<string, FileHookTypeConfig>;
29
- /**
30
- * When true (default), automatically mark a spec as 'done' when its
31
- * implementation analysis reaches 100% completion. Set to false to disable
32
- * the auto-done behaviour and only log the milestone.
33
- */
34
- autoMarkDone?: boolean;
35
29
  }
36
30
  export interface DebouncerOptions {
37
31
  /** Debounce window in milliseconds (default: 300) */
@@ -294,6 +288,10 @@ export interface StopHooksInput {
294
288
  /** Optional reason for stopping the hook engine */
295
289
  reason?: string;
296
290
  }
291
+ export interface HookStatusInput {
292
+ /** Absolute path to the project root, used to look up its diagnostic snapshot */
293
+ projectPath?: string;
294
+ }
297
295
  export interface ConfigureHooksInput {
298
296
  /** Absolute path to the project root directory */
299
297
  projectPath: string;
@@ -63,7 +63,6 @@ export * from './commercial-migration.js';
63
63
  export * from './coverage.js';
64
64
  export * from './ai-cost.js';
65
65
  export * from './hooks.js';
66
- export * from './hook-status-update.js';
67
66
  export * from './ci.js';
68
67
  export * from './spec-templates.js';
69
68
  export * from './spec-generator.js';
@@ -64,7 +64,6 @@ export * from './commercial-migration.js';
64
64
  export * from './coverage.js';
65
65
  export * from './ai-cost.js';
66
66
  export * from './hooks.js';
67
- export * from './hook-status-update.js';
68
67
  export * from './ci.js';
69
68
  export * from './spec-templates.js';
70
69
  export * from './spec-generator.js';
@@ -223,23 +223,4 @@ export interface ListSpecPromptsArgs {
223
223
  export interface SpecPromptRegistryEntry extends SpecPromptDefinition {
224
224
  specId: string;
225
225
  }
226
- export interface ClaudeHookEntry {
227
- type: string;
228
- prompt?: string;
229
- model?: string;
230
- statusMessage?: string;
231
- command?: string;
232
- once?: boolean;
233
- timeout?: number;
234
- [key: string]: unknown;
235
- }
236
- export interface ClaudeHookMatcher {
237
- matcher: string;
238
- hooks: ClaudeHookEntry[];
239
- }
240
- export type ClaudeHooksMap = Record<string, ClaudeHookMatcher[]>;
241
- export interface ClaudeSettings {
242
- hooks?: ClaudeHooksMap;
243
- [key: string]: unknown;
244
- }
245
226
  //# sourceMappingURL=mcp.d.ts.map
@@ -1,58 +1,46 @@
1
- /**
2
- * Sections whose values do not appear as literal text in spec.md body —
3
- * applied/verified at the metadata layer, never via string-replace.
4
- * Shared by apply-changes.ts (skip) and verify-write.ts (skip).
5
- */
6
- export declare const RECONCILE_METADATA_SECTIONS: ReadonlySet<string>;
7
- /** A single failed post-write verification entry (Bug D). */
1
+ export declare const SECTIONS_WITHOUT_LITERAL_BODY_TEXT: ReadonlySet<string>;
8
2
  export interface VerifyWriteFailure {
9
3
  section: string;
10
4
  newValue: string;
11
5
  reason: string;
12
6
  }
13
- /** Result of verifyWriteSucceeded (Bug D). */
14
7
  export interface VerifyWriteResult {
15
8
  verified: boolean;
16
9
  missingChanges: VerifyWriteFailure[];
17
10
  }
18
- /** A change that could not be applied to spec.md body (Bug D). */
19
11
  export interface SkippedChange {
20
12
  section: string;
21
13
  originalValue: string;
22
14
  reason: string;
15
+ reasonCategory: 'skipped' | 'ambiguous';
23
16
  }
24
- /** Result of applyChangesToSpec (Bug D). */
25
17
  export interface ApplyChangesResult {
26
18
  applied: number;
27
19
  skipped: SkippedChange[];
28
20
  fileWritten: boolean;
29
21
  }
30
- /** Body bounds of a markdown section, used by apply-changes' anchoring. */
22
+ export interface ApplyChangesToSpecFilesResult {
23
+ auditWritten: boolean;
24
+ }
31
25
  export interface SectionBounds {
32
26
  start: number;
33
27
  end: number;
34
28
  }
35
- /** Parsed snapshot of status.json used by status-reconciler (Bug G). */
36
29
  export interface CurrentStatus {
37
30
  totalSpecs: number;
38
31
  content: Record<string, unknown>;
39
- /** True when status.json exists; false when missing (ENOENT). */
40
32
  exists: boolean;
41
- /** True when status.json existed but could not be parsed/read for non-ENOENT reasons. */
42
33
  corrupt: boolean;
43
34
  }
44
- /** Result of reconcileStatusFromDisk (Bug G). */
45
35
  export interface StatusReconcileResult {
46
36
  reconciled: boolean;
47
37
  before: number;
48
38
  after: number;
49
39
  }
50
40
  import type { ProjectKnowledge } from './project/core.js';
51
- /** Result when a project was resolved via resolveProjectFromPath (Bug F). */
52
41
  export interface ResolvedProject {
53
42
  projectId: string;
54
43
  knowledge: ProjectKnowledge;
55
- /** True when knowledge was recovered from disk fingerprint. */
56
44
  recovered: boolean;
57
45
  }
58
46
  //# sourceMappingURL=reconcile.d.ts.map
@@ -1,10 +1,4 @@
1
- // types/reconcile.ts SPEC-1011: Types for reconcile engine modules
2
- /**
3
- * Sections whose values do not appear as literal text in spec.md body —
4
- * applied/verified at the metadata layer, never via string-replace.
5
- * Shared by apply-changes.ts (skip) and verify-write.ts (skip).
6
- */
7
- export const RECONCILE_METADATA_SECTIONS = new Set([
1
+ export const SECTIONS_WITHOUT_LITERAL_BODY_TEXT = new Set([
8
2
  'estimation',
9
3
  'cost',
10
4
  'status',
@@ -266,6 +266,11 @@ export interface LivingSpecReport {
266
266
  analyzedAt: string;
267
267
  progressUpdated: boolean;
268
268
  }
269
+ /** Options for analyzeLivingSpec — controls whether ## Progress is written to spec.md. */
270
+ export interface AnalyzeLivingSpecOptions {
271
+ /** When true, write the analysis into spec.md's ## Progress section. Default: false. */
272
+ writeProgress?: boolean;
273
+ }
269
274
  export interface SpecVersion {
270
275
  specId: string;
271
276
  version: number;
@@ -1,5 +1,6 @@
1
1
  import type { SpecStatus, SpecType, SpecScope, SpecTarget, ToolResult } from '../common/index.js';
2
2
  import type { Actuals } from '../estimation.js';
3
+ import type { GlobalConfig } from '../project/config-metrics.js';
3
4
  import type { ProjectKnowledge } from '../project.js';
4
5
  import type { Spec, ChecklistCategory, SpecDiagram, SpecSplitSuggestion, ReconcileChange } from './core.js';
5
6
  export interface ReconciliationInvocationContext {
@@ -230,10 +231,25 @@ export interface FilesystemImportDeps {
230
231
  listSpecs: (projectId: string) => Promise<Spec[]>;
231
232
  createSpec: (projectId: string, spec: Spec) => Promise<Spec>;
232
233
  getSpecFresh?: (projectId: string, specId: string) => Promise<Spec | null>;
234
+ getGlobalConfig?: () => Promise<Partial<GlobalConfig>>;
233
235
  }
234
236
  export interface PortableIndexReconciliationResult extends FilesystemImportResult {
235
237
  repositoryFilesChanged: string[];
236
238
  }
239
+ export interface ImportEntryContext {
240
+ projectPath: string;
241
+ projectId: string;
242
+ deps: FilesystemImportDeps;
243
+ options: {
244
+ strictPortableContract?: boolean;
245
+ };
246
+ specsRoot: string;
247
+ globalConfig: Partial<GlobalConfig>;
248
+ storeSpecs: Spec[];
249
+ specsById: Map<string, Spec>;
250
+ importedSpecIds: string[];
251
+ failures: FilesystemImportFailure[];
252
+ }
237
253
  export type ChallengeSpecFocus = 'failures' | 'concurrency' | 'scale' | 'security' | 'data-consistency';
238
254
  export type { ScopeFilter, ScopeFilterConfig } from './core.js';
239
255
  export interface SplitAnalysisInput {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "5.3.4",
3
+ "version": "5.3.6",
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.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"
38
+ "@planu/core-darwin-arm64": "5.3.6",
39
+ "@planu/core-darwin-x64": "5.3.6",
40
+ "@planu/core-linux-arm64-gnu": "5.3.6",
41
+ "@planu/core-linux-arm64-musl": "5.3.6",
42
+ "@planu/core-linux-x64-gnu": "5.3.6",
43
+ "@planu/core-linux-x64-musl": "5.3.6",
44
+ "@planu/core-win32-arm64-msvc": "5.3.6",
45
+ "@planu/core-win32-x64-msvc": "5.3.6"
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.4",
4
+ "version": "5.3.6",
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.4",
5
+ "version": "5.3.6",
6
6
  "icon": "assets/plugin/icon.svg",
7
7
  "command": ["npx", "@planu/cli@latest"],
8
8
  "packageName": "@planu/cli",
@@ -1,9 +0,0 @@
1
- /**
2
- * Scan .claude/settings.json (project-level) and ~/.claude/settings.json (global)
3
- * for deprecated type:"agent" hooks and migrate them to type:"command" equivalents.
4
- *
5
- * Fire-and-forget safe: never throws, logs to stderr only.
6
- * Idempotent: each projectPath is only migrated once per process lifetime.
7
- */
8
- export declare function migrateAgentHooksIfNeeded(projectPath: string): void;
9
- //# sourceMappingURL=agent-hook-migrator.d.ts.map