Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion .github/workflows/publish-maker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ on:
- alpha

permissions:
contents: read
contents: write
id-token: write
pull-requests: write

jobs:
resolve-version:
Expand Down Expand Up @@ -137,3 +138,44 @@ jobs:
MAKER_PACKAGE_VERSION: ${{ needs.resolve-version.outputs.version }}
NPM_TAG: ${{ inputs.tag }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Update Maker version policy
id: version-policy
run: |
node scripts/update-maker-version-policy.cjs \
--tag "$NPM_TAG" \
--version "$MAKER_PACKAGE_VERSION"
env:
MAKER_PACKAGE_VERSION: ${{ needs.resolve-version.outputs.version }}
NPM_TAG: ${{ inputs.tag }}

- name: Generate version policy PR token
id: version-policy-token
if: steps.version-policy.outputs.changed == 'true'
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets.RELEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}

- name: Create Maker version policy PR
if: steps.version-policy.outputs.changed == 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ steps.version-policy-token.outputs.token }}
branch: chore/update-maker-version-policy-${{ needs.resolve-version.outputs.version }}
delete-branch: true
title: 'chore(maker): update maker version policy for ${{ needs.resolve-version.outputs.version }}'
commit-message: |
chore(maker): update maker version policy

- Update config/maker-version-policy.json after @taptap/maker publish
- Preserve minimum_supported, blacklist, and message policy fields
- Verification: publish-maker workflow completed npm publish first
body: |
Updates `config/maker-version-policy.json` after publishing
`@taptap/maker@${{ needs.resolve-version.outputs.version }}` with npm tag
`${{ inputs.tag }}`.

The workflow only updates `latest` or `latest_beta` plus `updated_at`.
Manual policy fields such as `minimum_supported`, `blacklist`, and `message`
are preserved for review-driven changes.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,4 @@ native/node_modules/
# TapTap Maker local runtime state
.maker/
.npm-cache
docs/superpowers
9 changes: 9 additions & 0 deletions config/maker-version-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"schema_version": 1,
"latest": "0.0.20",
"latest_beta": "0.0.19-beta.1",
"minimum_supported": "0.0.1",
"blacklist": [],
"message": "TapTap Maker MCP package policy is current.",
"updated_at": "2026-06-23T00:00:00.000Z"
}
18 changes: 17 additions & 1 deletion docs/MAKER.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,9 @@ Python 运行时策略:
- `taptap-maker agents update`:只更新当前目录 `AGENTS.md` 中 TapTap Maker 管理的策略块,
保留用户自己的项目说明;缺少 `AGENTS.md` 时会创建文件。
- `taptap-maker upgrade`:当前目录的一站式升级入口,刷新 AI 客户端 MCP 配置,并在当前目录
已绑定 Maker 项目时执行 `AGENTS.md` 受管策略块更新。它不扫描其它 Maker 项目。
已绑定 Maker 项目时执行 `AGENTS.md` 受管策略块更新。它不扫描其它 Maker 项目。升级动作
不由 MCP 自己执行;MCP 只做版本检查并输出 `required_upgrade`、`update_available`、
`current`、`unavailable` 或 `skipped`。
- `taptap-maker dev-kit update`:检查当前环境可用的最新 AI dev kit,恢复或更新当前目录。

