@planu/cli 5.3.5 → 5.3.7
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.
- package/CHANGELOG.md +50 -0
- package/dist/config/runtime-policy.json +1 -0
- package/dist/engine/convention-scanner/codebase-scanner.js +1 -1
- package/dist/engine/hooks/handlers/on-impl-change.d.ts +17 -4
- package/dist/engine/hooks/handlers/on-impl-change.js +43 -44
- package/dist/engine/living-spec-analyzer.d.ts +5 -2
- package/dist/engine/living-spec-analyzer.js +5 -2
- package/dist/engine/planu-core.darwin-arm64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-arm64.node.sbom.json +4 -4
- package/dist/engine/planu-core.darwin-x64.node.manifest.json +7 -7
- package/dist/engine/planu-core.darwin-x64.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-arm64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-gnu.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-arm64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-arm64-musl.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-x64-gnu.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-gnu.node.sbom.json +4 -4
- package/dist/engine/planu-core.linux-x64-musl.node.manifest.json +7 -7
- package/dist/engine/planu-core.linux-x64-musl.node.sbom.json +4 -4
- package/dist/engine/planu-core.win32-arm64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-arm64-msvc.node.sbom.json +4 -4
- package/dist/engine/planu-core.win32-x64-msvc.node.manifest.json +7 -7
- package/dist/engine/planu-core.win32-x64-msvc.node.sbom.json +4 -4
- package/dist/engine/runtime-policy.js +7 -0
- package/dist/engine/scope-boundaries/contradiction-checker.js +28 -20
- package/dist/engine/spec-format/acceptance-criteria.js +5 -2
- package/dist/engine/spec-migrator/filesystem-import.js +113 -73
- package/dist/engine/spec-migrator/frontmatter-parser.js +15 -13
- package/dist/tools/auto-reconcile.js +1 -1
- package/dist/tools/challenge-spec/challenge-report.js +1 -1
- package/dist/tools/init-project/portable-index-reconciler.js +2 -1
- package/dist/tools/reconcile-spec.js +3 -1
- package/dist/tools/safe-handler.js +0 -2
- package/dist/tools/start-hooks/engine.d.ts +2 -3
- package/dist/tools/start-hooks/engine.js +50 -33
- package/dist/tools/start-hooks.d.ts +1 -1
- package/dist/tools/start-hooks.js +1 -2
- package/dist/tools/tool-registry/group-infra.js +36 -0
- package/dist/tools/update-status/transition-guard.js +17 -4
- package/dist/tools/validate-assurance.js +1 -1
- package/dist/tools/validate-lint.js +1 -1
- package/dist/tools/validate.d.ts +1 -0
- package/dist/tools/validate.js +21 -2
- package/dist/types/file-hooks.d.ts +4 -6
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.js +0 -1
- package/dist/types/mcp.d.ts +0 -19
- package/dist/types/runtime-policy.d.ts +1 -0
- package/dist/types/spec/core.d.ts +5 -0
- package/dist/types/spec/inputs.d.ts +16 -0
- package/package.json +9 -9
- package/planu-native.json +1 -1
- package/planu-plugin.json +1 -1
- package/dist/engine/hooks/agent-hook-migrator.d.ts +0 -9
- package/dist/engine/hooks/agent-hook-migrator.js +0 -140
- package/dist/types/hook-status-update.d.ts +0 -10
- package/dist/types/hook-status-update.js +0 -3
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
// instead of crashing the server. Includes cancellation-aware execution deadlines.
|
|
4
4
|
import { withUsageTracking } from './usage-tracking.js';
|
|
5
5
|
import { sanitizePath } from './sanitize-path.js';
|
|
6
|
-
import { ensureHooksStarted } from './start-hooks.js';
|
|
7
6
|
import { ensureWorkersStarted } from '../engine/workers/index.js';
|
|
8
7
|
import { recordError } from '../storage/error-telemetry-store.js';
|
|
9
8
|
import { hashProjectPath } from '../storage/base-store.js';
|
|
@@ -158,7 +157,6 @@ function sanitizeArgs(args, toolName) {
|
|
|
158
157
|
// planu_status is a read-only snapshot. Starting background services here made
|
|
159
158
|
// its latency depend on repository size and violated the MCP readOnlyHint.
|
|
160
159
|
if (toolName !== 'planu_status') {
|
|
161
|
-
ensureHooksStarted(obj.projectPath);
|
|
162
160
|
ensureWorkersStarted(obj.projectPath);
|
|
163
161
|
}
|
|
164
162
|
// Read-only check: track whether planu.json exists (no file writes)
|
|
@@ -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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
|
194
|
-
if (
|
|
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()
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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,
|
|
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
|
-
|
|
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. ' +
|
|
@@ -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
|
|
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: [
|
|
@@ -20,7 +20,7 @@ function readChangedSpecLogicFiles(projectPath) {
|
|
|
20
20
|
'src/tools/create-spec',
|
|
21
21
|
'src/tools/validate.ts',
|
|
22
22
|
'src/engine',
|
|
23
|
-
], { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
23
|
+
], { encoding: 'utf-8', timeout: 5_000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
24
24
|
changedFiles = output
|
|
25
25
|
.split('\n')
|
|
26
26
|
.map((line) => line.trim())
|
|
@@ -128,7 +128,7 @@ export async function runLintCheck(projectPath, lintCommand, context = {}) {
|
|
|
128
128
|
source: 'local:planu-validate',
|
|
129
129
|
});
|
|
130
130
|
}
|
|
131
|
-
return { passed:
|
|
131
|
+
return { passed: false, command, issueCount: 1, output };
|
|
132
132
|
}
|
|
133
133
|
const raw = commandOutput(err);
|
|
134
134
|
const output = formatLintFailureDiagnostics(plan, raw);
|
package/dist/tools/validate.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { ValidateInput, ToolResult, ContractSpec, ContractValidation } from
|
|
|
4
4
|
import type { ValidateWorkerProgress } from '../types/validate-worker.js';
|
|
5
5
|
export { validateContractCompliance };
|
|
6
6
|
export type { ContractSpec, ContractValidation };
|
|
7
|
+
export declare function createValidationStageRunner(onProgress?: (progress: ValidateWorkerProgress) => void): <T>(phase: string, command: string, work: () => T | Promise<T>) => Promise<T>;
|
|
7
8
|
/** Public MCP boundary: persist and acknowledge validation work without running it inline. */
|
|
8
9
|
export declare function handleValidate(args: ValidateInput): Promise<ToolResult>;
|
|
9
10
|
export declare function executeValidate(args: ValidateInput, server?: McpServer, onProgress?: (progress: ValidateWorkerProgress) => void): Promise<ToolResult>;
|
package/dist/tools/validate.js
CHANGED
|
@@ -85,7 +85,26 @@ import { resolveValidationWorktree, ValidationWorktreeError, } from '../engine/v
|
|
|
85
85
|
// Re-export for external use (SPEC-018)
|
|
86
86
|
export { validateContractCompliance };
|
|
87
87
|
const VALIDATION_GATE_TOTAL = 8;
|
|
88
|
-
|
|
88
|
+
const GATE_DEADLINE_EXEMPT_PHASES = new Set(['lint', 'compliance']);
|
|
89
|
+
async function runWithGateDeadline(phase, command, work) {
|
|
90
|
+
if (GATE_DEADLINE_EXEMPT_PHASES.has(phase)) {
|
|
91
|
+
return work();
|
|
92
|
+
}
|
|
93
|
+
const deadlineMs = getRuntimePolicy().validation.perGateTimeoutMs;
|
|
94
|
+
let timer;
|
|
95
|
+
const deadline = new Promise((_resolve, reject) => {
|
|
96
|
+
timer = setTimeout(() => {
|
|
97
|
+
reject(new Error(`[Planu] validate gate "${phase}" (${command}) exceeded its ${String(deadlineMs)}ms deadline`));
|
|
98
|
+
}, deadlineMs);
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
return await Promise.race([Promise.resolve(work()), deadline]);
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export function createValidationStageRunner(onProgress) {
|
|
89
108
|
const startedAt = Date.now();
|
|
90
109
|
let completedGates = 0;
|
|
91
110
|
return async (phase, command, work) => {
|
|
@@ -102,7 +121,7 @@ function createValidationStageRunner(onProgress) {
|
|
|
102
121
|
};
|
|
103
122
|
emit('running');
|
|
104
123
|
try {
|
|
105
|
-
const result = await work
|
|
124
|
+
const result = await runWithGateDeadline(phase, command, work);
|
|
106
125
|
completedGates += 1;
|
|
107
126
|
emit('passed');
|
|
108
127
|
return result;
|
|
@@ -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;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/types/index.js
CHANGED
|
@@ -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';
|
package/dist/types/mcp.d.ts
CHANGED
|
@@ -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
|
|
@@ -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.
|
|
3
|
+
"version": "5.3.7",
|
|
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.
|
|
39
|
-
"@planu/core-darwin-x64": "5.3.
|
|
40
|
-
"@planu/core-linux-arm64-gnu": "5.3.
|
|
41
|
-
"@planu/core-linux-arm64-musl": "5.3.
|
|
42
|
-
"@planu/core-linux-x64-gnu": "5.3.
|
|
43
|
-
"@planu/core-linux-x64-musl": "5.3.
|
|
44
|
-
"@planu/core-win32-arm64-msvc": "5.3.
|
|
45
|
-
"@planu/core-win32-x64-msvc": "5.3.
|
|
38
|
+
"@planu/core-darwin-arm64": "5.3.7",
|
|
39
|
+
"@planu/core-darwin-x64": "5.3.7",
|
|
40
|
+
"@planu/core-linux-arm64-gnu": "5.3.7",
|
|
41
|
+
"@planu/core-linux-arm64-musl": "5.3.7",
|
|
42
|
+
"@planu/core-linux-x64-gnu": "5.3.7",
|
|
43
|
+
"@planu/core-linux-x64-musl": "5.3.7",
|
|
44
|
+
"@planu/core-win32-arm64-msvc": "5.3.7",
|
|
45
|
+
"@planu/core-win32-x64-msvc": "5.3.7"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=24.0.0"
|
package/planu-native.json
CHANGED
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.
|
|
5
|
+
"version": "5.3.7",
|
|
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
|