gsd-pi 2.41.0-dev.cac69f9 → 2.42.0-dev.1df898f
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/README.md +23 -0
- package/dist/cli.js +18 -3
- package/dist/loader.js +3 -1
- package/dist/resource-loader.js +39 -6
- package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
- package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
- package/dist/resources/extensions/async-jobs/index.js +2 -0
- package/dist/resources/extensions/gsd/auto/loop.js +80 -0
- package/dist/resources/extensions/gsd/auto/phases.js +3 -5
- package/dist/resources/extensions/gsd/auto/session.js +6 -0
- package/dist/resources/extensions/gsd/auto-dashboard.js +2 -0
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
- package/dist/resources/extensions/gsd/auto-start.js +8 -11
- package/dist/resources/extensions/gsd/auto.js +28 -1
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
- package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
- package/dist/resources/extensions/gsd/commands/catalog.js +32 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
- package/dist/resources/extensions/gsd/context-injector.js +74 -0
- package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
- package/dist/resources/extensions/gsd/custom-verification.js +145 -0
- package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
- package/dist/resources/extensions/gsd/definition-loader.js +352 -0
- package/dist/resources/extensions/gsd/detection.js +19 -0
- package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
- package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
- package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
- package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
- package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
- package/dist/resources/extensions/gsd/engine-types.js +8 -0
- package/dist/resources/extensions/gsd/execution-policy.js +8 -0
- package/dist/resources/extensions/gsd/forensics.js +84 -0
- package/dist/resources/extensions/gsd/git-constants.js +1 -0
- package/dist/resources/extensions/gsd/git-service.js +1 -1
- package/dist/resources/extensions/gsd/graph.js +225 -0
- package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences.js +59 -8
- package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/dist/resources/extensions/gsd/repo-identity.js +46 -5
- package/dist/resources/extensions/gsd/run-manager.js +134 -0
- package/dist/resources/extensions/gsd/service-tier.js +13 -4
- package/dist/resources/extensions/gsd/session-lock.js +2 -2
- package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
- package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
- package/dist/resources/extensions/gsd/worktree.js +2 -2
- package/dist/resources/extensions/mcp-client/index.js +2 -1
- package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
- package/dist/resources/skills/create-workflow/SKILL.md +103 -0
- package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
- package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
- package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
- package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
- package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
- package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
- package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
- package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
- package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
- package/dist/web/standalone/.next/server/chunks/229.js +2 -2
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web-mode.d.ts +2 -0
- package/dist/web-mode.js +40 -4
- package/package.json +1 -1
- package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/agent.js +2 -0
- package/packages/pi-agent-core/dist/agent.js.map +1 -1
- package/packages/pi-agent-core/dist/types.d.ts +6 -0
- package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
- package/packages/pi-agent-core/dist/types.js.map +1 -1
- package/packages/pi-agent-core/src/agent.test.ts +53 -0
- package/packages/pi-agent-core/src/agent.ts +3 -0
- package/packages/pi-agent-core/src/types.ts +6 -0
- package/packages/pi-agent-core/tsconfig.json +1 -1
- package/packages/pi-ai/dist/models.d.ts +5 -3
- package/packages/pi-ai/dist/models.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +1135 -1588
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/models.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
- package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
- package/packages/pi-ai/scripts/generate-models.ts +1543 -0
- package/packages/pi-ai/src/models.generated.ts +1140 -1593
- package/packages/pi-ai/src/models.ts +7 -4
- package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
- package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
- package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
- package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
- package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
- package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
- package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
- package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
- package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
- package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
- package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
- package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
- package/src/resources/extensions/async-jobs/index.ts +1 -0
- package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
- package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
- package/src/resources/extensions/gsd/auto/loop.ts +91 -0
- package/src/resources/extensions/gsd/auto/phases.ts +3 -5
- package/src/resources/extensions/gsd/auto/session.ts +6 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
- package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
- package/src/resources/extensions/gsd/auto-start.ts +7 -10
- package/src/resources/extensions/gsd/auto.ts +31 -1
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
- package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
- package/src/resources/extensions/gsd/commands/catalog.ts +32 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
- package/src/resources/extensions/gsd/context-injector.ts +100 -0
- package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
- package/src/resources/extensions/gsd/custom-verification.ts +180 -0
- package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
- package/src/resources/extensions/gsd/definition-loader.ts +462 -0
- package/src/resources/extensions/gsd/detection.ts +19 -0
- package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
- package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
- package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
- package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
- package/src/resources/extensions/gsd/doctor-types.ts +1 -0
- package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
- package/src/resources/extensions/gsd/engine-types.ts +71 -0
- package/src/resources/extensions/gsd/execution-policy.ts +43 -0
- package/src/resources/extensions/gsd/forensics.ts +92 -0
- package/src/resources/extensions/gsd/git-constants.ts +1 -0
- package/src/resources/extensions/gsd/git-service.ts +0 -1
- package/src/resources/extensions/gsd/gitignore.ts +1 -1
- package/src/resources/extensions/gsd/graph.ts +312 -0
- package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
- package/src/resources/extensions/gsd/preferences-types.ts +3 -0
- package/src/resources/extensions/gsd/preferences.ts +62 -6
- package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
- package/src/resources/extensions/gsd/repo-identity.ts +48 -5
- package/src/resources/extensions/gsd/run-manager.ts +180 -0
- package/src/resources/extensions/gsd/service-tier.ts +17 -4
- package/src/resources/extensions/gsd/session-lock.ts +2 -2
- package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
- package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
- package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
- package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
- package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
- package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
- package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
- package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
- package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
- package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
- package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
- package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
- package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
- package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
- package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
- package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
- package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
- package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
- package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
- package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
- package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
- package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
- package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
- package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
- package/src/resources/extensions/gsd/worktree-resolver.ts +2 -3
- package/src/resources/extensions/gsd/worktree.ts +2 -2
- package/src/resources/extensions/mcp-client/index.ts +5 -1
- package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
- package/src/resources/skills/create-workflow/SKILL.md +103 -0
- package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
- package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
- package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
- package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
- package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
- package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
- package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
- package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
- package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
- /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* custom-workflow-engine.test.ts — Tests for CustomWorkflowEngine and CustomExecutionPolicy.
|
|
3
|
+
*
|
|
4
|
+
* Uses real temp directories with actual GRAPH.yaml files — no mocks.
|
|
5
|
+
* Tests the full engine lifecycle: deriveState → resolveDispatch → reconcile.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, afterEach } from "node:test";
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { mkdtempSync, rmSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { parse } from "yaml";
|
|
14
|
+
|
|
15
|
+
import { CustomWorkflowEngine } from "../custom-workflow-engine.ts";
|
|
16
|
+
import { CustomExecutionPolicy } from "../custom-execution-policy.ts";
|
|
17
|
+
import { writeGraph, readGraph, type WorkflowGraph, type GraphStep } from "../graph.ts";
|
|
18
|
+
import { stringify } from "yaml";
|
|
19
|
+
|
|
20
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const tmpDirs: string[] = [];
|
|
23
|
+
|
|
24
|
+
function makeTmpDir(): string {
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), "engine-test-"));
|
|
26
|
+
tmpDirs.push(dir);
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
afterEach(() => {
|
|
31
|
+
for (const d of tmpDirs) {
|
|
32
|
+
try { rmSync(d, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* Windows EPERM */ }
|
|
33
|
+
}
|
|
34
|
+
tmpDirs.length = 0;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
function makeStep(overrides: Partial<GraphStep> & { id: string }): GraphStep {
|
|
38
|
+
return {
|
|
39
|
+
title: overrides.id,
|
|
40
|
+
status: "pending",
|
|
41
|
+
prompt: `Do ${overrides.id}`,
|
|
42
|
+
dependsOn: [],
|
|
43
|
+
...overrides,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function makeGraph(steps: GraphStep[], name = "test-wf"): WorkflowGraph {
|
|
48
|
+
return {
|
|
49
|
+
steps,
|
|
50
|
+
metadata: { name, createdAt: "2026-01-01T00:00:00.000Z" },
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Write a graph to a temp dir and return engine + dir. Also writes a minimal DEFINITION.yaml so resolveDispatch/injectContext can read it. */
|
|
55
|
+
function setupEngine(
|
|
56
|
+
steps: GraphStep[],
|
|
57
|
+
name = "test-wf",
|
|
58
|
+
): { engine: CustomWorkflowEngine; runDir: string } {
|
|
59
|
+
const runDir = makeTmpDir();
|
|
60
|
+
const graph = makeGraph(steps, name);
|
|
61
|
+
writeGraph(runDir, graph);
|
|
62
|
+
|
|
63
|
+
// Write a minimal DEFINITION.yaml matching the graph steps
|
|
64
|
+
const def = {
|
|
65
|
+
version: 1,
|
|
66
|
+
name,
|
|
67
|
+
steps: steps.map((s) => ({
|
|
68
|
+
id: s.id,
|
|
69
|
+
name: s.title,
|
|
70
|
+
prompt: s.prompt,
|
|
71
|
+
requires: s.dependsOn,
|
|
72
|
+
produces: [],
|
|
73
|
+
})),
|
|
74
|
+
};
|
|
75
|
+
writeFileSync(join(runDir, "DEFINITION.yaml"), stringify(def), "utf-8");
|
|
76
|
+
|
|
77
|
+
return { engine: new CustomWorkflowEngine(runDir), runDir };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ─── deriveState ─────────────────────────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
describe("CustomWorkflowEngine.deriveState", () => {
|
|
83
|
+
it("returns running phase when steps are pending", async () => {
|
|
84
|
+
const { engine } = setupEngine([
|
|
85
|
+
makeStep({ id: "a" }),
|
|
86
|
+
makeStep({ id: "b", dependsOn: ["a"] }),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const state = await engine.deriveState("/unused");
|
|
90
|
+
|
|
91
|
+
assert.equal(state.phase, "running");
|
|
92
|
+
assert.equal(state.isComplete, false);
|
|
93
|
+
assert.ok(state.raw, "raw should contain the graph");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("returns complete phase when all steps are complete", async () => {
|
|
97
|
+
const { engine } = setupEngine([
|
|
98
|
+
makeStep({ id: "a", status: "complete" }),
|
|
99
|
+
makeStep({ id: "b", status: "complete" }),
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
const state = await engine.deriveState("/unused");
|
|
103
|
+
|
|
104
|
+
assert.equal(state.phase, "complete");
|
|
105
|
+
assert.equal(state.isComplete, true);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("treats expanded steps as done for completion check", async () => {
|
|
109
|
+
const { engine } = setupEngine([
|
|
110
|
+
makeStep({ id: "a", status: "expanded" }),
|
|
111
|
+
makeStep({ id: "a--001", status: "complete", parentStepId: "a" }),
|
|
112
|
+
makeStep({ id: "b", status: "complete" }),
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
const state = await engine.deriveState("/unused");
|
|
116
|
+
|
|
117
|
+
assert.equal(state.phase, "complete");
|
|
118
|
+
assert.equal(state.isComplete, true);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ─── resolveDispatch ─────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
describe("CustomWorkflowEngine.resolveDispatch", () => {
|
|
125
|
+
it("returns dispatch for first pending step", async () => {
|
|
126
|
+
const { engine } = setupEngine([
|
|
127
|
+
makeStep({ id: "step-1", prompt: "Do the first thing" }),
|
|
128
|
+
makeStep({ id: "step-2", dependsOn: ["step-1"] }),
|
|
129
|
+
], "my-workflow");
|
|
130
|
+
|
|
131
|
+
const state = await engine.deriveState("/unused");
|
|
132
|
+
const dispatch = await engine.resolveDispatch(state, { basePath: "/unused" });
|
|
133
|
+
|
|
134
|
+
assert.equal(dispatch.action, "dispatch");
|
|
135
|
+
if (dispatch.action === "dispatch") {
|
|
136
|
+
assert.equal(dispatch.step.unitType, "custom-step");
|
|
137
|
+
assert.equal(dispatch.step.unitId, "my-workflow/step-1");
|
|
138
|
+
assert.equal(dispatch.step.prompt, "Do the first thing");
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("returns stop when all steps are complete", async () => {
|
|
143
|
+
const { engine } = setupEngine([
|
|
144
|
+
makeStep({ id: "a", status: "complete" }),
|
|
145
|
+
makeStep({ id: "b", status: "complete" }),
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
const state = await engine.deriveState("/unused");
|
|
149
|
+
const dispatch = await engine.resolveDispatch(state, { basePath: "/unused" });
|
|
150
|
+
|
|
151
|
+
assert.equal(dispatch.action, "stop");
|
|
152
|
+
if (dispatch.action === "stop") {
|
|
153
|
+
assert.equal(dispatch.reason, "All steps complete");
|
|
154
|
+
assert.equal(dispatch.level, "info");
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("respects dependency ordering", async () => {
|
|
159
|
+
const { engine } = setupEngine([
|
|
160
|
+
makeStep({ id: "a" }),
|
|
161
|
+
makeStep({ id: "b", dependsOn: ["a"] }),
|
|
162
|
+
makeStep({ id: "c", dependsOn: ["b"] }),
|
|
163
|
+
], "dep-wf");
|
|
164
|
+
|
|
165
|
+
const state = await engine.deriveState("/unused");
|
|
166
|
+
const dispatch = await engine.resolveDispatch(state, { basePath: "/unused" });
|
|
167
|
+
|
|
168
|
+
// Should pick "a" (no deps), not "b" or "c"
|
|
169
|
+
assert.equal(dispatch.action, "dispatch");
|
|
170
|
+
if (dispatch.action === "dispatch") {
|
|
171
|
+
assert.equal(dispatch.step.unitId, "dep-wf/a");
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("picks next eligible step when earlier deps are complete", async () => {
|
|
176
|
+
const { engine } = setupEngine([
|
|
177
|
+
makeStep({ id: "a", status: "complete" }),
|
|
178
|
+
makeStep({ id: "b", dependsOn: ["a"] }),
|
|
179
|
+
makeStep({ id: "c", dependsOn: ["b"] }),
|
|
180
|
+
], "dep-wf");
|
|
181
|
+
|
|
182
|
+
const state = await engine.deriveState("/unused");
|
|
183
|
+
const dispatch = await engine.resolveDispatch(state, { basePath: "/unused" });
|
|
184
|
+
|
|
185
|
+
// "a" is done, "b" deps met, should pick "b"
|
|
186
|
+
assert.equal(dispatch.action, "dispatch");
|
|
187
|
+
if (dispatch.action === "dispatch") {
|
|
188
|
+
assert.equal(dispatch.step.unitId, "dep-wf/b");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ─── reconcile ───────────────────────────────────────────────────────────
|
|
194
|
+
|
|
195
|
+
describe("CustomWorkflowEngine.reconcile", () => {
|
|
196
|
+
it("marks step complete in GRAPH.yaml on disk", async () => {
|
|
197
|
+
const { engine, runDir } = setupEngine([
|
|
198
|
+
makeStep({ id: "step-1" }),
|
|
199
|
+
makeStep({ id: "step-2", dependsOn: ["step-1"] }),
|
|
200
|
+
], "wf");
|
|
201
|
+
|
|
202
|
+
const state = await engine.deriveState("/unused");
|
|
203
|
+
const result = await engine.reconcile(state, {
|
|
204
|
+
unitType: "custom-step",
|
|
205
|
+
unitId: "wf/step-1",
|
|
206
|
+
startedAt: Date.now() - 1000,
|
|
207
|
+
finishedAt: Date.now(),
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
assert.equal(result.outcome, "continue");
|
|
211
|
+
|
|
212
|
+
// Verify on-disk state
|
|
213
|
+
const graph = readGraph(runDir);
|
|
214
|
+
assert.equal(graph.steps[0].status, "complete");
|
|
215
|
+
assert.ok(graph.steps[0].finishedAt, "finishedAt should be set");
|
|
216
|
+
assert.equal(graph.steps[1].status, "pending");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("returns milestone-complete when all steps done", async () => {
|
|
220
|
+
const { engine } = setupEngine([
|
|
221
|
+
makeStep({ id: "only-step" }),
|
|
222
|
+
], "wf");
|
|
223
|
+
|
|
224
|
+
const state = await engine.deriveState("/unused");
|
|
225
|
+
const result = await engine.reconcile(state, {
|
|
226
|
+
unitType: "custom-step",
|
|
227
|
+
unitId: "wf/only-step",
|
|
228
|
+
startedAt: Date.now() - 1000,
|
|
229
|
+
finishedAt: Date.now(),
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
assert.equal(result.outcome, "milestone-complete");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("handles multi-segment unitId correctly", async () => {
|
|
236
|
+
const { engine, runDir } = setupEngine([
|
|
237
|
+
makeStep({ id: "deep-step" }),
|
|
238
|
+
], "nested/workflow");
|
|
239
|
+
|
|
240
|
+
const state = await engine.deriveState("/unused");
|
|
241
|
+
const result = await engine.reconcile(state, {
|
|
242
|
+
unitType: "custom-step",
|
|
243
|
+
unitId: "nested/workflow/deep-step",
|
|
244
|
+
startedAt: Date.now() - 1000,
|
|
245
|
+
finishedAt: Date.now(),
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
assert.equal(result.outcome, "milestone-complete");
|
|
249
|
+
const graph = readGraph(runDir);
|
|
250
|
+
assert.equal(graph.steps[0].status, "complete");
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ─── getDisplayMetadata ──────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
describe("CustomWorkflowEngine.getDisplayMetadata", () => {
|
|
257
|
+
it("returns correct progress summary", async () => {
|
|
258
|
+
const { engine } = setupEngine([
|
|
259
|
+
makeStep({ id: "a", status: "complete" }),
|
|
260
|
+
makeStep({ id: "b" }),
|
|
261
|
+
makeStep({ id: "c" }),
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
const state = await engine.deriveState("/unused");
|
|
265
|
+
const meta = engine.getDisplayMetadata(state);
|
|
266
|
+
|
|
267
|
+
assert.equal(meta.engineLabel, "WORKFLOW");
|
|
268
|
+
assert.equal(meta.currentPhase, "running");
|
|
269
|
+
assert.equal(meta.progressSummary, "Step 1/3");
|
|
270
|
+
assert.deepStrictEqual(meta.stepCount, { completed: 1, total: 3 });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("shows 0/N when no steps complete", async () => {
|
|
274
|
+
const { engine } = setupEngine([
|
|
275
|
+
makeStep({ id: "a" }),
|
|
276
|
+
makeStep({ id: "b" }),
|
|
277
|
+
]);
|
|
278
|
+
|
|
279
|
+
const state = await engine.deriveState("/unused");
|
|
280
|
+
const meta = engine.getDisplayMetadata(state);
|
|
281
|
+
|
|
282
|
+
assert.equal(meta.progressSummary, "Step 0/2");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it("shows N/N when all steps complete", async () => {
|
|
286
|
+
const { engine } = setupEngine([
|
|
287
|
+
makeStep({ id: "a", status: "complete" }),
|
|
288
|
+
makeStep({ id: "b", status: "complete" }),
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
const state = await engine.deriveState("/unused");
|
|
292
|
+
const meta = engine.getDisplayMetadata(state);
|
|
293
|
+
|
|
294
|
+
assert.equal(meta.progressSummary, "Step 2/2");
|
|
295
|
+
assert.equal(meta.currentPhase, "complete");
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ─── CustomExecutionPolicy ───────────────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
describe("CustomExecutionPolicy", () => {
|
|
302
|
+
it("verify returns continue", async () => {
|
|
303
|
+
// verify() reads DEFINITION.yaml from runDir to find step's verify policy
|
|
304
|
+
const runDir = makeTmpDir();
|
|
305
|
+
writeFileSync(join(runDir, "DEFINITION.yaml"), stringify({
|
|
306
|
+
version: 1, name: "wf", description: "test",
|
|
307
|
+
steps: [{ id: "step-1", name: "Step 1", prompt: "do it", produces: "step-1/output.md" }],
|
|
308
|
+
}));
|
|
309
|
+
const policy = new CustomExecutionPolicy(runDir);
|
|
310
|
+
const result = await policy.verify("custom-step", "wf/step-1", { basePath: runDir });
|
|
311
|
+
assert.equal(result, "continue");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("selectModel returns null", async () => {
|
|
315
|
+
const policy = new CustomExecutionPolicy("/tmp/run");
|
|
316
|
+
const result = await policy.selectModel("custom-step", "wf/step-1", { basePath: "/tmp" });
|
|
317
|
+
assert.equal(result, null);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("recover returns retry", async () => {
|
|
321
|
+
const policy = new CustomExecutionPolicy("/tmp/run");
|
|
322
|
+
const result = await policy.recover("custom-step", "wf/step-1", { basePath: "/tmp" });
|
|
323
|
+
assert.deepStrictEqual(result, { outcome: "retry", reason: "Default retry" });
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it("closeout returns no artifacts", async () => {
|
|
327
|
+
const policy = new CustomExecutionPolicy("/tmp/run");
|
|
328
|
+
const result = await policy.closeout("custom-step", "wf/step-1", {
|
|
329
|
+
basePath: "/tmp",
|
|
330
|
+
startedAt: Date.now(),
|
|
331
|
+
});
|
|
332
|
+
assert.deepStrictEqual(result, { committed: false, artifacts: [] });
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("prepareWorkspace resolves without error", async () => {
|
|
336
|
+
const policy = new CustomExecutionPolicy("/tmp/run");
|
|
337
|
+
await policy.prepareWorkspace("/tmp", "M001"); // Should not throw
|
|
338
|
+
});
|
|
339
|
+
});
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-custom-engine.test.ts — Tests that the custom engine path
|
|
3
|
+
* calls updateProgressWidget and that unitLabel handles "custom-step".
|
|
4
|
+
*
|
|
5
|
+
* Uses source-level assertions for the non-exported unitLabel function
|
|
6
|
+
* and the updateProgressWidget call placement. Tests exported helpers
|
|
7
|
+
* (unitVerb, unitPhaseLabel) directly.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it } from "node:test";
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { resolve } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
import { unitVerb, unitPhaseLabel } from "../auto-dashboard.js";
|
|
17
|
+
|
|
18
|
+
// ─── Tests ───────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
describe("Dashboard custom-engine: unitLabel and related helpers", () => {
|
|
21
|
+
it('unitVerb("custom-step") returns "executing workflow step"', () => {
|
|
22
|
+
assert.equal(unitVerb("custom-step"), "executing workflow step");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('unitPhaseLabel("custom-step") returns "WORKFLOW"', () => {
|
|
26
|
+
assert.equal(unitPhaseLabel("custom-step"), "WORKFLOW");
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('dashboard-overlay.ts contains a case for "custom-step" returning "Workflow Step"', () => {
|
|
30
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
31
|
+
const overlayPath = resolve(__filename, "../../dashboard-overlay.ts");
|
|
32
|
+
const source = readFileSync(overlayPath, "utf-8");
|
|
33
|
+
assert.ok(
|
|
34
|
+
source.includes('"custom-step"') && source.includes('"Workflow Step"'),
|
|
35
|
+
'dashboard-overlay.ts should contain case "custom-step": return "Workflow Step"',
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("Dashboard custom-engine: updateProgressWidget in custom engine path", () => {
|
|
41
|
+
it("loop.ts custom engine path includes updateProgressWidget call before runGuards", () => {
|
|
42
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
43
|
+
const loopPath = resolve(__filename, "../../auto/loop.ts");
|
|
44
|
+
const source = readFileSync(loopPath, "utf-8");
|
|
45
|
+
|
|
46
|
+
// Find the custom engine block
|
|
47
|
+
const customEngineStart = source.indexOf('s.activeEngineId !== "dev"');
|
|
48
|
+
assert.ok(customEngineStart > -1, "Should find custom engine path in loop.ts");
|
|
49
|
+
|
|
50
|
+
// The updateProgressWidget call should appear after the custom engine block start
|
|
51
|
+
// and before the runGuards call in that block
|
|
52
|
+
const afterCustomEngine = source.slice(customEngineStart);
|
|
53
|
+
const widgetCallIndex = afterCustomEngine.indexOf(
|
|
54
|
+
"deps.updateProgressWidget(ctx, iterData.unitType, iterData.unitId, iterData.state)",
|
|
55
|
+
);
|
|
56
|
+
const guardsCallIndex = afterCustomEngine.indexOf("runGuards(ic,");
|
|
57
|
+
assert.ok(widgetCallIndex > -1, "updateProgressWidget should be called in custom engine path");
|
|
58
|
+
assert.ok(
|
|
59
|
+
widgetCallIndex < guardsCallIndex,
|
|
60
|
+
"updateProgressWidget should be called before runGuards in custom engine path",
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("updateProgressWidget call is placed after iterData is built", () => {
|
|
65
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
66
|
+
const loopPath = resolve(__filename, "../../auto/loop.ts");
|
|
67
|
+
const source = readFileSync(loopPath, "utf-8");
|
|
68
|
+
|
|
69
|
+
const customEngineStart = source.indexOf('s.activeEngineId !== "dev"');
|
|
70
|
+
const afterCustomEngine = source.slice(customEngineStart);
|
|
71
|
+
|
|
72
|
+
// Verify custom engine path has iterData built before the widget call
|
|
73
|
+
const iterDataIndex = afterCustomEngine.indexOf("iterData = {");
|
|
74
|
+
const widgetIndex = afterCustomEngine.indexOf("deps.updateProgressWidget");
|
|
75
|
+
assert.ok(iterDataIndex > -1 && widgetIndex > -1, "Both iterData and widget call should exist");
|
|
76
|
+
assert.ok(
|
|
77
|
+
iterDataIndex < widgetIndex,
|
|
78
|
+
"iterData should be built before updateProgressWidget is called",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Verify the call uses iterData.state (which holds the derived GSD state)
|
|
82
|
+
assert.ok(
|
|
83
|
+
afterCustomEngine.includes("iterData.state"),
|
|
84
|
+
"Custom engine updateProgressWidget should reference iterData.state",
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
});
|