@plainconceptsplatform/workflows 0.4.0 → 0.4.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 CHANGED
@@ -48,7 +48,7 @@ Route names: refine, implement, direct, apply-review, merge-gate, audit, propose
48
48
 
49
49
  Use `workflows update --force` to force-overwrite managed files that differ from the package source.
50
50
 
51
- Install optional standalone templates with `add --template`. Available templates are `agentics-checks`, `agentics-maintenance`, `app-ci-dotnet-next`, and `app-ci-node-monorepo`. CI templates are stack-specific copies, not a combined template. Edit their top-level `env:` values for repository paths, package names, and commands.
51
+ Install optional standalone templates with `add --template`. Available templates are `agentics-checks`, `agentics-maintenance`, `app-ci-dotnet-next`, `app-ci-node-monorepo`, and `github-release`. CI templates are stack-specific copies, not a combined template. `github-release` publishes generated release notes when a `v*` tag is pushed. Edit their top-level `env:` values for repository paths, package names, and commands.
52
52
 
53
53
  ## List and search
54
54
 
@@ -9,6 +9,7 @@ export interface CatalogInstallOptions {
9
9
  readonly sourcePath?: string;
10
10
  readonly selectedRoutes?: readonly RouteName[];
11
11
  readonly inspection?: RepositoryInspection;
12
+ readonly compile?: (repositoryPath: string) => Promise<void>;
12
13
  }
13
14
  interface CatalogFile {
14
15
  readonly source: string;
@@ -1,5 +1,5 @@
1
1
  import { execFile } from "node:child_process";
2
- import { access, copyFile, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
2
+ import { access, copyFile, cp, mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
3
  import { constants } from "node:fs";
4
4
  import { dirname, join, relative, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -33,13 +33,6 @@ export async function installCatalog(repositoryPath, options = {}) {
33
33
  const fileName = file.target.split("/").pop() ?? "";
34
34
  return !excluded.has(fileName);
35
35
  });
36
- const managedFiles = filtered.filter((file) => file.managed);
37
- const conflicts = (await Promise.all(managedFiles.map(async (file) => {
38
- const destination = join(repositoryPath, file.target);
39
- return await exists(destination) && !(await filesMatch(file.source, destination)) ? file.target : undefined;
40
- }))).filter((file) => file !== undefined);
41
- if (conflicts.length > 0 && !options.force)
42
- return { installed: [], conflicts };
43
36
  const fileContents = new Map();
44
37
  for (const file of filtered) {
45
38
  fileContents.set(file.target, await readFile(file.source, "utf8"));
@@ -50,21 +43,14 @@ export async function installCatalog(repositoryPath, options = {}) {
50
43
  processedContents = injectStackIntoWorkers(processedContents, defaults);
51
44
  processedContents = transformOpencodeFiles(processedContents, options.inspection);
52
45
  }
53
- await Promise.all([...processedContents.entries()].map(async ([target, content]) => {
54
- const destination = join(repositoryPath, target);
55
- const originalFile = filtered.find((f) => f.target === target);
56
- if (originalFile !== undefined && !originalFile.managed && await exists(destination))
57
- return;
58
- await mkdir(dirname(destination), { recursive: true });
59
- await writeFile(destination, content, "utf8");
60
- }));
61
- await ensurePreCommitHook(repositoryPath);
62
- try {
63
- await runCompileIfAvailable(repositoryPath);
64
- }
65
- catch {
66
- // compile failure is non-fatal
67
- }
46
+ const updates = [...processedContents.entries()]
47
+ .filter(([target]) => filtered.find((file) => file.target === target)?.managed ?? true)
48
+ .map(([target, content]) => ({ target, content }));
49
+ const conflicts = await conflictingTargets(repositoryPath, updates);
50
+ if (conflicts.length > 0 && !options.force)
51
+ return { installed: [], conflicts };
52
+ const stagedLocks = await validateStagedCatalog(repositoryPath, updates, options.compile);
53
+ await applyTransaction(repositoryPath, [...updates, ...stagedLocks, await preCommitHookUpdate(repositoryPath)]);
68
54
  return { installed: [...processedContents.keys()].sort(), conflicts };
69
55
  }
70
56
  function injectStackIntoWorkers(files, defaults) {
@@ -121,31 +107,30 @@ export async function installMandatoryFiles(repositoryPath, options = {}) {
121
107
  }))).filter((file) => file !== undefined);
122
108
  if (conflicts.length > 0 && !options.force)
123
109
  return { installed: [], conflicts };
124
- await Promise.all(files.map(async (file) => {
125
- const destination = join(repositoryPath, file.target);
126
- await mkdir(dirname(destination), { recursive: true });
127
- await copyFile(file.source, destination);
128
- }));
110
+ const updates = await Promise.all(files.map(async (file) => ({ target: file.target, content: await readFile(file.source, "utf8") })));
111
+ await applyTransaction(repositoryPath, [...updates, await preCommitHookUpdate(repositoryPath)]);
129
112
  return { installed: files.map((file) => file.target), conflicts };
130
113
  }
