pulse-framework-cli 0.4.1

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 (64) hide show
  1. package/dist/commands/checkpoint.d.ts +2 -0
  2. package/dist/commands/checkpoint.js +129 -0
  3. package/dist/commands/correct.d.ts +2 -0
  4. package/dist/commands/correct.js +77 -0
  5. package/dist/commands/doctor.d.ts +2 -0
  6. package/dist/commands/doctor.js +183 -0
  7. package/dist/commands/escalate.d.ts +2 -0
  8. package/dist/commands/escalate.js +226 -0
  9. package/dist/commands/init.d.ts +2 -0
  10. package/dist/commands/init.js +570 -0
  11. package/dist/commands/learn.d.ts +2 -0
  12. package/dist/commands/learn.js +137 -0
  13. package/dist/commands/profile.d.ts +2 -0
  14. package/dist/commands/profile.js +39 -0
  15. package/dist/commands/reset.d.ts +2 -0
  16. package/dist/commands/reset.js +130 -0
  17. package/dist/commands/review.d.ts +2 -0
  18. package/dist/commands/review.js +129 -0
  19. package/dist/commands/run.d.ts +2 -0
  20. package/dist/commands/run.js +272 -0
  21. package/dist/commands/start.d.ts +2 -0
  22. package/dist/commands/start.js +196 -0
  23. package/dist/commands/status.d.ts +2 -0
  24. package/dist/commands/status.js +239 -0
  25. package/dist/commands/watch.d.ts +2 -0
  26. package/dist/commands/watch.js +98 -0
  27. package/dist/hooks/install.d.ts +1 -0
  28. package/dist/hooks/install.js +89 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +40 -0
  31. package/dist/lib/artifacts.d.ts +7 -0
  32. package/dist/lib/artifacts.js +52 -0
  33. package/dist/lib/briefing.d.ts +77 -0
  34. package/dist/lib/briefing.js +231 -0
  35. package/dist/lib/clipboard.d.ts +9 -0
  36. package/dist/lib/clipboard.js +116 -0
  37. package/dist/lib/config.d.ts +14 -0
  38. package/dist/lib/config.js +167 -0
  39. package/dist/lib/context-export.d.ts +30 -0
  40. package/dist/lib/context-export.js +149 -0
  41. package/dist/lib/exec.d.ts +9 -0
  42. package/dist/lib/exec.js +23 -0
  43. package/dist/lib/git.d.ts +24 -0
  44. package/dist/lib/git.js +74 -0
  45. package/dist/lib/input.d.ts +15 -0
  46. package/dist/lib/input.js +80 -0
  47. package/dist/lib/notifications.d.ts +2 -0
  48. package/dist/lib/notifications.js +25 -0
  49. package/dist/lib/paths.d.ts +4 -0
  50. package/dist/lib/paths.js +39 -0
  51. package/dist/lib/prompts.d.ts +43 -0
  52. package/dist/lib/prompts.js +270 -0
  53. package/dist/lib/scanner.d.ts +37 -0
  54. package/dist/lib/scanner.js +413 -0
  55. package/dist/lib/types.d.ts +37 -0
  56. package/dist/lib/types.js +2 -0
  57. package/package.json +42 -0
  58. package/templates/.cursorrules +159 -0
  59. package/templates/AGENTS.md +198 -0
  60. package/templates/cursor/mcp.json +9 -0
  61. package/templates/cursor/pulse.mdc +144 -0
  62. package/templates/roles/architect.cursorrules +15 -0
  63. package/templates/roles/backend.cursorrules +12 -0
  64. package/templates/roles/frontend.cursorrules +12 -0
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ /**
3
+ * Briefing Library - Aggregates data for Decision Briefings
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.calculateScopeCheck = calculateScopeCheck;
7
+ exports.calculateRiskSummary = calculateRiskSummary;
8
+ exports.calculateTimeSummary = calculateTimeSummary;
9
+ exports.generateRecommendation = generateRecommendation;
10
+ exports.renderProgressBar = renderProgressBar;
11
+ exports.renderBriefing = renderBriefing;
12
+ /**
13
+ * Calculate scope check against preset limits
14
+ */
15
+ function calculateScopeCheck(config, stats) {
16
+ const totalLines = stats.linesAdded + stats.linesDeleted;
17
+ const files = {
18
+ current: stats.filesChanged,
19
+ max: config.thresholds.warnMaxFilesChanged,
20
+ percent: Math.round((stats.filesChanged / config.thresholds.warnMaxFilesChanged) * 100),
21
+ };
22
+ const lines = {
23
+ current: totalLines,
24
+ max: config.thresholds.warnMaxLinesChanged,
25
+ percent: Math.round((totalLines / config.thresholds.warnMaxLinesChanged) * 100),
26
+ };
27
+ const deletes = {
28
+ current: stats.linesDeleted,
29
+ max: config.thresholds.warnMaxDeletions,
30
+ percent: Math.round((stats.linesDeleted / config.thresholds.warnMaxDeletions) * 100),
31
+ };
32
+ const exceededFields = [];
33
+ if (files.percent > 100)
34
+ exceededFields.push("files");
35
+ if (lines.percent > 100)
36
+ exceededFields.push("lines");
37
+ if (deletes.percent > 100)
38
+ exceededFields.push("deletes");
39
+ return {
40
+ files,
41
+ lines,
42
+ deletes,
43
+ exceeded: exceededFields.length > 0,
44
+ exceededFields,
45
+ };
46
+ }
47
+ /**
48
+ * Summarize risks from scan results
49
+ */
50
+ function calculateRiskSummary(scanResult) {
51
+ const criticalCount = scanResult.findings.filter((f) => f.severity === "critical").length;
52
+ const warningCount = scanResult.findings.filter((f) => f.severity === "warn").length;
53
+ const loopSignals = scanResult.findings
54
+ .filter((f) => f.code === "LOOP_SIGNAL")
55
+ .map((f) => f.message);
56
+ let loopRisk = "LOW";
57
+ if (loopSignals.length >= 3) {
58
+ loopRisk = "HIGH";
59
+ }
60
+ else if (loopSignals.length >= 1) {
61
+ loopRisk = "MEDIUM";
62
+ }
63
+ return {
64
+ criticalCount,
65
+ warningCount,
66
+ findings: scanResult.findings,
67
+ loopRisk,
68
+ loopSignals,
69
+ };
70
+ }
71
+ /**
72
+ * Calculate time-based metrics
73
+ */
74
+ function calculateTimeSummary(lastCheckpointAt, checkpointReminderMinutes) {
75
+ const now = Date.now();
76
+ let minutesSinceCheckpoint = null;
77
+ let checkpointOverdue = false;
78
+ if (lastCheckpointAt) {
79
+ const lastCp = Date.parse(lastCheckpointAt);
80
+ if (Number.isFinite(lastCp)) {
81
+ minutesSinceCheckpoint = Math.floor((now - lastCp) / 60000);
82
+ checkpointOverdue = minutesSinceCheckpoint > checkpointReminderMinutes;
83
+ }
84
+ }
85
+ return {
86
+ minutesSinceCheckpoint,
87
+ checkpointOverdue,
88
+ sessionMinutes: minutesSinceCheckpoint, // For now, same as checkpoint
89
+ };
90
+ }
91
+ /**
92
+ * Generate recommendation based on all factors
93
+ */
94
+ function generateRecommendation(scope, risk, time) {
95
+ // Critical findings = STOP
96
+ if (risk.criticalCount > 0) {
97
+ return {
98
+ action: "stop",
99
+ reason: `${risk.criticalCount} Critical Finding(s) - Fix vor Merge`,
100
+ command: "pulse doctor",
101
+ };
102
+ }
103
+ // High loop risk = Escalate
104
+ if (risk.loopRisk === "HIGH") {
105
+ return {
106
+ action: "escalate",
107
+ reason: "High loop risk detected",
108
+ command: "pulse escalate",
109
+ };
110
+ }
111
+ // Scope exceeded significantly = Checkpoint
112
+ if (scope.exceeded && scope.lines.percent > 150) {
113
+ return {
114
+ action: "checkpoint",
115
+ reason: `Scope exceeded (${scope.lines.current}/${scope.lines.max} Lines)`,
116
+ command: "pulse checkpoint -m 'wip: progress'",
117
+ };
118
+ }
119
+ // Checkpoint overdue = Checkpoint
120
+ if (time.checkpointOverdue) {
121
+ return {
122
+ action: "checkpoint",
123
+ reason: `${time.minutesSinceCheckpoint} min since last checkpoint`,
124
+ command: "pulse checkpoint -m 'wip: progress'",
125
+ };
126
+ }
127
+ // Medium loop risk = Checkpoint
128
+ if (risk.loopRisk === "MEDIUM") {
129
+ return {
130
+ action: "checkpoint",
131
+ reason: "Loop signals detected - checkpoint recommended",
132
+ command: "pulse checkpoint -m 'wip: checkpoint before continuing'",
133
+ };
134
+ }
135
+ // Warnings but manageable = Approve with note
136
+ if (risk.warningCount > 0) {
137
+ return {
138
+ action: "approve",
139
+ reason: `${risk.warningCount} Warning(s) - Approve with reservations`,
140
+ };
141
+ }
142
+ // All good
143
+ return {
144
+ action: "approve",
145
+ reason: "No findings, scope OK",
146
+ };
147
+ }
148
+ /**
149
+ * Render progress bar
150
+ */
151
+ function renderProgressBar(percent, width = 15) {
152
+ const filled = Math.min(width, Math.round((percent / 100) * width));
153
+ const empty = width - filled;
154
+ const bar = "█".repeat(filled) + "░".repeat(empty);
155
+ return bar;
156
+ }
157
+ /**
158
+ * Render full Decision Briefing to terminal
159
+ */
160
+ function renderBriefing(briefing) {
161
+ const lines = [];
162
+ const boxWidth = 55;
163
+ const hr = "─".repeat(boxWidth);
164
+ lines.push(`┌${"─".repeat(boxWidth)}┐`);
165
+ lines.push(`│ PULSE Review – Decision Briefing${" ".repeat(boxWidth - 35)}│`);
166
+ lines.push(`├${hr}┤`);
167
+ // Preset + Profile
168
+ const presetProfile = briefing.preset
169
+ ? `${briefing.preset}/${briefing.profile}`
170
+ : briefing.profile;
171
+ lines.push(`│ Profile: ${presetProfile}${" ".repeat(boxWidth - 11 - presetProfile.length)}│`);
172
+ lines.push(`├${hr}┤`);
173
+ // Scope Check
174
+ lines.push(`│ SCOPE-CHECK${" ".repeat(boxWidth - 12)}│`);
175
+ const { scope } = briefing;
176
+ const filesBar = renderProgressBar(scope.files.percent);
177
+ const linesBar = renderProgressBar(scope.lines.percent);
178
+ const deletesBar = renderProgressBar(scope.deletes.percent);
179
+ const filesLine = ` Files: ${scope.files.current}/${scope.files.max} (${scope.files.percent}%) ${filesBar}`;
180
+ const linesLine = ` Lines: ${scope.lines.current}/${scope.lines.max} (${scope.lines.percent}%) ${linesBar}`;
181
+ const deletesLine = ` Deletes: ${scope.deletes.current}/${scope.deletes.max} (${scope.deletes.percent}%) ${deletesBar}`;
182
+ lines.push(`│${filesLine}${" ".repeat(Math.max(0, boxWidth - filesLine.length))}│`);
183
+ lines.push(`│${linesLine}${" ".repeat(Math.max(0, boxWidth - linesLine.length))}│`);
184
+ lines.push(`│${deletesLine}${" ".repeat(Math.max(0, boxWidth - deletesLine.length))}│`);
185
+ if (scope.exceeded) {
186
+ const warn = ` ⚠️ Exceeded: ${scope.exceededFields.join(", ")}`;
187
+ lines.push(`│${warn}${" ".repeat(Math.max(0, boxWidth - warn.length))}│`);
188
+ }
189
+ lines.push(`├${hr}┤`);
190
+ // Risk Summary
191
+ lines.push(`│ RISK-SUMMARY${" ".repeat(boxWidth - 13)}│`);
192
+ const { risk, time } = briefing;
193
+ if (risk.criticalCount > 0) {
194
+ const crit = ` 🚨 ${risk.criticalCount} Critical`;
195
+ lines.push(`│${crit}${" ".repeat(Math.max(0, boxWidth - crit.length))}│`);
196
+ }
197
+ if (risk.warningCount > 0) {
198
+ const warn = ` ⚠️ ${risk.warningCount} Warnings`;
199
+ lines.push(`│${warn}${" ".repeat(Math.max(0, boxWidth - warn.length))}│`);
200
+ }
201
+ if (risk.criticalCount === 0 && risk.warningCount === 0) {
202
+ const ok = ` ✅ No findings`;
203
+ lines.push(`│${ok}${" ".repeat(Math.max(0, boxWidth - ok.length))}│`);
204
+ }
205
+ const timeStr = time.minutesSinceCheckpoint !== null
206
+ ? ` ⏱️ Checkpoint ${time.minutesSinceCheckpoint} min ago`
207
+ : ` ⏱️ No checkpoint`;
208
+ lines.push(`│${timeStr}${" ".repeat(Math.max(0, boxWidth - timeStr.length))}│`);
209
+ const loopEmoji = risk.loopRisk === "HIGH" ? "🔴" : risk.loopRisk === "MEDIUM" ? "🟡" : "🟢";
210
+ const loopStr = ` ${loopEmoji} Loop Risk: ${risk.loopRisk}`;
211
+ lines.push(`│${loopStr}${" ".repeat(Math.max(0, boxWidth - loopStr.length))}│`);
212
+ lines.push(`├${hr}┤`);
213
+ // Recommendation
214
+ const { recommendation } = briefing;
215
+ const actionEmoji = {
216
+ approve: "✅",
217
+ checkpoint: "⏱️",
218
+ escalate: "🚨",
219
+ stop: "🛑",
220
+ }[recommendation.action];
221
+ const recLine = `${actionEmoji} RECOMMENDATION: ${recommendation.action.toUpperCase()}`;
222
+ lines.push(`│ ${recLine}${" ".repeat(Math.max(0, boxWidth - recLine.length - 1))}│`);
223
+ const reasonLine = ` → ${recommendation.reason}`;
224
+ lines.push(`│${reasonLine}${" ".repeat(Math.max(0, boxWidth - reasonLine.length))}│`);
225
+ if (recommendation.command) {
226
+ const cmdLine = ` → ${recommendation.command}`;
227
+ lines.push(`│${cmdLine}${" ".repeat(Math.max(0, boxWidth - cmdLine.length))}│`);
228
+ }
229
+ lines.push(`└${"─".repeat(boxWidth)}┘`);
230
+ return lines.join("\n");
231
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copy text to system clipboard (cross-platform)
3
+ * Returns true if successful, false otherwise
4
+ */
5
+ export declare function copyToClipboard(text: string): Promise<boolean>;
6
+ /**
7
+ * Copy text to clipboard and return status message
8
+ */
9
+ export declare function copyAndNotify(text: string): Promise<string>;
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.copyToClipboard = copyToClipboard;
40
+ exports.copyAndNotify = copyAndNotify;
41
+ const node_os_1 = __importDefault(require("node:os"));
42
+ /**
43
+ * Copy text to system clipboard (cross-platform)
44
+ * Returns true if successful, false otherwise
45
+ */
46
+ async function copyToClipboard(text) {
47
+ const platform = node_os_1.default.platform();
48
+ try {
49
+ if (platform === "darwin") {
50
+ // macOS: pbcopy
51
+ await execWithStdin("pbcopy", [], text);
52
+ return true;
53
+ }
54
+ if (platform === "win32") {
55
+ // Windows: clip
56
+ await execWithStdin("clip", [], text);
57
+ return true;
58
+ }
59
+ if (platform === "linux") {
60
+ // Linux: try xclip, then xsel
61
+ try {
62
+ await execWithStdin("xclip", ["-selection", "clipboard"], text);
63
+ return true;
64
+ }
65
+ catch {
66
+ try {
67
+ await execWithStdin("xsel", ["--clipboard", "--input"], text);
68
+ return true;
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }
74
+ }
75
+ return false;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * Execute a command with stdin input
83
+ */
84
+ async function execWithStdin(cmd, args, input) {
85
+ const { spawn } = await Promise.resolve().then(() => __importStar(require("node:child_process")));
86
+ return new Promise((resolve, reject) => {
87
+ const proc = spawn(cmd, args, {
88
+ stdio: ["pipe", "pipe", "pipe"],
89
+ });
90
+ proc.on("error", reject);
91
+ proc.on("close", (code) => {
92
+ if (code === 0) {
93
+ resolve();
94
+ }
95
+ else {
96
+ reject(new Error(`${cmd} exited with code ${code}`));
97
+ }
98
+ });
99
+ proc.stdin?.write(input);
100
+ proc.stdin?.end();
101
+ });
102
+ }
103
+ /**
104
+ * Copy text to clipboard and return status message
105
+ */
106
+ async function copyAndNotify(text) {
107
+ const success = await copyToClipboard(text);
108
+ if (success) {
109
+ return "📋 In Zwischenablage kopiert!";
110
+ }
111
+ const platform = node_os_1.default.platform();
112
+ if (platform === "linux") {
113
+ return "⚠️ Clipboard not available (xclip/xsel not installed)";
114
+ }
115
+ return "⚠️ Could not copy to clipboard";
116
+ }
@@ -0,0 +1,14 @@
1
+ import type { PulseConfig, PresetName, PresetConfig } from "./types.js";
2
+ export declare const PRESETS: Record<PresetName, PresetConfig>;
3
+ export declare const DEFAULT_CONFIG: PulseConfig;
4
+ export declare function loadConfig(repoRoot: string): Promise<PulseConfig>;
5
+ export declare function writeDefaultConfig(repoRoot: string): Promise<void>;
6
+ export declare function normalizeConfig(input: any): PulseConfig;
7
+ /**
8
+ * Get all available preset names
9
+ */
10
+ export declare function getPresetNames(): PresetName[];
11
+ /**
12
+ * Get preset configuration by name
13
+ */
14
+ export declare function getPreset(name: PresetName): PresetConfig;
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_CONFIG = exports.PRESETS = void 0;
7
+ exports.loadConfig = loadConfig;
8
+ exports.writeDefaultConfig = writeDefaultConfig;
9
+ exports.normalizeConfig = normalizeConfig;
10
+ exports.getPresetNames = getPresetNames;
11
+ exports.getPreset = getPreset;
12
+ const promises_1 = __importDefault(require("node:fs/promises"));
13
+ const paths_js_1 = require("./paths.js");
14
+ // ════════════════════════════════════════════════════════════════════════════
15
+ // TEAM PRESETS
16
+ // ════════════════════════════════════════════════════════════════════════════
17
+ exports.PRESETS = {
18
+ frontend: {
19
+ warnMaxFilesChanged: 10,
20
+ warnMaxLinesChanged: 200,
21
+ warnMaxDeletions: 30,
22
+ checkpointReminderMinutes: 20,
23
+ extraSecretPatterns: [
24
+ "NEXT_PUBLIC_",
25
+ "VITE_",
26
+ ],
27
+ },
28
+ backend: {
29
+ warnMaxFilesChanged: 15,
30
+ warnMaxLinesChanged: 400,
31
+ warnMaxDeletions: 50,
32
+ checkpointReminderMinutes: 30,
33
+ extraSecretPatterns: [
34
+ "DATABASE_URL",
35
+ "REDIS_URL",
36
+ "SMTP_",
37
+ ],
38
+ },
39
+ fullstack: {
40
+ warnMaxFilesChanged: 20,
41
+ warnMaxLinesChanged: 500,
42
+ warnMaxDeletions: 60,
43
+ checkpointReminderMinutes: 25,
44
+ },
45
+ monorepo: {
46
+ warnMaxFilesChanged: 30,
47
+ warnMaxLinesChanged: 800,
48
+ warnMaxDeletions: 100,
49
+ checkpointReminderMinutes: 30,
50
+ },
51
+ custom: {
52
+ warnMaxFilesChanged: 15,
53
+ warnMaxLinesChanged: 300,
54
+ warnMaxDeletions: 50,
55
+ checkpointReminderMinutes: 30,
56
+ },
57
+ };
58
+ exports.DEFAULT_CONFIG = {
59
+ version: 1,
60
+ projectType: "unknown",
61
+ enforcement: "mixed",
62
+ notifications: "both",
63
+ thresholds: {
64
+ warnMaxFilesChanged: 15,
65
+ warnMaxLinesChanged: 300,
66
+ warnMaxDeletions: 50,
67
+ },
68
+ patterns: {
69
+ // Keep these as regex source strings (without surrounding / /).
70
+ // We intentionally include a few high-signal patterns + a generic catch-all.
71
+ secret: [
72
+ "AKIA[0-9A-Z]{16}", // AWS access key id
73
+ "ASIA[0-9A-Z]{16}", // AWS temporary key id
74
+ "ghp_[A-Za-z0-9]{36}", // GitHub classic token
75
+ "github_pat_[A-Za-z0-9_]{80,}", // GitHub fine-grained token
76
+ "sk_(live|test)_[A-Za-z0-9]{16,}", // Stripe
77
+ "AIzaSy[A-Za-z0-9_-]{30,}", // Google API key
78
+ "(?i)(api[_-]?key|secret|token|password)\\s*[:=]\\s*['\\\"][^'\\\"]{8,}['\\\"]", // generic
79
+ "(?i)-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----",
80
+ ],
81
+ prodUrl: [
82
+ // any http(s) URL that isn't localhost or example domains
83
+ "https?://(?!localhost\\b)(?!127\\.0\\.0\\.1\\b)(?!0\\.0\\.0\\.0\\b)(?!example\\.com\\b)[^\\s'\\\"]+",
84
+ ],
85
+ },
86
+ commands: {
87
+ test: "",
88
+ },
89
+ };
90
+ async function loadConfig(repoRoot) {
91
+ const p = (0, paths_js_1.configFile)(repoRoot);
92
+ try {
93
+ const raw = await promises_1.default.readFile(p, "utf8");
94
+ const parsed = JSON.parse(raw);
95
+ return normalizeConfig(parsed);
96
+ }
97
+ catch {
98
+ return exports.DEFAULT_CONFIG;
99
+ }
100
+ }
101
+ async function writeDefaultConfig(repoRoot) {
102
+ const p = (0, paths_js_1.configFile)(repoRoot);
103
+ await promises_1.default.writeFile(p, JSON.stringify(exports.DEFAULT_CONFIG, null, 2) + "\n", "utf8");
104
+ }
105
+ function normalizeConfig(input) {
106
+ // Apply preset defaults first if preset is set
107
+ const presetName = input?.preset;
108
+ const presetDefaults = presetName && exports.PRESETS[presetName] ? exports.PRESETS[presetName] : null;
109
+ const merged = {
110
+ ...exports.DEFAULT_CONFIG,
111
+ ...(typeof input === "object" && input ? input : {}),
112
+ thresholds: {
113
+ ...exports.DEFAULT_CONFIG.thresholds,
114
+ // Apply preset thresholds
115
+ ...(presetDefaults ? {
116
+ warnMaxFilesChanged: presetDefaults.warnMaxFilesChanged,
117
+ warnMaxLinesChanged: presetDefaults.warnMaxLinesChanged,
118
+ warnMaxDeletions: presetDefaults.warnMaxDeletions,
119
+ } : {}),
120
+ // User overrides take precedence
121
+ ...(input?.thresholds ?? {}),
122
+ },
123
+ patterns: {
124
+ ...exports.DEFAULT_CONFIG.patterns,
125
+ ...(input?.patterns ?? {}),
126
+ // Merge extra secret patterns from preset
127
+ secret: [
128
+ ...exports.DEFAULT_CONFIG.patterns.secret,
129
+ ...(presetDefaults?.extraSecretPatterns ?? []),
130
+ ...(input?.patterns?.secret ?? []),
131
+ ],
132
+ },
133
+ commands: {
134
+ ...exports.DEFAULT_CONFIG.commands,
135
+ ...(input?.commands ?? {}),
136
+ },
137
+ // Apply preset checkpoint reminder
138
+ checkpointReminderMinutes: input?.checkpointReminderMinutes
139
+ ?? presetDefaults?.checkpointReminderMinutes
140
+ ?? 30,
141
+ };
142
+ // Defensive: ensure required shapes
143
+ merged.version = 1;
144
+ merged.preset = presetName;
145
+ if (!["advisory", "mixed", "strict"].includes(merged.enforcement)) {
146
+ merged.enforcement = exports.DEFAULT_CONFIG.enforcement;
147
+ }
148
+ if (!["terminal", "macos", "both"].includes(merged.notifications)) {
149
+ merged.notifications = exports.DEFAULT_CONFIG.notifications;
150
+ }
151
+ if (!["node", "python", "unknown"].includes(merged.projectType)) {
152
+ merged.projectType = exports.DEFAULT_CONFIG.projectType;
153
+ }
154
+ return merged;
155
+ }
156
+ /**
157
+ * Get all available preset names
158
+ */
159
+ function getPresetNames() {
160
+ return Object.keys(exports.PRESETS);
161
+ }
162
+ /**
163
+ * Get preset configuration by name
164
+ */
165
+ function getPreset(name) {
166
+ return exports.PRESETS[name] ?? exports.PRESETS.custom;
167
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Context Export - Selective file export for escalation
3
+ */
4
+ export type FileExport = {
5
+ path: string;
6
+ content: string;
7
+ lines: number;
8
+ };
9
+ export type ContextExport = {
10
+ files: FileExport[];
11
+ totalFiles: number;
12
+ totalLines: number;
13
+ truncated: boolean;
14
+ };
15
+ /**
16
+ * Read and export specific files
17
+ */
18
+ export declare function exportFiles(repoRoot: string, patterns: string[]): Promise<ContextExport>;
19
+ /**
20
+ * Auto-detect relevant files from git diff
21
+ */
22
+ export declare function autoDetectFiles(repoRoot: string, diffNameStatus: string): Promise<string[]>;
23
+ /**
24
+ * Render context export as markdown
25
+ */
26
+ export declare function renderContextExport(ctx: ContextExport): string;
27
+ /**
28
+ * Render context export in XML-style for AI models
29
+ */
30
+ export declare function renderContextExportXml(ctx: ContextExport): string;