From dcf8bfa046442b5c24a5afee65207e36f50288cc Mon Sep 17 00:00:00 2001 From: 3metaJun <251347867+3metaJun@users.noreply.github.com> Date: Fri, 11 Sep 2026 14:54:25 +0800 Subject: [PATCH 1/2] fix: restore portable skill contracts --- README.md | 15 +- profiles/upstream-manifest.json | 190 ++++++++++++++++++ scripts/check-upstream.mjs | 43 ++++ scripts/context.test.mjs | 16 ++ scripts/model-config.mjs | 5 +- scripts/skill-integrity.test.mjs | 27 +++ scripts/sync-upstream.mjs | 1 + skills/automate-me/SKILL.md | 14 +- skills/how/SKILL.md | 4 +- skills/meta-mode/SKILL.md | 2 +- skills/meta-mode/playbooks/babysit.md | 2 +- .../meta-mode/playbooks/multi-phase-plan.md | 2 +- skills/no-comments/SKILL.md | 8 +- skills/reflect/SKILL.md | 6 +- skills/reflect/references/synthesizer.md | 4 +- skills/setup-mstack/SKILL.md | 8 +- 16 files changed, 317 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 77fc7a0..bb67fd3 100644 --- a/README.md +++ b/README.md @@ -261,10 +261,12 @@ To compare the local inventory with a pstack checkout, run: npm run check-upstream -- --source /path/to/pstack ``` -The check applies the renames in `profiles/upstreams.json`, reports the -portable skill count, and reports the state of the agents, Benny automation, -guide, and meta-mode tools. Add `--strict` to fail when a configured artifact -is missing, differs, or remains after removal upstream. Strict mode requires +The check applies the renames in `profiles/upstreams.json`, verifies the +reviewed body digest for every canonical pstack skill, reports the portable +skill count, and reports the state of the agents, Benny automation, guide, and +meta-mode tools. Add `--strict` to fail when a canonical skill body or +configured artifact is missing, differs, or remains after removal upstream. +Strict mode requires the source to be a clean Git checkout whose `HEAD` exactly matches the pinned commit. @@ -305,8 +307,9 @@ directory handle. The transaction provides process-crash recovery, but it does not provide a power-loss durability guarantee on that platform. Readers that ignore the sync lock can still observe files changing during the commit. -`--apply` writes `profiles/upstream-manifest.json`. Its hashes describe the -transformed upstream baseline, not package integrity. Files configured with +`--apply` writes `profiles/upstream-manifest.json`. Its artifact hashes describe +the transformed upstream baseline, while `canonicalSkills` records the pinned +source body and reviewed target body for each canonical skill. Files configured with `compareContent: false`, including the adapted agents, may intentionally differ from those hashes. A different checkout can be used as the destination with `--target /path/to/mstack`. diff --git a/profiles/upstream-manifest.json b/profiles/upstream-manifest.json index ef24c27..03ec700 100644 --- a/profiles/upstream-manifest.json +++ b/profiles/upstream-manifest.json @@ -61,5 +61,195 @@ "tools/meta-mode/watch-pr/watch-pr": "a17825c7862730dd7c897c1ac076971c0f11d2f9ff8fe37c4a74ebc6db92d549", "tools/meta-mode/worktree-audit.sh": "9f14b5f4f70a6ada93778e49701f854796b11776edccb9f73816ee2a8b8b623c" } + }, + "canonicalSkills": { + "architect": { + "source": "897a59beaa3cab107d98c256206e062cc4a5e9421a45b9d462fbded19291fea5", + "target": "cc8a035aa7a2aa417676c4e364b86f4a3e2b85748bf2d0916402105b011a1083" + }, + "arena": { + "source": "648697e237871e279e66166fd070dc272c73a310ababe7498c89713414ed4a14", + "target": "f100383e190b806567015d2cbbaf90b9a1b0151ae60b08aa6185fc6eca3c9552" + }, + "automate-me": { + "source": "c80a6024fa6bebfa6875975c74293415fa6f729087377998751dc1343c865e75", + "target": "b87d5af4db107ca6e3c2019f41875e36aeeda32b8748d8d92fbe133091529bb5" + }, + "blast-radius": { + "source": "20cb2945f5fe62055166f745107274af5f19428ca033e53a9b8bc69650d3ded1", + "target": "ca63c902c065c3de95d5c7f5db665b4cf8c3b881eafbef9dde3db66ca340a752" + }, + "bro": { + "source": "aa329d0ceeeecb6822a7174ad76bb07a7826a3c52ceb6a588dec4138815084bb", + "target": "8b822d47b9299627f5e4fe42684a031105792c8531443da1ae3423adfa2de96f" + }, + "create-verification-skill": { + "source": "644f2551403c1bca01a2855b34611b6e7be0ce0dc5b204514c376c0f6a6e6ac4", + "target": "7242b478eb0face01b1f1d27950efa69e415219b278993bc3e75f297707bba48" + }, + "figure-it-out": { + "source": "982f2c1024a3a24f6417426ad8e8a3e027fe6210a8a442308fb85073f8f3f6ef", + "target": "a892d780bae548ec537ad1edea44b11ab85c3223345c84276f567ef30bfdca97" + }, + "how": { + "source": "06b714dbd830bc829bfb89e33a1c39daf6d72ea412e488ea9d65f5f33b885a0c", + "target": "a3e2acb068e126bad1ddadb4b16bb14154d5687fffc8ce2fe8209660b83c2e4b" + }, + "interrogate": { + "source": "8aef2c35f2282bbc2636e10b4b52fe41ed5d00c86e775e82fbf8771b2b07f9be", + "target": "8772ecf8ed7e64118459160d4cd61096877b52cc557104a3ae305b036be16ac9" + }, + "maintain-verification-skill": { + "source": "515c0eaa054b3f6be1b1fb06f2c2f173c80fddb58bbcac57576f89c479bc68e8", + "target": "bfa8b97a5ca35d618228c37eb02c74589c0fcb1af13d06201b10e0eb3274e32c" + }, + "make-bot-ui": { + "source": "3624c8ef16cf2e48deb2a4049e72c6962a9431c8d6c6af90e7bd7ea0375224ec", + "target": "2f04984c34d64ad8119eac43f24298d480528b14a9876d672aee0c942aa0c2da" + }, + "meta-mode": { + "source": "9319963075ef537e1d83edc4814f6bc6a7c90779ea261227934c984a0a5e88fb", + "target": "65337fe034b2b34f782f2184e7df15bee535b58918a18efbe969206016c96b1e" + }, + "no-comments": { + "source": "5c5b0882297d704c3a9720c52b7a793c68b013eaf717989f0945624efdfe2b05", + "target": "f5bc3892fd057751d5928cffbebabbcde58804021a520bf67fac6b550d3399f5" + }, + "principle-attack-the-premise": { + "source": "c87bd7536f772f8a403ad8155535ee17a1bbec9a353156693eb3bb1520c87ae3", + "target": "58c2998cbe0318b648d2f78c080003c5d3685a0ede2a98e56bc29e2fdfa7417a" + }, + "principle-boundary-discipline": { + "source": "62ac2862c2caa22dd14531f51c9c6bb445ceb198ca98a687e25b4d47c1f10a28", + "target": "0ebee28a5f8bf5cac890dc37a18f3cdf66c360d85336fe8efa0be04430ecdddf" + }, + "principle-build-the-lever": { + "source": "458b269eab8e95a1573276955312f7d2a13ae8599879a9efc748248f6bc34b56", + "target": "b49623e8e41c072ce14e6479a0a93cb932cd1a320642334cf0ac8f9e7cf31ded" + }, + "principle-encode-lessons-in-structure": { + "source": "77044c83a9ac6df78fc643887ea5f1f11f082adaf14b717c060cdaed03cce863", + "target": "e66d95bd954a1e8ba298bc451ba1e9190c758f6d77c240540bedf8f28c895694" + }, + "principle-exhaust-the-design-space": { + "source": "8583310d7297b7823d75494faa57b003564d436807febecdecbd8321abe5cf6e", + "target": "67e7e29ab4f484bfddcac55ece4d70f6efc25ccbf91de7de31fcec385f9f059e" + }, + "principle-experience-first": { + "source": "58903529d733b1c1a7c9ccf127d45ef3c7d0d9f0b81164a4dc6b80efe6f2912b", + "target": "3013367ce42fabb55799ffbcc51ad20b2ae7ec1b898bad4ac4ceaa2f093edbe1" + }, + "principle-fix-root-causes": { + "source": "ba4cd38da1dcc8fe5432feb64457cba95594736a9b21b2ebf8f8c52aee068707", + "target": "2f48a508674c8b9f3339aba95929079af54fb7fbd367b4eb45175388f1e170f9" + }, + "principle-foundational-thinking": { + "source": "864b827e8199d946ce58beca0f89c4ed4099ed6e426fb698a3aaa45de2a9eea2", + "target": "e69eea824d81c4bb32cbcdb26513bf964882daeeb647bb99210978e86bec1a49" + }, + "principle-guard-the-context-window": { + "source": "9f60e2c79b35f865cb103462f9128c6760b70f603475d1e535e5930f61840f66", + "target": "d42b8f8b02cdcb4f84b466ffcd623ca911a8bbf93b46b545d8b3ca4e950b2580" + }, + "principle-laziness-protocol": { + "source": "9006579e9e3f33955d17569733db688ae65f22577dc3b21803d219a5a8445aff", + "target": "f45159ad7ea5f01a08d364897307f69ca18fa57b87d6ed6b89ca222a2388bf7d" + }, + "principle-make-operations-idempotent": { + "source": "540738217c3da7bf513b9886924ad6a2bc77e58be867f5bd2e1831c74bf2ce70", + "target": "5cf54da6cc34714fbe97e45fd61a4678662d087d7f6f6dc81cace3f34041f541" + }, + "principle-migrate-callers-then-delete-legacy-apis": { + "source": "09978915a4a11990cf0a9a02d9c08cc59e0b7a04c5c24b83aa560fbe4146f75b", + "target": "388e9cbc9e52bd72d9044d7e37126bd3355c4e8f1188447e2a6850fe4fc549d7" + }, + "principle-minimize-reader-load": { + "source": "42551c5dc74cd578163c148da552db3ff67d29b78600ef216bfd311fb828fe69", + "target": "d44ded81ac7cfc194dca65c9943a55fe8a058660485049a6e607e6bdf5d70738" + }, + "principle-model-the-domain": { + "source": "bbadbb9a723fac3f76e782ae45665eb94058ec10993e4bae36d4dc6b0e4eac07", + "target": "4464cc1a0a2366ff8249659c30f525dba6206947f037f07f5931238ed0e96f82" + }, + "principle-never-block-on-the-human": { + "source": "d82bce9ab9193249d1ec0d514202d89602f0fd3d312174d622c2dd222bae5a2e", + "target": "15d583bb6125a88dd0ff375bfdbdc1ac874027bf741cda5f6f4d3e939363051e" + }, + "principle-outcome-oriented-execution": { + "source": "beedcaed75572a6a1662d47f16da203c2bd2c9dbffbbd678d4ba187d67b7e670", + "target": "f2fe80adf2da1e1e15aa1d1b465f1cb963acb05114be9032a89ae6ead06df288" + }, + "principle-prove-it-works": { + "source": "ae13d287984a5864312b6286899d4ee2b60f8272b5e5e8fcc586a7c3ea51020b", + "target": "eb89aa6f3283cd73ba56d6cc01e0d4b9ec12367144988525b2c43761cbed4ada" + }, + "principle-redesign-from-first-principles": { + "source": "a1c7fd0a96b40e12a74bceb0e6bb20a9666dd844dcabfb624acb27da23b929fe", + "target": "b3ac6bc1c707aa8ad1a331851ad71aeb48f179136907ab2015f34881c674d056" + }, + "principle-separate-before-serializing-shared-state": { + "source": "05294b40448e927c5da1c7b6f5c6b9fed3744637e3833d2d948b9154a9dd00bb", + "target": "30cc80284a90bb5d94cde5a3533a8a27755832c2c4e638f8e3b97bbf8ba94924" + }, + "principle-sequence-verifiable-units": { + "source": "761390804ee36c32f5338d8db47ec54463d40ff96dbb296dfafabcd5f2593eec", + "target": "84bf426fd9c7f2bb8561baad84a06bb5b2c78534e7599bdd3fde47ce5596d707" + }, + "principle-subtract-before-you-add": { + "source": "a983a50e732c1ed315eba7aaf47a61329e31c4794eb1ab9d25d9945265b31e03", + "target": "65fa4e1a2a630040eb0abbcfeaf0e82241fd968443021b535f94b4b6d4b7aa0f" + }, + "principle-test-behavior-not-implementation": { + "source": "87e40efe4e486f7ea639d2ed1fc0abe6c93b8fd0e89a81218086909d2470a52e", + "target": "c6104107aa4902342d36eb5c6b7f99862eadff5ba27f367cde011ccdde4d8d17" + }, + "principle-type-system-discipline": { + "source": "c83da8031ffb4c86410030d3e261f659598fca2b7d8158fd9dc269d70a3a025e", + "target": "9397a9ce6e26e54d0e6e9f81e488f626f3daefd6903509536fbdfb0d021a274f" + }, + "recall": { + "source": "d265b0e5b6dc5b89c0407f3387d24462c988e1990a37d5359735137908128c5a", + "target": "fbf2ae86fe5f3aeff6a6454b1a0f0899eebb5311a37ba44a3a0088fc2523ca77" + }, + "reflect": { + "source": "6a4d4ccaace9ce88c3e268fcbedf3cfd40e527162a5e5aad6de8edbaf5e2b83f", + "target": "b0f292d4503c3591b21498718af8c90feaf2c4cf0ba25b0a7e374c7b42f7b0c1" + }, + "setup-mstack": { + "source": "a11137437e12831c73f228f449bdfefab487bf7e2c69a4b306189e8fb1e6ab3e", + "target": "5aeab4f18c5387b6c32ab72fd87c3b48fdfe419109e681045dfe42c155e33bcc" + }, + "show-me-your-work": { + "source": "831e85ba3f84f38bf338cd03e6af050fea357752e25a825e334c99f59d7f2077", + "target": "d189454d45306b8b2ace4456684fc9dbaf9d3c5305e2fe3c35afed8977f1552f" + }, + "swarm": { + "source": "32440570076df5a124d679e0419e5b03c1d405365bb443552789338427a8ce7b", + "target": "015c31b71a020a585b628ed8308245530c008b42facedd3040b385628b3ea599" + }, + "tdd": { + "source": "011cab0ecc04a3632121efb493ae4d60aa282a9b72c66de74dd8ad7e4313e05a", + "target": "bb077c26ad22c0171f3c06e72d288556a46a8fce34164ea7da686d78c135544c" + }, + "teach": { + "source": "bd286e5a6290673be2cd28aef7af2ec7f1fb138e6bce156ea657368c7e7fc8a4", + "target": "5a85664f8cce57429af0d8c060a20d8740579867f0976f4981032e8855cab2b5" + }, + "technical-writing": { + "source": "6d6d9e625ae412ee8f36df8deb6fd9203d7c6acca776d5ee3cabbb9126f8b84c", + "target": "40dafbe83d76600dfd9417bb3861b2a73ac75010838412a10b8fa2b82b00b9a0" + }, + "typescript-best-practices": { + "source": "28f9e61710e205f6f3c5476f333483cff0e988f6a5b639e2faad536344b40c5e", + "target": "02b41b2cbb0ac43940b8f4df8b54c55f1aac0a68890a9379f9b006b2a8f1f4a0" + }, + "unslop": { + "source": "c6d2572294d933a428211921069e9a248490e9b466ca59437cb4d9de248600ca", + "target": "6948335a00f9fed7a29204b9f08f74a9d9596d90989fd06f58f02d984ceab6f3" + }, + "why": { + "source": "222b93789754ba6ee901835137184acdca667f7e47c523bcc85aca86675fb249", + "target": "873aedc86d1321093e9a195ac8bff1783241b309a1d33808ab74f181c4b8f22d" + } } } diff --git a/scripts/check-upstream.mjs b/scripts/check-upstream.mjs index 34e2795..4a4c899 100644 --- a/scripts/check-upstream.mjs +++ b/scripts/check-upstream.mjs @@ -246,6 +246,14 @@ function digest(value) { return createHash("sha256").update(value).digest("hex"); } +function normalizedText(value) { + return value.toString("utf8").replace(/\r\n/g, "\n"); +} + +function normalizedDigest(value) { + return digest(normalizedText(value)); +} + function equalContent(path, current, expected) { if (textExtensions.has(extname(path).toLowerCase())) { return current.toString("utf8").replace(/\r\n/g, "\n") === expected.toString("utf8").replace(/\r\n/g, "\n"); @@ -325,6 +333,41 @@ if (!existsSync(manifestPath)) { if (strict) artifactProblems += 1; } if (manifest) { + const canonicalSkills = manifest.canonicalSkills; + if (!canonicalSkills || typeof canonicalSkills !== "object" || Array.isArray(canonicalSkills)) { + console.warn("Upstream manifest has no canonicalSkills body baselines; skill body drift was not checked."); + } else { + const canonicalProblems = []; + for (const sourceName of readdirSync(sourceSkills, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => entry.name)) { + const targetName = renames[sourceName] ?? sourceName; + const entry = canonicalSkills[targetName]; + const sourcePath = resolveInside(sourceRoot, join("skills", sourceName, "SKILL.md"), `canonical source skill ${sourceName}`); + const targetPath = resolveInside(targetRoot, join("skills", targetName, "SKILL.md"), `canonical target skill ${targetName}`); + if (!entry || typeof entry !== "object" || Array.isArray(entry)) { + canonicalProblems.push(`${targetName}: missing manifest entry`); + continue; + } + if (entry.source !== normalizedDigest(transformBuffer(sourcePath))) { + canonicalProblems.push(`${targetName}: upstream source body changed`); + } + if (!existsSync(targetPath)) { + canonicalProblems.push(`${targetName}: target SKILL.md is missing`); + } else if (entry.target !== normalizedDigest(readFileSync(targetPath))) { + canonicalProblems.push(`${targetName}: target body changed (possible truncation)`); + } + } + const knownTargets = new Set(expected); + for (const targetName of Object.keys(canonicalSkills)) { + if (!knownTargets.has(targetName)) canonicalProblems.push(`${targetName}: stale manifest entry`); + } + console.log(`canonical skill bodies: ${canonicalProblems.length ? `${canonicalProblems.length} problem(s)` : "all match manifest"}`); + if (strict && canonicalProblems.length) { + for (const problem of canonicalProblems) console.error(` ${problem}`); + artifactProblems += canonicalProblems.length; + } + } const removed = []; for (const [name, files] of Object.entries(manifest.artifacts ?? {})) { if (!files || typeof files !== "object" || Array.isArray(files)) { diff --git a/scripts/context.test.mjs b/scripts/context.test.mjs index 93df6b0..91da307 100644 --- a/scripts/context.test.mjs +++ b/scripts/context.test.mjs @@ -161,6 +161,22 @@ test("model configuration resolves harness overrides and rejects unknown roles", } }); +test("model configuration uses the platform home directory by default", () => { + const root = fixture("model-config-default"); + try { + const expectedPath = resolve(root, ".config", "mstack", "models.json"); + const result = run(modelConfig, ["--harness", "codex"], { + HOME: root, + USERPROFILE: root, + }); + assert.equal(result.status, 0, result.stderr); + assert.ok(result.stdout.includes(`No model configuration found at ${expectedPath}`)); + assert.doesNotMatch(result.stdout, /[\\/]~[\\/]/); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test("run-role emits a harness command and omits inherited model flags", () => { const result = run(runRole, [ "--harness", diff --git a/scripts/model-config.mjs b/scripts/model-config.mjs index c456278..1b7a395 100644 --- a/scripts/model-config.mjs +++ b/scripts/model-config.mjs @@ -15,10 +15,11 @@ function valueAfter(flag) { function expandHome(path) { if (path === "~") return homedir(); - return path?.startsWith("~/") ? join(homedir(), path.slice(2)) : path; + return path?.startsWith("~/") || path?.startsWith("~\\") ? join(homedir(), path.slice(2)) : path; } -const requestedPath = expandHome(valueAfter("--file") ?? join("~", ".config", "mstack", "models.json")); +const defaultPath = join(homedir(), ".config", "mstack", "models.json"); +const requestedPath = expandHome(valueAfter("--file") ?? defaultPath); if (!requestedPath) throw new Error("--file requires a path"); const configPath = resolve(requestedPath); if (!existsSync(configPath)) { diff --git a/scripts/skill-integrity.test.mjs b/scripts/skill-integrity.test.mjs index b786cc3..3bcc38b 100644 --- a/scripts/skill-integrity.test.mjs +++ b/scripts/skill-integrity.test.mjs @@ -1,4 +1,5 @@ import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; import { existsSync, readFileSync, readdirSync } from "node:fs"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; @@ -6,6 +7,13 @@ import test from "node:test"; const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const skillsRoot = join(repoRoot, "skills"); +const upstreamManifest = JSON.parse(readFileSync(join(repoRoot, "profiles", "upstream-manifest.json"), "utf8")); + +function normalizedDigest(value) { + return createHash("sha256") + .update(value.replace(/\r\n/g, "\n"), "utf8") + .digest("hex"); +} function readSkill(name) { return readFileSync(join(skillsRoot, name, "SKILL.md"), "utf8"); @@ -46,6 +54,8 @@ test("portable skill files contain no broken replacement artifacts", () => { /prethe current harness/i, /(?:poteto-mode|setup-pstack|poteto-agent)/i, /worker type:\s*generalPurpose/i, + /Comment Sicko/i, + /Task subagent/i, /cursor/i, /application support\/the current harness/i, ]; @@ -68,3 +78,20 @@ test("portable skill files contain no broken replacement artifacts", () => { } } }); + +test("canonical skill manifest detects body drift", () => { + const entries = upstreamManifest.canonicalSkills; + assert.ok(entries && typeof entries === "object" && !Array.isArray(entries)); + const skillNames = Object.keys(entries).sort(); + assert.ok(skillNames.length > 0); + for (const name of skillNames) { + assert.ok(existsSync(join(skillsRoot, name, "SKILL.md")), `${name} manifest entry has no target skill`); + const content = readSkill(name); + assert.equal(entries[name].target, normalizedDigest(content), `${name} body differs from its reviewed baseline`); + assert.notEqual( + entries[name].target, + normalizedDigest(`${content}\nThis mutation must invalidate the canonical body digest.`), + `${name} digest must change when its body changes`, + ); + } +}); diff --git a/scripts/sync-upstream.mjs b/scripts/sync-upstream.mjs index 4837743..22e24d3 100644 --- a/scripts/sync-upstream.mjs +++ b/scripts/sync-upstream.mjs @@ -456,6 +456,7 @@ const removed = removals; const manifest = { source: pstack.repository, commit: pstack.commit, + ...(previousManifest?.canonicalSkills ? { canonicalSkills: previousManifest.canonicalSkills } : {}), artifacts: Object.fromEntries( [...grouped.keys()].map((name) => [ name, diff --git a/skills/automate-me/SKILL.md b/skills/automate-me/SKILL.md index 07c3e7f..8c185b8 100644 --- a/skills/automate-me/SKILL.md +++ b/skills/automate-me/SKILL.md @@ -1,13 +1,13 @@ --- name: automate-me -description: "Use for \"automate me\", \"create/update/refresh my -mode skill\", \"turn/capture my preferences or working style into a skill\", or wanting agents to follow how the user works. Drafts or revises a personal -mode skill via create-skill + unslop, optionally pulling fresh evidence from recent transcripts." +description: "Use for \"automate me\", \"create/update/refresh my -mode skill\", \"turn/capture my preferences or working style into a skill\", or wanting agents to follow how the user works. Drafts or revises a personal -mode skill via the skill-authoring workflow and unslop, optionally pulling fresh evidence from recent transcripts." --- # Automate me A guided flow for turning the user's working conventions into a skill agents will follow. The output is one `-mode` skill tailored to them (e.g. `jay-mode`, `priya-mode`). -This skill orchestrates three others: an inline mining pass (see step 1), the current harness's built-in `create-skill` (authoring), and the **unslop** skill (prose discipline). It sequences them. It doesn't replace them. +This skill orchestrates three others: an inline mining pass (see step 1), the repository's skill-authoring workflow, and the **unslop** skill (prose discipline). It sequences them. It doesn't replace them. ## Flow @@ -63,17 +63,17 @@ The **meta-mode** skill shows the shape. Read it for granularity. Don't copy its ### 4. Draft the skill -Use the current harness's built-in `create-skill` skill to author the skill. Placement: +Use the repository's skill-authoring workflow to author the skill. Follow its local authoring guidance for placement: - Path: preserve an existing mode skill's category. For a new mode, use the active Harness's project skill root and its user-level skill root when the user prefers a personal skill. - Handle: the user's first name or chosen identifier. - Frontmatter `description`: trigger on their name + `/-mode` + "work in their style", not on generic keywords like "write code" or "review PR". -- Frontmatter formatting: follow `create-skill`'s YAML rules. Keep `description` as one YAML scalar. Quote it or use `description: >-` with indented continuation lines when punctuation or wrapping requires it. +- Frontmatter formatting: follow the authoring workflow's YAML rules. Keep `description` as one YAML scalar. Quote it or use `description: >-` with indented continuation lines when punctuation or wrapping requires it. - Keep the mode explicit by default. Apply it on every turn only when the user asks for that behavior. ### 5. Iterate on prose -Apply the **unslop** skill and `create-skill`'s writing guidelines to every line. +Apply the **unslop** skill and the authoring workflow's writing guidelines to every line. Show the draft to the user and take feedback. Expect multiple iterations. Cut ruthlessly. A mode skill is not a manual. @@ -92,11 +92,11 @@ Work in a worktree off main. Commit and open a PR. Don't push to main directly. ## Evaluation -A `-mode` skill is subjective output. A `create-skill`-style test/iterate benchmark loop isn't useful here. Vibe-check with the user: does it read like them? Did it miss anything? Then ship. +A `-mode` skill is subjective output. A benchmark loop designed for generic skill authoring isn't useful here. Vibe-check with the user: does it read like them? Did it miss anything? Then ship. Run a description-optimization loop only if the skill's trigger accuracy turns out to be a problem in practice. ## When not to use -- User wants a task-specific skill (not working conventions): `create-skill` alone, no mining required. +- User wants a task-specific skill (not working conventions): use the authoring workflow alone, with no mining required. - User wants to capture one narrow workflow (e.g. "how I write commit messages"). That's a regular skill, not a mode skill. diff --git a/skills/how/SKILL.md b/skills/how/SKILL.md index 10a4446..0118079 100644 --- a/skills/how/SKILL.md +++ b/skills/how/SKILL.md @@ -28,7 +28,7 @@ Each explorer gets the prompt in `references/explorer-prompt.md` with its angle ## Step 2b. Direct Explain (simple questions) -Spawn one Task subagent that explores and explains in one pass: +Delegate one read-only synthesizer worker through the active Harness delegation interface. It explores and explains in one pass: - `worker role`: `synthesizer` - `model`: the active mstack `synthesizer` role, defaulting to `inherit-parent` @@ -38,7 +38,7 @@ Build its prompt from `references/explainer-prompt.md` without the explorer-find ## Step 3. Synthesize (complex questions only) -Once all explorers have returned, spawn one Task subagent to synthesize their findings into one explanation: +Once all explorers have returned, delegate one read-only synthesizer worker through the active Harness delegation interface to synthesize their findings into one explanation: - `worker role`: `synthesizer` - `model`: the active mstack `synthesizer` role, defaulting to `inherit-parent` diff --git a/skills/meta-mode/SKILL.md b/skills/meta-mode/SKILL.md index 4dabb22..cca0917 100644 --- a/skills/meta-mode/SKILL.md +++ b/skills/meta-mode/SKILL.md @@ -30,7 +30,7 @@ Remaining triggers: - Parallel fan-out → the **swarm** skill for coverage matrices, races, gauntlets, and exploration partitions. Use **arena** for design or code bakeoffs with base selection and grafting. - Contested design → the **interrogate** skill (multi-model adversarial) before shipping. - Nontrivial multi-step → write the throughput checkpoint (Feature step 3). -- Any prose surface → the **unslop** skill. Your reply is a prose surface. Write it per **Writing the reply**. Agent-facing prose also follows the **create-skill** skill (the active Harness skill-authoring flow). +- Any prose surface → the **unslop** skill. Your reply is a prose surface. Write it per **Writing the reply**. Agent-facing prose also follows the repository's skill-authoring guidance. - Docs, RFCs, readmes, PR descriptions, or commit messages → the **technical-writing** skill (`/technical-writing`). - Before commit → the active Harness cleanup command. - Before review → the **no-comments** skill (`/no-comments`). diff --git a/skills/meta-mode/playbooks/babysit.md b/skills/meta-mode/playbooks/babysit.md index daa2fe1..2a9a29b 100644 --- a/skills/meta-mode/playbooks/babysit.md +++ b/skills/meta-mode/playbooks/babysit.md @@ -1,6 +1,6 @@ ### Babysit -**You own the merge frontier. Declare a mode, clear one PR at a time, stop where the human's call begins.** This playbook replaces the current harness's built-in babysit skill for these requests, so do not route there even though its description matches the same words. A request to land or ship is `playbooks/shipping.md`, which begins where this playbook ends. +**You own the merge frontier. Declare a mode, clear one PR at a time, stop where the human's call begins.** Use this playbook for babysitting requests instead of routing to another skill with overlapping trigger words. A request to land or ship is `playbooks/shipping.md`, which begins where this playbook ends. Babysitting starts when the user asks for it, which is normally once a phase or a whole stack is built, not when a PR opens. Finish the stack, get it green here, then land it through Shipping. diff --git a/skills/meta-mode/playbooks/multi-phase-plan.md b/skills/meta-mode/playbooks/multi-phase-plan.md index b4c8521..e6a49e6 100644 --- a/skills/meta-mode/playbooks/multi-phase-plan.md +++ b/skills/meta-mode/playbooks/multi-phase-plan.md @@ -4,7 +4,7 @@ 1. When the change is one or two files with an obvious approach, skip the plan. Say so and stop. 2. Settle open questions by prototype before you write. Run `playbooks/prototype.md` for each. Keep the branch, the SHA, and the screenshots for Appendix A. Ask the operator only about a product or preference call that no run can settle. Give options (the **never-block-on-the-human** principle skill). -3. Explore in workers with `worker type: "meta-agent"` and an explicit model per the Subagents section (the **guard-the-context-window** principle skill). Each returns file pointers, conventions, test commands, and entry points. No inlined dumps. +3. Explore in workers with `worker role: "explorer"` and an explicit model per the Subagents section (the **guard-the-context-window** principle skill). Each returns file pointers, conventions, test commands, and entry points. No inlined dumps. 4. Copy the skeleton below into the plan file and fill every placeholder. Unless the operator names a path, write the file under the agent store's `docs/`. Keep every heading and every sub-block in the order shown. One section per PR. One PR is one change with its own evidence (the **sequence-verifiable-units** principle skill). Name the execution playbook in **How to read this**. Pick between `playbooks/autopilot-full.md` and `playbooks/autopilot-stack.md` per the rule at the end of `playbooks/autopilot-stack.md`. A standing program takes `playbooks/orchestrate.md`. 5. Write with the `technical-writing` skill in full, then apply `unslop`. The body is one Diátaxis mode, how-to. Appendices hold explanation and reference. Each heading states the task or the finding. No long dashes. No mid-sentence colons. 6. Run `node "/check-plan.mjs" ` and fix every line it prints (the **encode-lessons-in-structure** principle skill). diff --git a/skills/no-comments/SKILL.md b/skills/no-comments/SKILL.md index 02cf554..1fd9a92 100644 --- a/skills/no-comments/SKILL.md +++ b/skills/no-comments/SKILL.md @@ -1,13 +1,13 @@ --- name: no-comments -description: "Spawn Comment Sicko, fix accepted findings, and offer encodings for claimed constraints." +description: "Delegate comment review, fix accepted findings, and offer encodings for claimed constraints." --- # No comments -Spawn Comment Sicko. Act on accepted findings. +Delegate a comment-review worker and act on accepted findings. -Defer to Comment Sicko's fresh perspective. +Defer to the comment-review worker's fresh perspective. ## Scope @@ -15,7 +15,7 @@ Use the caller's files or diff. Otherwise use the current diff against the base ## Steps -1. Spawn `Task` with `worker type: "Comment Sicko"`. Pass the scope. Do not restate its rules. +1. Delegate one read-only worker with the `reviewer` role through the active Harness delegation interface, scoped to comment review. When the optional `agents/comment-reviewer.md` instructions are available, pass them to the worker. Do not restate its rules. If delegation is unavailable, perform the same review directly. 2. Inspect its report and diff. Reject application-code edits, scope escapes, exception-protected deletions, misstated `MUST KILL` reasons, and flags that treat kept intentional code as guilty. Reshape flags on our-code surprises stay actionable. Do not restore those comments. A keep survives only with proof it is about something we cannot change. Audit missed scoped lint and TypeScript suppressions. Correctness or safety suppressions stay actionable `MUST KILL`s. Restore deletions only with exact exceptions and scoped proof. Before accepting thin `IMPORTANT` or `do not remove` kills or keeps, run `/how` or `/why` on their symbol. If a kill is ambiguous, do not restore. If a keep is refuted or still ambiguous, delete it. Revert and rerun one rejected report with the failure named. Reject a second, report it open, and fail `/no-comments`. 3. Fix trivial accepted flags directly by deleting a dead path, dropping a parameter, or using the real API. If any fix needs a shape, run `/architect` once for the accepted set and surrounding code. Stop at the sketch. Architect shapes. Step 4 implements. 4. Implement the smallest root-cause fix in scope. Remove every named workaround. If the root cause is out of scope, land the smallest in-scope fix and report the rest open. The **principle-fix-root-causes** and **principle-redesign-from-first-principles** skills guide intent only. Neither authorizes widening the fence nor fixing instances outside it. Never bolt on symptom guards. diff --git a/skills/reflect/SKILL.md b/skills/reflect/SKILL.md index 08b8afe..1c498f1 100644 --- a/skills/reflect/SKILL.md +++ b/skills/reflect/SKILL.md @@ -54,9 +54,9 @@ Backlog items file to whatever devex / backlog tracker your team uses automatica For each approved Accepted item, follow the Routing field exactly: - Trivial existing-skill edit (a one-line bullet, a tightened sentence, a stale fact corrected): parent does directly. -- Substantive existing-skill edit (a new section, a new pattern table, more than ~10 lines): hand to the current harness's built-in `create-skill` skill and run its draft / test / iterate loop. -- `tune description: ` (the skill exists but didn't trigger when it should have): hand to `create-skill` and run its description-optimization loop. -- `new skill via create-skill: `: hand creation to `create-skill`. Do not invent the shape ad hoc. +- Substantive existing-skill edit (a new section, a new pattern table, more than ~10 lines): hand to the repository's skill-authoring workflow and run its draft / test / iterate loop. +- `tune description: ` (the skill exists but didn't trigger when it should have): hand to the skill-authoring workflow and run its description-optimization loop. +- `new skill via authoring workflow: `: hand creation to the skill-authoring workflow. Do not invent the shape ad hoc. If your environment ships a SKILL.md validator, run it on every touched skill before declaring done. Skip this step if it doesn't. diff --git a/skills/reflect/references/synthesizer.md b/skills/reflect/references/synthesizer.md index d0e7819..93e91d0 100644 --- a/skills/reflect/references/synthesizer.md +++ b/skills/reflect/references/synthesizer.md @@ -14,7 +14,7 @@ Apply each criterion to every finding: - Durability: still true in 6 months once paths, SHAs, tool versions, and code shapes have changed. - Specificity: broad enough to apply across tasks, precise enough that a future agent recognizes when to use it. Reject vague platitudes ("write good code") and hyper-specific facts ("`` has 175 tokens at limit 80"). -- Existing-skill-first: propose `new skill via create-skill:` only when no existing skill is a real home, the pattern recurs, and the topic deserves its own skill. +- Existing-skill-first: propose `new skill via authoring workflow:` only when no existing skill is a real home, the pattern recurs, and the topic deserves its own skill. - Convergence: findings echoed by 2+ reviewers carry higher confidence. Singletons must clear a higher bar on the other criteria. - Decision-changing: a future agent does something different because of the edit, not just reads more text. - Structural-mechanism check: route to Backlog when a lint rule, script, metadata flag, or runtime check already enforces the rule or could enforce it cheaply. Skill prose is for things mechanisms cannot enforce. @@ -41,7 +41,7 @@ Output exactly the format below. No preamble, no narration. One sentence per cel |---|---|---| | | | | | | | > | -| | | > | +| | | > | One row per finding. The user approves row by row. diff --git a/skills/setup-mstack/SKILL.md b/skills/setup-mstack/SKILL.md index 4b0f776..cf60fe8 100644 --- a/skills/setup-mstack/SKILL.md +++ b/skills/setup-mstack/SKILL.md @@ -27,6 +27,9 @@ Use these roles: - `reviewer` for review and verification workers. - `judge` for comparisons and final decisions. - `explorer` for read-only repository exploration. +- `synthesizer` for combining findings into one answer or artifact. +- `candidate` for independent alternatives evaluated by an arena. +- `operator` for environment or lifecycle operations. Write the complete file on every run. A later run must produce the same file when the choices have not changed. @@ -39,7 +42,10 @@ Example: "implementer": "inherit-parent", "reviewer": "inherit-parent", "judge": "inherit-parent", - "explorer": "inherit-parent" + "explorer": "inherit-parent", + "synthesizer": "inherit-parent", + "candidate": "inherit-parent", + "operator": "inherit-parent" }, "overrides": { "codex": {}, From 63bbddb936f9ecaadd97022ad7e93cf7c4e800fe Mon Sep 17 00:00:00 2001 From: 3metaJun <251347867+3metaJun@users.noreply.github.com> Date: Fri, 11 Sep 2026 15:35:38 +0800 Subject: [PATCH 2/2] fix: close PR review gaps --- README.md | 7 ++-- profiles/upstream-manifest.json | 2 +- scripts/check-upstream.mjs | 8 ++++- scripts/context.test.mjs | 46 +++++++++++++++++++++++++ scripts/run-role.mjs | 2 +- scripts/skill-integrity.test.mjs | 7 ++-- scripts/smoke-harnesses.mjs | 2 +- scripts/sync-upstream.test.mjs | 58 ++++++++++++++++++++++++++++++++ skills/reflect/SKILL.md | 4 +-- 9 files changed, 122 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index bb67fd3..8069250 100644 --- a/README.md +++ b/README.md @@ -266,9 +266,10 @@ reviewed body digest for every canonical pstack skill, reports the portable skill count, and reports the state of the agents, Benny automation, guide, and meta-mode tools. Add `--strict` to fail when a canonical skill body or configured artifact is missing, differs, or remains after removal upstream. -Strict mode requires -the source to be a clean Git checkout whose `HEAD` exactly matches the pinned -commit. +Strict mode requires the source to be a clean Git checkout whose `HEAD` exactly +matches the pinned commit. If you intentionally edit a canonical skill, update +its reviewed target digest in `canonicalSkills` as part of that review; sync +preserves these entries and does not silently re baseline them. To preview and apply a transformed refresh of those non-skill artifacts: diff --git a/profiles/upstream-manifest.json b/profiles/upstream-manifest.json index 03ec700..2c600a5 100644 --- a/profiles/upstream-manifest.json +++ b/profiles/upstream-manifest.json @@ -213,7 +213,7 @@ }, "reflect": { "source": "6a4d4ccaace9ce88c3e268fcbedf3cfd40e527162a5e5aad6de8edbaf5e2b83f", - "target": "b0f292d4503c3591b21498718af8c90feaf2c4cf0ba25b0a7e374c7b42f7b0c1" + "target": "baedc055aceb55ed320db637c37a7e37270f5cbd654b41bd6246b158e172a5d4" }, "setup-mstack": { "source": "a11137437e12831c73f228f449bdfefab487bf7e2c69a4b306189e8fb1e6ab3e", diff --git a/scripts/check-upstream.mjs b/scripts/check-upstream.mjs index 4a4c899..9f2ce40 100644 --- a/scripts/check-upstream.mjs +++ b/scripts/check-upstream.mjs @@ -335,7 +335,9 @@ if (!existsSync(manifestPath)) { if (manifest) { const canonicalSkills = manifest.canonicalSkills; if (!canonicalSkills || typeof canonicalSkills !== "object" || Array.isArray(canonicalSkills)) { - console.warn("Upstream manifest has no canonicalSkills body baselines; skill body drift was not checked."); + const message = "Upstream manifest has no valid canonicalSkills body baselines; skill body drift was not checked."; + (strict ? console.error : console.warn)(message); + if (strict) artifactProblems += 1; } else { const canonicalProblems = []; for (const sourceName of readdirSync(sourceSkills, { withFileTypes: true }) @@ -345,6 +347,10 @@ if (!existsSync(manifestPath)) { const entry = canonicalSkills[targetName]; const sourcePath = resolveInside(sourceRoot, join("skills", sourceName, "SKILL.md"), `canonical source skill ${sourceName}`); const targetPath = resolveInside(targetRoot, join("skills", targetName, "SKILL.md"), `canonical target skill ${targetName}`); + if (!existsSync(sourcePath)) { + canonicalProblems.push(`${targetName}: upstream source SKILL.md is missing`); + continue; + } if (!entry || typeof entry !== "object" || Array.isArray(entry)) { canonicalProblems.push(`${targetName}: missing manifest entry`); continue; diff --git a/scripts/context.test.mjs b/scripts/context.test.mjs index 91da307..cd17e53 100644 --- a/scripts/context.test.mjs +++ b/scripts/context.test.mjs @@ -18,6 +18,7 @@ const optimizer = resolve("scripts", "optimize-context.mjs"); const reconciler = resolve("scripts", "reconcile-context.mjs"); const modelConfig = resolve("scripts", "model-config.mjs"); const runRole = resolve("scripts", "run-role.mjs"); +const smokeHarnesses = resolve("scripts", "smoke-harnesses.mjs"); function fixture(prefix) { return mkdtempSync(join(tmpdir(), `${prefix} 测试-`)); @@ -221,3 +222,48 @@ test("run-role resolves a configured model override", () => { rmSync(root, { recursive: true, force: true }); } }); + +test("run-role expands a Windows-style home path for an explicit model file", () => { + const root = fixture("run-role-home-path"); + const configPath = join(root, "models.json"); + try { + writeFileSync( + configPath, + `${JSON.stringify({ roles: { implementer: "parent-model" }, overrides: { opencode: { implementer: "remote-model" } } })}\n`, + "utf8", + ); + const result = run(runRole, [ + "--harness", + "opencode", + "--role", + "implementer", + "--prompt", + "inspect the repository", + "--file", + "~\\models.json", + ], { + HOME: root, + USERPROFILE: root, + }); + assert.equal(result.status, 0, result.stderr); + assert.equal(JSON.parse(result.stdout).model, "remote-model"); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test("smoke harnesses expands a Windows-style home path for skill targets", () => { + const root = fixture("smoke-home-path"); + try { + const result = run(smokeHarnesses, ["--harness", "codex", "--skill", "meta-mode", "--json"], { + HOME: root, + USERPROFILE: root, + HARNESS_SKILLS_CODEX_DIR: "~\\codex skills", + }); + const entries = JSON.parse(result.stdout); + assert.equal(entries.length, 1); + assert.equal(entries[0].target, join(root, "codex skills")); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); diff --git a/scripts/run-role.mjs b/scripts/run-role.mjs index 2d81f36..c90af78 100644 --- a/scripts/run-role.mjs +++ b/scripts/run-role.mjs @@ -26,7 +26,7 @@ function requireValue(flag) { function expandHome(path) { if (path === "~") return homedir(); - return path.startsWith("~/") ? join(homedir(), path.slice(2)) : path; + return path.startsWith("~/") || path.startsWith("~\\") ? join(homedir(), path.slice(2)) : path; } const harness = requireValue("--harness"); diff --git a/scripts/skill-integrity.test.mjs b/scripts/skill-integrity.test.mjs index 3bcc38b..3db4647 100644 --- a/scripts/skill-integrity.test.mjs +++ b/scripts/skill-integrity.test.mjs @@ -88,10 +88,7 @@ test("canonical skill manifest detects body drift", () => { assert.ok(existsSync(join(skillsRoot, name, "SKILL.md")), `${name} manifest entry has no target skill`); const content = readSkill(name); assert.equal(entries[name].target, normalizedDigest(content), `${name} body differs from its reviewed baseline`); - assert.notEqual( - entries[name].target, - normalizedDigest(`${content}\nThis mutation must invalidate the canonical body digest.`), - `${name} digest must change when its body changes`, - ); + const truncated = content.slice(0, Math.max(1, Math.floor(content.length / 2))); + assert.notEqual(entries[name].target, normalizedDigest(truncated), `${name} digest must detect truncation`); } }); diff --git a/scripts/smoke-harnesses.mjs b/scripts/smoke-harnesses.mjs index 9d96955..944fded 100644 --- a/scripts/smoke-harnesses.mjs +++ b/scripts/smoke-harnesses.mjs @@ -18,7 +18,7 @@ function valueAfter(flag) { function expandHome(path) { if (path === "~") return homedir(); - return path?.startsWith("~/") ? join(homedir(), path.slice(2)) : path; + return path?.startsWith("~/") || path?.startsWith("~\\") ? join(homedir(), path.slice(2)) : path; } function targetFor(harness) { diff --git a/scripts/sync-upstream.test.mjs b/scripts/sync-upstream.test.mjs index 1f8564e..f38eb2f 100644 --- a/scripts/sync-upstream.test.mjs +++ b/scripts/sync-upstream.test.mjs @@ -269,6 +269,7 @@ function fixture({ initializeGit = true } = {}) { write(join(source, "agents", "poteto-agent.md"), "---\nname: poteto-agent\n---\n"); write(join(source, "automations", "benny", "README.md"), "pstack uses poteto-mode.\n"); write(join(source, "docs", "guide", "02-poteto-mode.md"), "Use /poteto-mode with pstack.\nCursor confirms.\n"); + write(join(source, "skills", "poteto-mode", "SKILL.md"), "---\nname: poteto-mode\ndescription: fixture\n---\n"); write(join(source, "skills", "poteto-mode", "scripts", "tool.mjs"), "const mode = 'poteto-mode';\n"); write(join(source, "skills", "sample", "SKILL.md"), "---\nname: sample\ndescription: fixture\n---\n"); let commit = "0".repeat(40); @@ -300,9 +301,66 @@ function fixture({ initializeGit = true } = {}) { }, }, }, null, 2)); + write(join(target, "profiles", "upstream-manifest.json"), JSON.stringify({ + source: "https://example.test/pstack", + commit, + canonicalSkills: { + "meta-mode": { + source: sha256("---\nname: meta-mode\ndescription: fixture\n---\n"), + target: sha256("---\nname: meta-mode\ndescription: fixture\n---\n"), + }, + sample: { + source: sha256("---\nname: sample\ndescription: fixture\n---\n"), + target: sha256("---\nname: sample\ndescription: fixture\n---\n"), + }, + }, + artifacts: {}, + }, null, 2)); return { root, source, target }; } +test("strict canonical checks reject body mutation and diagnose a missing source body", () => { + const { root, source, target } = fixture(); + try { + applyBaseline(source, target); + write(join(target, "skills", "sample", "SKILL.md"), "---\nname: sample\ndescription: truncated\n---\n"); + const mutated = run(checker, ["--source", source, "--target", target, "--strict"]); + assert.notEqual(mutated.status, 0); + assert.match(mutated.stderr, /sample: target body changed/); + + write(join(target, "skills", "sample", "SKILL.md"), "---\nname: sample\ndescription: fixture\n---\n"); + unlinkSync(join(source, "skills", "sample", "SKILL.md")); + const commit = commitSourceChange(source, "remove sample skill body"); + updateProfile(target, (pstack) => { pstack.commit = commit; }); + const missing = run(checker, ["--source", source, "--target", target, "--strict"]); + assert.notEqual(missing.status, 0); + assert.match(missing.stderr, /sample: upstream source SKILL\.md is missing/); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test("strict canonical checks reject a missing or malformed manifest block", () => { + const { root, source, target } = fixture(); + try { + applyBaseline(source, target); + const manifestPath = join(target, "profiles", "upstream-manifest.json"); + const manifest = JSON.parse(readFileSync(manifestPath, "utf8")); + delete manifest.canonicalSkills; + write(manifestPath, JSON.stringify(manifest)); + const missing = run(checker, ["--source", source, "--target", target, "--strict"]); + assert.notEqual(missing.status, 0); + assert.match(missing.stderr, /no valid canonicalSkills body baselines/); + manifest.canonicalSkills = []; + write(manifestPath, JSON.stringify(manifest)); + const malformed = run(checker, ["--source", source, "--target", target, "--strict"]); + assert.notEqual(malformed.status, 0); + assert.match(malformed.stderr, /no valid canonicalSkills body baselines/); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + test("upstream sync previews, transforms, and protects changed files", () => { const { root, source, target } = fixture(); try { diff --git a/skills/reflect/SKILL.md b/skills/reflect/SKILL.md index 1c498f1..20853f8 100644 --- a/skills/reflect/SKILL.md +++ b/skills/reflect/SKILL.md @@ -54,8 +54,8 @@ Backlog items file to whatever devex / backlog tracker your team uses automatica For each approved Accepted item, follow the Routing field exactly: - Trivial existing-skill edit (a one-line bullet, a tightened sentence, a stale fact corrected): parent does directly. -- Substantive existing-skill edit (a new section, a new pattern table, more than ~10 lines): hand to the repository's skill-authoring workflow and run its draft / test / iterate loop. -- `tune description: ` (the skill exists but didn't trigger when it should have): hand to the skill-authoring workflow and run its description-optimization loop. +- Substantive existing-skill edit (a new section, a new pattern table, more than ~10 lines): hand to [the repository authoring playbook](../meta-mode/playbooks/authoring-a-skill.md) and run its draft, validation, and review steps. +- `tune description: ` (the skill exists but didn't trigger when it should have): use the repository authoring playbook's description review steps. - `new skill via authoring workflow: `: hand creation to the skill-authoring workflow. Do not invent the shape ad hoc. If your environment ships a SKILL.md validator, run it on every touched skill before declaring done. Skip this step if it doesn't.