codiedev 0.10.0-beta.5 → 0.10.0-beta.6
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/README.md +4 -4
- package/dist/captureSignals.js +2 -0
- package/dist/cli.js +38 -50
- package/dist/codexHookTrust.js +67 -4
- package/dist/codie.js +23 -74
- package/dist/codieLauncher.js +18 -22
- package/dist/commands/agentTemplates.generated.js +136 -0
- package/dist/commands/agents.js +1066 -0
- package/dist/commands/agentsSdk.generated.js +7 -0
- package/dist/commands/doctor.js +12 -38
- package/dist/commands/evals.js +187 -0
- package/dist/commands/pack.js +1 -1
- package/dist/commands/traces.js +79 -15
- package/dist/commands/tracesLabel.js +348 -0
- package/dist/connect.js +11 -52
- package/dist/copilotHooks.js +0 -4
- package/dist/cursorHooks.js +0 -15
- package/dist/harnessDirs.js +4 -0
- package/dist/hook.js +33 -197
- package/dist/limitEventReporter.js +1 -1
- package/dist/managedPi.js +240 -0
- package/dist/mcp.js +0 -307
- package/dist/packs/harnesses.js +40 -3
- package/dist/packs/install.js +2 -1
- package/dist/piHooks.js +216 -0
- package/dist/piRuntime.js +120 -0
- package/dist/selfUpdate.js +47 -2
- package/dist/utils.js +232 -115
- package/package.json +5 -7
- package/dist/codieStartup.js +0 -27
- package/dist/commands/promote.js +0 -53
- package/dist/commands/react.js +0 -38
- package/dist/commands/reverseTicket.js +0 -444
- package/dist/commands/security.js +0 -156
- package/dist/prUrl.js +0 -28
- package/dist/security.js +0 -417
- package/dist/securityDeepScan.js +0 -235
- package/dist/securityDoctor.js +0 -61
- package/dist/securityFeature.js +0 -6
- package/dist/securityInstall.js +0 -232
- package/dist/securityInventory.js +0 -566
- package/dist/securityPilot.js +0 -59
- package/dist/securityPolicy.js +0 -181
- package/dist/securitySession.js +0 -72
package/dist/codieStartup.js
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.refreshCodieSecurityPolicy = refreshCodieSecurityPolicy;
|
|
4
|
-
const securityFeature_1 = require("./securityFeature");
|
|
5
|
-
const securityPolicy_1 = require("./securityPolicy");
|
|
6
|
-
async function refreshCodieSecurityPolicy(config, syncPolicy = securityPolicy_1.syncEndpointSecurityPolicy) {
|
|
7
|
-
if (!(0, securityFeature_1.isAgentSecurityEnabled)(config))
|
|
8
|
-
return { status: "disabled" };
|
|
9
|
-
if (!config.backendUrl || !config.token || !config.companyId) {
|
|
10
|
-
return { status: "cached", error: "connected account details are incomplete" };
|
|
11
|
-
}
|
|
12
|
-
try {
|
|
13
|
-
const policy = await syncPolicy({
|
|
14
|
-
backendUrl: config.backendUrl,
|
|
15
|
-
token: config.token,
|
|
16
|
-
companyId: config.companyId,
|
|
17
|
-
...(config.deviceId ? { deviceId: config.deviceId } : {}),
|
|
18
|
-
}, "codie-harness");
|
|
19
|
-
return { status: "synced", policy };
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
return {
|
|
23
|
-
status: "cached",
|
|
24
|
-
error: error instanceof Error ? error.message : String(error),
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
}
|
package/dist/commands/promote.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runPromote = runPromote;
|
|
4
|
-
const shared_1 = require("./shared");
|
|
5
|
-
function parseArgs(args) {
|
|
6
|
-
let artifactId;
|
|
7
|
-
let keyOverride;
|
|
8
|
-
for (let i = 0; i < args.length; i++) {
|
|
9
|
-
const a = args[i];
|
|
10
|
-
if ((a === "--key" || a === "-k") && i + 1 < args.length) {
|
|
11
|
-
keyOverride = args[++i];
|
|
12
|
-
}
|
|
13
|
-
else if (a.startsWith("--key=")) {
|
|
14
|
-
keyOverride = a.slice("--key=".length);
|
|
15
|
-
}
|
|
16
|
-
else if (!a.startsWith("--") && !artifactId) {
|
|
17
|
-
artifactId = a;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
if (!artifactId) {
|
|
21
|
-
console.error("Usage: codiedev promote <extracted-artifact-id> [--key spec-foo.md]");
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
return { artifactId, keyOverride };
|
|
25
|
-
}
|
|
26
|
-
async function runPromote(args) {
|
|
27
|
-
const { artifactId, keyOverride } = parseArgs(args);
|
|
28
|
-
const config = (0, shared_1.requireConfig)();
|
|
29
|
-
const start = Date.now();
|
|
30
|
-
try {
|
|
31
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/promote", {
|
|
32
|
-
config,
|
|
33
|
-
body: { artifactId, keyOverride },
|
|
34
|
-
});
|
|
35
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
36
|
-
tool: "codiedev_promote",
|
|
37
|
-
ok: true,
|
|
38
|
-
latencyMs: Date.now() - start,
|
|
39
|
-
});
|
|
40
|
-
console.log(`✓ Promoted to authored artifact ${res.key}`);
|
|
41
|
-
console.log(` id: ${res.artifactId}`);
|
|
42
|
-
}
|
|
43
|
-
catch (err) {
|
|
44
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
45
|
-
tool: "codiedev_promote",
|
|
46
|
-
ok: false,
|
|
47
|
-
latencyMs: Date.now() - start,
|
|
48
|
-
error: err.message,
|
|
49
|
-
});
|
|
50
|
-
console.error(`Promote failed: ${err.message}`);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
}
|
package/dist/commands/react.js
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.runReact = runReact;
|
|
4
|
-
const shared_1 = require("./shared");
|
|
5
|
-
async function runReact(args, configOverride) {
|
|
6
|
-
const positionals = args.filter((a) => !a.startsWith("--"));
|
|
7
|
-
const [postId, emoji] = positionals;
|
|
8
|
-
if (!postId || !emoji) {
|
|
9
|
-
console.error("Usage: codiedev react <postId> <emoji>");
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
const config = configOverride ?? (0, shared_1.requireConfig)();
|
|
13
|
-
const start = Date.now();
|
|
14
|
-
try {
|
|
15
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/react", {
|
|
16
|
-
config,
|
|
17
|
-
body: { postId, emoji },
|
|
18
|
-
});
|
|
19
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
20
|
-
tool: "codiedev_react",
|
|
21
|
-
ok: true,
|
|
22
|
-
latencyMs: Date.now() - start,
|
|
23
|
-
});
|
|
24
|
-
console.log(res.action === "removed"
|
|
25
|
-
? `Removed ${res.emoji ?? emoji} from post ${postId}.`
|
|
26
|
-
: `Reacted to post ${postId} with ${res.emoji ?? emoji}.`);
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
30
|
-
tool: "codiedev_react",
|
|
31
|
-
ok: false,
|
|
32
|
-
latencyMs: Date.now() - start,
|
|
33
|
-
error: err.message,
|
|
34
|
-
});
|
|
35
|
-
console.error(`React failed: ${err.message}`);
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1,444 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isSafeRef = isSafeRef;
|
|
4
|
-
exports.runReverseTicket = runReverseTicket;
|
|
5
|
-
const child_process_1 = require("child_process");
|
|
6
|
-
const shared_1 = require("./shared");
|
|
7
|
-
const prUrl_1 = require("../prUrl");
|
|
8
|
-
function runArgv(bin, argv, opts = {}) {
|
|
9
|
-
const res = (0, child_process_1.spawnSync)(bin, argv, {
|
|
10
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
11
|
-
encoding: "utf8",
|
|
12
|
-
maxBuffer: opts.maxBuffer ?? 50 * 1024 * 1024,
|
|
13
|
-
shell: false,
|
|
14
|
-
});
|
|
15
|
-
if (res.error)
|
|
16
|
-
throw res.error;
|
|
17
|
-
if (res.status !== 0) {
|
|
18
|
-
throw new Error(`${bin} ${argv.join(" ")} failed (exit ${res.status}): ${(res.stderr || "").trim().slice(0, 500)}`);
|
|
19
|
-
}
|
|
20
|
-
return res.stdout ?? "";
|
|
21
|
-
}
|
|
22
|
-
const SAFE_REF_RE = /^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/;
|
|
23
|
-
function isSafeRef(ref) {
|
|
24
|
-
if (!ref || ref.startsWith("-"))
|
|
25
|
-
return false;
|
|
26
|
-
if (ref.includes("..") || ref.includes("@{"))
|
|
27
|
-
return false;
|
|
28
|
-
if (ref.endsWith("/") || ref.endsWith("."))
|
|
29
|
-
return false;
|
|
30
|
-
return SAFE_REF_RE.test(ref);
|
|
31
|
-
}
|
|
32
|
-
async function fetchPrOrMrBundle(parsed, rawUrl, noTruncate) {
|
|
33
|
-
console.log(`Fetching ${parsed.provider === "gitlab" ? "MR" : "PR"} ${parsed.owner}/${parsed.repo}#${parsed.number}…`);
|
|
34
|
-
if (parsed.provider === "github") {
|
|
35
|
-
let prJson;
|
|
36
|
-
let filesJson;
|
|
37
|
-
let diff;
|
|
38
|
-
try {
|
|
39
|
-
prJson = ghApi(`/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}`);
|
|
40
|
-
filesJson = ghApi(`/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}/files`);
|
|
41
|
-
try {
|
|
42
|
-
const rawDiff = ghRaw(`/repos/${parsed.owner}/${parsed.repo}/pulls/${parsed.number}`);
|
|
43
|
-
diff = noTruncate ? rawDiff : rawDiff.slice(0, 12_000);
|
|
44
|
-
}
|
|
45
|
-
catch {
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
console.error(`Failed to fetch PR via gh api: ${err.message}`);
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
repo: `${parsed.owner}/${parsed.repo}`,
|
|
54
|
-
number: parsed.number,
|
|
55
|
-
title: prJson.title ?? "",
|
|
56
|
-
body: prJson.body ?? "",
|
|
57
|
-
author: prJson.user?.login ?? "unknown",
|
|
58
|
-
authorEmail: prJson.user?.email ?? undefined,
|
|
59
|
-
mergedAt: prJson.merged_at
|
|
60
|
-
? Date.parse(prJson.merged_at)
|
|
61
|
-
: prJson.closed_at
|
|
62
|
-
? Date.parse(prJson.closed_at)
|
|
63
|
-
: Date.now(),
|
|
64
|
-
filesChanged: (filesJson ?? []).map((f) => f.filename),
|
|
65
|
-
diff,
|
|
66
|
-
htmlUrl: prJson.html_url ?? rawUrl,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
const projectPath = `${parsed.owner}/${parsed.repo}`;
|
|
70
|
-
let mrJson;
|
|
71
|
-
let changesJson;
|
|
72
|
-
try {
|
|
73
|
-
mrJson = glabApi(projectPath, `merge_requests/${parsed.number}`);
|
|
74
|
-
changesJson = glabApi(projectPath, `merge_requests/${parsed.number}/changes`);
|
|
75
|
-
}
|
|
76
|
-
catch (err) {
|
|
77
|
-
console.error(`Failed to fetch MR via glab api: ${err.message}`);
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
const changes = (changesJson?.changes ?? []);
|
|
81
|
-
const filesChanged = changes
|
|
82
|
-
.map((c) => c.new_path ?? c.old_path)
|
|
83
|
-
.filter((p) => Boolean(p));
|
|
84
|
-
const rawDiff = changes
|
|
85
|
-
.map((c) => {
|
|
86
|
-
const header = `diff --git a/${c.old_path ?? c.new_path} b/${c.new_path ?? c.old_path}`;
|
|
87
|
-
return [header, c.diff ?? ""].join("\n");
|
|
88
|
-
})
|
|
89
|
-
.join("\n");
|
|
90
|
-
const diff = noTruncate ? rawDiff : rawDiff.slice(0, 12_000);
|
|
91
|
-
return {
|
|
92
|
-
repo: projectPath,
|
|
93
|
-
number: parsed.number,
|
|
94
|
-
title: mrJson.title ?? "",
|
|
95
|
-
body: mrJson.description ?? "",
|
|
96
|
-
author: mrJson.author?.username ?? "unknown",
|
|
97
|
-
authorEmail: undefined,
|
|
98
|
-
mergedAt: mrJson.merged_at
|
|
99
|
-
? Date.parse(mrJson.merged_at)
|
|
100
|
-
: mrJson.closed_at
|
|
101
|
-
? Date.parse(mrJson.closed_at)
|
|
102
|
-
: Date.now(),
|
|
103
|
-
filesChanged,
|
|
104
|
-
diff,
|
|
105
|
-
htmlUrl: mrJson.web_url ?? rawUrl,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
function checkCli(bin) {
|
|
109
|
-
try {
|
|
110
|
-
runArgv(bin, ["--version"], { maxBuffer: 1024 * 1024 });
|
|
111
|
-
return true;
|
|
112
|
-
}
|
|
113
|
-
catch {
|
|
114
|
-
return false;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
function ghApi(path) {
|
|
118
|
-
return JSON.parse(runArgv("gh", ["api", path]));
|
|
119
|
-
}
|
|
120
|
-
function ghRaw(path) {
|
|
121
|
-
return runArgv("gh", [
|
|
122
|
-
"api",
|
|
123
|
-
"-H",
|
|
124
|
-
"Accept: application/vnd.github.v3.diff",
|
|
125
|
-
path,
|
|
126
|
-
]);
|
|
127
|
-
}
|
|
128
|
-
function glabApi(projectPath, subpath) {
|
|
129
|
-
const encoded = encodeURIComponent(projectPath);
|
|
130
|
-
return JSON.parse(runArgv("glab", ["api", `projects/${encoded}/${subpath}`]));
|
|
131
|
-
}
|
|
132
|
-
function parseArgs(args) {
|
|
133
|
-
let prUrl;
|
|
134
|
-
let base;
|
|
135
|
-
let forcedKey;
|
|
136
|
-
let noTruncate = false;
|
|
137
|
-
for (let i = 0; i < args.length; i++) {
|
|
138
|
-
const a = args[i];
|
|
139
|
-
if ((a === "--with" || a === "-w") && i + 1 < args.length) {
|
|
140
|
-
forcedKey = args[++i];
|
|
141
|
-
}
|
|
142
|
-
else if (a.startsWith("--with=")) {
|
|
143
|
-
forcedKey = a.slice("--with=".length);
|
|
144
|
-
}
|
|
145
|
-
else if ((a === "--base" || a === "-b") && i + 1 < args.length) {
|
|
146
|
-
base = args[++i];
|
|
147
|
-
}
|
|
148
|
-
else if (a.startsWith("--base=")) {
|
|
149
|
-
base = a.slice("--base=".length);
|
|
150
|
-
}
|
|
151
|
-
else if (a === "--full") {
|
|
152
|
-
noTruncate = true;
|
|
153
|
-
}
|
|
154
|
-
else if (!a.startsWith("--") && !prUrl) {
|
|
155
|
-
prUrl = a;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
return { prUrl, base, forcedKey, noTruncate };
|
|
159
|
-
}
|
|
160
|
-
async function runReverseTicket(args) {
|
|
161
|
-
const { prUrl, base, forcedKey, noTruncate } = parseArgs(args);
|
|
162
|
-
if (!prUrl) {
|
|
163
|
-
return runBranchMode({ explicitBase: base, forcedKey });
|
|
164
|
-
}
|
|
165
|
-
const parsed = (0, prUrl_1.parsePrOrMrUrl)(prUrl);
|
|
166
|
-
if (!parsed) {
|
|
167
|
-
console.error("Couldn't parse PR/MR URL. Expected formats:");
|
|
168
|
-
console.error(" https://github.com/<owner>/<repo>/pull/<number>");
|
|
169
|
-
console.error(" https://gitlab.com/<group>/<repo>/-/merge_requests/<number>");
|
|
170
|
-
console.error(" (self-hosted GitLab works too — same shape, your host)");
|
|
171
|
-
console.error("");
|
|
172
|
-
console.error("Run with no arguments for branch mode (uses your current local branch).");
|
|
173
|
-
process.exit(1);
|
|
174
|
-
}
|
|
175
|
-
const requiredCli = parsed.provider === "gitlab" ? "glab" : "gh";
|
|
176
|
-
if (!checkCli(requiredCli)) {
|
|
177
|
-
console.error(`This command needs the \`${requiredCli}\` CLI installed and authenticated.`);
|
|
178
|
-
if (requiredCli === "gh") {
|
|
179
|
-
console.error(" brew install gh (macOS)");
|
|
180
|
-
console.error(" gh auth login");
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
console.error(" brew install glab (macOS)");
|
|
184
|
-
console.error(" glab auth login");
|
|
185
|
-
}
|
|
186
|
-
process.exit(1);
|
|
187
|
-
}
|
|
188
|
-
const config = (0, shared_1.requireConfig)();
|
|
189
|
-
const bundle = await fetchPrOrMrBundle(parsed, prUrl, noTruncate);
|
|
190
|
-
console.log(` title: ${bundle.title}`);
|
|
191
|
-
console.log(` author: ${bundle.author}`);
|
|
192
|
-
console.log(` files: ${bundle.filesChanged.length}`);
|
|
193
|
-
console.log(` merged: ${new Date(bundle.mergedAt).toISOString()}`);
|
|
194
|
-
console.log("");
|
|
195
|
-
console.log("Generating ticket…");
|
|
196
|
-
console.log("");
|
|
197
|
-
const start = Date.now();
|
|
198
|
-
try {
|
|
199
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/reverseTicket", {
|
|
200
|
-
config,
|
|
201
|
-
body: {
|
|
202
|
-
pr: bundle,
|
|
203
|
-
forcedArtifactKey: forcedKey,
|
|
204
|
-
},
|
|
205
|
-
});
|
|
206
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
207
|
-
tool: "codiedev_reverse_ticket",
|
|
208
|
-
ok: true,
|
|
209
|
-
latencyMs: Date.now() - start,
|
|
210
|
-
});
|
|
211
|
-
if (res.match) {
|
|
212
|
-
console.log(`✓ Matched artifact: ${res.match.key} (score=${res.match.score.toFixed(2)})`);
|
|
213
|
-
console.log(` signals · body=${res.match.signals.bodyMention} · files=${res.match.signals.fileOverlap.toFixed(2)} · transcript=${res.match.signals.transcriptEdit}`);
|
|
214
|
-
}
|
|
215
|
-
else if (res.candidates.length > 0) {
|
|
216
|
-
console.log(`? Ambiguous / low confidence. Candidates:`);
|
|
217
|
-
for (const c of res.candidates.slice(0, 3)) {
|
|
218
|
-
console.log(` - ${c.key} (score=${c.score.toFixed(2)})`);
|
|
219
|
-
}
|
|
220
|
-
console.log(` Re-run with --with <key> to force a specific artifact.`);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
console.log("· No matched artifact. Ticket generated from PR diff alone.");
|
|
224
|
-
}
|
|
225
|
-
console.log(` prompt tokens (est): ${res.tokensInEstimate}`);
|
|
226
|
-
console.log("");
|
|
227
|
-
console.log("─────────── Generated ticket ───────────");
|
|
228
|
-
console.log("");
|
|
229
|
-
console.log(res.ticket);
|
|
230
|
-
}
|
|
231
|
-
catch (err) {
|
|
232
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
233
|
-
tool: "codiedev_reverse_ticket",
|
|
234
|
-
ok: false,
|
|
235
|
-
latencyMs: Date.now() - start,
|
|
236
|
-
error: err.message,
|
|
237
|
-
});
|
|
238
|
-
console.error(`Reverse-ticket failed: ${err.message}`);
|
|
239
|
-
process.exit(1);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
function git(cmd) {
|
|
243
|
-
try {
|
|
244
|
-
return (0, child_process_1.execSync)(`git ${cmd}`, {
|
|
245
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
246
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
247
|
-
})
|
|
248
|
-
.toString("utf8")
|
|
249
|
-
.trim();
|
|
250
|
-
}
|
|
251
|
-
catch (err) {
|
|
252
|
-
throw new Error(`git ${cmd} failed: ${err.message}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
function safeGit(fn) {
|
|
256
|
-
try {
|
|
257
|
-
return fn() || undefined;
|
|
258
|
-
}
|
|
259
|
-
catch {
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
async function runBranchMode(opts) {
|
|
264
|
-
try {
|
|
265
|
-
git("rev-parse --is-inside-work-tree");
|
|
266
|
-
}
|
|
267
|
-
catch {
|
|
268
|
-
console.error("Not inside a git repository. Run this from a checked-out repo.");
|
|
269
|
-
process.exit(1);
|
|
270
|
-
}
|
|
271
|
-
if (opts.explicitBase !== undefined && !isSafeRef(opts.explicitBase)) {
|
|
272
|
-
console.error(`Invalid --base ref: ${JSON.stringify(opts.explicitBase)}. Expected a plain branch or tag name.`);
|
|
273
|
-
process.exit(1);
|
|
274
|
-
}
|
|
275
|
-
const candidates = opts.explicitBase
|
|
276
|
-
? [opts.explicitBase]
|
|
277
|
-
: ["origin/main", "origin/master"];
|
|
278
|
-
let baseRef = null;
|
|
279
|
-
for (const c of candidates) {
|
|
280
|
-
try {
|
|
281
|
-
git(`rev-parse --verify ${c}`);
|
|
282
|
-
baseRef = c;
|
|
283
|
-
break;
|
|
284
|
-
}
|
|
285
|
-
catch {
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (!baseRef) {
|
|
289
|
-
console.error(`Couldn't find a base ref (tried ${candidates.join(", ")}). Pass --base <ref>.`);
|
|
290
|
-
process.exit(1);
|
|
291
|
-
}
|
|
292
|
-
const branch = git("rev-parse --abbrev-ref HEAD");
|
|
293
|
-
if (branch === "HEAD") {
|
|
294
|
-
console.error("Detached HEAD — checkout a branch first.");
|
|
295
|
-
process.exit(1);
|
|
296
|
-
}
|
|
297
|
-
const baseSha = git(`merge-base HEAD ${baseRef}`);
|
|
298
|
-
const headSha = git("rev-parse HEAD");
|
|
299
|
-
const isNoise = (p) => /(^|\/)(node_modules|dist|build|\.next|\.turbo|coverage|out)\//.test(p) ||
|
|
300
|
-
/(^|\/)package-lock\.json$/.test(p) ||
|
|
301
|
-
/(^|\/)yarn\.lock$/.test(p) ||
|
|
302
|
-
/(^|\/)pnpm-lock\.yaml$/.test(p) ||
|
|
303
|
-
/(^|\/)bun\.lock(b)?$/.test(p) ||
|
|
304
|
-
/(^|\/)Cargo\.lock$/.test(p) ||
|
|
305
|
-
/(^|\/)Gemfile\.lock$/.test(p) ||
|
|
306
|
-
/(^|\/)poetry\.lock$/.test(p) ||
|
|
307
|
-
/(^|\/)go\.sum$/.test(p);
|
|
308
|
-
const shellTolerant = (cmd) => {
|
|
309
|
-
try {
|
|
310
|
-
return (0, child_process_1.execSync)(`${cmd} || true`, {
|
|
311
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
312
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
313
|
-
})
|
|
314
|
-
.toString("utf8")
|
|
315
|
-
.trim();
|
|
316
|
-
}
|
|
317
|
-
catch {
|
|
318
|
-
return "";
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
const shQuote = (s) => `'${s.replace(/'/g, "'\\''")}'`;
|
|
322
|
-
const trackedRaw = git(`diff --name-only ${baseSha}`);
|
|
323
|
-
const trackedFiles = trackedRaw ? trackedRaw.split("\n").filter(Boolean) : [];
|
|
324
|
-
const untrackedRaw = safeGit(() => git("ls-files --others --exclude-standard"));
|
|
325
|
-
const untrackedFiles = untrackedRaw
|
|
326
|
-
? untrackedRaw.split("\n").filter(Boolean)
|
|
327
|
-
: [];
|
|
328
|
-
const filesChanged = Array.from(new Set([...trackedFiles, ...untrackedFiles]));
|
|
329
|
-
if (filesChanged.length === 0) {
|
|
330
|
-
console.error(`No changes detected between ${baseRef} and your working tree on ${branch}.`);
|
|
331
|
-
process.exit(1);
|
|
332
|
-
}
|
|
333
|
-
let stat = "";
|
|
334
|
-
try {
|
|
335
|
-
const trackedStat = git(`diff --stat=200 ${baseSha}`);
|
|
336
|
-
const untrackedStatLines = untrackedFiles.map((f) => {
|
|
337
|
-
const wc = shellTolerant(`wc -l < ${shQuote(f)}`);
|
|
338
|
-
const n = parseInt(wc.split(/\s+/)[0] || "0", 10) || 0;
|
|
339
|
-
return ` ${f} | ${n} ++++++++++ (new file)`;
|
|
340
|
-
});
|
|
341
|
-
stat = [trackedStat, ...untrackedStatLines]
|
|
342
|
-
.filter(Boolean)
|
|
343
|
-
.join("\n")
|
|
344
|
-
.slice(0, 8_000);
|
|
345
|
-
}
|
|
346
|
-
catch {
|
|
347
|
-
}
|
|
348
|
-
let diff = "";
|
|
349
|
-
try {
|
|
350
|
-
const sourceTracked = trackedFiles.filter((f) => !isNoise(f));
|
|
351
|
-
const trackedDiff = sourceTracked.length > 0
|
|
352
|
-
? git(`diff ${baseSha} -- ${sourceTracked.map(shQuote).join(" ")}`)
|
|
353
|
-
: git(`diff ${baseSha}`);
|
|
354
|
-
const sourceUntracked = untrackedFiles.filter((f) => !isNoise(f));
|
|
355
|
-
const untrackedDiffs = sourceUntracked.map((f) => shellTolerant(`git diff --no-index --no-color -- /dev/null ${shQuote(f)}`));
|
|
356
|
-
diff = [trackedDiff, ...untrackedDiffs]
|
|
357
|
-
.filter(Boolean)
|
|
358
|
-
.join("\n")
|
|
359
|
-
.slice(0, 40_000);
|
|
360
|
-
}
|
|
361
|
-
catch {
|
|
362
|
-
}
|
|
363
|
-
let repo;
|
|
364
|
-
try {
|
|
365
|
-
const remoteUrl = git("config --get remote.origin.url");
|
|
366
|
-
const m = remoteUrl.match(/[:/]([^/:]+)\/([^/:]+?)(?:\.git)?$/);
|
|
367
|
-
if (!m) {
|
|
368
|
-
throw new Error(`Couldn't parse owner/repo from remote: ${remoteUrl}`);
|
|
369
|
-
}
|
|
370
|
-
repo = `${m[1]}/${m[2]}`;
|
|
371
|
-
}
|
|
372
|
-
catch (err) {
|
|
373
|
-
console.error(`Couldn't read git remote.origin.url: ${err.message}`);
|
|
374
|
-
process.exit(1);
|
|
375
|
-
}
|
|
376
|
-
const title = safeGit(() => git("log -1 --format=%s")) ?? `WIP: ${branch}`;
|
|
377
|
-
const author = safeGit(() => git("config --get user.name"));
|
|
378
|
-
const authorEmail = safeGit(() => git("config --get user.email"));
|
|
379
|
-
console.log(`Branch: ${branch} (${repo})`);
|
|
380
|
-
console.log(`Base: ${baseRef} @ ${baseSha.slice(0, 12)}`);
|
|
381
|
-
console.log(`Head: ${headSha.slice(0, 12)}`);
|
|
382
|
-
console.log(`Files: ${filesChanged.length}`);
|
|
383
|
-
console.log("");
|
|
384
|
-
console.log("Generating ticket draft…");
|
|
385
|
-
console.log("");
|
|
386
|
-
const config = (0, shared_1.requireConfig)();
|
|
387
|
-
const start = Date.now();
|
|
388
|
-
try {
|
|
389
|
-
const res = await (0, shared_1.apiRequest)("POST", "/api/cli/reverseTicketFromBranch", {
|
|
390
|
-
config,
|
|
391
|
-
body: {
|
|
392
|
-
branch: {
|
|
393
|
-
repo,
|
|
394
|
-
branch,
|
|
395
|
-
baseSha,
|
|
396
|
-
headSha,
|
|
397
|
-
title,
|
|
398
|
-
author,
|
|
399
|
-
authorEmail,
|
|
400
|
-
filesChanged,
|
|
401
|
-
diff: diff || undefined,
|
|
402
|
-
stat: stat || undefined,
|
|
403
|
-
},
|
|
404
|
-
forcedArtifactKey: opts.forcedKey,
|
|
405
|
-
},
|
|
406
|
-
});
|
|
407
|
-
if (!res.ok || !res.artifactId) {
|
|
408
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
409
|
-
tool: "codiedev_reverse_ticket",
|
|
410
|
-
ok: false,
|
|
411
|
-
latencyMs: Date.now() - start,
|
|
412
|
-
error: "no result",
|
|
413
|
-
});
|
|
414
|
-
console.error("Reverse-ticket generation returned no result. The writer may have skipped due to missing context.");
|
|
415
|
-
process.exit(1);
|
|
416
|
-
}
|
|
417
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
418
|
-
tool: "codiedev_reverse_ticket",
|
|
419
|
-
ok: true,
|
|
420
|
-
latencyMs: Date.now() - start,
|
|
421
|
-
});
|
|
422
|
-
const portalHost = (process.env.CODIEDEV_PORTAL_URL || "https://codiedev.com").replace(/\/$/, "");
|
|
423
|
-
const portalLink = res.portalUrl.startsWith("http")
|
|
424
|
-
? res.portalUrl
|
|
425
|
-
: `${portalHost}${res.portalUrl.startsWith("/") ? "" : "/"}${res.portalUrl}`;
|
|
426
|
-
const verb = res.action === "overwritten" ? "Updated" : "Created";
|
|
427
|
-
console.log(`✓ ${verb} reverse-ticket draft.`);
|
|
428
|
-
console.log(` artifact: ${res.artifactKey ?? res.artifactId}`);
|
|
429
|
-
console.log(` portal: ${portalLink}`);
|
|
430
|
-
console.log("");
|
|
431
|
-
console.log("Open in your portal to review, edit, and push to Jira.");
|
|
432
|
-
console.log("Each call creates a fresh draft — clean up duplicates from the portal if needed.");
|
|
433
|
-
}
|
|
434
|
-
catch (err) {
|
|
435
|
-
(0, shared_1.recordAgentEvent)(config, {
|
|
436
|
-
tool: "codiedev_reverse_ticket",
|
|
437
|
-
ok: false,
|
|
438
|
-
latencyMs: Date.now() - start,
|
|
439
|
-
error: err.message,
|
|
440
|
-
});
|
|
441
|
-
console.error(`Reverse-ticket failed: ${err.message}`);
|
|
442
|
-
process.exit(1);
|
|
443
|
-
}
|
|
444
|
-
}
|