supered 0.2.0 → 0.3.0

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,120 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { promisify } from "node:util";
6
+
7
+ import { HOST_TARGETS, SKILL_ORDER } from "./supered-policy.js";
8
+
9
+ const execFileAsync = promisify(execFile);
10
+ const npmPackEnv = {
11
+ ...process.env,
12
+ npm_config_dry_run: "false"
13
+ };
14
+
15
+ export const PACKAGE_TARGETS = Object.keys(HOST_TARGETS);
16
+ export const REQUIRED_PACKAGE_FILES = [
17
+ ".claude-plugin/",
18
+ ".codex-plugin/",
19
+ ".cursor-plugin/",
20
+ ".opencode/",
21
+ "1.svg",
22
+ "assets/",
23
+ "bin/",
24
+ "docs/",
25
+ "gemini-extension.json",
26
+ "install.sh",
27
+ "lib/",
28
+ "skills/",
29
+ "README.md",
30
+ "LICENSE"
31
+ ];
32
+ export const EXCLUDED_PACKAGE_PREFIXES = [
33
+ "tests/",
34
+ "artifacts/",
35
+ "node_modules/"
36
+ ];
37
+
38
+ async function readJson(path) {
39
+ return JSON.parse(await readFile(path, "utf8"));
40
+ }
41
+
42
+ function packageFilePaths(pack) {
43
+ return pack.files.map((file) => file.path);
44
+ }
45
+
46
+ export function inspectPackageFiles(files) {
47
+ return {
48
+ missingRequiredFiles: REQUIRED_PACKAGE_FILES.filter((path) => {
49
+ if (path.endsWith("/")) {
50
+ return !files.some((file) => file.startsWith(path));
51
+ }
52
+ return !files.includes(path);
53
+ }),
54
+ excludedFileViolations: files.filter((file) => EXCLUDED_PACKAGE_PREFIXES.some((prefix) => file.startsWith(prefix)))
55
+ };
56
+ }
57
+
58
+ export async function verifyNpmPackage({ root = process.cwd(), tempRoot } = {}) {
59
+ const workRoot = tempRoot ?? await mkdtemp(join(tmpdir(), "supered-package-"));
60
+ const cleanup = !tempRoot;
61
+
62
+ try {
63
+ const packageJson = await readJson(join(root, "package.json"));
64
+ const { stdout } = await execFileAsync("npm", ["pack", "--pack-destination", workRoot, "--json"], {
65
+ cwd: root,
66
+ env: npmPackEnv
67
+ });
68
+ const [pack] = JSON.parse(stdout);
69
+ const tarball = join(workRoot, pack.filename);
70
+ await stat(tarball);
71
+
72
+ const files = packageFilePaths(pack);
73
+ const fileInspection = inspectPackageFiles(files);
74
+
75
+ if (fileInspection.missingRequiredFiles.length > 0) {
76
+ throw new Error(`Npm package is missing required files: ${fileInspection.missingRequiredFiles.join(", ")}`);
77
+ }
78
+ if (fileInspection.excludedFileViolations.length > 0) {
79
+ throw new Error(`Npm package includes local-only files: ${fileInspection.excludedFileViolations.join(", ")}`);
80
+ }
81
+
82
+ for (const target of PACKAGE_TARGETS) {
83
+ const dest = join(workRoot, target);
84
+ await execFileAsync(
85
+ "npm",
86
+ ["exec", "--yes", "--package", tarball, "--", "supered", "install", "--target", target, "--dest", dest],
87
+ { cwd: root, env: npmPackEnv }
88
+ );
89
+
90
+ for (const skill of SKILL_ORDER) {
91
+ await stat(join(dest, skill, "SKILL.md"));
92
+ }
93
+
94
+ await execFileAsync(
95
+ "npm",
96
+ ["exec", "--yes", "--package", tarball, "--", "supered", "doctor", "--target", target, "--dest", dest],
97
+ { cwd: root, env: npmPackEnv }
98
+ );
99
+ }
100
+
101
+ const result = {
102
+ packageName: packageJson.name,
103
+ version: packageJson.version,
104
+ tarballName: pack.filename,
105
+ files,
106
+ installedTargets: [...PACKAGE_TARGETS],
107
+ doctorTargets: [...PACKAGE_TARGETS],
108
+ installedSkills: [...SKILL_ORDER],
109
+ ...fileInspection
110
+ };
111
+ if (!cleanup) {
112
+ result.tarball = tarball;
113
+ }
114
+ return result;
115
+ } finally {
116
+ if (cleanup) {
117
+ await rm(workRoot, { recursive: true, force: true });
118
+ }
119
+ }
120
+ }
@@ -0,0 +1,135 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import { join, relative } from "node:path";
3
+
4
+ import { collectManifest, listSkills } from "./manifest.js";
5
+ import { REQUIRED_PACKAGE_FILES } from "./package-verification.js";
6
+ import { HOST_TARGETS, SKILL_ORDER } from "./supered-policy.js";
7
+
8
+ async function readText(root, path) {
9
+ return readFile(join(root, path), "utf8");
10
+ }
11
+
12
+ function add(errorList, condition, message) {
13
+ if (!condition) {
14
+ errorList.push(message);
15
+ }
16
+ }
17
+
18
+ function pushChecked(checked, paths) {
19
+ for (const path of paths) {
20
+ if (!checked.includes(path)) {
21
+ checked.push(path);
22
+ }
23
+ }
24
+ }
25
+
26
+ export async function validateReleaseBundle(root = process.cwd()) {
27
+ const errors = [];
28
+ const checked = [];
29
+ const manifest = await collectManifest(root);
30
+ const version = manifest.package.version;
31
+ const hostTargets = Object.keys(HOST_TARGETS);
32
+
33
+ pushChecked(checked, [
34
+ "package.json",
35
+ ".codex-plugin/plugin.json",
36
+ ".claude-plugin/plugin.json",
37
+ ".cursor-plugin/plugin.json",
38
+ "gemini-extension.json"
39
+ ]);
40
+
41
+ add(errors, manifest.package.name === "supered", "package.json name must be supered");
42
+ add(errors, manifest.package.publishConfig?.access === "public", "package.json publishConfig.access must be public");
43
+ add(errors, manifest.package.bin?.supered === "bin/supered.mjs", "package.json bin.supered must point at bin/supered.mjs");
44
+ add(errors, manifest.package.scripts?.["verify-site"] === "node ./scripts/verify-site.mjs", "verify-site script is missing");
45
+ add(
46
+ errors,
47
+ manifest.package.scripts?.["verify-package"] === "node ./scripts/verify-npm-package.mjs",
48
+ "verify-package script is missing"
49
+ );
50
+ add(
51
+ errors,
52
+ JSON.stringify(manifest.package.files) === JSON.stringify(REQUIRED_PACKAGE_FILES),
53
+ "package.json files list is not aligned"
54
+ );
55
+
56
+ add(errors, manifest.codexPlugin.name === "supered", ".codex-plugin/plugin.json name must be supered");
57
+ add(errors, manifest.codexPlugin.version === version, "Codex plugin version must match package.json");
58
+ add(errors, manifest.claudePlugin.version === version, "Claude plugin version must match package.json");
59
+ add(errors, manifest.cursorPlugin.version === version, "Cursor plugin version must match package.json");
60
+ add(errors, manifest.geminiExtension.version === version, "Gemini extension version must match package.json");
61
+ add(errors, manifest.codexPlugin.interface?.displayName === "Supered", "Codex plugin displayName must be Supered");
62
+ add(errors, manifest.codexPlugin.skills === "./skills/", "Codex plugin skills path must be ./skills/");
63
+ add(
64
+ errors,
65
+ JSON.stringify(manifest.codexPlugin.interface?.screenshots ?? []) === JSON.stringify(["./docs/preview.svg"]),
66
+ "Codex plugin screenshots must include docs preview"
67
+ );
68
+
69
+ const logoPath = manifest.codexPlugin.interface?.logo?.replace(/^\.\//, "");
70
+ if (logoPath) {
71
+ pushChecked(checked, [logoPath]);
72
+ try {
73
+ await access(join(root, logoPath));
74
+ } catch {
75
+ errors.push(`Missing logo file: ${logoPath}`);
76
+ }
77
+ } else {
78
+ errors.push("Codex plugin interface.logo is required");
79
+ }
80
+
81
+ const skills = await listSkills(root);
82
+ for (const skill of skills) {
83
+ checked.push(relative(root, skill.path));
84
+ add(errors, Boolean(skill.name), `${relative(root, skill.path)} is missing a name`);
85
+ add(errors, Boolean(skill.description), `${relative(root, skill.path)} is missing a description`);
86
+ add(errors, /^# /m.test(skill.body), `${relative(root, skill.path)} is missing an H1`);
87
+ }
88
+
89
+ const skillNames = skills.map((skill) => skill.name);
90
+ for (const expected of SKILL_ORDER) {
91
+ add(errors, skillNames.includes(expected), `Missing skill: ${expected}`);
92
+ }
93
+
94
+ pushChecked(checked, [
95
+ ".github/workflows/ci.yml",
96
+ "RELEASE_NOTES.md",
97
+ "docs/roadmap.md",
98
+ "docs/marketplace-checklist.md",
99
+ "docs/index.html",
100
+ "README.md",
101
+ "install.sh"
102
+ ]);
103
+
104
+ const ci = await readText(root, ".github/workflows/ci.yml");
105
+ for (const command of ["npm ci", "npm test", "npm run validate", "npm run smoke-install", "npm run verify-package", "npm run verify-site"]) {
106
+ add(errors, ci.includes(command), `CI must run ${command}`);
107
+ }
108
+
109
+ const releaseNotes = await readText(root, "RELEASE_NOTES.md");
110
+ add(errors, releaseNotes.includes(`# Supered v${version}`), `Release notes must include v${version}`);
111
+
112
+ const roadmap = await readText(root, "docs/roadmap.md");
113
+ add(errors, /^# Supered Roadmap/m.test(roadmap), "Roadmap must have a Supered heading");
114
+
115
+ const marketplace = await readText(root, "docs/marketplace-checklist.md");
116
+ add(errors, marketplace.includes("install.sh"), "Marketplace checklist must mention install.sh");
117
+ add(errors, marketplace.includes("verify-package"), "Marketplace checklist must mention package verification");
118
+
119
+ for (const host of hostTargets) {
120
+ const hostDoc = `docs/hosts/${host}.md`;
121
+ pushChecked(checked, [hostDoc]);
122
+ const doc = await readText(root, hostDoc);
123
+ add(errors, new RegExp(`# .*${host}`, "i").test(doc), `${hostDoc} must name the host`);
124
+ add(errors, doc.includes("install.sh"), `${hostDoc} must mention install.sh`);
125
+ add(errors, /SUPERED_TARGET|--target/.test(doc), `${hostDoc} must document target selection`);
126
+ }
127
+
128
+ return {
129
+ errors,
130
+ checked,
131
+ skills,
132
+ version,
133
+ hostTargets
134
+ };
135
+ }
@@ -0,0 +1,32 @@
1
+ import { join } from "node:path";
2
+
3
+ export const SKILL_ORDER = [
4
+ "using-supered",
5
+ "shape-the-task",
6
+ "make-a-map",
7
+ "build-in-slices",
8
+ "trace-the-fault",
9
+ "prove-the-change",
10
+ "ship-the-work"
11
+ ];
12
+
13
+ export const HOST_TARGETS = {
14
+ codex: ".codex/skills",
15
+ claude: ".claude/skills",
16
+ cursor: ".cursor/skills",
17
+ gemini: ".gemini/skills",
18
+ opencode: ".opencode/skills"
19
+ };
20
+
21
+ export function defaultInstallDest(target, home = process.env.HOME) {
22
+ if (!home) {
23
+ throw new Error("HOME is not set; pass --dest explicitly.");
24
+ }
25
+
26
+ const targetPath = HOST_TARGETS[target];
27
+ if (!targetPath) {
28
+ throw new Error(`Unsupported target: ${target}`);
29
+ }
30
+
31
+ return join(home, targetPath);
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supered",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A compact agent workflow kit for clarifying, building, verifying, and shipping software changes.",
5
5
  "type": "module",
6
6
  "homepage": "https://fhajjej-ship-it.github.io/Supered/",