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.
package/docs/roadmap.md CHANGED
@@ -19,6 +19,7 @@
19
19
 
20
20
  ## v0.3
21
21
 
22
+ - Install Health checks through `supered doctor`.
22
23
  - Skill authoring guide with contribution tests.
23
24
  - Release automation for tags, notes, and metadata checks.
24
25
  - Optional templates for repo launch checklists.
package/docs/styles.css CHANGED
@@ -176,6 +176,7 @@ h3 {
176
176
  }
177
177
 
178
178
  .workflow,
179
+ .proof,
179
180
  .install {
180
181
  border-top: 1px solid var(--line);
181
182
  padding: 68px clamp(24px, 6vw, 84px);
@@ -209,12 +210,47 @@ h3 {
209
210
  }
210
211
 
211
212
  .steps p,
213
+ .proof p,
212
214
  .install p {
213
215
  color: var(--muted);
214
216
  font-size: 18px;
215
217
  line-height: 1.45;
216
218
  }
217
219
 
220
+ .proof-grid {
221
+ display: grid;
222
+ gap: 16px;
223
+ grid-template-columns: repeat(3, minmax(0, 1fr));
224
+ margin-top: 30px;
225
+ }
226
+
227
+ .proof-grid a {
228
+ background: white;
229
+ border: 1px solid var(--line);
230
+ border-radius: 8px;
231
+ display: grid;
232
+ gap: 14px;
233
+ min-height: 150px;
234
+ padding: 22px;
235
+ }
236
+
237
+ .proof-grid a:hover {
238
+ border-color: var(--teal);
239
+ }
240
+
241
+ .proof-grid span {
242
+ color: var(--orange);
243
+ font-size: 13px;
244
+ font-weight: 850;
245
+ text-transform: uppercase;
246
+ }
247
+
248
+ .proof-grid strong {
249
+ color: var(--navy);
250
+ font-size: 22px;
251
+ line-height: 1.15;
252
+ }
253
+
218
254
  .install {
219
255
  align-items: start;
220
256
  display: grid;
@@ -269,6 +305,10 @@ code {
269
305
  grid-template-columns: 1fr;
270
306
  }
271
307
 
308
+ .proof-grid {
309
+ grid-template-columns: 1fr;
310
+ }
311
+
272
312
  .hero-media {
273
313
  justify-items: start;
274
314
  }
@@ -0,0 +1,33 @@
1
+ # Which Skill Should I Use?
2
+
3
+ Start with the shape of the work, not the name of the tool.
4
+
5
+ | Situation | Use this skill | Why |
6
+ | --- | --- | --- |
7
+ | The request is broad, mixed, or risky and you are not sure where to begin. | `using-supered` | Routes the session to the right next workflow. |
8
+ | The user asks for something vague, underspecified, or easy to overbuild. | `shape-the-task` | Produces a short brief, assumptions, non-goals, and acceptance signals. |
9
+ | The direction is approved but the files, order, or checks are unclear. | `make-a-map` | Turns intent into an execution map with checkpoints and verification. |
10
+ | You are implementing code, docs, packaging, or site changes. | `build-in-slices` | Keeps work small, reviewable, and easier to recover from. |
11
+ | Something is broken, flaky, confusing, or explained only by guesses. | `trace-the-fault` | Forces symptom capture, hypotheses, probes, and evidence before patches. |
12
+ | You are about to say work is done, fixed, published, or ready. | `prove-the-change` | Requires fresh proof before any completion claim. |
13
+ | The work needs commit, push, release, deploy, publish, or public handoff. | `ship-the-work` | Makes shipping part of the work and preserves evidence for readers. |
14
+
15
+ ## Fast Routing
16
+
17
+ - If the task is unclear: start with `shape-the-task`.
18
+ - If the task is clear but large: start with `make-a-map`.
19
+ - If the task is already planned: start with `build-in-slices`.
20
+ - If behavior is broken: start with `trace-the-fault`.
21
+ - If you are making a claim: start with `prove-the-change`.
22
+ - If the work leaves your machine: start with `ship-the-work`.
23
+
24
+ ## Combining Skills
25
+
26
+ Good sessions often use two or three skills in order:
27
+
28
+ - New feature: `shape-the-task` -> `make-a-map` -> `build-in-slices` -> `prove-the-change`.
29
+ - Bug fix: `trace-the-fault` -> `build-in-slices` -> `prove-the-change`.
30
+ - Public release: `prove-the-change` -> `ship-the-work`.
31
+ - Messy request: `using-supered` -> whichever skill the routing decision selects.
32
+
33
+ Do not load every skill just because they exist. Use the smallest workflow that changes the outcome.
@@ -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 workflow kit for agentic coding sessions.",
5
5
  "mcpServers": {},
6
6
  "contextFileName": "GEMINI.md"
package/install.sh CHANGED
@@ -6,6 +6,13 @@ SUPERED_REF="${SUPERED_REF:-main}"
6
6
  SUPERED_TARGET="${SUPERED_TARGET:-codex}"
7
7
  SUPERED_SOURCE_DIR="${SUPERED_SOURCE_DIR:-}"
8
8
  SUPERED_DEST="${SUPERED_DEST:-}"
9
+ SUPERED_SKILLS='using-supered
10
+ shape-the-task
11
+ make-a-map
12
+ build-in-slices
13
+ trace-the-fault
14
+ prove-the-change
15
+ ship-the-work'
9
16
 
10
17
  usage() {
11
18
  cat <<'EOF'
@@ -71,6 +78,23 @@ default_dest() {
71
78
  esac
72
79
  }
73
80
 
81
+ reject_symlink() {
82
+ [ ! -L "$1" ] || die "Refusing to install symlink: $1"
83
+ }
84
+
85
+ reject_source_symlinks() {
86
+ reject_symlink "$1"
87
+ linked="$(find "$1" -type l -print -quit)"
88
+ [ -z "$linked" ] || die "Refusing to install symlink: $linked"
89
+ }
90
+
91
+ reject_destination_symlinks() {
92
+ [ ! -L "$dest" ] || die "Refusing to install into symlinked destination: $dest"
93
+ for skill in $SUPERED_SKILLS; do
94
+ [ ! -L "$dest/$skill" ] || die "Refusing to install into symlinked destination: $dest/$skill"
95
+ done
96
+ }
97
+
74
98
  download_source() {
75
99
  tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/supered.XXXXXX")"
76
100
  archive="$tmp_dir/supered.tar.gz"
@@ -100,7 +124,19 @@ if [ -z "$dest" ]; then
100
124
  dest="$(default_dest)"
101
125
  fi
102
126
 
127
+ for skill in $SUPERED_SKILLS; do
128
+ skill_dir="$source_dir/skills/$skill"
129
+ [ -d "$skill_dir" ] || die "Missing skill directory: $skill_dir"
130
+ reject_source_symlinks "$skill_dir"
131
+ [ -f "$skill_dir/SKILL.md" ] || die "Missing skill file: $skill_dir/SKILL.md"
132
+ done
133
+
134
+ reject_destination_symlinks
103
135
  mkdir -p "$dest"
104
- cp -R "$source_dir/skills/." "$dest/"
136
+ reject_destination_symlinks
137
+
138
+ for skill in $SUPERED_SKILLS; do
139
+ cp -R "$source_dir/skills/$skill" "$dest/"
140
+ done
105
141
 
106
142
  printf 'Installed Supered for %s at %s\n' "$SUPERED_TARGET" "$dest"
@@ -0,0 +1,101 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+
4
+ import { SKILL_ORDER } from "./supered-policy.js";
5
+
6
+ export const EVAL_CATALOG_VERSION = "0.1";
7
+ export const SCORING_DIMENSIONS = [
8
+ "clarity",
9
+ "actionability",
10
+ "guardrails",
11
+ "evidence",
12
+ "outcome"
13
+ ];
14
+
15
+ async function readJson(path) {
16
+ return JSON.parse(await readFile(path, "utf8"));
17
+ }
18
+
19
+ function add(errorList, condition, message) {
20
+ if (!condition) {
21
+ errorList.push(message);
22
+ }
23
+ }
24
+
25
+ export async function readEvalPack(root = process.cwd()) {
26
+ return {
27
+ catalog: await readJson(join(root, "docs", "evals", "scenarios.json")),
28
+ report: await readJson(join(root, "docs", "evals", "baseline-results.json"))
29
+ };
30
+ }
31
+
32
+ export async function validateEvalPack(root = process.cwd()) {
33
+ const errors = [];
34
+ const { catalog, report } = await readEvalPack(root);
35
+ const scenarioIds = new Set();
36
+ const coveredSkills = new Set();
37
+
38
+ add(errors, catalog.product === "Supered", "Eval Pack catalog product must be Supered");
39
+ add(errors, catalog.version === EVAL_CATALOG_VERSION, `Eval Pack catalog version must be ${EVAL_CATALOG_VERSION}`);
40
+ add(errors, catalog.scoring?.maxScore === 5, "Eval Pack scoring maxScore must be 5");
41
+ add(
42
+ errors,
43
+ JSON.stringify(catalog.scoring?.dimensions ?? []) === JSON.stringify(SCORING_DIMENSIONS),
44
+ "Eval Pack scoring dimensions are not aligned"
45
+ );
46
+ add(errors, catalog.scenarios?.length === 10, "Eval Pack must include 10 scenarios");
47
+
48
+ for (const scenario of catalog.scenarios ?? []) {
49
+ add(errors, /^S\d{2}$/.test(scenario.id), `${scenario.id} must use an S00-style id`);
50
+ add(errors, !scenarioIds.has(scenario.id), `${scenario.id} must be unique`);
51
+ scenarioIds.add(scenario.id);
52
+
53
+ add(errors, scenario.title?.length >= 12, `${scenario.id} needs a specific title`);
54
+ add(errors, scenario.prompt?.length >= 80, `${scenario.id} prompt is too thin`);
55
+ add(errors, scenario.context?.length >= 80, `${scenario.id} context is too thin`);
56
+ add(errors, scenario.successCriteria?.length >= 3, `${scenario.id} needs success criteria`);
57
+ add(errors, scenario.expectedEvidence?.length >= 2, `${scenario.id} needs evidence expectations`);
58
+ add(errors, scenario.primarySkills?.length >= 1, `${scenario.id} needs a primary skill`);
59
+
60
+ for (const skill of scenario.primarySkills ?? []) {
61
+ coveredSkills.add(skill);
62
+ add(errors, SKILL_ORDER.includes(skill), `${scenario.id} references unknown skill ${skill}`);
63
+ }
64
+ }
65
+
66
+ for (const skill of SKILL_ORDER) {
67
+ add(errors, coveredSkills.has(skill), `Eval Pack does not exercise ${skill}`);
68
+ }
69
+
70
+ add(errors, report.product === "Supered", "Eval Pack report product must be Supered");
71
+ add(errors, report.catalogVersion === EVAL_CATALOG_VERSION, "Eval Pack report catalogVersion is not aligned");
72
+ add(errors, report.results?.length === 10, "Eval Pack report must include 10 results");
73
+ add(errors, report.summary?.averageScore >= 4.2, "Eval Pack baseline average should show useful outcomes");
74
+
75
+ for (const result of report.results ?? []) {
76
+ add(errors, scenarioIds.has(result.scenarioId), `${result.scenarioId} does not match a known scenario`);
77
+ add(
78
+ errors,
79
+ JSON.stringify(Object.keys(result.scores ?? {}).sort()) === JSON.stringify([...SCORING_DIMENSIONS].sort()),
80
+ `${result.scenarioId} scores must cover every scoring dimension`
81
+ );
82
+ for (const score of Object.values(result.scores ?? {})) {
83
+ add(errors, Number.isInteger(score), "Eval Pack scores must be integers");
84
+ add(errors, score >= 1 && score <= 5, "Eval Pack scores must be between 1 and 5");
85
+ }
86
+ add(errors, result.notes?.length >= 80, `${result.scenarioId} needs explanatory scoring notes`);
87
+ add(errors, result.recommendedSkill?.length > 0, `${result.scenarioId} needs a recommended skill`);
88
+ }
89
+
90
+ return {
91
+ errors,
92
+ catalog,
93
+ report,
94
+ summary: {
95
+ scenarioCount: catalog.scenarios?.length ?? 0,
96
+ resultCount: report.results?.length ?? 0,
97
+ averageScore: report.summary?.averageScore ?? 0,
98
+ coveredSkills: SKILL_ORDER.filter((skill) => coveredSkills.has(skill))
99
+ }
100
+ };
101
+ }
@@ -0,0 +1,88 @@
1
+ import { cp, lstat, mkdir, readdir, stat } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+
4
+ import { defaultInstallDest, SKILL_ORDER } from "./supered-policy.js";
5
+
6
+ async function assertNoSymlinks(path) {
7
+ const entry = await lstat(path);
8
+ if (entry.isSymbolicLink()) {
9
+ throw new Error(`Refusing to install symlink: ${path}`);
10
+ }
11
+
12
+ if (!entry.isDirectory()) {
13
+ return;
14
+ }
15
+
16
+ const children = await readdir(path);
17
+ await Promise.all(children.map((child) => assertNoSymlinks(join(path, child))));
18
+ }
19
+
20
+ async function assertSkillIsInstallable(skillsDir, skill) {
21
+ const skillDir = join(skillsDir, skill);
22
+ await assertNoSymlinks(skillDir);
23
+
24
+ const skillFile = join(skillDir, "SKILL.md");
25
+ try {
26
+ await stat(skillFile);
27
+ } catch (error) {
28
+ if (error.code === "ENOENT") {
29
+ throw new Error(`Missing skill file: ${skillFile}`);
30
+ }
31
+ throw error;
32
+ }
33
+ }
34
+
35
+ async function assertDestinationPathIsSafe(dest) {
36
+ try {
37
+ const destination = await lstat(dest);
38
+ if (destination.isSymbolicLink()) {
39
+ throw new Error(`Refusing to install into symlinked destination: ${dest}`);
40
+ }
41
+ } catch (error) {
42
+ if (error.code !== "ENOENT") {
43
+ throw error;
44
+ }
45
+ }
46
+ }
47
+
48
+ async function assertManagedDestinationsAreSafe(dest) {
49
+ for (const skill of SKILL_ORDER) {
50
+ const skillDest = join(dest, skill);
51
+ try {
52
+ const destination = await lstat(skillDest);
53
+ if (destination.isSymbolicLink()) {
54
+ throw new Error(`Refusing to install into symlinked destination: ${skillDest}`);
55
+ }
56
+ } catch (error) {
57
+ if (error.code !== "ENOENT") {
58
+ throw error;
59
+ }
60
+ }
61
+ }
62
+ }
63
+
64
+ export async function installSuperedSkills({ root = process.cwd(), target, dest, home = process.env.HOME } = {}) {
65
+ if (!target) {
66
+ throw new Error("Install requires --target.");
67
+ }
68
+
69
+ const installDest = dest ?? defaultInstallDest(target, home);
70
+ const skillsDir = resolve(root, "skills");
71
+
72
+ await assertDestinationPathIsSafe(installDest);
73
+ await assertManagedDestinationsAreSafe(installDest);
74
+ for (const skill of SKILL_ORDER) {
75
+ await assertSkillIsInstallable(skillsDir, skill);
76
+ }
77
+
78
+ await mkdir(installDest, { recursive: true });
79
+ for (const skill of SKILL_ORDER) {
80
+ await cp(join(skillsDir, skill), join(installDest, skill), { recursive: true });
81
+ }
82
+
83
+ return {
84
+ target,
85
+ dest: installDest,
86
+ installedSkills: [...SKILL_ORDER]
87
+ };
88
+ }
@@ -0,0 +1,181 @@
1
+ import { lstat, readFile, readdir } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
3
+
4
+ import { defaultInstallDest, SKILL_ORDER } from "./supered-policy.js";
5
+
6
+ function shellQuote(value) {
7
+ if (!/[\s'"\\]/.test(value)) {
8
+ return value;
9
+ }
10
+ return `'${value.replace(/'/g, "'\\''")}'`;
11
+ }
12
+
13
+ function fixCommand(target, dest) {
14
+ return `npx supered@latest install --target ${target} --dest ${shellQuote(dest)}`;
15
+ }
16
+
17
+ function issue(code, message, extra = {}) {
18
+ return {
19
+ code,
20
+ message,
21
+ ...extra
22
+ };
23
+ }
24
+
25
+ async function lstatOrNull(path) {
26
+ try {
27
+ return await lstat(path);
28
+ } catch (error) {
29
+ if (error.code === "ENOENT") {
30
+ return null;
31
+ }
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ async function findSymlink(path) {
37
+ const entry = await lstatOrNull(path);
38
+ if (!entry) {
39
+ return null;
40
+ }
41
+ if (entry.isSymbolicLink()) {
42
+ return path;
43
+ }
44
+ if (!entry.isDirectory()) {
45
+ return null;
46
+ }
47
+
48
+ const children = await readdir(path);
49
+ for (const child of children) {
50
+ const linked = await findSymlink(join(path, child));
51
+ if (linked) {
52
+ return linked;
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+
58
+ async function readInstalledSkill(dest, skill) {
59
+ const skillDir = join(dest, skill);
60
+ const linked = await findSymlink(skillDir);
61
+ if (linked) {
62
+ return {
63
+ linked
64
+ };
65
+ }
66
+
67
+ const skillFile = join(skillDir, "SKILL.md");
68
+ const fileStat = await lstatOrNull(skillFile);
69
+ if (!fileStat) {
70
+ return {
71
+ missing: true
72
+ };
73
+ }
74
+ if (fileStat.isSymbolicLink()) {
75
+ return {
76
+ linked: skillFile
77
+ };
78
+ }
79
+
80
+ return {
81
+ contents: await readFile(skillFile, "utf8")
82
+ };
83
+ }
84
+
85
+ export async function inspectSuperedInstall({ root = process.cwd(), target, dest, home = process.env.HOME } = {}) {
86
+ if (!target) {
87
+ throw new Error("Doctor requires --target.");
88
+ }
89
+
90
+ const installDest = dest ?? defaultInstallDest(target, home);
91
+ const sourceSkillsDir = resolve(root, "skills");
92
+ const issues = [];
93
+ const missingSkills = [];
94
+ const changedSkills = [];
95
+ const installedSkills = [];
96
+ const command = fixCommand(target, installDest);
97
+ const destStat = await lstatOrNull(installDest);
98
+
99
+ if (!destStat) {
100
+ return {
101
+ target,
102
+ dest: installDest,
103
+ status: "issues",
104
+ issues: [
105
+ issue("missing-destination", `Install destination does not exist: ${installDest}`, {
106
+ path: installDest
107
+ })
108
+ ],
109
+ installedSkills,
110
+ missingSkills: [...SKILL_ORDER],
111
+ changedSkills,
112
+ fixCommand: command
113
+ };
114
+ }
115
+
116
+ if (destStat.isSymbolicLink()) {
117
+ return {
118
+ target,
119
+ dest: installDest,
120
+ status: "issues",
121
+ issues: [
122
+ issue("symlinked-destination", `Install destination is a symlink: ${installDest}`, {
123
+ path: installDest
124
+ })
125
+ ],
126
+ installedSkills,
127
+ missingSkills,
128
+ changedSkills,
129
+ fixCommand: command
130
+ };
131
+ }
132
+
133
+ for (const skill of SKILL_ORDER) {
134
+ const expectedPath = join(sourceSkillsDir, skill, "SKILL.md");
135
+ const expected = await readFile(expectedPath, "utf8");
136
+ const installed = await readInstalledSkill(installDest, skill);
137
+
138
+ if (installed.linked) {
139
+ issues.push(
140
+ issue("unsafe-symlink", `Managed skill contains a symlink: ${installed.linked}`, {
141
+ skill,
142
+ path: installed.linked
143
+ })
144
+ );
145
+ continue;
146
+ }
147
+
148
+ if (installed.missing) {
149
+ missingSkills.push(skill);
150
+ issues.push(
151
+ issue("missing-skill", `Missing installed skill file: ${join(installDest, skill, "SKILL.md")}`, {
152
+ skill,
153
+ path: join(installDest, skill, "SKILL.md")
154
+ })
155
+ );
156
+ continue;
157
+ }
158
+
159
+ installedSkills.push(skill);
160
+ if (installed.contents !== expected) {
161
+ changedSkills.push(skill);
162
+ issues.push(
163
+ issue("changed-skill", `Installed skill differs from this Supered bundle: ${skill}`, {
164
+ skill,
165
+ path: join(installDest, skill, "SKILL.md")
166
+ })
167
+ );
168
+ }
169
+ }
170
+
171
+ return {
172
+ target,
173
+ dest: installDest,
174
+ status: issues.length === 0 ? "ok" : "issues",
175
+ issues,
176
+ installedSkills,
177
+ missingSkills,
178
+ changedSkills,
179
+ fixCommand: command
180
+ };
181
+ }
package/lib/manifest.js CHANGED
@@ -1,15 +1,9 @@
1
- import { access, readFile, readdir } from "node:fs/promises";
2
- import { join, relative } from "node:path";
1
+ import { readFile, readdir } from "node:fs/promises";
2
+ import { join } from "node:path";
3
3
 
4
- export const SKILL_ORDER = [
5
- "using-supered",
6
- "shape-the-task",
7
- "make-a-map",
8
- "build-in-slices",
9
- "trace-the-fault",
10
- "prove-the-change",
11
- "ship-the-work"
12
- ];
4
+ import { SKILL_ORDER } from "./supered-policy.js";
5
+
6
+ export { SKILL_ORDER } from "./supered-policy.js";
13
7
 
14
8
  async function readJson(path) {
15
9
  return JSON.parse(await readFile(path, "utf8"));
@@ -72,66 +66,6 @@ export async function collectManifest(root = process.cwd()) {
72
66
  }
73
67
 
74
68
  export async function validateProject(root = process.cwd()) {
75
- const errors = [];
76
- const checked = ["package.json"];
77
- const manifest = await collectManifest(root);
78
- checked.push(".codex-plugin/plugin.json");
79
- checked.push(".claude-plugin/plugin.json");
80
- checked.push(".cursor-plugin/plugin.json");
81
- checked.push("gemini-extension.json");
82
-
83
- if (manifest.package.name !== "supered") {
84
- errors.push("package.json name must be supered");
85
- }
86
-
87
- if (manifest.codexPlugin.name !== "supered") {
88
- errors.push(".codex-plugin/plugin.json name must be supered");
89
- }
90
-
91
- if (manifest.codexPlugin.interface?.displayName !== "Supered") {
92
- errors.push("Codex plugin displayName must be Supered");
93
- }
94
-
95
- if (manifest.codexPlugin.skills !== "./skills/") {
96
- errors.push("Codex plugin skills path must be ./skills/");
97
- }
98
-
99
- const logoPath = manifest.codexPlugin.interface?.logo?.replace(/^\.\//, "");
100
- if (logoPath) {
101
- checked.push(logoPath);
102
- try {
103
- await access(join(root, logoPath));
104
- } catch {
105
- errors.push(`Missing logo file: ${logoPath}`);
106
- }
107
- } else {
108
- errors.push("Codex plugin interface.logo is required");
109
- }
110
-
111
- const skills = await listSkills(root);
112
- for (const skill of skills) {
113
- checked.push(relative(root, skill.path));
114
- if (!skill.name) {
115
- errors.push(`${relative(root, skill.path)} is missing a name`);
116
- }
117
- if (!skill.description) {
118
- errors.push(`${relative(root, skill.path)} is missing a description`);
119
- }
120
- if (!/^# /m.test(skill.body)) {
121
- errors.push(`${relative(root, skill.path)} is missing an H1`);
122
- }
123
- }
124
-
125
- const skillNames = skills.map((skill) => skill.name);
126
- for (const expected of SKILL_ORDER) {
127
- if (!skillNames.includes(expected)) {
128
- errors.push(`Missing skill: ${expected}`);
129
- }
130
- }
131
-
132
- return {
133
- errors,
134
- checked,
135
- skills
136
- };
69
+ const { validateReleaseBundle } = await import("./release-bundle.js");
70
+ return validateReleaseBundle(root);
137
71
  }