@plainconceptsplatform/workflows 0.4.34 → 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 (73) 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 +1 -3
  5. package/loops/actions/classify-route/classify-route.sh +40 -35
  6. package/loops/actions/update-changelog/action.yml +113 -0
  7. package/loops/actions/validate-merge-gate-output/action.yml +40 -40
  8. package/loops/actions/validate-refine-output/validate-refine-output.sh +16 -3
  9. package/loops/actions/validate-review-output/action.yml +35 -35
  10. package/loops/actions/validate-triage-output/action.yml +36 -36
  11. package/loops/actions/verify-route-matrix/verify-route-matrix.sh +91 -36
  12. package/loops/scripts/compile-agent-workflows.mjs +195 -6
  13. package/loops/templates/agentics/agentics-checks.yml +86 -86
  14. package/loops/templates/agentics/agentics-maintenance.yml +121 -121
  15. package/loops/templates/ci/app-ci-dotnet-next.yml +268 -171
  16. package/loops/templates/ci/app-ci-node-monorepo.yml +208 -178
  17. package/loops/templates/issues/bug_report.yml +109 -109
  18. package/loops/templates/issues/feature_request.yml +75 -75
  19. package/loops/templates/opencode/opencode.ci.json +49 -47
  20. package/loops/templates/opencode/opencode.ci.json.md +46 -41
  21. package/loops/templates/release/github-release.yml +30 -30
  22. package/loops/workflows/agent-apply-review.md +465 -443
  23. package/loops/workflows/agent-audit.md +213 -197
  24. package/loops/workflows/agent-implement.md +538 -414
  25. package/loops/workflows/agent-merge-gate.md +730 -584
  26. package/loops/workflows/agent-refine.md +609 -418
  27. package/loops/workflows/agent-release.md +258 -0
  28. package/loops/workflows/agent-triage.md +447 -439
  29. package/loops/workflows/authorize-bot-work.yml +85 -82
  30. package/loops/workflows/shared/opencode-ci.md +206 -197
  31. package/loops/workflows/shared/platform-defaults.md +19 -16
  32. package/loops/workflows/work-router.yml +862 -632
  33. package/package.json +7 -8
  34. package/dist/action-validation.test.d.ts +0 -1
  35. package/dist/action-validation.test.js +0 -84
  36. package/dist/catalog-installation.d.ts +0 -30
  37. package/dist/catalog-installation.js +0 -379
  38. package/dist/catalog-installation.test.d.ts +0 -1
  39. package/dist/catalog-installation.test.js +0 -448
  40. package/dist/catalog-listing.d.ts +0 -13
  41. package/dist/catalog-listing.js +0 -70
  42. package/dist/catalog-listing.test.d.ts +0 -1
  43. package/dist/catalog-listing.test.js +0 -150
  44. package/dist/index.d.ts +0 -2
  45. package/dist/index.js +0 -218
  46. package/dist/index.test.d.ts +0 -1
  47. package/dist/index.test.js +0 -273
  48. package/dist/repository-inspection.d.ts +0 -23
  49. package/dist/repository-inspection.js +0 -113
  50. package/dist/repository-inspection.test.d.ts +0 -1
  51. package/dist/repository-inspection.test.js +0 -77
  52. package/dist/repository-state.d.ts +0 -18
  53. package/dist/repository-state.js +0 -77
  54. package/dist/repository-state.test.d.ts +0 -1
  55. package/dist/repository-state.test.js +0 -96
  56. package/dist/route-processing.d.ts +0 -6
  57. package/dist/route-processing.js +0 -150
  58. package/dist/route-processing.test.d.ts +0 -1
  59. package/dist/route-processing.test.js +0 -350
  60. package/dist/stack-defaults.d.ts +0 -11
  61. package/dist/stack-defaults.js +0 -104
  62. package/dist/stack-defaults.test.d.ts +0 -1
  63. package/dist/stack-defaults.test.js +0 -266
  64. package/dist/tui.d.ts +0 -25
  65. package/dist/tui.js +0 -287
  66. package/dist/tui.test.d.ts +0 -1
  67. package/dist/tui.test.js +0 -246
  68. package/dist/workflow-catalog.d.ts +0 -25
  69. package/dist/workflow-catalog.js +0 -59
  70. package/dist/workflow-catalog.test.d.ts +0 -1
  71. package/dist/workflow-catalog.test.js +0 -29
  72. package/loops/workflows/agent-direct.md +0 -375
  73. package/loops/workflows/agent-propose.md +0 -342
