@plainconceptsplatform/workflows 0.4.33 → 0.5.1

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.
Files changed (75) hide show
  1. package/README.md +88 -88
  2. package/loops/actions/agent-output.cjs +17 -17
  3. package/loops/actions/audit-close/action.yml +24 -0
  4. package/loops/actions/classify-route/action.yml +4 -3
  5. package/loops/actions/classify-route/classify-route.sh +69 -29
  6. package/loops/actions/load-issue-context/action.yml +2 -0
  7. package/loops/actions/update-changelog/action.yml +113 -0
  8. package/loops/actions/validate-merge-gate-output/action.yml +40 -0
  9. package/loops/actions/validate-merge-gate-output/validate-merge-gate-output.sh +34 -0
  10. package/loops/actions/validate-refine-output/validate-refine-output.sh +16 -3
  11. package/loops/actions/validate-review-output/action.yml +35 -0
  12. package/loops/actions/validate-review-output/validate-review-output.sh +30 -0
  13. package/loops/actions/validate-triage-output/action.yml +36 -0
  14. package/loops/actions/validate-triage-output/validate-triage-output.sh +36 -0
  15. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +121 -36
  16. package/loops/scripts/compile-agent-workflows.mjs +195 -6
  17. package/loops/templates/agentics/agentics-checks.yml +86 -86
  18. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  19. package/loops/templates/ci/app-ci-dotnet-next.yml +268 -171
  20. package/loops/templates/ci/app-ci-node-monorepo.yml +208 -178
  21. package/loops/templates/issues/bug_report.yml +109 -109
  22. package/loops/templates/issues/feature_request.yml +75 -75
  23. package/loops/templates/opencode/opencode.ci.json +49 -47
  24. package/loops/templates/opencode/opencode.ci.json.md +46 -41
  25. package/loops/templates/release/github-release.yml +30 -30
  26. package/loops/workflows/agent-apply-review.md +465 -373
  27. package/loops/workflows/agent-audit.md +213 -197
  28. package/loops/workflows/agent-implement.md +538 -374
  29. package/loops/workflows/agent-merge-gate.md +730 -485
  30. package/loops/workflows/agent-refine.md +609 -379
  31. package/loops/workflows/agent-release.md +258 -0
  32. package/loops/workflows/agent-triage.md +447 -0
  33. package/loops/workflows/authorize-bot-work.yml +85 -81
  34. package/loops/workflows/shared/opencode-ci.md +206 -197
  35. package/loops/workflows/shared/platform-defaults.md +19 -16
  36. package/loops/workflows/work-router.yml +862 -551
  37. package/package.json +42 -42
  38. package/dist/catalog-installation.d.ts +0 -30
  39. package/dist/catalog-installation.js +0 -352
  40. package/dist/catalog-installation.test.d.ts +0 -1
  41. package/dist/catalog-installation.test.js +0 -448
  42. package/dist/catalog-listing.d.ts +0 -13
  43. package/dist/catalog-listing.js +0 -70
  44. package/dist/catalog-listing.test.d.ts +0 -1
  45. package/dist/catalog-listing.test.js +0 -150
  46. package/dist/index.d.ts +0 -2
  47. package/dist/index.js +0 -218
  48. package/dist/index.test.d.ts +0 -1
  49. package/dist/index.test.js +0 -273
  50. package/dist/repository-inspection.d.ts +0 -23
  51. package/dist/repository-inspection.js +0 -113
  52. package/dist/repository-inspection.test.d.ts +0 -1
  53. package/dist/repository-inspection.test.js +0 -77
  54. package/dist/repository-state.d.ts +0 -18
  55. package/dist/repository-state.js +0 -77
  56. package/dist/repository-state.test.d.ts +0 -1
  57. package/dist/repository-state.test.js +0 -96
  58. package/dist/route-processing.d.ts +0 -6
  59. package/dist/route-processing.js +0 -150
  60. package/dist/route-processing.test.d.ts +0 -1
  61. package/dist/route-processing.test.js +0 -342
  62. package/dist/stack-defaults.d.ts +0 -11
  63. package/dist/stack-defaults.js +0 -104
  64. package/dist/stack-defaults.test.d.ts +0 -1
  65. package/dist/stack-defaults.test.js +0 -266
  66. package/dist/tui.d.ts +0 -25
  67. package/dist/tui.js +0 -287
  68. package/dist/tui.test.d.ts +0 -1
  69. package/dist/tui.test.js +0 -246
  70. package/dist/workflow-catalog.d.ts +0 -25
  71. package/dist/workflow-catalog.js +0 -57
  72. package/dist/workflow-catalog.test.d.ts +0 -1
  73. package/dist/workflow-catalog.test.js +0 -29
  74. package/loops/workflows/agent-direct.md +0 -363
  75. package/loops/workflows/agent-propose.md +0 -342
