agentplane 0.3.13 → 0.3.15
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/assets/RUNNER.md +1 -1
- package/assets/agents/ORCHESTRATOR.json +1 -1
- package/assets/agents/SKILL_EXTRACTOR.json +31 -0
- package/assets/framework.manifest.json +7 -0
- package/assets/policy/incidents.md +5 -3
- package/assets/policy/workflow.branch_pr.md +10 -5
- package/dist/.build-manifest.json +280 -180
- package/dist/cli/output.d.ts +29 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +33 -0
- package/dist/cli/run-cli/command-catalog/core.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/core.js +29 -87
- package/dist/cli/run-cli/command-catalog/lifecycle.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/lifecycle.js +4 -12
- package/dist/cli/run-cli/command-catalog/project.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog/project.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/project.js +16 -38
- package/dist/cli/run-cli/command-catalog/shared.d.ts +9 -6
- package/dist/cli/run-cli/command-catalog/shared.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/shared.js +23 -6
- package/dist/cli/run-cli/command-catalog/task.d.ts.map +1 -1
- package/dist/cli/run-cli/command-catalog/task.js +6 -18
- package/dist/cli/run-cli/command-catalog.d.ts +1 -1
- package/dist/cli/run-cli/command-catalog.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/recipes.d.ts.map +1 -1
- package/dist/cli/run-cli/commands/init/recipes.js +1 -0
- package/dist/cli/run-cli.js +1 -1
- package/dist/cli/run-cli.test-helpers.d.ts +1 -74
- package/dist/cli/run-cli.test-helpers.d.ts.map +1 -1
- package/dist/cli/run-cli.test-helpers.js +1 -769
- package/dist/commands/branch/cleanup-merged.d.ts.map +1 -1
- package/dist/commands/branch/cleanup-merged.js +5 -9
- package/dist/commands/branch/work-start.command.d.ts.map +1 -1
- package/dist/commands/branch/work-start.command.js +1 -0
- package/dist/commands/commit.spec.d.ts.map +1 -1
- package/dist/commands/commit.spec.js +2 -0
- package/dist/commands/doctor/branch-pr.d.ts +1 -1
- package/dist/commands/doctor/branch-pr.d.ts.map +1 -1
- package/dist/commands/doctor/branch-pr.js +5 -2
- package/dist/commands/guard/impl/commands.d.ts.map +1 -1
- package/dist/commands/guard/impl/commands.js +4 -1
- package/dist/commands/guard/impl/comment-commit.d.ts.map +1 -1
- package/dist/commands/guard/impl/comment-commit.js +2 -1
- package/dist/commands/guard/impl/env.d.ts +6 -0
- package/dist/commands/guard/impl/env.d.ts.map +1 -1
- package/dist/commands/guard/impl/env.js +41 -0
- package/dist/commands/pr/internal/auto-commit.d.ts.map +1 -1
- package/dist/commands/pr/internal/auto-commit.js +2 -1
- package/dist/commands/pr/internal/sync-branch.d.ts +36 -0
- package/dist/commands/pr/internal/sync-branch.d.ts.map +1 -0
- package/dist/commands/pr/internal/sync-branch.js +113 -0
- package/dist/commands/pr/internal/sync-github.d.ts +28 -0
- package/dist/commands/pr/internal/sync-github.d.ts.map +1 -0
- package/dist/commands/pr/internal/sync-github.js +178 -0
- package/dist/commands/pr/internal/sync-model.d.ts +36 -0
- package/dist/commands/pr/internal/sync-model.d.ts.map +1 -0
- package/dist/commands/pr/internal/sync-model.js +1 -0
- package/dist/commands/pr/internal/sync-open-step.d.ts +10 -0
- package/dist/commands/pr/internal/sync-open-step.d.ts.map +1 -0
- package/dist/commands/pr/internal/sync-open-step.js +128 -0
- package/dist/commands/pr/internal/sync-support.d.ts +7 -0
- package/dist/commands/pr/internal/sync-support.d.ts.map +1 -0
- package/dist/commands/pr/internal/sync-support.js +29 -0
- package/dist/commands/pr/internal/sync-update-step.d.ts +6 -0
- package/dist/commands/pr/internal/sync-update-step.d.ts.map +1 -0
- package/dist/commands/pr/internal/sync-update-step.js +68 -0
- package/dist/commands/pr/internal/sync.d.ts +2 -6
- package/dist/commands/pr/internal/sync.d.ts.map +1 -1
- package/dist/commands/pr/internal/sync.js +83 -529
- package/dist/commands/pr/open.d.ts.map +1 -1
- package/dist/commands/pr/open.js +25 -8
- package/dist/commands/pr/pr.command.d.ts.map +1 -1
- package/dist/commands/pr/pr.command.js +7 -2
- package/dist/commands/recipes/impl/apply.d.ts +1 -1
- package/dist/commands/recipes/impl/apply.d.ts.map +1 -1
- package/dist/commands/recipes/impl/apply.js +1 -2
- package/dist/commands/recipes/impl/commands/active.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/active.js +6 -5
- package/dist/commands/recipes/impl/commands/add.d.ts +1 -0
- package/dist/commands/recipes/impl/commands/add.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/add.js +32 -27
- package/dist/commands/recipes/impl/commands/detach.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/detach.js +35 -21
- package/dist/commands/recipes/impl/commands/disable.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/disable.js +5 -3
- package/dist/commands/recipes/impl/commands/enable.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/enable.js +5 -3
- package/dist/commands/recipes/impl/commands/explain.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/explain.js +57 -47
- package/dist/commands/recipes/impl/commands/info.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/info.js +25 -21
- package/dist/commands/recipes/impl/commands/install.d.ts +1 -1
- package/dist/commands/recipes/impl/commands/install.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/install.js +3 -13
- package/dist/commands/recipes/impl/commands/list-remote.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/list-remote.js +2 -3
- package/dist/commands/recipes/impl/commands/list.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/list.js +7 -6
- package/dist/commands/recipes/impl/commands/remove.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/remove.js +12 -7
- package/dist/commands/recipes/impl/commands/update.d.ts.map +1 -1
- package/dist/commands/recipes/impl/commands/update.js +38 -24
- package/dist/commands/recipes/impl/index.d.ts +1 -1
- package/dist/commands/recipes/impl/index.d.ts.map +1 -1
- package/dist/commands/recipes/impl/installed-recipes.d.ts +1 -1
- package/dist/commands/recipes/impl/installed-recipes.d.ts.map +1 -1
- package/dist/commands/recipes/impl/installed-recipes.js +1 -2
- package/dist/commands/recipes/impl/mutation-transaction.d.ts +7 -0
- package/dist/commands/recipes/impl/mutation-transaction.d.ts.map +1 -0
- package/dist/commands/recipes/impl/mutation-transaction.js +47 -0
- package/dist/commands/recipes/impl/overlay-project.d.ts +19 -3
- package/dist/commands/recipes/impl/overlay-project.d.ts.map +1 -1
- package/dist/commands/recipes/impl/overlay-project.js +76 -38
- package/dist/commands/recipes/impl/paths.d.ts +0 -3
- package/dist/commands/recipes/impl/paths.d.ts.map +1 -1
- package/dist/commands/recipes/impl/paths.js +0 -3
- package/dist/commands/recipes/impl/project-installed-recipes.d.ts +4 -1
- package/dist/commands/recipes/impl/project-installed-recipes.d.ts.map +1 -1
- package/dist/commands/recipes/impl/project-installed-recipes.js +6 -4
- package/dist/commands/recipes/impl/project-recipe-state.d.ts +1 -1
- package/dist/commands/recipes/impl/project-recipe-state.d.ts.map +1 -1
- package/dist/commands/recipes/impl/project-registry.d.ts +5 -1
- package/dist/commands/recipes/impl/project-registry.d.ts.map +1 -1
- package/dist/commands/recipes/impl/project-registry.js +34 -14
- package/dist/commands/recipes/impl/resolver.d.ts +1 -1
- package/dist/commands/recipes/impl/resolver.d.ts.map +1 -1
- package/dist/commands/recipes/impl/resolver.js +1 -1
- package/dist/commands/recipes/impl/types.d.ts +1 -1
- package/dist/commands/recipes/impl/types.d.ts.map +1 -1
- package/dist/commands/recipes/impl/version.d.ts +5 -0
- package/dist/commands/recipes/impl/version.d.ts.map +1 -0
- package/dist/commands/recipes/impl/version.js +9 -0
- package/dist/commands/recipes.d.ts +5 -4
- package/dist/commands/recipes.d.ts.map +1 -1
- package/dist/commands/recipes.js +3 -3
- package/dist/commands/release/apply.command.d.ts +1 -1
- package/dist/commands/release/apply.command.d.ts.map +1 -1
- package/dist/commands/release/apply.command.js +15 -379
- package/dist/commands/release/apply.mutation.d.ts +1 -0
- package/dist/commands/release/apply.mutation.d.ts.map +1 -1
- package/dist/commands/release/apply.mutation.js +24 -1
- package/dist/commands/release/apply.pipeline.d.ts +22 -0
- package/dist/commands/release/apply.pipeline.d.ts.map +1 -0
- package/dist/commands/release/apply.pipeline.js +371 -0
- package/dist/commands/release/apply.preflight.d.ts +2 -0
- package/dist/commands/release/apply.preflight.d.ts.map +1 -1
- package/dist/commands/release/apply.preflight.js +13 -4
- package/dist/commands/release/apply.types.d.ts +27 -0
- package/dist/commands/release/apply.types.d.ts.map +1 -1
- package/dist/commands/release.test-helpers.d.ts +4 -0
- package/dist/commands/release.test-helpers.d.ts.map +1 -1
- package/dist/commands/release.test-helpers.js +7 -0
- package/dist/commands/shared/reconcile-check.d.ts.map +1 -1
- package/dist/commands/shared/reconcile-check.js +2 -2
- package/dist/commands/shared/task-backend.d.ts +6 -1
- package/dist/commands/shared/task-backend.d.ts.map +1 -1
- package/dist/commands/shared/task-backend.js +34 -2
- package/dist/commands/shared/task-mutation.d.ts.map +1 -1
- package/dist/commands/shared/task-mutation.js +4 -4
- package/dist/commands/shared/task-store/intents.d.ts +34 -0
- package/dist/commands/shared/task-store/intents.d.ts.map +1 -0
- package/dist/commands/shared/task-store/intents.js +265 -0
- package/dist/commands/shared/task-store/readme.d.ts +28 -0
- package/dist/commands/shared/task-store/readme.d.ts.map +1 -0
- package/dist/commands/shared/task-store/readme.js +125 -0
- package/dist/commands/shared/task-store/store.d.ts +26 -0
- package/dist/commands/shared/task-store/store.d.ts.map +1 -0
- package/dist/commands/shared/task-store/store.js +105 -0
- package/dist/commands/shared/task-store/types.d.ts +94 -0
- package/dist/commands/shared/task-store/types.d.ts.map +1 -0
- package/dist/commands/shared/task-store/types.js +1 -0
- package/dist/commands/shared/task-store.d.ts +3 -109
- package/dist/commands/shared/task-store.d.ts.map +1 -1
- package/dist/commands/shared/task-store.js +2 -493
- package/dist/commands/task/block.d.ts.map +1 -1
- package/dist/commands/task/block.js +7 -2
- package/dist/commands/task/comment.d.ts.map +1 -1
- package/dist/commands/task/comment.js +7 -2
- package/dist/commands/task/finish-shared.d.ts.map +1 -1
- package/dist/commands/task/finish-shared.js +3 -3
- package/dist/commands/task/finish.d.ts.map +1 -1
- package/dist/commands/task/finish.js +102 -15
- package/dist/commands/task/hosted-close.command.d.ts.map +1 -1
- package/dist/commands/task/hosted-close.command.js +23 -2
- package/dist/commands/task/hosted-merge-sync.d.ts.map +1 -1
- package/dist/commands/task/hosted-merge-sync.js +9 -4
- package/dist/commands/task/list.run.d.ts.map +1 -1
- package/dist/commands/task/list.run.js +14 -4
- package/dist/commands/task/new.command.d.ts.map +1 -1
- package/dist/commands/task/new.command.js +16 -2
- package/dist/commands/task/new.js +2 -2
- package/dist/commands/task/show.d.ts.map +1 -1
- package/dist/commands/task/show.js +3 -3
- package/dist/commands/task/update.d.ts.map +1 -1
- package/dist/commands/task/update.js +11 -3
- package/dist/runner/adapters/codex.d.ts.map +1 -1
- package/dist/runner/adapters/codex.js +3 -33
- package/dist/runner/adapters/custom.d.ts.map +1 -1
- package/dist/runner/adapters/custom.js +3 -30
- package/dist/runner/adapters/runtime-shared.d.ts +14 -0
- package/dist/runner/adapters/runtime-shared.d.ts.map +1 -0
- package/dist/runner/adapters/runtime-shared.js +36 -0
- package/dist/runner/context/base-prompt-sources.d.ts +30 -0
- package/dist/runner/context/base-prompt-sources.d.ts.map +1 -0
- package/dist/runner/context/base-prompt-sources.js +144 -0
- package/dist/runner/context/base-prompts.d.ts +3 -22
- package/dist/runner/context/base-prompts.d.ts.map +1 -1
- package/dist/runner/context/base-prompts.js +6 -450
- package/dist/runner/context/overlay-prompt-blocks.d.ts +7 -0
- package/dist/runner/context/overlay-prompt-blocks.d.ts.map +1 -0
- package/dist/runner/context/overlay-prompt-blocks.js +72 -0
- package/dist/runner/context/prompt-block-shared.d.ts +54 -0
- package/dist/runner/context/prompt-block-shared.d.ts.map +1 -0
- package/dist/runner/context/prompt-block-shared.js +106 -0
- package/dist/runner/context/recipe-context.d.ts +2 -1
- package/dist/runner/context/recipe-context.d.ts.map +1 -1
- package/dist/runner/context/recipe-context.js +2 -1
- package/dist/runner/context/recipe-prompt-blocks.d.ts +6 -0
- package/dist/runner/context/recipe-prompt-blocks.d.ts.map +1 -0
- package/dist/runner/context/recipe-prompt-blocks.js +143 -0
- package/dist/runner/usecases/scenario-materialize-task.js +2 -2
- package/dist/runner/usecases/task-run-inspect.js +2 -2
- package/dist/runner/usecases/task-run-lifecycle-shared.js +2 -2
- package/dist/runner/usecases/task-run.d.ts.map +1 -1
- package/dist/runner/usecases/task-run.js +4 -2
- package/dist/runtime/capabilities/recipe.d.ts +1 -1
- package/dist/runtime/capabilities/recipe.d.ts.map +1 -1
- package/dist/runtime/execution-context.d.ts +63 -0
- package/dist/runtime/execution-context.d.ts.map +1 -0
- package/dist/{usecases/context/resolve-context.js → runtime/execution-context.js} +23 -26
- package/dist/runtime/incidents/advice-strategy.d.ts +15 -0
- package/dist/runtime/incidents/advice-strategy.d.ts.map +1 -0
- package/dist/runtime/incidents/advice-strategy.js +54 -0
- package/dist/runtime/incidents/plan-strategy.d.ts +9 -0
- package/dist/runtime/incidents/plan-strategy.d.ts.map +1 -0
- package/dist/runtime/incidents/plan-strategy.js +205 -0
- package/dist/runtime/incidents/registry-strategy.d.ts +6 -0
- package/dist/runtime/incidents/registry-strategy.d.ts.map +1 -0
- package/dist/runtime/incidents/registry-strategy.js +280 -0
- package/dist/runtime/incidents/resolve.d.ts +3 -25
- package/dist/runtime/incidents/resolve.d.ts.map +1 -1
- package/dist/runtime/incidents/resolve.js +3 -683
- package/dist/runtime/incidents/shared.d.ts +34 -0
- package/dist/runtime/incidents/shared.d.ts.map +1 -0
- package/dist/runtime/incidents/shared.js +171 -0
- package/dist/testing/cli-harness/recipe-archives.d.ts +28 -0
- package/dist/testing/cli-harness/recipe-archives.d.ts.map +1 -0
- package/dist/testing/cli-harness/recipe-archives.js +374 -0
- package/dist/testing/cli-harness/stdio.d.ts +26 -0
- package/dist/testing/cli-harness/stdio.d.ts.map +1 -0
- package/dist/testing/cli-harness/stdio.js +84 -0
- package/dist/testing/cli-harness.d.ts +25 -0
- package/dist/testing/cli-harness.d.ts.map +1 -0
- package/dist/testing/cli-harness.js +313 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +1 -0
- package/package.json +7 -4
- package/dist/commands/recipes/impl/manifest.d.ts +0 -4
- package/dist/commands/recipes/impl/manifest.d.ts.map +0 -1
- package/dist/commands/recipes/impl/manifest.js +0 -7
- package/dist/commands/recipes/impl/normalize.d.ts +0 -8
- package/dist/commands/recipes/impl/normalize.d.ts.map +0 -1
- package/dist/commands/recipes/impl/normalize.js +0 -54
- package/dist/commands/recipes/impl/scenario.d.ts +0 -16
- package/dist/commands/recipes/impl/scenario.d.ts.map +0 -1
- package/dist/commands/recipes/impl/scenario.js +0 -262
- package/dist/recipes/bundled-recipes.d.ts +0 -17
- package/dist/recipes/bundled-recipes.d.ts.map +0 -1
- package/dist/recipes/bundled-recipes.js +0 -15
- package/dist/usecases/context/resolve-context.d.ts +0 -68
- package/dist/usecases/context/resolve-context.d.ts.map +0 -1
- package/dist/usecases/task/task-list-usecase.d.ts +0 -9
- package/dist/usecases/task/task-list-usecase.d.ts.map +0 -1
- package/dist/usecases/task/task-list-usecase.js +0 -17
- package/dist/usecases/task/task-new-usecase.d.ts +0 -9
- package/dist/usecases/task/task-new-usecase.d.ts.map +0 -1
- package/dist/usecases/task/task-new-usecase.js +0 -17
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PolicyEngine } from "../../policy/engine.js";
|
|
2
|
-
import { loadTaskFromContext, writeTasksOrFallback } from "./task-backend.js";
|
|
3
|
-
import { applyTaskStoreIntentsToTask,
|
|
2
|
+
import { backendUsesLocalTaskStore, loadTaskFromContext, writeTasksOrFallback, } from "./task-backend.js";
|
|
3
|
+
import { applyTaskStoreIntentsToTask, getTaskStore, } from "./task-store.js";
|
|
4
4
|
export async function withTaskMutationStorage(opts) {
|
|
5
|
-
if (
|
|
5
|
+
if (backendUsesLocalTaskStore(opts.ctx)) {
|
|
6
6
|
return await opts.local(getTaskStore(opts.ctx));
|
|
7
7
|
}
|
|
8
8
|
return await opts.remote(opts.ctx.taskBackend);
|
|
@@ -14,7 +14,7 @@ export async function applyTaskMutation(opts) {
|
|
|
14
14
|
taskId: opts.taskId,
|
|
15
15
|
git: { stagedPaths: [] },
|
|
16
16
|
});
|
|
17
|
-
if (
|
|
17
|
+
if (backendUsesLocalTaskStore(opts.ctx)) {
|
|
18
18
|
const store = getTaskStore(opts.ctx);
|
|
19
19
|
const result = await store.update(opts.taskId, async (current) => {
|
|
20
20
|
const plan = await opts.build({ ...current });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { TaskData, TaskEvent } from "../../../backends/task-backend.js";
|
|
2
|
+
import type { CachedTask, TaskComment, TaskStoreIntent, TaskStoreIntentResult, TaskStoreLike, TaskStoreMutationOptions, TaskStorePatch, TaskStoreTaskPatch } from "./types.js";
|
|
3
|
+
export declare function setTaskFieldsIntent(task: TaskStoreTaskPatch): TaskStoreIntent;
|
|
4
|
+
export declare function appendTaskCommentsIntent(comments: TaskComment[]): TaskStoreIntent;
|
|
5
|
+
export declare function appendTaskCommentIntent(comment: TaskComment): TaskStoreIntent;
|
|
6
|
+
export declare function appendTaskEventsIntent(events: TaskEvent[]): TaskStoreIntent;
|
|
7
|
+
export declare function appendTaskEventIntent(event: TaskEvent): TaskStoreIntent;
|
|
8
|
+
export declare function replaceTaskDocIntent(opts: {
|
|
9
|
+
doc: string;
|
|
10
|
+
expectedCurrentDoc?: string | null;
|
|
11
|
+
}): TaskStoreIntent;
|
|
12
|
+
export declare function setTaskSectionIntent(opts: {
|
|
13
|
+
section: string;
|
|
14
|
+
text: string;
|
|
15
|
+
requiredSections: string[];
|
|
16
|
+
expectedCurrentText?: string | null;
|
|
17
|
+
}): TaskStoreIntent;
|
|
18
|
+
export declare function touchTaskDocMetaIntent(opts?: {
|
|
19
|
+
updatedBy?: string;
|
|
20
|
+
version?: 2 | 3;
|
|
21
|
+
}): TaskStoreIntent;
|
|
22
|
+
export declare function taskStorePatchFromIntents(intents: TaskStoreIntentResult): TaskStorePatch | null | undefined;
|
|
23
|
+
export declare function mutateTaskStore(store: TaskStoreLike, taskId: string, builder: (current: TaskData) => Promise<TaskStoreIntentResult> | TaskStoreIntentResult, opts?: TaskStoreMutationOptions): Promise<{
|
|
24
|
+
changed: boolean;
|
|
25
|
+
task: TaskData;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function applyTaskStoreIntentsToTask(task: TaskData, intents: TaskStoreIntentResult, opts?: {
|
|
28
|
+
currentDocVersion?: 2 | 3;
|
|
29
|
+
docUpdatedAt?: string;
|
|
30
|
+
}): TaskData;
|
|
31
|
+
export declare function applyTaskStoreIntents(entry: CachedTask, intents: TaskStoreIntent[]): TaskData;
|
|
32
|
+
export declare function resolveTaskStoreIntents(intents: TaskStoreIntentResult): TaskStoreIntent[];
|
|
33
|
+
export declare function resolveTaskStorePatch(patch: TaskStorePatch | null | undefined): TaskStoreIntent[];
|
|
34
|
+
//# sourceMappingURL=intents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intents.d.ts","sourceRoot":"","sources":["../../../../src/commands/shared/task-store/intents.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAK7E,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EAEX,eAAe,EACf,qBAAqB,EACrB,aAAa,EACb,wBAAwB,EACxB,cAAc,EACd,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,kBAAkB,GAAG,eAAe,CAE7E;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,WAAW,EAAE,GAAG,eAAe,CAEjF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,eAAe,CAE7E;AAED,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,CAE3E;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,eAAe,CAEvE;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,GAAG,eAAe,CAElB;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC,GAAG,eAAe,CAElB;AAED,wBAAgB,sBAAsB,CACpC,IAAI,GAAE;IACJ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;CACZ,GACL,eAAe,CAEjB;AAuFD,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,qBAAqB,GAC7B,cAAc,GAAG,IAAI,GAAG,SAAS,CA2DnC;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,EACtF,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAS/C;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,QAAQ,EACd,OAAO,EAAE,qBAAqB,EAC9B,IAAI,GAAE;IACJ,iBAAiB,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CAClB,GACL,QAAQ,CA0GV;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,QAAQ,CAI7F;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,qBAAqB,GAAG,eAAe,EAAE,CAEzF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,GAAG,eAAe,EAAE,CAEjG"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { applyTaskDocMutations, normalizeTaskDocVersion, taskDocToSectionMap, } from "@agentplaneorg/core";
|
|
2
|
+
import { assertExpectedTaskDoc, assertExpectedTaskSection, } from "../../../shared/task-doc-conflicts.js";
|
|
3
|
+
export function setTaskFieldsIntent(task) {
|
|
4
|
+
return { kind: "set-task-fields", task };
|
|
5
|
+
}
|
|
6
|
+
export function appendTaskCommentsIntent(comments) {
|
|
7
|
+
return { kind: "append-comments", comments };
|
|
8
|
+
}
|
|
9
|
+
export function appendTaskCommentIntent(comment) {
|
|
10
|
+
return appendTaskCommentsIntent([comment]);
|
|
11
|
+
}
|
|
12
|
+
export function appendTaskEventsIntent(events) {
|
|
13
|
+
return { kind: "append-events", events };
|
|
14
|
+
}
|
|
15
|
+
export function appendTaskEventIntent(event) {
|
|
16
|
+
return appendTaskEventsIntent([event]);
|
|
17
|
+
}
|
|
18
|
+
export function replaceTaskDocIntent(opts) {
|
|
19
|
+
return { kind: "replace-doc", ...opts };
|
|
20
|
+
}
|
|
21
|
+
export function setTaskSectionIntent(opts) {
|
|
22
|
+
return { kind: "set-section", ...opts };
|
|
23
|
+
}
|
|
24
|
+
export function touchTaskDocMetaIntent(opts = {}) {
|
|
25
|
+
return { kind: "touch-doc-meta", ...opts };
|
|
26
|
+
}
|
|
27
|
+
function normalizeComments(task) {
|
|
28
|
+
return Array.isArray(task.comments)
|
|
29
|
+
? task.comments.filter((item) => !!item && typeof item.author === "string" && typeof item.body === "string")
|
|
30
|
+
: [];
|
|
31
|
+
}
|
|
32
|
+
function normalizeEvents(task) {
|
|
33
|
+
return Array.isArray(task.events)
|
|
34
|
+
? task.events.filter((item) => !!item &&
|
|
35
|
+
typeof item.type === "string" &&
|
|
36
|
+
typeof item.at === "string" &&
|
|
37
|
+
typeof item.author === "string")
|
|
38
|
+
: [];
|
|
39
|
+
}
|
|
40
|
+
function applyDocMutationsToState(docState, mutations, opts) {
|
|
41
|
+
const applied = applyTaskDocMutations(docState, mutations, {
|
|
42
|
+
now: opts.docUpdatedAt,
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
...docState,
|
|
46
|
+
doc: applied.doc,
|
|
47
|
+
sections: applied.sections,
|
|
48
|
+
doc_version: applied.doc_version,
|
|
49
|
+
doc_updated_at: applied.doc_updated_at,
|
|
50
|
+
doc_updated_by: applied.doc_updated_by,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function normalizeTaskStoreIntents(intents) {
|
|
54
|
+
if (!intents)
|
|
55
|
+
return [];
|
|
56
|
+
if (Array.isArray(intents)) {
|
|
57
|
+
return intents.filter((intent) => intent != null);
|
|
58
|
+
}
|
|
59
|
+
return [intents];
|
|
60
|
+
}
|
|
61
|
+
function patchToIntents(patch) {
|
|
62
|
+
if (!patch)
|
|
63
|
+
return [];
|
|
64
|
+
const intents = [];
|
|
65
|
+
if (patch.task)
|
|
66
|
+
intents.push(setTaskFieldsIntent(patch.task));
|
|
67
|
+
if (patch.appendComments && patch.appendComments.length > 0) {
|
|
68
|
+
intents.push(appendTaskCommentsIntent(patch.appendComments));
|
|
69
|
+
}
|
|
70
|
+
if (patch.appendEvents && patch.appendEvents.length > 0) {
|
|
71
|
+
intents.push(appendTaskEventsIntent(patch.appendEvents));
|
|
72
|
+
}
|
|
73
|
+
if (patch.doc) {
|
|
74
|
+
intents.push(patch.doc.kind === "replace-doc"
|
|
75
|
+
? replaceTaskDocIntent({
|
|
76
|
+
doc: patch.doc.doc,
|
|
77
|
+
expectedCurrentDoc: patch.doc.expectedCurrentDoc,
|
|
78
|
+
})
|
|
79
|
+
: setTaskSectionIntent({
|
|
80
|
+
section: patch.doc.section,
|
|
81
|
+
text: patch.doc.text,
|
|
82
|
+
requiredSections: patch.doc.requiredSections,
|
|
83
|
+
expectedCurrentText: patch.doc.expectedCurrentText,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
if (patch.docMeta && (patch.doc !== undefined || patch.docMeta.touch === true)) {
|
|
87
|
+
intents.push(touchTaskDocMetaIntent({
|
|
88
|
+
updatedBy: patch.docMeta.updatedBy,
|
|
89
|
+
version: patch.docMeta.version,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
return intents;
|
|
93
|
+
}
|
|
94
|
+
export function taskStorePatchFromIntents(intents) {
|
|
95
|
+
const normalized = normalizeTaskStoreIntents(intents);
|
|
96
|
+
if (normalized.length === 0)
|
|
97
|
+
return null;
|
|
98
|
+
const patch = {};
|
|
99
|
+
for (const intent of normalized) {
|
|
100
|
+
switch (intent.kind) {
|
|
101
|
+
case "set-task-fields": {
|
|
102
|
+
patch.task = patch.task ? { ...patch.task, ...intent.task } : { ...intent.task };
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
case "append-comments": {
|
|
106
|
+
if (intent.comments.length > 0) {
|
|
107
|
+
patch.appendComments = [...(patch.appendComments ?? []), ...intent.comments];
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case "append-events": {
|
|
112
|
+
if (intent.events.length > 0) {
|
|
113
|
+
patch.appendEvents = [...(patch.appendEvents ?? []), ...intent.events];
|
|
114
|
+
}
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
case "replace-doc": {
|
|
118
|
+
const docPatch = {
|
|
119
|
+
kind: "replace-doc",
|
|
120
|
+
doc: intent.doc,
|
|
121
|
+
};
|
|
122
|
+
if (intent.expectedCurrentDoc !== undefined) {
|
|
123
|
+
docPatch.expectedCurrentDoc = intent.expectedCurrentDoc;
|
|
124
|
+
}
|
|
125
|
+
patch.doc = docPatch;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "set-section": {
|
|
129
|
+
const sectionPatch = {
|
|
130
|
+
kind: "set-section",
|
|
131
|
+
section: intent.section,
|
|
132
|
+
text: intent.text,
|
|
133
|
+
requiredSections: [...intent.requiredSections],
|
|
134
|
+
};
|
|
135
|
+
if (intent.expectedCurrentText !== undefined) {
|
|
136
|
+
sectionPatch.expectedCurrentText = intent.expectedCurrentText;
|
|
137
|
+
}
|
|
138
|
+
patch.doc = sectionPatch;
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "touch-doc-meta": {
|
|
142
|
+
patch.docMeta = {
|
|
143
|
+
touch: true,
|
|
144
|
+
updatedBy: intent.updatedBy ?? patch.docMeta?.updatedBy,
|
|
145
|
+
version: intent.version ?? patch.docMeta?.version,
|
|
146
|
+
};
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return patch;
|
|
152
|
+
}
|
|
153
|
+
export async function mutateTaskStore(store, taskId, builder, opts = {}) {
|
|
154
|
+
if (typeof store.mutate === "function") {
|
|
155
|
+
return await store.mutate(taskId, builder, opts);
|
|
156
|
+
}
|
|
157
|
+
return await store.patch(taskId, async (current) => taskStorePatchFromIntents(await builder(current)), opts);
|
|
158
|
+
}
|
|
159
|
+
export function applyTaskStoreIntentsToTask(task, intents, opts = {}) {
|
|
160
|
+
const normalizedIntents = normalizeTaskStoreIntents(intents);
|
|
161
|
+
if (normalizedIntents.length === 0)
|
|
162
|
+
return { ...task };
|
|
163
|
+
const current = task;
|
|
164
|
+
const next = { ...current };
|
|
165
|
+
let docState = {
|
|
166
|
+
comments: next.comments ?? null,
|
|
167
|
+
doc: String(next.doc ?? ""),
|
|
168
|
+
doc_updated_by: next.doc_updated_by,
|
|
169
|
+
doc_version: normalizeTaskDocVersion(opts.currentDocVersion ?? task.doc_version),
|
|
170
|
+
owner: next.owner,
|
|
171
|
+
sections: next.sections ?? null,
|
|
172
|
+
};
|
|
173
|
+
let touchDoc = false;
|
|
174
|
+
for (const intent of normalizedIntents) {
|
|
175
|
+
switch (intent.kind) {
|
|
176
|
+
case "set-task-fields": {
|
|
177
|
+
Object.assign(next, intent.task);
|
|
178
|
+
docState = {
|
|
179
|
+
...docState,
|
|
180
|
+
doc_updated_by: next.doc_updated_by,
|
|
181
|
+
doc_version: normalizeTaskDocVersion(next.doc_version ?? docState.doc_version),
|
|
182
|
+
owner: next.owner,
|
|
183
|
+
};
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
case "append-comments": {
|
|
187
|
+
if (intent.comments.length > 0) {
|
|
188
|
+
next.comments = [...normalizeComments(next), ...intent.comments];
|
|
189
|
+
docState = { ...docState, comments: next.comments };
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case "append-events": {
|
|
194
|
+
if (intent.events.length > 0) {
|
|
195
|
+
next.events = [...normalizeEvents(next), ...intent.events];
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case "replace-doc": {
|
|
200
|
+
if (intent.expectedCurrentDoc !== undefined) {
|
|
201
|
+
assertExpectedTaskDoc({
|
|
202
|
+
taskId: current.id,
|
|
203
|
+
currentDoc: docState.doc,
|
|
204
|
+
expectedDoc: intent.expectedCurrentDoc,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
docState = applyDocMutationsToState(docState, [{ kind: "replace-doc", doc: intent.doc }], {
|
|
208
|
+
docUpdatedAt: opts.docUpdatedAt,
|
|
209
|
+
});
|
|
210
|
+
touchDoc = true;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "set-section": {
|
|
214
|
+
if (intent.expectedCurrentText !== undefined) {
|
|
215
|
+
assertExpectedTaskSection({
|
|
216
|
+
taskId: current.id,
|
|
217
|
+
currentDoc: docState.doc,
|
|
218
|
+
section: intent.section,
|
|
219
|
+
expectedText: intent.expectedCurrentText,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
docState = applyDocMutationsToState(docState, [
|
|
223
|
+
{
|
|
224
|
+
kind: "set-section",
|
|
225
|
+
section: intent.section,
|
|
226
|
+
text: intent.text,
|
|
227
|
+
requiredSections: intent.requiredSections,
|
|
228
|
+
},
|
|
229
|
+
], { docUpdatedAt: opts.docUpdatedAt });
|
|
230
|
+
touchDoc = true;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
case "touch-doc-meta": {
|
|
234
|
+
docState = applyDocMutationsToState(docState, [
|
|
235
|
+
{
|
|
236
|
+
kind: "touch-doc-meta",
|
|
237
|
+
updatedBy: intent.updatedBy,
|
|
238
|
+
version: intent.version,
|
|
239
|
+
},
|
|
240
|
+
], { docUpdatedAt: opts.docUpdatedAt });
|
|
241
|
+
touchDoc = true;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (touchDoc) {
|
|
247
|
+
next.doc = docState.doc;
|
|
248
|
+
next.sections = docState.sections ?? taskDocToSectionMap(docState.doc);
|
|
249
|
+
next.doc_version = docState.doc_version;
|
|
250
|
+
next.doc_updated_at = docState.doc_updated_at;
|
|
251
|
+
next.doc_updated_by = docState.doc_updated_by;
|
|
252
|
+
}
|
|
253
|
+
return next;
|
|
254
|
+
}
|
|
255
|
+
export function applyTaskStoreIntents(entry, intents) {
|
|
256
|
+
return applyTaskStoreIntentsToTask(entry.task, intents, {
|
|
257
|
+
currentDocVersion: normalizeTaskDocVersion(entry.parsed.frontmatter.doc_version),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
export function resolveTaskStoreIntents(intents) {
|
|
261
|
+
return normalizeTaskStoreIntents(intents);
|
|
262
|
+
}
|
|
263
|
+
export function resolveTaskStorePatch(patch) {
|
|
264
|
+
return patchToIntents(patch);
|
|
265
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type TaskData } from "../../../backends/task-backend.js";
|
|
2
|
+
import { CliError } from "../../../shared/errors.js";
|
|
3
|
+
import type { CachedTask, TaskStoreContext } from "./types.js";
|
|
4
|
+
export declare function normalizeTaskRevision(value: unknown, fallback?: number): number;
|
|
5
|
+
export declare function readStoredTaskRevision(value: unknown): number | null;
|
|
6
|
+
export declare function isConcurrentReadmeChangeError(err: unknown): err is CliError;
|
|
7
|
+
export declare function throwTaskRevisionConflict(opts: {
|
|
8
|
+
taskId: string;
|
|
9
|
+
expectedRevision: number;
|
|
10
|
+
currentRevision: number;
|
|
11
|
+
}): never;
|
|
12
|
+
export declare function readTaskReadmeCached(opts: {
|
|
13
|
+
ctx: TaskStoreContext;
|
|
14
|
+
taskId: string;
|
|
15
|
+
}): Promise<CachedTask>;
|
|
16
|
+
export declare function ensureUnchangedOnDisk(opts: {
|
|
17
|
+
readmePath: string;
|
|
18
|
+
expectedMtimeMs: number;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
export declare function didReadmeChangeOnDisk(opts: {
|
|
21
|
+
readmePath: string;
|
|
22
|
+
expectedMtimeMs: number;
|
|
23
|
+
}): Promise<boolean>;
|
|
24
|
+
export declare function writeTaskReadme(opts: {
|
|
25
|
+
entry: CachedTask;
|
|
26
|
+
next: TaskData;
|
|
27
|
+
}): Promise<boolean>;
|
|
28
|
+
//# sourceMappingURL=readme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readme.d.ts","sourceRoot":"","sources":["../../../../src/commands/shared/task-store/readme.ts"],"names":[],"mappings":"AAYA,OAAO,EAAoB,KAAK,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAEpF,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAGrD,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAM/D,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,SAAI,GAAG,MAAM,CAE1E;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAEpE;AAED,wBAAgB,6BAA6B,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,QAAQ,CAM3E;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,KAAK,CAcR;AAED,wBAAsB,oBAAoB,CAAC,IAAI,EAAE;IAC/C,GAAG,EAAE,gBAAgB,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0BtB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,IAAI,CAAC,CAShB;AAED,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,OAAO,CAAC,CASnB;AAED,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,QAAQ,CAAC;CAChB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2CnB"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { docChanged, extractTaskDoc, mergeTaskDoc, normalizeTaskDocVersion, parseTaskReadme, renderTaskReadme, } from "@agentplaneorg/core";
|
|
4
|
+
import { taskRecordToData } from "../../../backends/task-backend.js";
|
|
5
|
+
import { exitCodeForError } from "../../../cli/exit-codes.js";
|
|
6
|
+
import { CliError } from "../../../shared/errors.js";
|
|
7
|
+
import { writeTextIfChanged } from "../../../shared/write-if-changed.js";
|
|
8
|
+
import { resolveDocUpdatedBy, taskDataToFrontmatter } from "../task-backend.js";
|
|
9
|
+
function taskReadmePath(ctx, taskId) {
|
|
10
|
+
return path.join(ctx.resolvedProject.gitRoot, ctx.config.paths.workflow_dir, taskId, "README.md");
|
|
11
|
+
}
|
|
12
|
+
export function normalizeTaskRevision(value, fallback = 1) {
|
|
13
|
+
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : fallback;
|
|
14
|
+
}
|
|
15
|
+
export function readStoredTaskRevision(value) {
|
|
16
|
+
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : null;
|
|
17
|
+
}
|
|
18
|
+
export function isConcurrentReadmeChangeError(err) {
|
|
19
|
+
return (err instanceof CliError &&
|
|
20
|
+
err.code === "E_IO" &&
|
|
21
|
+
err.message.startsWith("Task README changed concurrently:"));
|
|
22
|
+
}
|
|
23
|
+
export function throwTaskRevisionConflict(opts) {
|
|
24
|
+
throw new CliError({
|
|
25
|
+
exitCode: exitCodeForError("E_VALIDATION"),
|
|
26
|
+
code: "E_VALIDATION",
|
|
27
|
+
message: `Task revision changed concurrently: ${opts.taskId} ` +
|
|
28
|
+
`(expected revision ${opts.expectedRevision}, current revision ${opts.currentRevision})`,
|
|
29
|
+
context: {
|
|
30
|
+
task_id: opts.taskId,
|
|
31
|
+
expected_revision: opts.expectedRevision,
|
|
32
|
+
current_revision: opts.currentRevision,
|
|
33
|
+
reason_code: "task_revision_conflict",
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
export async function readTaskReadmeCached(opts) {
|
|
38
|
+
const readmePath = taskReadmePath(opts.ctx, opts.taskId);
|
|
39
|
+
let text;
|
|
40
|
+
let st;
|
|
41
|
+
try {
|
|
42
|
+
st = await stat(readmePath);
|
|
43
|
+
text = await readFile(readmePath, "utf8");
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const code = err?.code;
|
|
47
|
+
if (code === "ENOENT") {
|
|
48
|
+
throw new CliError({
|
|
49
|
+
exitCode: 4,
|
|
50
|
+
code: "E_IO",
|
|
51
|
+
message: `ENOENT: no such file or directory, open '${readmePath}'`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
throw err;
|
|
55
|
+
}
|
|
56
|
+
const parsed = parseTaskReadme(text);
|
|
57
|
+
const task = taskRecordToData({
|
|
58
|
+
id: opts.taskId,
|
|
59
|
+
frontmatter: parsed.frontmatter,
|
|
60
|
+
body: parsed.body,
|
|
61
|
+
readmePath,
|
|
62
|
+
});
|
|
63
|
+
return { task, readmePath, mtimeMs: st.mtimeMs, parsed, rawText: text };
|
|
64
|
+
}
|
|
65
|
+
export async function ensureUnchangedOnDisk(opts) {
|
|
66
|
+
const st = await stat(opts.readmePath);
|
|
67
|
+
if (st.mtimeMs !== opts.expectedMtimeMs) {
|
|
68
|
+
throw new CliError({
|
|
69
|
+
exitCode: exitCodeForError("E_IO"),
|
|
70
|
+
code: "E_IO",
|
|
71
|
+
message: `Task README changed concurrently: ${opts.readmePath}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function didReadmeChangeOnDisk(opts) {
|
|
76
|
+
try {
|
|
77
|
+
const st = await stat(opts.readmePath);
|
|
78
|
+
return st.mtimeMs !== opts.expectedMtimeMs;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
const code = err?.code;
|
|
82
|
+
if (code === "ENOENT")
|
|
83
|
+
return true;
|
|
84
|
+
throw err;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
export async function writeTaskReadme(opts) {
|
|
88
|
+
const { entry, next } = opts;
|
|
89
|
+
const frontmatter = { ...entry.parsed.frontmatter, ...taskDataToFrontmatter(next) };
|
|
90
|
+
let body = entry.parsed.body ?? "";
|
|
91
|
+
const existingDoc = extractTaskDoc(body);
|
|
92
|
+
const now = new Date().toISOString();
|
|
93
|
+
const currentDocVersion = normalizeTaskDocVersion(entry.parsed.frontmatter.doc_version);
|
|
94
|
+
const requestedDocVersion = normalizeTaskDocVersion(next.doc_version, currentDocVersion);
|
|
95
|
+
if (next.doc !== undefined) {
|
|
96
|
+
const nextDoc = String(next.doc ?? "");
|
|
97
|
+
body = mergeTaskDoc(body, nextDoc);
|
|
98
|
+
if (docChanged(existingDoc, nextDoc) || !frontmatter.doc_updated_at) {
|
|
99
|
+
frontmatter.doc_version = requestedDocVersion;
|
|
100
|
+
frontmatter.doc_updated_at = now;
|
|
101
|
+
frontmatter.doc_updated_by = resolveDocUpdatedBy(next);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
frontmatter.doc_version = normalizeTaskDocVersion(frontmatter.doc_version, requestedDocVersion);
|
|
105
|
+
if (typeof frontmatter.doc_updated_at !== "string" || frontmatter.doc_updated_at.trim() === "") {
|
|
106
|
+
frontmatter.doc_updated_at = now;
|
|
107
|
+
}
|
|
108
|
+
if (typeof frontmatter.doc_updated_by !== "string" || frontmatter.doc_updated_by.trim() === "") {
|
|
109
|
+
frontmatter.doc_updated_by = resolveDocUpdatedBy(next);
|
|
110
|
+
}
|
|
111
|
+
const storedRevision = readStoredTaskRevision(entry.parsed.frontmatter.revision);
|
|
112
|
+
frontmatter.revision = storedRevision ?? 1;
|
|
113
|
+
let nextText = renderTaskReadme(frontmatter, body);
|
|
114
|
+
nextText = nextText.endsWith("\n") ? nextText : `${nextText}\n`;
|
|
115
|
+
if (storedRevision !== null && nextText !== entry.rawText) {
|
|
116
|
+
frontmatter.revision = storedRevision + 1;
|
|
117
|
+
nextText = renderTaskReadme(frontmatter, body);
|
|
118
|
+
nextText = nextText.endsWith("\n") ? nextText : `${nextText}\n`;
|
|
119
|
+
}
|
|
120
|
+
await ensureUnchangedOnDisk({
|
|
121
|
+
readmePath: entry.readmePath,
|
|
122
|
+
expectedMtimeMs: entry.mtimeMs,
|
|
123
|
+
});
|
|
124
|
+
return await writeTextIfChanged(entry.readmePath, nextText);
|
|
125
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TaskData } from "../../../backends/task-backend.js";
|
|
2
|
+
import type { TaskStore as TaskStoreContract, TaskStoreContext, TaskStoreIntentResult, TaskStoreMutationOptions, TaskStorePatch } from "./types.js";
|
|
3
|
+
export declare class TaskStore implements TaskStoreContract {
|
|
4
|
+
private ctx;
|
|
5
|
+
private cache;
|
|
6
|
+
constructor(ctx: TaskStoreContext);
|
|
7
|
+
get(taskId: string): Promise<TaskData>;
|
|
8
|
+
update(taskId: string, updater: (current: TaskData) => Promise<TaskData> | TaskData, opts?: TaskStoreMutationOptions): Promise<{
|
|
9
|
+
changed: boolean;
|
|
10
|
+
task: TaskData;
|
|
11
|
+
}>;
|
|
12
|
+
patch(taskId: string, builder: (current: TaskData) => Promise<TaskStorePatch | null | undefined> | TaskStorePatch | null | undefined, opts?: TaskStoreMutationOptions): Promise<{
|
|
13
|
+
changed: boolean;
|
|
14
|
+
task: TaskData;
|
|
15
|
+
}>;
|
|
16
|
+
mutate(taskId: string, builder: (current: TaskData) => Promise<TaskStoreIntentResult> | TaskStoreIntentResult, opts?: TaskStoreMutationOptions): Promise<{
|
|
17
|
+
changed: boolean;
|
|
18
|
+
task: TaskData;
|
|
19
|
+
}>;
|
|
20
|
+
private getCached;
|
|
21
|
+
private runWithRetry;
|
|
22
|
+
private writeNextTask;
|
|
23
|
+
}
|
|
24
|
+
export declare function getTaskStore(ctx: TaskStoreContext): TaskStore;
|
|
25
|
+
export declare function backendIsLocalFileBackend(ctx: TaskStoreContext): boolean;
|
|
26
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../../src/commands/shared/task-store/store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mCAAmC,CAAC;AAiBlE,OAAO,KAAK,EAEV,SAAS,IAAI,iBAAiB,EAC9B,gBAAgB,EAChB,qBAAqB,EACrB,wBAAwB,EACxB,cAAc,EACf,MAAM,YAAY,CAAC;AAEpB,qBAAa,SAAU,YAAW,iBAAiB;IACjD,OAAO,CAAC,GAAG,CAAmB;IAC9B,OAAO,CAAC,KAAK,CAA0C;gBAE3C,GAAG,EAAE,gBAAgB;IAI3B,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAKtC,MAAM,CACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,EAC5D,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;IAM1C,KAAK,CACT,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CACP,OAAO,EAAE,QAAQ,KACd,OAAO,CAAC,cAAc,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,cAAc,GAAG,IAAI,GAAG,SAAS,EACnF,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;IAQ1C,MAAM,CACV,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC,qBAAqB,CAAC,GAAG,qBAAqB,EACtF,IAAI,GAAE,wBAA6B,GAClC,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC;YAOlC,SAAS;YAmBT,YAAY;YAiDZ,aAAa;CAgB5B;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,SAAS,CAI7D;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAExE"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { exitCodeForError } from "../../../cli/exit-codes.js";
|
|
2
|
+
import { CliError } from "../../../shared/errors.js";
|
|
3
|
+
import { backendUsesLocalTaskStore } from "../task-backend.js";
|
|
4
|
+
import { applyTaskStoreIntents, resolveTaskStoreIntents, resolveTaskStorePatch, } from "./intents.js";
|
|
5
|
+
import { didReadmeChangeOnDisk, isConcurrentReadmeChangeError, normalizeTaskRevision, readTaskReadmeCached, throwTaskRevisionConflict, writeTaskReadme, } from "./readme.js";
|
|
6
|
+
export class TaskStore {
|
|
7
|
+
ctx;
|
|
8
|
+
cache = new Map();
|
|
9
|
+
constructor(ctx) {
|
|
10
|
+
this.ctx = ctx;
|
|
11
|
+
}
|
|
12
|
+
async get(taskId) {
|
|
13
|
+
const entry = await this.getCached(taskId);
|
|
14
|
+
return entry.task;
|
|
15
|
+
}
|
|
16
|
+
async update(taskId, updater, opts = {}) {
|
|
17
|
+
return await this.runWithRetry(taskId, opts, async (entry) => {
|
|
18
|
+
return await updater({ ...entry.task });
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async patch(taskId, builder, opts = {}) {
|
|
22
|
+
return await this.mutate(taskId, async (current) => resolveTaskStorePatch(await builder(current)), opts);
|
|
23
|
+
}
|
|
24
|
+
async mutate(taskId, builder, opts = {}) {
|
|
25
|
+
return await this.runWithRetry(taskId, opts, async (entry) => {
|
|
26
|
+
const intents = resolveTaskStoreIntents(await builder({ ...entry.task }));
|
|
27
|
+
return applyTaskStoreIntents(entry, intents);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async getCached(taskId) {
|
|
31
|
+
const key = taskId.trim();
|
|
32
|
+
if (!key) {
|
|
33
|
+
throw new CliError({
|
|
34
|
+
exitCode: exitCodeForError("E_USAGE"),
|
|
35
|
+
code: "E_USAGE",
|
|
36
|
+
message: "task id is required",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
const existing = this.cache.get(key);
|
|
40
|
+
if (existing)
|
|
41
|
+
return await existing;
|
|
42
|
+
const load = (async () => {
|
|
43
|
+
return await readTaskReadmeCached({ ctx: this.ctx, taskId: key });
|
|
44
|
+
})();
|
|
45
|
+
this.cache.set(key, load);
|
|
46
|
+
return await load;
|
|
47
|
+
}
|
|
48
|
+
async runWithRetry(taskId, opts, computeNext) {
|
|
49
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
50
|
+
const entry = await this.getCached(taskId);
|
|
51
|
+
if (opts.expectedRevision !== undefined) {
|
|
52
|
+
const expectedRevision = normalizeTaskRevision(opts.expectedRevision);
|
|
53
|
+
const currentRevision = normalizeTaskRevision(entry.task.revision);
|
|
54
|
+
if (currentRevision !== expectedRevision) {
|
|
55
|
+
throwTaskRevisionConflict({ taskId, expectedRevision, currentRevision });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
let next;
|
|
59
|
+
try {
|
|
60
|
+
next = await computeNext(entry);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
if (attempt === 0 &&
|
|
64
|
+
err instanceof CliError &&
|
|
65
|
+
err.code === "E_VALIDATION" &&
|
|
66
|
+
(await didReadmeChangeOnDisk({
|
|
67
|
+
readmePath: entry.readmePath,
|
|
68
|
+
expectedMtimeMs: entry.mtimeMs,
|
|
69
|
+
}))) {
|
|
70
|
+
this.cache.delete(taskId);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
return await this.writeNextTask(taskId, entry, next);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (attempt === 0 && isConcurrentReadmeChangeError(err)) {
|
|
80
|
+
this.cache.delete(taskId);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw err;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const task = await this.get(taskId);
|
|
87
|
+
return { changed: false, task };
|
|
88
|
+
}
|
|
89
|
+
async writeNextTask(taskId, entry, next) {
|
|
90
|
+
const changed = await writeTaskReadme({ entry, next });
|
|
91
|
+
this.cache.set(taskId, (async () => {
|
|
92
|
+
return await readTaskReadmeCached({ ctx: this.ctx, taskId });
|
|
93
|
+
})());
|
|
94
|
+
const updated = await this.get(taskId);
|
|
95
|
+
return { changed, task: updated };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export function getTaskStore(ctx) {
|
|
99
|
+
const memo = ctx.memo;
|
|
100
|
+
memo.taskStore ??= new TaskStore(ctx);
|
|
101
|
+
return memo.taskStore;
|
|
102
|
+
}
|
|
103
|
+
export function backendIsLocalFileBackend(ctx) {
|
|
104
|
+
return backendUsesLocalTaskStore(ctx);
|
|
105
|
+
}
|