lalph 0.2.19 → 0.2.21
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 +94 -85
- package/package.json +1 -1
- package/src/Agents/planner.ts +1 -0
- package/src/Editor.ts +66 -0
- package/src/GitFlow.ts +13 -11
- package/src/PromptGen.ts +9 -2
- package/src/commands/edit.ts +6 -13
- package/src/commands/issue.ts +25 -37
- package/src/commands/plan.ts +21 -18
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
|
})));
|
|
@@ -151016,6 +150984,7 @@ ${options.task.description}
|
|
|
151016
150984
|
5. ${options.gitFlow.commitInstructions({
|
|
151017
150985
|
githubPrInstructions: sourceMeta.githubPrInstructions,
|
|
151018
150986
|
githubPrNumber: options.githubPrNumber,
|
|
150987
|
+
taskId: options.task.id ?? "unknown",
|
|
151019
150988
|
targetBranch: options.targetBranch
|
|
151020
150989
|
})}
|
|
151021
150990
|
6. **After ${options.gitFlow.requiresGithubPr ? "pushing" : "committing"}**
|
|
@@ -151063,8 +151032,13 @@ permission.
|
|
|
151063
151032
|
5. If any specifications need updating based on your new understanding, update them.
|
|
151064
151033
|
|
|
151065
151034
|
${prdNotes(options)}`;
|
|
151066
|
-
const planPrompt = (options) =>
|
|
151067
|
-
|
|
151035
|
+
const planPrompt = (options) => `<request><![CDATA[
|
|
151036
|
+
${options.plan}
|
|
151037
|
+
]]></request>
|
|
151038
|
+
|
|
151039
|
+
## Instructions
|
|
151040
|
+
|
|
151041
|
+
1. Your job is to create a detailed specification to fulfill the request and save it as a file.
|
|
151068
151042
|
First do some research to understand the request, then interview the user
|
|
151069
151043
|
to gather all the necessary requirements and details for the specification.
|
|
151070
151044
|
- If the user asks you to update an existing specification, find the relevant
|
|
@@ -151733,9 +151707,9 @@ const GitFlowPR = succeed$2(GitFlow, GitFlow.of({
|
|
|
151733
151707
|
branch: void 0,
|
|
151734
151708
|
setupInstructions: ({ githubPrNumber }) => githubPrNumber ? `The Github PR #${githubPrNumber} has been detected for this task and the branch has been checked out.
|
|
151735
151709
|
- Review feedback in the .lalph/feedback.md file (same folder as the prd.yml file).` : `Create a new branch for the task using the format \`{task id}/description\`, using the current HEAD as the base (don't checkout any other branches first).`,
|
|
151736
|
-
commitInstructions: (
|
|
151737
|
-
${githubPrInstructions}
|
|
151738
|
-
The PR description should include a summary of the changes made.${targetBranch ? `\n - The target branch for the PR should be \`${targetBranch}\`.` : ""}
|
|
151710
|
+
commitInstructions: (options) => `${!options.githubPrNumber ? `Create a pull request for this task. If the target branch does not exist, create it first.` : "Commit and push your changes to the pull request."}
|
|
151711
|
+
${options.githubPrInstructions}
|
|
151712
|
+
The PR description should include a summary of the changes made.${options.targetBranch ? `\n - The target branch for the PR should be \`${options.targetBranch}\`.` : ""}
|
|
151739
151713
|
- **DO NOT** commit any of the files in the \`.lalph\` directory.
|
|
151740
151714
|
- You have full permission to push branches, create PRs or create git commits.`,
|
|
151741
151715
|
reviewInstructions: `You are already on the PR branch with their changes.
|
|
@@ -151768,13 +151742,15 @@ const GitFlowCommit = effect$1(GitFlow, gen(function* () {
|
|
|
151768
151742
|
requiresGithubPr: false,
|
|
151769
151743
|
branch: `lalph/worker-${workerState.id}`,
|
|
151770
151744
|
setupInstructions: () => `You are already on a new branch for this task. You do not need to checkout any other branches.`,
|
|
151771
|
-
commitInstructions: () => `When you have completed your changes, **you must** commit them to the current local branch. Do not git push your changes or switch branches.
|
|
151745
|
+
commitInstructions: (options) => `When you have completed your changes, **you must** commit them to the current local branch. Do not git push your changes or switch branches.
|
|
151746
|
+
- Include \`References ${options.taskId}\` in each commit message.
|
|
151772
151747
|
- **DO NOT** commit any of the files in the \`.lalph\` directory.`,
|
|
151773
151748
|
reviewInstructions: `You are already on the branch with their changes.
|
|
151774
151749
|
After making any changes, commit them to the same branch. Do not git push your changes or switch branches.
|
|
151775
151750
|
|
|
151776
|
-
-
|
|
151777
|
-
-
|
|
151751
|
+
- Include \`References {task id}\` in each commit message.
|
|
151752
|
+
- **DO NOT** commit any of the files in the \`.lalph\` directory.
|
|
151753
|
+
- You have full permission to create git commits.`,
|
|
151778
151754
|
postWork: fnUntraced(function* ({ worktree, targetBranch, issueId }) {
|
|
151779
151755
|
if (!targetBranch) return yield* logWarning("GitFlowCommit: No target branch specified, skipping postWork.");
|
|
151780
151756
|
const prd = yield* Prd;
|
|
@@ -152120,6 +152096,41 @@ const commandPlanTasks = make$35("tasks", { specificationPath }).pipe(withDescri
|
|
|
152120
152096
|
Worktree.layer.pipe(provide$3(layerProjectIdPrompt))
|
|
152121
152097
|
]))));
|
|
152122
152098
|
|
|
152099
|
+
//#endregion
|
|
152100
|
+
//#region src/shared/config.ts
|
|
152101
|
+
const configEditor = string$1("LALPH_EDITOR").pipe(orElse(() => string$1("EDITOR")), map$5(parseCommand), withDefault$3(() => ["nano"]));
|
|
152102
|
+
|
|
152103
|
+
//#endregion
|
|
152104
|
+
//#region src/Editor.ts
|
|
152105
|
+
var Editor = class extends Service$1()("lalph/Editor", { make: gen(function* () {
|
|
152106
|
+
const fs = yield* FileSystem;
|
|
152107
|
+
const editor = yield* configEditor;
|
|
152108
|
+
const spawner = yield* ChildProcessSpawner;
|
|
152109
|
+
const edit = (path) => make$23(editor[0], [...editor.slice(1), path], {
|
|
152110
|
+
stdin: "inherit",
|
|
152111
|
+
stdout: "inherit",
|
|
152112
|
+
stderr: "inherit"
|
|
152113
|
+
}).pipe(exitCode, provideService(ChildProcessSpawner, spawner), orDie$2);
|
|
152114
|
+
return {
|
|
152115
|
+
edit,
|
|
152116
|
+
editTemp: fnUntraced(function* (options) {
|
|
152117
|
+
const initialContent = options.initialContent ?? "";
|
|
152118
|
+
const file = yield* fs.makeTempFileScoped({ suffix: options.suffix ?? ".txt" });
|
|
152119
|
+
if (initialContent) yield* fs.writeFileString(file, initialContent);
|
|
152120
|
+
if ((yield* make$23(editor[0], [...editor.slice(1), file], {
|
|
152121
|
+
stdin: "inherit",
|
|
152122
|
+
stdout: "inherit",
|
|
152123
|
+
stderr: "inherit"
|
|
152124
|
+
}).pipe(exitCode)) !== 0) return yield* new NoSuchElementError();
|
|
152125
|
+
const content = (yield* fs.readFileString(file)).trim();
|
|
152126
|
+
if (content === initialContent) return yield* new NoSuchElementError();
|
|
152127
|
+
return content;
|
|
152128
|
+
}, scoped$1, provideService(ChildProcessSpawner, spawner), option$1)
|
|
152129
|
+
};
|
|
152130
|
+
}) }) {
|
|
152131
|
+
static layer = effect$1(this, this.make).pipe(provide$3(PlatformServices));
|
|
152132
|
+
};
|
|
152133
|
+
|
|
152123
152134
|
//#endregion
|
|
152124
152135
|
//#region src/commands/plan.ts
|
|
152125
152136
|
const dangerous = boolean("dangerous").pipe(withAlias("d"), withDescription$1("Enable dangerous mode (skip permission prompts) during plan generation"));
|
|
@@ -152128,30 +152139,39 @@ const commandPlan = make$35("plan", {
|
|
|
152128
152139
|
dangerous,
|
|
152129
152140
|
withNewProject
|
|
152130
152141
|
}).pipe(withDescription("Iterate on an issue plan and create PRD tasks"), withHandler(fnUntraced(function* ({ dangerous, withNewProject }) {
|
|
152142
|
+
const thePlan = yield* (yield* Editor).editTemp({ suffix: ".md" });
|
|
152143
|
+
if (isNone(thePlan)) return;
|
|
152131
152144
|
const project = withNewProject ? yield* addOrUpdateProject() : yield* selectProject;
|
|
152132
152145
|
const { specsDirectory } = yield* commandRoot;
|
|
152133
152146
|
const commandPrefix = yield* getCommandPrefix;
|
|
152134
152147
|
yield* plan({
|
|
152148
|
+
plan: thePlan.value,
|
|
152135
152149
|
specsDirectory,
|
|
152136
152150
|
targetBranch: project.targetBranch,
|
|
152137
152151
|
commandPrefix,
|
|
152138
152152
|
dangerous
|
|
152139
152153
|
}).pipe(provideService(CurrentProjectId, project.id));
|
|
152140
|
-
}, provide$1([
|
|
152154
|
+
}, provide$1([
|
|
152155
|
+
Settings.layer,
|
|
152156
|
+
CurrentIssueSource.layer,
|
|
152157
|
+
Editor.layer
|
|
152158
|
+
]))), withSubcommands([commandPlanTasks]));
|
|
152141
152159
|
const plan = fnUntraced(function* (options) {
|
|
152142
152160
|
const fs = yield* FileSystem;
|
|
152143
152161
|
const pathService = yield* Path$1;
|
|
152144
152162
|
const worktree = yield* Worktree;
|
|
152145
152163
|
const cliAgent = yield* getOrSelectCliAgent;
|
|
152146
152164
|
yield* agentPlanner({
|
|
152165
|
+
plan: options.plan,
|
|
152147
152166
|
specsDirectory: options.specsDirectory,
|
|
152148
152167
|
commandPrefix: options.commandPrefix,
|
|
152149
152168
|
dangerous: options.dangerous,
|
|
152150
152169
|
cliAgent
|
|
152151
152170
|
});
|
|
152171
|
+
const planDetails = yield* pipe(fs.readFileString(pathService.join(worktree.directory, ".lalph", "plan.json")), flatMap$2(decodeEffect(PlanDetails)), mapError$2(() => new SpecNotFound()));
|
|
152152
152172
|
yield* log$1("Converting specification into tasks");
|
|
152153
152173
|
yield* agentTasker({
|
|
152154
|
-
specificationPath:
|
|
152174
|
+
specificationPath: planDetails.specification,
|
|
152155
152175
|
specsDirectory: options.specsDirectory,
|
|
152156
152176
|
commandPrefix: options.commandPrefix,
|
|
152157
152177
|
cliAgent
|
|
@@ -152160,16 +152180,15 @@ const plan = fnUntraced(function* (options) {
|
|
|
152160
152180
|
}, scoped$1, provide$1([
|
|
152161
152181
|
PromptGen.layer,
|
|
152162
152182
|
Prd.layerProvided,
|
|
152163
|
-
Worktree.layer
|
|
152183
|
+
Worktree.layer,
|
|
152164
152184
|
Settings.layer,
|
|
152165
152185
|
CurrentIssueSource.layer
|
|
152166
152186
|
]));
|
|
152187
|
+
var SpecNotFound = class extends TaggedError("SpecNotFound") {
|
|
152188
|
+
message = "The AI agent failed to produce a specification.";
|
|
152189
|
+
};
|
|
152167
152190
|
const PlanDetails = fromJsonString(Struct({ specification: String$1 }));
|
|
152168
152191
|
|
|
152169
|
-
//#endregion
|
|
152170
|
-
//#region src/shared/config.ts
|
|
152171
|
-
const configEditor = string$1("LALPH_EDITOR").pipe(orElse(() => string$1("EDITOR")), map$5(parseCommand), withDefault$3(() => ["nano"]));
|
|
152172
|
-
|
|
152173
152192
|
//#endregion
|
|
152174
152193
|
//#region src/commands/issue.ts
|
|
152175
152194
|
const issueTemplate = `---
|
|
@@ -152189,20 +152208,12 @@ const FrontMatterSchema = toCodecJson(Struct({
|
|
|
152189
152208
|
autoMerge: Boolean$2
|
|
152190
152209
|
}));
|
|
152191
152210
|
const handler$1 = flow(withHandler(fnUntraced(function* () {
|
|
152192
|
-
const
|
|
152193
|
-
|
|
152194
|
-
|
|
152195
|
-
|
|
152196
|
-
|
|
152197
|
-
|
|
152198
|
-
if ((yield* make$23(editor[0], [...editor.slice(1), tempFile], {
|
|
152199
|
-
stdin: "inherit",
|
|
152200
|
-
stdout: "inherit",
|
|
152201
|
-
stderr: "inherit"
|
|
152202
|
-
}).pipe(exitCode)) !== 0) return;
|
|
152203
|
-
const content = yield* fs.readFileString(tempFile);
|
|
152204
|
-
if (content.trim() === issueTemplate.trim()) return;
|
|
152205
|
-
const lines = content.split("\n");
|
|
152211
|
+
const content = yield* (yield* Editor).editTemp({
|
|
152212
|
+
suffix: ".md",
|
|
152213
|
+
initialContent: issueTemplate
|
|
152214
|
+
});
|
|
152215
|
+
if (isNone(content)) return;
|
|
152216
|
+
const lines = content.value.split("\n");
|
|
152206
152217
|
const yamlLines = [];
|
|
152207
152218
|
let descriptionStartIndex = 0;
|
|
152208
152219
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -152217,15 +152228,19 @@ const handler$1 = flow(withHandler(fnUntraced(function* () {
|
|
|
152217
152228
|
const yamlContent = yamlLines.join("\n");
|
|
152218
152229
|
const frontMatter = yield* decodeEffect(FrontMatterSchema)(import_dist.parse(yamlContent));
|
|
152219
152230
|
const description = lines.slice(descriptionStartIndex).join("\n").trim();
|
|
152220
|
-
|
|
152221
|
-
|
|
152222
|
-
|
|
152223
|
-
|
|
152224
|
-
|
|
152225
|
-
|
|
152226
|
-
|
|
152227
|
-
|
|
152228
|
-
}
|
|
152231
|
+
yield* gen(function* () {
|
|
152232
|
+
const source = yield* IssueSource;
|
|
152233
|
+
const projectId = yield* CurrentProjectId;
|
|
152234
|
+
const created = yield* source.createIssue(projectId, new PrdIssue({
|
|
152235
|
+
id: null,
|
|
152236
|
+
...frontMatter,
|
|
152237
|
+
description,
|
|
152238
|
+
state: "todo"
|
|
152239
|
+
}));
|
|
152240
|
+
console.log(`Created issue with ID: ${created.id}`);
|
|
152241
|
+
console.log(`URL: ${created.url}`);
|
|
152242
|
+
}).pipe(provide$1([layerProjectIdPrompt, CurrentIssueSource.layer]));
|
|
152243
|
+
})), provide(Editor.layer));
|
|
152229
152244
|
const commandIssue = make$35("issue").pipe(withDescription("Create a new issue in the selected issue source"), handler$1);
|
|
152230
152245
|
const commandIssueAlias = make$35("i").pipe(withDescription("Alias for 'issue' command"), handler$1);
|
|
152231
152246
|
|
|
@@ -152233,14 +152248,8 @@ const commandIssueAlias = make$35("i").pipe(withDescription("Alias for 'issue' c
|
|
|
152233
152248
|
//#region src/commands/edit.ts
|
|
152234
152249
|
const handler = withHandler(fnUntraced(function* () {
|
|
152235
152250
|
const prd = yield* Prd;
|
|
152236
|
-
|
|
152237
|
-
|
|
152238
|
-
extendEnv: true,
|
|
152239
|
-
stdin: "inherit",
|
|
152240
|
-
stdout: "inherit",
|
|
152241
|
-
stderr: "inherit"
|
|
152242
|
-
}).pipe(exitCode);
|
|
152243
|
-
}, scoped$1, provide$1(Prd.layerLocalProvided.pipe(provideMerge(layerProjectIdPrompt)))));
|
|
152251
|
+
yield* (yield* Editor).edit(prd.path);
|
|
152252
|
+
}, provide$1([Prd.layerLocalProvided.pipe(provideMerge(layerProjectIdPrompt)), Editor.layer])));
|
|
152244
152253
|
const commandEdit = make$35("edit").pipe(withDescription("Open the prd.yml file in your editor"), handler);
|
|
152245
152254
|
const commandEditAlias = make$35("e").pipe(withDescription("Alias for 'edit' command"), handler);
|
|
152246
152255
|
|
|
@@ -152250,7 +152259,7 @@ const commandSource = make$35("source").pipe(withDescription("Select the issue s
|
|
|
152250
152259
|
|
|
152251
152260
|
//#endregion
|
|
152252
152261
|
//#region package.json
|
|
152253
|
-
var version = "0.2.
|
|
152262
|
+
var version = "0.2.21";
|
|
152254
152263
|
|
|
152255
152264
|
//#endregion
|
|
152256
152265
|
//#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/GitFlow.ts
CHANGED
|
@@ -22,6 +22,7 @@ export class GitFlow extends ServiceMap.Service<
|
|
|
22
22
|
readonly githubPrNumber: number | undefined
|
|
23
23
|
readonly githubPrInstructions: string
|
|
24
24
|
readonly targetBranch: string | undefined
|
|
25
|
+
readonly taskId: string
|
|
25
26
|
}) => string
|
|
26
27
|
readonly reviewInstructions: string
|
|
27
28
|
readonly postWork: (options: {
|
|
@@ -63,13 +64,11 @@ export const GitFlowPR = Layer.succeed(
|
|
|
63
64
|
- Review feedback in the .lalph/feedback.md file (same folder as the prd.yml file).`
|
|
64
65
|
: `Create a new branch for the task using the format \`{task id}/description\`, using the current HEAD as the base (don't checkout any other branches first).`,
|
|
65
66
|
|
|
66
|
-
commitInstructions: (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
${githubPrInstructions}
|
|
72
|
-
The PR description should include a summary of the changes made.${targetBranch ? `\n - The target branch for the PR should be \`${targetBranch}\`.` : ""}
|
|
67
|
+
commitInstructions: (
|
|
68
|
+
options,
|
|
69
|
+
) => `${!options.githubPrNumber ? `Create a pull request for this task. If the target branch does not exist, create it first.` : "Commit and push your changes to the pull request."}
|
|
70
|
+
${options.githubPrInstructions}
|
|
71
|
+
The PR description should include a summary of the changes made.${options.targetBranch ? `\n - The target branch for the PR should be \`${options.targetBranch}\`.` : ""}
|
|
73
72
|
- **DO NOT** commit any of the files in the \`.lalph\` directory.
|
|
74
73
|
- You have full permission to push branches, create PRs or create git commits.`,
|
|
75
74
|
|
|
@@ -124,15 +123,18 @@ export const GitFlowCommit = Layer.effect(
|
|
|
124
123
|
setupInstructions: () =>
|
|
125
124
|
`You are already on a new branch for this task. You do not need to checkout any other branches.`,
|
|
126
125
|
|
|
127
|
-
commitInstructions:
|
|
128
|
-
|
|
126
|
+
commitInstructions: (
|
|
127
|
+
options,
|
|
128
|
+
) => `When you have completed your changes, **you must** commit them to the current local branch. Do not git push your changes or switch branches.
|
|
129
|
+
- Include \`References ${options.taskId}\` in each commit message.
|
|
129
130
|
- **DO NOT** commit any of the files in the \`.lalph\` directory.`,
|
|
130
131
|
|
|
131
132
|
reviewInstructions: `You are already on the branch with their changes.
|
|
132
133
|
After making any changes, commit them to the same branch. Do not git push your changes or switch branches.
|
|
133
134
|
|
|
134
|
-
-
|
|
135
|
-
-
|
|
135
|
+
- Include \`References {task id}\` in each commit message.
|
|
136
|
+
- **DO NOT** commit any of the files in the \`.lalph\` directory.
|
|
137
|
+
- You have full permission to create git commits.`,
|
|
136
138
|
|
|
137
139
|
postWork: Effect.fnUntraced(function* ({
|
|
138
140
|
worktree,
|
package/src/PromptGen.ts
CHANGED
|
@@ -172,6 +172,7 @@ ${options.task.description}
|
|
|
172
172
|
5. ${options.gitFlow.commitInstructions({
|
|
173
173
|
githubPrInstructions: sourceMeta.githubPrInstructions,
|
|
174
174
|
githubPrNumber: options.githubPrNumber,
|
|
175
|
+
taskId: options.task.id ?? "unknown",
|
|
175
176
|
targetBranch: options.targetBranch,
|
|
176
177
|
})}
|
|
177
178
|
6. **After ${options.gitFlow.requiresGithubPr ? "pushing" : "committing"}**
|
|
@@ -233,9 +234,15 @@ permission.
|
|
|
233
234
|
${prdNotes(options)}`
|
|
234
235
|
|
|
235
236
|
const planPrompt = (options: {
|
|
237
|
+
readonly plan: string
|
|
236
238
|
readonly specsDirectory: string
|
|
237
|
-
}) =>
|
|
238
|
-
|
|
239
|
+
}) => `<request><![CDATA[
|
|
240
|
+
${options.plan}
|
|
241
|
+
]]></request>
|
|
242
|
+
|
|
243
|
+
## Instructions
|
|
244
|
+
|
|
245
|
+
1. Your job is to create a detailed specification to fulfill the request and save it as a file.
|
|
239
246
|
First do some research to understand the request, then interview the user
|
|
240
247
|
to gather all the necessary requirements and details for the specification.
|
|
241
248
|
- 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, 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
|
|
@@ -32,32 +31,17 @@ const FrontMatterSchema = Schema.toCodecJson(
|
|
|
32
31
|
const handler = flow(
|
|
33
32
|
Command.withHandler(
|
|
34
33
|
Effect.fnUntraced(function* () {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const tempFile = yield* fs.makeTempFileScoped({
|
|
34
|
+
const editor = yield* Editor
|
|
35
|
+
|
|
36
|
+
const content = yield* editor.editTemp({
|
|
39
37
|
suffix: ".md",
|
|
38
|
+
initialContent: issueTemplate,
|
|
40
39
|
})
|
|
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()) {
|
|
40
|
+
if (Option.isNone(content)) {
|
|
57
41
|
return
|
|
58
42
|
}
|
|
59
43
|
|
|
60
|
-
const lines = content.split("\n")
|
|
44
|
+
const lines = content.value.split("\n")
|
|
61
45
|
const yamlLines: string[] = []
|
|
62
46
|
let descriptionStartIndex = 0
|
|
63
47
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -80,20 +64,24 @@ const handler = flow(
|
|
|
80
64
|
)
|
|
81
65
|
const description = lines.slice(descriptionStartIndex).join("\n").trim()
|
|
82
66
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
67
|
+
yield* Effect.gen(function* () {
|
|
68
|
+
const source = yield* IssueSource
|
|
69
|
+
const projectId = yield* CurrentProjectId
|
|
70
|
+
const created = yield* source.createIssue(
|
|
71
|
+
projectId,
|
|
72
|
+
new PrdIssue({
|
|
73
|
+
id: null,
|
|
74
|
+
...frontMatter,
|
|
75
|
+
description,
|
|
76
|
+
state: "todo",
|
|
77
|
+
}),
|
|
78
|
+
)
|
|
79
|
+
console.log(`Created issue with ID: ${created.id}`)
|
|
80
|
+
console.log(`URL: ${created.url}`)
|
|
81
|
+
}).pipe(Effect.provide([layerProjectIdPrompt, CurrentIssueSource.layer]))
|
|
82
|
+
}),
|
|
95
83
|
),
|
|
96
|
-
Command.provide(
|
|
84
|
+
Command.provide(Editor.layer),
|
|
97
85
|
)
|
|
98
86
|
|
|
99
87
|
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
|
]),
|