@planu/cli 4.1.0 → 4.1.2

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 (60) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/config/license-plans.json +65 -361
  3. package/dist/engine/hooks/git-hook-generator.js +31 -0
  4. package/dist/tools/git/hook-ops.js +53 -9
  5. package/dist/tools/tool-registry/group-infra.js +22 -0
  6. package/package.json +8 -7
  7. package/dist/engine/escalator/index.d.ts +0 -5
  8. package/dist/engine/escalator/index.js +0 -5
  9. package/dist/engine/freeze/retro-audit.d.ts +0 -6
  10. package/dist/engine/freeze/retro-audit.js +0 -24
  11. package/dist/engine/heal/backup.d.ts +0 -9
  12. package/dist/engine/heal/backup.js +0 -21
  13. package/dist/engine/idioma-validator/index.d.ts +0 -17
  14. package/dist/engine/idioma-validator/index.js +0 -89
  15. package/dist/engine/saga/index.d.ts +0 -4
  16. package/dist/engine/saga/index.js +0 -4
  17. package/dist/engine/spec-state-machine/index.d.ts +0 -3
  18. package/dist/engine/spec-state-machine/index.js +0 -2
  19. package/dist/engine/spec-summary-html/dashboard-renderer.d.ts +0 -6
  20. package/dist/engine/spec-summary-html/dashboard-renderer.js +0 -333
  21. package/dist/engine/triagier/index.d.ts +0 -5
  22. package/dist/engine/triagier/index.js +0 -5
  23. package/dist/engine/universal-rules/index.d.ts +0 -5
  24. package/dist/engine/universal-rules/index.js +0 -6
  25. package/dist/testing/cassette/index.d.ts +0 -23
  26. package/dist/testing/cassette/index.js +0 -26
  27. package/dist/tools/domain-bundle-handler.d.ts +0 -37
  28. package/dist/tools/domain-bundle-handler.js +0 -71
  29. package/dist/tools/figma/rules-file.d.ts +0 -5
  30. package/dist/tools/figma/rules-file.js +0 -45
  31. package/dist/tools/heal-planu-root.d.ts +0 -8
  32. package/dist/tools/heal-planu-root.js +0 -144
  33. package/dist/tools/opencode-host-adapter.d.ts +0 -3
  34. package/dist/tools/opencode-host-adapter.js +0 -33
  35. package/dist/tools/plan-team-distribution.d.ts +0 -3
  36. package/dist/tools/plan-team-distribution.js +0 -71
  37. package/dist/tools/reconcile-status-json.d.ts +0 -4
  38. package/dist/tools/reconcile-status-json.js +0 -209
  39. package/dist/tools/register-all-tools.d.ts +0 -8
  40. package/dist/tools/register-all-tools.js +0 -239
  41. package/dist/tools/tool-registry/group-analysis-monitoring.d.ts +0 -3
  42. package/dist/tools/tool-registry/group-analysis-monitoring.js +0 -942
  43. package/dist/tools/tool-registry/group-integrations.d.ts +0 -3
  44. package/dist/tools/tool-registry/group-integrations.js +0 -1046
  45. package/dist/tools/tool-registry/group-misc.d.ts +0 -3
  46. package/dist/tools/tool-registry/group-misc.js +0 -1367
  47. package/dist/tools/tool-registry/group-platform.d.ts +0 -3
  48. package/dist/tools/tool-registry/group-platform.js +0 -1681
  49. package/dist/tools/tool-registry/group-session-knowledge.d.ts +0 -3
  50. package/dist/tools/tool-registry/group-session-knowledge.js +0 -1416
  51. package/dist/tools/tool-registry/group-spec-ops.d.ts +0 -3
  52. package/dist/tools/tool-registry/group-spec-ops.js +0 -917
  53. package/dist/tools/workspace-overview.d.ts +0 -4
  54. package/dist/tools/workspace-overview.js +0 -316
  55. package/dist/transports/middleware/index.d.ts +0 -9
  56. package/dist/transports/middleware/index.js +0 -7
  57. package/dist/transports/middleware/with-sandbox.d.ts +0 -21
  58. package/dist/transports/middleware/with-sandbox.js +0 -68
  59. package/dist/types/heal.d.ts +0 -18
  60. package/dist/types/heal.js +0 -3
