Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 16 additions & 9 deletions .github/workflows/publish-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
- name: Validate the manifests declare this version
run: |
VERSION="${{ steps.resolve.outputs.value }}"
for f in packages/durable-streams/deno.json packages/runtime/deno.json packages/core/deno.json packages/acp/deno.json packages/testing/deno.json packages/test-agent/deno.json packages/web/deno.json packages/workflow/deno.json packages/cli/deno.json packages/code-review-agent/deno.json; do
for f in packages/durable-streams/deno.json packages/runtime/deno.json packages/core/deno.json packages/acp/deno.json packages/workflow/deno.json packages/git/deno.json packages/testing/deno.json packages/test-agent/deno.json packages/web/deno.json packages/cli/deno.json packages/code-review-agent/deno.json; do
declared="$(jq -r .version "$f")"
if [ "$declared" != "$VERSION" ]; then
echo "::error::$f declares $declared, not $VERSION — the tag does not match the manifests"
Expand Down Expand Up @@ -89,6 +89,20 @@ jobs:
package: packages/acp
version: ${{ needs.version.outputs.value }}

workflow:
needs: [version, core, durable-streams, runtime]
uses: ./.github/workflows/publish-one.yml
with:
package: packages/workflow
version: ${{ needs.version.outputs.value }}

git:
needs: [version, core, durable-streams, runtime, workflow]
uses: ./.github/workflows/publish-one.yml
with:
package: packages/git
version: ${{ needs.version.outputs.value }}

testing:
needs: [version, core, durable-streams, runtime]
uses: ./.github/workflows/publish-one.yml
Expand All @@ -110,15 +124,8 @@ jobs:
package: packages/web
version: ${{ needs.version.outputs.value }}

workflow:
needs: [version, core, durable-streams, runtime]
uses: ./.github/workflows/publish-one.yml
with:
package: packages/workflow
version: ${{ needs.version.outputs.value }}

cli:
needs: [version, acp, core, durable-streams, runtime, test-agent, testing, web, workflow]
needs: [version, acp, core, durable-streams, git, runtime, test-agent, testing, web, workflow]
uses: ./.github/workflows/publish-one.yml
with:
package: packages/cli
Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ Locate the package that owns the behavior you are about to change:
config — that every other package reaches the host through.
- `packages/durable-streams/` — the journal protocol, replay and divergence.
- `packages/workflow/` — workflow runs, run storage, and the Workspace.
- `packages/git/` — repository collaboration: `<Repository>`, `<Worktree>`,
`<Dir>`, the `Git.*` operations, pull requests and issues, and the GitHub
adapter behind them. It is the one Plugin `xmd` bundles, active by default for
the run-profile commands — `run`, `plan`, `syntax`, and `workflow start`,
`resume` and `fork`. The `xmd test` root is deliberately not one of them, so a
test document may shadow these names; a nested `<Execution host="run">` child
assembles the profile for itself.
- `packages/testing/` and `packages/test-agent/` — `<Test>` and the
deterministic agent; `packages/test-support/` is the one BDD surface all three
runtimes share.
Expand Down
125 changes: 75 additions & 50 deletions architecture.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"@executablemd/acp": "workspace:*",
"@executablemd/core": "workspace:*",
"@executablemd/durable-streams": "workspace:*",
"@executablemd/git": "workspace:*",
"@executablemd/runtime": "workspace:*",
"@executablemd/test-agent": "workspace:*",
"@executablemd/testing": "workspace:*",
Expand Down
30 changes: 14 additions & 16 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ import {
renderSyntaxMarkdown,
syntaxSymbols,
} from "./syntax.ts";
import { loadPlugins } from "./plugin-loader.ts";
import { assembleRunProfile } from "./run-profile.ts";
import type { PluginModuleLoader } from "./plugin-loader.ts";
import { installPlugins, NO_PLUGINS } from "./plugin-host.ts";
import type { CommandPlugins } from "./plugin-host.ts";
Expand All @@ -160,7 +160,7 @@ import type { HostWorkflowInstaller, WorkflowHost, WorkflowStart } from "./workf
import { runWorkflowManagement } from "./workflow-management.ts";
import { establishDefinition } from "./workflow-definition.ts";
import type { EstablishedDefinition } from "./workflow-definition.ts";
import { useCompositionComponents, useWorkflowServiceDenial } from "@executablemd/workflow";
import { useWorkflowServiceDenial } from "@executablemd/workflow";
import denoJson from "../deno.json" with { type: "json" };

const SECRET_DETECTION_OPTION = "--secret-detection";
Expand Down Expand Up @@ -954,12 +954,11 @@ export function* installDocumentComponents(mode: DocumentMode, verbose: boolean)
// `<Verbose>` is registered at all.
yield* Config.around({ verbose: () => verbose }, { at: "min" });

