codiedev 0.3.4 → 0.3.5
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/cli.js
CHANGED
|
@@ -22,6 +22,7 @@ const ping_1 = require("./commands/ping");
|
|
|
22
22
|
const inbox_1 = require("./commands/inbox");
|
|
23
23
|
const note_1 = require("./commands/note");
|
|
24
24
|
const promote_1 = require("./commands/promote");
|
|
25
|
+
const reverseTicket_1 = require("./commands/reverseTicket");
|
|
25
26
|
const HELP = `
|
|
26
27
|
CodieDev CLI
|
|
27
28
|
|
|
@@ -38,6 +39,8 @@ Artifacts:
|
|
|
38
39
|
Fetch an artifact (stdout by default)
|
|
39
40
|
codiedev promote <artifact-id> Promote an auto-extracted artifact
|
|
40
41
|
to an authored one
|
|
42
|
+
codiedev reverse-ticket <pr-url> Generate a Jira ticket from a merged PR
|
|
43
|
+
(auto-matches spec + thread + transcript)
|
|
41
44
|
|
|
42
45
|
Messaging:
|
|
43
46
|
codiedev ping <user> "<msg>" [--with <key>]
|
|
@@ -339,6 +342,10 @@ async function main() {
|
|
|
339
342
|
case "promote":
|
|
340
343
|
await (0, promote_1.runPromote)(rest);
|
|
341
344
|
return;
|
|
345
|
+
case "reverse-ticket":
|
|
346
|
+
case "reverseTicket":
|
|
347
|
+
await (0, reverseTicket_1.runReverseTicket)(rest);
|
|
348
|
+
return;
|
|
342
349
|
default:
|
|
343
350
|
console.error(`Unknown command: ${command}`);
|
|
344
351
|
console.error(HELP);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runReverseTicket(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runReverseTicket = runReverseTicket;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const shared_1 = require("./shared");
|
|
6
|
+
function parsePrUrl(url) {
|
|
7
|
+
const m = url.match(/github\.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/);
|
|
8
|
+
if (!m)
|
|
9
|
+
return null;
|
|
10
|
+
return { owner: m[1], repo: m[2], number: Number(m[3]) };
|
|
11
|
+
}
|
|
12
|
+
function checkGhCli() {
|
|
13
|
+
try {
|
|
14
|
+
(0, child_process_1.execSync)("gh --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function ghApi(path) {
|
|
22
|
+
const out = (0, child_process_1.execSync)(`gh api "${path}"`, {
|
|
23
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
24
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
25
|
+
}).toString("utf8");
|
|
26
|
+
return JSON.parse(out);
|
|
27
|
+
}
|
|
28
|
+
function ghRaw(path) {
|
|
29
|
+
return (0, child_process_1.execSync)(`gh api -H "Accept: application/vnd.github.v3.diff" "${path}"`, {
|
|
30
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
31
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
32
|
+
}).toString("utf8");
|
|
33
|
+
}
|
|
34
|
+
function parseArgs(args) {
|
|
35
|
+
let prUrl;
|
|
36
|
+
let forcedKey;
|
|
37
|
+
let noTruncate = false;
|
|
38
|
+
for (let i = 0; i < args.length; i++) {
|
|
39
|
+
const a = args[i];
|
|
40
|
+
if ((a === "--with" || a === "-w") && i + 1 < args.length) {
|
|
41
|
+
forcedKey = args[++i];
|
|
42
|
+
}
|
|
43
|
+
else if (a.startsWith("--with=")) {
|
|
44
|
+
forcedKey = a.slice("--with=".length);
|
|
45
|
+
}
|
|
46
|
+
else if (a === "--full") {
|
|
47
|
+
noTruncate = true;
|
|
48
|
+
}
|
|
49
|
+
else if (!a.startsWith("--") && !prUrl) {
|
|
50
|
+
prUrl = a;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!prUrl) {
|
|
54
|
+
console.error("Usage: codiedev reverse-ticket <pr-url> [--with <artifact-key>] [--full]");
|
|
55
|
+
console.error("");
|
|
56
|
+
console.error("Example: codiedev reverse-ticket https://github.com/signalandcode/repo/pull/42");
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
return { prUrl, forcedKey, noTruncate };
|
|
60
|
+
}
|
|
61
|
+
async function runReverseTicket(args) {
|
|
62
|
+
const { prUrl, forcedKey, noTruncate } = parseArgs(args);
|
|
63
|
+
const parsed = parsePrUrl(prUrl);
|
|
64
|
+
if (!parsed) {
|
|
65
|
+
console.error("Couldn't parse PR URL. Expected format: https://github.com/<owner>/<repo>/pull/<number>");
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
if (!checkGhCli()) {
|
|
69
|
+
console.error("This command needs the `gh` CLI installed and authenticated.");
|
|
70
|
+
console.error(" brew install gh (macOS)");
|
|
71
|
+
console.error(" gh auth login");
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
const config = (0, shared_1.requireConfig)();
|
|
75
|
+
console.log(`Fetching PR ${parsed.owner}/${parsed.repo}#${parsed.number}…`);
|
|
76
|
+
let prJson;
|
|
77
|
+
let filesJson;
|
|
78
|
+
let diff;
|
|
79
|
+
try {
|
|
80
|
+
prJson = ghApi(`/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}`);
|
|
81
|
+
filesJson = ghApi(`/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}/files`);
|
|
82
|
+
// Diff is nice-to-have; truncate if huge.
|
|
83
|
+
try {
|
|
84
|
+
const rawDiff = ghRaw(`/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}`);
|
|
85
|
+
diff = noTruncate ? rawDiff : rawDiff.slice(0, 12_000);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Skip diff on failure — the writer works without it.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error(`Failed to fetch PR via gh api: ${err.message}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const bundle = {
|
|
96
|
+
repo: `${parsed.owner}/${parsed.repo}`,
|
|
97
|
+
number: parsed.number,
|
|
98
|
+
title: prJson.title ?? "",
|
|
99
|
+
body: prJson.body ?? "",
|
|
100
|
+
author: prJson.user?.login ?? "unknown",
|
|
101
|
+
authorEmail: prJson.user?.email ?? undefined,
|
|
102
|
+
mergedAt: prJson.merged_at
|
|
103
|
+
? Date.parse(prJson.merged_at)
|
|
104
|
+
: prJson.closed_at
|
|
105
|
+
? Date.parse(prJson.closed_at)
|
|
106
|
+
: Date.now(),
|
|
107
|
+
filesChanged: (filesJson ?? []).map((f) => f.filename),
|
|
108
|
+
diff,
|
|
109
|
+
htmlUrl: prJson.html_url ?? prUrl,
|
|
110
|
+
};
|
|
111
|
+
console.log(` title: ${bundle.title}`);
|
|
112
|
+
console.log(` author: ${bundle.author}`);
|
|
113
|
+
console.log(` files: ${bundle.filesChanged.length}`);
|
|
114
|
+
console.log(` merged: ${new Date(bundle.mergedAt).toISOString()}`);
|
|
115
|
+
console.log("");
|
|
116
|
+
console.log("Generating ticket…");
|
|
117
|
+
console.log("");
|
|
118
|
+
try {
|
|
119
|
+
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/reverseTicket", {
|
|
120
|
+
config,
|
|
121
|
+
body: {
|
|
122
|
+
pr: bundle,
|
|
123
|
+
forcedArtifactKey: forcedKey,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
if (res.match) {
|
|
127
|
+
console.log(`✓ Matched artifact: ${res.match.key} (score=${res.match.score.toFixed(2)})`);
|
|
128
|
+
console.log(` signals · body=${res.match.signals.bodyMention} · files=${res.match.signals.fileOverlap.toFixed(2)} · transcript=${res.match.signals.transcriptEdit}`);
|
|
129
|
+
}
|
|
130
|
+
else if (res.candidates.length > 0) {
|
|
131
|
+
console.log(`? Ambiguous / low confidence. Candidates:`);
|
|
132
|
+
for (const c of res.candidates.slice(0, 3)) {
|
|
133
|
+
console.log(` - ${c.key} (score=${c.score.toFixed(2)})`);
|
|
134
|
+
}
|
|
135
|
+
console.log(` Re-run with --with <key> to force a specific artifact.`);
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log("· No matched artifact. Ticket generated from PR diff alone.");
|
|
139
|
+
}
|
|
140
|
+
console.log(` prompt tokens (est): ${res.tokensInEstimate}`);
|
|
141
|
+
console.log("");
|
|
142
|
+
console.log("─────────── Generated ticket ───────────");
|
|
143
|
+
console.log("");
|
|
144
|
+
console.log(res.ticket);
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.error(`Reverse-ticket failed: ${err.message}`);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|