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,413 @@
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.scanDiff = scanDiff;
7
+ exports.detectLoopSignals = detectLoopSignals;
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ function scanDiff(config, input) {
10
+ const findings = [];
11
+ const num = parseNumstat(input.diffNumstat);
12
+ const deletedFiles = parseDeletedFiles(input.diffNameStatus);
13
+ const touchedFiles = num.map((n) => n.file);
14
+ const stats = {
15
+ filesChanged: touchedFiles.length,
16
+ linesAdded: num.reduce((a, n) => a + n.added, 0),
17
+ linesDeleted: num.reduce((a, n) => a + n.deleted, 0),
18
+ deletedFiles,
19
+ touchedFiles,
20
+ };
21
+ // --- Critical: secrets ---
22
+ const secretHits = matchAny(config.patterns.secret, input.diffText);
23
+ if (secretHits.length) {
24
+ findings.push({
25
+ severity: "critical",
26
+ code: "SECRETS",
27
+ message: `Possible secrets found in diff (${secretHits.length} hit(s)).`,
28
+ details: secretHits.slice(0, 5).join("\n"),
29
+ });
30
+ }
31
+ // --- Warn: production URLs ---
32
+ const prodUrlHits = matchAny(config.patterns.prodUrl, input.diffText).filter((m) => !m.includes("localhost") && !m.includes("127.0.0.1"));
33
+ if (prodUrlHits.length) {
34
+ findings.push({
35
+ severity: config.enforcement === "strict" ? "critical" : "warn",
36
+ code: "PROD_URL",
37
+ message: `Production/external URL(s) detected in diff (${prodUrlHits.length} hit(s)). Prefer env vars.`,
38
+ details: prodUrlHits.slice(0, 5).join("\n"),
39
+ });
40
+ }
41
+ // --- Critical/Warn: mass deletes (DELETE safeguard) ---
42
+ if (deletedFiles.length) {
43
+ findings.push({
44
+ severity: config.enforcement === "advisory" ? "warn" : "critical",
45
+ code: "MASS_DELETE",
46
+ message: `File deletion detected (${deletedFiles.length} file(s)). DELETE requires explicit confirmation.`,
47
+ details: deletedFiles.slice(0, 10).join("\n"),
48
+ });
49
+ }
50
+ else if (stats.linesDeleted >= config.thresholds.warnMaxDeletions) {
51
+ findings.push({
52
+ severity: "warn",
53
+ code: "MASS_DELETE",
54
+ message: `High deletions in diff (${stats.linesDeleted}). Consider smaller milestones / checkpoint.`,
55
+ });
56
+ }
57
+ // --- Warn: big changeset (red flag: too much at once) ---
58
+ if (stats.filesChanged >= config.thresholds.warnMaxFilesChanged ||
59
+ stats.linesAdded + stats.linesDeleted >= config.thresholds.warnMaxLinesChanged) {
60
+ findings.push({
61
+ severity: "warn",
62
+ code: "BIG_CHANGESET",
63
+ message: `Large changeset (files=${stats.filesChanged}, lines=${stats.linesAdded + stats.linesDeleted}). Consider smaller milestones.`,
64
+ details: input.diffStat || undefined,
65
+ });
66
+ }
67
+ // --- Warn: dependencies changed (unknown deps) ---
68
+ const depsTouched = touchedFiles.some((f) => ["package.json", "package-lock.json", "pnpm-lock.yaml", "yarn.lock"].includes(node_path_1.default.basename(f)));
69
+ if (depsTouched) {
70
+ // Parse NEW dependencies from diff
71
+ const newDeps = extractNewDependencies(input.diffText);
72
+ if (newDeps.length > 0) {
73
+ findings.push({
74
+ severity: "warn",
75
+ code: "UNKNOWN_DEPS",
76
+ message: `New dependencies detected (${newDeps.length}): Do you know these?`,
77
+ details: newDeps.slice(0, 10).map((d) => ` + ${d.name}${d.version ? ` @ ${d.version}` : ""}`).join("\n"),
78
+ });
79
+ }
80
+ else {
81
+ findings.push({
82
+ severity: "warn",
83
+ code: "UNKNOWN_DEPS",
84
+ message: "Dependency/lockfile change detected. Check if changes are intended.",
85
+ });
86
+ }
87
+ }
88
+ // --- Warn: console.log / debug leftovers ---
89
+ const consoleHits = simpleLineHits(input.diffText, /^\+\s*console\.log\(/gm, 5);
90
+ if (consoleHits.length) {
91
+ findings.push({
92
+ severity: "warn",
93
+ code: "CONSOLE_LOG",
94
+ message: "console.log detected in added lines. Remove debug output before merging.",
95
+ details: consoleHits.join("\n"),
96
+ });
97
+ }
98
+ // --- Warn: commented-out code (very heuristic) ---
99
+ const commentedHits = simpleLineHits(input.diffText, /^\+\s*\/\/\s*(if|for|while|return|const|let|function|class)\b/gim, 5);
100
+ if (commentedHits.length) {
101
+ findings.push({
102
+ severity: "warn",
103
+ code: "COMMENTED_CODE",
104
+ message: "Commented-out code detected in added lines. Prefer deleting or tracking via issue.",
105
+ details: commentedHits.join("\n"),
106
+ });
107
+ }
108
+ // --- Warn: TODO without issue ref ---
109
+ const todoHits = simpleLineHits(input.diffText, /^\+\s*\/\/\s*TODO\b(?!.*#\d+)(?!.*CU-)(?!.*JIRA-)/gim, 5);
110
+ if (todoHits.length) {
111
+ findings.push({
112
+ severity: "warn",
113
+ code: "TODO_NO_ISSUE",
114
+ message: "TODO comment without issue reference detected. Add ticket reference to avoid orphan TODOs.",
115
+ details: todoHits.join("\n"),
116
+ });
117
+ }
118
+ return { findings, stats };
119
+ }
120
+ /**
121
+ * Extract NEW dependencies from git diff of package.json
122
+ * Looks for added lines with "package": "version" pattern
123
+ */
124
+ function extractNewDependencies(diffText) {
125
+ const deps = [];
126
+ const seen = new Set();
127
+ // Match added lines in package.json that look like dependencies
128
+ // Pattern: + "package-name": "^1.2.3"
129
+ const depLineRegex = /^\+\s*"(@?[\w\-./]+)"\s*:\s*"([^"]+)"/gm;
130
+ let match;
131
+ while ((match = depLineRegex.exec(diffText)) !== null) {
132
+ const name = match[1] ?? "";
133
+ const version = match[2] ?? "";
134
+ // Skip common non-dependency fields
135
+ if (isNonDependencyField(name))
136
+ continue;
137
+ // Skip if it looks like a lockfile internal entry
138
+ if (name.startsWith("node_modules/"))
139
+ continue;
140
+ // Must look like a valid npm package name
141
+ // - Starts with @ (scoped) or a letter
142
+ // - Contains only valid chars
143
+ // - Version looks like a semver range
144
+ if (!isValidPackageName(name))
145
+ continue;
146
+ if (!isValidVersionRange(version))
147
+ continue;
148
+ // Dedupe
149
+ if (seen.has(name))
150
+ continue;
151
+ seen.add(name);
152
+ deps.push({ name, version });
153
+ }
154
+ return deps;
155
+ }
156
+ /**
157
+ * Check if name looks like a valid npm package name
158
+ */
159
+ function isValidPackageName(name) {
160
+ // Scoped packages: @scope/name
161
+ if (name.startsWith("@")) {
162
+ return /^@[\w-]+\/[\w.-]+$/.test(name);
163
+ }
164
+ // Regular packages: name or name-with-dashes
165
+ return /^[a-z][\w.-]*$/.test(name);
166
+ }
167
+ /**
168
+ * Check if version looks like a semver range
169
+ */
170
+ function isValidVersionRange(version) {
171
+ // Common patterns: ^1.0.0, ~1.0.0, >=1.0.0, 1.0.0, *, latest
172
+ // Also: npm:package@version, workspace:*
173
+ if (version === "*" || version === "latest" || version === "next")
174
+ return true;
175
+ if (version.startsWith("npm:"))
176
+ return true;
177
+ if (version.startsWith("workspace:"))
178
+ return true;
179
+ if (/^[\^~>=<]?\d/.test(version))
180
+ return true;
181
+ return false;
182
+ }
183
+ /**
184
+ * Check if a field name is a common package.json field (not a dependency)
185
+ */
186
+ function isNonDependencyField(name) {
187
+ const nonDepFields = [
188
+ // package.json fields
189
+ "name", "version", "description", "main", "module", "types", "typings",
190
+ "scripts", "bin", "files", "repository", "keywords", "author", "license",
191
+ "bugs", "homepage", "engines", "private", "workspaces", "publishConfig",
192
+ "type", "exports", "imports", "sideEffects", "browserslist", "eslintConfig",
193
+ "prettier", "jest", "mocha", "nyc", "lint-staged", "husky", "config",
194
+ "peerDependenciesMeta", "bundleDependencies", "optionalDependencies",
195
+ "overrides", "resolutions", "packageManager", "volta", "directories",
196
+ // Script names
197
+ "dev", "build", "start", "test", "lint", "format", "clean", "watch",
198
+ "preinstall", "postinstall", "prepublish", "prepare",
199
+ // Lock file internal fields
200
+ "resolved", "integrity", "dev", "optional", "requires", "dependencies",
201
+ "node", "npm", "funding", "hasInstallScript", "hasShrinkwrap", "deprecated",
202
+ "peer", "engines", "os", "cpu", "libc", "bin", "license",
203
+ // Common config field values
204
+ "preset", "extends", "plugins", "rules", "env", "globals", "parser",
205
+ "parserOptions", "settings", "ignorePatterns",
206
+ ];
207
+ // Also skip if it looks like a version range or URL
208
+ if (/^[\d^~<>=*]/.test(name))
209
+ return true;
210
+ if (name.startsWith("http"))
211
+ return true;
212
+ if (name.startsWith("git"))
213
+ return true;
214
+ if (name.startsWith("file:"))
215
+ return true;
216
+ if (name.startsWith("npm:"))
217
+ return true;
218
+ return nonDepFields.includes(name);
219
+ }
220
+ function matchAny(patterns, text) {
221
+ const out = [];
222
+ for (const p of patterns) {
223
+ try {
224
+ const re = new RegExp(p, "g");
225
+ const m = text.match(re);
226
+ if (m)
227
+ out.push(...m.slice(0, 5));
228
+ }
229
+ catch {
230
+ // ignore invalid patterns
231
+ }
232
+ }
233
+ // de-dupe
234
+ return [...new Set(out)];
235
+ }
236
+ function simpleLineHits(text, re, limit) {
237
+ const hits = [];
238
+ let m;
239
+ while ((m = re.exec(text)) && hits.length < limit) {
240
+ hits.push(m[0]);
241
+ }
242
+ return hits;
243
+ }
244
+ function parseDeletedFiles(nameStatus) {
245
+ const files = [];
246
+ for (const line of nameStatus.split("\n")) {
247
+ const trimmed = line.trim();
248
+ if (!trimmed)
249
+ continue;
250
+ const [status, file] = trimmed.split(/\s+/);
251
+ if (status === "D" && file)
252
+ files.push(file);
253
+ }
254
+ return files;
255
+ }
256
+ function parseNumstat(numstat) {
257
+ const rows = [];
258
+ for (const line of numstat.split("\n")) {
259
+ const trimmed = line.trim();
260
+ if (!trimmed)
261
+ continue;
262
+ const parts = trimmed.split("\t");
263
+ if (parts.length < 3)
264
+ continue;
265
+ const [a, d, file] = parts;
266
+ const added = a === "-" ? 0 : Number(a);
267
+ const deleted = d === "-" ? 0 : Number(d);
268
+ rows.push({ added: Number.isFinite(added) ? added : 0, deleted: Number.isFinite(deleted) ? deleted : 0, file: file ?? "" });
269
+ }
270
+ return rows;
271
+ }
272
+ /**
273
+ * Analyze git log for loop signals
274
+ * @param gitLog Output from `git log --oneline -n 15`
275
+ * @param gitLogWithFiles Output from `git log --name-only --oneline -n 15`
276
+ */
277
+ function detectLoopSignals(gitLog, gitLogWithFiles) {
278
+ const signals = [];
279
+ const lines = gitLog.split("\n").filter((l) => l.trim());
280
+ const messages = lines.map((l) => l.replace(/^[a-f0-9]+\s+/, "").toLowerCase());
281
+ // ────────────────────────────────────────────────────────────────────────────
282
+ // Signal 1: Fix-Chain (multiple "fix" commits in a row)
283
+ // ────────────────────────────────────────────────────────────────────────────
284
+ const fixCount = messages.filter((m) => /^fix(\(|:|\s)/i.test(m)).length;
285
+ if (fixCount >= 3) {
286
+ signals.push({
287
+ type: "fix_chain",
288
+ severity: "warn",
289
+ message: `Loop signal: ${fixCount}x "fix" commits in last 15 commits. Possible fix-loop.`,
290
+ details: messages.slice(0, 6).join("\n"),
291
+ });
292
+ }
293
+ // ────────────────────────────────────────────────────────────────────────────
294
+ // Signal 2: Revert-Pattern (explizite Reverts)
295
+ // ────────────────────────────────────────────────────────────────────────────
296
+ const revertCount = messages.filter((m) => /\brevert\b/i.test(m)).length;
297
+ if (revertCount >= 1) {
298
+ signals.push({
299
+ type: "revert",
300
+ severity: revertCount >= 2 ? "critical" : "warn",
301
+ message: `Loop signal: ${revertCount}x "revert" found. A↔B toggling possible.`,
302
+ details: messages.filter((m) => /\brevert\b/i.test(m)).join("\n"),
303
+ });
304
+ }
305
+ // ────────────────────────────────────────────────────────────────────────────
306
+ // Signal 3: File-Churn (same file changed multiple times in short time)
307
+ // ────────────────────────────────────────────────────────────────────────────
308
+ if (gitLogWithFiles) {
309
+ const fileChanges = parseFileChangesFromLog(gitLogWithFiles);
310
+ const churnFiles = Object.entries(fileChanges)
311
+ .filter(([_, count]) => count >= 5)
312
+ .map(([file, count]) => `${file} (${count}x)`);
313
+ if (churnFiles.length > 0) {
314
+ signals.push({
315
+ type: "churn",
316
+ severity: "warn",
317
+ message: `Loop signal: File-Churn - ${churnFiles.length} file(s) changed 5+ times.`,
318
+ details: churnFiles.slice(0, 5).join("\n"),
319
+ });
320
+ }
321
+ }
322
+ // ────────────────────────────────────────────────────────────────────────────
323
+ // Signal 4: Fix without test changes
324
+ // ────────────────────────────────────────────────────────────────────────────
325
+ if (gitLogWithFiles) {
326
+ const fixWithoutTest = detectFixWithoutTest(gitLogWithFiles);
327
+ if (fixWithoutTest.length >= 2) {
328
+ signals.push({
329
+ type: "fix_no_test",
330
+ severity: "warn",
331
+ message: `Loop signal: ${fixWithoutTest.length}x "fix" commits without test changes.`,
332
+ details: fixWithoutTest.slice(0, 3).join("\n"),
333
+ });
334
+ }
335
+ }
336
+ // ────────────────────────────────────────────────────────────────────────────
337
+ // Signal 5: Pendulum (similar commit messages repeating)
338
+ // ────────────────────────────────────────────────────────────────────────────
339
+ const similarMessages = findSimilarMessages(messages);
340
+ if (similarMessages.length > 0) {
341
+ signals.push({
342
+ type: "pendeln",
343
+ severity: "critical",
344
+ message: `Loop signal: Similar commits repeating. Possible diff pendulum.`,
345
+ details: similarMessages.join("\n"),
346
+ });
347
+ }
348
+ return signals;
349
+ }
350
+ /**
351
+ * Parse file changes from git log --name-only output
352
+ */
353
+ function parseFileChangesFromLog(logWithFiles) {
354
+ const fileCount = {};
355
+ const lines = logWithFiles.split("\n");
356
+ for (const line of lines) {
357
+ const trimmed = line.trim();
358
+ // Skip commit hashes and empty lines
359
+ if (!trimmed || /^[a-f0-9]{7,}/.test(trimmed))
360
+ continue;
361
+ // Count file occurrences
362
+ if (trimmed.includes(".") || trimmed.includes("/")) {
363
+ fileCount[trimmed] = (fileCount[trimmed] ?? 0) + 1;
364
+ }
365
+ }
366
+ return fileCount;
367
+ }
368
+ /**
369
+ * Detect "fix" commits that don't touch test files
370
+ */
371
+ function detectFixWithoutTest(logWithFiles) {
372
+ const results = [];
373
+ const blocks = logWithFiles.split(/\n(?=[a-f0-9]{7,})/);
374
+ for (const block of blocks) {
375
+ const lines = block.split("\n").filter((l) => l.trim());
376
+ if (lines.length === 0)
377
+ continue;
378
+ const firstLine = lines[0] ?? "";
379
+ const message = firstLine.replace(/^[a-f0-9]+\s+/, "").toLowerCase();
380
+ // Check if it's a fix commit
381
+ if (!/^fix(\(|:|\s)/i.test(message))
382
+ continue;
383
+ // Check if any files are test files
384
+ const files = lines.slice(1);
385
+ const hasTestFile = files.some((f) => /\.(test|spec)\.[jt]sx?$/.test(f) ||
386
+ /__(tests|test)__/.test(f) ||
387
+ /\.test\./.test(f));
388
+ if (!hasTestFile) {
389
+ results.push(firstLine);
390
+ }
391
+ }
392
+ return results;
393
+ }
394
+ /**
395
+ * Find similar commit messages that might indicate pendeln
396
+ */
397
+ function findSimilarMessages(messages) {
398
+ const similar = [];
399
+ // Simple: Check for near-identical messages
400
+ for (let i = 0; i < messages.length - 1; i++) {
401
+ for (let j = i + 1; j < messages.length; j++) {
402
+ const m1 = messages[i] ?? "";
403
+ const m2 = messages[j] ?? "";
404
+ // Normalize: remove version numbers, timestamps
405
+ const norm1 = m1.replace(/v?\d+(\.\d+)*/g, "").replace(/\s+/g, " ").trim();
406
+ const norm2 = m2.replace(/v?\d+(\.\d+)*/g, "").replace(/\s+/g, " ").trim();
407
+ if (norm1 === norm2 && norm1.length > 10) {
408
+ similar.push(`"${m1}" ≈ "${m2}"`);
409
+ }
410
+ }
411
+ }
412
+ return similar;
413
+ }
@@ -0,0 +1,37 @@
1
+ export type PulseLayer = "concept" | "build" | "escalation";
2
+ export type EnforcementMode = "advisory" | "mixed" | "strict";
3
+ export type NotificationMode = "terminal" | "macos" | "both";
4
+ export type ProjectType = "node" | "python" | "unknown";
5
+ export type PresetName = "frontend" | "backend" | "fullstack" | "monorepo" | "custom";
6
+ export type PresetConfig = {
7
+ warnMaxFilesChanged: number;
8
+ warnMaxLinesChanged: number;
9
+ warnMaxDeletions: number;
10
+ checkpointReminderMinutes: number;
11
+ extraSecretPatterns?: string[];
12
+ };
13
+ export type PulseConfig = {
14
+ version: 1;
15
+ projectType: ProjectType;
16
+ enforcement: EnforcementMode;
17
+ notifications: NotificationMode;
18
+ preset?: PresetName;
19
+ thresholds: {
20
+ warnMaxFilesChanged: number;
21
+ warnMaxLinesChanged: number;
22
+ warnMaxDeletions: number;
23
+ };
24
+ patterns: {
25
+ secret: string[];
26
+ prodUrl: string[];
27
+ };
28
+ commands: {
29
+ test?: string;
30
+ };
31
+ checkpointReminderMinutes?: number;
32
+ };
33
+ export type PulseState = {
34
+ version: 1;
35
+ profile: PulseLayer;
36
+ lastCheckpointAt?: string;
37
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "pulse-framework-cli",
3
+ "version": "0.4.1",
4
+ "description": "Pulse Framework CLI – Guardrails, checkpoints, and escalation for AI-assisted development.",
5
+ "author": "Manuel Fuß <kontakt@manuel-fuss.de>",
6
+ "license": "MIT",
7
+ "homepage": "https://manuel-fuss.de/pulse",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/manuelfussTC/PulseFramework"
11
+ },
12
+ "keywords": [
13
+ "pulse",
14
+ "ai",
15
+ "cursor",
16
+ "agent",
17
+ "guardrails",
18
+ "checkpoint",
19
+ "cli"
20
+ ],
21
+ "bin": {
22
+ "pulse": "dist/index.js",
23
+ "pulse-framework": "dist/index.js"
24
+ },
25
+ "type": "commonjs",
26
+ "main": "dist/index.js",
27
+ "files": [
28
+ "dist",
29
+ "templates"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc -p tsconfig.json",
33
+ "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "dev": "tsx src/index.ts"
35
+ },
36
+ "dependencies": {
37
+ "chokidar": "^3.6.0",
38
+ "commander": "^12.1.0",
39
+ "glob": "^11.0.0"
40
+ }
41
+ }
42
+
@@ -0,0 +1,159 @@
1
+ # ═══════════════════════════════════════════════════════════════════════════════
2
+ # PULSE FRAMEWORK - AI Agent Rules (v1.1)
3
+ # ═══════════════════════════════════════════════════════════════════════════════
4
+ # Author: Manuel Fuß (kontakt@manuel-fuss.de)
5
+ # Repo: https://github.com/manuelfussTC/PulseFramework
6
+ #
7
+ # These rules apply to YOU, the AI Agent. You MUST follow them.
8
+ # ═══════════════════════════════════════════════════════════════════════════════
9
+
10
+ # ┌─────────────────────────────────────────────────────────────────────────────┐
11
+ # │ 🛑 CRITICAL RULE: 30-MINUTE AUTONOMY LIMIT │
12
+ # └─────────────────────────────────────────────────────────────────────────────┘
13
+ #
14
+ # You may work autonomously for a MAXIMUM of 30 minutes. Then you MUST:
15
+ #
16
+ # 1. STOP - No further action without human confirmation
17
+ # 2. SUMMARIZE - What have you done in the last 30 min?
18
+ # 3. ASK - "Should I continue? Here is my current status: [...]"
19
+ #
20
+ # WHY: After 30 min you lose context, build in wrong directions,
21
+ # and overwrite working things.
22
+ #
23
+ # For EVERY longer task:
24
+ # - Split into 30-min blocks
25
+ # - At the end of each block: Checkpoint + Summary + Question
26
+ # - NEVER "just one more quick thing" after 30 min
27
+
28
+ # ┌─────────────────────────────────────────────────────────────────────────────┐
29
+ # │ 🔒 5 CRITICAL SAFEGUARDS - NON-NEGOTIABLE │
30
+ # └─────────────────────────────────────────────────────────────────────────────┘
31
+
32
+ # SAFEGUARD 1: DELETE LOCK
33
+ # - NEVER delete a non-empty file without explicitly asking:
34
+ # "CONFIRM DELETION: [filename] - This file contains [X lines]. Delete?"
35
+ # - NEVER "Apply All" on a diff >50 lines without prior summary
36
+
37
+ # SAFEGUARD 2: GIT PUSH LOCK
38
+ # - NEVER git push without explicit instruction
39
+ # - Before every push: "Ready to push. Changes: [summary]. Confirm push?"
40
+
41
+ # SAFEGUARD 3: DEPLOYMENT LOCK
42
+ # - NEVER deploy without prior local test
43
+ # - Before deploy: "Local tests: [passed/failed]. Confirm deploy?"
44
+
45
+ # SAFEGUARD 4: BREAKING CHANGES
46
+ # - On API changes, schema migrations, config changes:
47
+ # "⚠️ BREAKING CHANGE: [what changes]. Continue?"
48
+
49
+ # SAFEGUARD 5: SECRETS
50
+ # - NEVER put secrets, API keys, tokens in code
51
+ # - If you see a secret: "🚨 SECRET FOUND: [masked]. Please move to .env."
52
+
53
+ # ┌─────────────────────────────────────────────────────────────────────────────┐
54
+ # │ 🔄 LOOP DETECTION - SELF-MONITORING │
55
+ # └─────────────────────────────────────────────────────────────────────────────┘
56
+
57
+ # You MUST monitor yourself for loops:
58
+
59
+ # LOOP TYPE 1: "Is fixed" but not
60
+ # - If you say >2x "now it should work" and it doesn't:
61
+ # - STOP. Say: "I'm stuck in a loop. Here's what I've tried: [...]"
62
+ # - Recommend escalation to a reasoning model
63
+
64
+ # LOOP TYPE 2: Back-and-forth (A↔B)
65
+ # - If you change something, then undo it, then change it again:
66
+ # - STOP. Say: "I'm toggling between two approaches. Which should I choose?"
67
+
68
+ # LOOP TYPE 3: Doesn't understand the problem
69
+ # - If you're unsure what the actual problem is:
70
+ # - STOP. Say: "I'm not sure I understand the problem.
71
+ # Let me summarize what I understood: [...]"
72
+
73
+ # LOOP TYPE 4: Too much at once
74
+ # - If the task is too big:
75
+ # - STOP. Say: "This task is too big for one block.
76
+ # I suggest: Milestone 1: [...], Milestone 2: [...]"
77
+
78
+ # LOOP TYPE 5: Verification loop (status/search/status without action)
79
+ # - If you keep running checks without implementing anything (2 cycles):
80
+ # - STOP. Say: "I'm in verification mode. I'll now implement the smallest concrete change."
81
+ # - Then implement ONE minimal change and re-check once.
82
+
83
+ # ┌─────────────────────────────────────────────────────────────────────────────┐
84
+ # │ 📋 CHECKPOINT PROTOCOL │
85
+ # └─────────────────────────────────────────────────────────────────────────────┘
86
+
87
+ # Every 5-10 minutes (or after every significant change):
88
+ #
89
+ # 1. GIT COMMIT with clear message
90
+ # 2. SHORT SUMMARY: "Checkpoint: [what was done]"
91
+ # 3. NEXT STEP: "Next up: [what comes next]"
92
+ #
93
+ # Commit message format:
94
+ # - feat: New feature
95
+ # - fix: Bug fix
96
+ # - refactor: Code improvement without functionality change
97
+ # - docs: Documentation
98
+ # - test: Tests
99
+ # - chore: Maintenance
100
+
101
+ # ┌─────────────────────────────────────────────────────────────────────────────┐
102
+ # │ 🚨 RED FLAGS - STOP IMMEDIATELY │
103
+ # └─────────────────────────────────────────────────────────────────────────────┘
104
+
105
+ # If you recognize any of these patterns, STOP IMMEDIATELY and report:
106
+ #
107
+ # 🚩 You no longer understand your own code
108
+ # 🚩 More than 200 lines in one change
109
+ # 🚩 Dependencies you don't know
110
+ # 🚩 Deleted files without explicit confirmation
111
+ # 🚩 Production URLs hardcoded
112
+ # 🚩 console.log/print statements for debugging forgotten
113
+ # 🚩 Commented-out code
114
+ # 🚩 TODO without issue reference
115
+
116
+ # ┌─────────────────────────────────────────────────────────────────────────────┐
117
+ # │ 📊 ESCALATION │
118
+ # └─────────────────────────────────────────────────────────────────────────────┘
119
+
120
+ # When you need to escalate, ALWAYS provide:
121
+ #
122
+ # 1. PROBLEM SUMMARY
123
+ # - What was the original task?
124
+ # - What did I try?
125
+ # - What doesn't work?
126
+ #
127
+ # 2. CONTEXT
128
+ # - Relevant code snippets
129
+ # - Error messages (complete)
130
+ # - Git history of recent changes
131
+ #
132
+ # 3. HYPOTHESES
133
+ # - What could be the cause?
134
+ # - What have I already ruled out?
135
+ #
136
+ # 4. SPECIFIC QUESTION
137
+ # - What exactly should the reasoning model answer?
138
+
139
+ # ┌─────────────────────────────────────────────────────────────────────────────┐
140
+ # │ ✅ START PROTOCOL │
141
+ # └─────────────────────────────────────────────────────────────────────────────┘
142
+
143
+ # For EVERY new task:
144
+ #
145
+ # 1. CONFIRM your understanding:
146
+ # "I understand the task as follows: [summary]"
147
+ #
148
+ # 2. PLAN before you code:
149
+ # "My plan: 1. [...] 2. [...] 3. [...]"
150
+ #
151
+ # 3. ASK about uncertainties:
152
+ # "Before I start: [specific question]"
153
+ #
154
+ # 4. ESTIMATE the effort:
155
+ # "Estimated effort: [X] minutes. Should I start?"
156
+
157
+ # ═══════════════════════════════════════════════════════════════════════════════
158
+ # END OF RULES - You are now ready. Follow these rules.
159
+ # ═══════════════════════════════════════════════════════════════════════════════