131
114
  export function isTemplateName(value) {
132
115
  return templateNames.includes(value);
133
116
  }
134
117
  export async function ensurePreCommitHook(repositoryPath) {
135
- const hookPath = join(repositoryPath, ".husky", "pre-commit");
118
+ const update = await preCommitHookUpdate(repositoryPath);
119
+ await applyTransaction(repositoryPath, [update]);
120
+ }
121
+ async function preCommitHookUpdate(repositoryPath) {
122
+ const target = ".husky/pre-commit";
123
+ const hookPath = join(repositoryPath, target);
136
124
  const compileLine = "node scripts/compile-agent-workflows.mjs";
137
125
  if (!await exists(hookPath)) {
138
- await mkdir(dirname(hookPath), { recursive: true });
139
- await writeFile(hookPath, `${compileLine}\n`, "utf8");
140
- return;
126
+ return { target, content: `${compileLine}\n` };
141
127
  }
142
128
  const content = await readFile(hookPath, "utf8");
143
129
  if (content.includes("compile-agent-workflows"))
144
- return;
145
- const newContent = content.endsWith("\n") || content === ""
146
- ? `${content}${compileLine}\n`
147
- : `${content}\n${compileLine}\n`;
148
- await writeFile(hookPath, newContent, "utf8");
130
+ return { target, content };
131
+ return { target, content: content.endsWith("\n") || content === ""
132
+ ? `${content}${compileLine}\n`
133
+ : `${content}\n${compileLine}\n` };
149
134
  }
150
135
  export async function runCompileIfAvailable(repositoryPath) {
151
136
  const script = join(repositoryPath, "scripts", "compile-agent-workflows.mjs");
@@ -153,11 +138,93 @@ export async function runCompileIfAvailable(repositoryPath) {
153
138
  await execFileAsync("node", [script, "--force"], { cwd: repositoryPath });
154
139
  }
155
140
  }
