@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
@@ -165,6 +165,36 @@ function buildPrePushScript(protectedBranches, stalenessThreshold, baseBranch) {
165
165
  ' fi',
166
166
  'fi',
167
167
  '',
168
+ '# Dependency freshness gate for pnpm projects',
169
+ 'if command -v pnpm >/dev/null 2>&1 && [ -f "package.json" ] && [ -f "pnpm-lock.yaml" ]; then',
170
+ ' echo "[Planu] Checking dependency freshness..."',
171
+ ' pnpm install --frozen-lockfile --ignore-scripts >/dev/null || {',
172
+ ' echo "ERROR: package.json and pnpm-lock.yaml are not synchronized."',
173
+ ' echo "Run: pnpm install --lockfile-only --ignore-scripts"',
174
+ ' exit 1',
175
+ ' }',
176
+ ' pnpm audit --audit-level=high || {',
177
+ ' echo "ERROR: High or critical dependency vulnerabilities found."',
178
+ ' echo "Run: pnpm audit and update the affected packages before pushing."',
179
+ ' exit 1',
180
+ ' }',
181
+ ' OUTDATED_JSON=$(mktemp)',
182
+ ' OUTDATED_ERR=$(mktemp)',
183
+ ' pnpm outdated --format=json >"$OUTDATED_JSON" 2>"$OUTDATED_ERR"',
184
+ ' OUTDATED_STATUS=$?',
185
+ ' if [ "$OUTDATED_STATUS" -gt 1 ]; then',
186
+ ' echo "ERROR: Failed to query dependency freshness from the registry."',
187
+ ' cat "$OUTDATED_ERR"',
188
+ ' rm -f "$OUTDATED_JSON" "$OUTDATED_ERR"',
189
+ ' exit 1',
190
+ ' fi',
191
+ " node -e \"const fs=require('fs');const raw=fs.readFileSync(process.argv[1],'utf8').trim();const data=raw?JSON.parse(raw):{};const count=Array.isArray(data)?data.length:Object.keys(data).length;if(count>0){console.error('ERROR: Outdated dependencies found. Run: bash scripts/check-updates.sh --apply, pnpm update, or update intentionally before pushing.');process.exit(1)}\" \"$OUTDATED_JSON\" || {",
192
+ ' rm -f "$OUTDATED_JSON" "$OUTDATED_ERR"',
193
+ ' exit 1',
194
+ ' }',
195
+ ' rm -f "$OUTDATED_JSON" "$OUTDATED_ERR"',
196
+ 'fi',
197
+ '',
168
198
  'exit 0',
169
199
  ].join('\n');
170
200
  }