MCP 运行期能力:
Expand All @@ -216,6 +218,20 @@ MCP 运行期能力:
该 root 识别当前项目,并输出 `MCP client roots` 与 `project_context_source` 诊断。
这样同一台机器上 Codex、Trae、Cursor 等客户端的用户级 MCP 配置不会因为某个项目写入
`cwd` 而互相污染。
- `maker://status` 和 `maker_status_lite` 会在启动后的异步检查完成后,以及最后一次成功远端
检查超过 12 小时时懒检查 `Maker MCP package update` 区块。这个检查只读取远端 policy,不执行升级,
也不会被业务 tools 触发。远端 policy 使用 `schema_version`、`latest`、`latest_beta`、
`minimum_supported`、`blacklist` 和 `message` 字段来决定是否需要升级。
状态只会报告 `required_upgrade`、`update_available`、`current`、`unavailable` 或 `skipped`。
如果状态是 `required_upgrade`,本地 AI 必须先向用户解释原因并征得同意;用户同意后,
若项目目录已确认,再运行 `npx -y -p @taptap/maker taptap-maker upgrade --target-dir <PROJECT_DIR>`;
如果还没有确认项目目录,只能说明 machine-level refresh 的取舍,再决定是否运行不带
`--target-dir` 的 `taptap-maker upgrade`。升级后要提示用户重启或 reconnect MCP session,
然后用 `maker://status` 或 `maker_status_lite` 复核结果。
- `@taptap/maker` 发版成功后,`publish-maker.yml` 会用 `scripts/update-maker-version-policy.cjs`
更新 `config/maker-version-policy.json` 并创建一个可 review 的 PR。`tag=latest` 只自动更新
`latest`,`tag=beta` 只自动更新 `latest_beta`,并刷新 `updated_at`;`minimum_supported`、
`blacklist` 和 `message` 保持人工策略字段,不由发版流程自动改。
- `taptap-maker doctor` 会输出 `Maker MCP tools availability`。如果当前 AI 对话里看不到
Maker proxy tools,先按该段提示重启 AI 客户端、新开 AI 对话,或在支持 `/mcp` 的客户端中
Reconnect `taptap-maker`;如果客户端不支持 MCP Roots 且输出 `pwd_alignment: cwd_mismatch`,
Expand Down
4 changes: 4 additions & 0 deletions scripts/release-scope.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ const MAKER_PATH_PREFIXES = [
const MAKER_EXACT_PATHS = new Set([
'.github/workflows/publish-maker.yml',
'bin/taptap-maker',
'config/maker-version-policy.json',
'docs/MAKER.md',
'docs/MAKER_CLI_MCP_SKILL_REWORK_OVERVIEW.md',
'scripts/bundle-maker.js',
'scripts/prepare-maker-package.js',
'scripts/resolve-maker-version.js',
'scripts/update-maker-version-policy.cjs',
]);

const RELEASE_INFRA_EXACT_PATHS = new Set([
Expand All @@ -32,6 +34,7 @@ const RELEASE_INFRA_EXACT_PATHS = new Set([
'.releaserc.cjs',
'CONTRIBUTING.md',
'README.md',
'config/maker-version-policy.json',
'docs/CI_CD.md',
'docs/MAKER.md',
'package-lock.json',
Expand All @@ -41,6 +44,7 @@ const RELEASE_INFRA_EXACT_PATHS = new Set([
'scripts/release-scope.cjs',
'scripts/resolve-main-release-version.js',
'scripts/resolve-maker-version.js',
'scripts/update-maker-version-policy.cjs',
'scripts/semantic-release-main-analyzer.cjs',
'src/__tests__/mainPackageManifest.test.ts',
'src/__tests__/mainReleaseVersionPolicy.test.ts',
Expand Down
17 changes: 17 additions & 0 deletions scripts/resolve-maker-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ function isStableThreeSegmentVersion(version) {
return /^\d+\.\d+\.\d+$/.test(version);
}

function isPrereleaseVersion(version) {
return version.includes('-');
}

function assertVersionMatchesTag(tag, version) {
const isPrerelease = isPrereleaseVersion(version);
if (PRERELEASE_TAGS.has(tag) && !isPrerelease) {
throw new Error(`Manual ${tag} publish requires a prerelease version like 0.0.1-${tag}.1.`);
}
if (!PRERELEASE_TAGS.has(tag) && isPrerelease) {
throw new Error(`Manual ${tag} publish requires a stable version like 0.0.1.`);
}
Comment thread
529951164 marked this conversation as resolved.
}

function changesMajorOrMinor(previousVersion, nextVersion) {
const previous = parseVersionCore(previousVersion);
const next = parseVersionCore(nextVersion);
Expand Down Expand Up @@ -320,6 +334,9 @@ function main() {
mode === 'manual'
? resolveManualVersion(currentVersion)
: resolveAutoVersion(tag, hasPublishedVersions, publishedVersions, currentVersion);
if (mode === 'manual') {
assertVersionMatchesTag(tag, version);
}
const majorMinorChanged = Boolean(
mode === 'manual' && currentVersion && changesMajorOrMinor(currentVersion, version)
);
Expand Down
166 changes: 166 additions & 0 deletions scripts/update-maker-version-policy.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env node
'use strict';

const fs = require('node:fs');
const path = require('node:path');

const DEFAULT_POLICY_FILE = path.join(__dirname, '..', 'config', 'maker-version-policy.json');
const VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;

function parseArgs(argv) {
const parsed = {
file: DEFAULT_POLICY_FILE,
};

for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === '--tag' || arg === '--version' || arg === '--file' || arg === '--updated-at') {
const value = argv[index + 1];
if (!value || value.startsWith('--')) {
throw new Error(`Missing value for ${arg}.`);
}
parsed[toCamelCase(arg.slice(2))] = value;
index += 1;
continue;
}
throw new Error(`Unknown argument: ${arg}`);
}

if (!parsed.tag) {
throw new Error('Missing required --tag.');
}
if (!parsed.version) {
throw new Error('Missing required --version.');
}

return parsed;
}

function updateMakerVersionPolicy(options) {
const tag = options.tag;
const version = options.version;
const file = path.resolve(options.file || DEFAULT_POLICY_FILE);
const field = tag === 'latest' ? 'latest' : tag === 'beta' ? 'latest_beta' : undefined;

assertValidVersion(version);
assertVersionMatchesTag(tag, version);
Comment thread
529951164 marked this conversation as resolved.

if (!field) {
Comment thread
greptile-apps[bot] marked this conversation as resolved.
return {
changed: false,
field: undefined,
version,
};
}

const policy = readPolicy(file);
assertPolicy(policy, file);

if (policy[field] === version) {
return {
changed: false,
field,
version,
};
}

const nextPolicy = {
...policy,
[field]: version,
updated_at: options.updatedAt || new Date().toISOString(),
};
fs.writeFileSync(file, `${JSON.stringify(nextPolicy, null, 2)}\n`, 'utf8');

return {
changed: true,
field,
version,
};
}

function readPolicy(file) {
try {
return JSON.parse(fs.readFileSync(file, 'utf8'));
} catch (error) {
throw new Error(
`Failed to read Maker version policy ${file}: ${
error instanceof Error ? error.message : String(error)
}`
);
}
}

function assertPolicy(policy, file) {
if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
throw new Error(`Invalid Maker version policy ${file}: expected JSON object.`);
}
if (policy.schema_version !== 1) {
throw new Error(`Invalid Maker version policy ${file}: schema_version must be 1.`);
}
for (const field of ['latest', 'latest_beta', 'minimum_supported']) {
assertValidVersion(policy[field], `policy.${field}`);
}
if (!Array.isArray(policy.blacklist)) {
throw new Error(`Invalid Maker version policy ${file}: blacklist must be an array.`);
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}

function assertValidVersion(version, label = 'version') {
if (typeof version !== 'string' || !VERSION_PATTERN.test(version)) {
throw new Error(`Invalid ${label}: ${version}. Expected semver like 0.0.1 or 0.0.1-beta.1.`);
}
}

function assertVersionMatchesTag(tag, version) {
const isPrerelease = version.includes('-');
if (tag === 'latest' && isPrerelease) {
throw new Error(`Invalid latest version: ${version}. The latest tag must publish a stable version.`);
}
if (tag === 'beta' && !isPrerelease) {
throw new Error(`Invalid beta version: ${version}. The beta tag must publish a prerelease version.`);
}
}

function toCamelCase(value) {
return value.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}

function writeGithubOutput(result) {
const outputPath = process.env.GITHUB_OUTPUT;
if (!outputPath) {
return;
}
fs.appendFileSync(outputPath, `changed=${String(result.changed)}\n`, 'utf8');
fs.appendFileSync(outputPath, `field=${result.field || ''}\n`, 'utf8');
fs.appendFileSync(outputPath, `version=${result.version}\n`, 'utf8');
}

function main() {
const parsed = parseArgs(process.argv.slice(2));
const result = updateMakerVersionPolicy({
file: parsed.file,
tag: parsed.tag,
version: parsed.version,
updatedAt: parsed.updatedAt,
});
writeGithubOutput(result);

if (result.changed) {
console.log(`Updated Maker version policy ${result.field} to ${result.version}.`);
} else {
console.log(`Maker version policy unchanged for tag ${parsed.tag}.`);
}
}

if (require.main === module) {
try {
main();
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
}
}

module.exports = {
updateMakerVersionPolicy,
};
34 changes: 27 additions & 7 deletions skills/update-taptap-mcp/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,25 @@ This workflow upgrades only the current machine and the current Maker project di
- Do not batch-upgrade multiple projects.
- Do not delete old config backup files. Historical `.bak.*` files have no reliable ownership
marker and must be left untouched.
- The MCP status surface only checks the package policy and reports
`required_upgrade`, `update_available`, `current`, `unavailable`, or `skipped`.
It never runs `taptap-maker upgrade` by itself.

## Required Steps

1. Identify the current Maker project directory. Use `--target-dir` only when the directory is
1. Read `maker://status`; if resources are unavailable, fall back to `maker_status_lite`.
If the `Maker MCP package update` section reports `required_upgrade`, explain the version reason
to the user first.
Do not run any upgrade command before that explanation and approval step.
2. Identify the current Maker project directory. Use `--target-dir` only when the directory is
confirmed to be a Maker project, which means it or one of its parents contains
`.maker-mcp/config.json`.
2. If the AI client has exactly one attached workspace and that workspace is the Maker project, use
3. If the AI client has exactly one attached workspace and that workspace is the Maker project, use
that workspace as `<PROJECT_DIR>`. If there are multiple workspaces, or the current directory is
not clearly a Maker project, ask the user for the Maker project directory before using
`--target-dir`.
3. Run the current package upgrade command for the confirmed project:
4. After the user approves the upgrade, run the current package upgrade command for the confirmed
project:

```bash
npx -y -p @taptap/maker taptap-maker upgrade --target-dir <PROJECT_DIR>
Expand All @@ -36,14 +44,26 @@ npx -y -p @taptap/maker taptap-maker upgrade --target-dir <PROJECT_DIR>
If the user only wants one client, pass `--ide codex`, `--ide cursor`, or `--ide claude`.

If the user only wants to refresh the machine-level MCP command and no Maker project directory is
confirmed, run `npx -y -p @taptap/maker taptap-maker upgrade` without `--target-dir` only after
explaining that project `AGENTS.md` will not be updated.
confirmed, explain that this refresh will not update project `AGENTS.md`, then run
`npx -y -p @taptap/maker taptap-maker upgrade` without `--target-dir` only after the user agrees.

4. Ask the user to restart/reconnect the AI client MCP session, or open a new AI conversation.
5. Ask the user to restart/reconnect the AI client MCP session, or open a new AI conversation.
Current conversations usually do not hot-load a new MCP process or re-read `AGENTS.md`.
5. After restart/reconnect, verify by reading `maker://status`; if resources are unavailable, call
6. After restart/reconnect, verify by reading `maker://status`; if resources are unavailable, call
`maker_status_lite`.

## Status-Driven Upgrade Notes

- Trigger timing is limited to the startup asynchronous check and the 12-hour TTL lazy check from
`maker://status` / `maker_status_lite`.
- Business tools do not trigger version checks.
- The remote policy fields are `schema_version`, `latest`, `latest_beta`,
`minimum_supported`, `blacklist`, and `message`.
- `required_upgrade` means the local AI must explain the reason, ask the user for approval, and only
then run the appropriate upgrade command.
- After any upgrade, restart or reconnect the MCP session before trusting the new status, then
verify with `maker://status` or `maker_status_lite`.

## Expected Effects

`taptap-maker upgrade` performs current-directory upgrade work:
Expand Down
Loading
Loading