141
+ async function conflictingTargets(repositoryPath, updates) {
142
+ const conflicts = (await Promise.all(updates.map(async ({ target, content }) => {
143
+ const destination = join(repositoryPath, target);
144
+ if (!await exists(destination))
145
+ return undefined;
146
+ return (await readFile(destination, "utf8")) === content ? undefined : target;
147
+ }))).filter((target) => target !== undefined);
148
+ return [...new Set(conflicts)].sort();
149
+ }
150
+ async function validateStagedCatalog(repositoryPath, updates, compileOverride) {
151
+ const temporaryRoot = join(repositoryPath, ".opencode", ".tmp");
152
+ await mkdir(temporaryRoot, { recursive: true });
153
+ const stagingPath = await mkdtemp(join(temporaryRoot, "workflows-"));
154
+ try {
155
+ await copyCompilationInputs(repositoryPath, stagingPath);
156
+ await writeUpdates(stagingPath, updates);
157
+ const compiler = compileOverride ?? await packageCompiler(stagingPath);
158
+ if (compiler === undefined)
159
+ return [];
160
+ await compiler(stagingPath);
161
+ return await generatedFiles(stagingPath);
162
+ }
163
+ finally {
164
+ await rm(stagingPath, { force: true, recursive: true });
165
+ }
166
+ }
167
+ async function copyCompilationInputs(repositoryPath, stagingPath) {
168
+ const githubPath = join(repositoryPath, ".github");
169
+ if (await exists(githubPath))
170
+ await cp(githubPath, join(stagingPath, ".github"), { recursive: true });
171
+ }
172
+ async function packageCompiler(repositoryPath) {
173
+ const scriptPath = join(repositoryPath, "scripts", "compile-agent-workflows.mjs");
174
+ if (!await exists(scriptPath))
175
+ return undefined;
176
+ const content = await readFile(scriptPath, "utf8");
177
+ return content.includes("gh aw compile") ? runCompileIfAvailable : undefined;
178
+ }
179
+ async function generatedFiles(repositoryPath) {
180
+ const workflowPath = join(repositoryPath, ".github", "workflows");
181
+ const updates = [];
182
+ if (await exists(workflowPath)) {
183
+ for (const file of await filesIn(workflowPath)) {
184
+ if (!file.endsWith(".lock.yml"))
185
+ continue;
186
+ updates.push({ target: `.github/workflows/${file.replaceAll("\\", "/")}`, content: await readFile(join(workflowPath, file), "utf8") });
187
+ }
188
+ }
189
+ const actionsLock = join(repositoryPath, ".github", "actions", "actions-lock.json");
190
+ if (await exists(actionsLock)) {
191
+ updates.push({ target: ".github/actions/actions-lock.json", content: await readFile(actionsLock, "utf8") });
192
+ }
193
+ return updates.sort((left, right) => left.target.localeCompare(right.target));
194
+ }
195
+ async function applyTransaction(repositoryPath, updates) {
196
+ const uniqueUpdates = [...new Map(updates.map((update) => [update.target, update])).values()];
197
+ const rollback = await Promise.all(uniqueUpdates.map(async ({ target }) => {
198
+ const path = join(repositoryPath, target);
199
+ return { target, existed: await exists(path), content: await exists(path) ? await readFile(path, "utf8") : undefined };
200
+ }));
201
+ try {
202
+ await writeUpdates(repositoryPath, uniqueUpdates);
203
+ }
204
+ catch (error) {
205
+ await Promise.all(rollback.map(async ({ target, existed, content }) => {
206
+ if (existed && content !== undefined) {
207
+ await writeUpdates(repositoryPath, [{ target, content }]);
208
+ }
209
+ else {
210
+ await rm(join(repositoryPath, target), { force: true });
211
+ }
212
+ }));
213
+ throw error;
214
+ }
215
+ }
216
+ async function writeUpdates(repositoryPath, updates) {
217
+ await Promise.all(updates.map(async ({ target, content }) => {
218
+ const destination = join(repositoryPath, target);
219
+ await mkdir(dirname(destination), { recursive: true });
220
+ await writeFile(destination, content, "utf8");
221
+ }));
222
+ }
156
223
  function catalogTemplateMeta(template) {
157
224
  const entry = catalogTemplates.find((item) => item.name === template);
158
225
  if (entry === undefined)
159
226
  throw new Error(`Unknown template: ${template}`);
160
- const directory = template.startsWith("opencode") ? "opencode" : template.startsWith("app-ci-") ? "ci" : "agentics";
227
+ const directory = template.startsWith("opencode") ? "opencode" : template.startsWith("app-ci-") ? "ci" : template === "github-release" ? "release" : "agentics";
161
228
  const isWorkflow = entry.file.endsWith(".yml");
162
229
  const target = template === "app-ci-dotnet-next"
163
230
  ? ".github/workflows/app-ci.yml"
@@ -154,6 +154,7 @@ describe("catalog installation", () => {
154
154
  "templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-2\" }\n",
155
155
  "templates/agentics/agentics-checks.yml": "name: Agentics checks\n",
156
156
  "templates/ci/app-ci-node-monorepo.yml": "name: Node CI\n",
157
+ "templates/release/github-release.yml": "name: Publish GitHub release\n",
157
158
  });
158
159
  const repositoryPath = await createDirectory({});
159
160
  await installCatalog(repositoryPath, { sourcePath });
@@ -166,6 +167,10 @@ describe("catalog installation", () => {
166
167
  installed: [".github/workflows/app-ci-node-monorepo.yml"],
167
168
  conflicts: [],
168
169
  });
170
+ await expect(installTemplate(repositoryPath, "github-release", { sourcePath })).resolves.toEqual({
171
+ installed: [".github/workflows/github-release.yml"],
172
+ conflicts: [],
173
+ });
169
174
  });
