@benzotti/jedi 0.1.2 → 0.1.4
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/action/action.yml +108 -0
- package/action/workflow-template.yml +172 -0
- package/dist/index.js +1135 -147
- package/framework/agents/jdi-backend.md +1 -1
- package/framework/agents/jdi-devops.md +1 -1
- package/framework/agents/jdi-executor.md +1 -1
- package/framework/agents/jdi-feedback-learner.md +18 -17
- package/framework/agents/jdi-frontend.md +1 -1
- package/framework/agents/jdi-pr-feedback.md +2 -0
- package/framework/agents/jdi-quality.md +1 -1
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
3
5
|
var __defProp = Object.defineProperty;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
13
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
21
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
22
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
23
|
+
for (let key of __getOwnPropNames(mod))
|
|
24
|
+
if (!__hasOwnProp.call(to, key))
|
|
25
|
+
__defProp(to, key, {
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
27
|
+
enumerable: true
|
|
28
|
+
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
31
|
+
return to;
|
|
32
|
+
};
|
|
4
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
5
34
|
var __returnValue = (v) => v;
|
|
6
35
|
function __exportSetter(name, newValue) {
|
|
@@ -7676,6 +7705,118 @@ var require_public_api = __commonJS((exports) => {
|
|
|
7676
7705
|
exports.stringify = stringify;
|
|
7677
7706
|
});
|
|
7678
7707
|
|
|
7708
|
+
// node_modules/yaml/dist/index.js
|
|
7709
|
+
var require_dist = __commonJS((exports) => {
|
|
7710
|
+
var composer = require_composer();
|
|
7711
|
+
var Document = require_Document();
|
|
7712
|
+
var Schema = require_Schema();
|
|
7713
|
+
var errors = require_errors();
|
|
7714
|
+
var Alias = require_Alias();
|
|
7715
|
+
var identity = require_identity();
|
|
7716
|
+
var Pair = require_Pair();
|
|
7717
|
+
var Scalar = require_Scalar();
|
|
7718
|
+
var YAMLMap = require_YAMLMap();
|
|
7719
|
+
var YAMLSeq = require_YAMLSeq();
|
|
7720
|
+
var cst = require_cst();
|
|
7721
|
+
var lexer = require_lexer();
|
|
7722
|
+
var lineCounter = require_line_counter();
|
|
7723
|
+
var parser = require_parser();
|
|
7724
|
+
var publicApi = require_public_api();
|
|
7725
|
+
var visit = require_visit();
|
|
7726
|
+
exports.Composer = composer.Composer;
|
|
7727
|
+
exports.Document = Document.Document;
|
|
7728
|
+
exports.Schema = Schema.Schema;
|
|
7729
|
+
exports.YAMLError = errors.YAMLError;
|
|
7730
|
+
exports.YAMLParseError = errors.YAMLParseError;
|
|
7731
|
+
exports.YAMLWarning = errors.YAMLWarning;
|
|
7732
|
+
exports.Alias = Alias.Alias;
|
|
7733
|
+
exports.isAlias = identity.isAlias;
|
|
7734
|
+
exports.isCollection = identity.isCollection;
|
|
7735
|
+
exports.isDocument = identity.isDocument;
|
|
7736
|
+
exports.isMap = identity.isMap;
|
|
7737
|
+
exports.isNode = identity.isNode;
|
|
7738
|
+
exports.isPair = identity.isPair;
|
|
7739
|
+
exports.isScalar = identity.isScalar;
|
|
7740
|
+
exports.isSeq = identity.isSeq;
|
|
7741
|
+
exports.Pair = Pair.Pair;
|
|
7742
|
+
exports.Scalar = Scalar.Scalar;
|
|
7743
|
+
exports.YAMLMap = YAMLMap.YAMLMap;
|
|
7744
|
+
exports.YAMLSeq = YAMLSeq.YAMLSeq;
|
|
7745
|
+
exports.CST = cst;
|
|
7746
|
+
exports.Lexer = lexer.Lexer;
|
|
7747
|
+
exports.LineCounter = lineCounter.LineCounter;
|
|
7748
|
+
exports.Parser = parser.Parser;
|
|
7749
|
+
exports.parse = publicApi.parse;
|
|
7750
|
+
exports.parseAllDocuments = publicApi.parseAllDocuments;
|
|
7751
|
+
exports.parseDocument = publicApi.parseDocument;
|
|
7752
|
+
exports.stringify = publicApi.stringify;
|
|
7753
|
+
exports.visit = visit.visit;
|
|
7754
|
+
exports.visitAsync = visit.visitAsync;
|
|
7755
|
+
});
|
|
7756
|
+
|
|
7757
|
+
// src/utils/git.ts
|
|
7758
|
+
var exports_git = {};
|
|
7759
|
+
__export(exports_git, {
|
|
7760
|
+
gitStatus: () => gitStatus,
|
|
7761
|
+
gitRoot: () => gitRoot,
|
|
7762
|
+
gitMergeBase: () => gitMergeBase,
|
|
7763
|
+
gitLog: () => gitLog,
|
|
7764
|
+
gitDiffNames: () => gitDiffNames,
|
|
7765
|
+
gitDiff: () => gitDiff,
|
|
7766
|
+
gitBranch: () => gitBranch,
|
|
7767
|
+
exec: () => exec
|
|
7768
|
+
});
|
|
7769
|
+
async function exec(cmd, cwd) {
|
|
7770
|
+
const proc = Bun.spawn(cmd, {
|
|
7771
|
+
cwd: cwd ?? process.cwd(),
|
|
7772
|
+
stdout: "pipe",
|
|
7773
|
+
stderr: "pipe"
|
|
7774
|
+
});
|
|
7775
|
+
const stdout2 = await new Response(proc.stdout).text();
|
|
7776
|
+
const exitCode = await proc.exited;
|
|
7777
|
+
return { stdout: stdout2.trim(), exitCode };
|
|
7778
|
+
}
|
|
7779
|
+
async function gitDiff(staged) {
|
|
7780
|
+
const args = ["git", "diff"];
|
|
7781
|
+
if (staged)
|
|
7782
|
+
args.push("--cached");
|
|
7783
|
+
const { stdout: stdout2 } = await exec(args);
|
|
7784
|
+
return stdout2;
|
|
7785
|
+
}
|
|
7786
|
+
async function gitDiffNames(staged) {
|
|
7787
|
+
const args = ["git", "diff", "--name-only"];
|
|
7788
|
+
if (staged)
|
|
7789
|
+
args.push("--cached");
|
|
7790
|
+
const { stdout: stdout2 } = await exec(args);
|
|
7791
|
+
return stdout2 ? stdout2.split(`
|
|
7792
|
+
`) : [];
|
|
7793
|
+
}
|
|
7794
|
+
async function gitLog(range) {
|
|
7795
|
+
const args = ["git", "log", "--oneline"];
|
|
7796
|
+
if (range)
|
|
7797
|
+
args.push(range);
|
|
7798
|
+
else
|
|
7799
|
+
args.push("-20");
|
|
7800
|
+
const { stdout: stdout2 } = await exec(args);
|
|
7801
|
+
return stdout2;
|
|
7802
|
+
}
|
|
7803
|
+
async function gitBranch() {
|
|
7804
|
+
const { stdout: stdout2 } = await exec(["git", "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
7805
|
+
return stdout2;
|
|
7806
|
+
}
|
|
7807
|
+
async function gitRoot() {
|
|
7808
|
+
const { stdout: stdout2 } = await exec(["git", "rev-parse", "--show-toplevel"]);
|
|
7809
|
+
return stdout2;
|
|
7810
|
+
}
|
|
7811
|
+
async function gitStatus() {
|
|
7812
|
+
const { stdout: stdout2 } = await exec(["git", "status", "--porcelain"]);
|
|
7813
|
+
return stdout2;
|
|
7814
|
+
}
|
|
7815
|
+
async function gitMergeBase(branch) {
|
|
7816
|
+
const { stdout: stdout2 } = await exec(["git", "merge-base", "HEAD", branch]);
|
|
7817
|
+
return stdout2;
|
|
7818
|
+
}
|
|
7819
|
+
|
|
7679
7820
|
// node_modules/consola/dist/core.mjs
|
|
7680
7821
|
var LogLevels = {
|
|
7681
7822
|
silent: Number.NEGATIVE_INFINITY,
|
|
@@ -9203,30 +9344,73 @@ var initCommand = defineCommand({
|
|
|
9203
9344
|
type: "boolean",
|
|
9204
9345
|
description: "Overwrite existing files",
|
|
9205
9346
|
default: false
|
|
9347
|
+
},
|
|
9348
|
+
ci: {
|
|
9349
|
+
type: "boolean",
|
|
9350
|
+
description: "Headless CI mode (no prompts, JSON output)",
|
|
9351
|
+
default: false
|
|
9352
|
+
},
|
|
9353
|
+
storage: {
|
|
9354
|
+
type: "string",
|
|
9355
|
+
description: "Storage adapter to configure (default: fs)"
|
|
9356
|
+
},
|
|
9357
|
+
"storage-path": {
|
|
9358
|
+
type: "string",
|
|
9359
|
+
description: "Storage base path (default: .jdi/persistence/)"
|
|
9206
9360
|
}
|
|
9207
9361
|
},
|
|
9208
9362
|
async run({ args }) {
|
|
9209
9363
|
const cwd = process.cwd();
|
|
9210
9364
|
const projectType = await detectProjectType(cwd);
|
|
9211
|
-
|
|
9212
|
-
|
|
9365
|
+
if (!args.ci) {
|
|
9366
|
+
consola.info(`Detected project type: ${projectType}`);
|
|
9367
|
+
consola.start("Initialising JDI...");
|
|
9368
|
+
}
|
|
9213
9369
|
const dirs = [
|
|
9214
9370
|
".claude/commands/jdi",
|
|
9215
9371
|
".jdi/plans",
|
|
9216
9372
|
".jdi/research",
|
|
9217
9373
|
".jdi/codebase",
|
|
9218
9374
|
".jdi/reviews",
|
|
9219
|
-
".jdi/config"
|
|
9375
|
+
".jdi/config",
|
|
9376
|
+
".jdi/persistence"
|
|
9220
9377
|
];
|
|
9221
9378
|
for (const dir of dirs) {
|
|
9222
9379
|
await Bun.write(join3(cwd, dir, ".gitkeep"), "");
|
|
9223
9380
|
}
|
|
9224
9381
|
await copyFrameworkFiles(cwd, projectType, args.force);
|
|
9225
|
-
|
|
9226
|
-
|
|
9227
|
-
|
|
9228
|
-
|
|
9229
|
-
|
|
9382
|
+
if (args.storage || args["storage-path"]) {
|
|
9383
|
+
const { parse, stringify } = await Promise.resolve().then(() => __toESM(require_dist(), 1));
|
|
9384
|
+
const configPath = join3(cwd, ".jdi", "config", "jdi-config.yaml");
|
|
9385
|
+
let config = {};
|
|
9386
|
+
try {
|
|
9387
|
+
const existing = await Bun.file(configPath).text();
|
|
9388
|
+
config = parse(existing) ?? {};
|
|
9389
|
+
} catch {}
|
|
9390
|
+
config.storage = {
|
|
9391
|
+
adapter: args.storage ?? "fs",
|
|
9392
|
+
base_path: args["storage-path"] ?? ".jdi/persistence/"
|
|
9393
|
+
};
|
|
9394
|
+
await Bun.write(configPath, stringify(config));
|
|
9395
|
+
}
|
|
9396
|
+
if (args.ci) {
|
|
9397
|
+
const result = {
|
|
9398
|
+
status: "initialised",
|
|
9399
|
+
project_type: projectType,
|
|
9400
|
+
cwd,
|
|
9401
|
+
storage: {
|
|
9402
|
+
adapter: args.storage ?? "fs",
|
|
9403
|
+
base_path: args["storage-path"] ?? ".jdi/persistence/"
|
|
9404
|
+
}
|
|
9405
|
+
};
|
|
9406
|
+
console.log(JSON.stringify(result));
|
|
9407
|
+
} else {
|
|
9408
|
+
consola.success("JDI initialised successfully!");
|
|
9409
|
+
consola.info("");
|
|
9410
|
+
consola.info("Get started:");
|
|
9411
|
+
consola.info(' /jdi:create-plan "your feature"');
|
|
9412
|
+
consola.info(' /jdi:quick "small fix"');
|
|
9413
|
+
}
|
|
9230
9414
|
}
|
|
9231
9415
|
});
|
|
9232
9416
|
|
|
@@ -9234,69 +9418,292 @@ var initCommand = defineCommand({
|
|
|
9234
9418
|
import { resolve as resolve2 } from "path";
|
|
9235
9419
|
|
|
9236
9420
|
// src/utils/adapter.ts
|
|
9421
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
9237
9422
|
import { join as join4 } from "path";
|
|
9238
9423
|
import { existsSync as existsSync3 } from "fs";
|
|
9239
|
-
|
|
9240
|
-
// node_modules/yaml/dist/index.js
|
|
9241
|
-
var composer = require_composer();
|
|
9242
|
-
var Document = require_Document();
|
|
9243
|
-
var Schema = require_Schema();
|
|
9244
|
-
var errors = require_errors();
|
|
9245
|
-
var Alias = require_Alias();
|
|
9246
|
-
var identity = require_identity();
|
|
9247
|
-
var Pair = require_Pair();
|
|
9248
|
-
var Scalar = require_Scalar();
|
|
9249
|
-
var YAMLMap = require_YAMLMap();
|
|
9250
|
-
var YAMLSeq = require_YAMLSeq();
|
|
9251
|
-
var cst = require_cst();
|
|
9252
|
-
var lexer = require_lexer();
|
|
9253
|
-
var lineCounter = require_line_counter();
|
|
9254
|
-
var parser = require_parser();
|
|
9255
|
-
var publicApi = require_public_api();
|
|
9256
|
-
var visit = require_visit();
|
|
9257
|
-
var $Composer = composer.Composer;
|
|
9258
|
-
var $Document = Document.Document;
|
|
9259
|
-
var $Schema = Schema.Schema;
|
|
9260
|
-
var $YAMLError = errors.YAMLError;
|
|
9261
|
-
var $YAMLParseError = errors.YAMLParseError;
|
|
9262
|
-
var $YAMLWarning = errors.YAMLWarning;
|
|
9263
|
-
var $Alias = Alias.Alias;
|
|
9264
|
-
var $isAlias = identity.isAlias;
|
|
9265
|
-
var $isCollection = identity.isCollection;
|
|
9266
|
-
var $isDocument = identity.isDocument;
|
|
9267
|
-
var $isMap = identity.isMap;
|
|
9268
|
-
var $isNode = identity.isNode;
|
|
9269
|
-
var $isPair = identity.isPair;
|
|
9270
|
-
var $isScalar = identity.isScalar;
|
|
9271
|
-
var $isSeq = identity.isSeq;
|
|
9272
|
-
var $Pair = Pair.Pair;
|
|
9273
|
-
var $Scalar = Scalar.Scalar;
|
|
9274
|
-
var $YAMLMap = YAMLMap.YAMLMap;
|
|
9275
|
-
var $YAMLSeq = YAMLSeq.YAMLSeq;
|
|
9276
|
-
var $Lexer = lexer.Lexer;
|
|
9277
|
-
var $LineCounter = lineCounter.LineCounter;
|
|
9278
|
-
var $Parser = parser.Parser;
|
|
9279
|
-
var $parse = publicApi.parse;
|
|
9280
|
-
var $parseAllDocuments = publicApi.parseAllDocuments;
|
|
9281
|
-
var $parseDocument = publicApi.parseDocument;
|
|
9282
|
-
var $stringify = publicApi.stringify;
|
|
9283
|
-
var $visit = visit.visit;
|
|
9284
|
-
var $visitAsync = visit.visitAsync;
|
|
9285
|
-
|
|
9286
|
-
// src/utils/adapter.ts
|
|
9287
9424
|
async function readAdapter(cwd) {
|
|
9288
9425
|
const adapterPath = join4(cwd, ".jdi", "config", "adapter.yaml");
|
|
9289
9426
|
if (!existsSync3(adapterPath))
|
|
9290
9427
|
return null;
|
|
9291
9428
|
const content = await Bun.file(adapterPath).text();
|
|
9292
|
-
return
|
|
9429
|
+
return import_yaml.parse(content);
|
|
9430
|
+
}
|
|
9431
|
+
|
|
9432
|
+
// src/utils/claude.ts
|
|
9433
|
+
var PROMPT_LENGTH_THRESHOLD = 1e5;
|
|
9434
|
+
async function findClaude() {
|
|
9435
|
+
const path = Bun.which("claude");
|
|
9436
|
+
if (path)
|
|
9437
|
+
return path;
|
|
9438
|
+
const { exec: exec2 } = await Promise.resolve().then(() => exports_git);
|
|
9439
|
+
const { stdout: stdout2, exitCode } = await exec2(["which", "claude"]);
|
|
9440
|
+
if (exitCode === 0 && stdout2)
|
|
9441
|
+
return stdout2;
|
|
9442
|
+
throw new Error("Claude CLI not found. Install it from https://docs.anthropic.com/en/docs/claude-code");
|
|
9443
|
+
}
|
|
9444
|
+
var lastEventType = "";
|
|
9445
|
+
function formatStreamEvent(event) {
|
|
9446
|
+
if (event.type === "assistant" && event.message?.content) {
|
|
9447
|
+
const parts = [];
|
|
9448
|
+
for (const block of event.message.content) {
|
|
9449
|
+
if (block.type === "text" && block.text) {
|
|
9450
|
+
const prefix = lastEventType === "tool" ? `
|
|
9451
|
+
` : "";
|
|
9452
|
+
parts.push(prefix + block.text.trim());
|
|
9453
|
+
lastEventType = "text";
|
|
9454
|
+
} else if (block.type === "tool_use") {
|
|
9455
|
+
const name = block.name ?? "tool";
|
|
9456
|
+
const input = block.input;
|
|
9457
|
+
let detail = "";
|
|
9458
|
+
if (input?.file_path) {
|
|
9459
|
+
const segments = input.file_path.split("/");
|
|
9460
|
+
detail = ` \u2192 ${segments.slice(-3).join("/")}`;
|
|
9461
|
+
} else if (name === "Bash" && input?.command) {
|
|
9462
|
+
detail = ` \u2192 ${input.command.slice(0, 60)}`;
|
|
9463
|
+
} else if (input?.pattern) {
|
|
9464
|
+
detail = ` \u2192 ${input.pattern}`;
|
|
9465
|
+
}
|
|
9466
|
+
parts.push(` \u26A1 ${name}${detail}`);
|
|
9467
|
+
lastEventType = "tool";
|
|
9468
|
+
}
|
|
9469
|
+
}
|
|
9470
|
+
if (parts.length > 0)
|
|
9471
|
+
return parts.join(`
|
|
9472
|
+
`) + `
|
|
9473
|
+
`;
|
|
9474
|
+
}
|
|
9475
|
+
if (event.type === "result" && event.subtype === "error_tool_result") {
|
|
9476
|
+
lastEventType = "error";
|
|
9477
|
+
return ` \u274C Tool error
|
|
9478
|
+
`;
|
|
9479
|
+
}
|
|
9480
|
+
return null;
|
|
9481
|
+
}
|
|
9482
|
+
async function spawnClaude(prompt2, opts) {
|
|
9483
|
+
const claudePath = await findClaude();
|
|
9484
|
+
const args = [
|
|
9485
|
+
claudePath,
|
|
9486
|
+
"-p",
|
|
9487
|
+
"--verbose",
|
|
9488
|
+
"--output-format",
|
|
9489
|
+
"stream-json",
|
|
9490
|
+
"--permission-mode",
|
|
9491
|
+
opts?.permissionMode ?? "acceptEdits"
|
|
9492
|
+
];
|
|
9493
|
+
if (opts?.allowedTools) {
|
|
9494
|
+
for (const tool of opts.allowedTools) {
|
|
9495
|
+
args.push("--allowedTools", tool);
|
|
9496
|
+
}
|
|
9497
|
+
}
|
|
9498
|
+
if (opts?.model) {
|
|
9499
|
+
args.push("--model", opts.model);
|
|
9500
|
+
}
|
|
9501
|
+
const useStdin = prompt2.length >= PROMPT_LENGTH_THRESHOLD;
|
|
9502
|
+
if (!useStdin) {
|
|
9503
|
+
args.push(prompt2);
|
|
9504
|
+
}
|
|
9505
|
+
consola.start(`Launching Claude Code...
|
|
9506
|
+
`);
|
|
9507
|
+
const proc = Bun.spawn(args, {
|
|
9508
|
+
cwd: opts?.cwd ?? process.cwd(),
|
|
9509
|
+
stdout: "pipe",
|
|
9510
|
+
stderr: "inherit",
|
|
9511
|
+
stdin: useStdin ? "pipe" : "ignore"
|
|
9512
|
+
});
|
|
9513
|
+
if (useStdin) {
|
|
9514
|
+
proc.stdin.write(prompt2);
|
|
9515
|
+
proc.stdin.end();
|
|
9516
|
+
}
|
|
9517
|
+
const reader = proc.stdout.getReader();
|
|
9518
|
+
const decoder = new TextDecoder;
|
|
9519
|
+
let buffer = "";
|
|
9520
|
+
try {
|
|
9521
|
+
while (true) {
|
|
9522
|
+
const { done, value } = await reader.read();
|
|
9523
|
+
if (done)
|
|
9524
|
+
break;
|
|
9525
|
+
buffer += decoder.decode(value, { stream: true });
|
|
9526
|
+
const lines = buffer.split(`
|
|
9527
|
+
`);
|
|
9528
|
+
buffer = lines.pop() ?? "";
|
|
9529
|
+
for (const line of lines) {
|
|
9530
|
+
const trimmed = line.trim();
|
|
9531
|
+
if (!trimmed)
|
|
9532
|
+
continue;
|
|
9533
|
+
try {
|
|
9534
|
+
const event = JSON.parse(trimmed);
|
|
9535
|
+
const output = formatStreamEvent(event);
|
|
9536
|
+
if (output)
|
|
9537
|
+
process.stdout.write(output);
|
|
9538
|
+
} catch {}
|
|
9539
|
+
}
|
|
9540
|
+
}
|
|
9541
|
+
if (buffer.trim()) {
|
|
9542
|
+
try {
|
|
9543
|
+
const event = JSON.parse(buffer.trim());
|
|
9544
|
+
const output = formatStreamEvent(event);
|
|
9545
|
+
if (output)
|
|
9546
|
+
process.stdout.write(output);
|
|
9547
|
+
} catch {}
|
|
9548
|
+
}
|
|
9549
|
+
} catch {}
|
|
9550
|
+
const exitCode = await proc.exited;
|
|
9551
|
+
process.stdout.write(`
|
|
9552
|
+
`);
|
|
9553
|
+
return { exitCode };
|
|
9554
|
+
}
|
|
9555
|
+
|
|
9556
|
+
// src/storage/index.ts
|
|
9557
|
+
var import_yaml2 = __toESM(require_dist(), 1);
|
|
9558
|
+
import { join as join6 } from "path";
|
|
9559
|
+
import { existsSync as existsSync5 } from "fs";
|
|
9560
|
+
|
|
9561
|
+
// src/storage/fs-storage.ts
|
|
9562
|
+
import { join as join5 } from "path";
|
|
9563
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
9564
|
+
|
|
9565
|
+
class FsStorage {
|
|
9566
|
+
basePath;
|
|
9567
|
+
constructor(basePath = ".jdi/persistence") {
|
|
9568
|
+
this.basePath = basePath;
|
|
9569
|
+
}
|
|
9570
|
+
async load(key) {
|
|
9571
|
+
const filePath = join5(this.basePath, `${key}.md`);
|
|
9572
|
+
if (!existsSync4(filePath))
|
|
9573
|
+
return null;
|
|
9574
|
+
return Bun.file(filePath).text();
|
|
9575
|
+
}
|
|
9576
|
+
async save(key, content) {
|
|
9577
|
+
if (!existsSync4(this.basePath)) {
|
|
9578
|
+
mkdirSync2(this.basePath, { recursive: true });
|
|
9579
|
+
}
|
|
9580
|
+
const filePath = join5(this.basePath, `${key}.md`);
|
|
9581
|
+
await Bun.write(filePath, content);
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
|
|
9585
|
+
// src/storage/index.ts
|
|
9586
|
+
async function createStorage(cwd, config) {
|
|
9587
|
+
let adapter = config?.adapter ?? "fs";
|
|
9588
|
+
let basePath = config?.basePath;
|
|
9589
|
+
if (!config?.adapter && !config?.basePath) {
|
|
9590
|
+
const configPath = join6(cwd, ".jdi", "config", "jdi-config.yaml");
|
|
9591
|
+
if (existsSync5(configPath)) {
|
|
9592
|
+
const content = await Bun.file(configPath).text();
|
|
9593
|
+
const parsed = import_yaml2.parse(content);
|
|
9594
|
+
if (parsed?.storage?.adapter)
|
|
9595
|
+
adapter = parsed.storage.adapter;
|
|
9596
|
+
if (parsed?.storage?.base_path)
|
|
9597
|
+
basePath = parsed.storage.base_path;
|
|
9598
|
+
}
|
|
9599
|
+
}
|
|
9600
|
+
if (adapter === "fs") {
|
|
9601
|
+
const resolvedPath = basePath ? join6(cwd, basePath) : join6(cwd, ".jdi", "persistence");
|
|
9602
|
+
return new FsStorage(resolvedPath);
|
|
9603
|
+
}
|
|
9604
|
+
const adapterPath = join6(cwd, adapter);
|
|
9605
|
+
if (!existsSync5(adapterPath)) {
|
|
9606
|
+
throw new Error(`Storage adapter not found: ${adapterPath}
|
|
9607
|
+
` + `Set storage.adapter in .jdi/config/jdi-config.yaml to "fs" or a path to a custom adapter module.`);
|
|
9608
|
+
}
|
|
9609
|
+
try {
|
|
9610
|
+
const mod = await import(adapterPath);
|
|
9611
|
+
const AdapterClass = mod.default ?? mod.Storage ?? mod[Object.keys(mod)[0]];
|
|
9612
|
+
if (!AdapterClass || typeof AdapterClass !== "function") {
|
|
9613
|
+
throw new Error(`Storage adapter at ${adapterPath} must export a class as default export.`);
|
|
9614
|
+
}
|
|
9615
|
+
const instance = new AdapterClass({ basePath, cwd });
|
|
9616
|
+
if (typeof instance.load !== "function" || typeof instance.save !== "function") {
|
|
9617
|
+
throw new Error(`Storage adapter at ${adapterPath} must implement JediStorage (load and save methods).`);
|
|
9618
|
+
}
|
|
9619
|
+
return instance;
|
|
9620
|
+
} catch (err) {
|
|
9621
|
+
if (err.message?.includes("Storage adapter"))
|
|
9622
|
+
throw err;
|
|
9623
|
+
throw new Error(`Failed to load storage adapter from ${adapterPath}: ${err.message}`);
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
|
|
9627
|
+
// src/utils/storage-lifecycle.ts
|
|
9628
|
+
import { join as join7 } from "path";
|
|
9629
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
9630
|
+
async function loadPersistedState(cwd, storage) {
|
|
9631
|
+
let learningsPath = null;
|
|
9632
|
+
let codebaseIndexPath = null;
|
|
9633
|
+
const learnings = await storage.load("learnings");
|
|
9634
|
+
if (learnings) {
|
|
9635
|
+
const dir = join7(cwd, ".jdi", "framework", "learnings");
|
|
9636
|
+
if (!existsSync6(dir))
|
|
9637
|
+
mkdirSync3(dir, { recursive: true });
|
|
9638
|
+
learningsPath = join7(dir, "_consolidated.md");
|
|
9639
|
+
await Bun.write(learningsPath, learnings);
|
|
9640
|
+
}
|
|
9641
|
+
const codebaseIndex = await storage.load("codebase-index");
|
|
9642
|
+
if (codebaseIndex) {
|
|
9643
|
+
const dir = join7(cwd, ".jdi", "codebase");
|
|
9644
|
+
if (!existsSync6(dir))
|
|
9645
|
+
mkdirSync3(dir, { recursive: true });
|
|
9646
|
+
codebaseIndexPath = join7(dir, "INDEX.md");
|
|
9647
|
+
await Bun.write(codebaseIndexPath, codebaseIndex);
|
|
9648
|
+
}
|
|
9649
|
+
return { learningsPath, codebaseIndexPath };
|
|
9650
|
+
}
|
|
9651
|
+
async function savePersistedState(cwd, storage) {
|
|
9652
|
+
let learningsSaved = false;
|
|
9653
|
+
let codebaseIndexSaved = false;
|
|
9654
|
+
const learningsDir = join7(cwd, ".jdi", "framework", "learnings");
|
|
9655
|
+
if (existsSync6(learningsDir)) {
|
|
9656
|
+
const merged = await mergeLearningFiles(learningsDir);
|
|
9657
|
+
if (merged) {
|
|
9658
|
+
await storage.save("learnings", merged);
|
|
9659
|
+
learningsSaved = true;
|
|
9660
|
+
}
|
|
9661
|
+
}
|
|
9662
|
+
const indexPath = join7(cwd, ".jdi", "codebase", "INDEX.md");
|
|
9663
|
+
if (existsSync6(indexPath)) {
|
|
9664
|
+
const content = await Bun.file(indexPath).text();
|
|
9665
|
+
if (content.trim()) {
|
|
9666
|
+
await storage.save("codebase-index", content);
|
|
9667
|
+
codebaseIndexSaved = true;
|
|
9668
|
+
}
|
|
9669
|
+
}
|
|
9670
|
+
return { learningsSaved, codebaseIndexSaved };
|
|
9671
|
+
}
|
|
9672
|
+
async function mergeLearningFiles(dir) {
|
|
9673
|
+
const categories = ["general", "backend", "frontend", "testing", "devops"];
|
|
9674
|
+
const sections = [];
|
|
9675
|
+
for (const category of categories) {
|
|
9676
|
+
const filePath = join7(dir, `${category}.md`);
|
|
9677
|
+
if (!existsSync6(filePath))
|
|
9678
|
+
continue;
|
|
9679
|
+
const content = await Bun.file(filePath).text();
|
|
9680
|
+
const trimmed = content.trim();
|
|
9681
|
+
const lines = trimmed.split(`
|
|
9682
|
+
`).filter((l2) => l2.trim() && !l2.startsWith("#") && !l2.startsWith("<!--"));
|
|
9683
|
+
if (lines.length === 0)
|
|
9684
|
+
continue;
|
|
9685
|
+
sections.push(trimmed);
|
|
9686
|
+
}
|
|
9687
|
+
const consolidatedPath = join7(dir, "_consolidated.md");
|
|
9688
|
+
if (existsSync6(consolidatedPath)) {
|
|
9689
|
+
const content = await Bun.file(consolidatedPath).text();
|
|
9690
|
+
const trimmed = content.trim();
|
|
9691
|
+
if (trimmed && !sections.some((s2) => s2.includes(trimmed))) {
|
|
9692
|
+
sections.push(trimmed);
|
|
9693
|
+
}
|
|
9694
|
+
}
|
|
9695
|
+
return sections.length > 0 ? sections.join(`
|
|
9696
|
+
|
|
9697
|
+
---
|
|
9698
|
+
|
|
9699
|
+
`) : null;
|
|
9293
9700
|
}
|
|
9294
9701
|
|
|
9295
9702
|
// src/commands/plan.ts
|
|
9296
9703
|
var planCommand = defineCommand({
|
|
9297
9704
|
meta: {
|
|
9298
9705
|
name: "plan",
|
|
9299
|
-
description: "
|
|
9706
|
+
description: "Plan a feature using Claude Code"
|
|
9300
9707
|
},
|
|
9301
9708
|
args: {
|
|
9302
9709
|
description: {
|
|
@@ -9307,6 +9714,11 @@ var planCommand = defineCommand({
|
|
|
9307
9714
|
output: {
|
|
9308
9715
|
type: "string",
|
|
9309
9716
|
description: "Write prompt to file instead of stdout"
|
|
9717
|
+
},
|
|
9718
|
+
print: {
|
|
9719
|
+
type: "boolean",
|
|
9720
|
+
description: "Print the prompt to stdout instead of executing",
|
|
9721
|
+
default: false
|
|
9310
9722
|
}
|
|
9311
9723
|
},
|
|
9312
9724
|
async run({ args }) {
|
|
@@ -9336,29 +9748,61 @@ var planCommand = defineCommand({
|
|
|
9336
9748
|
if (args.output) {
|
|
9337
9749
|
await Bun.write(resolve2(cwd, args.output), prompt2);
|
|
9338
9750
|
consola.success(`Prompt written to ${args.output}`);
|
|
9339
|
-
} else {
|
|
9751
|
+
} else if (args.print) {
|
|
9340
9752
|
console.log(prompt2);
|
|
9753
|
+
} else {
|
|
9754
|
+
const storage = await createStorage(cwd);
|
|
9755
|
+
await loadPersistedState(cwd, storage);
|
|
9756
|
+
const { exitCode } = await spawnClaude(prompt2, { cwd });
|
|
9757
|
+
await savePersistedState(cwd, storage);
|
|
9758
|
+
if (exitCode !== 0) {
|
|
9759
|
+
consola.error(`Claude exited with code ${exitCode}`);
|
|
9760
|
+
process.exit(exitCode);
|
|
9761
|
+
}
|
|
9341
9762
|
}
|
|
9342
9763
|
}
|
|
9343
9764
|
});
|
|
9344
9765
|
|
|
9345
9766
|
// src/commands/implement.ts
|
|
9346
9767
|
import { resolve as resolve3 } from "path";
|
|
9768
|
+
|
|
9769
|
+
// src/utils/state.ts
|
|
9770
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
9771
|
+
import { join as join8 } from "path";
|
|
9772
|
+
import { existsSync as existsSync7 } from "fs";
|
|
9773
|
+
async function readState(cwd) {
|
|
9774
|
+
const statePath = join8(cwd, ".jdi", "config", "state.yaml");
|
|
9775
|
+
if (!existsSync7(statePath))
|
|
9776
|
+
return null;
|
|
9777
|
+
const content = await Bun.file(statePath).text();
|
|
9778
|
+
return import_yaml3.parse(content);
|
|
9779
|
+
}
|
|
9780
|
+
async function writeState(cwd, state) {
|
|
9781
|
+
const statePath = join8(cwd, ".jdi", "config", "state.yaml");
|
|
9782
|
+
await Bun.write(statePath, import_yaml3.stringify(state));
|
|
9783
|
+
}
|
|
9784
|
+
|
|
9785
|
+
// src/commands/implement.ts
|
|
9347
9786
|
var implementCommand = defineCommand({
|
|
9348
9787
|
meta: {
|
|
9349
9788
|
name: "implement",
|
|
9350
|
-
description: "
|
|
9789
|
+
description: "Execute an implementation plan using Claude Code"
|
|
9351
9790
|
},
|
|
9352
9791
|
args: {
|
|
9353
9792
|
plan: {
|
|
9354
9793
|
type: "positional",
|
|
9355
|
-
description: "Path to the PLAN.md file
|
|
9356
|
-
required:
|
|
9794
|
+
description: "Path to the PLAN.md file (auto-detected from state if omitted)",
|
|
9795
|
+
required: false
|
|
9357
9796
|
},
|
|
9358
9797
|
output: {
|
|
9359
9798
|
type: "string",
|
|
9360
9799
|
description: "Write prompt to file instead of stdout"
|
|
9361
9800
|
},
|
|
9801
|
+
print: {
|
|
9802
|
+
type: "boolean",
|
|
9803
|
+
description: "Print the prompt to stdout instead of executing",
|
|
9804
|
+
default: false
|
|
9805
|
+
},
|
|
9362
9806
|
team: {
|
|
9363
9807
|
type: "boolean",
|
|
9364
9808
|
description: "Force Agent Teams mode",
|
|
@@ -9372,6 +9816,16 @@ var implementCommand = defineCommand({
|
|
|
9372
9816
|
},
|
|
9373
9817
|
async run({ args }) {
|
|
9374
9818
|
const cwd = process.cwd();
|
|
9819
|
+
let planPath = args.plan;
|
|
9820
|
+
if (!planPath) {
|
|
9821
|
+
const state = await readState(cwd);
|
|
9822
|
+
planPath = state?.current_plan?.path ?? null;
|
|
9823
|
+
if (!planPath) {
|
|
9824
|
+
consola.error("No plan specified and no current plan found in state. Run `jdi plan` first or provide a plan path.");
|
|
9825
|
+
process.exit(1);
|
|
9826
|
+
}
|
|
9827
|
+
consola.info(`Using current plan: ${planPath}`);
|
|
9828
|
+
}
|
|
9375
9829
|
const baseProtocol = resolve3(cwd, ".jdi/framework/components/meta/AgentBase.md");
|
|
9376
9830
|
const complexityRouter = resolve3(cwd, ".jdi/framework/components/meta/ComplexityRouter.md");
|
|
9377
9831
|
const orchestration = resolve3(cwd, ".jdi/framework/components/meta/AgentTeamsOrchestration.md");
|
|
@@ -9394,7 +9848,7 @@ Override: --single (force single-agent mode)` : "";
|
|
|
9394
9848
|
`- Working directory: ${cwd}`,
|
|
9395
9849
|
``,
|
|
9396
9850
|
`## Task`,
|
|
9397
|
-
`Execute implementation plan: ${resolve3(cwd,
|
|
9851
|
+
`Execute implementation plan: ${resolve3(cwd, planPath)}${overrideFlag}`,
|
|
9398
9852
|
``,
|
|
9399
9853
|
`Follow the implement-plan orchestration:`,
|
|
9400
9854
|
`1. Read codebase context (.jdi/codebase/SUMMARY.md if exists)`,
|
|
@@ -9410,27 +9864,21 @@ Override: --single (force single-agent mode)` : "";
|
|
|
9410
9864
|
if (args.output) {
|
|
9411
9865
|
await Bun.write(resolve3(cwd, args.output), prompt2);
|
|
9412
9866
|
consola.success(`Prompt written to ${args.output}`);
|
|
9413
|
-
} else {
|
|
9867
|
+
} else if (args.print) {
|
|
9414
9868
|
console.log(prompt2);
|
|
9869
|
+
} else {
|
|
9870
|
+
const storage = await createStorage(cwd);
|
|
9871
|
+
await loadPersistedState(cwd, storage);
|
|
9872
|
+
const { exitCode } = await spawnClaude(prompt2, { cwd });
|
|
9873
|
+
await savePersistedState(cwd, storage);
|
|
9874
|
+
if (exitCode !== 0) {
|
|
9875
|
+
consola.error(`Claude exited with code ${exitCode}`);
|
|
9876
|
+
process.exit(exitCode);
|
|
9877
|
+
}
|
|
9415
9878
|
}
|
|
9416
9879
|
}
|
|
9417
9880
|
});
|
|
9418
9881
|
|
|
9419
|
-
// src/utils/state.ts
|
|
9420
|
-
import { join as join5 } from "path";
|
|
9421
|
-
import { existsSync as existsSync4 } from "fs";
|
|
9422
|
-
async function readState(cwd) {
|
|
9423
|
-
const statePath = join5(cwd, ".jdi", "config", "state.yaml");
|
|
9424
|
-
if (!existsSync4(statePath))
|
|
9425
|
-
return null;
|
|
9426
|
-
const content = await Bun.file(statePath).text();
|
|
9427
|
-
return $parse(content);
|
|
9428
|
-
}
|
|
9429
|
-
async function writeState(cwd, state) {
|
|
9430
|
-
const statePath = join5(cwd, ".jdi", "config", "state.yaml");
|
|
9431
|
-
await Bun.write(statePath, $stringify(state));
|
|
9432
|
-
}
|
|
9433
|
-
|
|
9434
9882
|
// src/commands/status.ts
|
|
9435
9883
|
var statusCommand = defineCommand({
|
|
9436
9884
|
meta: {
|
|
@@ -9478,15 +9926,15 @@ var statusCommand = defineCommand({
|
|
|
9478
9926
|
import { relative } from "path";
|
|
9479
9927
|
|
|
9480
9928
|
// src/utils/resolve-components.ts
|
|
9481
|
-
import { join as
|
|
9929
|
+
import { join as join9, basename } from "path";
|
|
9482
9930
|
import { homedir } from "os";
|
|
9483
9931
|
async function resolveComponents(cwd) {
|
|
9484
9932
|
const components = [];
|
|
9485
9933
|
const seen = new Set;
|
|
9486
9934
|
const sources = [
|
|
9487
|
-
{ dir:
|
|
9488
|
-
{ dir:
|
|
9489
|
-
{ dir:
|
|
9935
|
+
{ dir: join9(cwd, ".jdi", "framework", "components"), source: "project" },
|
|
9936
|
+
{ dir: join9(homedir(), ".jdi", "components"), source: "user" },
|
|
9937
|
+
{ dir: join9(import.meta.dir, "../framework/components"), source: "builtin" }
|
|
9490
9938
|
];
|
|
9491
9939
|
for (const { dir, source } of sources) {
|
|
9492
9940
|
try {
|
|
@@ -9495,7 +9943,7 @@ async function resolveComponents(cwd) {
|
|
|
9495
9943
|
const name = basename(file, ".md");
|
|
9496
9944
|
if (!seen.has(name)) {
|
|
9497
9945
|
seen.add(name);
|
|
9498
|
-
components.push({ name, path:
|
|
9946
|
+
components.push({ name, path: join9(dir, file), source });
|
|
9499
9947
|
}
|
|
9500
9948
|
}
|
|
9501
9949
|
} catch {}
|
|
@@ -9523,51 +9971,6 @@ var componentsCommand = defineCommand({
|
|
|
9523
9971
|
}
|
|
9524
9972
|
});
|
|
9525
9973
|
|
|
9526
|
-
// src/utils/git.ts
|
|
9527
|
-
async function exec(cmd, cwd) {
|
|
9528
|
-
const proc = Bun.spawn(cmd, {
|
|
9529
|
-
cwd: cwd ?? process.cwd(),
|
|
9530
|
-
stdout: "pipe",
|
|
9531
|
-
stderr: "pipe"
|
|
9532
|
-
});
|
|
9533
|
-
const stdout2 = await new Response(proc.stdout).text();
|
|
9534
|
-
const exitCode = await proc.exited;
|
|
9535
|
-
return { stdout: stdout2.trim(), exitCode };
|
|
9536
|
-
}
|
|
9537
|
-
async function gitDiffNames(staged) {
|
|
9538
|
-
const args = ["git", "diff", "--name-only"];
|
|
9539
|
-
if (staged)
|
|
9540
|
-
args.push("--cached");
|
|
9541
|
-
const { stdout: stdout2 } = await exec(args);
|
|
9542
|
-
return stdout2 ? stdout2.split(`
|
|
9543
|
-
`) : [];
|
|
9544
|
-
}
|
|
9545
|
-
async function gitLog(range) {
|
|
9546
|
-
const args = ["git", "log", "--oneline"];
|
|
9547
|
-
if (range)
|
|
9548
|
-
args.push(range);
|
|
9549
|
-
else
|
|
9550
|
-
args.push("-20");
|
|
9551
|
-
const { stdout: stdout2 } = await exec(args);
|
|
9552
|
-
return stdout2;
|
|
9553
|
-
}
|
|
9554
|
-
async function gitBranch() {
|
|
9555
|
-
const { stdout: stdout2 } = await exec(["git", "rev-parse", "--abbrev-ref", "HEAD"]);
|
|
9556
|
-
return stdout2;
|
|
9557
|
-
}
|
|
9558
|
-
async function gitRoot() {
|
|
9559
|
-
const { stdout: stdout2 } = await exec(["git", "rev-parse", "--show-toplevel"]);
|
|
9560
|
-
return stdout2;
|
|
9561
|
-
}
|
|
9562
|
-
async function gitStatus() {
|
|
9563
|
-
const { stdout: stdout2 } = await exec(["git", "status", "--porcelain"]);
|
|
9564
|
-
return stdout2;
|
|
9565
|
-
}
|
|
9566
|
-
async function gitMergeBase(branch) {
|
|
9567
|
-
const { stdout: stdout2 } = await exec(["git", "merge-base", "HEAD", branch]);
|
|
9568
|
-
return stdout2;
|
|
9569
|
-
}
|
|
9570
|
-
|
|
9571
9974
|
// src/commands/commit.ts
|
|
9572
9975
|
import { dirname as dirname2 } from "path";
|
|
9573
9976
|
function detectScope(files) {
|
|
@@ -9686,8 +10089,8 @@ Use --all to stage and commit all, or stage files manually.`);
|
|
|
9686
10089
|
});
|
|
9687
10090
|
|
|
9688
10091
|
// src/commands/pr.ts
|
|
9689
|
-
import { existsSync as
|
|
9690
|
-
import { join as
|
|
10092
|
+
import { existsSync as existsSync8 } from "fs";
|
|
10093
|
+
import { join as join10 } from "path";
|
|
9691
10094
|
async function hasGhCli() {
|
|
9692
10095
|
const { exitCode } = await exec(["which", "gh"]);
|
|
9693
10096
|
return exitCode === 0;
|
|
@@ -9737,8 +10140,8 @@ var prCommand = defineCommand({
|
|
|
9737
10140
|
|
|
9738
10141
|
**Plan:** ${state.position.plan_name}` : "";
|
|
9739
10142
|
let template = "";
|
|
9740
|
-
const templatePath =
|
|
9741
|
-
if (
|
|
10143
|
+
const templatePath = join10(cwd, ".github", "pull_request_template.md");
|
|
10144
|
+
if (existsSync8(templatePath)) {
|
|
9742
10145
|
template = await Bun.file(templatePath).text();
|
|
9743
10146
|
}
|
|
9744
10147
|
const title = branch.replace(/^(feat|fix|chore|docs|refactor|test|ci)\//, "").replace(/[-_]/g, " ").replace(/^\w/, (c3) => c3.toUpperCase());
|
|
@@ -9791,7 +10194,7 @@ import { resolve as resolve5 } from "path";
|
|
|
9791
10194
|
var reviewCommand = defineCommand({
|
|
9792
10195
|
meta: {
|
|
9793
10196
|
name: "review",
|
|
9794
|
-
description: "
|
|
10197
|
+
description: "Review a PR using Claude Code"
|
|
9795
10198
|
},
|
|
9796
10199
|
args: {
|
|
9797
10200
|
pr: {
|
|
@@ -9802,6 +10205,11 @@ var reviewCommand = defineCommand({
|
|
|
9802
10205
|
output: {
|
|
9803
10206
|
type: "string",
|
|
9804
10207
|
description: "Write prompt to file instead of stdout"
|
|
10208
|
+
},
|
|
10209
|
+
print: {
|
|
10210
|
+
type: "boolean",
|
|
10211
|
+
description: "Print the prompt to stdout instead of executing",
|
|
10212
|
+
default: false
|
|
9805
10213
|
}
|
|
9806
10214
|
},
|
|
9807
10215
|
async run({ args }) {
|
|
@@ -9878,8 +10286,14 @@ ${data.body}` : ""
|
|
|
9878
10286
|
if (args.output) {
|
|
9879
10287
|
await Bun.write(resolve5(process.cwd(), args.output), prompt2);
|
|
9880
10288
|
consola.success(`Review prompt written to ${args.output}`);
|
|
9881
|
-
} else {
|
|
10289
|
+
} else if (args.print) {
|
|
9882
10290
|
console.log(prompt2);
|
|
10291
|
+
} else {
|
|
10292
|
+
const { exitCode } = await spawnClaude(prompt2, { cwd: process.cwd() });
|
|
10293
|
+
if (exitCode !== 0) {
|
|
10294
|
+
consola.error(`Claude exited with code ${exitCode}`);
|
|
10295
|
+
process.exit(exitCode);
|
|
10296
|
+
}
|
|
9883
10297
|
}
|
|
9884
10298
|
}
|
|
9885
10299
|
});
|
|
@@ -10013,7 +10427,7 @@ import { resolve as resolve6 } from "path";
|
|
|
10013
10427
|
var quickCommand = defineCommand({
|
|
10014
10428
|
meta: {
|
|
10015
10429
|
name: "quick",
|
|
10016
|
-
description: "
|
|
10430
|
+
description: "Make a focused change using Claude Code"
|
|
10017
10431
|
},
|
|
10018
10432
|
args: {
|
|
10019
10433
|
description: {
|
|
@@ -10024,6 +10438,11 @@ var quickCommand = defineCommand({
|
|
|
10024
10438
|
output: {
|
|
10025
10439
|
type: "string",
|
|
10026
10440
|
description: "Write prompt to file instead of stdout"
|
|
10441
|
+
},
|
|
10442
|
+
print: {
|
|
10443
|
+
type: "boolean",
|
|
10444
|
+
description: "Print the prompt to stdout instead of executing",
|
|
10445
|
+
default: false
|
|
10027
10446
|
}
|
|
10028
10447
|
},
|
|
10029
10448
|
async run({ args }) {
|
|
@@ -10057,14 +10476,20 @@ var quickCommand = defineCommand({
|
|
|
10057
10476
|
if (args.output) {
|
|
10058
10477
|
await Bun.write(resolve6(cwd, args.output), prompt2);
|
|
10059
10478
|
consola.success(`Prompt written to ${args.output}`);
|
|
10060
|
-
} else {
|
|
10479
|
+
} else if (args.print) {
|
|
10061
10480
|
console.log(prompt2);
|
|
10481
|
+
} else {
|
|
10482
|
+
const { exitCode } = await spawnClaude(prompt2, { cwd });
|
|
10483
|
+
if (exitCode !== 0) {
|
|
10484
|
+
consola.error(`Claude exited with code ${exitCode}`);
|
|
10485
|
+
process.exit(exitCode);
|
|
10486
|
+
}
|
|
10062
10487
|
}
|
|
10063
10488
|
}
|
|
10064
10489
|
});
|
|
10065
10490
|
|
|
10066
10491
|
// src/commands/worktree.ts
|
|
10067
|
-
import { existsSync as
|
|
10492
|
+
import { existsSync as existsSync9 } from "fs";
|
|
10068
10493
|
function slugify(name) {
|
|
10069
10494
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
10070
10495
|
}
|
|
@@ -10098,7 +10523,7 @@ var worktreeCommand = defineCommand({
|
|
|
10098
10523
|
}
|
|
10099
10524
|
const slug = slugify(args.name);
|
|
10100
10525
|
const worktreePath = `${root}/.worktrees/${slug}`;
|
|
10101
|
-
if (
|
|
10526
|
+
if (existsSync9(worktreePath)) {
|
|
10102
10527
|
consola.error(`Worktree already exists at ${worktreePath}`);
|
|
10103
10528
|
return;
|
|
10104
10529
|
}
|
|
@@ -10171,7 +10596,6 @@ var worktreeCommand = defineCommand({
|
|
|
10171
10596
|
consola.info(` cd ${worktreePath}`);
|
|
10172
10597
|
}
|
|
10173
10598
|
});
|
|
10174
|
-
|
|
10175
10599
|
// src/commands/worktree-remove.ts
|
|
10176
10600
|
var worktreeRemoveCommand = defineCommand({
|
|
10177
10601
|
meta: {
|
|
@@ -10265,7 +10689,7 @@ Specify a worktree name: jdi worktree-remove <name>`);
|
|
|
10265
10689
|
|
|
10266
10690
|
// src/commands/plan-review.ts
|
|
10267
10691
|
import { resolve as resolve7 } from "path";
|
|
10268
|
-
import { existsSync as
|
|
10692
|
+
import { existsSync as existsSync10 } from "fs";
|
|
10269
10693
|
function parsePlanSummary(content) {
|
|
10270
10694
|
const nameMatch = content.match(/^# .+?: (.+)$/m);
|
|
10271
10695
|
const name = nameMatch?.[1] ?? "Unknown";
|
|
@@ -10304,7 +10728,7 @@ var planReviewCommand = defineCommand({
|
|
|
10304
10728
|
consola.error("No plan found. Run `jdi plan` first.");
|
|
10305
10729
|
return;
|
|
10306
10730
|
}
|
|
10307
|
-
if (!
|
|
10731
|
+
if (!existsSync10(planPath)) {
|
|
10308
10732
|
consola.error(`Plan not found: ${planPath}`);
|
|
10309
10733
|
return;
|
|
10310
10734
|
}
|
|
@@ -10394,7 +10818,7 @@ Tasks (${tasks.length}):`);
|
|
|
10394
10818
|
|
|
10395
10819
|
// src/commands/plan-approve.ts
|
|
10396
10820
|
import { resolve as resolve8 } from "path";
|
|
10397
|
-
import { existsSync as
|
|
10821
|
+
import { existsSync as existsSync11 } from "fs";
|
|
10398
10822
|
var planApproveCommand = defineCommand({
|
|
10399
10823
|
meta: {
|
|
10400
10824
|
name: "plan-approve",
|
|
@@ -10423,7 +10847,7 @@ var planApproveCommand = defineCommand({
|
|
|
10423
10847
|
consola.error("No plan to approve. Run `jdi plan` first.");
|
|
10424
10848
|
return;
|
|
10425
10849
|
}
|
|
10426
|
-
if (!
|
|
10850
|
+
if (!existsSync11(planPath)) {
|
|
10427
10851
|
consola.error(`Plan not found: ${planPath}`);
|
|
10428
10852
|
return;
|
|
10429
10853
|
}
|
|
@@ -10447,15 +10871,576 @@ var planApproveCommand = defineCommand({
|
|
|
10447
10871
|
consola.info("Say 'implement this' in Claude Code or run `/jdi:implement-plan` to execute.");
|
|
10448
10872
|
}
|
|
10449
10873
|
});
|
|
10874
|
+
|
|
10875
|
+
// src/commands/action.ts
|
|
10876
|
+
import { resolve as resolve9 } from "path";
|
|
10877
|
+
|
|
10878
|
+
// src/utils/clickup.ts
|
|
10879
|
+
var CLICKUP_URL_PATTERNS = [
|
|
10880
|
+
/app\.clickup\.com\/t\/([a-z0-9]+)/i,
|
|
10881
|
+
/sharing\.clickup\.com\/t\/([a-z0-9]+)/i,
|
|
10882
|
+
/clickup\.com\/t\/([a-z0-9]+)/i
|
|
10883
|
+
];
|
|
10884
|
+
function extractClickUpId(text) {
|
|
10885
|
+
for (const pattern of CLICKUP_URL_PATTERNS) {
|
|
10886
|
+
const match = text.match(pattern);
|
|
10887
|
+
if (match)
|
|
10888
|
+
return match[1];
|
|
10889
|
+
}
|
|
10890
|
+
return null;
|
|
10891
|
+
}
|
|
10892
|
+
async function fetchClickUpTicket(taskId) {
|
|
10893
|
+
const token = process.env.CLICKUP_API_TOKEN;
|
|
10894
|
+
if (!token)
|
|
10895
|
+
return null;
|
|
10896
|
+
try {
|
|
10897
|
+
const res = await fetch(`https://api.clickup.com/api/v2/task/${taskId}`, { headers: { Authorization: token } });
|
|
10898
|
+
if (!res.ok)
|
|
10899
|
+
return null;
|
|
10900
|
+
const data = await res.json();
|
|
10901
|
+
const acceptanceCriteria = [];
|
|
10902
|
+
if (data.checklists) {
|
|
10903
|
+
for (const checklist of data.checklists) {
|
|
10904
|
+
for (const item of checklist.items ?? []) {
|
|
10905
|
+
acceptanceCriteria.push(item.name);
|
|
10906
|
+
}
|
|
10907
|
+
}
|
|
10908
|
+
}
|
|
10909
|
+
const subtasks = (data.subtasks ?? []).map((st) => ({
|
|
10910
|
+
name: st.name,
|
|
10911
|
+
status: st.status?.status ?? "unknown"
|
|
10912
|
+
}));
|
|
10913
|
+
const priorityMap = {
|
|
10914
|
+
1: "urgent",
|
|
10915
|
+
2: "high",
|
|
10916
|
+
3: "normal",
|
|
10917
|
+
4: "low"
|
|
10918
|
+
};
|
|
10919
|
+
return {
|
|
10920
|
+
id: data.id,
|
|
10921
|
+
name: data.name,
|
|
10922
|
+
description: data.description ?? "",
|
|
10923
|
+
status: data.status?.status ?? "unknown",
|
|
10924
|
+
priority: priorityMap[data.priority?.id] ?? "normal",
|
|
10925
|
+
acceptanceCriteria,
|
|
10926
|
+
subtasks
|
|
10927
|
+
};
|
|
10928
|
+
} catch {
|
|
10929
|
+
return null;
|
|
10930
|
+
}
|
|
10931
|
+
}
|
|
10932
|
+
function formatTicketAsContext(ticket) {
|
|
10933
|
+
const lines = [
|
|
10934
|
+
`## ClickUp Ticket: ${ticket.name}`,
|
|
10935
|
+
`- **ID:** ${ticket.id}`,
|
|
10936
|
+
`- **Status:** ${ticket.status}`,
|
|
10937
|
+
`- **Priority:** ${ticket.priority}`,
|
|
10938
|
+
``,
|
|
10939
|
+
`### Description`,
|
|
10940
|
+
ticket.description || "_No description_"
|
|
10941
|
+
];
|
|
10942
|
+
if (ticket.acceptanceCriteria.length > 0) {
|
|
10943
|
+
lines.push(``, `### Acceptance Criteria`);
|
|
10944
|
+
for (const ac of ticket.acceptanceCriteria) {
|
|
10945
|
+
lines.push(`- [ ] ${ac}`);
|
|
10946
|
+
}
|
|
10947
|
+
}
|
|
10948
|
+
if (ticket.subtasks.length > 0) {
|
|
10949
|
+
lines.push(``, `### Subtasks`);
|
|
10950
|
+
for (const st of ticket.subtasks) {
|
|
10951
|
+
const check = st.status === "complete" ? "x" : " ";
|
|
10952
|
+
lines.push(`- [${check}] ${st.name}`);
|
|
10953
|
+
}
|
|
10954
|
+
}
|
|
10955
|
+
return lines.join(`
|
|
10956
|
+
`);
|
|
10957
|
+
}
|
|
10958
|
+
|
|
10959
|
+
// src/utils/github.ts
|
|
10960
|
+
async function postGitHubComment(repo, issueNumber, body) {
|
|
10961
|
+
await exec([
|
|
10962
|
+
"gh",
|
|
10963
|
+
"api",
|
|
10964
|
+
`repos/${repo}/issues/${issueNumber}/comments`,
|
|
10965
|
+
"-f",
|
|
10966
|
+
`body=${body}`
|
|
10967
|
+
]);
|
|
10968
|
+
}
|
|
10969
|
+
async function reactToComment(repo, commentId, reaction) {
|
|
10970
|
+
await exec([
|
|
10971
|
+
"gh",
|
|
10972
|
+
"api",
|
|
10973
|
+
`repos/${repo}/issues/comments/${commentId}/reactions`,
|
|
10974
|
+
"-f",
|
|
10975
|
+
`content=${reaction}`
|
|
10976
|
+
]);
|
|
10977
|
+
}
|
|
10978
|
+
async function fetchCommentThread(repo, issueNumber) {
|
|
10979
|
+
const { stdout: stdout2, exitCode } = await exec([
|
|
10980
|
+
"gh",
|
|
10981
|
+
"api",
|
|
10982
|
+
`repos/${repo}/issues/${issueNumber}/comments`,
|
|
10983
|
+
"--paginate",
|
|
10984
|
+
"--jq",
|
|
10985
|
+
`.[] | {id: .id, author: .user.login, body: .body, createdAt: .created_at}`
|
|
10986
|
+
]);
|
|
10987
|
+
if (exitCode !== 0 || !stdout2.trim())
|
|
10988
|
+
return [];
|
|
10989
|
+
const comments = [];
|
|
10990
|
+
for (const line of stdout2.trim().split(`
|
|
10991
|
+
`)) {
|
|
10992
|
+
if (!line.trim())
|
|
10993
|
+
continue;
|
|
10994
|
+
try {
|
|
10995
|
+
const parsed = JSON.parse(line);
|
|
10996
|
+
comments.push({
|
|
10997
|
+
id: parsed.id,
|
|
10998
|
+
author: parsed.author,
|
|
10999
|
+
body: parsed.body,
|
|
11000
|
+
createdAt: parsed.createdAt,
|
|
11001
|
+
isJedi: parsed.body.includes("Powered by [@benzotti/jedi]") || parsed.body.includes("Jedi plan") || parsed.body.includes("Jedi quick") || parsed.body.includes("Jedi implement")
|
|
11002
|
+
});
|
|
11003
|
+
} catch {}
|
|
11004
|
+
}
|
|
11005
|
+
return comments;
|
|
11006
|
+
}
|
|
11007
|
+
function buildConversationContext(thread, currentCommentId) {
|
|
11008
|
+
const jediSegments = [];
|
|
11009
|
+
let inJediConversation = false;
|
|
11010
|
+
for (const comment of thread) {
|
|
11011
|
+
if (comment.id === currentCommentId)
|
|
11012
|
+
break;
|
|
11013
|
+
if (comment.body.includes("@jedi")) {
|
|
11014
|
+
inJediConversation = true;
|
|
11015
|
+
jediSegments.push(comment);
|
|
11016
|
+
} else if (inJediConversation) {
|
|
11017
|
+
jediSegments.push(comment);
|
|
11018
|
+
if (comment.isJedi) {}
|
|
11019
|
+
}
|
|
11020
|
+
}
|
|
11021
|
+
const previousJediRuns = jediSegments.filter((c3) => c3.isJedi).length;
|
|
11022
|
+
const isFollowUp = previousJediRuns > 0;
|
|
11023
|
+
if (jediSegments.length === 0) {
|
|
11024
|
+
return { history: "", previousJediRuns: 0, isFollowUp: false };
|
|
11025
|
+
}
|
|
11026
|
+
const lines = ["## Previous Conversation", ""];
|
|
11027
|
+
for (const comment of jediSegments) {
|
|
11028
|
+
const role = comment.isJedi ? "Jedi" : `@${comment.author}`;
|
|
11029
|
+
let body = comment.body;
|
|
11030
|
+
if (comment.isJedi && body.length > 2000) {
|
|
11031
|
+
body = body.slice(0, 2000) + `
|
|
11032
|
+
|
|
11033
|
+
... (truncated)`;
|
|
11034
|
+
}
|
|
11035
|
+
lines.push(`**${role}** (${comment.createdAt}):`);
|
|
11036
|
+
lines.push(body);
|
|
11037
|
+
lines.push("");
|
|
11038
|
+
}
|
|
11039
|
+
return { history: lines.join(`
|
|
11040
|
+
`), previousJediRuns, isFollowUp };
|
|
11041
|
+
}
|
|
11042
|
+
function formatResultComment(command, success, summary) {
|
|
11043
|
+
const status = success ? "Completed" : "Failed";
|
|
11044
|
+
const icon = success ? "white_check_mark" : "x";
|
|
11045
|
+
return [
|
|
11046
|
+
`### :${icon}: Jedi ${command} \u2014 ${status}`,
|
|
11047
|
+
``,
|
|
11048
|
+
`<details>`,
|
|
11049
|
+
`<summary>Details</summary>`,
|
|
11050
|
+
``,
|
|
11051
|
+
summary,
|
|
11052
|
+
``,
|
|
11053
|
+
`</details>`,
|
|
11054
|
+
``,
|
|
11055
|
+
`---`,
|
|
11056
|
+
`_Powered by [@benzotti/jedi](https://github.com/zottiben/jedi)_`
|
|
11057
|
+
].join(`
|
|
11058
|
+
`);
|
|
11059
|
+
}
|
|
11060
|
+
|
|
11061
|
+
// src/commands/action.ts
|
|
11062
|
+
function parseComment(comment, isFollowUp) {
|
|
11063
|
+
const match = comment.match(/@jedi\s+(.+)/is);
|
|
11064
|
+
if (!match) {
|
|
11065
|
+
if (isFollowUp) {
|
|
11066
|
+
return {
|
|
11067
|
+
command: "plan",
|
|
11068
|
+
description: comment.trim(),
|
|
11069
|
+
clickUpUrl: null,
|
|
11070
|
+
fullFlow: false,
|
|
11071
|
+
isFeedback: true
|
|
11072
|
+
};
|
|
11073
|
+
}
|
|
11074
|
+
return null;
|
|
11075
|
+
}
|
|
11076
|
+
const body = match[1].trim();
|
|
11077
|
+
const clickUpMatch = body.match(/(https?:\/\/[^\s]*clickup\.com\/t\/[a-z0-9]+)/i);
|
|
11078
|
+
const clickUpUrl = clickUpMatch ? clickUpMatch[1] : null;
|
|
11079
|
+
const description = body.replace(/(https?:\/\/[^\s]*clickup\.com\/t\/[a-z0-9]+)/i, "").replace(/\s+/g, " ").trim();
|
|
11080
|
+
const lower = body.toLowerCase();
|
|
11081
|
+
if (lower.startsWith("plan ")) {
|
|
11082
|
+
return { command: "plan", description, clickUpUrl, fullFlow: false, isFeedback: false };
|
|
11083
|
+
}
|
|
11084
|
+
if (lower.startsWith("implement")) {
|
|
11085
|
+
return { command: "implement", description, clickUpUrl, fullFlow: false, isFeedback: false };
|
|
11086
|
+
}
|
|
11087
|
+
if (lower.startsWith("quick ")) {
|
|
11088
|
+
return { command: "quick", description, clickUpUrl, fullFlow: false, isFeedback: false };
|
|
11089
|
+
}
|
|
11090
|
+
if (lower.startsWith("review")) {
|
|
11091
|
+
return { command: "review", description, clickUpUrl, fullFlow: false, isFeedback: false };
|
|
11092
|
+
}
|
|
11093
|
+
if (lower.startsWith("feedback")) {
|
|
11094
|
+
return { command: "feedback", description, clickUpUrl, fullFlow: false, isFeedback: false };
|
|
11095
|
+
}
|
|
11096
|
+
if (lower.startsWith("do ")) {
|
|
11097
|
+
if (clickUpUrl) {
|
|
11098
|
+
return { command: "plan", description, clickUpUrl, fullFlow: true, isFeedback: false };
|
|
11099
|
+
}
|
|
11100
|
+
return { command: "quick", description, clickUpUrl, fullFlow: false, isFeedback: false };
|
|
11101
|
+
}
|
|
11102
|
+
if (/^(approved?|lgtm|looks?\s*good|ship\s*it)/i.test(lower)) {
|
|
11103
|
+
return {
|
|
11104
|
+
command: "plan",
|
|
11105
|
+
description: body,
|
|
11106
|
+
clickUpUrl: null,
|
|
11107
|
+
fullFlow: false,
|
|
11108
|
+
isFeedback: true
|
|
11109
|
+
};
|
|
11110
|
+
}
|
|
11111
|
+
if (isFollowUp) {
|
|
11112
|
+
return {
|
|
11113
|
+
command: "plan",
|
|
11114
|
+
description: body,
|
|
11115
|
+
clickUpUrl: null,
|
|
11116
|
+
fullFlow: false,
|
|
11117
|
+
isFeedback: true
|
|
11118
|
+
};
|
|
11119
|
+
}
|
|
11120
|
+
return { command: "plan", description, clickUpUrl, fullFlow: false, isFeedback: false };
|
|
11121
|
+
}
|
|
11122
|
+
var actionCommand = defineCommand({
|
|
11123
|
+
meta: {
|
|
11124
|
+
name: "action",
|
|
11125
|
+
description: "GitHub Action entry point \u2014 parse @jedi comment and run workflow"
|
|
11126
|
+
},
|
|
11127
|
+
args: {
|
|
11128
|
+
comment: {
|
|
11129
|
+
type: "positional",
|
|
11130
|
+
description: "The raw comment body containing @jedi mention",
|
|
11131
|
+
required: true
|
|
11132
|
+
},
|
|
11133
|
+
"comment-id": {
|
|
11134
|
+
type: "string",
|
|
11135
|
+
description: "GitHub comment ID for reactions"
|
|
11136
|
+
},
|
|
11137
|
+
"pr-number": {
|
|
11138
|
+
type: "string",
|
|
11139
|
+
description: "PR number (if triggered from a PR)"
|
|
11140
|
+
},
|
|
11141
|
+
"issue-number": {
|
|
11142
|
+
type: "string",
|
|
11143
|
+
description: "Issue number (if triggered from an issue)"
|
|
11144
|
+
},
|
|
11145
|
+
repo: {
|
|
11146
|
+
type: "string",
|
|
11147
|
+
description: "Repository in owner/repo format"
|
|
11148
|
+
}
|
|
11149
|
+
},
|
|
11150
|
+
async run({ args }) {
|
|
11151
|
+
const cwd = process.cwd();
|
|
11152
|
+
const repo = args.repo ?? process.env.GITHUB_REPOSITORY;
|
|
11153
|
+
const commentId = args["comment-id"] ? Number(args["comment-id"]) : null;
|
|
11154
|
+
const issueNumber = Number(args["pr-number"] ?? args["issue-number"] ?? 0);
|
|
11155
|
+
let conversationHistory = "";
|
|
11156
|
+
let isFollowUp = false;
|
|
11157
|
+
if (repo && issueNumber) {
|
|
11158
|
+
const thread = await fetchCommentThread(repo, issueNumber);
|
|
11159
|
+
const context = buildConversationContext(thread, commentId ?? 0);
|
|
11160
|
+
conversationHistory = context.history;
|
|
11161
|
+
isFollowUp = context.isFollowUp;
|
|
11162
|
+
if (isFollowUp) {
|
|
11163
|
+
consola.info(`Continuing conversation (${context.previousJediRuns} previous Jedi run(s))`);
|
|
11164
|
+
}
|
|
11165
|
+
}
|
|
11166
|
+
const intent = parseComment(args.comment, isFollowUp);
|
|
11167
|
+
if (!intent) {
|
|
11168
|
+
consola.error("Could not parse @jedi intent from comment");
|
|
11169
|
+
process.exit(1);
|
|
11170
|
+
}
|
|
11171
|
+
consola.info(`Parsed intent: ${intent.isFeedback ? "feedback on previous" : intent.command}${intent.fullFlow ? " (full flow)" : ""}`);
|
|
11172
|
+
if (repo && commentId) {
|
|
11173
|
+
await reactToComment(repo, commentId, "eyes").catch(() => {});
|
|
11174
|
+
}
|
|
11175
|
+
const storage = await createStorage(cwd);
|
|
11176
|
+
const { learningsPath, codebaseIndexPath } = await loadPersistedState(cwd, storage);
|
|
11177
|
+
let ticketContext = "";
|
|
11178
|
+
if (intent.clickUpUrl) {
|
|
11179
|
+
const taskId = extractClickUpId(intent.clickUpUrl);
|
|
11180
|
+
if (taskId) {
|
|
11181
|
+
consola.info(`Fetching ClickUp ticket: ${taskId}`);
|
|
11182
|
+
const ticket = await fetchClickUpTicket(taskId);
|
|
11183
|
+
if (ticket) {
|
|
11184
|
+
ticketContext = formatTicketAsContext(ticket);
|
|
11185
|
+
consola.success(`Loaded ticket: ${ticket.name}`);
|
|
11186
|
+
}
|
|
11187
|
+
}
|
|
11188
|
+
}
|
|
11189
|
+
const projectType = await detectProjectType(cwd);
|
|
11190
|
+
const adapter = await readAdapter(cwd);
|
|
11191
|
+
const techStack = adapter?.tech_stack ? Object.entries(adapter.tech_stack).map(([k2, v2]) => `${k2}: ${v2}`).join(", ") : projectType;
|
|
11192
|
+
const contextLines = [
|
|
11193
|
+
`## Project Context`,
|
|
11194
|
+
`- Type: ${projectType}`,
|
|
11195
|
+
`- Tech stack: ${techStack}`,
|
|
11196
|
+
`- Working directory: ${cwd}`
|
|
11197
|
+
];
|
|
11198
|
+
if (learningsPath) {
|
|
11199
|
+
contextLines.push(`- Learnings: ${learningsPath}`);
|
|
11200
|
+
}
|
|
11201
|
+
if (codebaseIndexPath) {
|
|
11202
|
+
contextLines.push(`- Codebase index: ${codebaseIndexPath}`);
|
|
11203
|
+
}
|
|
11204
|
+
if (ticketContext) {
|
|
11205
|
+
contextLines.push(``, ticketContext);
|
|
11206
|
+
}
|
|
11207
|
+
const baseProtocol = resolve9(cwd, ".jdi/framework/components/meta/AgentBase.md");
|
|
11208
|
+
let prompt2;
|
|
11209
|
+
if (intent.isFeedback) {
|
|
11210
|
+
prompt2 = [
|
|
11211
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11212
|
+
``,
|
|
11213
|
+
...contextLines,
|
|
11214
|
+
``,
|
|
11215
|
+
conversationHistory,
|
|
11216
|
+
``,
|
|
11217
|
+
`## Current Feedback`,
|
|
11218
|
+
`The user has provided feedback on previous Jedi work:`,
|
|
11219
|
+
``,
|
|
11220
|
+
`> ${intent.description}`,
|
|
11221
|
+
``,
|
|
11222
|
+
`## Instructions`,
|
|
11223
|
+
`Review the previous conversation above. The user is iterating on work Jedi already started.`,
|
|
11224
|
+
``,
|
|
11225
|
+
`- If the feedback is an **approval** ("approved", "lgtm", "looks good", "ship it"), finalise the current work \u2014 create commits and/or a PR as appropriate.`,
|
|
11226
|
+
`- If the feedback is a **refinement** ("change task 2", "use a different approach", "add error handling"), apply the requested changes to the existing plan or implementation. Present an updated summary when done.`,
|
|
11227
|
+
`- If the feedback is a **question**, answer it with full context from the conversation.`,
|
|
11228
|
+
``,
|
|
11229
|
+
`Read state.yaml and any existing plan files to understand what was previously done. Apply changes incrementally \u2014 do not restart from scratch.`
|
|
11230
|
+
].join(`
|
|
11231
|
+
`);
|
|
11232
|
+
} else {
|
|
11233
|
+
const agentSpec = resolve9(cwd, `.jdi/framework/agents/jdi-planner.md`);
|
|
11234
|
+
const historyBlock = conversationHistory ? `
|
|
11235
|
+
${conversationHistory}
|
|
11236
|
+
|
|
11237
|
+
The above is prior conversation on this issue for context.
|
|
11238
|
+
` : "";
|
|
11239
|
+
switch (intent.command) {
|
|
11240
|
+
case "plan":
|
|
11241
|
+
prompt2 = [
|
|
11242
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11243
|
+
`You are jdi-planner. Read ${agentSpec} for your full specification.`,
|
|
11244
|
+
``,
|
|
11245
|
+
...contextLines,
|
|
11246
|
+
historyBlock,
|
|
11247
|
+
`## Task`,
|
|
11248
|
+
`Create an implementation plan for: ${intent.description}`,
|
|
11249
|
+
ticketContext ? `
|
|
11250
|
+
Use the ClickUp ticket above as the primary requirements source.` : ``,
|
|
11251
|
+
``,
|
|
11252
|
+
`Follow the planning workflow in your spec. Present the plan summary when complete and ask for feedback. The user will respond via another GitHub comment.`
|
|
11253
|
+
].join(`
|
|
11254
|
+
`);
|
|
11255
|
+
break;
|
|
11256
|
+
case "implement":
|
|
11257
|
+
prompt2 = [
|
|
11258
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11259
|
+
`Read ${resolve9(cwd, ".jdi/framework/components/meta/ComplexityRouter.md")} for complexity routing rules.`,
|
|
11260
|
+
`Read ${resolve9(cwd, ".jdi/framework/components/meta/AgentTeamsOrchestration.md")} for Agent Teams orchestration (if needed).`,
|
|
11261
|
+
``,
|
|
11262
|
+
...contextLines,
|
|
11263
|
+
historyBlock,
|
|
11264
|
+
`## Task`,
|
|
11265
|
+
`Execute the current implementation plan. Read state.yaml for the active plan path.`,
|
|
11266
|
+
`Follow the implement-plan orchestration. Present a summary when complete and ask for feedback.`
|
|
11267
|
+
].join(`
|
|
11268
|
+
`);
|
|
11269
|
+
break;
|
|
11270
|
+
case "quick":
|
|
11271
|
+
prompt2 = [
|
|
11272
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11273
|
+
``,
|
|
11274
|
+
...contextLines,
|
|
11275
|
+
historyBlock,
|
|
11276
|
+
`## Task`,
|
|
11277
|
+
`Make this quick change: ${intent.description}`,
|
|
11278
|
+
`Keep changes minimal and focused. Commit when done. Present what you changed.`
|
|
11279
|
+
].join(`
|
|
11280
|
+
`);
|
|
11281
|
+
break;
|
|
11282
|
+
case "review":
|
|
11283
|
+
prompt2 = [
|
|
11284
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11285
|
+
``,
|
|
11286
|
+
...contextLines,
|
|
11287
|
+
historyBlock,
|
|
11288
|
+
`## Task`,
|
|
11289
|
+
`Review the current PR. Post line-level review comments via gh api.`
|
|
11290
|
+
].join(`
|
|
11291
|
+
`);
|
|
11292
|
+
break;
|
|
11293
|
+
case "feedback":
|
|
11294
|
+
prompt2 = [
|
|
11295
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11296
|
+
``,
|
|
11297
|
+
...contextLines,
|
|
11298
|
+
historyBlock,
|
|
11299
|
+
`## Task`,
|
|
11300
|
+
`Address PR feedback comments. Read comments via gh api, make changes, reply to each.`
|
|
11301
|
+
].join(`
|
|
11302
|
+
`);
|
|
11303
|
+
break;
|
|
11304
|
+
default:
|
|
11305
|
+
prompt2 = [
|
|
11306
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11307
|
+
``,
|
|
11308
|
+
...contextLines,
|
|
11309
|
+
historyBlock,
|
|
11310
|
+
`## Task`,
|
|
11311
|
+
intent.description
|
|
11312
|
+
].join(`
|
|
11313
|
+
`);
|
|
11314
|
+
}
|
|
11315
|
+
}
|
|
11316
|
+
let success = true;
|
|
11317
|
+
try {
|
|
11318
|
+
const { exitCode } = await spawnClaude(prompt2, {
|
|
11319
|
+
cwd,
|
|
11320
|
+
permissionMode: "bypassPermissions"
|
|
11321
|
+
});
|
|
11322
|
+
if (exitCode !== 0) {
|
|
11323
|
+
success = false;
|
|
11324
|
+
consola.error(`Claude exited with code ${exitCode}`);
|
|
11325
|
+
}
|
|
11326
|
+
if (intent.fullFlow && success) {
|
|
11327
|
+
consola.info("Full flow: now running implement...");
|
|
11328
|
+
const implementPrompt = [
|
|
11329
|
+
`Read ${baseProtocol} for the base agent protocol.`,
|
|
11330
|
+
`Read ${resolve9(cwd, ".jdi/framework/components/meta/ComplexityRouter.md")} for complexity routing rules.`,
|
|
11331
|
+
`Read ${resolve9(cwd, ".jdi/framework/components/meta/AgentTeamsOrchestration.md")} for Agent Teams orchestration (if needed).`,
|
|
11332
|
+
``,
|
|
11333
|
+
...contextLines,
|
|
11334
|
+
``,
|
|
11335
|
+
`## Task`,
|
|
11336
|
+
`Execute the most recently created implementation plan in .jdi/plans/.`,
|
|
11337
|
+
`Follow the implement-plan orchestration. Present a summary when complete.`
|
|
11338
|
+
].join(`
|
|
11339
|
+
`);
|
|
11340
|
+
const implResult = await spawnClaude(implementPrompt, {
|
|
11341
|
+
cwd,
|
|
11342
|
+
permissionMode: "bypassPermissions"
|
|
11343
|
+
});
|
|
11344
|
+
if (implResult.exitCode !== 0) {
|
|
11345
|
+
success = false;
|
|
11346
|
+
}
|
|
11347
|
+
}
|
|
11348
|
+
} catch (err) {
|
|
11349
|
+
success = false;
|
|
11350
|
+
consola.error("Execution failed:", err);
|
|
11351
|
+
}
|
|
11352
|
+
const saved = await savePersistedState(cwd, storage);
|
|
11353
|
+
if (saved.learningsSaved)
|
|
11354
|
+
consola.info("Learnings persisted to storage");
|
|
11355
|
+
if (saved.codebaseIndexSaved)
|
|
11356
|
+
consola.info("Codebase index persisted to storage");
|
|
11357
|
+
if (repo && issueNumber) {
|
|
11358
|
+
const actionLabel = intent.isFeedback ? "feedback" : intent.command;
|
|
11359
|
+
const summary = success ? `Executed \`${actionLabel}\` successfully.${saved.learningsSaved ? " Learnings updated." : ""}` : `Execution of \`${actionLabel}\` failed. Check workflow logs for details.`;
|
|
11360
|
+
const commentBody = formatResultComment(actionLabel, success, summary);
|
|
11361
|
+
await postGitHubComment(repo, issueNumber, commentBody).catch((err) => {
|
|
11362
|
+
consola.error("Failed to post result comment:", err);
|
|
11363
|
+
});
|
|
11364
|
+
}
|
|
11365
|
+
if (repo && commentId) {
|
|
11366
|
+
const reaction = success ? "+1" : "-1";
|
|
11367
|
+
await reactToComment(repo, commentId, reaction).catch(() => {});
|
|
11368
|
+
}
|
|
11369
|
+
if (!success)
|
|
11370
|
+
process.exit(1);
|
|
11371
|
+
}
|
|
11372
|
+
});
|
|
11373
|
+
|
|
11374
|
+
// src/commands/setup-action.ts
|
|
11375
|
+
import { join as join12, dirname as dirname3 } from "path";
|
|
11376
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4 } from "fs";
|
|
11377
|
+
var setupActionCommand = defineCommand({
|
|
11378
|
+
meta: {
|
|
11379
|
+
name: "setup-action",
|
|
11380
|
+
description: "Set up the Jedi GitHub Action in your repository"
|
|
11381
|
+
},
|
|
11382
|
+
args: {},
|
|
11383
|
+
async run() {
|
|
11384
|
+
const cwd = process.cwd();
|
|
11385
|
+
const workflowDest = join12(cwd, ".github", "workflows", "jedi.yml");
|
|
11386
|
+
if (existsSync12(workflowDest)) {
|
|
11387
|
+
consola.warn(`Workflow already exists at ${workflowDest}`);
|
|
11388
|
+
consola.info("Skipping workflow copy. Delete it manually to regenerate.");
|
|
11389
|
+
} else {
|
|
11390
|
+
const templatePath = join12(import.meta.dir, "../../action/workflow-template.yml");
|
|
11391
|
+
if (!existsSync12(templatePath)) {
|
|
11392
|
+
consola.error("Workflow template not found. Ensure @benzotti/jedi is properly installed.");
|
|
11393
|
+
process.exit(1);
|
|
11394
|
+
}
|
|
11395
|
+
const dir = dirname3(workflowDest);
|
|
11396
|
+
if (!existsSync12(dir))
|
|
11397
|
+
mkdirSync4(dir, { recursive: true });
|
|
11398
|
+
const template = await Bun.file(templatePath).text();
|
|
11399
|
+
await Bun.write(workflowDest, template);
|
|
11400
|
+
consola.success(`Created ${workflowDest}`);
|
|
11401
|
+
}
|
|
11402
|
+
consola.info("");
|
|
11403
|
+
consola.box([
|
|
11404
|
+
"Jedi GitHub Action Setup",
|
|
11405
|
+
"",
|
|
11406
|
+
"Uses: anthropics/claude-code-action@v1",
|
|
11407
|
+
"Trigger: @jedi in issue/PR comments",
|
|
11408
|
+
"",
|
|
11409
|
+
"Required secrets (set via GitHub UI or CLI):",
|
|
11410
|
+
"",
|
|
11411
|
+
" gh secret set ANTHROPIC_API_KEY --body '<your-key>'",
|
|
11412
|
+
"",
|
|
11413
|
+
"Optional secrets:",
|
|
11414
|
+
"",
|
|
11415
|
+
" gh secret set CLICKUP_API_TOKEN --body '<your-token>'",
|
|
11416
|
+
"",
|
|
11417
|
+
"Usage: Comment on any issue or PR with:",
|
|
11418
|
+
"",
|
|
11419
|
+
" @jedi plan <description>",
|
|
11420
|
+
" @jedi quick <small fix>",
|
|
11421
|
+
" @jedi do <clickup-ticket-url>",
|
|
11422
|
+
" @jedi review",
|
|
11423
|
+
" @jedi feedback",
|
|
11424
|
+
"",
|
|
11425
|
+
"Conversation: Reply to Jedi with feedback to iterate,",
|
|
11426
|
+
"or say 'approved' to finalise."
|
|
11427
|
+
].join(`
|
|
11428
|
+
`));
|
|
11429
|
+
}
|
|
11430
|
+
});
|
|
10450
11431
|
// package.json
|
|
10451
11432
|
var package_default = {
|
|
10452
11433
|
name: "@benzotti/jedi",
|
|
10453
|
-
version: "0.1.
|
|
11434
|
+
version: "0.1.4",
|
|
10454
11435
|
description: "JDI - Context-efficient AI development framework for Claude Code",
|
|
10455
11436
|
type: "module",
|
|
10456
11437
|
bin: {
|
|
10457
11438
|
jdi: "./dist/index.js"
|
|
10458
11439
|
},
|
|
11440
|
+
exports: {
|
|
11441
|
+
".": "./dist/index.js",
|
|
11442
|
+
"./storage": "./src/storage/index.ts"
|
|
11443
|
+
},
|
|
10459
11444
|
scripts: {
|
|
10460
11445
|
build: "bun build src/index.ts --outdir dist --target bun",
|
|
10461
11446
|
dev: "bun run src/index.ts",
|
|
@@ -10472,7 +11457,8 @@ var package_default = {
|
|
|
10472
11457
|
},
|
|
10473
11458
|
files: [
|
|
10474
11459
|
"dist",
|
|
10475
|
-
"framework"
|
|
11460
|
+
"framework",
|
|
11461
|
+
"action"
|
|
10476
11462
|
],
|
|
10477
11463
|
repository: {
|
|
10478
11464
|
type: "git",
|
|
@@ -10505,7 +11491,9 @@ var main = defineCommand({
|
|
|
10505
11491
|
worktree: worktreeCommand,
|
|
10506
11492
|
"worktree-remove": worktreeRemoveCommand,
|
|
10507
11493
|
"plan-review": planReviewCommand,
|
|
10508
|
-
"plan-approve": planApproveCommand
|
|
11494
|
+
"plan-approve": planApproveCommand,
|
|
11495
|
+
action: actionCommand,
|
|
11496
|
+
"setup-action": setupActionCommand
|
|
10509
11497
|
}
|
|
10510
11498
|
});
|
|
10511
11499
|
runMain(main);
|