From aa06d106cdd451f7e0332126743c51d632c588cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=A6=E5=85=89=E5=85=89?= <2251816159@qq.com> Date: Sun, 7 Jun 2026 10:10:23 +0800 Subject: [PATCH] Clarify approved-plan handoff and installer sync --- .github/workflows/validate.yml | 20 ++++++ INSTALL.md | 3 + README.md | 7 +++ scripts/install-adapters.mjs | 62 ++++++++++++++++++- scripts/test-install-adapters.mjs | 59 ++++++++++++++++++ scripts/validate-skill.mjs | 59 +++++++++++++++++- skills/crossframe-code/SKILL.md | 1 + .../evals/dual-core-routing-conflict-tests.md | 46 +++++++++++--- skills/crossframe-coder/SKILL.md | 19 +++++- .../references/handoff-to-crossframe-code.md | 17 ++++- .../references/source-driven-api-check.md | 16 +++++ 11 files changed, 294 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/validate.yml create mode 100644 scripts/test-install-adapters.mjs diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..12f8f0a --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,20 @@ +name: Validate CrossFrame Skills + +on: + pull_request: + push: + branches: + - main + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + - name: Validate skill structure + run: node scripts/validate-skill.mjs + - name: Test installer behavior + run: node scripts/test-install-adapters.mjs diff --git a/INSTALL.md b/INSTALL.md index e96463a..8b4be59 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -79,4 +79,7 @@ This copies `AGENTS.md` and both skill folders to `.agent-skills/` inside the ta - Use `--dry-run` to preview writes. - Existing files and directories are not overwritten unless `--force` is provided. +- For Codex self-installs, the installer copies `crossframe-coder` before `crossframe-code` so the suite is less likely to be left half-installed if the host reloads the active diagnosis skill. +- Identical skill directories are reported as `SKIP ... (identical)` even with `--force`; this avoids touching active skill directories unnecessarily. +- Non-identical skill directories are synchronized before use; stale destination files are pruned by the installer. - Platform adapters are intentionally thin. They point agents to `skills/crossframe-code/SKILL.md` instead of duplicating the full skill instructions. diff --git a/README.md b/README.md index f0b0c53..c15c825 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,13 @@ Run the static validation script from the repository root: node scripts/validate-skill.mjs ``` +CI should run the same validator and installer smoke test used locally: + +```bash +node scripts/validate-skill.mjs +node scripts/test-install-adapters.mjs +``` + The validator checks plugin metadata, skill frontmatter, referenced files, architecture lenses, templates, eval coverage, Golden Master rules, local-project risk scan rules, review scope and stack-convention rules, settlement-consistency constraints, and installation isolation. ## Influences and Attribution diff --git a/scripts/install-adapters.mjs b/scripts/install-adapters.mjs index 08e8410..c7f22e5 100644 --- a/scripts/install-adapters.mjs +++ b/scripts/install-adapters.mjs @@ -47,10 +47,23 @@ if (platforms.size === 0) { process.exit(1); } +validateInstallPlan(); + for (const platform of platforms) { install(platform); } +function validateInstallPlan() { + for (const platform of platforms) { + if (!["codex", "claude", "cursor", "gemini", "generic"].includes(platform)) { + throw new Error(`Unsupported platform: ${platform}`); + } + if (["cursor", "gemini", "generic"].includes(platform) && !target) { + throw new Error(`--target is required for ${platform}`); + } + } +} + function install(platform) { if (platform === "codex") { const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex"); @@ -118,7 +131,54 @@ function copyDir(source, destination) { } logWrite("dir", source, destination); if (dryRun) return; - fs.cpSync(source, destination, { recursive: true, force }); + fs.mkdirSync(destination, { recursive: true }); + copyDirectoryContents(source, destination); + pruneStaleDestination(source, destination); + if (!directoriesMatch(source, destination)) { + throw new Error(`Directory sync did not produce an exact match: ${destination}`); + } +} + +function copyDirectoryContents(source, destination) { + const entries = fs.readdirSync(source, { withFileTypes: true }); + for (const entry of entries) { + const sourcePath = path.join(source, entry.name); + const destinationPath = path.join(destination, entry.name); + if (entry.isDirectory()) { + fs.mkdirSync(destinationPath, { recursive: true }); + copyDirectoryContents(sourcePath, destinationPath); + } else if (entry.isFile()) { + fs.mkdirSync(path.dirname(destinationPath), { recursive: true }); + fs.copyFileSync(sourcePath, destinationPath); + } + } +} + +function pruneStaleDestination(source, destination) { + const sourceFiles = new Set(collectFiles(source)); + const destinationFiles = collectFiles(destination); + for (const relativePath of destinationFiles) { + if (!sourceFiles.has(relativePath)) { + const stalePath = path.join(destination, relativePath); + fs.rmSync(stalePath, { force: true }); + logSkip("file", path.join(source, relativePath), stalePath, "removed stale destination file"); + } + } + pruneEmptyDirs(destination); +} + +function pruneEmptyDirs(root, relativeRoot = "") { + const absoluteRoot = path.join(root, relativeRoot); + if (!fs.existsSync(absoluteRoot)) return; + const entries = fs.readdirSync(absoluteRoot, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + pruneEmptyDirs(root, path.join(relativeRoot, entry.name)); + } + } + if (relativeRoot && fs.readdirSync(absoluteRoot).length === 0) { + fs.rmdirSync(absoluteRoot); + } } function directoriesMatch(source, destination) { diff --git a/scripts/test-install-adapters.mjs b/scripts/test-install-adapters.mjs new file mode 100644 index 0000000..5beb393 --- /dev/null +++ b/scripts/test-install-adapters.mjs @@ -0,0 +1,59 @@ +import assert from "node:assert/strict"; +import childProcess from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +function runInstall(home, args = ["--platform", "codex", "--force"]) { + return childProcess.spawnSync(process.execPath, ["scripts/install-adapters.mjs", ...args], { + cwd: repoRoot, + env: { + ...process.env, + CODEX_HOME: home, + CLAUDE_HOME: path.join(home, ".claude"), + }, + encoding: "utf8", + }); +} + +function assertSuccess(result) { + assert.equal(result.status, 0, result.stderr || result.stdout); +} + +const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "crossframe-install-")); + +try { + let result = runInstall(tempHome); + assertSuccess(result); + + assert.ok(fs.existsSync(path.join(tempHome, "skills", "crossframe-coder", "SKILL.md"))); + assert.ok(fs.existsSync(path.join(tempHome, "skills", "crossframe-code", "SKILL.md"))); + + const stale = path.join(tempHome, "skills", "crossframe-code", "obsolete-reference.md"); + fs.writeFileSync(stale, "old file"); + + result = runInstall(tempHome); + assertSuccess(result); + assert.equal(fs.existsSync(stale), false, "stale file should be pruned"); + assert.match(result.stdout, /removed stale destination file/); + + result = runInstall(tempHome); + assertSuccess(result); + assert.match(result.stdout, /SKIP dir: .*crossframe-coder.*identical/); + assert.match(result.stdout, /SKIP dir: .*crossframe-code.*identical/); + + const allHome = fs.mkdtempSync(path.join(os.tmpdir(), "crossframe-install-all-")); + try { + result = runInstall(allHome, ["--all", "--force"]); + assert.notEqual(result.status, 0, "--all without --target should fail before writes"); + assert.match(result.stderr, /--target is required/); + assert.equal(fs.existsSync(path.join(allHome, "skills")), false, "--all preflight should not install Codex skills first"); + } finally { + fs.rmSync(allHome, { recursive: true, force: true }); + } +} finally { + fs.rmSync(tempHome, { recursive: true, force: true }); +} diff --git a/scripts/validate-skill.mjs b/scripts/validate-skill.mjs index 16318fe..33cdfbd 100644 --- a/scripts/validate-skill.mjs +++ b/scripts/validate-skill.mjs @@ -32,6 +32,10 @@ function check(condition, name, detail = "") { else fail(name, detail); } +function occurrenceCount(text, phrase) { + return text.split(phrase).length - 1; +} + let plugin; try { plugin = JSON.parse(read(path.join(".codex-plugin", "plugin.json"))); @@ -268,6 +272,7 @@ check(skillText.includes("templates/debug-observation-output.md"), "SKILL.md ref check(skillText.includes("templates/source-driven-output.md"), "SKILL.md references source-driven output"); check(skillText.includes("In dual-core use, prefer handing clear implementation requests to `crossframe-coder`"), "SKILL.md prefers coder handoff for clear implementation"); check(skillText.includes("Keep direct implementation in `crossframe-code` only when no coder skill is available"), "SKILL.md keeps crossframe-code implementation as fallback"); +check(skillText.includes("evals/dual-core-routing-conflict-tests.md"), "crossframe-code Trial Materials lists dual-core routing conflict eval"); check(skillText.includes("Fixing suspicious legacy output"), "SKILL.md rejects premature suspicious-output fixes"); const problemRouter = exists(path.join("references", "problem-router.md"), skillRoot) @@ -738,15 +743,25 @@ const dualCoreConflictEval = exists(path.join("evals", "dual-core-routing-confli check(Boolean(dualCoreConflictEval), "dual-core routing conflict eval exists"); for (const phrase of [ "Vague Bugfix Without Failure Evidence", - "Approved Deep Risk Plan Implementation", + "Missing Approved Deep Risk Plan", + "Present Approved Deep Risk Plan", "Review And Fix In One Prompt", "Security Shortcut Request", "Clear Local Implementation", ]) { check(dualCoreConflictEval.includes(phrase), `dual-core routing conflict eval includes ${phrase}`); } -check(dualCoreConflictEval.includes("review first"), "dual-core routing conflict eval requires review before fix"); -check(dualCoreConflictEval.includes("crossframe-coder may implement only named files"), "dual-core routing conflict eval constrains approved plan implementation"); +check(dualCoreConflictEval.includes("Review first"), "dual-core routing conflict eval requires review before fix"); +check(dualCoreConflictEval.includes("No new files unless the plan names them"), "dual-core routing conflict eval forbids unplanned new files"); +check(dualCoreConflictEval.includes("Run exactly the listed verification first"), "dual-core routing conflict eval constrains approved plan verification"); +check( + occurrenceCount(dualCoreConflictEval, "crossframe-coder may implement only named files and planned verification.") === 0, + "dual-core routing conflict eval removes duplicate approved-plan line" +); +check( + occurrenceCount(dualCoreConflictEval, "review first, pin review scope, and separate blocking findings from suggestions.") === 0, + "dual-core routing conflict eval removes duplicate review-first line" +); const ledgerLiteEvalPath = path.join("evals", "ledgerlite-project-risk-scan-smoke-tests.md"); const ledgerLiteEval = exists(ledgerLiteEvalPath, skillRoot) ? read(ledgerLiteEvalPath, skillRoot) : ""; @@ -775,6 +790,10 @@ check(coderText.includes("Edit code only when the user clearly asks"), "crossfra check(coderText.includes("hand off to `crossframe-code`"), "crossframe-coder hands off unclear or high-risk work"); check(coderText.includes("Fresh verification is required"), "crossframe-coder requires fresh verification"); check(coderText.includes("verification blocked"), "crossframe-coder uses blocked verification status"); +check(coderText.includes("Approved High-Risk Plan Exception"), "crossframe-coder defines approved high-risk plan exception"); +check(coderText.includes("no unresolved required confirmations"), "crossframe-coder approved plan exception requires confirmations to be resolved"); +check(coderText.includes("first safe slice"), "crossframe-coder approved plan exception limits implementation to first safe slice"); +check(coderText.includes("evals/golden-implementation-reports.md"), "crossframe-coder Trial Materials lists golden implementation reports"); const coderReferenced = new Set( Array.from(coderText.matchAll(/`([^`]+\.(?:md|yaml|json))`/g), (match) => match[1]).filter((relativePath) => @@ -812,6 +831,15 @@ const coderHandoff = exists(path.join("references", "handoff-to-crossframe-code. : ""; check(coderHandoff.includes("auth") && coderHandoff.includes("tenant") && coderHandoff.includes("billing"), "crossframe-coder handoff covers high-risk boundaries"); check(coderHandoff.includes("Verification cannot be defined"), "crossframe-coder handoff covers missing verification"); +check(coderHandoff.includes("unless the Approved High-Risk Plan Exception is satisfied"), "handoff reference allows approved high-risk plan exception"); +check(coderHandoff.includes("Approved Plan Exception Check"), "handoff reference includes approved plan exception checklist"); + +const coderSourceDriven = exists(path.join("references", "source-driven-api-check.md"), coderRoot) + ? read(path.join("references", "source-driven-api-check.md"), coderRoot) + : ""; +check(coderSourceDriven.includes("Implementation Decision Table"), "crossframe-coder source-driven check includes implementation decision table"); +check(coderSourceDriven.includes("Prefer local installed version"), "crossframe-coder source-driven check prefers local installed version"); +check(coderSourceDriven.includes("approved plan exception"), "crossframe-coder source-driven check routes high-risk SDK work through approved plan exception"); const coderVerification = exists(path.join("references", "verification-matrix.md"), coderRoot) ? read(path.join("references", "verification-matrix.md"), coderRoot) @@ -896,6 +924,7 @@ check(readme.includes("not automatically installed unless the user runs `scripts check(readme.includes("Platform Adapters"), "README includes platform adapters section"); check(readme.includes("Claude Code") && readme.includes("Cursor") && readme.includes("Gemini CLI"), "README names supported non-Codex platforms"); check(readme.includes("install-adapters.mjs"), "README documents install adapter script"); +check(readme.includes("node scripts/test-install-adapters.mjs"), "README documents installer smoke test command"); check(readme.includes("Influences and Attribution"), "README includes influences and attribution section"); check(readme.includes("felipereisdev/code-review-skill"), "README attributes felipereisdev code-review-skill"); check(readme.includes("review scope selection") && readme.includes("stack detection") && readme.includes("project convention-first"), "README names borrowed review-scope stack-convention ideas"); @@ -905,6 +934,9 @@ const installDoc = exists("INSTALL.md", repoRoot) ? read("INSTALL.md", repoRoot) check(installDoc.includes("Installing CrossFrame Code"), "INSTALL.md exists and has title"); check(installDoc.includes("skills/crossframe-coder/SKILL.md"), "INSTALL.md documents crossframe-coder entrypoint"); check(installDoc.includes("both skills"), "INSTALL.md says installer copies both skills"); +check(installDoc.includes("copies `crossframe-coder` before `crossframe-code`"), "INSTALL.md documents Codex self-install order"); +check(installDoc.includes("SKIP") && installDoc.includes("identical"), "INSTALL.md documents identical skip behavior"); +check(installDoc.includes("stale destination files"), "INSTALL.md documents stale file pruning"); for (const phrase of ["Codex", "Claude Code", "Cursor", "Gemini CLI", "Generic Agents", "--dry-run", "--force"]) { check(installDoc.includes(phrase), `INSTALL.md documents ${phrase}`); } @@ -946,6 +978,27 @@ check(installScript.includes("codex") && installScript.includes("claude") && ins check(installScript.includes("crossframe-code") && installScript.includes("crossframe-coder"), "install script installs both skills"); check(installScript.includes('["crossframe-coder", "crossframe-code"]'), "install script copies coder before code for Codex self-updates"); check(installScript.includes("directoriesMatch") && installScript.includes("SKIP"), "install script skips identical skill directories"); +check(installScript.includes("validateInstallPlan"), "install script validates full install plan before writing"); +check(installScript.includes("copyDirectoryContents") && installScript.includes("copyFileSync"), "install script copies directories without fs.cpSync"); +check(installScript.includes("pruneStaleDestination"), "install script prunes stale destination files"); +check(installScript.includes("Directory sync did not produce an exact match"), "install script verifies post-copy exact sync"); +check(installScript.includes("removed stale destination file"), "install script logs stale destination pruning"); + +const installTest = exists(path.join("scripts", "test-install-adapters.mjs"), repoRoot) + ? read(path.join("scripts", "test-install-adapters.mjs"), repoRoot) + : ""; +check(Boolean(installTest), "install adapter executable smoke test exists"); +check(installTest.includes("CODEX_HOME"), "install smoke test uses isolated CODEX_HOME"); +check(installTest.includes("obsolete-reference.md"), "install smoke test covers stale file pruning"); +check(installTest.includes("SKIP dir"), "install smoke test covers identical skip"); +check(installTest.includes("--all") && installTest.includes("--target is required"), "install smoke test covers --all target preflight"); + +const validateWorkflow = exists(path.join(".github", "workflows", "validate.yml"), repoRoot) + ? read(path.join(".github", "workflows", "validate.yml"), repoRoot) + : ""; +check(Boolean(validateWorkflow), "GitHub Actions validation workflow exists"); +check(validateWorkflow.includes("node scripts/validate-skill.mjs"), "workflow runs validator"); +check(validateWorkflow.includes("node scripts/test-install-adapters.mjs"), "workflow runs installer smoke test"); const installedPath = path.join(os.homedir(), ".codex", "skills", "crossframe-code"); info("user .codex skill install status", fs.existsSync(installedPath) ? `installed at ${installedPath}` : `not installed at ${installedPath}`); diff --git a/skills/crossframe-code/SKILL.md b/skills/crossframe-code/SKILL.md index a6c79a2..5a33e2a 100644 --- a/skills/crossframe-code/SKILL.md +++ b/skills/crossframe-code/SKILL.md @@ -145,4 +145,5 @@ Treat these as blockers: - `evals/review-scope-and-stack-smoke-tests.md`: smoke prompts for diff scope, stack detection, project convention, and review verdict behavior. - `evals/local-project-risk-scan-smoke-tests.md`: smoke prompts for risky modules, legacy hotspots, AI patch regression surfaces, and safe refactoring candidates. - `evals/problem-router-smoke-tests.md`: smoke prompts for request routing across debugging, implementation, review, source-driven, high-risk, and toolchain cases. +- `evals/dual-core-routing-conflict-tests.md`: conflict checks for code/coder routing, approved-plan implementation, review-before-fix, and security shortcut handling. - `evals/golden-patch-plans.md`: sample passing outputs for local, architecture, and post-implementation modes. diff --git a/skills/crossframe-code/evals/dual-core-routing-conflict-tests.md b/skills/crossframe-code/evals/dual-core-routing-conflict-tests.md index 808a30c..d2c8614 100644 --- a/skills/crossframe-code/evals/dual-core-routing-conflict-tests.md +++ b/skills/crossframe-code/evals/dual-core-routing-conflict-tests.md @@ -26,12 +26,44 @@ Failure mode: - Blindly edits files or invents a likely bug without evidence. -## Test 2: Approved Deep Risk Plan Implementation +## Test 2A: Missing Approved Deep Risk Plan Prompt: ```text -Implement this approved Deep Risk Patch Plan for the billing webhook. Only edit the listed files and run the listed verification. +Implement this approved Deep Risk Patch Plan for the billing webhook. +``` + +Expected route: + +```text +problem-router -> high-risk implementation -> missing approved plan payload -> handoff report or crossframe-code +``` + +Expected behavior: + +- If the plan text, named files, confirmations, or verification are missing, do not implement. +- Use `crossframe-coder` handoff report or return to `crossframe-code`. + +Failure mode: + +- Treats a high-risk implementation request as approved without the plan payload. + +## Test 2B: Present Approved Deep Risk Plan + +Prompt: + +```text +Implement this approved Deep Risk Patch Plan for the billing webhook. + +Approved Deep Risk Patch Plan: +- Approved: yes. +- Named files: src/billing/webhook.ts, tests/billing/webhook.test.ts. +- Behavior to preserve: existing signature verification and tenant binding. +- Non-goals: no schema migration, no new ledger table. +- Required verification: npm test -- webhook.test.ts. +- First safe slice: add duplicate refund replay guard only. +- P0/P1 evidence anchors: complete in the plan. ``` Expected route: @@ -42,14 +74,13 @@ problem-router -> high-risk implementation -> approved plan check -> crossframe- Expected behavior: -- Confirm the plan is present, approved, named-file scoped, and has verification. -- `crossframe-coder` may implement only named files and planned verification. -- crossframe-coder may implement only named files and planned verification. -- If the plan is missing or unverified, keep it in `crossframe-code` for Deep Risk planning. +- `crossframe-coder` may implement only named files. +- No new files unless the plan names them. +- Run exactly the listed verification first. Failure mode: -- Treats the high-risk object as an ordinary local implementation. +- Expands the high-risk plan or edits files not named by the approved plan. ## Test 3: Review And Fix In One Prompt @@ -68,7 +99,6 @@ problem-router -> PR / diff / AI patch review -> review-output first Expected behavior: - Review first, pin review scope, and separate blocking findings from suggestions. -- review first, pin review scope, and separate blocking findings from suggestions. - Implementation happens only after explicit follow-up approval and a patch plan or file-scoped fix request. Failure mode: diff --git a/skills/crossframe-coder/SKILL.md b/skills/crossframe-coder/SKILL.md index 64438c8..302fc32 100644 --- a/skills/crossframe-coder/SKILL.md +++ b/skills/crossframe-coder/SKILL.md @@ -34,7 +34,21 @@ Classify the request: 6. **Tooling/config/docs**: scripts, config, CI, docs, examples. 7. **High-risk implementation**: auth, tenant, money, migration, concurrency, durable state, webhook, outbox, security, or multi-file legacy change. -If the request is high-risk or mechanism-unclear, stop and hand off to `crossframe-code`. +If the request is high-risk or mechanism-unclear, stop and hand off to `crossframe-code` unless the Approved High-Risk Plan Exception applies. + +## Approved High-Risk Plan Exception + +High-risk implementation may stay in `crossframe-coder` only when all entry conditions are true: + +- An approved Compact or Deep Risk Patch Plan is present in the current context. +- The plan names the exact files allowed for editing. +- The plan names behavior to preserve and explicit non-goals. +- The plan has no unresolved required confirmations. +- The plan includes runnable verification or a concrete manual replay/check. +- P0/P1 findings in the plan already include complete evidence anchors. +- The requested work is the first safe slice, not a broader redesign. + +If any condition is missing, use `templates/handoff-report.md` and return to `crossframe-code`. ## Workflow @@ -87,7 +101,7 @@ Use `references/handoff-to-crossframe-code.md` and `templates/handoff-report.md` - The object under change is unclear. - Multiple mechanisms plausibly explain the failure. -- The patch touches auth, authorization, tenant isolation, billing, payments, coupons, invoices, ledger, migrations, queues, locks, retries, idempotency, webhook, outbox, revenue, or durable state. +- The patch touches auth, authorization, tenant isolation, billing, payments, coupons, invoices, ledger, migrations, queues, locks, retries, idempotency, webhook, outbox, revenue, or durable state, unless the Approved High-Risk Plan Exception is satisfied. - The change requires behavior-preserving replacement of legacy code. - The user asks for PR/diff review rather than implementation. - Verification cannot be defined. @@ -110,3 +124,4 @@ Use `references/handoff-to-crossframe-code.md` and `templates/handoff-report.md` - `evals/codewriter-boundary-tests.md`: checks that high-risk work is handed off. - `evals/verification-failure-tests.md`: checks verification failure and blocked reporting. - `evals/handoff-to-crossframe-code-tests.md`: checks handoff boundaries. +- `evals/golden-implementation-reports.md`: sample passing implementation, blocked verification, source-driven, and high-risk handoff reports. diff --git a/skills/crossframe-coder/references/handoff-to-crossframe-code.md b/skills/crossframe-coder/references/handoff-to-crossframe-code.md index aaf7d5d..12be58a 100644 --- a/skills/crossframe-coder/references/handoff-to-crossframe-code.md +++ b/skills/crossframe-coder/references/handoff-to-crossframe-code.md @@ -8,10 +8,25 @@ Use this instead of editing when implementation risk exceeds a small verified sl - Multiple plausible root causes remain. - Behavior preservation is unknown. - Verification cannot be defined. -- The change touches auth, authorization, tenant isolation, billing, payment, coupon, invoice, ledger, migration, queue, lock, retry, idempotency, webhook, outbox, revenue, or durable state. +- The change touches auth, authorization, tenant isolation, billing, payment, coupon, invoice, ledger, migration, queue, lock, retry, idempotency, webhook, outbox, revenue, or durable state, unless the Approved High-Risk Plan Exception is satisfied. - The work is PR/diff review rather than implementation. - Legacy behavior needs characterization before correction. +## Approved Plan Exception Check + +Before implementing an approved high-risk plan, verify: + +- plan present: +- explicit approval: +- named files: +- behavior to preserve: +- non-goals: +- unresolved confirmations: +- verification command/manual check: +- first safe slice: + +If any field is missing, hand off. + ## Handoff Report Fields - Requested implementation: diff --git a/skills/crossframe-coder/references/source-driven-api-check.md b/skills/crossframe-coder/references/source-driven-api-check.md index f8e90dd..8367032 100644 --- a/skills/crossframe-coder/references/source-driven-api-check.md +++ b/skills/crossframe-coder/references/source-driven-api-check.md @@ -18,6 +18,22 @@ Use this when implementation depends on framework, SDK, library, generated clien - If docs and local types disagree, prefer local types for the installed version and note the mismatch. - If the source evidence cannot be checked, mark the implementation risk and keep the slice smaller. +## Implementation Decision Table + +| Situation | Required action | Allowed implementation | +|---|---|---| +| Local types or generated client exist | Inspect local API shape before editing | Use installed-version shape | +| Existing wrapper exists | Prefer wrapper over raw SDK call | Edit caller or wrapper only if scoped | +| Official docs disagree with local types | Prefer local installed version and note mismatch | Small guarded slice or handoff | +| API is tied to auth, billing, tenant, migration, or durable state | Require approved plan exception | No direct edit without approved plan | +| Source evidence cannot be checked | Mark implementation risk | Keep slice smaller or hand off | + +## Bad Examples + +- Copying current online docs into a project pinned to an older major version. +- Bypassing a local SDK wrapper to make the patch shorter. +- Changing idempotency, auth, tenant, or payment parameters while "just updating the SDK call." + ## Output Fields - Package or framework: