From c6ea6e1a68741ae55bea76c9a642d1effad24960 Mon Sep 17 00:00:00 2001 From: kiannidev <156195510+kiannidev@users.noreply.github.com> Date: Sun, 5 Jul 2026 18:28:10 +0200 Subject: [PATCH] feat(engine): add hasPlanCompletedSteps and document packages Add the partial-progress plan DAG predicate, update the engine README export list, and link engine/miner packages from the root README. Co-authored-by: Cursor --- README.md | 2 + packages/gittensory-engine/README.md | 1 + packages/gittensory-engine/src/index.ts | 1 + .../gittensory-engine/src/plan-completed.ts | 8 +++ test/unit/plan-completed.test.ts | 54 +++++++++++++++++++ 5 files changed, 66 insertions(+) create mode 100644 packages/gittensory-engine/src/plan-completed.ts create mode 100644 test/unit/plan-completed.test.ts diff --git a/README.md b/README.md index e9c7bb6f43..a02b922373 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ See [Tuning your reviews](https://gittensory.aethereal.dev/docs/tuning) for the | Website | [gittensory.aethereal.dev](https://gittensory.aethereal.dev/) | | Docs | [gittensory.aethereal.dev/docs](https://gittensory.aethereal.dev/docs) | | MCP package | [@jsonbored/gittensory-mcp](https://www.npmjs.com/package/@jsonbored/gittensory-mcp) | +| Engine package | [`@jsonbored/gittensory-engine`](packages/gittensory-engine/README.md) — shared deterministic logic for the review stack and miner | +| Miner package | [`@jsonbored/gittensory-miner`](packages/gittensory-miner/README.md) — local foundation CLI for the autonomous miner runtime | | API | [API browser](https://gittensory.aethereal.dev/api) and [OpenAPI JSON](https://gittensory-api.aethereal.dev/openapi.json) | | GitHub App | [Install](https://github.com/apps/gittensory/installations/new) and [setup docs](https://gittensory.aethereal.dev/docs/github-app) | | Browser extension | [Extension page](https://gittensory.aethereal.dev/extension) | diff --git a/packages/gittensory-engine/README.md b/packages/gittensory-engine/README.md index a4124aeb0c..be54700a5c 100644 --- a/packages/gittensory-engine/README.md +++ b/packages/gittensory-engine/README.md @@ -535,6 +535,7 @@ dashboard progress summaries: - `hasPlanPendingSteps(plan)` — any step is `pending` - `hasPlanRunningSteps(plan)` — any step is `running` - `hasPlanSkippedSteps(plan)` — any step is `skipped` +- `hasPlanCompletedSteps(plan)` — any step is `completed` ## Opportunity competition diff --git a/packages/gittensory-engine/src/index.ts b/packages/gittensory-engine/src/index.ts index 5d52e13836..9c926fd903 100644 --- a/packages/gittensory-engine/src/index.ts +++ b/packages/gittensory-engine/src/index.ts @@ -145,6 +145,7 @@ export { hasPlanFailedSteps } from "./plan-failure.js"; export { hasPlanPendingSteps } from "./plan-pending.js"; export { hasPlanRunningSteps } from "./plan-running.js"; export { hasPlanSkippedSteps } from "./plan-skipped.js"; +export { hasPlanCompletedSteps } from "./plan-completed.js"; export * from "./plan-templates.js"; export * from "./portfolio/queue.js"; export { diff --git a/packages/gittensory-engine/src/plan-completed.ts b/packages/gittensory-engine/src/plan-completed.ts new file mode 100644 index 0000000000..ffdf11b75a --- /dev/null +++ b/packages/gittensory-engine/src/plan-completed.ts @@ -0,0 +1,8 @@ +import type { PlanDag } from "./plan-export.js"; + +/** + * Return whether any step in the plan is completed. Pure — reads the plan DAG only. + */ +export function hasPlanCompletedSteps(plan: PlanDag): boolean { + return plan.steps.some((step) => step.status === "completed"); +} diff --git a/test/unit/plan-completed.test.ts b/test/unit/plan-completed.test.ts new file mode 100644 index 0000000000..b4804f60e4 --- /dev/null +++ b/test/unit/plan-completed.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from "vitest"; + +import { hasPlanCompletedSteps } from "../../packages/gittensory-engine/src/plan-completed"; +import type { PlanStep } from "../../packages/gittensory-engine/src/plan-export"; + +function step(over: Partial & { id: string; title: string }): PlanStep { + return { + actionClass: undefined, + dependsOn: [], + status: "pending", + attempts: 0, + maxAttempts: 3, + lastError: null, + ...over, + }; +} + +describe("hasPlanCompletedSteps", () => { + it("returns false for an empty plan", () => { + expect(hasPlanCompletedSteps({ steps: [] })).toBe(false); + }); + + it("returns false when no step is completed", () => { + expect( + hasPlanCompletedSteps({ + steps: [ + step({ id: "a", title: "Build", status: "pending" }), + step({ id: "b", title: "Test", status: "running" }), + ], + }), + ).toBe(false); + }); + + it("returns true when at least one step is completed", () => { + expect( + hasPlanCompletedSteps({ + steps: [ + step({ id: "a", title: "Build", status: "completed" }), + step({ id: "b", title: "Test", status: "pending" }), + ], + }), + ).toBe(true); + }); + + it("is exported from the package barrel", async () => { + const barrel = await import("../../packages/gittensory-engine/src/index"); + expect(typeof barrel.hasPlanCompletedSteps).toBe("function"); + expect( + barrel.hasPlanCompletedSteps({ + steps: [step({ id: "a", title: "A", status: "completed" })], + }), + ).toBe(true); + }); +});