// The repository-composition vocabulary, as ordinary shadowable defaults,
// with the documentation that describes it. Bootstrapping it installs no
// provider, discovers no repository, acquires no lock and reaches no network:
// what a name *does* is decided by whichever provider the command installed,
// and a runtime that installs none still resolves every one of these.
yield* useCompositionComponents();
// The repository-composition vocabulary is not bootstrapped here. It belongs
// to the bundled Git Plugin, which the run profile installs in the command
// scope this assembly runs inside — so `xmd run` and a nested
// `host="run"` child resolve every one of those names, and the `xmd test`
// root, whose profile carries no Plugin, resolves none of them.

// Compose testing around the single core execution entrypoint: both
// commands register the components (assertions work in regular documents,
Expand Down Expand Up @@ -2980,10 +2979,12 @@ export function* runXmd(
* document. `undefined` is an invocation that installs none — help, the
* version, and the internal worker mode.
*
* What a command line selected is the complete list, in the order it was
* written. XMD bundles none and defaults to none: a command that named no
* `--plugin` installs nothing, and a package that happens to be installed stays
* inert until it is named.
* What a command runs with is the profile `assembleRunProfile()` builds: the
* one Plugin XMD bundles, for the run-profile commands the Plugin itself
* declares for, and then whatever the operator selected in the order they wrote
* it. A package that happens to be installed stays inert until it is named,
* and a command outside that profile — `xmd test`, `upgrade`, a workflow
* management action — carries no Plugin it did not ask for.
*/
function* withPlugins(
selection: PluginSelection | undefined,
Expand All @@ -2996,10 +2997,7 @@ function* withPlugins(
}
let plugins: CommandPlugins;
try {
// Captured before the first module is loaded, so a relative path and a
// package specifier both resolve where the caller is standing.
const directory = yield* cwd();
const loaded = yield* loadPlugins(selection.specifiers, directory, loadPluginModule);
const loaded = yield* assembleRunProfile(selection, loadPluginModule);
plugins = yield* installPlugins(loaded, {
command: selection.command,
args: selection.args,
Expand Down
7 changes: 2 additions & 5 deletions packages/cli/src/compiled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ import { compiledUpgradeAssembly } from "./compiled-upgrade.ts";
import { useMachineSessions } from "./session-coordinator.ts";
import { useDenoWorkflowHost } from "./deno-workflow.ts";
import { denoRunRepositories } from "./deno-repositories.ts";
import {
isCredentialHelperMode,
runCredentialHelper,
} from "@executablemd/workflow/credential-helper";
import type { HelperAssembly } from "@executablemd/workflow/credential-helper";
import { isCredentialHelperMode, runCredentialHelper } from "@executablemd/git/credential-helper";
import type { HelperAssembly } from "@executablemd/git/credential-helper";
import { useCompiledService } from "./compiled-service.ts";

/**
Expand Down
5 changes: 1 addition & 4 deletions packages/cli/src/credential-helper-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@

import process from "node:process";
import { main } from "effection";
import {
isCredentialHelperMode,
runCredentialHelper,
} from "@executablemd/workflow/credential-helper";
import { isCredentialHelperMode, runCredentialHelper } from "@executablemd/git/credential-helper";

// The launcher already names the mode; what follows it is Git's operation. The
// whole of what this program does is awaited, so a failure to read the request
Expand Down
19 changes: 8 additions & 11 deletions packages/cli/src/deno-repositories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,27 +16,26 @@

import type { Operation } from "effection";
import { cwd } from "@executablemd/runtime";
import { useRunComposition } from "@executablemd/workflow/deno";
import type { HelperAssembly } from "@executablemd/workflow/credential-helper";
import { gitHubIssuesConfiguration } from "./github-issues-config.ts";
import { gitHubPullRequestsConfiguration } from "./github-pull-requests-config.ts";
import { useRunComposition } from "@executablemd/git/deno";
import type { HelperAssembly } from "@executablemd/git/credential-helper";
import { DEFAULT_REPOSITORY_ROOT } from "./run-repositories.ts";
import type { RepositoryInstaller } from "./run-repositories.ts";

/**
* The live provider Deno and the compiled binary install.
*
* The two GitHub configurations are read once, when the installer runs, so an
* operator who wrote something this host cannot use learns it before a document
* expands rather than in the middle of one.
* It reads no configuration. The two GitHub variables belong to the adapter
* inside `@executablemd/git`, which reads them when an invoked GitHub-backed
* operation needs them — so a run that opens no pull request and files no issue
* reads neither, and an operator who wrote something unusable learns it from
* the operation that needed it rather than from a command that was only
* starting up.
*/
export function denoRunRepositories(
helper: HelperAssembly,
root: string = DEFAULT_REPOSITORY_ROOT,
): RepositoryInstaller {
return function* (): Operation<void> {
const gitHubIssues = yield* gitHubIssuesConfiguration();
const gitHubPullRequests = yield* gitHubPullRequestsConfiguration();
yield* useRunComposition({
root,
// The directory this execution starts in, which is where the ambient
Expand All @@ -45,8 +44,6 @@ export function denoRunRepositories(
// working directory is discovered from that one.
cwd: yield* cwd(),
helper,
...(gitHubIssues === undefined ? {} : { gitHubIssues }),
...(gitHubPullRequests === undefined ? {} : { gitHubPullRequests }),
});
};
}
25 changes: 12 additions & 13 deletions packages/cli/src/deno-workflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,12 @@ import {
useWorkflowRunHost,
withWorkflowWorkspace,
} from "@executablemd/workflow/deno";
import { gitWorkspaceAttachment } from "@executablemd/git/deno";
import type { WorkflowExecutionTransitions } from "@executablemd/workflow/deno";
import type { WorkflowRunDatabase } from "@executablemd/workflow";
import type { HelperAssembly } from "@executablemd/workflow/credential-helper";
import type { HelperAssembly } from "@executablemd/git/credential-helper";
import { readLegacyDefinitionSource } from "./workflow-source.ts";
import type { WorkflowHost } from "./workflow.ts";
import { gitHubIssuesConfiguration } from "./github-issues-config.ts";
import { gitHubPullRequestsConfiguration } from "./github-pull-requests-config.ts";
import { useWorkflowAgentProfile } from "./workflow-agent.ts";

/** Where a run lives when nothing says otherwise. */
Expand All @@ -48,13 +47,6 @@ export function* useDenoWorkflowHost(helper: HelperAssembly): Operation<Workflow
const configured = yield* readEnv(RUN_STORAGE_ROOT_ENV);
const root =
configured === undefined || configured === "" ? DEFAULT_RUN_STORAGE_ROOT : configured;
// Read once, at host construction, so an operator who wrote something this
// host cannot use learns it before a document runs rather than in the middle
// of one. Absent installs no issue provider at all.
const gitHubIssues = yield* gitHubIssuesConfiguration();
// Read once, here, for the same reason: an operator who wrote something this
// host cannot use learns it before a document runs.
const gitHubPullRequests = yield* gitHubPullRequestsConfiguration();
return {
useRunHost(): Operation<WorkflowExecutionTransitions> {
// The same reader the lifecycle installation captures. A version-1 run
Expand All @@ -74,9 +66,16 @@ export function* useDenoWorkflowHost(helper: HelperAssembly): Operation<Workflow
},
attach<T>(database: WorkflowRunDatabase, operation: Operation<T>): Operation<T> {
return withWorkflowWorkspace(database, operation, {
...(gitHubIssues === undefined ? {} : { gitHubIssues }),
...(gitHubPullRequests === undefined ? {} : { gitHubPullRequests }),
helper,
// The Git vocabulary is a feature this host attaches, not something the
// run's own package installs: what `<Repository>` and `<Git.Push>` mean
// belongs to `@executablemd/git`, and the credential helper and the two
// GitHub ceilings are configuration for that feature rather than for
// the run.
attachments: [
gitWorkspaceAttachment({
helper,
}),
],
// Only a live or partial attachment reaches this, which is what keeps a
// completed replay from starting an agent process to restore a turn it
// already has the answer to.
Expand Down
7 changes: 2 additions & 5 deletions packages/cli/src/deno.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ import type { UpgradeAssembly } from "./upgrade.ts";
import { useMachineSessions } from "./session-coordinator.ts";
import { useDenoWorkflowHost } from "./deno-workflow.ts";
import { denoRunRepositories } from "./deno-repositories.ts";
import {
isCredentialHelperMode,
runCredentialHelper,
} from "@executablemd/workflow/credential-helper";
import type { HelperAssembly } from "@executablemd/workflow/credential-helper";
import { isCredentialHelperMode, runCredentialHelper } from "@executablemd/git/credential-helper";
import type { HelperAssembly } from "@executablemd/git/credential-helper";
import { useDenoService } from "./deno-service.ts";

const ENTRYPOINT = fileURLToPath(import.meta.url);
Expand Down
10 changes: 6 additions & 4 deletions packages/cli/src/plugin-host.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/**
* Installing the Plugins one command runs with.
*
* The modules the operator selected, in the order they wrote them, and nothing
* else: XMD ships no Plugin and installs none by default, so a command that
* named none installs none. That order fixes how middleware composes — the
* first Plugin installed is the outermost wrapper — and decides nothing else:
* The list it is handed, in the order it is handed them, and nothing else.
* This is the generic installer: which Plugins a command runs with is the
* caller's business — `assembleRunProfile()` is where XMD's own bundled prefix
* is decided — and an empty list installs nothing at all. That order fixes how
* middleware composes — the first Plugin installed is the outermost wrapper —
* and decides nothing else:
* two Plugins claiming one name, one component name or one structural construct
* are refused rather than settled by position.
*
Expand Down
Loading
Loading