@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
package/package.json CHANGED
@@ -1,43 +1,43 @@
1
- {
2
- "name": "@plainconceptsplatform/workflows",
3
- "version": "0.4.33",
4
- "description": "Install and update Platform GitHub agentic workflows.",
5
- "keywords": [
6
- "github-actions",
7
- "agentic-workflows",
8
- "plain-concepts"
9
- ],
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/PlainConceptsPlatform/Agentic-Workflows.git"
13
- },
14
- "bugs": {
15
- "url": "https://github.com/PlainConceptsPlatform/Agentic-Workflows/issues"
16
- },
17
- "homepage": "https://github.com/PlainConceptsPlatform/Agentic-Workflows#readme",
18
- "license": "UNLICENSED",
19
- "type": "module",
20
- "bin": {
21
- "workflows": "./dist/index.js"
22
- },
23
- "files": [
24
- "dist",
25
- "loops",
26
- "README.md"
27
- ],
28
- "scripts": {
29
- "build": "tsc --project tsconfig.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
- },
35
- "engines": {
36
- "node": ">=20.19"
37
- },
38
- "devDependencies": {
39
- "@types/node": "^22.0.0",
40
- "typescript": "^5.8.0",
41
- "vitest": "^3.0.0"
42
- }
1
+ {
2
+ "name": "@plainconceptsplatform/workflows",
3
+ "version": "0.5.1",
4
+ "description": "Install and update Platform GitHub agentic workflows.",
5
+ "keywords": [
6
+ "github-actions",
7
+ "agentic-workflows",
8
+ "plain-concepts"
9
+ ],
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/PlainConceptsPlatform/Agentic-Workflows.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/PlainConceptsPlatform/Agentic-Workflows/issues"
16
+ },
17
+ "homepage": "https://github.com/PlainConceptsPlatform/Agentic-Workflows#readme",
18
+ "license": "UNLICENSED",
19
+ "type": "module",
20
+ "bin": {
21
+ "workflows": "./dist/index.js"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "loops",
26
+ "README.md"
27
+ ],
28
+ "engines": {
29
+ "node": ">=20.19"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.0.0",
33
+ "typescript": "^5.8.0",
34
+ "vitest": "^3.0.0",
35
+ "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
  }
