diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json new file mode 100644 index 0000000..b984c18 --- /dev/null +++ b/.codex-plugin/plugin.json @@ -0,0 +1,6 @@ +{ + "name": "wisdom-lens", + "version": "1.2.1", + "description": "Decision frameworks, AI failure diagnostics, and operating principles for consequential work.", + "skills": "./skills/" +} diff --git a/README.md b/README.md index 0cdb315..9044fae 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,23 @@ Distilled into actionable frameworks for modern professionals: - **The Wisdom Playbook** — 20 chapters covering mind, work, people, and the core self - **The New Lens** — 16 chapters mapping AI-age failure modes and the builder's protocol +## Install and load + +The same `skills/` directory is packaged without duplicated skill content: + +- **Claude Code:** install this repository as a plugin using `.claude-plugin/plugin.json` (existing support is unchanged). +- **Codex:** install the repository as a plugin; `.codex-plugin/plugin.json` points Codex at `skills/`. +- **Kimi Code CLI:** run `/plugins install https://github.com/CodeWithJuber/wisdomlens`, then `/reload`. Kimi reads `kimi.plugin.json`. +- **OpenClaw:** review the bundle, then run `openclaw plugins install https://github.com/CodeWithJuber/wisdomlens --accept-capabilities`. OpenClaw maps the Codex bundle's skill root; the existing Claude prompt hooks are detected but not executed by OpenClaw. + +Start a new session after installation. These are instruction-only skills: they add no executable tools, credentials, or network access. Host permissions and approval policies still apply. + +## Provenance and safety + +Wisdom Lens is an author-created operational synthesis presented through two named frameworks, **The Wisdom Playbook** and **The New Lens**. Earlier public repository metadata described the work as distilled from ancient wisdom texts, but this package does not include source passages or citation mappings sufficient to verify each principle against an original source. + +Treat the skills as authorial interpretation and practical guidance—not original source text, a canonical translation, a religious ruling, or professional legal, medical, financial, or safety advice. For consequential decisions, verify claims against primary sources and consult a qualified human where appropriate. The package contains repository-authored skill material only; it does not bundle user profiles, session logs, or private agent memory. + ## Skills | Skill | Triggers | @@ -40,3 +57,7 @@ Each skill includes detailed reference files: ## Philosophy All terminology is universal and generic. The principles are framework-agnostic and apply to any professional context — leadership, product development, team management, personal growth, or building with AI tools. + +## Validation + +Run `node scripts/validate.mjs` to check every `SKILL.md` name, description, directory match, relative reference, package manifest, and the private-memory exclusion. diff --git a/kimi.plugin.json b/kimi.plugin.json new file mode 100644 index 0000000..f1e088a --- /dev/null +++ b/kimi.plugin.json @@ -0,0 +1,10 @@ +{ + "name": "wisdom-lens", + "version": "1.2.1", + "description": "Decision frameworks, AI failure diagnostics, and operating principles for consequential work.", + "skills": "./skills/", + "interface": { + "displayName": "Wisdom Lens", + "shortDescription": "Structured judgment and AI-workflow guidance" + } +} diff --git a/scripts/validate.mjs b/scripts/validate.mjs new file mode 100644 index 0000000..52e3cb8 --- /dev/null +++ b/scripts/validate.mjs @@ -0,0 +1,122 @@ +#!/usr/bin/env node + +import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { dirname, join, relative, resolve, sep } from "node:path"; +import { fileURLToPath } from "node:url"; + +const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const errors = []; + +function fail(message) { + errors.push(message); +} + +function readJson(path) { + try { + return JSON.parse(readFileSync(path, "utf8")); + } catch (error) { + fail(`${relative(root, path)}: invalid JSON (${error.message})`); + return {}; + } +} + +function filesUnder(path) { + return readdirSync(path, { withFileTypes: true }).flatMap((entry) => { + const child = join(path, entry.name); + return entry.isDirectory() ? filesUnder(child) : [child]; + }); +} + +function scalar(frontmatter, key) { + const match = frontmatter.match(new RegExp(`^${key}:\\s*(.*)$`, "m")); + if (!match) return ""; + const value = match[1].trim(); + if (value === ">" || value === "|") { + const after = frontmatter.slice(match.index + match[0].length); + const lines = after.match(/^(?:\r?\n[ \t]+[^\r\n]*)+/)?.[0] ?? ""; + return lines.replace(/\r?\n[ \t]+/g, " ").trim(); + } + return value.replace(/^(["'])(.*)\1$/, "$2"); +} + +const skillFiles = filesUnder(join(root, "skills")).filter( + (path) => path.endsWith(`${sep}SKILL.md`), +); + +if (skillFiles.length === 0) fail("skills/: no SKILL.md files found"); + +for (const path of skillFiles) { + const label = relative(root, path); + const text = readFileSync(path, "utf8"); + const frontmatter = text.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/)?.[1]; + if (!frontmatter) { + fail(`${label}: missing closed YAML frontmatter`); + continue; + } + + const name = scalar(frontmatter, "name"); + const description = scalar(frontmatter, "description"); + const directory = dirname(path).split(sep).at(-1); + + if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name) || name.length > 64) { + fail(`${label}: name must be a 1-64 character lowercase kebab-case slug`); + } + if (name !== directory) fail(`${label}: name must match parent directory '${directory}'`); + if (!description || description.length > 1024) { + fail(`${label}: description must contain 1-1024 characters`); + } + + const references = [ + ...text.matchAll(/\[[^\]]*\]\((?!https?:|#|mailto:)([^)]+)\)/g), + ...text.matchAll(/`((?:references|scripts|assets)\/[^`]+)`/g), + ].map((match) => match[1].split("#", 1)[0]); + + for (const reference of references) { + const target = resolve(dirname(path), reference); + if (!target.startsWith(`${dirname(path)}${sep}`) || !existsSync(target)) { + fail(`${label}: missing or unsafe relative reference '${reference}'`); + } + } +} + +const manifests = [ + [".claude-plugin/plugin.json", "skills"], + [".codex-plugin/plugin.json", "skills"], + ["kimi.plugin.json", "skills"], +]; + +for (const jsonPath of [".claude-plugin/marketplace.json", "hooks/hooks.json"]) { + readJson(join(root, jsonPath)); +} + +for (const [manifestPath, skillsKey] of manifests) { + const path = join(root, manifestPath); + if (!existsSync(path)) { + fail(`${manifestPath}: missing manifest`); + continue; + } + const manifest = readJson(path); + if (manifest.name !== "wisdom-lens") fail(`${manifestPath}: unexpected plugin name`); + if (manifest.version !== "1.2.1") fail(`${manifestPath}: version must match 1.2.1`); + const skillsPath = resolve(root, manifest[skillsKey] ?? ""); + if (skillsPath !== join(root, "skills") || !existsSync(skillsPath)) { + fail(`${manifestPath}: skills must resolve to ./skills/`); + } +} + +for (const path of filesUnder(root)) { + const repoPath = relative(root, path).replaceAll("\\", "/"); + if (repoPath.startsWith(".git/")) continue; + if (/(^|\/)(MEMORY\.md|USER\.md)$/i.test(repoPath) || /(^|\/)memory\//i.test(repoPath)) { + fail(`${repoPath}: private agent-memory files must not be packaged`); + } +} + +if (errors.length) { + console.error(errors.map((error) => `- ${error}`).join("\n")); + process.exit(1); +} + +console.log( + `Validated ${skillFiles.length} skills, ${manifests.length} package manifests, 2 supporting JSON files, and relative references.`, +);