@@ -189,19 +219,33 @@ function buildPostCommitDriftScript() {
189
219
  '[ -z "$CHANGED" ] && exit 0',
190
220
  '',
191
221
  '# Build JSON array of file paths',
192
- 'FILES_JSON=$(echo "$CHANGED" | awk \'',
193
- ' BEGIN { printf "[" }',
194
- ' NR > 1 { printf "," }',
195
- // eslint-disable-next-line no-useless-escape
196
- ' { gsub(/"/, "\\\\\""); printf "\\"%s\\"", $0 }',
197
- ' END { printf "]" }',
198
- "')",
222
+ 'FILES_JSON="["',
223
+ 'FIRST=1',
224
+ 'while IFS= read -r FILE_PATH; do',
225
+ ' ESCAPED=$(printf \'%s\' "$FILE_PATH" | sed \'s/\\\\/\\\\\\\\/g; s/"/\\\\"/g\')',
226
+ ' if [ "$FIRST" -eq 0 ]; then',
227
+ ' FILES_JSON="$FILES_JSON,"',
228
+ ' fi',
229
+ ' FILES_JSON="$FILES_JSON\\"$ESCAPED\\""',
230
+ ' FIRST=0',
231
+ 'done <<PLANU_CHANGED_FILES',
232
+ '$CHANGED',
233
+ 'PLANU_CHANGED_FILES',
234
+ 'FILES_JSON="$FILES_JSON]"',
199
235
  '',
200
236
  "TS=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || date -u)",
201
237
  '',
202
238
  '# Check for drift-worthy files (dep manifests or uncommon extensions)',
203
- 'HAS_DEP=$(echo "$CHANGED" | grep -cE \'(^|/)(package\\.json|go\\.mod|requirements\\.txt|Cargo\\.toml|pom\\.xml|build\\.gradle|pyproject\\.toml|Gemfile|composer\\.json)$\' 2>/dev/null || echo 0)',
204
- "HAS_EXT=$(echo \"$CHANGED\" | grep -oE '\\.[a-zA-Z]+$' | grep -cvE '\\.(ts|tsx|js|jsx|mjs|cjs|json|md|mdx|sh|bash|txt|yaml|yml|toml|lock|sum|mod|py|go|rs|java|kt|rb|php|cs|dart|swift|html|css|scss|vue|svelte|env|sql|xml|csv)$' 2>/dev/null || echo 0)",
239
+ 'if echo "$CHANGED" | grep -qE \'(^|/)(package\\.json|go\\.mod|requirements\\.txt|Cargo\\.toml|pom\\.xml|build\\.gradle|pyproject\\.toml|Gemfile|composer\\.json)$\' 2>/dev/null; then',
240
+ ' HAS_DEP=1',
241
+ 'else',
242
+ ' HAS_DEP=0',
243
+ 'fi',
244
+ "if echo \"$CHANGED\" | grep -oE '\\.[a-zA-Z]+$' | grep -qvE '\\.(ts|tsx|js|jsx|mjs|cjs|json|md|mdx|sh|bash|txt|yaml|yml|toml|lock|sum|mod|py|go|rs|java|kt|rb|php|cs|dart|swift|html|css|scss|vue|svelte|env|sql|xml|csv)$' 2>/dev/null; then",
245
+ ' HAS_EXT=1',
246
+ 'else',
247
+ ' HAS_EXT=0',
248
+ 'fi',
205
249
  '',
206
250
  '# Only write if there is something worth processing',
207
251
  'if [ "$HAS_DEP" -gt 0 ] || [ "$HAS_EXT" -gt 0 ]; then',
@@ -14,6 +14,9 @@ import { safe, safeLicensed, safeTracked } from '../safe-handler.js';
14
14
  import { handleActivateLicense, handleDeactivateLicense } from '../activate-license.js';
15
15
  import { handleLicenseStatus } from '../license-status.js';
16
16
  import { LicenseStatusOutputSchema } from '../schemas/index.js';
17
+ // ── OAuth tools (register-oauth-tools.ts / register-configure-oauth-tool.ts) ─
18
+ import { handleStartOAuthFlow, handleOAuthStatus, StartOAuthFlowInputSchema, OAuthStatusInputSchema, } from '../oauth-handler.js';
19
+ import { handleConfigureOAuth } from '../configure-oauth-handler.js';
17
20
  // ── Usage tools (register-usage-tools.ts) ────────────────────────────────────
18
21
  import { handleUsageStats } from '../usage-stats.js';
19
22
  import { handleUsageReport } from '../usage-report.js';
@@ -126,6 +129,25 @@ export function registerInfraGroupTools(server) {
126
129
  outputSchema: LicenseStatusOutputSchema,
127
130
  annotations: { title: 'License Status', readOnlyHint: true },
128
131
  }, safe(async (args) => handleLicenseStatus(args)));
