@plainconceptsplatform/workflows 0.1.2 → 0.1.5
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 +28 -10
- package/dist/catalog-installation.d.ts +3 -2
- package/dist/catalog-installation.js +26 -6
- package/dist/catalog-installation.test.js +104 -10
- package/dist/catalog-listing.d.ts +13 -0
- package/dist/catalog-listing.js +68 -0
- package/dist/catalog-listing.test.d.ts +1 -0
- package/dist/catalog-listing.test.js +137 -0
- package/dist/index.js +62 -11
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +112 -0
- package/dist/repository-inspection.test.d.ts +1 -0
- package/dist/repository-inspection.test.js +77 -0
- package/dist/workflow-catalog.d.ts +9 -7
- package/dist/workflow-catalog.js +21 -16
- package/dist/workflow-catalog.test.js +18 -3
- package/loops/actions/add-issue-labels/action.yml +30 -29
- package/loops/actions/agent-output.cjs +1 -0
- package/loops/actions/apply-agent-bundle/action.yml +24 -23
- package/loops/actions/apply-agent-bundle/apply-bundle.sh +1 -0
- package/loops/actions/apply-agent-comments/action.yml +42 -41
- package/loops/actions/apply-agent-labels/action.yml +55 -54
- package/loops/actions/apply-agent-output/action.yml +108 -107
- package/loops/actions/audit-close/action.yml +104 -103
- package/loops/actions/classify-route/action.yml +91 -90
- package/loops/actions/classify-route/classify-route.sh +1 -0
- package/loops/actions/cleanup-artifacts/action.yml +65 -64
- package/loops/actions/close-agent-issues/action.yml +43 -42
- package/loops/actions/create-agent-issues/action.yml +52 -51
- package/loops/actions/create-issue-comment/action.yml +29 -28
- package/loops/actions/download-agent-output/action.yml +53 -52
- package/loops/actions/identify-gate-subject/action.yml +97 -96
- package/loops/actions/link-pr-to-issue/action.yml +40 -39
- package/loops/actions/list-open-issues/action.yml +33 -32
- package/loops/actions/load-issue-context/action.yml +43 -42
- package/loops/actions/merge-agent-pr/action.yml +36 -35
- package/loops/actions/push-agent-branch/action.yml +45 -44
- package/loops/actions/remove-issue-labels/action.yml +35 -34
- package/loops/actions/stale-recovery/action.yml +288 -287
- package/loops/actions/update-agent-issues/action.yml +58 -57
- package/loops/actions/validate-refine-output/action.yml +44 -43
- package/loops/actions/validate-refine-output/validate-refine-output.sh +1 -0
- package/loops/actions/verify-composite-actions/action.yml +9 -8
- package/loops/actions/verify-composite-actions/verify-composite-actions.sh +1 -0
- package/loops/actions/verify-refine-output/action.yml +9 -8
- package/loops/actions/verify-refine-output/verify-refine-output.sh +1 -0
- package/loops/actions/verify-route-matrix/action.yml +9 -8
- package/loops/actions/verify-route-matrix/verify-route-matrix.sh +1 -0
- package/loops/scripts/compile-agent-workflows.mjs +18 -2
- package/loops/templates/agentics/agentics-checks.yml +86 -0
- package/loops/templates/agentics/agentics-maintenance.yml +121 -0
- package/loops/templates/ci/app-ci-dotnet-next.yml +167 -0
- package/loops/templates/ci/app-ci-node-monorepo.yml +178 -0
- package/loops/templates/opencode/opencode.ci.json +46 -0
- package/loops/templates/opencode/opencode.ci.json.md +41 -0
- package/loops/workflows/agent-apply-review.md +9 -10
- package/loops/workflows/agent-audit.md +6 -7
- package/loops/workflows/agent-direct.md +7 -8
- package/loops/workflows/agent-implement.md +7 -8
- package/loops/workflows/agent-merge-gate.md +9 -10
- package/loops/workflows/agent-propose.md +7 -8
- package/loops/workflows/agent-refine.md +5 -7
- package/loops/workflows/shared/opencode-ci.md +1 -0
- package/loops/workflows/shared/platform-defaults.md +1 -0
- package/loops/workflows/work-router.yml +1 -0
- package/package.json +2 -2
- package/loops/aw/repo-config.md +0 -12
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { run } from "./index.js";
|
|
6
|
+
const temporaryDirectories = [];
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await Promise.all(temporaryDirectories.splice(0).map((directory) => rm(directory, { force: true, recursive: true })));
|
|
9
|
+
});
|
|
10
|
+
describe("workflows CLI", () => {
|
|
11
|
+
it("prints template names in help", async () => {
|
|
12
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
13
|
+
await expect(run(["--help"])).resolves.toBe(0);
|
|
14
|
+
expect(log).toHaveBeenCalledWith(expect.stringContaining("agentics-checks"));
|
|
15
|
+
expect(log).toHaveBeenCalledWith(expect.stringContaining("app-ci-dotnet-next"));
|
|
16
|
+
expect(log).toHaveBeenCalledWith(expect.stringContaining("opencode.ci.json"));
|
|
17
|
+
log.mockRestore();
|
|
18
|
+
});
|
|
19
|
+
it("rejects an unsupported template", async () => {
|
|
20
|
+
const error = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
21
|
+
await expect(run(["add", "--template", "unknown"])).resolves.toBe(1);
|
|
22
|
+
expect(error).toHaveBeenCalledWith("add accepts only --force or --template agentics-checks|agentics-maintenance|app-ci-dotnet-next|app-ci-node-monorepo|opencode.ci.json.");
|
|
23
|
+
error.mockRestore();
|
|
24
|
+
});
|
|
25
|
+
it("lists all workflows and templates with install status [ ] when none installed", async () => {
|
|
26
|
+
const repositoryPath = await createRepository({});
|
|
27
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
28
|
+
await expect(run(["list"], repositoryPath)).resolves.toBe(0);
|
|
29
|
+
expect(log).toHaveBeenCalledTimes(1);
|
|
30
|
+
const output = log.mock.calls[0][0];
|
|
31
|
+
expect(output).toContain("Workflows:");
|
|
32
|
+
expect(output).toContain("Templates:");
|
|
33
|
+
expect(output).toContain("refine");
|
|
34
|
+
expect(output).toContain("implement");
|
|
35
|
+
expect(output).toContain("agentics-checks");
|
|
36
|
+
expect(output).toContain("app-ci-dotnet-next");
|
|
37
|
+
// None installed: all [ ]
|
|
38
|
+
const installedCount = (output.match(/\[x\]/g) ?? []).length;
|
|
39
|
+
expect(installedCount).toBe(0);
|
|
40
|
+
// 7 routes + 5 templates = 12 entries
|
|
41
|
+
const uninstalledCount = (output.match(/\[ \]/g) ?? []).length;
|
|
42
|
+
expect(uninstalledCount).toBe(12);
|
|
43
|
+
log.mockRestore();
|
|
44
|
+
});
|
|
45
|
+
it("marks installed workflows with [x]", async () => {
|
|
46
|
+
const repositoryPath = await createRepository({
|
|
47
|
+
".github/workflows/agent-refine.md": "# Refine",
|
|
48
|
+
".github/workflows/agentics-checks.yml": "name: Agentics checks",
|
|
49
|
+
});
|
|
50
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
51
|
+
await expect(run(["list"], repositoryPath)).resolves.toBe(0);
|
|
52
|
+
const output = log.mock.calls[0][0];
|
|
53
|
+
const installed = output.match(/(\[x\])/g) ?? [];
|
|
54
|
+
expect(installed.length).toBe(2);
|
|
55
|
+
// refine and agentics-checks should be [x]
|
|
56
|
+
const refineLines = output.split("\n").filter((line) => line.includes("refine") || line.includes("agentics-checks"));
|
|
57
|
+
expect(refineLines.filter((line) => line.includes("[x]"))).toHaveLength(2);
|
|
58
|
+
log.mockRestore();
|
|
59
|
+
});
|
|
60
|
+
it("search filters by name", async () => {
|
|
61
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
62
|
+
await expect(run(["search", "audit"])).resolves.toBe(0);
|
|
63
|
+
const output = log.mock.calls[0][0];
|
|
64
|
+
expect(output).toContain("audit");
|
|
65
|
+
expect(output).not.toContain("Templates:");
|
|
66
|
+
// Only the audit route should be returned — no other route name should appear.
|
|
67
|
+
const visibleRoutes = ["refine", "implement", "direct", "apply-review", "merge-gate", "propose"];
|
|
68
|
+
for (const route of visibleRoutes) {
|
|
69
|
+
expect(output).not.toContain(`${route} —`);
|
|
70
|
+
}
|
|
71
|
+
log.mockRestore();
|
|
72
|
+
});
|
|
73
|
+
it("search filters by description keyword", async () => {
|
|
74
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
75
|
+
await expect(run(["search", "CI"])).resolves.toBe(0);
|
|
76
|
+
const output = log.mock.calls[0][0];
|
|
77
|
+
expect(output).toContain("dotnet-next");
|
|
78
|
+
expect(output).toContain("node-monorepo");
|
|
79
|
+
expect(output).not.toContain("refine");
|
|
80
|
+
log.mockRestore();
|
|
81
|
+
});
|
|
82
|
+
it("search with no matches prints a no-match message", async () => {
|
|
83
|
+
const log = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
84
|
+
await expect(run(["search", "nonexistent"])).resolves.toBe(0);
|
|
85
|
+
const output = log.mock.calls[0][0];
|
|
86
|
+
expect(output).toBe("No workflows matched the search query.");
|
|
87
|
+
log.mockRestore();
|
|
88
|
+
});
|
|
89
|
+
it("search with no query fails", async () => {
|
|
90
|
+
const error = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
91
|
+
await expect(run(["search"])).resolves.toBe(1);
|
|
92
|
+
expect(error).toHaveBeenCalledWith("search requires exactly one query argument.");
|
|
93
|
+
error.mockRestore();
|
|
94
|
+
});
|
|
95
|
+
it("search with too many arguments fails", async () => {
|
|
96
|
+
const error = vi.spyOn(console, "error").mockImplementation(() => undefined);
|
|
97
|
+
await expect(run(["search", "a", "b"])).resolves.toBe(1);
|
|
98
|
+
expect(error).toHaveBeenCalledWith("search requires exactly one query argument.");
|
|
99
|
+
error.mockRestore();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
async function createRepository(files) {
|
|
103
|
+
const repositoryPath = await mkdtemp(join(tmpdir(), "workflows-"));
|
|
104
|
+
temporaryDirectories.push(repositoryPath);
|
|
105
|
+
await Promise.all(Object.entries(files).map(async ([relativePath, content]) => {
|
|
106
|
+
const path = join(repositoryPath, relativePath);
|
|
107
|
+
const { mkdir } = await import("node:fs/promises");
|
|
108
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
109
|
+
await writeFile(path, content, "utf8");
|
|
110
|
+
}));
|
|
111
|
+
return repositoryPath;
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { inspectRepository, resolveVisibility } from "./repository-inspection.js";
|
|
6
|
+
import { run } from "./index.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("repository inspection", () => {
|
|
12
|
+
it("finds agent workflows and supported stack markers", async () => {
|
|
13
|
+
const repositoryPath = await createRepository({
|
|
14
|
+
".github/workflows/agent-refine.md": "# Refine",
|
|
15
|
+
".github/workflows/agent-custom.md": "# Custom",
|
|
16
|
+
".github/workflows/other.md": "# Other",
|
|
17
|
+
"package.json": "{}",
|
|
18
|
+
"pnpm-lock.yaml": "lockfileVersion: '9.0'",
|
|
19
|
+
"apps/api/Numa.slnx": "<Solution />",
|
|
20
|
+
"openspec/changes/.gitkeep": "",
|
|
21
|
+
});
|
|
22
|
+
await expect(inspectRepository(repositoryPath)).resolves.toMatchObject({
|
|
23
|
+
existingAgentWorkflows: ["agent-custom.md", "agent-refine.md"],
|
|
24
|
+
stackHints: {
|
|
25
|
+
packageJson: true,
|
|
26
|
+
pnpmLockfile: true,
|
|
27
|
+
solutionFiles: [join(repositoryPath, "apps", "api", "Numa.slnx")],
|
|
28
|
+
openSpec: true,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
it("uses an explicit visibility before environment or GitHub", async () => {
|
|
33
|
+
const runner = { run: async () => '{"visibility":"public"}' };
|
|
34
|
+
await expect(resolveVisibility("repo", "private", { PLATFORM_WORKFLOWS_VISIBILITY: "public" }, runner)).resolves.toEqual({
|
|
35
|
+
value: "private",
|
|
36
|
+
source: "argument",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
it("falls back to private when GitHub CLI is unavailable", async () => {
|
|
40
|
+
const runner = { run: async () => Promise.reject(new Error("missing gh")) };
|
|
41
|
+
await expect(resolveVisibility("repo", undefined, {}, runner)).resolves.toEqual({ value: "private", source: "fallback" });
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe("CLI commands", () => {
|
|
45
|
+
it("keeps init available without writing configuration", async () => {
|
|
46
|
+
const repositoryPath = await createRepository({ "package.json": "{}" });
|
|
47
|
+
const output = captureConsole("log");
|
|
48
|
+
await expect(run(["init", "--visibility", "public"], repositoryPath)).resolves.toBe(0);
|
|
49
|
+
expect(output.calls).toHaveLength(1);
|
|
50
|
+
await expect(import("node:fs/promises").then(({ access }) => access(join(repositoryPath, ".github/workflows/shared/repo-config.md")))).rejects.toThrow();
|
|
51
|
+
output.restore();
|
|
52
|
+
});
|
|
53
|
+
it("accepts update as an add alias", async () => {
|
|
54
|
+
const repositoryPath = await createRepository({});
|
|
55
|
+
const error = captureConsole("error");
|
|
56
|
+
await expect(run(["update", "--invalid"], repositoryPath)).resolves.toBe(1);
|
|
57
|
+
expect(error.calls).toEqual(["update accepts only --force or --template agentics-checks|agentics-maintenance|app-ci-dotnet-next|app-ci-node-monorepo|opencode.ci.json."]);
|
|
58
|
+
error.restore();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
function captureConsole(method) {
|
|
62
|
+
const original = console[method];
|
|
63
|
+
const calls = [];
|
|
64
|
+
console[method] = (message) => calls.push(message);
|
|
65
|
+
return { calls, restore: () => { console[method] = original; } };
|
|
66
|
+
}
|
|
67
|
+
async function createRepository(files) {
|
|
68
|
+
const repositoryPath = await mkdtemp(join(tmpdir(), "workflows-"));
|
|
69
|
+
temporaryDirectories.push(repositoryPath);
|
|
70
|
+
await Promise.all(Object.entries(files).map(async ([relativePath, content]) => {
|
|
71
|
+
const path = join(repositoryPath, relativePath);
|
|
72
|
+
const { mkdir } = await import("node:fs/promises");
|
|
73
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
74
|
+
await writeFile(path, content, "utf8");
|
|
75
|
+
}));
|
|
76
|
+
return repositoryPath;
|
|
77
|
+
}
|
|
@@ -3,15 +3,17 @@ export type RouteName = (typeof routeNames)[number];
|
|
|
3
3
|
export interface WorkflowRoute {
|
|
4
4
|
readonly name: RouteName;
|
|
5
5
|
readonly worker: string;
|
|
6
|
+
readonly description: string;
|
|
6
7
|
readonly defaultEnabled: boolean;
|
|
7
8
|
}
|
|
8
9
|
export declare const workflowRoutes: readonly WorkflowRoute[];
|
|
9
10
|
export declare const packageOwnedTargets: readonly [".github/actions", ".github/workflows/agent-*.md", ".github/workflows/shared/platform-defaults.md", ".github/workflows/shared/opencode-ci.md", ".github/workflows/work-router.yml", "scripts/compile-agent-workflows.mjs"];
|
|
10
|
-
export declare const
|
|
11
|
-
export declare const
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
readonly
|
|
15
|
-
readonly
|
|
11
|
+
export declare const generatedConsumerTargets: readonly [".github/workflows/agent-*.lock.yml", ".github/aw/actions-lock.json"];
|
|
12
|
+
export declare const templateNames: readonly ["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "opencode.ci.json"];
|
|
13
|
+
export type TemplateName = (typeof templateNames)[number];
|
|
14
|
+
export interface CatalogTemplate {
|
|
15
|
+
readonly name: TemplateName;
|
|
16
|
+
readonly file: string;
|
|
17
|
+
readonly description: string;
|
|
16
18
|
}
|
|
17
|
-
export declare const
|
|
19
|
+
export declare const catalogTemplates: readonly CatalogTemplate[];
|
package/dist/workflow-catalog.js
CHANGED
|
@@ -8,13 +8,13 @@ export const routeNames = [
|
|
|
8
8
|
"propose",
|
|
9
9
|
];
|
|
10
10
|
export const workflowRoutes = [
|
|
11
|
-
{ name: "refine", worker: "agent-refine.md", defaultEnabled: true },
|
|
12
|
-
{ name: "implement", worker: "agent-implement.md", defaultEnabled: true },
|
|
13
|
-
{ name: "direct", worker: "agent-direct.md", defaultEnabled: true },
|
|
14
|
-
{ name: "apply-review", worker: "agent-apply-review.md", defaultEnabled: true },
|
|
15
|
-
{ name: "merge-gate", worker: "agent-merge-gate.md", defaultEnabled: true },
|
|
16
|
-
{ name: "audit", worker: "agent-audit.md", defaultEnabled: true },
|
|
17
|
-
{ name: "propose", worker: "agent-propose.md", defaultEnabled: false },
|
|
11
|
+
{ name: "refine", worker: "agent-refine.md", description: "Refines an issue into a user story, on a first pass or after the author has answered the bot's questions.", defaultEnabled: true },
|
|
12
|
+
{ name: "implement", worker: "agent-implement.md", description: "Implements an issue and opens a pull request. Stops there: the merge decision belongs to the merge gate.", defaultEnabled: true },
|
|
13
|
+
{ name: "direct", worker: "agent-direct.md", description: "Executes a free-form instruction from an issue body and posts the results back on the same issue.", defaultEnabled: true },
|
|
14
|
+
{ name: "apply-review", worker: "agent-apply-review.md", description: "Applies reviewer feedback to an open pull request the bot authored, then pushes the fixes to the same branch.", defaultEnabled: true },
|
|
15
|
+
{ name: "merge-gate", worker: "agent-merge-gate.md", description: "Decides what happens to a bot-authored pull request once CI has reported: merge, hand to a human, or fix CI.", defaultEnabled: true },
|
|
16
|
+
{ name: "audit", worker: "agent-audit.md", description: "Read-only repository audit. Finds 5-7 problems, scores each 1-10, files a single issue with the top 3 refined as actionable user stories.", defaultEnabled: true },
|
|
17
|
+
{ name: "propose", worker: "agent-propose.md", description: "Proposes the next feature. Reads the manifesto, recent history, and comparable tools, scores candidates, and files the winner as a single issue.", defaultEnabled: false },
|
|
18
18
|
];
|
|
19
19
|
export const packageOwnedTargets = [
|
|
20
20
|
".github/actions",
|
|
@@ -24,16 +24,21 @@ export const packageOwnedTargets = [
|
|
|
24
24
|
".github/workflows/work-router.yml",
|
|
25
25
|
"scripts/compile-agent-workflows.mjs",
|
|
26
26
|
];
|
|
27
|
-
export const consumerOwnedTargets = [
|
|
28
|
-
".github/workflows/shared/repo-config.md",
|
|
29
|
-
];
|
|
30
27
|
export const generatedConsumerTargets = [
|
|
31
28
|
".github/workflows/agent-*.lock.yml",
|
|
32
|
-
".github/workflows/agentics-maintenance.yml",
|
|
33
29
|
".github/aw/actions-lock.json",
|
|
34
30
|
];
|
|
35
|
-
export const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
export const templateNames = [
|
|
32
|
+
"agentics-checks",
|
|
33
|
+
"agentics-maintenance",
|
|
34
|
+
"app-ci-dotnet-next",
|
|
35
|
+
"app-ci-node-monorepo",
|
|
36
|
+
"opencode.ci.json",
|
|
37
|
+
];
|
|
38
|
+
export const catalogTemplates = [
|
|
39
|
+
{ name: "agentics-checks", file: "agentics-checks.yml", description: "Agentics checks: verifies generated agent lockfiles, actionlint, and compile on PRs touching workflow files." },
|
|
40
|
+
{ name: "agentics-maintenance", file: "agentics-maintenance.yml", description: "Agentic maintenance: scheduled daily maintenance workflow for keeping workflows and actions up to date." },
|
|
41
|
+
{ name: "app-ci-dotnet-next", file: "app-ci-dotnet-next.yml", description: "App CI pipeline for a .NET + Next.js monorepo: build, test, and lint on PRs and schedule." },
|
|
42
|
+
{ name: "app-ci-node-monorepo", file: "app-ci-node-monorepo.yml", description: "App CI pipeline for a Node monorepo: build, test, and lint on PRs and schedule." },
|
|
43
|
+
{ name: "opencode.ci.json", file: "opencode.ci.json", description: "Standalone OpenCode CI config: plainconcepts provider, GLM model registration, ci-workflow-agent, and LSP defaults for consumer repositories." },
|
|
44
|
+
];
|
|
@@ -1,14 +1,29 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { catalogTemplates, generatedConsumerTargets, packageOwnedTargets, routeNames, templateNames, workflowRoutes, } from "./workflow-catalog.js";
|
|
3
3
|
describe("workflow catalog", () => {
|
|
4
4
|
it("assigns one worker to each route", () => {
|
|
5
5
|
expect(workflowRoutes.map((route) => route.name)).toEqual(routeNames);
|
|
6
6
|
expect(new Set(workflowRoutes.map((route) => route.worker)).size).toBe(workflowRoutes.length);
|
|
7
7
|
});
|
|
8
|
-
it("
|
|
8
|
+
it("gives every route a non-empty description", () => {
|
|
9
|
+
for (const route of workflowRoutes) {
|
|
10
|
+
expect(route.description.length).toBeGreaterThan(0);
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
it("keeps generated files outside package ownership", () => {
|
|
9
14
|
const packageTargets = new Set(packageOwnedTargets);
|
|
10
|
-
for (const target of
|
|
15
|
+
for (const target of generatedConsumerTargets) {
|
|
11
16
|
expect(packageTargets.has(target)).toBe(false);
|
|
12
17
|
}
|
|
13
18
|
});
|
|
19
|
+
it("lists supported optional templates", () => {
|
|
20
|
+
expect(templateNames).toEqual(["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "opencode.ci.json"]);
|
|
21
|
+
});
|
|
22
|
+
it("gives every catalog template a non-empty description and file", () => {
|
|
23
|
+
expect(catalogTemplates.map((template) => template.name)).toEqual([...templateNames]);
|
|
24
|
+
expect(new Set(catalogTemplates.map((template) => template.file)).size).toBe(catalogTemplates.length);
|
|
25
|
+
for (const template of catalogTemplates) {
|
|
26
|
+
expect(template.description.length).toBeGreaterThan(0);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
14
29
|
});
|
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/add-issue-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Add issue labels
|
|
3
|
+
description: Add one or more labels to an issue or pull request.
|
|
4
|
+
inputs:
|
|
5
|
+
token:
|
|
6
|
+
description: GitHub token used by github-script.
|
|
7
|
+
required: true
|
|
8
|
+
issue-number:
|
|
9
|
+
description: Issue or pull request number.
|
|
10
|
+
required: true
|
|
11
|
+
labels:
|
|
12
|
+
description: Newline-delimited labels to add.
|
|
13
|
+
required: true
|
|
14
|
+
runs:
|
|
15
|
+
using: composite
|
|
16
|
+
steps:
|
|
17
|
+
- name: Add labels
|
|
18
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
19
|
+
env:
|
|
20
|
+
ISSUE_NUMBER: ${{ inputs.issue-number }}
|
|
21
|
+
LABELS: ${{ inputs.labels }}
|
|
22
|
+
with:
|
|
23
|
+
github-token: ${{ inputs.token }}
|
|
24
|
+
script: |
|
|
25
|
+
const labels = process.env.LABELS.split(/\r?\n/).filter(Boolean);
|
|
26
|
+
await github.rest.issues.addLabels({
|
|
27
|
+
...context.repo,
|
|
28
|
+
issue_number: Number(process.env.ISSUE_NUMBER),
|
|
29
|
+
labels,
|
|
30
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Managed by @plainconceptsplatform/workflows. Source: loops/actions/agent-output.cjs. Update with `workflows update --force`; consumer edits may be overwritten.
|
|
1
2
|
const fs = require('fs');
|
|
2
3
|
|
|
3
4
|
// Every apply-agent-* action reads the same artifact the same way: a missing file means the
|
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-bundle/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Apply agent bundle
|
|
3
|
+
description: Fast-forward an intended branch to the single commit exported by a verified git bundle.
|
|
4
|
+
inputs:
|
|
5
|
+
bundle-file:
|
|
6
|
+
description: Path to the git bundle from the agent artifact.
|
|
7
|
+
required: true
|
|
8
|
+
target-branch:
|
|
9
|
+
description: Branch that must fast-forward to the bundle commit.
|
|
10
|
+
required: true
|
|
11
|
+
base-branch:
|
|
12
|
+
description: Existing branch used only when target-branch does not exist.
|
|
13
|
+
required: false
|
|
14
|
+
default: ''
|
|
15
|
+
runs:
|
|
16
|
+
using: composite
|
|
17
|
+
steps:
|
|
18
|
+
- name: Fast-forward branch from bundle
|
|
19
|
+
shell: bash
|
|
20
|
+
env:
|
|
21
|
+
BUNDLE_FILE: ${{ inputs.bundle-file }}
|
|
22
|
+
TARGET_BRANCH: ${{ inputs.target-branch }}
|
|
23
|
+
BASE_BRANCH: ${{ inputs.base-branch }}
|
|
24
|
+
run: bash "$GITHUB_ACTION_PATH/apply-bundle.sh" "$BUNDLE_FILE" "$TARGET_BRANCH" "$BASE_BRANCH"
|
|
@@ -1,41 +1,42 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-comments/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Apply agent comments
|
|
3
|
+
description: Post every add_comment item from agent_output.json.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
token:
|
|
9
|
+
description: GitHub token with issues:write and pull-requests:write.
|
|
10
|
+
required: true
|
|
11
|
+
runs:
|
|
12
|
+
using: composite
|
|
13
|
+
steps:
|
|
14
|
+
- name: Post comments
|
|
15
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
16
|
+
env:
|
|
17
|
+
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
18
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
19
|
+
with:
|
|
20
|
+
github-token: ${{ inputs.token }}
|
|
21
|
+
script: |
|
|
22
|
+
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
23
|
+
const items = readAgentItems(process.env.OUTPUT_FILE, 'add_comment');
|
|
24
|
+
|
|
25
|
+
let posted = 0;
|
|
26
|
+
|
|
27
|
+
for (const item of items) {
|
|
28
|
+
if (!item.item_number) {
|
|
29
|
+
core.warning(`add_comment item has no item_number, skipping: ${JSON.stringify(item)}`);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
await github.rest.issues.createComment({
|
|
34
|
+
...context.repo,
|
|
35
|
+
issue_number: Number(item.item_number),
|
|
36
|
+
body: item.body,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
posted += 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
core.info(`Posted ${posted} comment(s).`);
|
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
let
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
1
|
+
# Managed by @plainconceptsplatform/workflows. Source: loops/actions/apply-agent-labels/action.yml. Update with workflows update --force; consumer edits may be overwritten.
|
|
2
|
+
name: Apply agent labels
|
|
3
|
+
description: Apply every add_labels and remove_labels item from agent_output.json.
|
|
4
|
+
inputs:
|
|
5
|
+
output-file:
|
|
6
|
+
description: Path to agent_output.json.
|
|
7
|
+
required: true
|
|
8
|
+
token:
|
|
9
|
+
description: GitHub token with issues:write.
|
|
10
|
+
required: true
|
|
11
|
+
runs:
|
|
12
|
+
using: composite
|
|
13
|
+
steps:
|
|
14
|
+
- name: Apply label changes
|
|
15
|
+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
|
16
|
+
env:
|
|
17
|
+
AGENT_OUTPUT_LIB: ${{ github.action_path }}/../agent-output.cjs
|
|
18
|
+
OUTPUT_FILE: ${{ inputs.output-file }}
|
|
19
|
+
with:
|
|
20
|
+
github-token: ${{ inputs.token }}
|
|
21
|
+
script: |
|
|
22
|
+
const { readAgentItems } = require(process.env.AGENT_OUTPUT_LIB);
|
|
23
|
+
const items = readAgentItems(process.env.OUTPUT_FILE)
|
|
24
|
+
.filter((item) => item.type === 'add_labels' || item.type === 'remove_labels');
|
|
25
|
+
|
|
26
|
+
let added = 0;
|
|
27
|
+
let removed = 0;
|
|
28
|
+
|
|
29
|
+
for (const item of items) {
|
|
30
|
+
const labels = (item.labels ?? []).map((label) => label.name).filter(Boolean);
|
|
31
|
+
|
|
32
|
+
if (!item.item_number || labels.length === 0) {
|
|
33
|
+
core.warning(`${item.type} item is not actionable, skipping: ${JSON.stringify(item)}`);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const issue_number = Number(item.item_number);
|
|
38
|
+
|
|
39
|
+
if (item.type === 'add_labels') {
|
|
40
|
+
await github.rest.issues.addLabels({ ...context.repo, issue_number, labels });
|
|
41
|
+
added += labels.length;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const name of labels) {
|
|
46
|
+
try {
|
|
47
|
+
await github.rest.issues.removeLabel({ ...context.repo, issue_number, name });
|
|
48
|
+
removed += 1;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (error.status !== 404) throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
core.info(`Added ${added} label(s), removed ${removed}.`);
|