lalph 0.2.18 → 0.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +152 -118
- package/package.json +1 -1
- package/src/Agents/planner.ts +1 -0
- package/src/Editor.ts +66 -0
- package/src/Github/Cli.ts +71 -39
- package/src/PromptGen.ts +8 -2
- package/src/commands/edit.ts +6 -13
- package/src/commands/issue.ts +15 -23
- package/src/commands/plan.ts +21 -18
- package/src/domain/GithubComment.ts +30 -30
package/dist/cli.mjs
CHANGED
|
@@ -7235,7 +7235,7 @@ const getOption = /* @__PURE__ */ dual(2, (self, service) => {
|
|
|
7235
7235
|
* @since 4.0.0
|
|
7236
7236
|
* @category Utils
|
|
7237
7237
|
*/
|
|
7238
|
-
const merge$
|
|
7238
|
+
const merge$5 = /* @__PURE__ */ dual(2, (self, that) => {
|
|
7239
7239
|
if (self.mapUnsafe.size === 0) return that;
|
|
7240
7240
|
if (that.mapUnsafe.size === 0) return self;
|
|
7241
7241
|
const map = new Map(self.mapUnsafe);
|
|
@@ -9229,7 +9229,7 @@ const servicesWith$1 = (f) => withFiber$1((fiber) => f(fiber.services));
|
|
|
9229
9229
|
/** @internal */
|
|
9230
9230
|
const provideServices$1 = /* @__PURE__ */ dual(2, (self, services) => {
|
|
9231
9231
|
if (effectIsExit(self)) return self;
|
|
9232
|
-
return updateServices$1(self, merge$
|
|
9232
|
+
return updateServices$1(self, merge$5(services));
|
|
9233
9233
|
});
|
|
9234
9234
|
/** @internal */
|
|
9235
9235
|
const provideService$1 = function() {
|
|
@@ -12030,38 +12030,6 @@ const mergeAllEffect = (layers, memoMap, scope) => {
|
|
|
12030
12030
|
* @category zipping
|
|
12031
12031
|
*/
|
|
12032
12032
|
const mergeAll = (...layers) => fromBuild((memoMap, scope) => mergeAllEffect(layers, memoMap, scope));
|
|
12033
|
-
/**
|
|
12034
|
-
* Merges this layer with the specified layer concurrently, producing a new layer with combined input and output types.
|
|
12035
|
-
*
|
|
12036
|
-
* This is a binary version of `mergeAll` that merges exactly two layers or one layer with an array of layers.
|
|
12037
|
-
* The layers are built concurrently and their outputs are combined.
|
|
12038
|
-
*
|
|
12039
|
-
* @example
|
|
12040
|
-
* ```ts
|
|
12041
|
-
* import { Effect, Layer, ServiceMap } from "effect"
|
|
12042
|
-
*
|
|
12043
|
-
* class Database extends ServiceMap.Service<Database, {
|
|
12044
|
-
* readonly query: (sql: string) => Effect.Effect<string>
|
|
12045
|
-
* }>()("Database") {}
|
|
12046
|
-
*
|
|
12047
|
-
* class Logger extends ServiceMap.Service<Logger, {
|
|
12048
|
-
* readonly log: (msg: string) => Effect.Effect<void>
|
|
12049
|
-
* }>()("Logger") {}
|
|
12050
|
-
*
|
|
12051
|
-
* const dbLayer = Layer.succeed(Database)({
|
|
12052
|
-
* query: (sql: string) => Effect.succeed("result")
|
|
12053
|
-
* })
|
|
12054
|
-
* const loggerLayer = Layer.succeed(Logger)({
|
|
12055
|
-
* log: (msg: string) => Effect.sync(() => console.log(msg))
|
|
12056
|
-
* })
|
|
12057
|
-
*
|
|
12058
|
-
* const mergedLayer = Layer.merge(dbLayer, loggerLayer)
|
|
12059
|
-
* ```
|
|
12060
|
-
*
|
|
12061
|
-
* @since 2.0.0
|
|
12062
|
-
* @category zipping
|
|
12063
|
-
*/
|
|
12064
|
-
const merge$5 = /* @__PURE__ */ dual(2, (self, that) => mergeAll(self, ...Array.isArray(that) ? that : [that]));
|
|
12065
12033
|
const provideWith = (self, that, f) => fromBuild((memoMap, scope) => flatMap$4(Array.isArray(that) ? mergeAllEffect(that, memoMap, scope) : that.build(memoMap, scope), (context) => self.build(memoMap, scope).pipe(provideServices$1(context), map$11((merged) => f(merged, context)))));
|
|
12066
12034
|
/**
|
|
12067
12035
|
* Feeds the output services of this builder into the input of the specified
|
|
@@ -12204,7 +12172,7 @@ const provide$3 = /* @__PURE__ */ dual(2, (self, that) => provideWith(self, that
|
|
|
12204
12172
|
* @since 2.0.0
|
|
12205
12173
|
* @category utils
|
|
12206
12174
|
*/
|
|
12207
|
-
const provideMerge = /* @__PURE__ */ dual(2, (self, that) => provideWith(self, that, (self, that) => merge$
|
|
12175
|
+
const provideMerge = /* @__PURE__ */ dual(2, (self, that) => provideWith(self, that, (self, that) => merge$5(that, self)));
|
|
12208
12176
|
/**
|
|
12209
12177
|
* Constructs a layer dynamically based on the output of this layer.
|
|
12210
12178
|
*
|
|
@@ -51194,7 +51162,7 @@ const TypeId$29 = "~effect/Cache";
|
|
|
51194
51162
|
*/
|
|
51195
51163
|
const makeWith$1 = (options) => servicesWith$1((services) => {
|
|
51196
51164
|
const self = Object.create(Proto$14);
|
|
51197
|
-
self.lookup = (key) => updateServices$1(options.lookup(key), (input) => merge$
|
|
51165
|
+
self.lookup = (key) => updateServices$1(options.lookup(key), (input) => merge$5(services, input));
|
|
51198
51166
|
self.map = make$45();
|
|
51199
51167
|
self.capacity = options.capacity;
|
|
51200
51168
|
self.timeToLive = options.timeToLive ? (exit, key) => fromDurationInputUnsafe(options.timeToLive(exit, key)) : defaultTimeToLive;
|
|
@@ -59031,7 +58999,7 @@ const SpanNameGenerator$1 = /* @__PURE__ */ Reference("effect/http/HttpClient/Sp
|
|
|
59031
58999
|
/**
|
|
59032
59000
|
* @since 4.0.0
|
|
59033
59001
|
*/
|
|
59034
|
-
const layerMergedServices = (effect) => effect$1(HttpClient)(servicesWith((services) => map$8(effect, (client) => transformResponse(client, updateServices((input) => merge$
|
|
59002
|
+
const layerMergedServices = (effect) => effect$1(HttpClient)(servicesWith((services) => map$8(effect, (client) => transformResponse(client, updateServices((input) => merge$5(services, input))))));
|
|
59035
59003
|
const responseRegistry = /* @__PURE__ */ (() => {
|
|
59036
59004
|
if ("FinalizationRegistry" in globalThis && globalThis.FinalizationRegistry) {
|
|
59037
59005
|
const registry = /* @__PURE__ */ new FinalizationRegistry((controller) => {
|
|
@@ -60328,7 +60296,7 @@ const fromWebSocket = (acquire, options) => withFiber((fiber) => {
|
|
|
60328
60296
|
latch.openUnsafe();
|
|
60329
60297
|
if (opts?.onOpen) yield* opts.onOpen;
|
|
60330
60298
|
return yield* join(fiberSet).pipe(catchFilter(SocketCloseError.filterClean((_) => !closeCodeIsError(_)), (_) => void_$1));
|
|
60331
|
-
})).pipe(updateServices((input) => merge$
|
|
60299
|
+
})).pipe(updateServices((input) => merge$5(acquireContext, input)), ensuring$2(sync(() => {
|
|
60332
60300
|
latch.closeUnsafe();
|
|
60333
60301
|
currentWS = void 0;
|
|
60334
60302
|
})));
|
|
@@ -150368,32 +150336,41 @@ const PollResponse = Union([TokenResponse, PollErrorResponse]);
|
|
|
150368
150336
|
|
|
150369
150337
|
//#endregion
|
|
150370
150338
|
//#region src/domain/GithubComment.ts
|
|
150371
|
-
var Author = class extends Class("
|
|
150372
|
-
var Comment = class extends Class("
|
|
150339
|
+
var Author = class extends Class("Author")({ login: String$1 }) {};
|
|
150340
|
+
var Comment = class extends Class("Comment")({
|
|
150373
150341
|
id: String$1,
|
|
150374
150342
|
body: String$1,
|
|
150375
|
-
author: Author
|
|
150343
|
+
author: Author,
|
|
150344
|
+
createdAt: String$1
|
|
150376
150345
|
}) {};
|
|
150377
|
-
var
|
|
150378
|
-
var
|
|
150379
|
-
var PullRequest = class extends Class("github/PullRequest")({
|
|
150346
|
+
var PullRequestComments = class extends Class("PullRequestComments")({ nodes: Array$1(Comment) }) {};
|
|
150347
|
+
var PullRequest = class extends Class("PullRequest")({
|
|
150380
150348
|
url: String$1,
|
|
150349
|
+
reviewDecision: Null,
|
|
150350
|
+
reviews: suspend(() => Reviews),
|
|
150381
150351
|
reviewThreads: suspend(() => ReviewThreads),
|
|
150382
150352
|
comments: PullRequestComments
|
|
150383
150353
|
}) {};
|
|
150384
|
-
var Repository = class extends Class("
|
|
150385
|
-
var Data = class extends Class("
|
|
150386
|
-
var
|
|
150387
|
-
var
|
|
150354
|
+
var Repository = class extends Class("Repository")({ pullRequest: PullRequest }) {};
|
|
150355
|
+
var Data = class extends Class("Data")({ repository: Repository }) {};
|
|
150356
|
+
var GithubPullRequestData = class extends Class("GithubPullRequestData")({ data: Data }) {};
|
|
150357
|
+
var Review = class extends Class("Review")({
|
|
150358
|
+
id: String$1,
|
|
150359
|
+
author: Author,
|
|
150360
|
+
body: String$1
|
|
150361
|
+
}) {};
|
|
150362
|
+
var Reviews = class extends Class("Reviews")({ nodes: Array$1(Review) }) {};
|
|
150363
|
+
var ReviewComment = class extends Class("ReviewComment")({
|
|
150388
150364
|
id: String$1,
|
|
150389
150365
|
author: Author,
|
|
150390
150366
|
body: String$1,
|
|
150391
150367
|
path: String$1,
|
|
150392
|
-
originalLine:
|
|
150393
|
-
diffHunk: String$1
|
|
150368
|
+
originalLine: Number$1,
|
|
150369
|
+
diffHunk: String$1,
|
|
150370
|
+
createdAt: String$1
|
|
150394
150371
|
}) {};
|
|
150395
|
-
var NodeComments = class extends Class("
|
|
150396
|
-
var
|
|
150372
|
+
var NodeComments = class extends Class("NodeComments")({ nodes: Array$1(ReviewComment) }) {};
|
|
150373
|
+
var ReviewThreadsNode = class extends Class("ReviewThreadsNode")({
|
|
150397
150374
|
isCollapsed: Boolean$2,
|
|
150398
150375
|
isOutdated: Boolean$2,
|
|
150399
150376
|
isResolved: Boolean$2,
|
|
@@ -150402,8 +150379,7 @@ var ReviewThreadNode = class extends Class("github/ReviewThreadNode")({
|
|
|
150402
150379
|
commentNodes = this.comments.nodes;
|
|
150403
150380
|
shouldDisplayThread = !this.isCollapsed && !this.isOutdated;
|
|
150404
150381
|
};
|
|
150405
|
-
var
|
|
150406
|
-
var ReviewThreads = class extends Class("github/ReviewThreads")({ edges: Array$1(ReviewThreadsEdge) }) {};
|
|
150382
|
+
var ReviewThreads = class extends Class("ReviewThreads")({ nodes: Array$1(ReviewThreadsNode) }) {};
|
|
150407
150383
|
|
|
150408
150384
|
//#endregion
|
|
150409
150385
|
//#region src/Github/Cli.ts
|
|
@@ -150413,15 +150389,16 @@ var GithubCli = class extends Service$1()("lalph/Github/Cli", { make: gen(functi
|
|
|
150413
150389
|
onNone: () => fail$4(new GithubCliRepoNotFound()),
|
|
150414
150390
|
onSome: (value) => succeed$1(value)
|
|
150415
150391
|
}))))).split("/");
|
|
150416
|
-
const reviewComments = (pr) => make$23`gh api graphql -f owner=${owner} -f repo=${repo} -F pr=${pr} -f query=${githubReviewCommentsQuery}`.pipe(string, flatMap$2(decodeEffect(
|
|
150392
|
+
const reviewComments = (pr) => make$23`gh api graphql -f owner=${owner} -f repo=${repo} -F pr=${pr} -f query=${githubReviewCommentsQuery}`.pipe(string, flatMap$2(decodeEffect(PullRequestDataFromJson)), map$8((data) => {
|
|
150417
150393
|
return {
|
|
150418
|
-
comments: data.data.repository.pullRequest.comments.
|
|
150419
|
-
|
|
150394
|
+
comments: data.data.repository.pullRequest.comments.nodes.filter((c) => !c.author.login.startsWith("github")),
|
|
150395
|
+
reviews: data.data.repository.pullRequest.reviews.nodes.filter((r) => r.body.trim().length > 0),
|
|
150396
|
+
reviewThreads: data.data.repository.pullRequest.reviewThreads.nodes
|
|
150420
150397
|
};
|
|
150421
150398
|
}), provideService(ChildProcessSpawner, spawner));
|
|
150422
|
-
const prFeedbackMd = (pr) => reviewComments(pr).pipe(map$8(({ comments, reviewThreads }) => {
|
|
150399
|
+
const prFeedbackMd = (pr) => reviewComments(pr).pipe(map$8(({ comments, reviewThreads, reviews }) => {
|
|
150423
150400
|
const eligibleReviewThreads = reviewThreads.filter((thread) => thread.shouldDisplayThread);
|
|
150424
|
-
if (comments.length === 0 && eligibleReviewThreads.length === 0) return `No review comments found.`;
|
|
150401
|
+
if (comments.length === 0 && eligibleReviewThreads.length === 0 && reviews.length === 0) return `No review comments found.`;
|
|
150425
150402
|
let content = `# PR feedback
|
|
150426
150403
|
|
|
150427
150404
|
Comments are rendered in XML format.`;
|
|
@@ -150432,6 +150409,18 @@ Comments are rendered in XML format.`;
|
|
|
150432
150409
|
## Review Comments
|
|
150433
150410
|
|
|
150434
150411
|
${reviewCommentsMd}`;
|
|
150412
|
+
}
|
|
150413
|
+
if (reviews.length > 0) {
|
|
150414
|
+
const reviewsXml = reviews.map((review) => `<review author="${review.author.login}">
|
|
150415
|
+
<body><![CDATA[${review.body}]]></body>
|
|
150416
|
+
</review>`).join("\n");
|
|
150417
|
+
content += `
|
|
150418
|
+
|
|
150419
|
+
## Reviews
|
|
150420
|
+
|
|
150421
|
+
<reviews>
|
|
150422
|
+
${reviewsXml}
|
|
150423
|
+
</reviews>`;
|
|
150435
150424
|
}
|
|
150436
150425
|
if (comments.length > 0) {
|
|
150437
150426
|
const generalCommentsXml = comments.map((comment) => renderGeneralComment(comment)).join("\n");
|
|
@@ -150470,46 +150459,55 @@ const renderReviewComments = (comment, followup) => `<comment author="${comment.
|
|
|
150470
150459
|
const renderGeneralComment = (comment) => ` <comment author="${comment.author.login}">
|
|
150471
150460
|
<body><![CDATA[${comment.body}]]></body>
|
|
150472
150461
|
</comment>`;
|
|
150473
|
-
const
|
|
150462
|
+
const PullRequestDataFromJson = fromJsonString(GithubPullRequestData);
|
|
150474
150463
|
const githubReviewCommentsQuery = `
|
|
150475
|
-
|
|
150476
|
-
|
|
150477
|
-
|
|
150478
|
-
|
|
150479
|
-
|
|
150480
|
-
|
|
150481
|
-
|
|
150482
|
-
|
|
150483
|
-
|
|
150484
|
-
|
|
150485
|
-
isResolved
|
|
150486
|
-
comments(first: 100) {
|
|
150487
|
-
nodes {
|
|
150488
|
-
id
|
|
150489
|
-
author { login }
|
|
150490
|
-
body
|
|
150491
|
-
path
|
|
150492
|
-
originalLine
|
|
150493
|
-
diffHunk
|
|
150494
|
-
createdAt
|
|
150495
|
-
}
|
|
150496
|
-
}
|
|
150497
|
-
}
|
|
150464
|
+
query FetchPRComments($owner: String!, $repo: String!, $pr: Int!) {
|
|
150465
|
+
repository(owner: $owner, name: $repo) {
|
|
150466
|
+
pullRequest(number: $pr) {
|
|
150467
|
+
url
|
|
150468
|
+
reviewDecision
|
|
150469
|
+
reviews(first: 100) {
|
|
150470
|
+
nodes {
|
|
150471
|
+
id
|
|
150472
|
+
author {
|
|
150473
|
+
login
|
|
150498
150474
|
}
|
|
150475
|
+
body
|
|
150499
150476
|
}
|
|
150500
|
-
|
|
150501
|
-
|
|
150502
|
-
|
|
150477
|
+
}
|
|
150478
|
+
reviewThreads(first: 100) {
|
|
150479
|
+
nodes {
|
|
150480
|
+
isCollapsed
|
|
150481
|
+
isOutdated
|
|
150482
|
+
isResolved
|
|
150483
|
+
comments(first: 100) {
|
|
150484
|
+
nodes {
|
|
150503
150485
|
id
|
|
150486
|
+
author {
|
|
150487
|
+
login
|
|
150488
|
+
}
|
|
150504
150489
|
body
|
|
150505
|
-
|
|
150490
|
+
path
|
|
150491
|
+
originalLine
|
|
150492
|
+
diffHunk
|
|
150506
150493
|
createdAt
|
|
150507
150494
|
}
|
|
150508
150495
|
}
|
|
150509
150496
|
}
|
|
150510
150497
|
}
|
|
150498
|
+
comments(first: 100) {
|
|
150499
|
+
nodes {
|
|
150500
|
+
id
|
|
150501
|
+
body
|
|
150502
|
+
author {
|
|
150503
|
+
login
|
|
150504
|
+
}
|
|
150505
|
+
createdAt
|
|
150506
|
+
}
|
|
150507
|
+
}
|
|
150511
150508
|
}
|
|
150512
150509
|
}
|
|
150510
|
+
}
|
|
150513
150511
|
`;
|
|
150514
150512
|
|
|
150515
150513
|
//#endregion
|
|
@@ -151033,8 +151031,13 @@ permission.
|
|
|
151033
151031
|
5. If any specifications need updating based on your new understanding, update them.
|
|
151034
151032
|
|
|
151035
151033
|
${prdNotes(options)}`;
|
|
151036
|
-
const planPrompt = (options) =>
|
|
151037
|
-
|
|
151034
|
+
const planPrompt = (options) => `<request><![CDATA[
|
|
151035
|
+
${options.plan}
|
|
151036
|
+
]]></request>
|
|
151037
|
+
|
|
151038
|
+
## Instructions
|
|
151039
|
+
|
|
151040
|
+
1. Your job is to create a detailed specification to fulfill the request and save it as a file.
|
|
151038
151041
|
First do some research to understand the request, then interview the user
|
|
151039
151042
|
to gather all the necessary requirements and details for the specification.
|
|
151040
151043
|
- If the user asks you to update an existing specification, find the relevant
|
|
@@ -152090,6 +152093,41 @@ const commandPlanTasks = make$35("tasks", { specificationPath }).pipe(withDescri
|
|
|
152090
152093
|
Worktree.layer.pipe(provide$3(layerProjectIdPrompt))
|
|
152091
152094
|
]))));
|
|
152092
152095
|
|
|
152096
|
+
//#endregion
|
|
152097
|
+
//#region src/shared/config.ts
|
|
152098
|
+
const configEditor = string$1("LALPH_EDITOR").pipe(orElse(() => string$1("EDITOR")), map$5(parseCommand), withDefault$3(() => ["nano"]));
|
|
152099
|
+
|
|
152100
|
+
//#endregion
|
|
152101
|
+
//#region src/Editor.ts
|
|
152102
|
+
var Editor = class extends Service$1()("lalph/Editor", { make: gen(function* () {
|
|
152103
|
+
const fs = yield* FileSystem;
|
|
152104
|
+
const editor = yield* configEditor;
|
|
152105
|
+
const spawner = yield* ChildProcessSpawner;
|
|
152106
|
+
const edit = (path) => make$23(editor[0], [...editor.slice(1), path], {
|
|
152107
|
+
stdin: "inherit",
|
|
152108
|
+
stdout: "inherit",
|
|
152109
|
+
stderr: "inherit"
|
|
152110
|
+
}).pipe(exitCode, provideService(ChildProcessSpawner, spawner), orDie$2);
|
|
152111
|
+
return {
|
|
152112
|
+
edit,
|
|
152113
|
+
editTemp: fnUntraced(function* (options) {
|
|
152114
|
+
const initialContent = options.initialContent ?? "";
|
|
152115
|
+
const file = yield* fs.makeTempFileScoped({ suffix: options.suffix ?? ".txt" });
|
|
152116
|
+
if (initialContent) yield* fs.writeFileString(file, initialContent);
|
|
152117
|
+
if ((yield* make$23(editor[0], [...editor.slice(1), file], {
|
|
152118
|
+
stdin: "inherit",
|
|
152119
|
+
stdout: "inherit",
|
|
152120
|
+
stderr: "inherit"
|
|
152121
|
+
}).pipe(exitCode)) !== 0) return yield* new NoSuchElementError();
|
|
152122
|
+
const content = (yield* fs.readFileString(file)).trim();
|
|
152123
|
+
if (content === initialContent) return yield* new NoSuchElementError();
|
|
152124
|
+
return content;
|
|
152125
|
+
}, scoped$1, provideService(ChildProcessSpawner, spawner), option$1)
|
|
152126
|
+
};
|
|
152127
|
+
}) }) {
|
|
152128
|
+
static layer = effect$1(this, this.make).pipe(provide$3(PlatformServices));
|
|
152129
|
+
};
|
|
152130
|
+
|
|
152093
152131
|
//#endregion
|
|
152094
152132
|
//#region src/commands/plan.ts
|
|
152095
152133
|
const dangerous = boolean("dangerous").pipe(withAlias("d"), withDescription$1("Enable dangerous mode (skip permission prompts) during plan generation"));
|
|
@@ -152098,30 +152136,39 @@ const commandPlan = make$35("plan", {
|
|
|
152098
152136
|
dangerous,
|
|
152099
152137
|
withNewProject
|
|
152100
152138
|
}).pipe(withDescription("Iterate on an issue plan and create PRD tasks"), withHandler(fnUntraced(function* ({ dangerous, withNewProject }) {
|
|
152139
|
+
const thePlan = yield* (yield* Editor).editTemp({ suffix: ".md" });
|
|
152140
|
+
if (isNone(thePlan)) return;
|
|
152101
152141
|
const project = withNewProject ? yield* addOrUpdateProject() : yield* selectProject;
|
|
152102
152142
|
const { specsDirectory } = yield* commandRoot;
|
|
152103
152143
|
const commandPrefix = yield* getCommandPrefix;
|
|
152104
152144
|
yield* plan({
|
|
152145
|
+
plan: thePlan.value,
|
|
152105
152146
|
specsDirectory,
|
|
152106
152147
|
targetBranch: project.targetBranch,
|
|
152107
152148
|
commandPrefix,
|
|
152108
152149
|
dangerous
|
|
152109
152150
|
}).pipe(provideService(CurrentProjectId, project.id));
|
|
152110
|
-
}, provide$1([
|
|
152151
|
+
}, provide$1([
|
|
152152
|
+
Settings.layer,
|
|
152153
|
+
CurrentIssueSource.layer,
|
|
152154
|
+
Editor.layer
|
|
152155
|
+
]))), withSubcommands([commandPlanTasks]));
|
|
152111
152156
|
const plan = fnUntraced(function* (options) {
|
|
152112
152157
|
const fs = yield* FileSystem;
|
|
152113
152158
|
const pathService = yield* Path$1;
|
|
152114
152159
|
const worktree = yield* Worktree;
|
|
152115
152160
|
const cliAgent = yield* getOrSelectCliAgent;
|
|
152116
152161
|
yield* agentPlanner({
|
|
152162
|
+
plan: options.plan,
|
|
152117
152163
|
specsDirectory: options.specsDirectory,
|
|
152118
152164
|
commandPrefix: options.commandPrefix,
|
|
152119
152165
|
dangerous: options.dangerous,
|
|
152120
152166
|
cliAgent
|
|
152121
152167
|
});
|
|
152168
|
+
const planDetails = yield* pipe(fs.readFileString(pathService.join(worktree.directory, ".lalph", "plan.json")), flatMap$2(decodeEffect(PlanDetails)), mapError$2(() => new SpecNotFound()));
|
|
152122
152169
|
yield* log$1("Converting specification into tasks");
|
|
152123
152170
|
yield* agentTasker({
|
|
152124
|
-
specificationPath:
|
|
152171
|
+
specificationPath: planDetails.specification,
|
|
152125
152172
|
specsDirectory: options.specsDirectory,
|
|
152126
152173
|
commandPrefix: options.commandPrefix,
|
|
152127
152174
|
cliAgent
|
|
@@ -152130,16 +152177,15 @@ const plan = fnUntraced(function* (options) {
|
|
|
152130
152177
|
}, scoped$1, provide$1([
|
|
152131
152178
|
PromptGen.layer,
|
|
152132
152179
|
Prd.layerProvided,
|
|
152133
|
-
Worktree.layer
|
|
152180
|
+
Worktree.layer,
|
|
152134
152181
|
Settings.layer,
|
|
152135
152182
|
CurrentIssueSource.layer
|
|
152136
152183
|
]));
|
|
152184
|
+
var SpecNotFound = class extends TaggedError("SpecNotFound") {
|
|
152185
|
+
message = "The AI agent failed to produce a specification.";
|
|
152186
|
+
};
|
|
152137
152187
|
const PlanDetails = fromJsonString(Struct({ specification: String$1 }));
|
|
152138
152188
|
|
|
152139
|
-
//#endregion
|
|
152140
|
-
//#region src/shared/config.ts
|
|
152141
|
-
const configEditor = string$1("LALPH_EDITOR").pipe(orElse(() => string$1("EDITOR")), map$5(parseCommand), withDefault$3(() => ["nano"]));
|
|
152142
|
-
|
|
152143
152189
|
//#endregion
|
|
152144
152190
|
//#region src/commands/issue.ts
|
|
152145
152191
|
const issueTemplate = `---
|
|
@@ -152160,19 +152206,13 @@ const FrontMatterSchema = toCodecJson(Struct({
|
|
|
152160
152206
|
}));
|
|
152161
152207
|
const handler$1 = flow(withHandler(fnUntraced(function* () {
|
|
152162
152208
|
const source = yield* IssueSource;
|
|
152163
|
-
const fs = yield* FileSystem;
|
|
152164
152209
|
const projectId = yield* CurrentProjectId;
|
|
152165
|
-
const
|
|
152166
|
-
|
|
152167
|
-
|
|
152168
|
-
|
|
152169
|
-
|
|
152170
|
-
|
|
152171
|
-
stderr: "inherit"
|
|
152172
|
-
}).pipe(exitCode)) !== 0) return;
|
|
152173
|
-
const content = yield* fs.readFileString(tempFile);
|
|
152174
|
-
if (content.trim() === issueTemplate.trim()) return;
|
|
152175
|
-
const lines = content.split("\n");
|
|
152210
|
+
const content = yield* (yield* Editor).editTemp({
|
|
152211
|
+
suffix: ".md",
|
|
152212
|
+
initialContent: issueTemplate
|
|
152213
|
+
});
|
|
152214
|
+
if (isNone(content)) return;
|
|
152215
|
+
const lines = content.value.split("\n");
|
|
152176
152216
|
const yamlLines = [];
|
|
152177
152217
|
let descriptionStartIndex = 0;
|
|
152178
152218
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -152195,7 +152235,7 @@ const handler$1 = flow(withHandler(fnUntraced(function* () {
|
|
|
152195
152235
|
}));
|
|
152196
152236
|
console.log(`Created issue with ID: ${created.id}`);
|
|
152197
152237
|
console.log(`URL: ${created.url}`);
|
|
152198
|
-
}, scoped$1)), provide(
|
|
152238
|
+
}, scoped$1)), provide(mergeAll(layerProjectIdPrompt, CurrentIssueSource.layer, Editor.layer)));
|
|
152199
152239
|
const commandIssue = make$35("issue").pipe(withDescription("Create a new issue in the selected issue source"), handler$1);
|
|
152200
152240
|
const commandIssueAlias = make$35("i").pipe(withDescription("Alias for 'issue' command"), handler$1);
|
|
152201
152241
|
|
|
@@ -152203,14 +152243,8 @@ const commandIssueAlias = make$35("i").pipe(withDescription("Alias for 'issue' c
|
|
|
152203
152243
|
//#region src/commands/edit.ts
|
|
152204
152244
|
const handler = withHandler(fnUntraced(function* () {
|
|
152205
152245
|
const prd = yield* Prd;
|
|
152206
|
-
|
|
152207
|
-
|
|
152208
|
-
extendEnv: true,
|
|
152209
|
-
stdin: "inherit",
|
|
152210
|
-
stdout: "inherit",
|
|
152211
|
-
stderr: "inherit"
|
|
152212
|
-
}).pipe(exitCode);
|
|
152213
|
-
}, scoped$1, provide$1(Prd.layerLocalProvided.pipe(provideMerge(layerProjectIdPrompt)))));
|
|
152246
|
+
yield* (yield* Editor).edit(prd.path);
|
|
152247
|
+
}, provide$1([Prd.layerLocalProvided.pipe(provideMerge(layerProjectIdPrompt)), Editor.layer])));
|
|
152214
152248
|
const commandEdit = make$35("edit").pipe(withDescription("Open the prd.yml file in your editor"), handler);
|
|
152215
152249
|
const commandEditAlias = make$35("e").pipe(withDescription("Alias for 'edit' command"), handler);
|
|
152216
152250
|
|
|
@@ -152220,7 +152254,7 @@ const commandSource = make$35("source").pipe(withDescription("Select the issue s
|
|
|
152220
152254
|
|
|
152221
152255
|
//#endregion
|
|
152222
152256
|
//#region package.json
|
|
152223
|
-
var version = "0.2.
|
|
152257
|
+
var version = "0.2.20";
|
|
152224
152258
|
|
|
152225
152259
|
//#endregion
|
|
152226
152260
|
//#region src/commands/projects/ls.ts
|
package/package.json
CHANGED
package/src/Agents/planner.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Worktree } from "../Worktree.ts"
|
|
|
5
5
|
import type { CliAgent } from "../domain/CliAgent.ts"
|
|
6
6
|
|
|
7
7
|
export const agentPlanner = Effect.fnUntraced(function* (options: {
|
|
8
|
+
readonly plan: string
|
|
8
9
|
readonly specsDirectory: string
|
|
9
10
|
readonly commandPrefix: (
|
|
10
11
|
command: ChildProcess.Command,
|
package/src/Editor.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { Cause, Effect, FileSystem, Layer, ServiceMap } from "effect"
|
|
2
|
+
import { configEditor } from "./shared/config.ts"
|
|
3
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
|
4
|
+
import { PlatformServices } from "./shared/platform.ts"
|
|
5
|
+
|
|
6
|
+
export class Editor extends ServiceMap.Service<Editor>()("lalph/Editor", {
|
|
7
|
+
make: Effect.gen(function* () {
|
|
8
|
+
const fs = yield* FileSystem.FileSystem
|
|
9
|
+
const editor = yield* configEditor
|
|
10
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner
|
|
11
|
+
|
|
12
|
+
const edit = (path: string) =>
|
|
13
|
+
ChildProcess.make(editor[0]!, [...editor.slice(1), path], {
|
|
14
|
+
stdin: "inherit",
|
|
15
|
+
stdout: "inherit",
|
|
16
|
+
stderr: "inherit",
|
|
17
|
+
}).pipe(
|
|
18
|
+
ChildProcess.exitCode,
|
|
19
|
+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
|
|
20
|
+
Effect.orDie,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
const editTemp = Effect.fnUntraced(
|
|
24
|
+
function* (options: {
|
|
25
|
+
readonly initialContent?: string
|
|
26
|
+
readonly suffix?: string
|
|
27
|
+
}) {
|
|
28
|
+
const initialContent = options.initialContent ?? ""
|
|
29
|
+
const file = yield* fs.makeTempFileScoped({
|
|
30
|
+
suffix: options.suffix ?? ".txt",
|
|
31
|
+
})
|
|
32
|
+
if (initialContent) {
|
|
33
|
+
yield* fs.writeFileString(file, initialContent)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const exitCode = yield* ChildProcess.make(
|
|
37
|
+
editor[0]!,
|
|
38
|
+
[...editor.slice(1), file],
|
|
39
|
+
{
|
|
40
|
+
stdin: "inherit",
|
|
41
|
+
stdout: "inherit",
|
|
42
|
+
stderr: "inherit",
|
|
43
|
+
},
|
|
44
|
+
).pipe(ChildProcess.exitCode)
|
|
45
|
+
|
|
46
|
+
if (exitCode !== 0) {
|
|
47
|
+
return yield* new Cause.NoSuchElementError()
|
|
48
|
+
}
|
|
49
|
+
const content = (yield* fs.readFileString(file)).trim()
|
|
50
|
+
if (content === initialContent) {
|
|
51
|
+
return yield* new Cause.NoSuchElementError()
|
|
52
|
+
}
|
|
53
|
+
return content
|
|
54
|
+
},
|
|
55
|
+
Effect.scoped,
|
|
56
|
+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
|
|
57
|
+
Effect.option,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return { edit, editTemp } as const
|
|
61
|
+
}),
|
|
62
|
+
}) {
|
|
63
|
+
static layer = Layer.effect(this, this.make).pipe(
|
|
64
|
+
Layer.provide(PlatformServices),
|
|
65
|
+
)
|
|
66
|
+
}
|
package/src/Github/Cli.ts
CHANGED
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
} from "effect"
|
|
11
11
|
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"
|
|
12
12
|
import {
|
|
13
|
-
CommentsData,
|
|
14
|
-
ReviewComment,
|
|
15
13
|
Comment,
|
|
14
|
+
GithubPullRequestData,
|
|
15
|
+
ReviewComment,
|
|
16
16
|
} from "../domain/GithubComment.ts"
|
|
17
17
|
|
|
18
18
|
export class GithubCli extends ServiceMap.Service<GithubCli>()(
|
|
@@ -41,17 +41,19 @@ export class GithubCli extends ServiceMap.Service<GithubCli>()(
|
|
|
41
41
|
const reviewComments = (pr: number) =>
|
|
42
42
|
ChildProcess.make`gh api graphql -f owner=${owner} -f repo=${repo} -F pr=${pr} -f query=${githubReviewCommentsQuery}`.pipe(
|
|
43
43
|
ChildProcess.string,
|
|
44
|
-
Effect.flatMap(Schema.decodeEffect(
|
|
44
|
+
Effect.flatMap(Schema.decodeEffect(PullRequestDataFromJson)),
|
|
45
45
|
Effect.map((data) => {
|
|
46
46
|
const comments =
|
|
47
|
-
data.data.repository.pullRequest.comments.
|
|
48
|
-
(
|
|
47
|
+
data.data.repository.pullRequest.comments.nodes.filter(
|
|
48
|
+
(c) => !c.author.login.startsWith("github"),
|
|
49
49
|
)
|
|
50
|
-
const
|
|
51
|
-
data.data.repository.pullRequest.
|
|
52
|
-
(
|
|
50
|
+
const reviews =
|
|
51
|
+
data.data.repository.pullRequest.reviews.nodes.filter(
|
|
52
|
+
(r) => r.body.trim().length > 0,
|
|
53
53
|
)
|
|
54
|
-
|
|
54
|
+
const reviewThreads =
|
|
55
|
+
data.data.repository.pullRequest.reviewThreads.nodes
|
|
56
|
+
return { comments, reviews, reviewThreads } as const
|
|
55
57
|
}),
|
|
56
58
|
Effect.provideService(
|
|
57
59
|
ChildProcessSpawner.ChildProcessSpawner,
|
|
@@ -61,12 +63,16 @@ export class GithubCli extends ServiceMap.Service<GithubCli>()(
|
|
|
61
63
|
|
|
62
64
|
const prFeedbackMd = (pr: number) =>
|
|
63
65
|
reviewComments(pr).pipe(
|
|
64
|
-
Effect.map(({ comments, reviewThreads }) => {
|
|
66
|
+
Effect.map(({ comments, reviewThreads, reviews }) => {
|
|
65
67
|
const eligibleReviewThreads = reviewThreads.filter(
|
|
66
68
|
(thread) => thread.shouldDisplayThread,
|
|
67
69
|
)
|
|
68
70
|
|
|
69
|
-
if (
|
|
71
|
+
if (
|
|
72
|
+
comments.length === 0 &&
|
|
73
|
+
eligibleReviewThreads.length === 0 &&
|
|
74
|
+
reviews.length === 0
|
|
75
|
+
) {
|
|
70
76
|
return `No review comments found.`
|
|
71
77
|
}
|
|
72
78
|
|
|
@@ -90,6 +96,23 @@ Comments are rendered in XML format.`
|
|
|
90
96
|
${reviewCommentsMd}`
|
|
91
97
|
}
|
|
92
98
|
|
|
99
|
+
if (reviews.length > 0) {
|
|
100
|
+
const reviewsXml = reviews
|
|
101
|
+
.map(
|
|
102
|
+
(review) => `<review author="${review.author.login}">
|
|
103
|
+
<body><![CDATA[${review.body}]]></body>
|
|
104
|
+
</review>`,
|
|
105
|
+
)
|
|
106
|
+
.join("\n")
|
|
107
|
+
content += `
|
|
108
|
+
|
|
109
|
+
## Reviews
|
|
110
|
+
|
|
111
|
+
<reviews>
|
|
112
|
+
${reviewsXml}
|
|
113
|
+
</reviews>`
|
|
114
|
+
}
|
|
115
|
+
|
|
93
116
|
if (comments.length > 0) {
|
|
94
117
|
const generalCommentsXml = comments
|
|
95
118
|
.map((comment) => renderGeneralComment(comment))
|
|
@@ -155,45 +178,54 @@ const renderGeneralComment = (
|
|
|
155
178
|
|
|
156
179
|
// Schema definitions and GraphQL query
|
|
157
180
|
|
|
158
|
-
const
|
|
181
|
+
const PullRequestDataFromJson = Schema.fromJsonString(GithubPullRequestData)
|
|
159
182
|
|
|
160
183
|
const githubReviewCommentsQuery = `
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
isResolved
|
|
172
|
-
comments(first: 100) {
|
|
173
|
-
nodes {
|
|
174
|
-
id
|
|
175
|
-
author { login }
|
|
176
|
-
body
|
|
177
|
-
path
|
|
178
|
-
originalLine
|
|
179
|
-
diffHunk
|
|
180
|
-
createdAt
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
+
query FetchPRComments($owner: String!, $repo: String!, $pr: Int!) {
|
|
185
|
+
repository(owner: $owner, name: $repo) {
|
|
186
|
+
pullRequest(number: $pr) {
|
|
187
|
+
url
|
|
188
|
+
reviewDecision
|
|
189
|
+
reviews(first: 100) {
|
|
190
|
+
nodes {
|
|
191
|
+
id
|
|
192
|
+
author {
|
|
193
|
+
login
|
|
184
194
|
}
|
|
195
|
+
body
|
|
185
196
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
197
|
+
}
|
|
198
|
+
reviewThreads(first: 100) {
|
|
199
|
+
nodes {
|
|
200
|
+
isCollapsed
|
|
201
|
+
isOutdated
|
|
202
|
+
isResolved
|
|
203
|
+
comments(first: 100) {
|
|
204
|
+
nodes {
|
|
189
205
|
id
|
|
206
|
+
author {
|
|
207
|
+
login
|
|
208
|
+
}
|
|
190
209
|
body
|
|
191
|
-
|
|
210
|
+
path
|
|
211
|
+
originalLine
|
|
212
|
+
diffHunk
|
|
192
213
|
createdAt
|
|
193
214
|
}
|
|
194
215
|
}
|
|
195
216
|
}
|
|
196
217
|
}
|
|
218
|
+
comments(first: 100) {
|
|
219
|
+
nodes {
|
|
220
|
+
id
|
|
221
|
+
body
|
|
222
|
+
author {
|
|
223
|
+
login
|
|
224
|
+
}
|
|
225
|
+
createdAt
|
|
226
|
+
}
|
|
227
|
+
}
|
|
197
228
|
}
|
|
198
229
|
}
|
|
230
|
+
}
|
|
199
231
|
`
|
package/src/PromptGen.ts
CHANGED
|
@@ -233,9 +233,15 @@ permission.
|
|
|
233
233
|
${prdNotes(options)}`
|
|
234
234
|
|
|
235
235
|
const planPrompt = (options: {
|
|
236
|
+
readonly plan: string
|
|
236
237
|
readonly specsDirectory: string
|
|
237
|
-
}) =>
|
|
238
|
-
|
|
238
|
+
}) => `<request><![CDATA[
|
|
239
|
+
${options.plan}
|
|
240
|
+
]]></request>
|
|
241
|
+
|
|
242
|
+
## Instructions
|
|
243
|
+
|
|
244
|
+
1. Your job is to create a detailed specification to fulfill the request and save it as a file.
|
|
239
245
|
First do some research to understand the request, then interview the user
|
|
240
246
|
to gather all the necessary requirements and details for the specification.
|
|
241
247
|
- If the user asks you to update an existing specification, find the relevant
|
package/src/commands/edit.ts
CHANGED
|
@@ -1,27 +1,20 @@
|
|
|
1
1
|
import { Command } from "effect/unstable/cli"
|
|
2
2
|
import { Effect, Layer } from "effect"
|
|
3
|
-
import { ChildProcess } from "effect/unstable/process"
|
|
4
3
|
import { Prd } from "../Prd.ts"
|
|
5
|
-
import { configEditor } from "../shared/config.ts"
|
|
6
4
|
import { layerProjectIdPrompt } from "../Projects.ts"
|
|
5
|
+
import { Editor } from "../Editor.ts"
|
|
7
6
|
|
|
8
7
|
const handler = Command.withHandler(
|
|
9
8
|
Effect.fnUntraced(
|
|
10
9
|
function* () {
|
|
11
10
|
const prd = yield* Prd
|
|
12
|
-
const editor = yield*
|
|
13
|
-
|
|
14
|
-
yield* ChildProcess.make(editor[0]!, [...editor.slice(1), prd.path], {
|
|
15
|
-
extendEnv: true,
|
|
16
|
-
stdin: "inherit",
|
|
17
|
-
stdout: "inherit",
|
|
18
|
-
stderr: "inherit",
|
|
19
|
-
}).pipe(ChildProcess.exitCode)
|
|
11
|
+
const editor = yield* Editor
|
|
12
|
+
yield* editor.edit(prd.path)
|
|
20
13
|
},
|
|
21
|
-
Effect.
|
|
22
|
-
Effect.provide(
|
|
14
|
+
Effect.provide([
|
|
23
15
|
Prd.layerLocalProvided.pipe(Layer.provideMerge(layerProjectIdPrompt)),
|
|
24
|
-
|
|
16
|
+
Editor.layer,
|
|
17
|
+
]),
|
|
25
18
|
),
|
|
26
19
|
)
|
|
27
20
|
|
package/src/commands/issue.ts
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { Command } from "effect/unstable/cli"
|
|
2
2
|
import { CurrentIssueSource } from "../CurrentIssueSource.ts"
|
|
3
|
-
import { Effect,
|
|
3
|
+
import { Effect, flow, Layer, Option, Schema } from "effect"
|
|
4
4
|
import { IssueSource } from "../IssueSource.ts"
|
|
5
|
-
import { ChildProcess } from "effect/unstable/process"
|
|
6
5
|
import { PrdIssue } from "../domain/PrdIssue.ts"
|
|
7
6
|
import * as Yaml from "yaml"
|
|
8
|
-
import { configEditor } from "../shared/config.ts"
|
|
9
7
|
import { CurrentProjectId } from "../Settings.ts"
|
|
10
8
|
import { layerProjectIdPrompt } from "../Projects.ts"
|
|
9
|
+
import { Editor } from "../Editor.ts"
|
|
11
10
|
|
|
12
11
|
const issueTemplate = `---
|
|
13
12
|
title: Issue Title
|
|
@@ -33,31 +32,18 @@ const handler = flow(
|
|
|
33
32
|
Command.withHandler(
|
|
34
33
|
Effect.fnUntraced(function* () {
|
|
35
34
|
const source = yield* IssueSource
|
|
36
|
-
const fs = yield* FileSystem.FileSystem
|
|
37
35
|
const projectId = yield* CurrentProjectId
|
|
38
|
-
const
|
|
36
|
+
const editor = yield* Editor
|
|
37
|
+
|
|
38
|
+
const content = yield* editor.editTemp({
|
|
39
39
|
suffix: ".md",
|
|
40
|
+
initialContent: issueTemplate,
|
|
40
41
|
})
|
|
41
|
-
|
|
42
|
-
yield* fs.writeFileString(tempFile, issueTemplate)
|
|
43
|
-
|
|
44
|
-
const exitCode = yield* ChildProcess.make(
|
|
45
|
-
editor[0]!,
|
|
46
|
-
[...editor.slice(1), tempFile],
|
|
47
|
-
{
|
|
48
|
-
stdin: "inherit",
|
|
49
|
-
stdout: "inherit",
|
|
50
|
-
stderr: "inherit",
|
|
51
|
-
},
|
|
52
|
-
).pipe(ChildProcess.exitCode)
|
|
53
|
-
if (exitCode !== 0) return
|
|
54
|
-
|
|
55
|
-
const content = yield* fs.readFileString(tempFile)
|
|
56
|
-
if (content.trim() === issueTemplate.trim()) {
|
|
42
|
+
if (Option.isNone(content)) {
|
|
57
43
|
return
|
|
58
44
|
}
|
|
59
45
|
|
|
60
|
-
const lines = content.split("\n")
|
|
46
|
+
const lines = content.value.split("\n")
|
|
61
47
|
const yamlLines: string[] = []
|
|
62
48
|
let descriptionStartIndex = 0
|
|
63
49
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -93,7 +79,13 @@ const handler = flow(
|
|
|
93
79
|
console.log(`URL: ${created.url}`)
|
|
94
80
|
}, Effect.scoped),
|
|
95
81
|
),
|
|
96
|
-
Command.provide(
|
|
82
|
+
Command.provide(
|
|
83
|
+
Layer.mergeAll(
|
|
84
|
+
layerProjectIdPrompt,
|
|
85
|
+
CurrentIssueSource.layer,
|
|
86
|
+
Editor.layer,
|
|
87
|
+
),
|
|
88
|
+
),
|
|
97
89
|
)
|
|
98
90
|
|
|
99
91
|
export const commandIssue = Command.make("issue").pipe(
|
package/src/commands/plan.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Data,
|
|
3
|
-
Effect,
|
|
4
|
-
FileSystem,
|
|
5
|
-
Layer,
|
|
6
|
-
Option,
|
|
7
|
-
Path,
|
|
8
|
-
pipe,
|
|
9
|
-
Schema,
|
|
10
|
-
} from "effect"
|
|
1
|
+
import { Data, Effect, FileSystem, Option, Path, pipe, Schema } from "effect"
|
|
11
2
|
import { PromptGen } from "../PromptGen.ts"
|
|
12
3
|
import { Prd } from "../Prd.ts"
|
|
13
4
|
import { Worktree } from "../Worktree.ts"
|
|
@@ -17,14 +8,11 @@ import { Command, Flag } from "effect/unstable/cli"
|
|
|
17
8
|
import { CurrentIssueSource } from "../CurrentIssueSource.ts"
|
|
18
9
|
import { commandRoot } from "./root.ts"
|
|
19
10
|
import { CurrentProjectId, Settings } from "../Settings.ts"
|
|
20
|
-
import {
|
|
21
|
-
addOrUpdateProject,
|
|
22
|
-
layerProjectIdPrompt,
|
|
23
|
-
selectProject,
|
|
24
|
-
} from "../Projects.ts"
|
|
11
|
+
import { addOrUpdateProject, selectProject } from "../Projects.ts"
|
|
25
12
|
import { agentPlanner } from "../Agents/planner.ts"
|
|
26
13
|
import { agentTasker } from "../Agents/tasker.ts"
|
|
27
14
|
import { commandPlanTasks } from "./plan/tasks.ts"
|
|
15
|
+
import { Editor } from "../Editor.ts"
|
|
28
16
|
|
|
29
17
|
const dangerous = Flag.boolean("dangerous").pipe(
|
|
30
18
|
Flag.withAlias("d"),
|
|
@@ -46,25 +34,36 @@ export const commandPlan = Command.make("plan", {
|
|
|
46
34
|
Command.withHandler(
|
|
47
35
|
Effect.fnUntraced(
|
|
48
36
|
function* ({ dangerous, withNewProject }) {
|
|
37
|
+
const editor = yield* Editor
|
|
38
|
+
|
|
39
|
+
const thePlan = yield* editor.editTemp({
|
|
40
|
+
suffix: ".md",
|
|
41
|
+
})
|
|
42
|
+
if (Option.isNone(thePlan)) return
|
|
43
|
+
|
|
49
44
|
const project = withNewProject
|
|
50
45
|
? yield* addOrUpdateProject()
|
|
51
46
|
: yield* selectProject
|
|
52
47
|
const { specsDirectory } = yield* commandRoot
|
|
53
48
|
const commandPrefix = yield* getCommandPrefix
|
|
49
|
+
|
|
54
50
|
yield* plan({
|
|
51
|
+
plan: thePlan.value,
|
|
55
52
|
specsDirectory,
|
|
56
53
|
targetBranch: project.targetBranch,
|
|
57
54
|
commandPrefix,
|
|
58
55
|
dangerous,
|
|
59
56
|
}).pipe(Effect.provideService(CurrentProjectId, project.id))
|
|
60
57
|
},
|
|
61
|
-
Effect.provide([Settings.layer, CurrentIssueSource.layer]),
|
|
58
|
+
Effect.provide([Settings.layer, CurrentIssueSource.layer, Editor.layer]),
|
|
62
59
|
),
|
|
63
60
|
),
|
|
64
61
|
Command.withSubcommands([commandPlanTasks]),
|
|
65
62
|
)
|
|
63
|
+
|
|
66
64
|
const plan = Effect.fnUntraced(
|
|
67
65
|
function* (options: {
|
|
66
|
+
readonly plan: string
|
|
68
67
|
readonly specsDirectory: string
|
|
69
68
|
readonly targetBranch: Option.Option<string>
|
|
70
69
|
readonly commandPrefix: (
|
|
@@ -75,23 +74,27 @@ const plan = Effect.fnUntraced(
|
|
|
75
74
|
const fs = yield* FileSystem.FileSystem
|
|
76
75
|
const pathService = yield* Path.Path
|
|
77
76
|
const worktree = yield* Worktree
|
|
77
|
+
|
|
78
78
|
const cliAgent = yield* getOrSelectCliAgent
|
|
79
79
|
|
|
80
80
|
yield* agentPlanner({
|
|
81
|
+
plan: options.plan,
|
|
81
82
|
specsDirectory: options.specsDirectory,
|
|
82
83
|
commandPrefix: options.commandPrefix,
|
|
83
84
|
dangerous: options.dangerous,
|
|
84
85
|
cliAgent,
|
|
85
86
|
})
|
|
86
87
|
|
|
87
|
-
yield* Effect.log("Converting specification into tasks")
|
|
88
88
|
const planDetails = yield* pipe(
|
|
89
89
|
fs.readFileString(
|
|
90
90
|
pathService.join(worktree.directory, ".lalph", "plan.json"),
|
|
91
91
|
),
|
|
92
92
|
Effect.flatMap(Schema.decodeEffect(PlanDetails)),
|
|
93
|
+
Effect.mapError(() => new SpecNotFound()),
|
|
93
94
|
)
|
|
94
95
|
|
|
96
|
+
yield* Effect.log("Converting specification into tasks")
|
|
97
|
+
|
|
95
98
|
yield* agentTasker({
|
|
96
99
|
specificationPath: planDetails.specification,
|
|
97
100
|
specsDirectory: options.specsDirectory,
|
|
@@ -114,7 +117,7 @@ const plan = Effect.fnUntraced(
|
|
|
114
117
|
Effect.provide([
|
|
115
118
|
PromptGen.layer,
|
|
116
119
|
Prd.layerProvided,
|
|
117
|
-
Worktree.layer
|
|
120
|
+
Worktree.layer,
|
|
118
121
|
Settings.layer,
|
|
119
122
|
CurrentIssueSource.layer,
|
|
120
123
|
]),
|
|
@@ -1,62 +1,70 @@
|
|
|
1
1
|
import * as S from "effect/Schema"
|
|
2
2
|
|
|
3
|
-
export class Author extends S.Class<Author>("
|
|
3
|
+
export class Author extends S.Class<Author>("Author")({
|
|
4
4
|
login: S.String,
|
|
5
5
|
}) {}
|
|
6
6
|
|
|
7
|
-
export class Comment extends S.Class<Comment>("
|
|
7
|
+
export class Comment extends S.Class<Comment>("Comment")({
|
|
8
8
|
id: S.String,
|
|
9
9
|
body: S.String,
|
|
10
10
|
author: Author,
|
|
11
|
-
|
|
12
|
-
}) {}
|
|
13
|
-
|
|
14
|
-
export class CommentsEdge extends S.Class<CommentsEdge>("github/CommentsEdge")({
|
|
15
|
-
node: Comment,
|
|
11
|
+
createdAt: S.String,
|
|
16
12
|
}) {}
|
|
17
13
|
|
|
18
14
|
export class PullRequestComments extends S.Class<PullRequestComments>(
|
|
19
15
|
"PullRequestComments",
|
|
20
16
|
)({
|
|
21
|
-
|
|
17
|
+
nodes: S.Array(Comment),
|
|
22
18
|
}) {}
|
|
23
19
|
|
|
24
|
-
export class PullRequest extends S.Class<PullRequest>("
|
|
20
|
+
export class PullRequest extends S.Class<PullRequest>("PullRequest")({
|
|
25
21
|
url: S.String,
|
|
22
|
+
reviewDecision: S.Null,
|
|
23
|
+
reviews: S.suspend(() => Reviews),
|
|
26
24
|
reviewThreads: S.suspend(() => ReviewThreads),
|
|
27
25
|
comments: PullRequestComments,
|
|
28
26
|
}) {}
|
|
29
27
|
|
|
30
|
-
export class Repository extends S.Class<Repository>("
|
|
28
|
+
export class Repository extends S.Class<Repository>("Repository")({
|
|
31
29
|
pullRequest: PullRequest,
|
|
32
30
|
}) {}
|
|
33
31
|
|
|
34
|
-
export class Data extends S.Class<Data>("
|
|
32
|
+
export class Data extends S.Class<Data>("Data")({
|
|
35
33
|
repository: Repository,
|
|
36
34
|
}) {}
|
|
37
35
|
|
|
38
|
-
export class
|
|
36
|
+
export class GithubPullRequestData extends S.Class<GithubPullRequestData>(
|
|
37
|
+
"GithubPullRequestData",
|
|
38
|
+
)({
|
|
39
39
|
data: Data,
|
|
40
40
|
}) {}
|
|
41
41
|
|
|
42
|
-
export class
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
export class Review extends S.Class<Review>("Review")({
|
|
43
|
+
id: S.String,
|
|
44
|
+
author: Author,
|
|
45
|
+
body: S.String,
|
|
46
|
+
}) {}
|
|
47
|
+
|
|
48
|
+
export class Reviews extends S.Class<Reviews>("Reviews")({
|
|
49
|
+
nodes: S.Array(Review),
|
|
50
|
+
}) {}
|
|
51
|
+
|
|
52
|
+
export class ReviewComment extends S.Class<ReviewComment>("ReviewComment")({
|
|
45
53
|
id: S.String,
|
|
46
54
|
author: Author,
|
|
47
55
|
body: S.String,
|
|
48
56
|
path: S.String,
|
|
49
|
-
originalLine: S.
|
|
57
|
+
originalLine: S.Number,
|
|
50
58
|
diffHunk: S.String,
|
|
51
|
-
|
|
59
|
+
createdAt: S.String,
|
|
52
60
|
}) {}
|
|
53
61
|
|
|
54
|
-
export class NodeComments extends S.Class<NodeComments>("
|
|
62
|
+
export class NodeComments extends S.Class<NodeComments>("NodeComments")({
|
|
55
63
|
nodes: S.Array(ReviewComment),
|
|
56
64
|
}) {}
|
|
57
65
|
|
|
58
|
-
export class
|
|
59
|
-
"
|
|
66
|
+
export class ReviewThreadsNode extends S.Class<ReviewThreadsNode>(
|
|
67
|
+
"ReviewThreadsNode",
|
|
60
68
|
)({
|
|
61
69
|
isCollapsed: S.Boolean,
|
|
62
70
|
isOutdated: S.Boolean,
|
|
@@ -67,14 +75,6 @@ export class ReviewThreadNode extends S.Class<ReviewThreadNode>(
|
|
|
67
75
|
readonly shouldDisplayThread = !this.isCollapsed && !this.isOutdated
|
|
68
76
|
}
|
|
69
77
|
|
|
70
|
-
export class
|
|
71
|
-
|
|
72
|
-
)({
|
|
73
|
-
node: ReviewThreadNode,
|
|
74
|
-
}) {}
|
|
75
|
-
|
|
76
|
-
export class ReviewThreads extends S.Class<ReviewThreads>(
|
|
77
|
-
"github/ReviewThreads",
|
|
78
|
-
)({
|
|
79
|
-
edges: S.Array(ReviewThreadsEdge),
|
|
78
|
+
export class ReviewThreads extends S.Class<ReviewThreads>("ReviewThreads")({
|
|
79
|
+
nodes: S.Array(ReviewThreadsNode),
|
|
80
80
|
}) {}
|