132
+ // ── OAuth tools ────────────────────────────────────────────────────────────
133
+ server.registerTool('start_oauth_flow', {
134
+ description: 'Start guided OAuth setup for supported integrations such as Figma, GitHub, Sentry, or Supabase.',
135
+ inputSchema: StartOAuthFlowInputSchema,
136
+ annotations: { title: 'Start OAuth Flow', readOnlyHint: false },
137
+ }, safeLicensed('start_oauth_flow', async (args) => handleStartOAuthFlow(args)));
138
+ server.registerTool('oauth_status', {
139
+ description: 'Show which OAuth-backed integrations are connected for this project.',
140
+ inputSchema: OAuthStatusInputSchema,
141
+ annotations: { title: 'OAuth Status', readOnlyHint: true },
142
+ }, safeLicensed('oauth_status', async (args) => handleOAuthStatus(args)));
143
+ server.registerTool('configure_oauth', {
144
+ description: 'Inspect Planu MCP OAuth/API-key auth status or test a bearer token without exposing secrets.',
145
+ inputSchema: {
146
+ action: z.enum(['status', 'test']).describe('Action: status | test'),
147
+ token: z.string().max(4096).optional().describe('Bearer token to test when action=test'),
148
+ },
149
+ annotations: { title: 'Configure OAuth', readOnlyHint: true },
150
+ }, safe(async (args) => handleConfigureOAuth(args)));
129
151
  // ── Usage tools ────────────────────────────────────────────────────────────
