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,137 @@
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.registerLearnCommand = registerLearnCommand;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ const artifacts_js_1 = require("../lib/artifacts.js");
10
+ const input_js_1 = require("../lib/input.js");
11
+ const paths_js_1 = require("../lib/paths.js");
12
+ function registerLearnCommand(program) {
13
+ program
14
+ .command("learn")
15
+ .description("Save learned knowledge (Problem β†’ Solution β†’ Rule)")
16
+ .option("--problem <text>", "What was the problem?")
17
+ .option("--solution <text>", "What was the solution?")
18
+ .option("--rule <text>", "Derived rule")
19
+ .option("--reason <text>", "Why this rule?")
20
+ .option("--no-promote", "Do not ask to update .cursorrules")
21
+ .action(async (opts) => {
22
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
23
+ if (!repoRoot)
24
+ throw new Error("Not in a git repository.");
25
+ await (0, artifacts_js_1.ensurePulseDirs)(repoRoot);
26
+ // eslint-disable-next-line no-console
27
+ console.log("\nπŸ“š PULSE Learn\n");
28
+ // Gather information
29
+ const problem = opts.problem ?? (await (0, input_js_1.promptText)("What was the problem?", ""));
30
+ const solution = opts.solution ?? (await (0, input_js_1.promptText)("What was the solution?", ""));
31
+ const rule = opts.rule ?? (await (0, input_js_1.promptText)("Derived rule (what to observe?)", ""));
32
+ const reason = opts.reason ?? (await (0, input_js_1.promptText)("Why? (optional)", ""));
33
+ const ts = (0, artifacts_js_1.timestampId)();
34
+ // ════════════════════════════════════════════════════════════════════════
35
+ // Create memory entry
36
+ // ════════════════════════════════════════════════════════════════════════
37
+ const entry = [
38
+ `## Learning: ${ts}`,
39
+ ``,
40
+ problem.trim() ? `**Problem:** ${problem.trim()}` : "",
41
+ solution.trim() ? `**Solution:** ${solution.trim()}` : "",
42
+ rule.trim() ? `**Rule:** ${rule.trim()}` : "",
43
+ reason.trim() ? `**Reason:** ${reason.trim()}` : "",
44
+ ``,
45
+ `---`,
46
+ ``,
47
+ ]
48
+ .filter((l) => l !== "")
49
+ .join("\n");
50
+ const memPath = node_path_1.default.join((0, paths_js_1.pulseDir)(repoRoot), "memory.md");
51
+ // Create file with header if doesn't exist
52
+ try {
53
+ await promises_1.default.access(memPath);
54
+ }
55
+ catch {
56
+ const header = `# PULSE Memory\n\nLearned rules and insights from this project.\n\n---\n\n`;
57
+ await promises_1.default.writeFile(memPath, header, "utf8");
58
+ }
59
+ await promises_1.default.appendFile(memPath, entry, "utf8");
60
+ // eslint-disable-next-line no-console
61
+ console.log(`βœ… Saved: ${memPath}`);
62
+ // ════════════════════════════════════════════════════════════════════════
63
+ // Auto-Promotion zu .cursorrules
64
+ // ════════════════════════════════════════════════════════════════════════
65
+ if (rule.trim() && opts.promote !== false) {
66
+ // eslint-disable-next-line no-console
67
+ console.log(`\n${"─".repeat(50)}`);
68
+ // eslint-disable-next-line no-console
69
+ console.log(`\nπŸ“‹ Proposal for .cursorrules:\n`);
70
+ const cursorrulesSnippet = formatCursorrulesSnippet(rule, reason, problem);
71
+ // eslint-disable-next-line no-console
72
+ console.log(cursorrulesSnippet);
73
+ const doPromote = await (0, input_js_1.promptConfirm)("\nAdd to .cursorrules?", true);
74
+ if (doPromote) {
75
+ const cursorrulesPath = node_path_1.default.join(repoRoot, ".cursorrules");
76
+ await appendToCursorrules(cursorrulesPath, cursorrulesSnippet);
77
+ // eslint-disable-next-line no-console
78
+ console.log(`\nβœ… Added to .cursorrules!`);
79
+ }
80
+ else {
81
+ // eslint-disable-next-line no-console
82
+ console.log(`\nℹ️ Not added. You can add it manually later.`);
83
+ }
84
+ }
85
+ // eslint-disable-next-line no-console
86
+ console.log("");
87
+ });
88
+ }
89
+ /**
90
+ * Format a rule for .cursorrules
91
+ */
92
+ function formatCursorrulesSnippet(rule, reason, problem) {
93
+ const lines = [];
94
+ lines.push(`# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”`);
95
+ lines.push(`# β”‚ LEARNED RULE β”‚`);
96
+ lines.push(`# β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜`);
97
+ lines.push(`#`);
98
+ lines.push(`# ${rule.trim()}`);
99
+ if (reason.trim()) {
100
+ lines.push(`#`);
101
+ lines.push(`# Reason: ${reason.trim()}`);
102
+ }
103
+ if (problem.trim()) {
104
+ lines.push(`#`);
105
+ lines.push(`# Context: ${problem.trim()}`);
106
+ }
107
+ lines.push(`#`);
108
+ return lines.join("\n");
109
+ }
110
+ /**
111
+ * Append snippet to .cursorrules (create if doesn't exist)
112
+ */
113
+ async function appendToCursorrules(filepath, snippet) {
114
+ let content = "";
115
+ try {
116
+ content = await promises_1.default.readFile(filepath, "utf8");
117
+ }
118
+ catch {
119
+ // File doesn't exist, create with header
120
+ content = `# ═══════════════════════════════════════════════════════════════════════════════
121
+ # PULSE FRAMEWORK - AI Agent Rules
122
+ # ═══════════════════════════════════════════════════════════════════════════════
123
+
124
+ `;
125
+ }
126
+ // Check if there's already a "LEARNED RULE" section
127
+ const hasLearnedSection = content.includes("# LEARNED RULE");
128
+ if (hasLearnedSection) {
129
+ // Append to existing section (before the last closing block if possible)
130
+ content = content.trimEnd() + "\n\n" + snippet + "\n";
131
+ }
132
+ else {
133
+ // Add at the end
134
+ content = content.trimEnd() + "\n\n" + snippet + "\n";
135
+ }
136
+ await promises_1.default.writeFile(filepath, content, "utf8");
137
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerProfileCommand(program: Command): void;
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerProfileCommand = registerProfileCommand;
4
+ const artifacts_js_1 = require("../lib/artifacts.js");
5
+ const paths_js_1 = require("../lib/paths.js");
6
+ function registerProfileCommand(program) {
7
+ const profileCmd = program
8
+ .command("profile")
9
+ .description("View or set the active Pulse layer profile (concept/build/escalation).");
10
+ // Show current profile
11
+ profileCmd
12
+ .command("show", { isDefault: true })
13
+ .description("Show current profile")
14
+ .action(async () => {
15
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
16
+ if (!repoRoot)
17
+ throw new Error("Not inside a git repository.");
18
+ const state = await (0, artifacts_js_1.loadState)(repoRoot);
19
+ // eslint-disable-next-line no-console
20
+ console.log(state.profile);
21
+ });
22
+ // Set profile
23
+ profileCmd
24
+ .command("set <layer>")
25
+ .description("Set profile: concept | build | escalation")
26
+ .action(async (layer) => {
27
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
28
+ if (!repoRoot)
29
+ throw new Error("Not inside a git repository.");
30
+ if (!["concept", "build", "escalation"].includes(layer)) {
31
+ throw new Error(`Invalid layer: ${layer}. Use: concept | build | escalation`);
32
+ }
33
+ const state = await (0, artifacts_js_1.loadState)(repoRoot);
34
+ state.profile = layer;
35
+ await (0, artifacts_js_1.saveState)(repoRoot, state);
36
+ // eslint-disable-next-line no-console
37
+ console.log(`Profile set to ${state.profile}`);
38
+ });
39
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerResetCommand(program: Command): void;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerResetCommand = registerResetCommand;
4
+ const paths_js_1 = require("../lib/paths.js");
5
+ const input_js_1 = require("../lib/input.js");
6
+ const exec_js_1 = require("../lib/exec.js");
7
+ const git_js_1 = require("../lib/git.js");
8
+ function registerResetCommand(program) {
9
+ program
10
+ .command("reset")
11
+ .description("Safe Git reset with safeguards (for loop recovery)")
12
+ .option("-n, --commits <n>", "Number of commits to reset (default: 1)", "1")
13
+ .option("--soft", "Soft reset (changes remain staged)")
14
+ .option("--hard", "Hard reset (changes are DISCARDED)")
15
+ .option("-y, --yes", "Do not ask for confirmation")
16
+ .action(async (opts) => {
17
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
18
+ if (!repoRoot)
19
+ throw new Error("Not in a git repository.");
20
+ const numCommits = Math.max(1, Math.min(10, parseInt(opts.commits ?? "1", 10)));
21
+ const branch = await (0, git_js_1.gitCurrentBranch)(repoRoot);
22
+ // eslint-disable-next-line no-console
23
+ console.log("\nπŸ”„ PULSE Reset\n");
24
+ // ══════════════════════════════════════════════════════════════════════
25
+ // Safeguard: Not on main/master without explicit confirmation
26
+ // ══════════════════════════════════════════════════════════════════════
27
+ const protectedBranches = ["main", "master", "develop", "production"];
28
+ if (protectedBranches.includes(branch.toLowerCase())) {
29
+ // eslint-disable-next-line no-console
30
+ console.log(`⚠️ WARNING: You are on '${branch}' (protected branch)!\n`);
31
+ if (!opts.yes) {
32
+ const confirm = await (0, input_js_1.promptConfirm)(`Really reset on '${branch}'? (not recommended)`, false);
33
+ if (!confirm) {
34
+ // eslint-disable-next-line no-console
35
+ console.log("❌ Aborted.\n");
36
+ return;
37
+ }
38
+ }
39
+ }
40
+ // ══════════════════════════════════════════════════════════════════════
41
+ // Zeige betroffene Commits
42
+ // ══════════════════════════════════════════════════════════════════════
43
+ const recentLog = await (0, git_js_1.gitLogOneline)(repoRoot, numCommits + 2);
44
+ const logLines = recentLog.split("\n").filter((l) => l.trim());
45
+ // eslint-disable-next-line no-console
46
+ console.log(`πŸ“ Branch: ${branch}`);
47
+ // eslint-disable-next-line no-console
48
+ console.log(`πŸ“‹ Affected commits (${numCommits}):\n`);
49
+ for (let i = 0; i < Math.min(numCommits, logLines.length); i++) {
50
+ // eslint-disable-next-line no-console
51
+ console.log(` πŸ—‘οΈ ${logLines[i]}`);
52
+ }
53
+ if (logLines.length > numCommits) {
54
+ // eslint-disable-next-line no-console
55
+ console.log(`\n βœ… New HEAD: ${logLines[numCommits]}`);
56
+ }
57
+ // eslint-disable-next-line no-console
58
+ console.log("");
59
+ // ══════════════════════════════════════════════════════════════════════
60
+ // Reset-Modus wΓ€hlen
61
+ // ══════════════════════════════════════════════════════════════════════
62
+ let mode = "mixed";
63
+ if (opts.soft) {
64
+ mode = "soft";
65
+ }
66
+ else if (opts.hard) {
67
+ mode = "hard";
68
+ }
69
+ else if (!opts.yes) {
70
+ const choices = [
71
+ { value: "mixed", label: "πŸ”„ Mixed (default) - changes remain unstaged" },
72
+ { value: "soft", label: "πŸ“ Soft - changes remain staged" },
73
+ { value: "hard", label: "πŸ—‘οΈ Hard - changes are DISCARDED" },
74
+ ];
75
+ mode = await (0, input_js_1.promptSelect)("Reset mode", choices, "mixed");
76
+ }
77
+ // ══════════════════════════════════════════════════════════════════════
78
+ // Letzte BestΓ€tigung bei Hard Reset
79
+ // ══════════════════════════════════════════════════════════════════════
80
+ if (mode === "hard" && !opts.yes) {
81
+ // eslint-disable-next-line no-console
82
+ console.log("\n⚠️ HARD RESET: All uncommitted changes will be lost!\n");
83
+ const confirm = await (0, input_js_1.promptConfirm)("Really continue?", false);
84
+ if (!confirm) {
85
+ // eslint-disable-next-line no-console
86
+ console.log("❌ Aborted.\n");
87
+ return;
88
+ }
89
+ }
90
+ // ══════════════════════════════════════════════════════════════════════
91
+ // Git Reset ausfΓΌhren
92
+ // ══════════════════════════════════════════════════════════════════════
93
+ const resetArg = `HEAD~${numCommits}`;
94
+ const modeArg = mode === "mixed" ? "" : `--${mode}`;
95
+ const args = ["reset", modeArg, resetArg].filter(Boolean);
96
+ // eslint-disable-next-line no-console
97
+ console.log(`\nπŸ”§ Running: git ${args.join(" ")}\n`);
98
+ const result = await (0, exec_js_1.exec)("git", args, { cwd: repoRoot });
99
+ if (result.exitCode !== 0) {
100
+ // eslint-disable-next-line no-console
101
+ console.error(`❌ Git reset failed:\n${result.stderr}`);
102
+ process.exit(1);
103
+ }
104
+ // ══════════════════════════════════════════════════════════════════════
105
+ // Erfolgsmeldung
106
+ // ══════════════════════════════════════════════════════════════════════
107
+ const newLog = await (0, git_js_1.gitLogOneline)(repoRoot, 1);
108
+ // eslint-disable-next-line no-console
109
+ console.log(`βœ… Reset successful!`);
110
+ // eslint-disable-next-line no-console
111
+ console.log(`πŸ“ New HEAD: ${newLog}\n`);
112
+ // Hinweise
113
+ if (mode === "soft" || mode === "mixed") {
114
+ // eslint-disable-next-line no-console
115
+ console.log(`πŸ’‘ Tip: Changes are still there.`);
116
+ // eslint-disable-next-line no-console
117
+ console.log(` β†’ git status - Show changes`);
118
+ // eslint-disable-next-line no-console
119
+ console.log(` β†’ git stash - Stash temporarily`);
120
+ // eslint-disable-next-line no-console
121
+ console.log(` β†’ git checkout . - Discard\n`);
122
+ }
123
+ // eslint-disable-next-line no-console
124
+ console.log(`πŸ’‘ Next step:`);
125
+ // eslint-disable-next-line no-console
126
+ console.log(` β†’ pulse start - Start new approach`);
127
+ // eslint-disable-next-line no-console
128
+ console.log(` β†’ pulse escalate - Escalate problem\n`);
129
+ });
130
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerReviewCommand(program: Command): void;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerReviewCommand = registerReviewCommand;
4
+ const artifacts_js_1 = require("../lib/artifacts.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const paths_js_1 = require("../lib/paths.js");
7
+ const git_js_1 = require("../lib/git.js");
8
+ const scanner_js_1 = require("../lib/scanner.js");
9
+ const exec_js_1 = require("../lib/exec.js");
10
+ const briefing_js_1 = require("../lib/briefing.js");
11
+ function registerReviewCommand(program) {
12
+ program
13
+ .command("review")
14
+ .alias("r")
15
+ .description("Review v2: Decision Briefing with automatic aggregation")
16
+ .option("--staged", "Staged diff instead of working tree")
17
+ .option("--full", "Full checklist in addition to briefing")
18
+ .option("--json", "Output as JSON")
19
+ .action(async (opts) => {
20
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
21
+ if (!repoRoot)
22
+ throw new Error("Not in a git repository.");
23
+ const [state, config] = await Promise.all([
24
+ (0, artifacts_js_1.loadState)(repoRoot),
25
+ (0, config_js_1.loadConfig)(repoRoot),
26
+ ]);
27
+ // Gather all data in parallel
28
+ const [status, log, diffText, diffStat, diffNumstat, diffNameStatus, logWithFiles] = await Promise.all([
29
+ (0, git_js_1.gitStatusPorcelain)(repoRoot),
30
+ (0, git_js_1.gitLogOneline)(repoRoot, 15),
31
+ (0, git_js_1.gitDiffText)(repoRoot, { staged: opts.staged }),
32
+ (0, git_js_1.gitDiffStat)(repoRoot, { staged: opts.staged }),
33
+ (0, git_js_1.gitDiffNumstat)(repoRoot, { staged: opts.staged }),
34
+ (0, git_js_1.gitDiffNameStatus)(repoRoot, { staged: opts.staged }),
35
+ (0, exec_js_1.exec)("git", ["log", "--name-only", "--oneline", "-15"], { cwd: repoRoot }).then((r) => r.stdout),
36
+ ]);
37
+ // Run scanner
38
+ const scanResult = (0, scanner_js_1.scanDiff)(config, { diffText, diffStat, diffNumstat, diffNameStatus });
39
+ // Add loop signals to findings
40
+ const loopSignals = (0, scanner_js_1.detectLoopSignals)(log, logWithFiles);
41
+ for (const signal of loopSignals) {
42
+ scanResult.findings.push({
43
+ severity: signal.severity,
44
+ code: "LOOP_SIGNAL",
45
+ message: signal.message,
46
+ details: signal.details,
47
+ });
48
+ }
49
+ // Calculate briefing components
50
+ const scope = (0, briefing_js_1.calculateScopeCheck)(config, scanResult.stats);
51
+ const risk = (0, briefing_js_1.calculateRiskSummary)(scanResult);
52
+ const time = (0, briefing_js_1.calculateTimeSummary)(state.lastCheckpointAt, config.checkpointReminderMinutes ?? 30);
53
+ const recommendation = (0, briefing_js_1.generateRecommendation)(scope, risk, time);
54
+ const briefing = {
55
+ preset: config.preset ?? null,
56
+ profile: state.profile,
57
+ scope,
58
+ risk,
59
+ time,
60
+ recommendation,
61
+ };
62
+ // JSON output
63
+ if (opts.json) {
64
+ // eslint-disable-next-line no-console
65
+ console.log(JSON.stringify(briefing, null, 2));
66
+ return;
67
+ }
68
+ // Render briefing
69
+ // eslint-disable-next-line no-console
70
+ console.log("\n" + (0, briefing_js_1.renderBriefing)(briefing) + "\n");
71
+ // Save artifact
72
+ const ts = (0, artifacts_js_1.timestampId)();
73
+ const filename = `${ts}-review.md`;
74
+ const artifactLines = [
75
+ `# Review Pulse (${ts})`,
76
+ ``,
77
+ `## Decision Briefing`,
78
+ ``,
79
+ `- Preset: **${config.preset ?? "custom"}**`,
80
+ `- Profile: **${state.profile}**`,
81
+ `- Scope: **${opts.staged ? "staged" : "working tree"}**`,
82
+ ``,
83
+ `### Scope-Check`,
84
+ ``,
85
+ `| Metric | Current | Limit | Status |`,
86
+ `|--------|---------|-------|--------|`,
87
+ `| Files | ${scope.files.current} | ${scope.files.max} | ${scope.files.percent}% |`,
88
+ `| Lines | ${scope.lines.current} | ${scope.lines.max} | ${scope.lines.percent}% |`,
89
+ `| Deletes | ${scope.deletes.current} | ${scope.deletes.max} | ${scope.deletes.percent}% |`,
90
+ ``,
91
+ `### Risk Summary`,
92
+ ``,
93
+ `- Critical: ${risk.criticalCount}`,
94
+ `- Warnings: ${risk.warningCount}`,
95
+ `- Loop Risk: ${risk.loopRisk}`,
96
+ `- Checkpoint: ${time.minutesSinceCheckpoint ?? "n/a"} min`,
97
+ ``,
98
+ `### Recommendation`,
99
+ ``,
100
+ `**${recommendation.action.toUpperCase()}**: ${recommendation.reason}`,
101
+ recommendation.command ? `\n→ \`${recommendation.command}\`` : "",
102
+ ``,
103
+ ];
104
+ // Add findings if any
105
+ if (scanResult.findings.length > 0) {
106
+ artifactLines.push(`### Findings`, ``);
107
+ for (const f of scanResult.findings) {
108
+ const emoji = f.severity === "critical" ? "🚨" : "⚠️";
109
+ artifactLines.push(`- ${emoji} **${f.code}**: ${f.message}`);
110
+ }
111
+ artifactLines.push(``);
112
+ }
113
+ // Add full checklist if requested
114
+ if (opts.full) {
115
+ artifactLines.push(`---`, ``, `## Full Checklist`, ``, `### Git Context`, ``, `**Status:**`, "```", status || "(clean)", "```", ``, `**Recent Commits:**`, "```", log || "(none)", "```", ``, `**Diff Stat:**`, "```", diffStat || "(no changes)", "```", ``, `### Code Quality`, `- [ ] Do I understand the code? (If no: STOP)`, `- [ ] Naming OK?`, `- [ ] Error handling present?`, `- [ ] Edge cases considered?`, ``, `### Functionality`, `- [ ] Works as required?`, `- [ ] Tested locally?`, `- [ ] Invalid input handling OK?`, ``, `### Security`, `- [ ] No hardcoded secrets?`, `- [ ] Input validation?`, `- [ ] AuthZ/AuthN impact understood?`, ``, `### Git History`, `- [ ] Commit messages clear?`, `- [ ] Changes traceable?`, ``, `### Red Flags`, `- [ ] Code I don't understand`, `- [ ] Hundreds of lines in one commit`, `- [ ] Unknown dependencies`, `- [ ] Deleted files without confirmation`, ``, `## Decision`, ``, `- [ ] βœ… Approve`, `- [ ] ❌ Reject`, `- [ ] 🚨 Escalate`, ``, `**Notes:**`, ``);
116
+ }
117
+ const content = artifactLines.filter((l) => l !== "").join("\n");
118
+ const p = await (0, artifacts_js_1.writeArtifact)(repoRoot, "reviews", filename, content);
119
+ // eslint-disable-next-line no-console
120
+ console.log(`βœ… Saved: ${p}`);
121
+ // Show recommendation action
122
+ if (recommendation.command) {
123
+ // eslint-disable-next-line no-console
124
+ console.log(`\nπŸ’‘ Recommended next step:`);
125
+ // eslint-disable-next-line no-console
126
+ console.log(` β†’ ${recommendation.command}\n`);
127
+ }
128
+ });
129
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerRunCommand(program: Command): void;