cli-mergeguard 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # cli-mergeguard
2
+
3
+ 从 CLI.Tax 安装并运行 MergeGuard 技能:智能合并守卫——快照分支+预演+验证式合并+规则衰减防护。
4
+
5
+ ```bash
6
+ npx cli-mergeguard@latest install
7
+ ```
8
+
9
+ Source: https://github.com/88208555/MergeGuard-clitax.git
10
+
11
+ `mergeguard.skill.request/1.0` 协议,端点 `https://cli.tax/Mm7GnPqR2v`。
package/cli.mjs ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { dirname } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { dispatchOfficialSkillCli, runIntakeHandshake } from './installer.mjs'
5
+
6
+ const INTAKE_QUESTIONS = [
7
+ { id: 'repoType', prompt: 'Repository type: git, none (no version control), or snapshot (MergeGuard snapshot mode)?', required: true, example: 'snapshot' },
8
+ { id: 'riskLevel', prompt: 'Risk level: low, medium, or high?', required: true, example: 'medium' },
9
+ { id: 'baselineBranch', prompt: 'Baseline branch name (default: main)?', required: false, example: 'main' },
10
+ ]
11
+
12
+ await dispatchOfficialSkillCli({
13
+ packageRoot: dirname(fileURLToPath(import.meta.url)),
14
+ runCommand: (context) => runIntakeHandshake(context, {
15
+ questions: INTAKE_QUESTIONS,
16
+ outputFile: 'MERGGUARD-REQUIREMENTS.json',
17
+ afterCapabilities(output) {
18
+ const instruction = output.nextStep?.instruction
19
+ if (typeof instruction === 'string' && instruction.trim()) console.log(instruction)
20
+ },
21
+ }),
22
+ })
package/installer.mjs ADDED
@@ -0,0 +1,241 @@
1
+ /**
2
+ * 五个官方技能共用这一份安装器。packages/*-cli/installer.mjs 必须与本文件字节一致。
3
+ * 禁止第二套超时、第二套版本来源、第二套 bin 名。
4
+ */
5
+ import { copyFile, mkdir, writeFile } from 'node:fs/promises'
6
+ import { existsSync, readFileSync } from 'node:fs'
7
+ import { dirname, join, resolve } from 'node:path'
8
+ import { stdin, stdout } from 'node:process'
9
+ import { createInterface } from 'node:readline/promises'
10
+ import { fileURLToPath } from 'node:url'
11
+
12
+ export const LOOKUP_TIMEOUT_MS = 8000
13
+ export const CALL_TIMEOUT_MS = 120_000
14
+ const INSTALL_META = 'install-meta.json'
15
+
16
+ function asObject(value, label) {
17
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
18
+ throw new Error(`${label} must be an object`)
19
+ }
20
+ return value
21
+ }
22
+
23
+ function requiredString(value, label) {
24
+ const text = typeof value === 'string' ? value.trim() : ''
25
+ if (!text) throw new Error(`${label} is required`)
26
+ return text
27
+ }
28
+
29
+ export function loadOfficialSkillContext(packageRoot) {
30
+ const pkg = asObject(JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')), 'package.json')
31
+ const skill = asObject(JSON.parse(readFileSync(join(packageRoot, 'skill/skill.json'), 'utf8')), 'skill.json')
32
+ const npmName = requiredString(pkg.name, 'package.json name')
33
+ const packageVersion = requiredString(pkg.version, 'package.json version')
34
+ const displayName = requiredString(skill.displayName, 'skill.json displayName')
35
+ const skillName = requiredString(skill.name, 'skill.json name')
36
+ const schemaVersion = requiredString(skill.schemaVersion, 'skill.json schemaVersion')
37
+ const endpoint = requiredString(skill.endpoint, 'skill.json endpoint')
38
+ const skillVersion = requiredString(skill.version, 'skill.json version')
39
+ const runtimeCode = requiredString(endpoint.replace(/^https:\/\/cli\.tax\//, ''), 'runtime code')
40
+ if (!/^[A-Za-z0-9]{10}$/.test(runtimeCode)) {
41
+ throw new Error(`skill.json endpoint must be https://cli.tax/{10-char-code}: ${endpoint}`)
42
+ }
43
+ if (skillVersion.replace(/^v/i, '') !== packageVersion.replace(/^v/i, '')) {
44
+ throw new Error(`skill.json ${skillVersion} must match package.json ${packageVersion}`)
45
+ }
46
+ return {
47
+ packageRoot,
48
+ npmName,
49
+ packageVersion,
50
+ displayName,
51
+ skillName,
52
+ schemaVersion,
53
+ endpoint,
54
+ skillVersion,
55
+ runtimeCode,
56
+ latestEndpoint: `https://cli.tax/api/public/skills/${runtimeCode}`,
57
+ skillDir: join(packageRoot, 'skill'),
58
+ }
59
+ }
60
+
61
+ export function readInstallMeta(target) {
62
+ const path = join(target, INSTALL_META)
63
+ if (!existsSync(path)) return null
64
+ return asObject(JSON.parse(readFileSync(path, 'utf8')), INSTALL_META)
65
+ }
66
+
67
+ export function installTarget(skillName, explicit) {
68
+ if (explicit) return resolve(explicit)
69
+ const codexHome = process.env.CODEX_HOME?.trim()
70
+ if (codexHome) return join(codexHome, 'skills', skillName)
71
+ return join(process.cwd(), '.codex', 'skills', skillName)
72
+ }
73
+
74
+ export async function fetchLatestVersion(context) {
75
+ const response = await fetch(context.latestEndpoint, { signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) })
76
+ if (!response.ok) throw new Error(`cli.tax skill lookup failed: HTTP ${response.status}`)
77
+ const data = asObject(await response.json(), 'cli.tax skill lookup')
78
+ return {
79
+ version: requiredString(data.version, 'cli.tax skill lookup version'),
80
+ displayName: requiredString(data.displayName, 'cli.tax skill lookup displayName'),
81
+ }
82
+ }
83
+
84
+ export async function callOfficialSkill(context, operation, input) {
85
+ const requestId = `${context.npmName}-${Date.now()}`
86
+ const response = await fetch(context.endpoint, {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({
90
+ input: {
91
+ schemaVersion: context.schemaVersion,
92
+ requestId,
93
+ operation,
94
+ input,
95
+ },
96
+ }),
97
+ signal: AbortSignal.timeout(CALL_TIMEOUT_MS),
98
+ })
99
+ let payload
100
+ try {
101
+ payload = await response.json()
102
+ } catch {
103
+ throw new Error(`${context.displayName} ${operation} failed: non-JSON response (HTTP ${response.status}). Check ${context.endpoint}.`)
104
+ }
105
+ if (!response.ok || payload?.ok !== true) {
106
+ const message = payload?.error?.message
107
+ if (typeof message !== 'string' || !message.trim()) {
108
+ throw new Error(`${context.displayName} ${operation} failed: HTTP ${response.status}`)
109
+ }
110
+ throw new Error(`${context.displayName} ${operation} failed: ${message}`)
111
+ }
112
+ return payload
113
+ }
114
+
115
+ export async function installOfficialSkill(context, explicit) {
116
+ const target = installTarget(context.skillName, explicit)
117
+ await mkdir(target, { recursive: true })
118
+ const previous = readInstallMeta(target)
119
+ await copyFile(join(context.skillDir, 'SKILL.md'), join(target, 'SKILL.md'))
120
+ await copyFile(join(context.skillDir, 'skill.json'), join(target, 'skill.json'))
121
+ const installed = asObject(JSON.parse(readFileSync(join(target, 'skill.json'), 'utf8')), 'installed skill.json')
122
+ const installedVersion = requiredString(installed.version, 'installed skill.json version')
123
+ await writeFile(join(target, INSTALL_META), `${JSON.stringify({
124
+ source: context.runtimeCode,
125
+ slug: context.skillName,
126
+ version: installedVersion,
127
+ packageVersion: context.packageVersion,
128
+ endpoint: context.endpoint,
129
+ installedAt: new Date().toISOString(),
130
+ }, null, 2)}\n`)
131
+ if (previous?.version && previous.version !== installedVersion) {
132
+ console.log(`${context.displayName} skill updated: ${target}`)
133
+ console.log(` ${previous.version} → ${installedVersion}`)
134
+ } else {
135
+ console.log(`${context.displayName} skill installed: ${target} (${installedVersion})`)
136
+ }
137
+ console.log('Next: return to your IDE and state the goal. The agent reads the installed SKILL.md.')
138
+ }
139
+
140
+ export async function checkOfficialSkill(context, explicit) {
141
+ const target = installTarget(context.skillName, explicit)
142
+ const current = readInstallMeta(target)
143
+ if (!current) {
144
+ console.log(`${context.displayName} skill is not installed. Run: npx ${context.npmName}@latest install`)
145
+ process.exitCode = 1
146
+ return
147
+ }
148
+ const installedVersion = requiredString(current.version, 'install-meta.json version')
149
+ const packageVersion = requiredString(current.packageVersion, 'install-meta.json packageVersion')
150
+ console.log(`Installed: ${installedVersion} (package ${packageVersion})`)
151
+ const latest = await fetchLatestVersion(context)
152
+ console.log(`Latest on cli.tax: ${latest.version}`)
153
+ if (installedVersion === latest.version) {
154
+ console.log('Up to date.')
155
+ return
156
+ }
157
+ console.log(`Update available: ${installedVersion} → ${latest.version}`)
158
+ console.log(`Run: npx ${context.npmName}@latest install`)
159
+ process.exitCode = 1
160
+ }
161
+
162
+ export function defaultUsage(context, extraLines) {
163
+ const lines = [
164
+ `${context.npmName} — install and run the ${context.displayName} skill from CLI.Tax`,
165
+ '',
166
+ 'Usage:',
167
+ ` npx ${context.npmName}@latest install [directory]`,
168
+ ` Install the ${context.displayName} skill for the current IDE.`,
169
+ ` npx ${context.npmName}@latest check [directory]`,
170
+ ' Check whether the installed skill has a newer version.',
171
+ ` npx ${context.npmName}@latest run`,
172
+ ' Run the skill handshake: discover capabilities and collect intake answers.',
173
+ '',
174
+ `Endpoint: ${context.endpoint}`,
175
+ ]
176
+ if (extraLines?.length) lines.push('', ...extraLines)
177
+ return lines.join('\n')
178
+ }
179
+
180
+ export async function runIntakeHandshake(context, spec) {
181
+ const capabilities = await callOfficialSkill(context, 'capabilities', {})
182
+ const output = capabilities.output && typeof capabilities.output === 'object' ? capabilities.output : {}
183
+ const skill = output.skill && typeof output.skill === 'object' ? output.skill : {}
184
+ const version = typeof skill.version === 'string' && skill.version.trim()
185
+ ? skill.version.trim()
186
+ : context.skillVersion
187
+ console.log(`${context.displayName} ${version}`)
188
+ if (typeof spec.afterCapabilities === 'function') spec.afterCapabilities(output)
189
+ const readline = createInterface({ input: stdin, output: stdout })
190
+ const answers = []
191
+ try {
192
+ for (const question of spec.questions) {
193
+ const requiredMark = question.required ? ' (required)' : ''
194
+ console.log(`\n${question.prompt}${requiredMark}`)
195
+ console.log(`Example: ${question.example}`)
196
+ for (;;) {
197
+ const answer = (await readline.question('> ')).trim()
198
+ if (answer) {
199
+ answers.push({ id: question.id, prompt: question.prompt, answer })
200
+ break
201
+ }
202
+ if (!question.required) break
203
+ console.log('This question is required. Please answer before continuing.')
204
+ }
205
+ }
206
+ } finally {
207
+ readline.close()
208
+ }
209
+ const target = join(process.cwd(), spec.outputFile)
210
+ await writeFile(target, `${JSON.stringify({
211
+ schemaVersion: context.schemaVersion,
212
+ endpoint: context.endpoint,
213
+ createdAt: new Date().toISOString(),
214
+ answers,
215
+ }, null, 2)}\n`)
216
+ console.log(`\nRequirements saved: ${target}`)
217
+ console.log('Next: continue in your IDE agent with this file.')
218
+ }
219
+
220
+ export async function dispatchOfficialSkillCli(options) {
221
+ const packageRoot = options.packageRoot ?? dirname(fileURLToPath(options.importMetaUrl))
222
+ const context = loadOfficialSkillContext(packageRoot)
223
+ const args = process.argv.slice(2)
224
+ const command = args[0] ?? 'help'
225
+ const argument = args[1]
226
+ try {
227
+ if (command === 'install') await installOfficialSkill(context, argument)
228
+ else if (command === 'check') await checkOfficialSkill(context, argument)
229
+ else if (command === 'run') await options.runCommand(context)
230
+ else if (command === 'help' || command === '--help' || command === '-h') {
231
+ console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
232
+ } else {
233
+ console.error(`Unknown command: ${command}`)
234
+ console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
235
+ process.exitCode = 1
236
+ }
237
+ } catch (error) {
238
+ console.error(error instanceof Error ? error.message : error)
239
+ process.exitCode = 1
240
+ }
241
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "bin": {
3
+ "cli-mergeguard": "./cli.mjs"
4
+ },
5
+ "description": "MergeGuard skill installer for CLI.Tax: smart merge guard with snapshot branches, verified merge, and rule decay protection.",
6
+ "files": [
7
+ "cli.mjs",
8
+ "installer.mjs",
9
+ "README.md",
10
+ "skill/SKILL.md",
11
+ "skill/skill.json"
12
+ ],
13
+ "license": "UNLICENSED",
14
+ "name": "cli-mergeguard",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/88208555/MergeGuard-clitax.git"
18
+ },
19
+ "type": "module",
20
+ "version": "7.0.1"
21
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: mergeguard
3
+ description: '智能合并守卫:快照分支+预演+验证式合并+规则衰减防护,解决全球 IDE 分支合并难题。'
4
+ ---
5
+
6
+ # MergeGuard
7
+
8
+ 智能合并守卫——解决分支合并难题。
9
+
10
+ ## 三条铁律
11
+
12
+ 1. **合并不破坏**:合并前自动快照,全程隔离区进行,验证通过才落盘,一键回滚
13
+ 2. **AI 提议、测试裁决**:冲突方案可由模型生成,但必须通过测试验证才算数
14
+ 3. **小白可用**:零 git 心智的"快照分支"模式,全程向导式
15
+
16
+ ## 分层合并策略
17
+
18
+ - **L1 文本层**:行级三方合并(兜底)
19
+ - **L2 结构层**:AST 级/JSON 键路径级/公式图节点级合并(主力)
20
+ - **L3 意图层**:AI 分析冲突意图 + 生成分辨率提案
21
+
22
+ ## 操作目录(14 个)
23
+
24
+ | 操作 | 说明 |
25
+ |------|------|
26
+ | capabilities / help | 能力发现 + JSON Schema |
27
+ | intake | 收集仓库形态/风险等级/基线 |
28
+ | branch-create | 创建快照分支 |
29
+ | branch-list | 列出所有分支 |
30
+ | branch-switch | 切换当前分支 |
31
+ | diff-report | 版本间结构化差异 |
32
+ | preflight | 合并预演(冲突分级) |
33
+ | resolve-propose | AI 冲突分辨率提案 |
34
+ | merge-verified | 验证式合并(三道验证+落盘/拒绝) |
35
+ | rollback | 一键回滚到合并前快照 |
36
+ | ledger-query | 变更台账/审计查询 |
37
+ | ruleguard-scan | 规则衰减扫描 |
38
+ | ruleguard-compile | 规则编译 |
39
+
40
+ ## 规则衰减防护(RuleGuard)
41
+
42
+ 内置规则:no-inline-style / no-hardcode-color / no-eval / no-debug / no-magic-number
43
+ 三层防护:规则编译 → 写入时拦截 → 跨分支规则一致性
44
+
45
+ ## 验证式合并流程
46
+
47
+ ```
48
+ 合并候选(隔离区)
49
+
50
+ ① 结构效验:语法可解析、引用闭合
51
+ ② 黄金基准:全量回归测试
52
+ ③ 冒烟执行:沙箱跑通黄金路径
53
+
54
+ 全绿 → 落盘 + 更新台账
55
+ 任一红 → 拒绝落盘 + AI 修复提案
56
+ ```
57
+
58
+ ## 与技能链集成
59
+
60
+ - aimlock:快照基础设施复用,合并须过 mutate-gate
61
+ - blueprint:蓝图 IR 节点级合并
62
+ - calctool:公式图节点级合并 + 基准数字对账
63
+ - swarm:大合并任务拆单
64
+ - Validator:三道验证裁剪复用
@@ -0,0 +1,10 @@
1
+ {
2
+ "description": "MergeGuard 是智能合并守卫——解决全球 IDE 二十年未解好的分支合并难题。三条铁律:合并不破坏(快照隔离+一键回滚)、AI 提议测试裁决(模型无裁判权)、小白可用(零 git 心智向导)。分层合并策略:L1 文本层兜底 + L2 结构层主力(AST/JSON/公式图节点级合并)+ L3 意图层 AI 增强。验证式合并:隔离区执行三道验证(结构效验+黄金基准+冒烟),全绿才落盘。规则衰减防护:内置规则编译器+写入时拦截+跨分支规则一致性。调用顺序为 capabilities、intake、branch-create、preflight、merge-verified。合并不崩、崩了能回、伪冲突自动识别、静默坏合必拦截。本技能面向真实交付:每一步都有输入、规则与失败面,禁止把加载中、超时或未知状态当成空成功。调用前必须先走 capabilities,再按 nextStep 前进;必填项未回答不得进入下一操作。日志只保存必要元数据,密钥不得写入公开页面。",
3
+ "displayName": "MergeGuard",
4
+ "endpoint": "https://cli.tax/Mm7GnPqR2v",
5
+ "method": "POST",
6
+ "name": "mergeguard",
7
+ "schemaVersion": "mergeguard.skill.request/1.0",
8
+ "type": "Skill",
9
+ "version": "v7.0.1"
10
+ }