@@ -1,342 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { addRouteExclusion, excludedWorkerFiles, processRoutes, stripRouteFromClassifier, stripRouteFromRouter, } from "./route-processing.js";
3
- import { routeNames } from "./workflow-catalog.js";
4
- const ROUTER_YAML = `# header
5
- name: "All Work Router"
6
-
7
- on:
8
- schedule:
9
- - cron: "17 1 * * 1"
10
- - cron: "43 3 * * *"
11
- - cron: "0 6 * * *"
12
- - cron: "*/5 * * * *"
13
- - cron: "0 */2 * * *"
14
- - cron: "29 7 * * *"
15
-
16
- workflow_dispatch:
17
- inputs:
18
- operation:
19
- description: "Operation to run"
20
- required: true
21
- type: choice
22
- options:
23
- - refine
24
- - implement
25
- - direct
26
- - apply-review
27
- - merge-gate
28
- - audit
29
- - propose
30
- - audit-close
31
- - cleanup-artifacts
32
- - reconcile-bot-pr-runs
33
- - stale-recovery
34
- - validate
35
-
36
- jobs:
37
- call-refine:
38
- needs: [classify, authorize]
39
- if: needs.classify.outputs.route == 'refine' && needs.authorize.outputs.trusted == 'true'
40
- uses: ./.github/workflows/agent-refine.lock.yml
41
- secrets:
42
- OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}
43
-
44
- call-implement:
45
- needs: [classify, authorize]
46
- if: needs.classify.outputs.route == 'implement' && needs.authorize.outputs.trusted == 'true'
47
- uses: ./.github/workflows/agent-implement.lock.yml
48
- secrets:
49
- OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}
50
-
51
- call-audit:
52
- needs: classify
53
- if: needs.classify.outputs.route == 'audit'
54
- uses: ./.github/workflows/agent-audit.lock.yml
55
- secrets:
56
- OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}
57
-
58
- call-propose:
59
- needs: classify
60
- if: needs.classify.outputs.route == 'propose'
61
- uses: ./.github/workflows/agent-propose.lock.yml
62
- secrets:
63
- OPENAI_API_KEY: \${{ secrets.OPENAI_API_KEY }}
64
-
65
- audit-close:
66
- needs: classify
67
- if: needs.classify.outputs.route == 'audit-close'
68
- runs-on: ubuntu-latest
69
- `;
70
- const CLASSIFIER_SH = `#!/usr/bin/env bash
71
- set -euo pipefail
72
-
73
- readonly AUDIT_CRON="17 1 * * 1"
74
- readonly AUDIT_CLOSE_CRON="43 3 * * *"
75
- readonly CLEANUP_ARTIFACTS_CRON="0 6 * * *"
76
- readonly RECONCILE_BOT_PR_RUNS_CRON="*/5 * * * *"
77
- readonly STALE_RECOVERY_CRON="0 */2 * * *"
78
- readonly PROPOSE_CRON="29 7 * * *"
79
-
80
- classify_route() {
81
- local route="none" error=""
82
-
83
- case "\${EVENT:-}" in
84
- schedule)
85
- trigger_kind="scheduled"
86
- case "\${SCHEDULE:-}" in
87
- "\$AUDIT_CRON") route="audit" ;;
88
- "\$AUDIT_CLOSE_CRON") route="audit-close" ;;
89
- "\$CLEANUP_ARTIFACTS_CRON") route="cleanup-artifacts" ;;
90
- "\$RECONCILE_BOT_PR_RUNS_CRON") route="reconcile-bot-pr-runs" ;;
91
- "\$STALE_RECOVERY_CRON") route="stale-recovery" ;;
92
- "\$PROPOSE_CRON") route="propose" ;;
93
- *) error="no route for cron '\${SCHEDULE:-}'" ;;
94
- esac
95
- ;;
96
-
97
- workflow_dispatch)
98
- trigger_kind="manual"
99
- case "\${OPERATION:-}" in
100
- refine | implement | direct)
101
- route="\${OPERATION}"
102
- ;;
103
- apply-review)
104
- route="apply-review"
105
- ;;
106
- merge-gate)
107
- route="merge-gate"
108
- ;;
109
- audit | propose)
110
- route="\${OPERATION}"
111
- trigger_kind="\${INPUT_TRIGGER_KIND:-manual}"
112
- ;;
113
- audit-close | cleanup-artifacts | reconcile-bot-pr-runs | stale-recovery | validate)
114
- route="\${OPERATION}"
115
- ;;
116
- *)
117
- error="unknown operation '\${OPERATION:-}'"
118
- ;;
119
- esac
120
- ;;
121
- esac
122
-
123
- cat <<EOF
124
- route=\${route}
125
- error=\${error}
126
- EOF
127
- }
128
-
129
- if [ "\${BASH_SOURCE[0]}" = "\$0" ]; then
130
- classify_route
131
- fi
132
- `;
133
- const MATRIX_SH = `#!/usr/bin/env bash
134
- set -euo pipefail
135
-
136
- echo "── Router wiring ─────────────────────────────────────────────────────────"
137
- for route in refine implement direct apply-review merge-gate audit propose bot-approve \\
138
- audit-close cleanup-artifacts reconcile-bot-pr-runs stale-recovery validate; do
139
- if grep -q "route == '\${route}'" "\$ROUTER_YML"; then
140
- PASS=\$((PASS + 1))
141
- else
142
- FAIL=\$((FAIL + 1))
143
- echo "FAIL: work-router.yml has no job for route '\${route}'" >&2
144
- fi
145
- done
146
-
147
- while read -r operation; do
148
- if grep -q "route == '\${operation}'" "\$ROUTER_YML"; then
149
- PASS=\$((PASS + 1))
150
- else
151
- FAIL=\$((FAIL + 1))
152
- echo "FAIL: dispatch operation '\${operation}' has no job in work-router.yml" >&2
153
- fi
154
- done < <(sed -n '/^ operation:/,/^ issue-number:/p' "\$ROUTER_YML" | sed -n 's/^ - //p')
155
-
156
- echo
157
- if [ "\$FAIL" -eq 0 ]; then
158
- echo "Route matrix: \${PASS} passed"
159
- else
160
- echo "Route matrix: \${PASS} passed, \${FAIL} FAILED" >&2
161
- fi
162
-
163
- exit \$((FAIL > 0))
164
- `;
165
- describe("stripRouteFromRouter", () => {
166
- it("removes the propose cron entry", () => {
167
- const result = stripRouteFromRouter(ROUTER_YAML, "propose");
168
- expect(result).not.toContain('cron: "29 7 * * *"');
169
- expect(result).toContain('cron: "17 1 * * 1"');
170
- });
171
- it("removes the call-propose job block", () => {
172
- const result = stripRouteFromRouter(ROUTER_YAML, "propose");
173
- expect(result).not.toContain("call-propose");
174
- expect(result).toContain("call-refine");
175
- expect(result).toContain("call-audit");
176
- });
177
- it("removes propose from the dispatch options", () => {
178
- const result = stripRouteFromRouter(ROUTER_YAML, "propose");
179
- expect(result).not.toMatch(/^\s+- propose$/m);
180
- expect(result).toMatch(/^\s+- refine$/m);
181
- expect(result).toMatch(/^\s+- audit$/m);
182
- });
183
- it("removes the audit cron entry", () => {
184
- const result = stripRouteFromRouter(ROUTER_YAML, "audit");
185
- expect(result).not.toContain('cron: "17 1 * * 1"');
186
- expect(result).toContain('cron: "29 7 * * *"');
187
- });
188
- it("removes the call-audit job block", () => {
189
- const result = stripRouteFromRouter(ROUTER_YAML, "audit");
190
- expect(result).not.toContain("call-audit");
191
- expect(result).toContain("call-refine");
192
- });
193
- it("preserves the audit-close job when removing audit", () => {
194
- const result = stripRouteFromRouter(ROUTER_YAML, "audit");
195
- expect(result).toContain("audit-close");
196
- });
197
- it("does not modify the yaml when stripping a route that has no cron", () => {
198
- const yamlWithoutCron = ROUTER_YAML.replace(/ - cron: "17 1 \* \* 1"\n/, "");
199
- const result = stripRouteFromRouter(yamlWithoutCron, "audit");
200
- expect(result).not.toContain("call-audit");
201
- });
202
- });
203
- describe("stripRouteFromClassifier", () => {
204
- it("removes the PROPOSE_CRON constant", () => {
205
- const result = stripRouteFromClassifier(CLASSIFIER_SH, "propose");
206
- expect(result).not.toContain('readonly PROPOSE_CRON');
207
- expect(result).toContain('readonly AUDIT_CRON');
208
- });
209
- it("removes the propose schedule case", () => {
210
- const result = stripRouteFromClassifier(CLASSIFIER_SH, "propose");
211
- expect(result).not.toContain('"$PROPOSE_CRON") route="propose"');
212
- expect(result).toContain('"$AUDIT_CRON") route="audit"');
213
- });
214
- it("removes the AUDIT_CRON constant", () => {
215
- const result = stripRouteFromClassifier(CLASSIFIER_SH, "audit");
216
- expect(result).not.toContain('readonly AUDIT_CRON');
217
- expect(result).toContain('readonly PROPOSE_CRON');
218
- });
219
- it("removes the audit schedule case", () => {
220
- const result = stripRouteFromClassifier(CLASSIFIER_SH, "audit");
221
- expect(result).not.toContain('"$AUDIT_CRON") route="audit"');
222
- expect(result).toContain('"$PROPOSE_CRON") route="propose"');
223
- });
224
- it("removes propose from the dispatch case union", () => {
225
- const result = stripRouteFromClassifier(CLASSIFIER_SH, "propose");
226
- expect(result).not.toContain("audit | propose)");
227
- expect(result).toContain("audit)");
228
- });
229
- });
230
- describe("addRouteExclusion", () => {
231
- it("adds an exclusion assertion for the route", () => {
232
- const result = addRouteExclusion(MATRIX_SH, "propose");
233
- expect(result).toContain("excluded route 'propose'");
234
- expect(result).toContain("propose correctly excluded from work-router.yml");
235
- });
236
- it("does not add the exclusion twice", () => {
237
- const once = addRouteExclusion(MATRIX_SH, "propose");
238
- const twice = addRouteExclusion(once, "propose");
239
- const matchCount = (twice.match(/excluded route 'propose'/g) ?? []).length;
240
- expect(matchCount).toBe(1);
241
- });
242
- });
243
- describe("processRoutes", () => {
244
- it("returns the same map when all routes are selected", () => {
245
- const files = new Map([
246
- ["work-router.yml", ROUTER_YAML],
247
- ["classify-route.sh", CLASSIFIER_SH],
248
- ["verify-route-matrix.sh", MATRIX_SH],
249
- ]);
250
- const result = processRoutes(files, [...routeNames]);
251
- expect(result).toBe(files);
252
- });
253
- it("strips every worker route when no routes are selected", () => {
254
- const files = new Map([
255
- [".github/workflows/work-router.yml", ROUTER_YAML],
256
- [".github/actions/classify-route/classify-route.sh", CLASSIFIER_SH],
257
- [".github/actions/verify-route-matrix/verify-route-matrix.sh", MATRIX_SH],
258
- ]);
259
- const result = processRoutes(files, []);
260
- const router = result.get(".github/workflows/work-router.yml");
261
- expect(router).not.toContain("call-propose");
262
- expect(router).not.toContain("call-audit");
263
- expect(router).not.toContain("- propose");
264
- expect(router).not.toContain("- refine");
265
- const classifier = result.get(".github/actions/classify-route/classify-route.sh");
266
- expect(classifier).toBe(CLASSIFIER_SH);
267
- const matrix = result.get(".github/actions/verify-route-matrix/verify-route-matrix.sh");
268
- expect(matrix).toContain("Route matrix: selected routes valid");
269
- expect(matrix).toContain("for route in refine implement direct apply-review merge-gate audit propose");
270
- });
271
- it("strips propose from all three files when unselected", () => {
272
- const files = new Map([
273
- [".github/workflows/work-router.yml", ROUTER_YAML],
274
- [".github/actions/classify-route/classify-route.sh", CLASSIFIER_SH],
275
- [".github/actions/verify-route-matrix/verify-route-matrix.sh", MATRIX_SH],
276
- ]);
277
- const selectedRoutes = routeNames.filter((r) => r !== "propose");
278
- const result = processRoutes(files, selectedRoutes);
279
- const router = result.get(".github/workflows/work-router.yml");
280
- expect(router).not.toContain("call-propose");
281
- expect(router).not.toContain('cron: "29 7 * * *"');
282
- expect(router).not.toMatch(/^\s+- propose$/m);
283
- const classifier = result.get(".github/actions/classify-route/classify-route.sh");
284
- expect(classifier).toBe(CLASSIFIER_SH);
285
- const matrix = result.get(".github/actions/verify-route-matrix/verify-route-matrix.sh");
286
- expect(matrix).toContain("for route in refine implement direct apply-review merge-gate audit");
287
- expect(matrix).toContain("for route in propose");
288
- });
289
- it("strips audit from all three files when unselected", () => {
290
- const files = new Map([
291
- ["work-router.yml", ROUTER_YAML],
292
- ["classify-route.sh", CLASSIFIER_SH],
293
- ["verify-route-matrix.sh", MATRIX_SH],
294
- ]);
295
- const selectedRoutes = routeNames.filter((r) => r !== "audit");
296
- const result = processRoutes(files, selectedRoutes);
297
- const router = result.get("work-router.yml");
298
- expect(router).not.toContain("call-audit");
299
- expect(router).not.toContain('cron: "17 1 * * 1"');
300
- const classifier = result.get("classify-route.sh");
301
- expect(classifier).toBe(CLASSIFIER_SH);
302
- });
303
- it("processes multiple excluded routes at once", () => {
304
- const files = new Map([
305
- ["work-router.yml", ROUTER_YAML],
306
- ["classify-route.sh", CLASSIFIER_SH],
307
- ["verify-route-matrix.sh", MATRIX_SH],
308
- ]);
309
- const selectedRoutes = ["refine", "implement"];
310
- const result = processRoutes(files, selectedRoutes);
311
- const router = result.get("work-router.yml");
312
- expect(router).toContain("call-refine");
313
- expect(router).toContain("call-implement");
314
- expect(router).not.toContain("call-audit");
315
- expect(router).not.toContain("call-propose");
316
- });
317
- });
318
- describe("excludedWorkerFiles", () => {
319
- it("returns worker files for unselected routes", () => {
320
- const selectedRoutes = ["refine", "implement"];
321
- const excluded = excludedWorkerFiles(selectedRoutes);
322
- expect(excluded.has("agent-refine.md")).toBe(false);
323
- expect(excluded.has("agent-implement.md")).toBe(false);
324
- expect(excluded.has("agent-audit.md")).toBe(true);
325
- expect(excluded.has("agent-propose.md")).toBe(true);
326
- });
327
- it("returns an empty set when all routes are selected", () => {
328
- const excluded = excludedWorkerFiles([...routeNames]);
329
- expect(excluded.size).toBe(0);
330
- });
331
- it("returns all worker files when no routes are selected", () => {
332
- const excluded = excludedWorkerFiles([]);
333
- expect(excluded.size).toBe(7);
334
- expect(excluded.has("agent-refine.md")).toBe(true);
335
- expect(excluded.has("agent-implement.md")).toBe(true);
336
- expect(excluded.has("agent-direct.md")).toBe(true);
337
- expect(excluded.has("agent-apply-review.md")).toBe(true);
338
- expect(excluded.has("agent-merge-gate.md")).toBe(true);
339
- expect(excluded.has("agent-audit.md")).toBe(true);
340
- expect(excluded.has("agent-propose.md")).toBe(true);
341
- });
342
- });
@@ -1,11 +0,0 @@
1
- import type { RepositoryInspection } from "./repository-inspection.js";
2
- export interface StackDefaults {
3
- readonly verifyCommands: string;
4
- readonly repoRulesBase: string;
5
- readonly hasDotnet: boolean;
6
- readonly hasNodeOnly: boolean;
7
- }
8
- export declare function generateStackDefaults(inspection: RepositoryInspection): StackDefaults;
9
- export declare function injectStackEnv(content: string, defaults: StackDefaults): string;
10
- export declare function generateOpencodeCi(baseContent: string, inspection: RepositoryInspection): string;
11
- export declare function generateOpencodeConfig(baseContent: string, inspection: RepositoryInspection): string;
@@ -1,104 +0,0 @@
1
- export function generateStackDefaults(inspection) {
2
- const hasDotnet = inspection.stackHints.solutionFiles.length > 0;
3
- const hasNode = inspection.stackHints.pnpmLockfile;
4
- const hasNodeOnly = hasNode && !hasDotnet;
5
- let verifyCommands;
6
- let repoRulesBase;
7
- if (hasDotnet && hasNode) {
8
- verifyCommands = ".NET: dotnet restore && dotnet build -c Release && dotnet test | Web: pnpm lint && pnpm test && pnpm build";
9
- repoRulesBase = "Full-stack .NET + React/Next.js repository. Follow Clean Architecture layering: API → Application → Domain. Infrastructure implements Application ports. Do not reference EF Core or ASP.NET from Application. Frontend communicates exclusively via HTTP endpoints. Run both .NET and frontend verification.";
10
- }
11
- else if (hasDotnet) {
12
- verifyCommands = "dotnet restore && dotnet build -c Release --no-restore && dotnet test";
13
- repoRulesBase = ".NET repository using Clean Architecture. Follow layering: API → Application → Domain. Infrastructure implements Application ports. Do not reference EF Core or ASP.NET from Application.";
14
- }
15
- else {
16
- verifyCommands = "pnpm verify";
17
- repoRulesBase = "Node.js repository. Follow existing project conventions and import boundaries.";
18
- }
19
- return { verifyCommands, repoRulesBase, hasDotnet, hasNodeOnly };
20
- }
21
- export function injectStackEnv(content, defaults) {
22
- let result = content;
23
- if (result.includes("VERIFY_COMMANDS:")) {
24
- result = result.replace(/ VERIFY_COMMANDS: ".*"/, ` VERIFY_COMMANDS: "${defaults.verifyCommands}"`);
25
- }
26
- else if (/^env:\n/m.test(result)) {
27
- result = result.replace(/^env:\n/m, `env:\n VERIFY_COMMANDS: "${defaults.verifyCommands}"\n`);
28
- }
29
- return result;
30
- }
31
- export function generateOpencodeCi(baseContent, inspection) {
32
- let result = baseContent;
33
- if (inspection.stackHints.solutionFiles.length > 0) {
34
- const solutionPath = inspection.stackHints.solutionFiles[0].replaceAll("\\", "/");
35
- const nugetSteps = ` - name: Cache NuGet packages
36
- uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
37
- with:
38
- path: ~/.nuget/packages
39
- key: nuget-\${{ runner.os }}-\${{ hashFiles('**/*.slnx', '**/Directory.Packages.props') }}
40
- restore-keys: nuget-\${{ runner.os }}-
41
-
42
- - name: Restore .NET dependencies
43
- run: dotnet restore ${solutionPath}
44
- `;
45
- result = insertBeforeMarker(result, nugetSteps, " - name: Install workspace dependencies");
46
- }
47
- if (inspection.stackHints.openSpec) {
48
- const openspecStep = ` - name: Install OpenSpec CLI
49
- run: |
50
- set -euo pipefail
51
- npm install -g "@fission-ai/openspec@1.8.0"
52
- openspec --version
53
- `;
54
- result = insertBeforeMarker(result, openspecStep, " - name: Install workspace dependencies");
55
- }
56
- if (inspection.stackHints.packageJson && !result.includes("--legacy-peer-deps")) {
57
- result = result.replace(` if ! npm install --prefix .opencode; then\n echo "No plugin deps, skipping."\n exit 0\n fi`, ` if ! npm install --prefix .opencode; then\n echo "::warning::Strict npm install failed on a peer conflict. Retrying with --legacy-peer-deps; check .opencode/package.json."\n npm install --prefix .opencode --legacy-peer-deps\n fi`);
58
- }
59
- return result;
60
- }
61
- function insertBeforeMarker(content, steps, marker) {
62
- if (content.includes(marker)) {
63
- return content.replace(marker, steps + marker);
64
- }
65
- return content + "\n" + steps;
66
- }
67
- export function generateOpencodeConfig(baseContent, inspection) {
68
- try {
69
- const config = JSON.parse(baseContent);
70
- const hasDotnet = inspection.stackHints.solutionFiles.length > 0;
71
- if (!hasDotnet && config.lsp !== undefined) {
72
- delete config.lsp;
73
- }
74
- else if (hasDotnet && config.lsp === undefined) {
75
- config.lsp = {
76
- csharp: { disabled: true },
77
- fsharp: { disabled: true },
78
- razor: { disabled: true },
79
- };
80
- }
81
- const agent = config.agent;
82
- if (agent !== undefined) {
83
- const agentEntry = agent["ci-workflow-agent"];
84
- if (agentEntry !== undefined) {
85
- let prompt = agentEntry.prompt ?? "";
86
- if (hasDotnet) {
87
- if (!prompt.includes(".NET guardrails")) {
88
- prompt += "\n\n# .NET guardrails\nFollow Clean Architecture layering: API → Application → Domain. Infrastructure implements Application ports. Application must not reference EF Core or ASP.NET. Use Central Package Management (Directory.Packages.props). Build in Release mode for CI.";
89
- }
90
- }
91
- else {
92
- if (!prompt.includes("Node/React rules")) {
93
- prompt += "\n\n# Node/React rules\nFollow Feature-Sliced Design import boundaries. Use pnpm, never npm or yarn. All user-facing text must be i18n messages. TypeScript strict mode.";
94
- }
95
- }
96
- agentEntry.prompt = prompt;
97
- }
98
- }
99
- return JSON.stringify(config, null, 2) + "\n";
100
- }
101
- catch {
102
- return baseContent;
103
- }
104
- }
@@ -1 +0,0 @@
1
- export {};