170
175
  it("installCatalog installs mandatory opencode.ci.json and compile script alongside catalog files", async () => {
171
176
  const sourcePath = await createDirectory({
@@ -256,6 +261,47 @@ describe("catalog installation", () => {
256
261
  await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8"))
257
262
  .resolves.toBe("pnpm lint\nnode scripts/compile-agent-workflows.mjs\n");
258
263
  });
264
+ it("leaves consumer files untouched when staged workflow compilation fails", async () => {
265
+ const sourcePath = await createDirectory({
266
+ "actions/check/action.yml": "package action\n",
267
+ "workflows/agent-check.md": "package workflow\n",
268
+ "scripts/compile-agent-workflows.mjs": "compile\n",
269
+ "templates/opencode/opencode.ci.json": "{ \"model\": \"package\" }\n",
270
+ });
271
+ const repositoryPath = await createDirectory({
272
+ ".github/actions/check/action.yml": "consumer action\n",
273
+ ".github/workflows/agent-check.md": "consumer workflow\n",
274
+ "opencode.ci.json": "{ \"model\": \"consumer\" }\n",
275
+ "scripts/compile-agent-workflows.mjs": "consumer compiler\n",
276
+ });
277
+ await expect(installCatalog(repositoryPath, {
278
+ force: true,
279
+ sourcePath,
280
+ compile: async () => { throw new Error("compile failed"); },
281
+ })).rejects.toThrow("compile failed");
282
+ await expect(readFile(join(repositoryPath, ".github/workflows/agent-check.md"), "utf8"))
283
+ .resolves.toBe("consumer workflow\n");
284
+ await expect(readFile(join(repositoryPath, "scripts/compile-agent-workflows.mjs"), "utf8"))
285
+ .resolves.toBe("consumer compiler\n");
286
+ await expect(readFile(join(repositoryPath, ".husky", "pre-commit"), "utf8")).rejects.toThrow();
287
+ });
288
+ it("applies staged generated locks with managed sources", async () => {
289
+ const sourcePath = await createDirectory({
290
+ "actions/check/action.yml": "package action\n",
291
+ "workflows/agent-check.md": "package workflow\n",
292
+ "scripts/compile-agent-workflows.mjs": "compile\n",
293
+ "templates/opencode/opencode.ci.json": "{ \"model\": \"package\" }\n",
294
+ });
295
+ const repositoryPath = await createDirectory({});
296
+ await installCatalog(repositoryPath, {
297
+ sourcePath,
298
+ compile: async (stagingPath) => {
299
+ await writeFile(join(stagingPath, ".github", "workflows", "agent-check.lock.yml"), "opencode run --log-level ERROR\n", "utf8");
300
+ },
301
+ });
302
+ await expect(readFile(join(repositoryPath, ".github", "workflows", "agent-check.lock.yml"), "utf8"))
303
+ .resolves.toBe("opencode run --log-level ERROR\n");
304
+ });
259
305
  it("installs the opencode.ci.json template to the repository root", async () => {
260
306
  const sourcePath = await createDirectory({
261
307
  "templates/opencode/opencode.ci.json": "{ \"model\": \"plainconcepts/glm-5-2\" }\n",
@@ -16,7 +16,7 @@ describe("catalog listing", () => {
16
16
  const routeNames = entries.filter((entry) => entry.kind === "route").map((entry) => entry.name);
17
17
  const templateNames = entries.filter((entry) => entry.kind === "template").map((entry) => entry.name);
18
18
  expect(routeNames).toEqual(["refine", "implement", "direct", "apply-review", "merge-gate", "audit", "propose"]);
19
- expect(templateNames).toEqual(["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "opencode.ci.json"]);
19
+ expect(templateNames).toEqual(["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "github-release", "opencode.ci.json"]);
20
20
  });
21
21
  it("reports all entries as not installed in an empty repository", async () => {
22
22
  const repositoryPath = await createRepository({});
@@ -61,12 +61,13 @@ describe("workflows CLI", () => {
61
61
  expect(output).toContain("implement");
62
62
  expect(output).toContain("agentics-checks");
63
63
  expect(output).toContain("app-ci-dotnet-next");
64
+ expect(output).toContain("github-release");
64
65
  // None installed: all [ ]
65
66
  const installedCount = (output.match(/\[x\]/g) ?? []).length;
66
67
  expect(installedCount).toBe(0);
67
- // 7 routes + 5 templates = 12 entries
68
+ // 7 routes + 6 templates = 13 entries
68
69
  const uninstalledCount = (output.match(/\[ \]/g) ?? []).length;
69
- expect(uninstalledCount).toBe(12);
70
+ expect(uninstalledCount).toBe(13);
70
71
  log.mockRestore();
71
72
  });
72
73
  it("marks installed workflows with [x]", async () => {
@@ -14,7 +14,7 @@ export interface MandatoryFile {
14
14
  }
15
15
  export declare const mandatoryFiles: readonly MandatoryFile[];
16
16
  export declare const generatedConsumerTargets: readonly [".github/workflows/agent-*.lock.yml", ".github/aw/actions-lock.json"];
17
- export declare const templateNames: readonly ["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "opencode.ci.json"];
17
+ export declare const templateNames: readonly ["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "github-release", "opencode.ci.json"];
18
18
  export type TemplateName = (typeof templateNames)[number];
19
19
  export interface CatalogTemplate {
20
20
  readonly name: TemplateName;
@@ -38,6 +38,7 @@ export const templateNames = [
38
38
  "agentics-maintenance",
39
39
  "app-ci-dotnet-next",
40
40
  "app-ci-node-monorepo",
41
+ "github-release",
41
42
  "opencode.ci.json",
42
43
  ];
43
44
  export const catalogTemplates = [
@@ -45,5 +46,6 @@ export const catalogTemplates = [
45
46
  { name: "agentics-maintenance", file: "agentics-maintenance.yml", description: "Agentic maintenance: scheduled daily maintenance workflow for keeping workflows and actions up to date." },
46
47
  { 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." },
47
48
  { 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." },
49
+ { name: "github-release", file: "github-release.yml", description: "Publishes or updates a GitHub Release with generated notes whenever a v* tag is pushed." },
48
50
  { 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." },
49
51
  ];
@@ -17,7 +17,7 @@ describe("workflow catalog", () => {
17
17
  }
18
18
  });
19
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"]);
20
+ expect(templateNames).toEqual(["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "github-release", "opencode.ci.json"]);
21
21
  });
22
22
  it("gives every catalog template a non-empty description and file", () => {
23
23
  expect(catalogTemplates.map((template) => template.name)).toEqual([...templateNames]);
@@ -0,0 +1,30 @@
1
+ # Managed by @plainconceptsplatform/workflows. Source: loops/templates/release/github-release.yml. Update with `workflows update --force`; consumer edits may be overwritten.
2
+ name: Publish GitHub release
3
+
4
+ on:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ permissions:
10
+ contents: write
11
+
12
+ jobs:
13
+ publish:
14
+ name: Publish release
15
+ runs-on: ubuntu-latest
16
+ timeout-minutes: 10
17
+ steps:
18
+ - name: Create or update GitHub release
19
+ env:
20
+ GH_TOKEN: ${{ github.token }}
21
+ TAG: ${{ github.ref_name }}
22
+ shell: bash
23
+ run: |
24
+ set -euo pipefail
25
+
26
+ if gh release view "$TAG" >/dev/null 2>&1; then
27
+ gh release edit "$TAG" --title "$TAG" --generate-notes
28
+ else
29
+ gh release create "$TAG" --title "$TAG" --generate-notes
30
+ fi
@@ -2,7 +2,6 @@
2
2
  # Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-apply-review.md. Update with `workflows update --force`; consumer edits may be overwritten.
3
3
  env:
4
4
  REPO_RULES: "Apply only actionable outstanding reviewer feedback to the selected bot pull request. Make minimal changes that address each comment. Preserve architecture and do not weaken tests. Run full verification after changes."
5
- OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
6
5
  WORKING_LABEL: bot-working
7
6
  REVIEW_LABEL: review
8
7
  REVIEW_MARKER: "<!-- agent-apply-review -->"
@@ -222,7 +221,7 @@ engine:
222
221
  id: opencode
223
222
  version: "1.2.14"
224
223
  env:
225
- OPENAI_BASE_URL: ${{ env.OPENAI_BASE_URL }}
224
+ OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
226
225
 
227
226
  model: openai/glm-5-2
228
227
 
@@ -2,7 +2,6 @@
2
2
  # Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-audit.md. Update with `workflows update --force`; consumer edits may be overwritten.
3
3
  env:
4
4
  REPO_RULES: "Read-only repository audit. Report only reproducible, actionable defects with evidence. Look for: architectural layer violations, missing tests, security gaps, performance issues, and documentation drift. Do not modify files, commit, push, or run write operations."
5
- OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
6
5
  AUDIT_MARKER: "<!-- agent-audit -->"
7
6
  GIT_AUTHOR_NAME: "github-actions[bot]"
8
7
  GIT_AUTHOR_EMAIL: "github-actions[bot]@users.noreply.github.com"
@@ -52,7 +51,7 @@ engine:
52
51
  id: opencode
53
52
  version: "1.2.14"
54
53
  env:
55
- OPENAI_BASE_URL: ${{ env.OPENAI_BASE_URL }}
54
+ OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
56
55
 
57
56
  model: openai/glm-5-2
58
57
 
@@ -2,7 +2,6 @@
2
2
  # Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-direct.md. Update with `workflows update --force`; consumer edits may be overwritten.
3
3
  env:
4
4
  REPO_RULES: "Execute the selected issue's latest human instruction exactly as asked. Follow repository documentation and existing patterns. Keep scope to the requested outcome."
5
- OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
6
5
  WORKING_LABEL: bot-working
7
6
  REVIEW_LABEL: review
8
7
  DIRECT_LABEL: direct
@@ -188,7 +187,7 @@ engine:
188
187
  id: opencode
189
188
  version: "1.2.14"
190
189
  env:
191
- OPENAI_BASE_URL: ${{ env.OPENAI_BASE_URL }}
190
+ OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
192
191
 
193
192
  model: openai/glm-5-2
194
193
 
@@ -2,7 +2,6 @@
2
2
  # Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-implement.md. Update with `workflows update --force`; consumer edits may be overwritten.
3
3
  env:
4
4
  REPO_RULES: "Implement only the selected issue. Follow repository documentation and existing conventions. Do not weaken tests, lower coverage thresholds, or bypass checks. Run the project's full verification suite before creating a pull request."
5
- OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
6
5
  IMPLEMENT_LABEL: implement
7
6
  WORKING_LABEL: bot-working
8
7
  REVIEW_LABEL: review
@@ -192,7 +191,7 @@ engine:
192
191
  id: opencode
193
192
  version: "1.2.14"
194
193
  env:
195
- OPENAI_BASE_URL: ${{ env.OPENAI_BASE_URL }}
194
+ OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
196
195
 
197
196
  model: openai/glm-5-2
198
197
 
@@ -2,7 +2,6 @@
2
2
  # Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-merge-gate.md. Update with `workflows update --force`; consumer edits may be overwritten.
3
3
  env:
4
4
  REPO_RULES: "Make a risk-based merge decision for the selected bot pull request. Merge only when CI is green and no risk indicators are present. Flag security, schema, auth, or calculation changes for human review. Do not merge protected file changes."
5
- OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
6
5
  WORKING_LABEL: bot-working
7
6
  IMPLEMENT_LABEL: implement
8
7
  REVIEW_LABEL: review
@@ -289,7 +288,7 @@ engine:
289
288
  id: opencode
290
289
  version: "1.2.14"
291
290
  env:
292
- OPENAI_BASE_URL: ${{ env.OPENAI_BASE_URL }}
291
+ OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
293
292
 
294
293
  model: openai/glm-5-2
295
294
 
@@ -2,7 +2,6 @@
2
2
  # Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-propose.md. Update with `workflows update --force`; consumer edits may be overwritten.
3
3
  env:
4
4
  REPO_RULES: "Propose one focused product candidate from repository evidence and curated feature radar. Respect documented product goals and architecture boundaries. Do not propose features that conflict with the project's stated scope."
5
- OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
6
5
  PROPOSED_LABEL: proposed
7
6
  IMPLEMENT_LABEL: implement
8
7
  PROPOSE_MARKER: "<!-- agent-propose -->"
@@ -66,7 +65,7 @@ engine:
66
65
  id: opencode
67
66
  version: "1.2.14"
68
67
  env:
69
- OPENAI_BASE_URL: ${{ env.OPENAI_BASE_URL }}
68
+ OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
70
69
 
71
70
  model: openai/glm-5-2
72
71
 
@@ -2,7 +2,6 @@
2
2
  # Managed by @plainconceptsplatform/workflows. Source: loops/workflows/agent-refine.md. Update with `workflows update --force`; consumer edits may be overwritten.
3
3
  env:
4
4
  REPO_RULES: "Refine only the selected issue into a grounded, implementation-ready user story. Read repository documentation for domain context. Write acceptance criteria that match existing patterns. Do not implement code."
5
- OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
6
5
  REFINE_LABEL: refine
7
6
  REFINED_LABEL: refined
8
7
  WORKING_LABEL: bot-working
@@ -227,7 +226,7 @@ engine:
227
226
  id: opencode
228
227
  version: "1.2.14"
229
228
  env:
230
- OPENAI_BASE_URL: ${{ env.OPENAI_BASE_URL }}
229
+ OPENAI_BASE_URL: https://forge.plainconcepts.com/v1
231
230
  args:
232
231
  - "--model"
233
232
  - "plainconcepts/glm-5-2"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plainconceptsplatform/workflows",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "Install and update Platform GitHub agentic workflows.",
5
5
  "keywords": [
6
6
  "github-actions",