130
152
  server.registerTool('usage_stats', {
131
153
  description: 'View your Planu usage statistics — tool calls, top tools, remaining daily calls. ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planu/cli",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
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",
@@ -32,12 +32,12 @@
32
32
  "packageName": "@planu/core"
33
33
  },
34
34
  "optionalDependencies": {
35
- "@planu/core-darwin-arm64": "4.1.0",
36
- "@planu/core-darwin-x64": "4.1.0",
37
- "@planu/core-linux-arm64-gnu": "4.1.0",
38
- "@planu/core-linux-arm64-musl": "4.1.0",
39
- "@planu/core-linux-x64-gnu": "4.1.0",
40
- "@planu/core-linux-x64-musl": "4.1.0"
35
+ "@planu/core-darwin-arm64": "4.1.2",
36
+ "@planu/core-darwin-x64": "4.1.2",
37
+ "@planu/core-linux-arm64-gnu": "4.1.2",
38
+ "@planu/core-linux-arm64-musl": "4.1.2",
39
+ "@planu/core-linux-x64-gnu": "4.1.2",
40
+ "@planu/core-linux-x64-musl": "4.1.2"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=24.0.0"
@@ -68,6 +68,7 @@
68
68
  "test:integration": "vitest run tests/integration",
69
69
  "check": "pnpm typecheck && pnpm lint && pnpm format:check",
70
70
  "check:strict": "pnpm typecheck && pnpm lint && pnpm format:check && pnpm audit:deadcode && pnpm audit:circular && pnpm audit:types && pnpm audit:security && pnpm audit:licenses && pnpm audit:i18n",
71
+ "check:deps:fresh": "bash scripts/check-dependency-freshness.sh",
71
72
  "audit:deadcode": "knip",
72
73
  "audit:circular": "madge --circular --extensions ts src/",
73
74
  "audit:types": "type-coverage --at-least 98 --ignore-catch --strict --ignore-files 'tests/**'",
@@ -1,5 +0,0 @@
1
- export { RETRY_BUDGETS, nextDecision } from './policy.js';
2
- export { incrementCounter, readCounter, resetCounter } from './retry-counter.js';
3
- export type { RetryKey } from './retry-counter.js';
4
- export { withEscalation } from './with-escalation.js';
5
- //# sourceMappingURL=index.d.ts.map
@@ -1,5 +0,0 @@
1
- // engine/escalator/index.ts — SPEC-729: Public API re-exports
2
- export { RETRY_BUDGETS, nextDecision } from './policy.js';
3
- export { incrementCounter, readCounter, resetCounter } from './retry-counter.js';
4
- export { withEscalation } from './with-escalation.js';
5
- //# sourceMappingURL=index.js.map
@@ -1,6 +0,0 @@
1
- import type { RetroAuditInput } from '../../types/index.js';
2
- /**
3
- * Record a retro-audit event for a frozen spec edit.
4
- */
5
- export declare function recordRetroAudit(input: RetroAuditInput): Promise<void>;
6
- //# sourceMappingURL=retro-audit.d.ts.map
@@ -1,24 +0,0 @@
1
- // engine/freeze/retro-audit.ts — SPEC-747: Append retro_audit entries to transition log
2
- import { appendTransitionEvent } from '../../storage/transition-log.js';
3
- /**
4
- * Record a retro-audit event for a frozen spec edit.
5
- */
6
- export async function recordRetroAudit(input) {
7
- await appendTransitionEvent({
8
- projectId: input.projectId,
9
- specId: input.specId,
10
- eventType: 'retro_audit',
11
- from: 'done',
12
- to: 'done',
13
- actor: input.actor ?? 'system',
14
- reason: input.reason,
15
- sessionId: input.sessionId,
16
- modelId: input.modelId,
17
- meta: {
18
- specVersionBefore: input.versionBefore,
19
- specVersionAfter: input.versionAfter,
20
- forceEdit: input.forceEdit ?? false,
21
- },
22
- });
23
- }
24
- //# sourceMappingURL=retro-audit.js.map
@@ -1,9 +0,0 @@
1
- /**
2
- * Create a backup of `filePath` at `<filePath>.bak.<unix-ms>`.
3
- * Must be called BEFORE the atomic rename of the new content.
4
- *
5
- * @returns The backup file path.
6
- * @throws If the source file doesn't exist or the copy fails.
7
- */
8
- export declare function backupFile(filePath: string, timestampMs?: number): Promise<string>;
9
- //# sourceMappingURL=backup.d.ts.map
@@ -1,21 +0,0 @@
1
- // engine/heal/backup.ts — SPEC-745
2
- // Creates timestamped backup files before any tier-1 heal.
3
- import { copyFile } from 'node:fs/promises';
4
- import { existsSync } from 'node:fs';
5
- /**
6
- * Create a backup of `filePath` at `<filePath>.bak.<unix-ms>`.
7
- * Must be called BEFORE the atomic rename of the new content.
8
- *
9
- * @returns The backup file path.
10
- * @throws If the source file doesn't exist or the copy fails.
11
- */
12
- export async function backupFile(filePath, timestampMs) {
13
- const ts = timestampMs ?? Date.now();
14
- const backupPath = `${filePath}.bak.${ts}`;
15
- if (!existsSync(filePath)) {
16
- throw new Error(`Cannot backup non-existent file: ${filePath}`);
17
- }
18
- await copyFile(filePath, backupPath);
19
- return backupPath;
20
- }
21
- //# sourceMappingURL=backup.js.map
@@ -1,17 +0,0 @@
1
- export type IdiomaVerdict = 'es' | 'en' | 'mixed' | 'unknown' | 'abstain';
2
- export interface IdiomaResult {
3
- verdict: IdiomaVerdict;
4
- reason?: string;
5
- wordCount?: number;
6
- esScore?: number;
7
- enScore?: number;
8
- }
9
- /**
10
- * Detect the language of a spec's prose content.
11
- *
12
- * - Strips fenced code blocks before analysis.
13
- * - Abstains when prose word count is below 50 (SPEC-724: avoids noise on early drafts).
14
- * - Returns 'mixed' when both ES and EN markers exceed threshold.
15
- */
16
- export declare function detectIdioma(text: string): IdiomaResult;
17
- //# sourceMappingURL=index.d.ts.map
@@ -1,89 +0,0 @@
1
- // engine/idioma-validator/index.ts — SPEC-724: Spanish/English mixing detector for spec prose.
2
- // Skips fenced code blocks and abstains when prose word count is below 50 words.
3
- import { stripFencedBlocks } from '../spec-format/text-fences.js';
4
- const SPANISH_MARKERS = [
5
- 'de',
6
- 'del',
7
- 'la',
8
- 'el',
9
- 'en',
10
- 'con',
11
- 'para',
12
- 'por',
13
- 'una',
14
- 'los',
15
- 'las',
16
- 'al',
17
- 'se',
18
- 'que',
19
- 'es',
20
- 'son',
21
- 'fue',
22
- 'está',
23
- 'este',
24
- 'esta',
25
- 'estos',
26
- 'estas',
27
- 'un',
28
- 'unos',
29
- 'unas',
30
- ];
31
- const ENGLISH_MARKERS = [
32
- 'the',
33
- 'and',
34
- 'for',
35
- 'with',
36
- 'that',
37
- 'this',
38
- 'are',
39
- 'from',
40
- 'have',
41
- 'has',
42
- 'will',
43
- 'should',
44
- 'must',
45
- 'when',
46
- 'where',
47
- 'which',
48
- ];
49
- /**
50
- * Detect the language of a spec's prose content.
51
- *
52
- * - Strips fenced code blocks before analysis.
53
- * - Abstains when prose word count is below 50 (SPEC-724: avoids noise on early drafts).
54
- * - Returns 'mixed' when both ES and EN markers exceed threshold.
55
- */
56
- export function detectIdioma(text) {
57
- const prose = stripFencedBlocks(text);
58
- const words = prose.split(/\s+/).filter(Boolean);
59
- const wordCount = words.length;
60
- // SPEC-724: abstain on short prose stubs — not enough signal for reliable detection
61
- if (wordCount < 50) {
62
- return { verdict: 'abstain', reason: 'INSUFFICIENT_PROSE', wordCount };
63
- }
64
- const lower = prose.toLowerCase();
65
- const tokens = lower.split(/[\s\W]+/).filter((t) => t.length > 0);
66
- const totalTokens = tokens.length || 1;
67
- const esHits = tokens.filter((t) => SPANISH_MARKERS.includes(t)).length;
68
- const enHits = tokens.filter((t) => ENGLISH_MARKERS.includes(t)).length;
69
- const esScore = esHits / totalTokens;
70
- const enScore = enHits / totalTokens;
71
- const THRESHOLD = 0.03; // >3% hit rate = language present
72
- const hasEs = esScore > THRESHOLD;
73
- const hasEn = enScore > THRESHOLD;
74
- let verdict;
75
- if (hasEs && hasEn) {
76
- verdict = 'mixed';
77
- }
78
- else if (hasEs) {
79
- verdict = 'es';
80
- }
81
- else if (hasEn) {
82
- verdict = 'en';
83
- }
84
- else {
85
- verdict = 'unknown';
86
- }
87
- return { verdict, wordCount, esScore, enScore };
88
- }
89
- //# sourceMappingURL=index.js.map
@@ -1,4 +0,0 @@
1
- export { runSaga } from './cascade-saga.js';
2
- export { runTransitionCascadeSaga } from './transition-cascade.js';
3
- export type { TransitionCascadeCtx, TransitionCascadeResult } from '../../types/saga.js';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1,4 +0,0 @@
1
- // engine/saga/index.ts — SPEC-735: Saga barrel exports
2
- export { runSaga } from './cascade-saga.js';
3
- export { runTransitionCascadeSaga } from './transition-cascade.js';
4
- //# sourceMappingURL=index.js.map
@@ -1,3 +0,0 @@
1
- export type { TransitionTrigger, TransitionContext, TransitionRecord } from './transition-spec.js';
2
- export { transitionSpec } from './transition-spec.js';
3
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +0,0 @@
1
- export { transitionSpec } from './transition-spec.js';
2
- //# sourceMappingURL=index.js.map
@@ -1,6 +0,0 @@
1
- import type { Spec } from '../../types/index.js';
2
- export declare function getInlineCss(): string;
3
- export declare function getInlineScript(): string;
4
- /** Generate the full dashboard HTML document. */
5
- export declare function generateDashboardHtml(specs: Spec[], availablePages?: string[]): string;
6
- //# sourceMappingURL=dashboard-renderer.d.ts.map