package/dist/tui.test.js DELETED
@@ -1,246 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { applyFilter, cancelSelection, clearFilter, createSelectionState, filterItems, fuzzyMatch, getItemsToInstall, moveCursorDown, moveCursorUp, submitSelection, toggleSelection, } from "./tui.js";
3
- function makeEntry(name, kind, installed = false) {
4
- return {
5
- kind,
6
- name,
7
- description: `${name} description`,
8
- file: `${name}.md`,
9
- installed,
10
- };
11
- }
12
- function makeEntries(installed = []) {
13
- const routes = ["refine", "implement", "direct", "triage", "apply-review", "merge-gate", "audit", "propose"];
14
- const templates = ["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "opencode.ci.json"];
15
- return [
16
- ...routes.map((name) => makeEntry(name, "route", installed.includes(name))),
17
- ...templates.map((name) => makeEntry(name, "template", installed.includes(name))),
18
- ];
19
- }
20
- describe("createSelectionState", () => {
21
- it("returns all items as visible initially", () => {
22
- const entries = makeEntries();
23
- const state = createSelectionState(entries);
24
- expect(state.allItems).toHaveLength(13);
25
- expect(state.visibleItems).toHaveLength(13);
26
- });
27
- it("pre-selects installed items", () => {
28
- const entries = makeEntries(["refine", "agentics-checks"]);
29
- const state = createSelectionState(entries);
30
- expect(state.selected.has("refine")).toBe(true);
31
- expect(state.selected.has("agentics-checks")).toBe(true);
32
- expect(state.selected.has("implement")).toBe(false);
33
- });
34
- it("starts with no filter and cursor at 0", () => {
35
- const state = createSelectionState(makeEntries());
36
- expect(state.filter).toBe("");
37
- expect(state.cursor).toBe(0);
38
- expect(state.status).toBe("selecting");
39
- });
40
- it("pre-selects nothing when nothing is installed", () => {
41
- const state = createSelectionState(makeEntries());
42
- expect(state.selected.size).toBe(0);
43
- });
44
- });
45
- describe("fuzzyMatch", () => {
46
- it("returns true for empty query", () => {
47
- expect(fuzzyMatch("anything", "")).toBe(true);
48
- });
49
- it("matches exact string", () => {
50
- expect(fuzzyMatch("refine", "refine")).toBe(true);
51
- });
52
- it("matches subsequence characters", () => {
53
- expect(fuzzyMatch("refine", "rfe")).toBe(true);
54
- });
55
- it("matches non-contiguous characters", () => {
56
- expect(fuzzyMatch("app-ci-dotnet-next", "acdn")).toBe(true);
57
- });
58
- it("is case-insensitive", () => {
59
- expect(fuzzyMatch("Refine", "REFINE")).toBe(true);
60
- expect(fuzzyMatch("REFINE", "refine")).toBe(true);
61
- });
62
- it("returns false when characters are not in order", () => {
63
- expect(fuzzyMatch("refine", "efinr")).toBe(false);
64
- });
65
- it("returns false when query has characters not in text", () => {
66
- expect(fuzzyMatch("refine", "xyz")).toBe(false);
67
- });
68
- it("matches against full name + description with combined filter", () => {
69
- const text = "agentics-checks Agentics checks: verifies generated agent lockfiles";
70
- expect(fuzzyMatch(text, "acch")).toBe(true);
71
- });
72
- });
73
- describe("filterItems", () => {
74
- const entries = makeEntries();
75
- it("returns all items when filter is empty", () => {
76
- expect(filterItems(entries, "")).toHaveLength(13);
77
- });
78
- it("filters by name", () => {
79
- const result = filterItems(entries, "refine");
80
- expect(result).toHaveLength(1);
81
- expect(result[0].name).toBe("refine");
82
- });
83
- it("filters by description", () => {
84
- const result = filterItems(entries, "description");
85
- expect(result).toHaveLength(13);
86
- });
87
- it("filters by fuzzy subsequence over name and description", () => {
88
- const result = filterItems(entries, "acdn");
89
- const names = result.map((e) => e.name);
90
- expect(names).toContain("app-ci-dotnet-next");
91
- });
92
- it("returns empty when nothing matches", () => {
93
- expect(filterItems(entries, "zzzzz")).toHaveLength(0);
94
- });
95
- });
96
- describe("applyFilter", () => {
97
- it("updates the filter and visibleItems", () => {
98
- const state = createSelectionState(makeEntries());
99
- const next = applyFilter(state, "refine");
100
- expect(next.filter).toBe("refine");
101
- expect(next.visibleItems.length).toBe(1);
102
- expect(next.visibleItems[0].name).toBe("refine");
103
- });
104
- it("clamps cursor when result shrinks", () => {
105
- const entries = makeEntries();
106
- let state = createSelectionState(entries);
107
- state = { ...state, cursor: 10 };
108
- state = applyFilter(state, "ci");
109
- expect(state.cursor).toBeLessThanOrEqual(Math.max(0, state.visibleItems.length - 1));
110
- expect(state.cursor).toBeLessThan(state.visibleItems.length);
111
- });
112
- it("sets cursor to 0 when no results match", () => {
113
- const state = applyFilter(createSelectionState(makeEntries()), "zzz");
114
- expect(state.visibleItems).toHaveLength(0);
115
- expect(state.cursor).toBe(0);
116
- });
117
- it("preserves selected set when filtering", () => {
118
- const entries = makeEntries(["refine"]);
119
- const state = applyFilter(createSelectionState(entries), "ref");
120
- expect(state.selected.has("refine")).toBe(true);
121
- });
122
- });
123
- describe("clearFilter", () => {
124
- it("resets filter to empty and restores all items", () => {
125
- let state = applyFilter(createSelectionState(makeEntries()), "ref");
126
- state = clearFilter(state);
127
- expect(state.filter).toBe("");
128
- expect(state.visibleItems).toHaveLength(13);
129
- });
130
- });
131
- describe("moveCursorUp / moveCursorDown", () => {
132
- it("moves cursor down by one", () => {
133
- const state = createSelectionState(makeEntries());
134
- const next = moveCursorDown(state);
135
- expect(next.cursor).toBe(1);
136
- });
137
- it("wraps cursor to top from bottom", () => {
138
- const entries = makeEntries();
139
- let state = createSelectionState(entries);
140
- state = { ...state, cursor: entries.length - 1 };
141
- state = moveCursorDown(state);
142
- expect(state.cursor).toBe(0);
143
- });
144
- it("moves cursor up by one", () => {
145
- const entries = makeEntries();
146
- let state = createSelectionState(entries);
147
- state = { ...state, cursor: 3 };
148
- state = moveCursorUp(state);
149
- expect(state.cursor).toBe(2);
150
- });
151
- it("wraps cursor to bottom from top", () => {
152
- const state = moveCursorUp(createSelectionState(makeEntries()));
153
- expect(state.cursor).toBe(12);
154
- });
155
- it("does not move when list is empty", () => {
156
- const entries = [];
157
- const state = createSelectionState(entries);
158
- expect(moveCursorUp(state).cursor).toBe(0);
159
- expect(moveCursorDown(state).cursor).toBe(0);
160
- });
161
- it("navigates within filtered results", () => {
162
- let state = createSelectionState(makeEntries());
163
- state = applyFilter(state, "ci");
164
- state = moveCursorDown(state);
165
- expect(state.cursor).toBe(1);
166
- expect(state.cursor).toBeLessThan(state.visibleItems.length);
167
- });
168
- });
169
- describe("toggleSelection", () => {
170
- it("selects an unselected item at cursor", () => {
171
- const state = createSelectionState(makeEntries());
172
- expect(state.selected.has("refine")).toBe(false);
173
- const next = toggleSelection(state);
174
- expect(next.selected.has("refine")).toBe(true);
175
- });
176
- it("deselects a selected item at cursor", () => {
177
- const entries = makeEntries(["refine"]);
178
- const state = createSelectionState(entries);
179
- expect(state.selected.has("refine")).toBe(true);
180
- const next = toggleSelection(state);
181
- expect(next.selected.has("refine")).toBe(false);
182
- });
183
- it("toggles the item at cursor position within filtered list", () => {
184
- let state = createSelectionState(makeEntries());
185
- state = applyFilter(state, "ci");
186
- state = moveCursorDown(state);
187
- const entryAtCursor = state.visibleItems[state.cursor];
188
- const next = toggleSelection(state);
189
- expect(next.selected.has(entryAtCursor.name)).toBe(true);
190
- });
191
- it("does nothing when list is empty", () => {
192
- const state = createSelectionState([]);
193
- const next = toggleSelection(state);
194
- expect(next.selected.size).toBe(0);
195
- });
196
- it("does not change other selections", () => {
197
- const entries = makeEntries(["refine", "audit"]);
198
- const state = createSelectionState(entries);
199
- const next = toggleSelection(state);
200
- expect(next.selected.has("refine")).toBe(false);
201
- expect(next.selected.has("audit")).toBe(true);
202
- });
203
- });
204
- describe("submitSelection / cancelSelection", () => {
205
- it("sets status to submitting", () => {
206
- const state = submitSelection(createSelectionState(makeEntries()));
207
- expect(state.status).toBe("submitting");
208
- });
209
- it("sets status to cancelled", () => {
210
- const state = cancelSelection(createSelectionState(makeEntries()));
211
- expect(state.status).toBe("cancelled");
212
- });
213
- });
214
- describe("getItemsToInstall", () => {
215
- it("returns empty when nothing is selected", () => {
216
- const state = createSelectionState(makeEntries());
217
- expect(getItemsToInstall(state)).toHaveLength(0);
218
- });
219
- it("returns only selected items that are not already installed", () => {
220
- const entries = makeEntries(["refine"]);
221
- let state = createSelectionState(entries);
222
- state = { ...state, cursor: 1 };
223
- state = toggleSelection(state);
224
- const items = getItemsToInstall(state);
225
- const names = items.map((e) => e.name);
226
- expect(names).toContain("implement");
227
- expect(names).not.toContain("refine");
228
- });
229
- it("returns already-installed items when force is true", () => {
230
- const entries = makeEntries(["refine"]);
231
- let state = createSelectionState(entries);
232
- const items = getItemsToInstall(state, true);
233
- const names = items.map((e) => e.name);
234
- expect(names).toContain("refine");
235
- });
236
- it("includes both routes and templates", () => {
237
- let state = createSelectionState(makeEntries());
238
- state = toggleSelection(state);
239
- state = { ...state, cursor: 8 };
240
- state = toggleSelection(state);
241
- const items = getItemsToInstall(state);
242
- const kinds = items.map((e) => e.kind);
243
- expect(kinds).toContain("route");
244
- expect(kinds).toContain("template");
245
- });
246
- });
@@ -1,25 +0,0 @@
1
- export declare const routeNames: readonly ["refine", "implement", "direct", "triage", "apply-review", "merge-gate", "audit", "propose"];
2
- export type RouteName = (typeof routeNames)[number];
3
- export interface WorkflowRoute {
4
- readonly name: RouteName;
5
- readonly worker: string;
6
- readonly description: string;
7
- readonly defaultEnabled: boolean;
8
- }
9
- export declare const workflowRoutes: readonly WorkflowRoute[];
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", "opencode.ci.json"];
11
- export interface MandatoryFile {
12
- readonly source: string;
13
- readonly target: string;
14
- }
15
- export declare const mandatoryFiles: readonly MandatoryFile[];
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", "bug-report", "feature-request", "github-release", "opencode.ci.json", "visual-evidence"];
18
- export type TemplateName = (typeof templateNames)[number];
19
- export interface CatalogTemplate {
20
- readonly name: TemplateName;
21
- readonly file: string;
22
- readonly description: string;
23
- readonly target?: string;
24
- }
25
- export declare const catalogTemplates: readonly CatalogTemplate[];
@@ -1,59 +0,0 @@
1
- export const routeNames = [
2
- "refine",
3
- "implement",
4
- "direct",
5
- "triage",
6
- "apply-review",
7
- "merge-gate",
8
- "audit",
9
- "propose",
10
- ];
11
- export const workflowRoutes = [
12
- { 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 },
13
- { 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 },
14
- { 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 },
15
- { name: "triage", worker: "agent-triage.md", description: "Triages issues opened by outside collaborators: runs 10 checks (template, security, size, danger, duplicates, clarity, reproducibility, acceptance, cross-cutting). Loops up to 3 rounds. Passes to refine or blocks.", defaultEnabled: true },
16
- { 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 },
17
- { 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 },
18
- { 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 },
19
- { 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 },
20
- ];
21
- export const packageOwnedTargets = [
22
- ".github/actions",
23
- ".github/workflows/agent-*.md",
24
- ".github/workflows/shared/platform-defaults.md",
25
- ".github/workflows/shared/opencode-ci.md",
26
- ".github/workflows/work-router.yml",
27
- "scripts/compile-agent-workflows.mjs",
28
- "opencode.ci.json",
29
- ];
30
- export const mandatoryFiles = [
31
- { source: "templates/opencode/opencode.ci.json", target: "opencode.ci.json" },
32
- { source: "scripts/compile-agent-workflows.mjs", target: "scripts/compile-agent-workflows.mjs" },
33
- ];
34
- export const generatedConsumerTargets = [
35
- ".github/workflows/agent-*.lock.yml",
36
- ".github/aw/actions-lock.json",
37
- ];
38
- export const templateNames = [
39
- "agentics-checks",
40
- "agentics-maintenance",
41
- "app-ci-dotnet-next",
42
- "app-ci-node-monorepo",
43
- "bug-report",
44
- "feature-request",
45
- "github-release",
46
- "opencode.ci.json",
47
- "visual-evidence",
48
- ];
49
- export const catalogTemplates = [
50
- { name: "agentics-checks", file: "agentics-checks.yml", description: "Agentics checks: verifies generated agent lockfiles, actionlint, and compile on PRs touching workflow files." },
51
- { name: "agentics-maintenance", file: "agentics-maintenance.yml", description: "Agentic maintenance: scheduled daily maintenance workflow for keeping workflows and actions up to date." },
52
- { 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." },
53
- { 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." },
54
- { name: "bug-report", file: "bug_report.yml", description: "Bug report issue template: what happened, repro steps, expected behavior, acceptance criteria, environment, logs.", target: ".github/ISSUE_TEMPLATE/bug_report.yml" },
55
- { name: "feature-request", file: "feature_request.yml", description: "Feature request issue template scoped to small, well-scoped improvements (Small/Medium only; large work belongs in a planning issue).", target: ".github/ISSUE_TEMPLATE/feature_request.yml" },
56
- { name: "github-release", file: "github-release.yml", description: "Publishes or updates a GitHub Release with generated notes whenever a v* tag is pushed." },
57
- { 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." },
58
- { name: "visual-evidence", file: "visual-evidence.yml", description: "Visual evidence: captures screenshots of UI changes on bot-authored PRs by reading the capturePlan left by the agent in evidence.json and executing it on a runner with Docker and Chrome access." },
59
- ];
@@ -1 +0,0 @@
1
- export {};
@@ -1,29 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { catalogTemplates, generatedConsumerTargets, packageOwnedTargets, routeNames, templateNames, workflowRoutes, } from "./workflow-catalog.js";
3
- describe("workflow catalog", () => {
4
- it("assigns one worker to each route", () => {
5
- expect(workflowRoutes.map((route) => route.name)).toEqual(routeNames);
6
- expect(new Set(workflowRoutes.map((route) => route.worker)).size).toBe(workflowRoutes.length);
7
- });
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", () => {
14
- const packageTargets = new Set(packageOwnedTargets);
15
- for (const target of generatedConsumerTargets) {
16
- expect(packageTargets.has(target)).toBe(false);
17
- }
18
- });
19
- it("lists supported optional templates", () => {
20
- expect(templateNames).toEqual(["agentics-checks", "agentics-maintenance", "app-ci-dotnet-next", "app-ci-node-monorepo", "bug-report", "feature-request", "github-release", "opencode.ci.json", "visual-evidence"]);
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
- });
29
- });