@plainconceptsplatform/workflows 0.6.1 → 0.16.2
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 +107 -88
- package/dist/catalog-installation.d.ts +39 -2
- package/dist/catalog-installation.js +172 -109
- package/dist/index.js +128 -73
- package/dist/package-baseline.d.ts +25 -0
- package/dist/package-baseline.js +138 -0
- package/dist/route-processing.d.ts +0 -2
- package/dist/route-processing.js +22 -91
- package/dist/stack-defaults.js +18 -12
- package/dist/tui.js +27 -43
- package/dist/worker-env.d.ts +46 -0
- package/dist/worker-env.js +179 -0
- package/dist/workflow-catalog.d.ts +4 -2
- package/dist/workflow-catalog.js +5 -3
- package/loops/actions/add-issue-labels/action.yml +20 -0
- package/loops/actions/audit-close/action.yml +180 -128
- package/loops/actions/classify-route/classify-route.sh +8 -2
- package/loops/actions/housekeeping/action.yml +251 -0
- package/loops/actions/merge-agent-pr/action.yml +13 -0
- package/loops/actions/report-workflow-errors/action.yml +414 -0
- package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +13 -1
- package/loops/actions/validate-triage-output/action.yml +1 -1
- package/loops/actions/validate-triage-output/validate-triage-output.sh +9 -5
- package/loops/actions/verify-composite-actions/verify-composite-actions.sh +53 -0
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +911 -170
- package/loops/templates/agentics/agentics-error-report.yml +98 -0
- package/loops/templates/opencode/opencode.ci.json +1 -1
- package/loops/workflows/agent-apply-review.md +438 -469
- package/loops/workflows/agent-audit.md +200 -213
- package/loops/workflows/agent-implement.md +591 -640
- package/loops/workflows/agent-merge-gate.md +826 -844
- package/loops/workflows/agent-refine.md +590 -633
- package/loops/workflows/agent-release.md +244 -258
- package/loops/workflows/agent-triage.md +476 -447
- package/loops/workflows/authorize-bot-work.yml +26 -6
- package/loops/workflows/shared/platform-defaults.md +18 -1
- package/loops/workflows/work-router.yml +1185 -1038
- package/package.json +9 -8
- package/dist/action-validation.test.d.ts +0 -1
- package/dist/action-validation.test.js +0 -87
- package/dist/catalog-installation.test.d.ts +0 -1
- package/dist/catalog-installation.test.js +0 -485
- package/dist/catalog-listing.test.d.ts +0 -1
- package/dist/catalog-listing.test.js +0 -150
- package/dist/index.test.d.ts +0 -1
- package/dist/index.test.js +0 -273
- package/dist/repository-inspection.test.d.ts +0 -1
- package/dist/repository-inspection.test.js +0 -77
- package/dist/route-processing.test.d.ts +0 -1
- package/dist/route-processing.test.js +0 -283
- package/dist/stack-defaults.test.d.ts +0 -1
- package/dist/stack-defaults.test.js +0 -266
- package/dist/tui.test.d.ts +0 -1
- package/dist/tui.test.js +0 -249
- package/dist/workflow-catalog.test.d.ts +0 -1
- package/dist/workflow-catalog.test.js +0 -29
- package/loops/actions/stale-recovery/action.yml +0 -288
- package/loops/actions/update-changelog/action.yml +0 -113
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plainconceptsplatform/workflows",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Install and update Platform GitHub agentic workflows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github-actions",
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
"loops",
|
|
26
26
|
"README.md"
|
|
27
27
|
],
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc --project tsconfig.build.json",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
32
|
+
"release": "pnpm build && pnpm test && npm publish --access public",
|
|
33
|
+
"prepack": "node ./scripts/copy-loops.mjs"
|
|
34
|
+
},
|
|
28
35
|
"engines": {
|
|
29
36
|
"node": ">=20.19"
|
|
30
37
|
},
|
|
@@ -33,11 +40,5 @@
|
|
|
33
40
|
"typescript": "^5.8.0",
|
|
34
41
|
"vitest": "^3.0.0",
|
|
35
42
|
"yaml": "^2.9.0"
|
|
36
|
-
},
|
|
37
|
-
"scripts": {
|
|
38
|
-
"build": "tsc --project tsconfig.json",
|
|
39
|
-
"test": "vitest run",
|
|
40
|
-
"typecheck": "tsc --noEmit --project tsconfig.json",
|
|
41
|
-
"release": "pnpm build && pnpm test && npm publish --access public"
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { readFile, readdir } from "node:fs/promises";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { describe, expect, it } from "vitest";
|
|
5
|
-
import { parse } from "yaml";
|
|
6
|
-
async function collectActionYmlFiles(actionsDir) {
|
|
7
|
-
const entries = await readdir(actionsDir, { withFileTypes: true });
|
|
8
|
-
const results = [];
|
|
9
|
-
for (const entry of entries) {
|
|
10
|
-
if (entry.isDirectory()) {
|
|
11
|
-
for (const sub of await readdir(join(actionsDir, entry.name), { withFileTypes: true })) {
|
|
12
|
-
if (sub.isFile() && sub.name === "action.yml") {
|
|
13
|
-
results.push(join(actionsDir, entry.name, sub.name));
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return results;
|
|
19
|
-
}
|
|
20
|
-
async function loadManifests() {
|
|
21
|
-
// catalogSourcePath() resolves against the published package layout, where loops/ ships
|
|
22
|
-
// next to the compiled module. In this repository loops/ lives at the root, two levels
|
|
23
|
-
// above this test, so resolve it from the source tree instead.
|
|
24
|
-
const actionsDir = join(fileURLToPath(new URL("../../loops", import.meta.url)), "actions");
|
|
25
|
-
const files = await collectActionYmlFiles(actionsDir);
|
|
26
|
-
return Promise.all(files.map(async (file) => {
|
|
27
|
-
const content = await readFile(file, "utf8");
|
|
28
|
-
return { file, content, manifest: parse(content) };
|
|
29
|
-
}));
|
|
30
|
-
}
|
|
31
|
-
describe("action.yml manifest validation", () => {
|
|
32
|
-
it("finds action.yml files under loops/actions/", async () => {
|
|
33
|
-
const manifests = await loadManifests();
|
|
34
|
-
expect(manifests.length).toBeGreaterThanOrEqual(1);
|
|
35
|
-
});
|
|
36
|
-
it("every action.yml parses as valid YAML", async () => {
|
|
37
|
-
for (const { file, content } of await loadManifests()) {
|
|
38
|
-
expect(() => parse(content), `${file}: failed to parse as YAML`).not.toThrow();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
it("every action.yml has runs.using === composite", async () => {
|
|
42
|
-
for (const { file, manifest } of await loadManifests()) {
|
|
43
|
-
expect(manifest.runs?.using, `${file}: runs.using is not "composite"`).toBe("composite");
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
it("every action.yml has at least one step in runs.steps", async () => {
|
|
47
|
-
for (const { file, manifest } of await loadManifests()) {
|
|
48
|
-
expect(manifest.runs?.steps, `${file}: runs.steps is undefined`).toBeDefined();
|
|
49
|
-
expect(manifest.runs.steps.length, `${file}: runs.steps is empty — file may be truncated`).toBeGreaterThanOrEqual(1);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
it("every step with shell: also has run:", async () => {
|
|
53
|
-
for (const { file, manifest } of await loadManifests()) {
|
|
54
|
-
const steps = manifest.runs?.steps ?? [];
|
|
55
|
-
for (const [index, step] of steps.entries()) {
|
|
56
|
-
const label = step.name ?? step.id ?? `step ${index}`;
|
|
57
|
-
if (step.shell !== undefined) {
|
|
58
|
-
expect(step.run, `${file}: step "${label}" has shell: but no run:`).toBeDefined();
|
|
59
|
-
expect(typeof step.run, `${file}: step "${label}" run is not a string`).toBe("string");
|
|
60
|
-
expect(step.run.trim().length, `${file}: step "${label}" run is empty`).toBeGreaterThan(0);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
it("every step with env: also has run: or uses:", async () => {
|
|
66
|
-
for (const { file, manifest } of await loadManifests()) {
|
|
67
|
-
const steps = manifest.runs?.steps ?? [];
|
|
68
|
-
for (const [index, step] of steps.entries()) {
|
|
69
|
-
const label = step.name ?? step.id ?? `step ${index}`;
|
|
70
|
-
if (step.env !== undefined) {
|
|
71
|
-
expect(step.run !== undefined || step.uses !== undefined, `${file}: step "${label}" has env: but no run: or uses:`).toBe(true);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
it("no action.yml is truncated (last step has run: or uses: with non-empty value)", async () => {
|
|
77
|
-
for (const { file, manifest } of await loadManifests()) {
|
|
78
|
-
const steps = manifest.runs?.steps ?? [];
|
|
79
|
-
expect(steps.length, `${file}: no steps found — file may be truncated`).toBeGreaterThanOrEqual(1);
|
|
80
|
-
const lastStep = steps[steps.length - 1];
|
|
81
|
-
const hasTerminal = (lastStep.run !== undefined && lastStep.run.trim().length > 0) ||
|
|
82
|
-
(lastStep.uses !== undefined && lastStep.uses.trim().length > 0) ||
|
|
83
|
-
(lastStep.with !== undefined);
|
|
84
|
-
expect(hasTerminal, `${file}: last step appears truncated`).toBe(true);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,485 +0,0 @@
|
|
|
1
|
-
import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import { constants } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
5
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
6
|
-
import { catalogSourcePath, ensurePreCommitHook, installCatalog, installedRoutes, installMandatoryFiles, installTemplate, removeRouteFiles } from "./catalog-installation.js";
|
|
7
|
-
const temporaryDirectories = [];
|
|
8
|
-
afterEach(async () => {
|
|
9
|
-
await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true })));
|
|
10
|
-
});
|
|
11
|
-
describe("catalog installation", () => {
|
|
12
|
-
it("resolves loops beside built package files", async () => {
|
|
13
|
-
const packageDirectory = await createDirectory({
|
|
14
|
-
"dist/catalog-installation.js": "export {};\n",
|
|
15
|
-
"loops/workflows/agent-check.md": "# Check\n",
|
|
16
|
-
});
|
|
17
|
-
expect(catalogSourcePath(join(packageDirectory, "dist", "catalog-installation.js"))).toBe(join(packageDirectory, "loops"));
|
|
18
|
-
});
|
|
19
|
-
it("installCatalog with empty selectedRoutes installs mandatory and infrastructure files but no worker .md files", async () => {
|
|
20
|
-
const sourcePath = await createDirectory({
|
|
21
|
-
"actions/check/action.yml": "name: Check\n",
|
|
22
|
-
"workflows/agent-refine.md": "# Refine\n",
|
|
23
|
-
"workflows/agent-implement.md": "# Implement\n",
|
|
24
|
-
"workflows/agent-direct.md": "# Direct\n",
|
|
25
|
-
"workflows/agent-audit.md": "# Audit\n",
|
|
26
|
-
"workflows/agent-propose.md": "# Propose\n",
|
|
27
|
-
"workflows/agent-apply-review.md": "# Apply Review\n",
|
|
28
|
-
"workflows/agent-merge-gate.md": "# Merge Gate\n",
|
|
29
|
-
"workflows/shared/defaults.md": "defaults\n",
|
|
30
|
-
"workflows/work-router.yml": "name: Router\n",
|
|
31
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
32
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
33
|
-
});
|
|
34
|
-
const repositoryPath = await createDirectory({});
|
|
35
|
-
const result = await installCatalog(repositoryPath, { sourcePath, selectedRoutes: [] });
|
|
36
|
-
expect(result.installed).toContain("opencode.ci.json");
|
|
37
|
-
expect(result.installed).toContain("scripts/compile-agent-workflows.mjs");
|
|
38
|
-
expect(result.installed).toContain(".github/actions/check/action.yml");
|
|
39
|
-
expect(result.installed.some((f) => f.endsWith("shared/defaults.md"))).toBe(true);
|
|
40
|
-
expect(result.installed.some((f) => f.endsWith("work-router.yml"))).toBe(true);
|
|
41
|
-
// No worker .md files
|
|
42
|
-
expect(result.installed).not.toContain(".github/workflows/agent-refine.md");
|
|
43
|
-
expect(result.installed).not.toContain(".github/workflows/agent-implement.md");
|
|
44
|
-
expect(result.installed).not.toContain(".github/workflows/agent-audit.md");
|
|
45
|
-
});
|
|
46
|
-
it("installs package-owned loops files including mandatory opencode.ci.json and compile script", async () => {
|
|
47
|
-
const sourcePath = await createDirectory({
|
|
48
|
-
"actions/check/action.yml": "name: Check\n",
|
|
49
|
-
"workflows/agent-check.md": "# Check\n",
|
|
50
|
-
"workflows/shared/defaults.md": "defaults\n",
|
|
51
|
-
"scripts/compile-agent-workflows.mjs": "console.log('compile');\n",
|
|
52
|
-
"scripts/compile.mjs": "console.log('old compile');\n",
|
|
53
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
54
|
-
"workflows/agent-check.lock.yml": "generated\n",
|
|
55
|
-
"actions/actions-lock.json": "generated\n",
|
|
56
|
-
});
|
|
57
|
-
const repositoryPath = await createDirectory({});
|
|
58
|
-
await expect(installCatalog(repositoryPath, { sourcePath })).resolves.toEqual({
|
|
59
|
-
installed: [
|
|
60
|
-
".github/actions/check/action.yml",
|
|
61
|
-
".github/workflows/agent-check.md",
|
|
62
|
-
".github/workflows/shared/defaults.md",
|
|
63
|
-
"opencode.ci.json",
|
|
64
|
-
"scripts/compile-agent-workflows.mjs",
|
|
65
|
-
"scripts/compile.mjs",
|
|
66
|
-
],
|
|
67
|
-
conflicts: [],
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
it("copies ownership headers for base workflows and selected templates", async () => {
|
|
71
|
-
const header = "# Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-check.md. Update with `workflows update --force`; consumer edits may be overwritten.\n";
|
|
72
|
-
const templateHeader = "# Managed by @plainconceptsplatform/workflows. Source: loops/templates/agentics/agentics-checks.yml. Update with `workflows update --force`; consumer edits may be overwritten.\n";
|
|
73
|
-
const sourcePath = await createDirectory({
|
|
74
|
-
"actions/check/action.yml": "# Managed by @plainconceptsplatform/workflows. Source: loops/actions/check/action.yml. Update with `workflows update --force`; consumer edits may be overwritten.\nname: Check\n",
|
|
75
|
-
"workflows/agent-check.md": `---\n${header}# Check\n`,
|
|
76
|
-
"scripts/compile-agent-workflows.mjs": "// Managed by @plainconceptsplatform/workflows. Source: loops/scripts/compile-agent-workflows.mjs. Update with `workflows update --force`; consumer edits may be overwritten.\n",
|
|
77
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
78
|
-
"templates/agentics/agentics-checks.yml": `${templateHeader}name: Agentics checks\n`,
|
|
79
|
-
});
|
|
80
|
-
const repositoryPath = await createDirectory({});
|
|
81
|
-
await installCatalog(repositoryPath, { sourcePath });
|
|
82
|
-
await installTemplate(repositoryPath, "agentics-checks", { sourcePath });
|
|
83
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8")).resolves.toMatch(new RegExp(`^---\\n${escapeRegularExpression(header)}`));
|
|
84
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agentics-checks.yml"), "utf8")).resolves.toMatch(new RegExp(`^${escapeRegularExpression(templateHeader)}`));
|
|
85
|
-
});
|
|
86
|
-
it("does not report identical managed files as conflicts", async () => {
|
|
87
|
-
const sourcePath = await createDirectory({
|
|
88
|
-
"actions/check/action.yml": "name: Check\n",
|
|
89
|
-
"workflows/agent-check.md": "# Check\n",
|
|
90
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
91
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
92
|
-
});
|
|
93
|
-
const repositoryPath = await createDirectory({ ".github/workflows/agent-check.md": "# Check\n" });
|
|
94
|
-
await expect(installCatalog(repositoryPath, { sourcePath })).resolves.toMatchObject({ conflicts: [] });
|
|
95
|
-
});
|
|
96
|
-
it("does not manage legacy repository configuration files", async () => {
|
|
97
|
-
const sourcePath = await createDirectory({
|
|
98
|
-
"actions/check/action.yml": "name: Check\n",
|
|
99
|
-
"workflows/agent-check.md": "# Check\n",
|
|
100
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
101
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
102
|
-
});
|
|
103
|
-
const repositoryPath = await createDirectory({
|
|
104
|
-
".github/workflows/shared/repo-config.md": "legacy consumer config\n",
|
|
105
|
-
});
|
|
106
|
-
await expect(installCatalog(repositoryPath, { sourcePath })).resolves.toMatchObject({
|
|
107
|
-
installed: [
|
|
108
|
-
".github/actions/check/action.yml",
|
|
109
|
-
".github/workflows/agent-check.md",
|
|
110
|
-
"opencode.ci.json",
|
|
111
|
-
"scripts/compile-agent-workflows.mjs",
|
|
112
|
-
],
|
|
113
|
-
conflicts: [],
|
|
114
|
-
});
|
|
115
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/shared/repo-config.md"), "utf8")).resolves.toBe("legacy consumer config\n");
|
|
116
|
-
});
|
|
117
|
-
it("requires force for different managed files", async () => {
|
|
118
|
-
const sourcePath = await createDirectory({
|
|
119
|
-
"actions/check/action.yml": "package action\n",
|
|
120
|
-
"workflows/agent-check.md": "package workflow\n",
|
|
121
|
-
"scripts/compile-agent-workflows.mjs": "package script\n",
|
|
122
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
123
|
-
});
|
|
124
|
-
const repositoryPath = await createDirectory({
|
|
125
|
-
".github/actions/check/action.yml": "consumer action\n",
|
|
126
|
-
".github/workflows/agent-check.md": "consumer workflow\n",
|
|
127
|
-
"scripts/compile-agent-workflows.mjs": "consumer script\n",
|
|
128
|
-
"opencode.ci.json": "{ \"model\": \"consumer-model\" }\n",
|
|
129
|
-
});
|
|
130
|
-
await expect(installCatalog(repositoryPath, { sourcePath })).resolves.toEqual({
|
|
131
|
-
installed: [],
|
|
132
|
-
conflicts: [
|
|
133
|
-
".github/actions/check/action.yml",
|
|
134
|
-
".github/workflows/agent-check.md",
|
|
135
|
-
"opencode.ci.json",
|
|
136
|
-
"scripts/compile-agent-workflows.mjs",
|
|
137
|
-
],
|
|
138
|
-
});
|
|
139
|
-
await expect(installCatalog(repositoryPath, { force: true, sourcePath })).resolves.toMatchObject({
|
|
140
|
-
conflicts: [
|
|
141
|
-
".github/actions/check/action.yml",
|
|
142
|
-
".github/workflows/agent-check.md",
|
|
143
|
-
"opencode.ci.json",
|
|
144
|
-
"scripts/compile-agent-workflows.mjs",
|
|
145
|
-
],
|
|
146
|
-
});
|
|
147
|
-
await expect(readFile(join(repositoryPath, ".github/actions/check/action.yml"), "utf8")).resolves.toBe("package action\n");
|
|
148
|
-
});
|
|
149
|
-
it("installs templates only when explicitly selected", async () => {
|
|
150
|
-
const sourcePath = await createDirectory({
|
|
151
|
-
"actions/check/action.yml": "name: Check\n",
|
|
152
|
-
"workflows/agent-check.md": "# Check\n",
|
|
153
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
154
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
155
|
-
"templates/agentics/agentics-checks.yml": "name: Agentics checks\n",
|
|
156
|
-
"templates/ci/app-ci-node-monorepo.yml": "name: Node CI\n",
|
|
157
|
-
"templates/release/github-release.yml": "name: Publish GitHub release\n",
|
|
158
|
-
});
|
|
159
|
-
const repositoryPath = await createDirectory({});
|
|
160
|
-
await installCatalog(repositoryPath, { sourcePath });
|
|
161
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agentics-checks.yml"), "utf8")).rejects.toThrow();
|
|
162
|
-
await expect(installTemplate(repositoryPath, "agentics-checks", { sourcePath })).resolves.toEqual({
|
|
163
|
-
installed: [".github/workflows/agentics-checks.yml"],
|
|
164
|
-
conflicts: [],
|
|
165
|
-
});
|
|
166
|
-
await expect(installTemplate(repositoryPath, "app-ci-node-monorepo", { sourcePath })).resolves.toEqual({
|
|
167
|
-
installed: [".github/workflows/app-ci-node-monorepo.yml"],
|
|
168
|
-
conflicts: [],
|
|
169
|
-
});
|
|
170
|
-
await expect(installTemplate(repositoryPath, "github-release", { sourcePath })).resolves.toEqual({
|
|
171
|
-
installed: [".github/workflows/github-release.yml"],
|
|
172
|
-
conflicts: [],
|
|
173
|
-
});
|
|
174
|
-
});
|
|
175
|
-
it("installCatalog installs mandatory opencode.ci.json and compile script alongside catalog files", async () => {
|
|
176
|
-
const sourcePath = await createDirectory({
|
|
177
|
-
"actions/check/action.yml": "name: Check\n",
|
|
178
|
-
"workflows/agent-check.md": "# Check\n",
|
|
179
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
180
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
181
|
-
});
|
|
182
|
-
const repositoryPath = await createDirectory({});
|
|
183
|
-
const result = await installCatalog(repositoryPath, { sourcePath });
|
|
184
|
-
expect(result.installed).toContain("opencode.ci.json");
|
|
185
|
-
expect(result.installed).toContain("scripts/compile-agent-workflows.mjs");
|
|
186
|
-
await expect(readFile(join(repositoryPath, "opencode.ci.json"), "utf8")).resolves.toBe("{ \"model\": \"plainconcepts/glm-5-3\" }\n");
|
|
187
|
-
await expect(readFile(join(repositoryPath, "scripts/compile-agent-workflows.mjs"), "utf8")).resolves.toBe("compile\n");
|
|
188
|
-
});
|
|
189
|
-
it("installMandatoryFiles installs opencode.ci.json and compile script without catalog files", async () => {
|
|
190
|
-
const sourcePath = await createDirectory({
|
|
191
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
192
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
193
|
-
});
|
|
194
|
-
const repositoryPath = await createDirectory({});
|
|
195
|
-
const result = await installMandatoryFiles(repositoryPath, { sourcePath });
|
|
196
|
-
expect(result.installed).toEqual(["opencode.ci.json", "scripts/compile-agent-workflows.mjs"]);
|
|
197
|
-
await expect(readFile(join(repositoryPath, "opencode.ci.json"), "utf8")).resolves.toBe("{ \"model\": \"plainconcepts/glm-5-3\" }\n");
|
|
198
|
-
await expect(readFile(join(repositoryPath, "scripts/compile-agent-workflows.mjs"), "utf8")).resolves.toBe("compile\n");
|
|
199
|
-
});
|
|
200
|
-
it("installMandatoryFiles reports conflicts on differing files", async () => {
|
|
201
|
-
const sourcePath = await createDirectory({
|
|
202
|
-
"scripts/compile-agent-workflows.mjs": "package script\n",
|
|
203
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
204
|
-
});
|
|
205
|
-
const repositoryPath = await createDirectory({
|
|
206
|
-
"opencode.ci.json": "{ \"model\": \"consumer\" }\n",
|
|
207
|
-
"scripts/compile-agent-workflows.mjs": "consumer script\n",
|
|
208
|
-
});
|
|
209
|
-
await expect(installMandatoryFiles(repositoryPath, { sourcePath })).resolves.toEqual({
|
|
210
|
-
installed: [],
|
|
211
|
-
conflicts: ["opencode.ci.json", "scripts/compile-agent-workflows.mjs"],
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
it("installMandatoryFiles overwrites with force", async () => {
|
|
215
|
-
const sourcePath = await createDirectory({
|
|
216
|
-
"scripts/compile-agent-workflows.mjs": "package script\n",
|
|
217
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
218
|
-
});
|
|
219
|
-
const repositoryPath = await createDirectory({
|
|
220
|
-
"opencode.ci.json": "{ \"model\": \"consumer\" }\n",
|
|
221
|
-
"scripts/compile-agent-workflows.mjs": "consumer script\n",
|
|
222
|
-
});
|
|
223
|
-
await expect(installMandatoryFiles(repositoryPath, { force: true, sourcePath })).resolves.toMatchObject({
|
|
224
|
-
installed: ["opencode.ci.json", "scripts/compile-agent-workflows.mjs"],
|
|
225
|
-
});
|
|
226
|
-
await expect(readFile(join(repositoryPath, "opencode.ci.json"), "utf8")).resolves.toBe("{ \"model\": \"plainconcepts/glm-5-3\" }\n");
|
|
227
|
-
});
|
|
228
|
-
it("installMandatoryFiles does not install catalog workflow files", async () => {
|
|
229
|
-
const sourcePath = await createDirectory({
|
|
230
|
-
"actions/check/action.yml": "name: Check\n",
|
|
231
|
-
"workflows/agent-check.md": "# Check\n",
|
|
232
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
233
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
234
|
-
});
|
|
235
|
-
const repositoryPath = await createDirectory({});
|
|
236
|
-
await installMandatoryFiles(repositoryPath, { sourcePath });
|
|
237
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8")).rejects.toThrow();
|
|
238
|
-
});
|
|
239
|
-
it("installCatalog deduplicates compile script in both scripts/ and mandatory files", async () => {
|
|
240
|
-
const sourcePath = await createDirectory({
|
|
241
|
-
"actions/check/action.yml": "name: Check\n",
|
|
242
|
-
"workflows/agent-check.md": "# Check\n",
|
|
243
|
-
"scripts/compile-agent-workflows.mjs": "same compile script\n",
|
|
244
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
245
|
-
});
|
|
246
|
-
const repositoryPath = await createDirectory({});
|
|
247
|
-
const result = await installCatalog(repositoryPath, { sourcePath });
|
|
248
|
-
const compileEntries = result.installed.filter((path) => path === "scripts/compile-agent-workflows.mjs");
|
|
249
|
-
expect(compileEntries).toHaveLength(1);
|
|
250
|
-
});
|
|
251
|
-
it("creates a Husky pre-commit hook when the consumer has none", async () => {
|
|
252
|
-
const repositoryPath = await createDirectory({});
|
|
253
|
-
await ensurePreCommitHook(repositoryPath);
|
|
254
|
-
await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8"))
|
|
255
|
-
.resolves.toBe("if git diff --cached --name-only -- .github | grep -q .; then\n node scripts/compile-agent-workflows.mjs\n git add -- .github/workflows/*.lock.yml\n [ ! -f .github/actions/actions-lock.json ] || git add -- .github/actions/actions-lock.json\nfi\n");
|
|
256
|
-
});
|
|
257
|
-
it("keeps existing pre-commit commands and appends the compiler once", async () => {
|
|
258
|
-
const repositoryPath = await createDirectory({ ".husky/pre-commit": "pnpm lint\n" });
|
|
259
|
-
await ensurePreCommitHook(repositoryPath);
|
|
260
|
-
await ensurePreCommitHook(repositoryPath);
|
|
261
|
-
await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8"))
|
|
262
|
-
.resolves.toBe("pnpm lint\nif git diff --cached --name-only -- .github | grep -q .; then\n node scripts/compile-agent-workflows.mjs\n git add -- .github/workflows/*.lock.yml\n [ ! -f .github/actions/actions-lock.json ] || git add -- .github/actions/actions-lock.json\nfi\n");
|
|
263
|
-
});
|
|
264
|
-
it("upgrades an existing compiler hook to stage generated locks", async () => {
|
|
265
|
-
const repositoryPath = await createDirectory({ ".husky/pre-commit": "node scripts/compile-agent-workflows.mjs\n" });
|
|
266
|
-
await ensurePreCommitHook(repositoryPath);
|
|
267
|
-
await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8"))
|
|
268
|
-
.resolves.toContain("if git diff --cached --name-only -- .github | grep -q .; then");
|
|
269
|
-
});
|
|
270
|
-
it("repairs a malformed compiler hook prefixed with pnpm exec", async () => {
|
|
271
|
-
const repositoryPath = await createDirectory({
|
|
272
|
-
".husky/pre-commit": "pnpm exec if git diff --cached --name-only -- .github | grep -q .; then\n node scripts/compile-agent-workflows.mjs\n git add -- .github/workflows/*.lock.yml\n [ ! -f .github/actions/actions-lock.json ] || git add -- .github/actions/actions-lock.json\nfi\n",
|
|
273
|
-
});
|
|
274
|
-
await ensurePreCommitHook(repositoryPath);
|
|
275
|
-
await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8"))
|
|
276
|
-
.resolves.not.toContain("pnpm exec if");
|
|
277
|
-
});
|
|
278
|
-
it("leaves consumer files untouched when staged workflow compilation fails", async () => {
|
|
279
|
-
const sourcePath = await createDirectory({
|
|
280
|
-
"actions/check/action.yml": "package action\n",
|
|
281
|
-
"workflows/agent-check.md": "package workflow\n",
|
|
282
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
283
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"package\" }\n",
|
|
284
|
-
});
|
|
285
|
-
const repositoryPath = await createDirectory({
|
|
286
|
-
".github/actions/check/action.yml": "consumer action\n",
|
|
287
|
-
".github/workflows/agent-check.md": "consumer workflow\n",
|
|
288
|
-
"opencode.ci.json": "{ \"model\": \"consumer\" }\n",
|
|
289
|
-
"scripts/compile-agent-workflows.mjs": "consumer compiler\n",
|
|
290
|
-
});
|
|
291
|
-
await expect(installCatalog(repositoryPath, {
|
|
292
|
-
force: true,
|
|
293
|
-
sourcePath,
|
|
294
|
-
compile: async () => { throw new Error("compile failed"); },
|
|
295
|
-
})).rejects.toThrow("compile failed");
|
|
296
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8"))
|
|
297
|
-
.resolves.toBe("consumer workflow\n");
|
|
298
|
-
await expect(readFile(join(repositoryPath, "scripts/compile-agent-workflows.mjs"), "utf8"))
|
|
299
|
-
.resolves.toBe("consumer compiler\n");
|
|
300
|
-
await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8")).rejects.toThrow();
|
|
301
|
-
});
|
|
302
|
-
it("preserves consumer-specific worker environment values during a forced update", async () => {
|
|
303
|
-
const sourcePath = await createDirectory({
|
|
304
|
-
"actions/check/action.yml": "name: Check\n",
|
|
305
|
-
"workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"package verify\"\n REPO_RULES: \"package rules\"\n---\nengine:\n env:\n OPENAI_BASE_URL: https://forge.plainconcepts.com/v1\n",
|
|
306
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
307
|
-
"templates/opencode/opencode.ci.json": "{}\n",
|
|
308
|
-
});
|
|
309
|
-
const repositoryPath = await createDirectory({
|
|
310
|
-
".github/workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"consumer verify\"\n REPO_RULES: \"consumer rules\"\n---\nengine:\n env:\n OPENAI_BASE_URL: https://consumer.example/v1\n",
|
|
311
|
-
});
|
|
312
|
-
await installCatalog(repositoryPath, { force: true, sourcePath });
|
|
313
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8")).resolves.toContain(" REPO_RULES: \"consumer rules\"\n---\nengine:\n env:\n OPENAI_BASE_URL: https://consumer.example/v1\n");
|
|
314
|
-
});
|
|
315
|
-
// gh-aw cannot share runs-on through an import, so each worker names its own pool and a
|
|
316
|
-
// forced update must leave it alone. Rewriting it moves a repository's agents onto another
|
|
317
|
-
// pool, and one of ours has two machines in it.
|
|
318
|
-
it("keeps the consumer's runner pool through a forced update", async () => {
|
|
319
|
-
const sourcePath = await createDirectory({
|
|
320
|
-
"actions/check/action.yml": "name: Check\n",
|
|
321
|
-
"workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"package verify\"\n---\njobs:\n agent:\n runs-on: agents-arc\n deterministic:\n runs-on: ubuntu-latest\nruns-on: agents-arc\nruns-on-slim: agents-arc\n",
|
|
322
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
323
|
-
"templates/opencode/opencode.ci.json": "{}\n",
|
|
324
|
-
});
|
|
325
|
-
const repositoryPath = await createDirectory({
|
|
326
|
-
".github/workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"consumer verify\"\n---\njobs:\n agent:\n runs-on: OwnPool\n deterministic:\n runs-on: ubuntu-latest\nruns-on: OwnPool\nruns-on-slim: OwnPool\n",
|
|
327
|
-
});
|
|
328
|
-
await installCatalog(repositoryPath, { force: true, sourcePath });
|
|
329
|
-
const written = await readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8");
|
|
330
|
-
expect(written).toContain("runs-on: OwnPool");
|
|
331
|
-
expect(written).not.toContain("agents-arc");
|
|
332
|
-
// GitHub's own runner is not a per-repository choice and stays as the package has it.
|
|
333
|
-
expect(written).toContain("runs-on: ubuntu-latest");
|
|
334
|
-
// And the env value is still preserved, which this shares a code path with.
|
|
335
|
-
expect(written).toContain('VERIFY_COMMANDS: "consumer verify"');
|
|
336
|
-
});
|
|
337
|
-
// A consumer naming two pools has drifted rather than decided. Guessing which it meant
|
|
338
|
-
// would be worse than leaving the package's, so it is left.
|
|
339
|
-
it("leaves the package pool when the consumer names more than one", async () => {
|
|
340
|
-
const sourcePath = await createDirectory({
|
|
341
|
-
"actions/check/action.yml": "name: Check\n",
|
|
342
|
-
"workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"package verify\"\n---\njobs:\n agent:\n runs-on: agents-arc\n deterministic:\n runs-on: ubuntu-latest\nruns-on: agents-arc\nruns-on-slim: agents-arc\n",
|
|
343
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
344
|
-
"templates/opencode/opencode.ci.json": "{}\n",
|
|
345
|
-
});
|
|
346
|
-
const repositoryPath = await createDirectory({
|
|
347
|
-
".github/workflows/agent-check.md": "---\nenv:\n VERIFY_COMMANDS: \"consumer verify\"\n---\njobs:\n agent:\n runs-on: OwnPool\n other:\n runs-on: SomethingElse\n",
|
|
348
|
-
});
|
|
349
|
-
await installCatalog(repositoryPath, { force: true, sourcePath });
|
|
350
|
-
const written = await readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8");
|
|
351
|
-
expect(written).toContain("runs-on: agents-arc");
|
|
352
|
-
});
|
|
353
|
-
it("applies staged generated locks with managed sources", async () => {
|
|
354
|
-
const sourcePath = await createDirectory({
|
|
355
|
-
"actions/check/action.yml": "package action\n",
|
|
356
|
-
"workflows/agent-check.md": "package workflow\n",
|
|
357
|
-
"scripts/compile-agent-workflows.mjs": "compile\n",
|
|
358
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"package\" }\n",
|
|
359
|
-
});
|
|
360
|
-
const repositoryPath = await createDirectory({});
|
|
361
|
-
await installCatalog(repositoryPath, {
|
|
362
|
-
sourcePath,
|
|
363
|
-
compile: async (stagingPath) => {
|
|
364
|
-
await expect(access(join(stagingPath, ".git"), constants.F_OK)).resolves.toBeUndefined();
|
|
365
|
-
await writeFile(join(stagingPath, ".github", "workflows", "agent-check.lock.yml"), "opencode run --log-level ERROR\n", "utf8");
|
|
366
|
-
},
|
|
367
|
-
});
|
|
368
|
-
await expect(readFile(join(repositoryPath, ".github", "workflows", "agent-check.lock.yml"), "utf8"))
|
|
369
|
-
.resolves.toBe("opencode run --log-level ERROR\n");
|
|
370
|
-
});
|
|
371
|
-
it("installs the opencode.ci.json template to the repository root", async () => {
|
|
372
|
-
const sourcePath = await createDirectory({
|
|
373
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
374
|
-
});
|
|
375
|
-
const repositoryPath = await createDirectory({});
|
|
376
|
-
await expect(installTemplate(repositoryPath, "opencode.ci.json", { sourcePath })).resolves.toEqual({
|
|
377
|
-
installed: ["opencode.ci.json"],
|
|
378
|
-
conflicts: [],
|
|
379
|
-
});
|
|
380
|
-
await expect(readFile(join(repositoryPath, "opencode.ci.json"), "utf8")).resolves.toBe("{ \"model\": \"plainconcepts/glm-5-3\" }\n");
|
|
381
|
-
});
|
|
382
|
-
it("installs the .NET and Next.js CI template as app-ci.yml", async () => {
|
|
383
|
-
const sourcePath = await createDirectory({
|
|
384
|
-
"templates/ci/app-ci-dotnet-next.yml": "name: App: CI\n",
|
|
385
|
-
});
|
|
386
|
-
const repositoryPath = await createDirectory({});
|
|
387
|
-
await expect(installTemplate(repositoryPath, "app-ci-dotnet-next", { sourcePath })).resolves.toEqual({
|
|
388
|
-
installed: [".github/workflows/app-ci.yml"],
|
|
389
|
-
conflicts: [],
|
|
390
|
-
});
|
|
391
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/app-ci.yml"), "utf8")).resolves.toBe("name: App: CI\n");
|
|
392
|
-
});
|
|
393
|
-
it("requires force to replace the opencode.ci.json template", async () => {
|
|
394
|
-
const sourcePath = await createDirectory({
|
|
395
|
-
"templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-3\" }\n",
|
|
396
|
-
});
|
|
397
|
-
const repositoryPath = await createDirectory({
|
|
398
|
-
"opencode.ci.json": "{ \"model\": \"consumer-model\" }\n",
|
|
399
|
-
});
|
|
400
|
-
await expect(installTemplate(repositoryPath, "opencode.ci.json", { sourcePath })).resolves.toEqual({
|
|
401
|
-
installed: [],
|
|
402
|
-
conflicts: ["opencode.ci.json"],
|
|
403
|
-
});
|
|
404
|
-
await expect(installTemplate(repositoryPath, "opencode.ci.json", { force: true, sourcePath })).resolves.toMatchObject({
|
|
405
|
-
installed: ["opencode.ci.json"],
|
|
406
|
-
});
|
|
407
|
-
await expect(readFile(join(repositoryPath, "opencode.ci.json"), "utf8")).resolves.toBe("{ \"model\": \"plainconcepts/glm-5-3\" }\n");
|
|
408
|
-
});
|
|
409
|
-
it("requires force to replace a selected template", async () => {
|
|
410
|
-
const sourcePath = await createDirectory({
|
|
411
|
-
"templates/agentics/agentics-checks.yml": "package template\n",
|
|
412
|
-
});
|
|
413
|
-
const repositoryPath = await createDirectory({
|
|
414
|
-
".github/workflows/agentics-checks.yml": "consumer template\n",
|
|
415
|
-
});
|
|
416
|
-
await expect(installTemplate(repositoryPath, "agentics-checks", { sourcePath })).resolves.toEqual({
|
|
417
|
-
installed: [],
|
|
418
|
-
conflicts: [".github/workflows/agentics-checks.yml"],
|
|
419
|
-
});
|
|
420
|
-
await expect(installTemplate(repositoryPath, "agentics-checks", { force: true, sourcePath })).resolves.toMatchObject({
|
|
421
|
-
installed: [".github/workflows/agentics-checks.yml"],
|
|
422
|
-
});
|
|
423
|
-
});
|
|
424
|
-
it("installs issue templates to .github/ISSUE_TEMPLATE/", async () => {
|
|
425
|
-
const sourcePath = await createDirectory({
|
|
426
|
-
"templates/issues/bug_report.yml": "name: Bug report\n",
|
|
427
|
-
"templates/issues/feature_request.yml": "name: Feature request\n",
|
|
428
|
-
});
|
|
429
|
-
const repositoryPath = await createDirectory({});
|
|
430
|
-
await expect(installTemplate(repositoryPath, "bug-report", { sourcePath })).resolves.toEqual({
|
|
431
|
-
installed: [".github/ISSUE_TEMPLATE/bug_report.yml"],
|
|
432
|
-
conflicts: [],
|
|
433
|
-
});
|
|
434
|
-
await expect(installTemplate(repositoryPath, "feature-request", { sourcePath })).resolves.toEqual({
|
|
435
|
-
installed: [".github/ISSUE_TEMPLATE/feature_request.yml"],
|
|
436
|
-
conflicts: [],
|
|
437
|
-
});
|
|
438
|
-
await expect(readFile(join(repositoryPath, ".github/ISSUE_TEMPLATE/bug_report.yml"), "utf8")).resolves.toBe("name: Bug report\n");
|
|
439
|
-
await expect(readFile(join(repositoryPath, ".github/ISSUE_TEMPLATE/feature_request.yml"), "utf8")).resolves.toBe("name: Feature request\n");
|
|
440
|
-
});
|
|
441
|
-
});
|
|
442
|
-
describe("route lifecycle", () => {
|
|
443
|
-
it("detects installed route workers in workflowRoutes order", async () => {
|
|
444
|
-
const repositoryPath = await createDirectory({
|
|
445
|
-
".github/workflows/agent-implement.md": "# Implement\n",
|
|
446
|
-
".github/workflows/agent-refine.md": "# Refine\n",
|
|
447
|
-
});
|
|
448
|
-
await expect(installedRoutes(repositoryPath)).resolves.toEqual(["refine", "implement"]);
|
|
449
|
-
});
|
|
450
|
-
it("returns an empty list when no route workers are installed", async () => {
|
|
451
|
-
const repositoryPath = await createDirectory({});
|
|
452
|
-
await expect(installedRoutes(repositoryPath)).resolves.toEqual([]);
|
|
453
|
-
});
|
|
454
|
-
it("removes a route worker and its generated lock, leaving other workers", async () => {
|
|
455
|
-
const repositoryPath = await createDirectory({
|
|
456
|
-
".github/workflows/agent-refine.md": "# Refine\n",
|
|
457
|
-
".github/workflows/agent-refine.lock.yml": "generated\n",
|
|
458
|
-
".github/workflows/agent-implement.md": "# Implement\n",
|
|
459
|
-
});
|
|
460
|
-
await expect(removeRouteFiles(repositoryPath, ["refine"])).resolves.toEqual([
|
|
461
|
-
".github/workflows/agent-refine.lock.yml",
|
|
462
|
-
".github/workflows/agent-refine.md",
|
|
463
|
-
]);
|
|
464
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agent-refine.md"), "utf8")).rejects.toThrow();
|
|
465
|
-
await expect(readFile(join(repositoryPath, ".github/workflows/agent-implement.md"), "utf8")).resolves.toBe("# Implement\n");
|
|
466
|
-
});
|
|
467
|
-
it("ignores routes that are not installed", async () => {
|
|
468
|
-
const repositoryPath = await createDirectory({});
|
|
469
|
-
await expect(removeRouteFiles(repositoryPath, ["audit"])).resolves.toEqual([]);
|
|
470
|
-
});
|
|
471
|
-
});
|
|
472
|
-
async function createDirectory(files) {
|
|
473
|
-
const directory = await mkdtemp(join(tmpdir(), "workflows-"));
|
|
474
|
-
temporaryDirectories.push(directory);
|
|
475
|
-
await Promise.all(Object.entries(files).map(async ([relativePath, content]) => {
|
|
476
|
-
const path = join(directory, relativePath);
|
|
477
|
-
const { mkdir } = await import("node:fs/promises");
|
|
478
|
-
await mkdir(dirname(path), { recursive: true });
|
|
479
|
-
await writeFile(path, content, "utf8");
|
|
480
|
-
}));
|
|
481
|
-
return directory;
|
|
482
|
-
}
|
|
483
|
-
function escapeRegularExpression(value) {
|
|
484
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
485
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|