@benzotti/jedi 0.1.3 → 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 +873 -82
- 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,55 @@ 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
|
+
|
|
7679
7757
|
// src/utils/git.ts
|
|
7680
7758
|
var exports_git = {};
|
|
7681
7759
|
__export(exports_git, {
|
|
@@ -9266,30 +9344,73 @@ var initCommand = defineCommand({
|
|
|
9266
9344
|
type: "boolean",
|
|
9267
9345
|
description: "Overwrite existing files",
|
|
9268
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/)"
|
|
9269
9360
|
}
|
|
9270
9361
|
},
|
|
9271
9362
|
async run({ args }) {
|
|
9272
9363
|
const cwd = process.cwd();
|
|
9273
9364
|
const projectType = await detectProjectType(cwd);
|
|
9274
|
-
|
|
9275
|
-
|
|
9365
|
+
if (!args.ci) {
|
|
9366
|
+
consola.info(`Detected project type: ${projectType}`);
|
|
9367
|
+
consola.start("Initialising JDI...");
|
|
9368
|
+
}
|
|
9276
9369
|
const dirs = [
|
|
9277
9370
|
".claude/commands/jdi",
|
|
9278
9371
|
".jdi/plans",
|
|
9279
9372
|
".jdi/research",
|
|
9280
9373
|
".jdi/codebase",
|
|
9281
9374
|
".jdi/reviews",
|
|
9282
|
-
".jdi/config"
|
|
9375
|
+
".jdi/config",
|
|
9376
|
+
".jdi/persistence"
|
|
9283
9377
|
];
|
|
9284
9378
|
for (const dir of dirs) {
|
|
9285
9379
|
await Bun.write(join3(cwd, dir, ".gitkeep"), "");
|
|
9286
9380
|
}
|
|
9287
9381
|
await copyFrameworkFiles(cwd, projectType, args.force);
|
|
9288
|
-
|
|
9289
|
-
|
|
9290
|
-
|
|
9291
|
-
|
|
9292
|
-
|
|
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
|
+
}
|
|
9293
9414
|
}
|
|
9294
9415
|
});
|
|
9295
9416
|
|
|
@@ -9297,62 +9418,15 @@ var initCommand = defineCommand({
|
|
|
9297
9418
|
import { resolve as resolve2 } from "path";
|
|
9298
9419
|
|
|
9299
9420
|
// src/utils/adapter.ts
|
|
9421
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
9300
9422
|
import { join as join4 } from "path";
|
|
9301
9423
|
import { existsSync as existsSync3 } from "fs";
|
|
9302
|
-
|
|
9303
|
-
// node_modules/yaml/dist/index.js
|
|
9304
|
-
var composer = require_composer();
|
|
9305
|
-
var Document = require_Document();
|
|
9306
|
-
var Schema = require_Schema();
|
|
9307
|
-
var errors = require_errors();
|
|
9308
|
-
var Alias = require_Alias();
|
|
9309
|
-
var identity = require_identity();
|
|
9310
|
-
var Pair = require_Pair();
|
|
9311
|
-
var Scalar = require_Scalar();
|
|
9312
|
-
var YAMLMap = require_YAMLMap();
|
|
9313
|
-
var YAMLSeq = require_YAMLSeq();
|
|
9314
|
-
var cst = require_cst();
|
|
9315
|
-
var lexer = require_lexer();
|
|
9316
|
-
var lineCounter = require_line_counter();
|
|
9317
|
-
var parser = require_parser();
|
|
9318
|
-
var publicApi = require_public_api();
|
|
9319
|
-
var visit = require_visit();
|
|
9320
|
-
var $Composer = composer.Composer;
|
|
9321
|
-
var $Document = Document.Document;
|
|
9322
|
-
var $Schema = Schema.Schema;
|
|
9323
|
-
var $YAMLError = errors.YAMLError;
|
|
9324
|
-
var $YAMLParseError = errors.YAMLParseError;
|
|
9325
|
-
var $YAMLWarning = errors.YAMLWarning;
|
|
9326
|
-
var $Alias = Alias.Alias;
|
|
9327
|
-
var $isAlias = identity.isAlias;
|
|
9328
|
-
var $isCollection = identity.isCollection;
|
|
9329
|
-
var $isDocument = identity.isDocument;
|
|
9330
|
-
var $isMap = identity.isMap;
|
|
9331
|
-
var $isNode = identity.isNode;
|
|
9332
|
-
var $isPair = identity.isPair;
|
|
9333
|
-
var $isScalar = identity.isScalar;
|
|
9334
|
-
var $isSeq = identity.isSeq;
|
|
9335
|
-
var $Pair = Pair.Pair;
|
|
9336
|
-
var $Scalar = Scalar.Scalar;
|
|
9337
|
-
var $YAMLMap = YAMLMap.YAMLMap;
|
|
9338
|
-
var $YAMLSeq = YAMLSeq.YAMLSeq;
|
|
9339
|
-
var $Lexer = lexer.Lexer;
|
|
9340
|
-
var $LineCounter = lineCounter.LineCounter;
|
|
9341
|
-
var $Parser = parser.Parser;
|
|
9342
|
-
var $parse = publicApi.parse;
|
|
9343
|
-
var $parseAllDocuments = publicApi.parseAllDocuments;
|
|
9344
|
-
var $parseDocument = publicApi.parseDocument;
|
|
9345
|
-
var $stringify = publicApi.stringify;
|
|
9346
|
-
var $visit = visit.visit;
|
|
9347
|
-
var $visitAsync = visit.visitAsync;
|
|
9348
|
-
|
|
9349
|
-
// src/utils/adapter.ts
|
|
9350
9424
|
async function readAdapter(cwd) {
|
|
9351
9425
|
const adapterPath = join4(cwd, ".jdi", "config", "adapter.yaml");
|
|
9352
9426
|
if (!existsSync3(adapterPath))
|
|
9353
9427
|
return null;
|
|
9354
9428
|
const content = await Bun.file(adapterPath).text();
|
|
9355
|
-
return
|
|
9429
|
+
return import_yaml.parse(content);
|
|
9356
9430
|
}
|
|
9357
9431
|
|
|
9358
9432
|
// src/utils/claude.ts
|
|
@@ -9479,6 +9553,152 @@ async function spawnClaude(prompt2, opts) {
|
|
|
9479
9553
|
return { exitCode };
|
|
9480
9554
|
}
|
|
9481
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;
|
|
9700
|
+
}
|
|
9701
|
+
|
|
9482
9702
|
// src/commands/plan.ts
|
|
9483
9703
|
var planCommand = defineCommand({
|
|
9484
9704
|
meta: {
|
|
@@ -9531,7 +9751,10 @@ var planCommand = defineCommand({
|
|
|
9531
9751
|
} else if (args.print) {
|
|
9532
9752
|
console.log(prompt2);
|
|
9533
9753
|
} else {
|
|
9754
|
+
const storage = await createStorage(cwd);
|
|
9755
|
+
await loadPersistedState(cwd, storage);
|
|
9534
9756
|
const { exitCode } = await spawnClaude(prompt2, { cwd });
|
|
9757
|
+
await savePersistedState(cwd, storage);
|
|
9535
9758
|
if (exitCode !== 0) {
|
|
9536
9759
|
consola.error(`Claude exited with code ${exitCode}`);
|
|
9537
9760
|
process.exit(exitCode);
|
|
@@ -9544,18 +9767,19 @@ var planCommand = defineCommand({
|
|
|
9544
9767
|
import { resolve as resolve3 } from "path";
|
|
9545
9768
|
|
|
9546
9769
|
// src/utils/state.ts
|
|
9547
|
-
|
|
9548
|
-
import {
|
|
9770
|
+
var import_yaml3 = __toESM(require_dist(), 1);
|
|
9771
|
+
import { join as join8 } from "path";
|
|
9772
|
+
import { existsSync as existsSync7 } from "fs";
|
|
9549
9773
|
async function readState(cwd) {
|
|
9550
|
-
const statePath =
|
|
9551
|
-
if (!
|
|
9774
|
+
const statePath = join8(cwd, ".jdi", "config", "state.yaml");
|
|
9775
|
+
if (!existsSync7(statePath))
|
|
9552
9776
|
return null;
|
|
9553
9777
|
const content = await Bun.file(statePath).text();
|
|
9554
|
-
return
|
|
9778
|
+
return import_yaml3.parse(content);
|
|
9555
9779
|
}
|
|
9556
9780
|
async function writeState(cwd, state) {
|
|
9557
|
-
const statePath =
|
|
9558
|
-
await Bun.write(statePath,
|
|
9781
|
+
const statePath = join8(cwd, ".jdi", "config", "state.yaml");
|
|
9782
|
+
await Bun.write(statePath, import_yaml3.stringify(state));
|
|
9559
9783
|
}
|
|
9560
9784
|
|
|
9561
9785
|
// src/commands/implement.ts
|
|
@@ -9643,7 +9867,10 @@ Override: --single (force single-agent mode)` : "";
|
|
|
9643
9867
|
} else if (args.print) {
|
|
9644
9868
|
console.log(prompt2);
|
|
9645
9869
|
} else {
|
|
9870
|
+
const storage = await createStorage(cwd);
|
|
9871
|
+
await loadPersistedState(cwd, storage);
|
|
9646
9872
|
const { exitCode } = await spawnClaude(prompt2, { cwd });
|
|
9873
|
+
await savePersistedState(cwd, storage);
|
|
9647
9874
|
if (exitCode !== 0) {
|
|
9648
9875
|
consola.error(`Claude exited with code ${exitCode}`);
|
|
9649
9876
|
process.exit(exitCode);
|
|
@@ -9699,15 +9926,15 @@ var statusCommand = defineCommand({
|
|
|
9699
9926
|
import { relative } from "path";
|
|
9700
9927
|
|
|
9701
9928
|
// src/utils/resolve-components.ts
|
|
9702
|
-
import { join as
|
|
9929
|
+
import { join as join9, basename } from "path";
|
|
9703
9930
|
import { homedir } from "os";
|
|
9704
9931
|
async function resolveComponents(cwd) {
|
|
9705
9932
|
const components = [];
|
|
9706
9933
|
const seen = new Set;
|
|
9707
9934
|
const sources = [
|
|
9708
|
-
{ dir:
|
|
9709
|
-
{ dir:
|
|
9710
|
-
{ 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" }
|
|
9711
9938
|
];
|
|
9712
9939
|
for (const { dir, source } of sources) {
|
|
9713
9940
|
try {
|
|
@@ -9716,7 +9943,7 @@ async function resolveComponents(cwd) {
|
|
|
9716
9943
|
const name = basename(file, ".md");
|
|
9717
9944
|
if (!seen.has(name)) {
|
|
9718
9945
|
seen.add(name);
|
|
9719
|
-
components.push({ name, path:
|
|
9946
|
+
components.push({ name, path: join9(dir, file), source });
|
|
9720
9947
|
}
|
|
9721
9948
|
}
|
|
9722
9949
|
} catch {}
|
|
@@ -9862,8 +10089,8 @@ Use --all to stage and commit all, or stage files manually.`);
|
|
|
9862
10089
|
});
|
|
9863
10090
|
|
|
9864
10091
|
// src/commands/pr.ts
|
|
9865
|
-
import { existsSync as
|
|
9866
|
-
import { join as
|
|
10092
|
+
import { existsSync as existsSync8 } from "fs";
|
|
10093
|
+
import { join as join10 } from "path";
|
|
9867
10094
|
async function hasGhCli() {
|
|
9868
10095
|
const { exitCode } = await exec(["which", "gh"]);
|
|
9869
10096
|
return exitCode === 0;
|
|
@@ -9913,8 +10140,8 @@ var prCommand = defineCommand({
|
|
|
9913
10140
|
|
|
9914
10141
|
**Plan:** ${state.position.plan_name}` : "";
|
|
9915
10142
|
let template = "";
|
|
9916
|
-
const templatePath =
|
|
9917
|
-
if (
|
|
10143
|
+
const templatePath = join10(cwd, ".github", "pull_request_template.md");
|
|
10144
|
+
if (existsSync8(templatePath)) {
|
|
9918
10145
|
template = await Bun.file(templatePath).text();
|
|
9919
10146
|
}
|
|
9920
10147
|
const title = branch.replace(/^(feat|fix|chore|docs|refactor|test|ci)\//, "").replace(/[-_]/g, " ").replace(/^\w/, (c3) => c3.toUpperCase());
|
|
@@ -10262,7 +10489,7 @@ var quickCommand = defineCommand({
|
|
|
10262
10489
|
});
|
|
10263
10490
|
|
|
10264
10491
|
// src/commands/worktree.ts
|
|
10265
|
-
import { existsSync as
|
|
10492
|
+
import { existsSync as existsSync9 } from "fs";
|
|
10266
10493
|
function slugify(name) {
|
|
10267
10494
|
return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
10268
10495
|
}
|
|
@@ -10296,7 +10523,7 @@ var worktreeCommand = defineCommand({
|
|
|
10296
10523
|
}
|
|
10297
10524
|
const slug = slugify(args.name);
|
|
10298
10525
|
const worktreePath = `${root}/.worktrees/${slug}`;
|
|
10299
|
-
if (
|
|
10526
|
+
if (existsSync9(worktreePath)) {
|
|
10300
10527
|
consola.error(`Worktree already exists at ${worktreePath}`);
|
|
10301
10528
|
return;
|
|
10302
10529
|
}
|
|
@@ -10462,7 +10689,7 @@ Specify a worktree name: jdi worktree-remove <name>`);
|
|
|
10462
10689
|
|
|
10463
10690
|
// src/commands/plan-review.ts
|
|
10464
10691
|
import { resolve as resolve7 } from "path";
|
|
10465
|
-
import { existsSync as
|
|
10692
|
+
import { existsSync as existsSync10 } from "fs";
|
|
10466
10693
|
function parsePlanSummary(content) {
|
|
10467
10694
|
const nameMatch = content.match(/^# .+?: (.+)$/m);
|
|
10468
10695
|
const name = nameMatch?.[1] ?? "Unknown";
|
|
@@ -10501,7 +10728,7 @@ var planReviewCommand = defineCommand({
|
|
|
10501
10728
|
consola.error("No plan found. Run `jdi plan` first.");
|
|
10502
10729
|
return;
|
|
10503
10730
|
}
|
|
10504
|
-
if (!
|
|
10731
|
+
if (!existsSync10(planPath)) {
|
|
10505
10732
|
consola.error(`Plan not found: ${planPath}`);
|
|
10506
10733
|
return;
|
|
10507
10734
|
}
|
|
@@ -10591,7 +10818,7 @@ Tasks (${tasks.length}):`);
|
|
|
10591
10818
|
|
|
10592
10819
|
// src/commands/plan-approve.ts
|
|
10593
10820
|
import { resolve as resolve8 } from "path";
|
|
10594
|
-
import { existsSync as
|
|
10821
|
+
import { existsSync as existsSync11 } from "fs";
|
|
10595
10822
|
var planApproveCommand = defineCommand({
|
|
10596
10823
|
meta: {
|
|
10597
10824
|
name: "plan-approve",
|
|
@@ -10620,7 +10847,7 @@ var planApproveCommand = defineCommand({
|
|
|
10620
10847
|
consola.error("No plan to approve. Run `jdi plan` first.");
|
|
10621
10848
|
return;
|
|
10622
10849
|
}
|
|
10623
|
-
if (!
|
|
10850
|
+
if (!existsSync11(planPath)) {
|
|
10624
10851
|
consola.error(`Plan not found: ${planPath}`);
|
|
10625
10852
|
return;
|
|
10626
10853
|
}
|
|
@@ -10644,15 +10871,576 @@ var planApproveCommand = defineCommand({
|
|
|
10644
10871
|
consola.info("Say 'implement this' in Claude Code or run `/jdi:implement-plan` to execute.");
|
|
10645
10872
|
}
|
|
10646
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
|
+
});
|
|
10647
11431
|
// package.json
|
|
10648
11432
|
var package_default = {
|
|
10649
11433
|
name: "@benzotti/jedi",
|
|
10650
|
-
version: "0.1.
|
|
11434
|
+
version: "0.1.4",
|
|
10651
11435
|
description: "JDI - Context-efficient AI development framework for Claude Code",
|
|
10652
11436
|
type: "module",
|
|
10653
11437
|
bin: {
|
|
10654
11438
|
jdi: "./dist/index.js"
|
|
10655
11439
|
},
|
|
11440
|
+
exports: {
|
|
11441
|
+
".": "./dist/index.js",
|
|
11442
|
+
"./storage": "./src/storage/index.ts"
|
|
11443
|
+
},
|
|
10656
11444
|
scripts: {
|
|
10657
11445
|
build: "bun build src/index.ts --outdir dist --target bun",
|
|
10658
11446
|
dev: "bun run src/index.ts",
|
|
@@ -10669,7 +11457,8 @@ var package_default = {
|
|
|
10669
11457
|
},
|
|
10670
11458
|
files: [
|
|
10671
11459
|
"dist",
|
|
10672
|
-
"framework"
|
|
11460
|
+
"framework",
|
|
11461
|
+
"action"
|
|
10673
11462
|
],
|
|
10674
11463
|
repository: {
|
|
10675
11464
|
type: "git",
|
|
@@ -10702,7 +11491,9 @@ var main = defineCommand({
|
|
|
10702
11491
|
worktree: worktreeCommand,
|
|
10703
11492
|
"worktree-remove": worktreeRemoveCommand,
|
|
10704
11493
|
"plan-review": planReviewCommand,
|
|
10705
|
-
"plan-approve": planApproveCommand
|
|
11494
|
+
"plan-approve": planApproveCommand,
|
|
11495
|
+
action: actionCommand,
|
|
11496
|
+
"setup-action": setupActionCommand
|
|
10706
11497
|
}
|
|
10707
11498
|
});
|
|
10708
11499
|
runMain(main);
|