Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
27 changes: 27 additions & 0 deletions scripts/resolve-maker-version.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@ 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) && readPrereleaseTag(version) !== tag) {
throw new Error(
`Manual ${tag} publish requires ${tag} 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 readPrereleaseTag(version) {
const match = /^\d+\.\d+\.\d+-([0-9A-Za-z-]+)/.exec(version);
return match ? match[1] : '';
}

function changesMajorOrMinor(previousVersion, nextVersion) {
const previous = parseVersionCore(previousVersion);
const next = parseVersionCore(nextVersion);
Expand Down Expand Up @@ -320,6 +344,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
169 changes: 169 additions & 0 deletions scripts/update-maker-version-policy.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
#!/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) ||
policy.blacklist.some((item) => typeof item !== 'string' || !VERSION_PATTERN.test(item))
) {
throw new Error(`Invalid Maker version policy ${file}: blacklist must be a semver string 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,
};
Loading
Loading