@@ -1,30 +0,0 @@
1
- import { type RouteName, type TemplateName } from "./workflow-catalog.js";
2
- import type { RepositoryInspection } from "./repository-inspection.js";
3
- export interface CatalogInstallResult {
4
- readonly installed: readonly string[];
5
- readonly conflicts: readonly string[];
6
- }
7
- export interface CatalogInstallOptions {
8
- readonly force?: boolean;
9
- readonly sourcePath?: string;
10
- readonly selectedRoutes?: readonly RouteName[];
11
- readonly inspection?: RepositoryInspection;
12
- readonly compile?: (repositoryPath: string) => Promise<void>;
13
- }
14
- interface CatalogFile {
15
- readonly source: string;
16
- readonly target: string;
17
- readonly managed: boolean;
18
- }
19
- export declare function mandatoryFileSpecs(sourcePath: string): CatalogFile[];
20
- export declare function catalogSourcePath(modulePath?: string): string;
21
- export declare function installCatalog(repositoryPath: string, options?: CatalogInstallOptions): Promise<CatalogInstallResult>;
22
- export declare function installTemplate(repositoryPath: string, template: TemplateName, options?: CatalogInstallOptions): Promise<CatalogInstallResult>;
23
- export declare function installMandatoryFiles(repositoryPath: string, options?: CatalogInstallOptions): Promise<CatalogInstallResult>;
24
- export declare function installedRoutes(repositoryPath: string): Promise<RouteName[]>;
25
- export declare function removeRouteFiles(repositoryPath: string, routes: readonly RouteName[]): Promise<string[]>;
26
- export declare function isTemplateName(value: string): value is TemplateName;
27
- export declare function ensurePreCommitHook(repositoryPath: string): Promise<void>;
28
- export declare function runCompileIfAvailable(repositoryPath: string): Promise<void>;
29
- export declare function exists(path: string): Promise<boolean>;
30
- export {};
@@ -1,352 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { access, copyFile, cp, mkdtemp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
3
- import { constants } from "node:fs";
4
- import { dirname, join, relative, resolve } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { promisify } from "node:util";
7
- import { catalogTemplates, mandatoryFiles, routeNames, templateNames, workflowRoutes } from "./workflow-catalog.js";
8
- import { processRoutes, excludedWorkerFiles } from "./route-processing.js";
9
- import { generateOpencodeCi, generateOpencodeConfig, generateStackDefaults, injectStackEnv } from "./stack-defaults.js";
10
- const execFileAsync = promisify(execFile);
11
- const sourceMappings = [
12
- ["actions", ".github/actions"],
13
- ["workflows", ".github/workflows"],
14
- ["scripts", "scripts"],
15
- ];
16
- export function mandatoryFileSpecs(sourcePath) {
17
- return mandatoryFiles.map((spec) => ({
18
- source: join(sourcePath, spec.source),
19
- target: spec.target,
20
- managed: true,
21
- }));
22
- }
23
- export function catalogSourcePath(modulePath = fileURLToPath(import.meta.url)) {
24
- return resolve(dirname(modulePath), "..", "loops");
25
- }
26
- export async function installCatalog(repositoryPath, options = {}) {
27
- const sourcePath = options.sourcePath ?? catalogSourcePath();
28
- const selectedRoutes = options.selectedRoutes ?? routeNames;
29
- const allFiles = [...await catalogFiles(sourcePath), ...mandatoryFileSpecs(sourcePath)];
30
- const deduplicated = allFiles.filter((file, index) => allFiles.findIndex((f) => f.target === file.target) === index).sort((left, right) => left.target.localeCompare(right.target));
31
- const excluded = excludedWorkerFiles(selectedRoutes);
32
- const filtered = deduplicated.filter((file) => {
33
- const fileName = file.target.split("/").pop() ?? "";
34
- return !excluded.has(fileName);
35
- });
36
- const fileContents = new Map();
37
- for (const file of filtered) {
38
- fileContents.set(file.target, await readFile(file.source, "utf8"));
39
- }
40
- let processedContents = processRoutes(fileContents, selectedRoutes);
41
- if (options.inspection !== undefined) {
42
- const defaults = generateStackDefaults(options.inspection);
43
- processedContents = injectStackIntoWorkers(processedContents, defaults);
44
- processedContents = transformOpencodeFiles(processedContents, options.inspection);
45
- }
46
- processedContents = await preserveConsumerWorkerEnv(repositoryPath, processedContents);
47
- const updates = [...processedContents.entries()]
48
- .filter(([target]) => filtered.find((file) => file.target === target)?.managed ?? true)
49
- .map(([target, content]) => ({ target, content }));
50
- const conflicts = await conflictingTargets(repositoryPath, updates);
51
- if (conflicts.length > 0 && !options.force)
52
- return { installed: [], conflicts };
53
- const stagedLocks = await validateStagedCatalog(repositoryPath, updates, options.compile);
54
- await applyTransaction(repositoryPath, [...updates, ...stagedLocks, await preCommitHookUpdate(repositoryPath)]);
55
- return { installed: [...processedContents.keys()].sort(), conflicts };
56
- }
57
- function injectStackIntoWorkers(files, defaults) {
58
- const result = new Map(files);
59
- for (const [key, content] of result) {
60
- if (key.startsWith(".github/workflows/agent-") && key.endsWith(".md")) {
61
- result.set(key, injectStackEnv(content, defaults));
62
- }
63
- }
64
- return result;
65
- }
66
- async function preserveConsumerWorkerEnv(repositoryPath, files) {
67
- const result = new Map(files);
68
- for (const [target, content] of result) {
69
- if (!target.startsWith(".github/workflows/agent-") || !target.endsWith(".md"))
70
- continue;
71
- const existingPath = join(repositoryPath, target);
72
- if (!await exists(existingPath))
73
- continue;
74
- result.set(target, mergeWorkerEnv(content, await readFile(existingPath, "utf8")));
75
- }
76
- return result;
77
- }
78
- function mergeWorkerEnv(packageContent, consumerContent) {
79
- const consumerEnv = workerEnvValues(consumerContent);
80
- let result = packageContent.replace(/^ ([A-Z][A-Z0-9_]*): .+$/gm, (line, key) => consumerEnv.has(key) ? ` ${key}: ${consumerEnv.get(key)}` : line);
81
- const endpoint = engineEndpoint(consumerContent);
82
- if (endpoint !== undefined) {
83
- result = result.replace(/^ OPENAI_BASE_URL: .+$/m, ` OPENAI_BASE_URL: ${endpoint}`);
84
- }
85
- return result;
86
- }
87
- function workerEnvValues(content) {
88
- const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/m.exec(content)?.[1];
89
- const envBlock = frontmatter === undefined ? undefined : /^env:\r?\n((?: .*\r?\n?)*)/m.exec(frontmatter)?.[1];
90
- const values = new Map();
91
- if (envBlock === undefined)
92
- return values;
93
- for (const line of envBlock.split(/\r?\n/)) {
94
- const match = /^ ([A-Z][A-Z0-9_]*): (.+)$/.exec(line);
95
- if (match !== null)
96
- values.set(match[1], match[2]);
97
- }
98
- return values;
99
- }
100
- function engineEndpoint(content) {
101
- return /^ OPENAI_BASE_URL: (.+)$/m.exec(content)?.[1];
102
- }
103
- function transformOpencodeFiles(files, inspection) {
104
- const result = new Map(files);
105
- for (const [key, content] of result) {
106
- if (key.endsWith("opencode-ci.md")) {
107
- result.set(key, generateOpencodeCi(content, inspection));
108
- }
109
- else if (key === "opencode.ci.json") {
110
- result.set(key, generateOpencodeConfig(content, inspection));
111
- }
112
- }
113
- return result;
114
- }
115
- export async function installTemplate(repositoryPath, template, options = {}) {
116
- const sourcePath = options.sourcePath ?? catalogSourcePath();
117
- const meta = catalogTemplateMeta(template);
118
- const source = join(sourcePath, "templates", meta.directory, meta.file);
119
- const target = meta.target;
120
- const destination = join(repositoryPath, target);
121
- const conflicts = await exists(destination) && !(await filesMatch(source, destination)) ? [target] : [];
122
- if (conflicts.length > 0 && !options.force)
123
- return { installed: [], conflicts };
124
- await mkdir(dirname(destination), { recursive: true });
125
- await copyFile(source, destination);
126
- if (options.inspection !== undefined && template === "opencode.ci.json") {
127
- const baseContent = await readFile(source, "utf8");
128
- const transformed = generateOpencodeConfig(baseContent, options.inspection);
129
- await writeFile(destination, transformed, "utf8");
130
- }
131
- try {
132
- await runCompileIfAvailable(repositoryPath);
133
- }
134
- catch {
135
- // compile failure is non-fatal
136
- }
137
- return { installed: [target], conflicts };
138
- }
139
- export async function installMandatoryFiles(repositoryPath, options = {}) {
140
- const sourcePath = options.sourcePath ?? catalogSourcePath();
141
- const files = mandatoryFileSpecs(sourcePath).sort((left, right) => left.target.localeCompare(right.target));
142
- const conflicts = (await Promise.all(files.map(async (file) => {
143
- const destination = join(repositoryPath, file.target);
144
- return await exists(destination) && !(await filesMatch(file.source, destination)) ? file.target : undefined;
145
- }))).filter((file) => file !== undefined);
146
- if (conflicts.length > 0 && !options.force)
147
- return { installed: [], conflicts };
148
- const updates = await Promise.all(files.map(async (file) => ({ target: file.target, content: await readFile(file.source, "utf8") })));
149
- await applyTransaction(repositoryPath, [...updates, await preCommitHookUpdate(repositoryPath)]);
150
- return { installed: files.map((file) => file.target), conflicts };
151
- }
152
- export async function installedRoutes(repositoryPath) {
153
- const found = await Promise.all(workflowRoutes.map(async (route) => (await exists(join(repositoryPath, ".github", "workflows", route.worker))) ? route.name : undefined));
154
- return found.filter((name) => name !== undefined);
155
- }
156
- export async function removeRouteFiles(repositoryPath, routes) {
157
- const workerByRoute = new Map(workflowRoutes.map((route) => [route.name, route.worker]));
158
- const removed = [];
159
- for (const route of routes) {
160
- const worker = workerByRoute.get(route);
161
- if (worker === undefined)
162
- continue;
163
- const lock = worker.replace(/\.md$/, ".lock.yml");
164
- for (const file of [worker, lock]) {
165
- const destination = join(repositoryPath, ".github", "workflows", file);
166
- if (await exists(destination)) {
167
- await rm(destination, { force: true });
168
- removed.push(`.github/workflows/${file}`);
169
- }
170
- }
171
- }
172
- return removed.sort();
173
- }
174
- export function isTemplateName(value) {
175
- return templateNames.includes(value);
176
- }
177
- export async function ensurePreCommitHook(repositoryPath) {
178
- const update = await preCommitHookUpdate(repositoryPath);
179
- await applyTransaction(repositoryPath, [update]);
180
- }
181
- async function preCommitHookUpdate(repositoryPath) {
182
- const target = ".husky/pre-commit";
183
- const hookPath = join(repositoryPath, target);
184
- const compileLine = "node scripts/compile-agent-workflows.mjs";
185
- const stageLine = "git add -- .github/workflows/*.lock.yml";
186
- const actionLockLine = "[ ! -f .github/actions/actions-lock.json ] || git add -- .github/actions/actions-lock.json";
187
- const managedLines = `if git diff --cached --name-only -- .github | grep -q .; then\n ${compileLine}\n ${stageLine}\n ${actionLockLine}\nfi\n`;
188
- if (!await exists(hookPath)) {
189
- return { target, content: managedLines };
190
- }
191
- const content = (await readFile(hookPath, "utf8"))
192
- .replace("pnpm exec if git diff --cached --name-only -- .github | grep -q .; then", "if git diff --cached --name-only -- .github | grep -q .; then");
193
- if (content.includes("compile-agent-workflows")) {
194
- const legacyLines = `${compileLine}\n${stageLine}\n${actionLockLine}\n`;
195
- if (content.includes(managedLines))
196
- return { target, content };
197
- if (content.includes(legacyLines))
198
- return { target, content: content.replace(legacyLines, managedLines) };
199
- const suffix = content.endsWith("\n") || content === "" ? "" : "\n";
200
- return { target, content: `${content}${suffix}${managedLines}` };
201
- }
202
- return { target, content: content.endsWith("\n") || content === ""
203
- ? `${content}${managedLines}`
204
- : `${content}\n${managedLines}` };
205
- }
206
- export async function runCompileIfAvailable(repositoryPath) {
207
- const script = join(repositoryPath, "scripts", "compile-agent-workflows.mjs");
208
- if (await exists(script)) {
209
- await execFileAsync("node", [script, "--force"], { cwd: repositoryPath });
210
- }
211
- }
212
- async function conflictingTargets(repositoryPath, updates) {
213
- const conflicts = (await Promise.all(updates.map(async ({ target, content }) => {
214
- const destination = join(repositoryPath, target);
215
- if (!await exists(destination))
216
- return undefined;
217
- return (await readFile(destination, "utf8")) === content ? undefined : target;
218
- }))).filter((target) => target !== undefined);
219
- return [...new Set(conflicts)].sort();
220
- }
221
- async function validateStagedCatalog(repositoryPath, updates, compileOverride) {
222
- const temporaryRoot = join(repositoryPath, ".opencode", ".tmp");
223
- await mkdir(temporaryRoot, { recursive: true });
224
- const stagingPath = await mkdtemp(join(temporaryRoot, "workflows-"));
225
- try {
226
- await copyCompilationInputs(repositoryPath, stagingPath);
227
- await writeUpdates(stagingPath, updates);
228
- await initializeStagingRepository(stagingPath);
229
- const compiler = compileOverride ?? await packageCompiler(stagingPath);
230
- if (compiler === undefined)
231
- return [];
232
- await compiler(stagingPath);
233
- return await generatedFiles(stagingPath);
234
- }
235
- finally {
236
- await rm(stagingPath, { force: true, recursive: true });
237
- }
238
- }
239
- async function copyCompilationInputs(repositoryPath, stagingPath) {
240
- const githubPath = join(repositoryPath, ".github");
241
- if (await exists(githubPath))
242
- await cp(githubPath, join(stagingPath, ".github"), { recursive: true });
243
- }
244
- async function initializeStagingRepository(stagingPath) {
245
- await execFileAsync("git", ["init", "--quiet"], { cwd: stagingPath, windowsHide: true });
246
- }
247
- async function packageCompiler(repositoryPath) {
248
- const scriptPath = join(repositoryPath, "scripts", "compile-agent-workflows.mjs");
249
- if (!await exists(scriptPath))
250
- return undefined;
251
- const content = await readFile(scriptPath, "utf8");
252
- return content.includes("gh aw compile") ? runCompileIfAvailable : undefined;
253
- }
254
- async function generatedFiles(repositoryPath) {
255
- const workflowPath = join(repositoryPath, ".github", "workflows");
256
- const updates = [];
257
- if (await exists(workflowPath)) {
258
- for (const file of await filesIn(workflowPath)) {
259
- if (!file.endsWith(".lock.yml"))
260
- continue;
261
- updates.push({ target: `.github/workflows/${file.replaceAll("\\", "/")}`, content: await readFile(join(workflowPath, file), "utf8") });
262
- }
263
- }
264
- const actionsLock = join(repositoryPath, ".github", "actions", "actions-lock.json");
265
- if (await exists(actionsLock)) {
266
- updates.push({ target: ".github/actions/actions-lock.json", content: await readFile(actionsLock, "utf8") });
267
- }
268
- return updates.sort((left, right) => left.target.localeCompare(right.target));
269
- }
270
- async function applyTransaction(repositoryPath, updates) {
271
- const uniqueUpdates = [...new Map(updates.map((update) => [update.target, update])).values()];
272
- const rollback = await Promise.all(uniqueUpdates.map(async ({ target }) => {
273
- const path = join(repositoryPath, target);
274
- return { target, existed: await exists(path), content: await exists(path) ? await readFile(path, "utf8") : undefined };
275
- }));
276
- try {
277
- await writeUpdates(repositoryPath, uniqueUpdates);
278
- }
279
- catch (error) {
280
- await Promise.all(rollback.map(async ({ target, existed, content }) => {
281
- if (existed && content !== undefined) {
282
- await writeUpdates(repositoryPath, [{ target, content }]);
283
- }
284
- else {
285
- await rm(join(repositoryPath, target), { force: true });
286
- }
287
- }));
288
- throw error;
289
- }
290
- }
291
- async function writeUpdates(repositoryPath, updates) {
292
- await Promise.all(updates.map(async ({ target, content }) => {
293
- const destination = join(repositoryPath, target);
294
- await mkdir(dirname(destination), { recursive: true });
295
- await writeFile(destination, content, "utf8");
296
- }));
297
- }
298
- function catalogTemplateMeta(template) {
299
- const entry = catalogTemplates.find((item) => item.name === template);
300
- if (entry === undefined)
301
- throw new Error(`Unknown template: ${template}`);
302
- const directory = template.startsWith("opencode") ? "opencode" : template.startsWith("app-ci-") ? "ci" : template === "github-release" ? "release" : template === "visual-evidence" ? "visual-evidence" : template === "bug-report" || template === "feature-request" ? "issues" : "agentics";
303
- const isWorkflow = entry.file.endsWith(".yml");
304
- const inferredTarget = template === "app-ci-dotnet-next"
305
- ? ".github/workflows/app-ci.yml"
306
- : isWorkflow ? `.github/workflows/${entry.file}` : entry.file;
307
- const target = entry.target ?? inferredTarget;
308
- return { directory, file: entry.file, target };
309
- }
310
- async function catalogFiles(sourcePath) {
311
- const files = [];
312
- for (const [sourceDirectory, targetDirectory] of sourceMappings) {
313
- for (const file of await filesIn(join(sourcePath, sourceDirectory))) {
314
- if (isGeneratedFile(file))
315
- continue;
316
- files.push({
317
- source: join(sourcePath, sourceDirectory, file),
318
- target: `${targetDirectory}/${file.replaceAll("\\", "/")}`,
319
- managed: true,
320
- });
321
- }
322
- }
323
- return files.sort((left, right) => left.target.localeCompare(right.target));
324
- }
325
- function isGeneratedFile(file) {
326
- const normalized = file.replaceAll("\\", "/");
327
- return normalized.endsWith(".lock.yml") || normalized.endsWith("actions-lock.json");
328
- }
329
- async function filesIn(path) {
330
- const entries = await readdir(path, { recursive: true, withFileTypes: true });
331
- return entries
332
- .filter((entry) => entry.isFile())
333
- .map((entry) => entry.parentPath === undefined ? entry.name : relative(path, join(entry.parentPath, entry.name)));
334
- }
335
- async function filesMatch(source, destination) {
336
- try {
337
- const [sourceContent, destinationContent] = await Promise.all([readFile(source), readFile(destination)]);
338
- return sourceContent.equals(destinationContent);
339
- }
340
- catch {
341
- return false;
342
- }
343
- }
344
- export async function exists(path) {
345
- try {
346
- await access(path, constants.F_OK);
347
- return true;
348
- }
349
- catch {
350
- return false;
351
- }
352
- }
@@ -1 +0,0 @@
1
- export {};