supered 0.2.1 → 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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supered",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Small, evidence-first workflows for coding agents.",
5
5
  "author": {
6
6
  "name": "Farouk Hajjej",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supered",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A compact agent workflow kit for clarifying, building, verifying, and shipping software changes.",
5
5
  "author": {
6
6
  "name": "Farouk Hajjej",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supered",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Small, evidence-first workflows for coding agents.",
5
5
  "homepage": "https://fhajjej-ship-it.github.io/Supered/",
6
6
  "license": "MIT",
package/README.md CHANGED
@@ -30,6 +30,12 @@ Install with npx:
30
30
  npx supered install --target codex
31
31
  ```
32
32
 
33
+ Check the install:
34
+
35
+ ```bash
36
+ npx supered doctor --target codex
37
+ ```
38
+
33
39
  Or install the default Codex skill set with curl:
34
40
 
35
41
  ```bash
@@ -50,6 +56,7 @@ cd Supered
50
56
  npm test
51
57
  npm run validate
52
58
  node ./bin/supered.mjs install --target codex
59
+ node ./bin/supered.mjs doctor --target codex
53
60
  ```
54
61
 
55
62
  For Claude or Gemini, replace `codex` with `claude` or `gemini`.
@@ -91,13 +98,14 @@ npm run smoke-install
91
98
  npm run verify-site
92
99
  npm run verify-package
93
100
  node ./bin/supered.mjs skills --json
101
+ node ./bin/supered.mjs doctor --target codex --json
94
102
  ```
95
103
 
96
104
  The validator checks package metadata, plugin metadata, and skill frontmatter so the public repo does not drift into a half-installable state.
97
105
 
98
106
  `npm run verify-site` opens the landing page in Chromium at desktop and mobile sizes, checks the logo and workflow text, and writes screenshots to `artifacts/site/`.
99
107
 
100
- `npm run verify-package` builds the npm tarball and verifies `npx`-style installs for every supported host target.
108
+ `npm run verify-package` builds the npm tarball and verifies `npx`-style installs plus Doctor checks for every supported host target.
101
109
 
102
110
  ## Design Principles
103
111
 
package/bin/supered.mjs CHANGED
@@ -3,6 +3,7 @@ import { dirname, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
5
5
  import { installSuperedSkills } from "../lib/host-install.js";
6
+ import { inspectSuperedInstall } from "../lib/install-doctor.js";
6
7
  import { listSkills } from "../lib/manifest.js";
7
8
  import { validateReleaseBundle } from "../lib/release-bundle.js";
8
9
 
@@ -16,10 +17,12 @@ Usage:
16
17
  supered skills [--json]
17
18
  supered validate
18
19
  supered install --target <codex|claude|cursor|gemini|opencode> [--dest <path>]
20
+ supered doctor --target <codex|claude|cursor|gemini|opencode> [--dest <path>] [--json]
19
21
 
20
22
  Examples:
21
23
  npx supered install --target codex
22
24
  npx supered install --target gemini --dest ~/.gemini/skills
25
+ supered doctor --target codex
23
26
  `);
24
27
  }
25
28
 
@@ -62,6 +65,40 @@ async function installCommand() {
62
65
  console.log(`Installed Supered skills for ${result.target} at ${result.dest}.`);
63
66
  }
64
67
 
68
+ async function doctorCommand() {
69
+ const targetIndex = args.indexOf("--target");
70
+ const destIndex = args.indexOf("--dest");
71
+ const target = targetIndex === -1 ? "" : args[targetIndex + 1];
72
+ const dest = destIndex === -1 ? undefined : args[destIndex + 1];
73
+ const json = args.includes("--json");
74
+
75
+ if (!target || (destIndex !== -1 && !dest)) {
76
+ throw new Error("Doctor requires --target <codex|claude|cursor|gemini|opencode>.");
77
+ }
78
+
79
+ const result = await inspectSuperedInstall({ root, target, dest });
80
+ if (json) {
81
+ console.log(JSON.stringify(result, null, 2));
82
+ if (result.status !== "ok") {
83
+ process.exitCode = 1;
84
+ }
85
+ return;
86
+ }
87
+
88
+ if (result.status === "ok") {
89
+ console.log(`Supered doctor passed for ${result.target} at ${result.dest}.`);
90
+ console.log(`${result.installedSkills.length} skills installed and current.`);
91
+ return;
92
+ }
93
+
94
+ console.log(`Supered doctor found ${result.issues.length} issue${result.issues.length === 1 ? "" : "s"} for ${result.target} at ${result.dest}.`);
95
+ for (const installIssue of result.issues) {
96
+ console.log(`- [${installIssue.code}] ${installIssue.message}`);
97
+ }
98
+ console.log(`Fix: ${result.fixCommand}`);
99
+ process.exitCode = 1;
100
+ }
101
+
65
102
  try {
66
103
  if (!command || command === "help" || command === "--help") {
67
104
  printHelp();
@@ -71,6 +108,8 @@ try {
71
108
  await validateCommand();
72
109
  } else if (command === "install") {
73
110
  await installCommand();
111
+ } else if (command === "doctor") {
112
+ await doctorCommand();
74
113
  } else {
75
114
  throw new Error(`Unknown command: ${command}`);
76
115
  }
package/docs/install.md CHANGED
@@ -6,12 +6,14 @@ Supered ships plain skill folders plus lightweight plugin manifests.
6
6
 
7
7
  ```bash
8
8
  npx supered install --target codex
9
+ npx supered doctor --target codex
9
10
  ```
10
11
 
11
12
  Choose another host:
12
13
 
13
14
  ```bash
14
15
  npx supered install --target opencode
16
+ npx supered doctor --target opencode
15
17
  ```
16
18
 
17
19
  ## One-line install
@@ -30,20 +32,34 @@ curl -fsSL https://raw.githubusercontent.com/fhajjej-ship-it/Supered/main/instal
30
32
 
31
33
  ```bash
32
34
  node ./bin/supered.mjs install --target codex
35
+ node ./bin/supered.mjs doctor --target codex
33
36
  ```
34
37
 
35
38
  ## Claude
36
39
 
37
40
  ```bash
38
41
  node ./bin/supered.mjs install --target claude
42
+ node ./bin/supered.mjs doctor --target claude
39
43
  ```
40
44
 
41
45
  ## Gemini
42
46
 
43
47
  ```bash
44
48
  node ./bin/supered.mjs install --target gemini
49
+ node ./bin/supered.mjs doctor --target gemini
45
50
  ```
46
51
 
52
+ ## Doctor
53
+
54
+ Doctor checks Install Health without changing files:
55
+
56
+ ```bash
57
+ supered doctor --target codex
58
+ supered doctor --target codex --json
59
+ ```
60
+
61
+ It reports missing skills, changed skill files, missing destinations, and unsafe symlinks. The fix it prints is a reinstall command for the same target and destination.
62
+
47
63
  ## Manual
48
64
 
49
65
  Copy each folder in `skills/` into the skill directory used by your agent host.
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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supered",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A compact workflow kit for agentic coding sessions.",
5
5
  "mcpServers": {},
6
6
  "contextFileName": "GEMINI.md"
@@ -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
+ }
@@ -90,6 +90,12 @@ export async function verifyNpmPackage({ root = process.cwd(), tempRoot } = {})
90
90
  for (const skill of SKILL_ORDER) {
91
91
  await stat(join(dest, skill, "SKILL.md"));
92
92
  }
93
+
94
+ await execFileAsync(
95
+ "npm",
96
+ ["exec", "--yes", "--package", tarball, "--", "supered", "doctor", "--target", target, "--dest", dest],
97
+ { cwd: root, env: npmPackEnv }
98
+ );
93
99
  }
94
100
 
95
101
  const result = {
@@ -98,6 +104,7 @@ export async function verifyNpmPackage({ root = process.cwd(), tempRoot } = {})
98
104
  tarballName: pack.filename,
99
105
  files,
100
106
  installedTargets: [...PACKAGE_TARGETS],
107
+ doctorTargets: [...PACKAGE_TARGETS],
101
108
  installedSkills: [...SKILL_ORDER],
102
109
  ...fileInspection
103
110
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supered",
3
- "version": "0.2.1",
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/",