@sweny-ai/core 0.1.42 → 0.1.44

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,199 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { readFileSync, existsSync } from "node:fs";
3
+ import { resolve, relative } from "node:path";
4
+ import { execSync } from "node:child_process";
5
+ /**
6
+ * Version consistency tests — ensure the v5 migration is complete
7
+ * and prevent regressions when the action version changes.
8
+ *
9
+ * These tests validate that:
10
+ * 1. No stale v4 action references remain in source or docs
11
+ * 2. The README uses dark-mode-safe logo rendering
12
+ * 3. The CLI init wizard generates the correct action version
13
+ * 4. Docs code blocks don't mix the generic action with triage-only inputs
14
+ */
15
+ const ROOT = resolve(import.meta.dirname, "..", "..", "..", "..");
16
+ const CURRENT_ACTION_VERSION = "v5";
17
+ /** The previous action version — split into pieces so git grep doesn't match this file */
18
+ const PREV_VERSION = `v${"4"}`;
19
+ /** Files that are historical records — allowed to reference old versions */
20
+ const HISTORICAL_GLOBS = ["docs/superpowers/", ".tasks/", "node_modules/", ".changeset/", "CHANGELOG"];
21
+ function isHistorical(filePath) {
22
+ const rel = relative(ROOT, filePath);
23
+ return HISTORICAL_GLOBS.some((g) => rel.startsWith(g));
24
+ }
25
+ /**
26
+ * Use git grep for fast, indexed search across the repo.
27
+ * Returns array of "file:line:content" strings.
28
+ */
29
+ function gitGrep(pattern, extraArgs = []) {
30
+ try {
31
+ const result = execSync(`git grep -n "${pattern}" -- . ${extraArgs.map((a) => `"${a}"`).join(" ")}`, {
32
+ cwd: ROOT,
33
+ encoding: "utf-8",
34
+ maxBuffer: 10 * 1024 * 1024,
35
+ });
36
+ return result.trim().split("\n").filter(Boolean);
37
+ }
38
+ catch {
39
+ // git grep exits 1 when no matches — that's fine
40
+ return [];
41
+ }
42
+ }
43
+ // ─── 1. No stale v4 references ──────────────────────────────────────
44
+ describe("action version consistency", () => {
45
+ it(`has no swenyai/sweny@${PREV_VERSION} references in source or docs`, () => {
46
+ const matches = gitGrep(`swenyai/sweny@${PREV_VERSION}`);
47
+ const live = matches.filter((m) => {
48
+ const file = resolve(ROOT, m.split(":")[0]);
49
+ return !isHistorical(file);
50
+ });
51
+ if (live.length > 0) {
52
+ const summary = live
53
+ .map((m) => {
54
+ const [f, line] = m.split(":");
55
+ return ` ${f}:${line}`;
56
+ })
57
+ .join("\n");
58
+ expect.fail(`Found ${live.length} stale swenyai/sweny@${PREV_VERSION} reference(s):\n${summary}\n\n` +
59
+ `All live references should use swenyai/sweny@${CURRENT_ACTION_VERSION}`);
60
+ }
61
+ });
62
+ it("CLAUDE.md references the current action version", () => {
63
+ const claude = readFileSync(resolve(ROOT, "CLAUDE.md"), "utf-8");
64
+ expect(claude).toContain(`swenyai/sweny@${CURRENT_ACTION_VERSION}`);
65
+ expect(claude).not.toContain(`swenyai/sweny@${PREV_VERSION}`);
66
+ });
67
+ it("CLAUDE.md package table references the current version", () => {
68
+ const claude = readFileSync(resolve(ROOT, "CLAUDE.md"), "utf-8");
69
+ expect(claude).toMatch(new RegExp(`@sweny-ai/core.*${CURRENT_ACTION_VERSION}`));
70
+ });
71
+ it("CLI init generates workflows with the current action version", () => {
72
+ const initSrc = readFileSync(resolve(ROOT, "packages/core/src/cli/init.ts"), "utf-8");
73
+ expect(initSrc).toContain(`swenyai/sweny@${CURRENT_ACTION_VERSION}`);
74
+ expect(initSrc).not.toContain(`swenyai/sweny@${PREV_VERSION}`);
75
+ });
76
+ });
77
+ // ─── 2. README quality ──────────────────────────────────────────────
78
+ describe("README", () => {
79
+ const readme = readFileSync(resolve(ROOT, "README.md"), "utf-8");
80
+ it("uses <picture> element for dark mode logo", () => {
81
+ expect(readme).toContain("<picture>");
82
+ expect(readme).toContain("prefers-color-scheme: dark");
83
+ expect(readme).toContain("prefers-color-scheme: light");
84
+ });
85
+ it("maps dark scheme to light logo and light scheme to dark logo", () => {
86
+ // Dark background needs light (white) text logo
87
+ expect(readme).toMatch(/prefers-color-scheme:\s*dark.*logo-lockup-light\.svg/s);
88
+ // Light background needs dark text logo
89
+ expect(readme).toMatch(/prefers-color-scheme:\s*light.*logo-lockup-dark\.svg/s);
90
+ });
91
+ it("references both logo assets that exist on disk", () => {
92
+ expect(existsSync(resolve(ROOT, "assets/logo-lockup-dark.svg"))).toBe(true);
93
+ expect(existsSync(resolve(ROOT, "assets/logo-lockup-light.svg"))).toBe(true);
94
+ });
95
+ it("does not use the old Recipes heading", () => {
96
+ expect(readme).not.toMatch(/^## Recipes$/m);
97
+ });
98
+ it("has an Actions section with the multi-repo table", () => {
99
+ expect(readme).toMatch(/^## Actions$/m);
100
+ expect(readme).toContain("swenyai/triage@v1");
101
+ expect(readme).toContain("swenyai/e2e@v1");
102
+ expect(readme).toContain(`swenyai/sweny@${CURRENT_ACTION_VERSION}`);
103
+ });
104
+ });
105
+ // ─── 3. action.yml describes a generic workflow runner ──────────────
106
+ describe("action.yml", () => {
107
+ const actionYml = readFileSync(resolve(ROOT, "action.yml"), "utf-8");
108
+ it("has a description under 125 characters", () => {
109
+ const match = actionYml.match(/^description:\s*"(.+)"$/m);
110
+ expect(match).not.toBeNull();
111
+ expect(match[1].length).toBeLessThan(125);
112
+ });
113
+ it("requires a workflow input", () => {
114
+ expect(actionYml).toContain("workflow:");
115
+ });
116
+ it("does not have triage-specific inputs", () => {
117
+ // These belong on swenyai/triage, not the generic runner
118
+ const triageInputs = [
119
+ "observability-provider",
120
+ "sentry-auth-token",
121
+ "dd-api-key",
122
+ "dd-app-key",
123
+ "issue-tracker-provider",
124
+ "linear-api-key",
125
+ "dry-run",
126
+ "time-range",
127
+ "severity-focus",
128
+ ];
129
+ for (const input of triageInputs) {
130
+ expect(actionYml).not.toContain(`${input}:`);
131
+ }
132
+ });
133
+ it("runs sweny workflow run", () => {
134
+ expect(actionYml).toMatch(/sweny workflow run/);
135
+ });
136
+ });
137
+ // ─── 4. Docs code blocks don't mix generic action with triage inputs ─
138
+ describe("docs action code blocks", () => {
139
+ const TRIAGE_ONLY_INPUTS = [
140
+ "observability-provider",
141
+ "sentry-auth-token",
142
+ "sentry-org",
143
+ "sentry-project",
144
+ "dd-api-key",
145
+ "dd-app-key",
146
+ "issue-tracker-provider",
147
+ "linear-api-key",
148
+ "linear-team-id",
149
+ "dry-run:",
150
+ "time-range",
151
+ "severity-focus",
152
+ "service-filter",
153
+ "investigation-depth",
154
+ ];
155
+ /**
156
+ * Extract YAML code blocks that reference swenyai/sweny@v5
157
+ * and check they don't use triage-only inputs.
158
+ */
159
+ function findMisusedBlocks(filePath) {
160
+ if (!existsSync(filePath))
161
+ return [];
162
+ const content = readFileSync(filePath, "utf-8");
163
+ const blocks = content.match(/```ya?ml[\s\S]*?```/g) || [];
164
+ const issues = [];
165
+ for (const block of blocks) {
166
+ if (!block.includes(`swenyai/sweny@${CURRENT_ACTION_VERSION}`))
167
+ continue;
168
+ for (const input of TRIAGE_ONLY_INPUTS) {
169
+ if (block.includes(input)) {
170
+ issues.push(`${relative(ROOT, filePath)}: code block uses swenyai/sweny@${CURRENT_ACTION_VERSION} with triage-only input "${input}"`);
171
+ break; // one issue per block is enough
172
+ }
173
+ }
174
+ }
175
+ return issues;
176
+ }
177
+ // These are the docs files that show GitHub Action examples
178
+ const ACTION_DOC_FILES = [
179
+ "packages/web/src/content/docs/action/index.md",
180
+ "packages/web/src/content/docs/action/examples.md",
181
+ "packages/web/src/content/docs/action/scheduling.md",
182
+ "packages/web/src/content/docs/action/inputs.md",
183
+ "packages/web/src/content/docs/action/service-map.md",
184
+ "packages/web/src/content/docs/index.mdx",
185
+ "packages/web/src/content/docs/getting-started/quick-start.md",
186
+ "packages/web/src/content/docs/workflows/triage.md",
187
+ "packages/web/src/content/docs/workflows/implement.md",
188
+ "packages/web/src/content/docs/cloud/getting-started.mdx",
189
+ "packages/web/src/content/docs/advanced/mcp-servers.md",
190
+ "packages/web/src/content/docs/advanced/troubleshooting.md",
191
+ ];
192
+ it("no docs code block uses swenyai/sweny@v5 with triage-only inputs", () => {
193
+ const allIssues = ACTION_DOC_FILES.flatMap((f) => findMisusedBlocks(resolve(ROOT, f)));
194
+ if (allIssues.length > 0) {
195
+ expect.fail(`Found ${allIssues.length} code block(s) mixing swenyai/sweny@${CURRENT_ACTION_VERSION} with triage-only inputs:\n` +
196
+ allIssues.map((i) => ` ${i}`).join("\n"));
197
+ }
198
+ });
199
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sweny-ai/core",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "sweny": "./dist/cli/main.js"