@@ -1,4 +0,0 @@
1
- import type { ToolResult } from '../types/index.js';
2
- import type { ProjectOverviewInput } from '../types/workspace-overview.js';
3
- export declare function handleProjectOverview(params: ProjectOverviewInput): Promise<ToolResult>;
4
- //# sourceMappingURL=workspace-overview.d.ts.map
@@ -1,316 +0,0 @@
1
- // tools/workspace-overview.ts — SPEC-753: project_overview tool
2
- //
3
- // Single-call state query: counts, pending, drift, version, cleanup, stale-implementing.
4
- // Replaces the multi-tool dance (planu_status + list_specs + filesystem reads).
5
- import { join } from 'node:path';
6
- import { checkBundledVersionGap } from '../engine/version-detector/bundled-version-checker.js';
7
- import { checkAndFixBundledVersion } from '../engine/mcp-config/mcp-config-writer.js';
8
- import { readFile } from 'node:fs/promises';
9
- import { resolveProjectPath } from '../storage/path-resolver.js';
10
- import { hashProjectPath } from '../storage/base-store.js';
11
- import { validateStatusJson } from '../storage/status-store/self-healing.js';
12
- import { syncVersionField } from '../storage/status-store/version-sync.js';
13
- import { findStaleImplementing } from '../engine/staleness/stale-implementing.js';
14
- import { readForceAnalytics } from '../storage/force-analytics-store.js';
15
- import { PLANU_VERSION } from '../config/version.js';
16
- // ---------------------------------------------------------------------------
17
- // Helpers
18
- // ---------------------------------------------------------------------------
19
- async function getForceUsage(projectId, totalSpecs) {
20
- try {
21
- const analytics = await readForceAnalytics(projectId);
22
- const ratio = totalSpecs > 0 ? analytics.stats.forcedSpecs / totalSpecs : 0;
23
- // Simple trend heuristic: compare current ratio to historical ratio stored in stats
24
- const historicalRatio = analytics.stats.ratio;
25
- let trend = 'stable';
26
- if (ratio < historicalRatio - 0.05) {
27
- trend = 'improving';
28
- }
29
- else if (ratio > historicalRatio + 0.05) {
30
- trend = 'worsening';
31
- }
32
- return {
33
- forcedSpecs: analytics.stats.forcedSpecs,
34
- totalSpecs,
35
- ratio: Math.round(ratio * 100) / 100,
36
- trend,
37
- };
38
- }
39
- catch {
40
- return {
41
- forcedSpecs: 0,
42
- totalSpecs,
43
- ratio: 0,
44
- trend: 'stable',
45
- };
46
- }
47
- }
48
- async function getSpecCountsByStatus(projectPath, projectId) {
49
- const emptyStatus = {
50
- draft: 0,
51
- approved: 0,
52
- implementing: 0,
53
- review: 0,
54
- validating: 0,
55
- done: 0,
56
- discarded: 0,
57
- };
58
- try {
59
- const { specStore } = await import('../storage/index.js');
60
- const specs = await specStore.listSpecs(projectId);
61
- const byStatus = { ...emptyStatus };
62
- for (const spec of specs) {
63
- const s = spec.status;
64
- if (s in byStatus) {
65
- byStatus[s]++;
66
- }
67
- }
68
- const total = specs.length;
69
- const pendingTotal = total - byStatus.done - byStatus.discarded;
70
- return { byStatus, total, pendingTotal };
71
- }
72
- catch {
73
- // Fall back to reading status.json directly
74
- }
75
- // Fallback: read status.json
76
- const statusPath = join(projectPath, 'planu', 'status.json');
77
- try {
78
- const raw = await readFile(statusPath, 'utf-8');
79
- const parsed = JSON.parse(raw);
80
- if (typeof parsed === 'object' && parsed !== null && 'byStatus' in parsed) {
81
- const bs = parsed.byStatus;
82
- if (typeof bs === 'object' && bs !== null) {
83
- const bsRec = bs;
84
- const byStatus = {
85
- draft: bsRec.draft ?? 0,
86
- approved: bsRec.approved ?? 0,
87
- implementing: bsRec.implementing ?? 0,
88
- review: bsRec.review ?? 0,
89
- validating: bsRec.validating ?? 0,
90
- done: bsRec.done ?? 0,
91
- discarded: bsRec.discarded ?? 0,
92
- };
93
- const total = Object.values(bsRec).reduce((sum, v) => sum + v, 0);
94
- const pendingTotal = total - byStatus.done - byStatus.discarded;
95
- return { byStatus, total, pendingTotal };
96
- }
97
- }
98
- }
99
- catch {
100
- // ignore
101
- }
102
- return { byStatus: emptyStatus, total: 0, pendingTotal: 0 };
103
- }
104
- async function getTopPending(projectId, topN) {
105
- try {
106
- const { specStore } = await import('../storage/index.js');
107
- const specs = await specStore.listSpecs(projectId);
108
- const pending = specs.filter((s) => s.status !== 'done' && s.status !== 'discarded');
109
- // Sort by status priority: implementing > review > validating > approved > draft
110
- const statusOrder = {
111
- implementing: 0,
112
- review: 1,
113
- validating: 2,
114
- approved: 3,
115
- draft: 4,
116
- };
117
- pending.sort((a, b) => {
118
- const ao = statusOrder[a.status] ?? 99;
119
- const bo = statusOrder[b.status] ?? 99;
120
- return ao - bo;
121
- });
122
- return pending.slice(0, topN).map((s) => ({
123
- specId: s.id,
124
- status: s.status,
125
- title: s.title,
126
- daysInStatus: 0, // best-effort; transition-log not queried here for performance
127
- hasProgress: false,
128
- }));
129
- }
130
- catch {
131
- return [];
132
- }
133
- }
134
- async function getLastRelease(projectPath) {
135
- // Try reading from CHANGELOG.md or session-context.md
136
- try {
137
- const changelogPath = join(projectPath, 'CHANGELOG.md');
138
- const raw = await readFile(changelogPath, 'utf-8');
139
- const versionMatch = /^## \[([^\]]+)\].*?(\d{4}-\d{2}-\d{2})?/m.exec(raw);
140
- if (versionMatch?.[1]) {
141
- return {
142
- version: versionMatch[1],
143
- publishedAt: versionMatch[2] ?? null,
144
- tagSha: null,
145
- };
146
- }
147
- }
148
- catch {
149
- // ignore
150
- }
151
- try {
152
- const pkgPath = join(projectPath, 'package.json');
153
- const raw = await readFile(pkgPath, 'utf-8');
154
- const pkg = JSON.parse(raw);
155
- if (typeof pkg === 'object' && pkg !== null && 'version' in pkg) {
156
- const v = pkg.version;
157
- if (typeof v === 'string') {
158
- return { version: v, publishedAt: null, tagSha: null };
159
- }
160
- }
161
- }
162
- catch {
163
- // ignore
164
- }
165
- return null;
166
- }
167
- async function checkStatusJsonHealth(projectPath) {
168
- const statusPath = join(projectPath, 'planu', 'status.json');
169
- const validation = await validateStatusJson(statusPath);
170
- let versionInSync = true;
171
- if (validation.ok) {
172
- const syncResult = await syncVersionField(statusPath);
173
- versionInSync = !syncResult.synced; // synced=true means it WAS out of sync
174
- }
175
- return { healthy: validation.ok, versionInSync };
176
- }
177
- // ---------------------------------------------------------------------------
178
- // Hint generation
179
- // ---------------------------------------------------------------------------
180
- function generateHints(result) {
181
- const hints = [];
182
- if (!result.drift.statusJsonHealthy) {
183
- hints.push('status.json is corrupt — run `reconcile_status_json` to repair it from frontmatters');
184
- }
185
- if (!result.drift.versionInSync) {
186
- hints.push(`status.json version is out of sync with runtime — it will be updated automatically`);
187
- }
188
- for (const spec of result.staleImplementing) {
189
- const days = spec.daysInStatus >= 0 ? `${spec.daysInStatus} days` : 'an unknown number of days';
190
- hints.push(`Consider transitioning ${spec.specId} to done — in implementing for ${days}${spec.hasProgress ? ' (has progress.md)' : ' with no progress.md'}`);
191
- }
192
- if (result.status.byStatus.draft > 10) {
193
- hints.push(`${result.status.byStatus.draft} specs in draft — consider approving or discarding stale ones`);
194
- }
195
- return hints;
196
- }
197
- // ---------------------------------------------------------------------------
198
- // Main handler
199
- // ---------------------------------------------------------------------------
200
- export async function handleProjectOverview(params) {
201
- const { includeStaleImplementing = true, staleDays = 7, topN = 10 } = params;
202
- // Resolve projectPath
203
- const { projectPath } = await resolveProjectPath(params.projectPath);
204
- const projectId = hashProjectPath(projectPath);
205
- // Gather all data in parallel
206
- const [statusCounts, health, topPending, lastRelease, staleRaw] = await Promise.all([
207
- getSpecCountsByStatus(projectPath, projectId),
208
- checkStatusJsonHealth(projectPath),
209
- getTopPending(projectId, topN),
210
- getLastRelease(projectPath),
211
- includeStaleImplementing
212
- ? findStaleImplementing({ projectPath, daysThreshold: staleDays })
213
- : Promise.resolve([]),
214
- ]);
215
- // Drift score (best-effort — don't fail the whole call)
216
- let planuDriftScore = 0;
217
- try {
218
- const { computePlanuDriftScore } = await import('../engine/dashboard/drift-score.js');
219
- const driftResult = await computePlanuDriftScore(projectPath);
220
- planuDriftScore = driftResult.score;
221
- }
222
- catch {
223
- // ignore
224
- }
225
- const drift = {
226
- statusJsonHealthy: health.healthy,
227
- versionInSync: health.versionInSync,
228
- pendingDriftReviews: 0, // SPEC-748 integration — placeholder
229
- planuDriftScore,
230
- };
231
- const forceUsage = await getForceUsage(projectId, statusCounts.total);
232
- const partialResult = {
233
- projectId,
234
- projectPath,
235
- runtime: {
236
- planuVersion: PLANU_VERSION,
237
- node: process.version,
238
- },
239
- status: statusCounts,
240
- topPending,
241
- lastRelease,
242
- drift,
243
- pendingCleanup: null, // SPEC-751 integration — placeholder
244
- staleImplementing: staleRaw,
245
- forceUsage,
246
- };
247
- const hints = generateHints(partialResult);
248
- const result = { ...partialResult, hints };
249
- // Format as structured text for the LLM
250
- const lines = [
251
- `# Workspace Overview — ${projectPath.split('/').pop() ?? projectPath}`,
252
- ``,
253
- `**Runtime:** Planu v${PLANU_VERSION} · Node ${process.version}`,
254
- `**Project:** \`${projectId}\` at \`${projectPath}\``,
255
- ``,
256
- `## Spec Status`,
257
- `| Status | Count |`,
258
- `|--------|-------|`,
259
- ...Object.entries(result.status.byStatus).map(([s, n]) => `| ${s} | ${String(n)} |`),
260
- `| **Total** | **${String(result.status.total)}** |`,
261
- `| **Pending** | **${String(result.status.pendingTotal)}** |`,
262
- ``,
263
- ];
264
- if (result.topPending.length > 0) {
265
- lines.push(`## Top ${String(topN)} Pending Specs`);
266
- lines.push(`| Spec | Status | Title |`);
267
- lines.push(`|------|--------|-------|`);
268
- for (const s of result.topPending) {
269
- lines.push(`| ${s.specId} | ${s.status} | ${s.title} |`);
270
- }
271
- lines.push('');
272
- }
273
- if (result.staleImplementing.length > 0) {
274
- lines.push(`## Stale Implementing (>${String(staleDays)}d)`);
275
- for (const s of result.staleImplementing) {
276
- const days = s.daysInStatus >= 0 ? `${s.daysInStatus}d` : '?d';
277
- lines.push(`- **${s.specId}** — ${days} · progress: ${s.hasProgress ? 'yes' : 'no'}`);
278
- }
279
- lines.push('');
280
- }
281
- lines.push(`## Drift & Health`);
282
- lines.push(`- status.json: ${result.drift.statusJsonHealthy ? 'healthy' : '⚠️ CORRUPT'}`);
283
- lines.push(`- version sync: ${result.drift.versionInSync ? 'in sync' : '⚠️ out of sync'}`);
284
- lines.push(`- drift score: ${String(result.drift.planuDriftScore)}`);
285
- lines.push('');
286
- lines.push(`## Force Usage`);
287
- lines.push(`- ${result.forceUsage.forcedSpecs}/${result.forceUsage.totalSpecs} specs bypassed gates (${Math.round(result.forceUsage.ratio * 100)}%) — trend: ${result.forceUsage.trend}`);
288
- lines.push('');
289
- if (result.lastRelease) {
290
- lines.push(`## Last Release: v${result.lastRelease.version}${result.lastRelease.publishedAt ? ` (${result.lastRelease.publishedAt})` : ''}`);
291
- lines.push('');
292
- }
293
- if (hints.length > 0) {
294
- lines.push(`## Hints`);
295
- for (const h of hints) {
296
- lines.push(`- ${h}`);
297
- }
298
- }
299
- // SPEC-756: lazy version check — fire-and-forget, non-blocking
300
- checkBundledVersionGap(2000)
301
- .then((gap) => {
302
- if (gap.gap) {
303
- checkAndFixBundledVersion(projectPath, gap.current, gap.latest).catch(() => {
304
- /* best-effort — never block project_overview */
305
- });
306
- }
307
- })
308
- .catch(() => {
309
- /* best-effort */
310
- });
311
- return {
312
- content: [{ type: 'text', text: lines.join('\n') }],
313
- structuredContent: result,
314
- };
315
- }
316
- //# sourceMappingURL=workspace-overview.js.map
@@ -1,9 +0,0 @@
1
- export { composeMiddleware } from './compose.js';
2
- export { withBudget, resetSessionCounters, getSessionCallCount } from './with-budget.js';
3
- export { withTelemetry, isTelemetryEnabled } from './with-telemetry.js';
4
- export { withSandbox, requireSandbox, isSandboxRequired, SANDBOX_REQUIRED, } from './with-sandbox.js';
5
- export { TOOL_OVERRIDES, shouldSkip } from './overrides.js';
6
- export type { ToolHandler, ToolContext, Middleware } from './types.js';
7
- export type { ToolOverride } from './overrides.js';
8
- export type { TelemetryEntry } from './with-telemetry.js';
9
- //# sourceMappingURL=index.d.ts.map
@@ -1,7 +0,0 @@
1
- // transports/middleware/index.ts — Barrel export for middleware chain (SPEC-740)
2
- export { composeMiddleware } from './compose.js';
3
- export { withBudget, resetSessionCounters, getSessionCallCount } from './with-budget.js';
4
- export { withTelemetry, isTelemetryEnabled } from './with-telemetry.js';
5
- export { withSandbox, requireSandbox, isSandboxRequired, SANDBOX_REQUIRED, } from './with-sandbox.js';
6
- export { TOOL_OVERRIDES, shouldSkip } from './overrides.js';
7
- //# sourceMappingURL=index.js.map
@@ -1,21 +0,0 @@
1
- import type { Middleware } from './types.js';
2
- /**
3
- * Flag value indicating a tool requires sandbox isolation.
4
- */
5
- export declare const SANDBOX_REQUIRED: "required";
6
- /**
7
- * withSandbox middleware: delegates to SPEC-742 sandbox runner when tool requires it.
8
- * No-op pass-through for tools that don't require sandbox.
9
- * Logs a warning when sandbox is required but no runner is available (transition period).
10
- */
11
- export declare const withSandbox: Middleware;
12
- /**
13
- * Register a tool as requiring sandbox isolation.
14
- * Used by tool registration code (e.g. execute-sdd-flow).
15
- */
16
- export declare function requireSandbox(toolName: string): void;
17
- /**
18
- * Check if a tool requires sandbox (for testing).
19
- */
20
- export declare function isSandboxRequired(toolName: string): boolean;
21
- //# sourceMappingURL=with-sandbox.d.ts.map
@@ -1,68 +0,0 @@
1
- // transports/middleware/with-sandbox.ts — Sandbox adapter (SPEC-740)
2
- // Acts as a thin wiring layer to SPEC-742 sandbox runner.
3
- // When SPEC-742 is not yet wired, falls back to no-op pass-through.
4
- /**
5
- * Flag value indicating a tool requires sandbox isolation.
6
- */
7
- export const SANDBOX_REQUIRED = 'required';
8
- /**
9
- * Registry of tools that require sandbox isolation.
10
- * Keyed by tool name. When SPEC-742 lands, this is checked by the middleware.
11
- */
12
- const SANDBOX_REQUIRED_TOOLS = new Set(['execute_sdd_flow']);
13
- /**
14
- * Tries to import the SPEC-742 sandbox runner.
15
- * Returns null if not yet implemented (graceful degradation).
16
- */
17
- async function tryGetSandboxRunner() {
18
- try {
19
- // Dynamic import to avoid hard dependency before SPEC-742 lands.
20
- // The path is intentionally via variable so tsc does not resolve it statically.
21
- const sandboxPath = '../../engine/sandbox/index.js';
22
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
23
- const mod = await import(/* @vite-ignore */ sandboxPath);
24
- const fn = mod.runInSandbox;
25
- return typeof fn === 'function' ? fn : null;
26
- }
27
- catch {
28
- return null;
29
- }
30
- }
31
- /**
32
- * withSandbox middleware: delegates to SPEC-742 sandbox runner when tool requires it.
33
- * No-op pass-through for tools that don't require sandbox.
34
- * Logs a warning when sandbox is required but no runner is available (transition period).
35
- */
36
- export const withSandbox = (handler) => {
37
- return async (input, ctx) => {
38
- if (!SANDBOX_REQUIRED_TOOLS.has(ctx.toolName)) {
39
- // No sandbox required — pass through
40
- return handler(input, ctx);
41
- }
42
- // Sandbox required — try to get runner from SPEC-742
43
- const runner = await tryGetSandboxRunner();
44
- if (runner === null) {
45
- // SPEC-742 not yet wired — log warning and pass through (transition period)
46
- console.warn(`[Planu] Sandbox runner not available for ${ctx.toolName}. ` +
47
- 'Running without sandbox isolation. Install SPEC-742 to enable.');
48
- return handler(input, ctx);
49
- }
50
- // Delegate to SPEC-742 runner — the handler is still called but command
51
- // execution inside it will go through the sandbox runner
52
- return handler(input, ctx);
53
- };
54
- };
55
- /**
56
- * Register a tool as requiring sandbox isolation.
57
- * Used by tool registration code (e.g. execute-sdd-flow).
58
- */
59
- export function requireSandbox(toolName) {
60
- SANDBOX_REQUIRED_TOOLS.add(toolName);
61
- }
62
- /**
63
- * Check if a tool requires sandbox (for testing).
64
- */
65
- export function isSandboxRequired(toolName) {
66
- return SANDBOX_REQUIRED_TOOLS.has(toolName);
67
- }
68
- //# sourceMappingURL=with-sandbox.js.map
@@ -1,18 +0,0 @@
1
- export type HealTier = 1 | 2 | 3;
2
- export interface HealFileResult {
3
- path: string;
4
- tier: HealTier;
5
- action: 'auto-fixed' | 'proposal-emitted' | 'skipped';
6
- backup?: string;
7
- proposalPath?: string;
8
- reason?: string;
9
- }
10
- export interface HealRunResult {
11
- scanned: number;
12
- results: HealFileResult[];
13
- skipped: {
14
- path: string;
15
- reason: string;
16
- }[];
17
- }
18
- //# sourceMappingURL=heal.d.ts.map
@@ -1,3 +0,0 @@
1
- // types/heal.ts — SPEC-745: Heal policy types
2
- export {};
3
- //# sourceMappingURL=heal.js.map