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.
- package/dist/commands/checkpoint.d.ts +2 -0
- package/dist/commands/checkpoint.js +129 -0
- package/dist/commands/correct.d.ts +2 -0
- package/dist/commands/correct.js +77 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +183 -0
- package/dist/commands/escalate.d.ts +2 -0
- package/dist/commands/escalate.js +226 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +570 -0
- package/dist/commands/learn.d.ts +2 -0
- package/dist/commands/learn.js +137 -0
- package/dist/commands/profile.d.ts +2 -0
- package/dist/commands/profile.js +39 -0
- package/dist/commands/reset.d.ts +2 -0
- package/dist/commands/reset.js +130 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.js +129 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +272 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +196 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +239 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +98 -0
- package/dist/hooks/install.d.ts +1 -0
- package/dist/hooks/install.js +89 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -0
- package/dist/lib/artifacts.d.ts +7 -0
- package/dist/lib/artifacts.js +52 -0
- package/dist/lib/briefing.d.ts +77 -0
- package/dist/lib/briefing.js +231 -0
- package/dist/lib/clipboard.d.ts +9 -0
- package/dist/lib/clipboard.js +116 -0
- package/dist/lib/config.d.ts +14 -0
- package/dist/lib/config.js +167 -0
- package/dist/lib/context-export.d.ts +30 -0
- package/dist/lib/context-export.js +149 -0
- package/dist/lib/exec.d.ts +9 -0
- package/dist/lib/exec.js +23 -0
- package/dist/lib/git.d.ts +24 -0
- package/dist/lib/git.js +74 -0
- package/dist/lib/input.d.ts +15 -0
- package/dist/lib/input.js +80 -0
- package/dist/lib/notifications.d.ts +2 -0
- package/dist/lib/notifications.js +25 -0
- package/dist/lib/paths.d.ts +4 -0
- package/dist/lib/paths.js +39 -0
- package/dist/lib/prompts.d.ts +43 -0
- package/dist/lib/prompts.js +270 -0
- package/dist/lib/scanner.d.ts +37 -0
- package/dist/lib/scanner.js +413 -0
- package/dist/lib/types.d.ts +37 -0
- package/dist/lib/types.js +2 -0
- package/package.json +42 -0
- package/templates/.cursorrules +159 -0
- package/templates/AGENTS.md +198 -0
- package/templates/cursor/mcp.json +9 -0
- package/templates/cursor/pulse.mdc +144 -0
- package/templates/roles/architect.cursorrules +15 -0
- package/templates/roles/backend.cursorrules +12 -0
- 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,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,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,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
|
+
}
|