@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.
- package/CHANGELOG.md +47 -0
- package/dist/engine/execution/validate-job-executor.js +25 -10
- 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/reconcile/apply-changes.d.ts +0 -12
- package/dist/engine/reconcile/apply-changes.js +11 -33
- package/dist/engine/reconcile/verify-write.js +2 -3
- package/dist/engine/rules-reconciler.js +21 -2
- package/dist/engine/scope-boundaries/contradiction-checker.js +20 -13
- package/dist/engine/spec-format/acceptance-criteria.js +11 -2
- package/dist/engine/spec-migrator/filesystem-import.js +113 -73
- package/dist/engine/spec-migrator/frontmatter-parser.js +15 -13
- package/dist/engine/universal-rules/catalog.js +6 -0
- package/dist/engine/universal-rules/rules/agent-teams.js +0 -2
- package/dist/engine/universal-rules/rules/planu-approval-gates.js +0 -1
- package/dist/engine/universal-rules/rules/planu-bdd-criteria.js +0 -1
- package/dist/engine/universal-rules/rules/planu-clean-code-no-comments.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-clean-code-no-comments.js +30 -0
- package/dist/engine/universal-rules/rules/planu-debate-review.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-debate-review.js +38 -0
- package/dist/engine/universal-rules/rules/planu-dogfood-bugs.js +0 -2
- package/dist/engine/universal-rules/rules/planu-english-specs.js +0 -1
- package/dist/engine/universal-rules/rules/planu-minimal-change.js +0 -2
- package/dist/engine/universal-rules/rules/planu-modes.js +0 -2
- package/dist/engine/universal-rules/rules/planu-release-policy.js +0 -1
- package/dist/engine/universal-rules/rules/planu-revert-proof-tests.d.ts +3 -0
- package/dist/engine/universal-rules/rules/planu-revert-proof-tests.js +34 -0
- package/dist/engine/universal-rules/rules/planu-sdd-model-routing.js +0 -1
- package/dist/engine/universal-rules/rules/planu-workflow.js +0 -2
- package/dist/engine/validator/validation-report-writer.js +1 -1
- 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.d.ts +1 -1
- package/dist/tools/reconcile-spec.js +306 -179
- 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/dod-gates.js +3 -3
- package/dist/tools/update-status/transition-guard.js +17 -4
- 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/reconcile.d.ts +5 -17
- package/dist/types/reconcile.js +1 -7
- 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
|
@@ -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. ' +
|
|
@@ -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
|
|
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;
|
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
|
|
@@ -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
|
-
|
|
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
|
package/dist/types/reconcile.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
|
|
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.
|
|
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.
|
|
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.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
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.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
|