cli-validator 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 +11 -0
- package/cli.mjs +23 -0
- package/installer.mjs +241 -0
- package/package.json +21 -0
- package/skill/SKILL.md +54 -0
- package/skill/skill.json +10 -0
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# cli-validator
|
|
2
|
+
|
|
3
|
+
从 CLI.Tax 安装并运行 Validator 技能:交付前质量门禁——三道防线递进验证,黄金基准对抗大模型漂移,终审裁决分级放行。
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx cli-validator@latest install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Source: https://github.com/88208555/Validator-clitax.git
|
|
10
|
+
|
|
11
|
+
`validator.skill.request/1.0` 协议,端点 `https://cli.tax/Xx9ZkQmW3p`。
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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: 'goal', prompt: 'What is being validated? Describe the deliverable and expected behavior.', required: true, example: '电商运营仪表盘计算工具:输入访客数/订单数/GMV/广告费,输出转化率/客单价/ROAS。' },
|
|
8
|
+
{ id: 'riskLevel', prompt: 'Risk level: low, medium, or high?', required: true, example: 'medium' },
|
|
9
|
+
{ id: 'complianceReqs', prompt: 'Compliance requirements? (e.g., etl-2, pci-dss, general) Leave empty if none.', required: false, example: 'general' },
|
|
10
|
+
{ id: 'targetFiles', prompt: 'Files to validate (or "auto" to scan all).', required: false, example: 'auto' },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
await dispatchOfficialSkillCli({
|
|
14
|
+
packageRoot: dirname(fileURLToPath(import.meta.url)),
|
|
15
|
+
runCommand: (context) => runIntakeHandshake(context, {
|
|
16
|
+
questions: INTAKE_QUESTIONS,
|
|
17
|
+
outputFile: 'VALIDATOR-REQUIREMENTS.json',
|
|
18
|
+
afterCapabilities(output) {
|
|
19
|
+
const instruction = output.nextStep?.instruction
|
|
20
|
+
if (typeof instruction === 'string' && instruction.trim()) console.log(instruction)
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
})
|
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-validator": "./cli.mjs"
|
|
4
|
+
},
|
|
5
|
+
"description": "Validator skill installer for CLI.Tax: delivery quality gate with three defense lines and golden baseline testing.",
|
|
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-validator",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/88208555/Validator-clitax.git"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"version": "7.0.1"
|
|
21
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: validator
|
|
3
|
+
description: '交付前质量门禁:三道防线(静态/动态/对抗)递进验证,黄金基准对抗大模型漂移,执行证据杜绝自报伪造,终审裁决分级放行。'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Validator
|
|
7
|
+
|
|
8
|
+
技能链最后一站——交付前质量门禁。
|
|
9
|
+
|
|
10
|
+
## 核心原则
|
|
11
|
+
|
|
12
|
+
1. **确定性验证**:所有正式判定由确定性引擎执行,模型只做辅助解释
|
|
13
|
+
2. **零信任自报**:不接受任何"自报通过",必须有执行证据
|
|
14
|
+
3. **分级放行**:P0 阻断 / P1 降级放行 / P2 提示
|
|
15
|
+
4. **非侵入优先**:默认非侵入;侵入式测试仅在显式授权沙箱中
|
|
16
|
+
|
|
17
|
+
## 三道防线
|
|
18
|
+
|
|
19
|
+
- **第一道:静态防线** — validate-structure + security-scan + compliance-audit
|
|
20
|
+
- **第二道:动态防线** — functional-verify + sandbox-run + fuzz-input + perf-benchmark
|
|
21
|
+
- **第三道:对抗防线** — intrusive-test + mutation testing
|
|
22
|
+
|
|
23
|
+
## 操作目录(12 个)
|
|
24
|
+
|
|
25
|
+
| 操作 | 类别 | 说明 |
|
|
26
|
+
|------|------|------|
|
|
27
|
+
| capabilities | 元 | 能力发现 + JSON Schema |
|
|
28
|
+
| intake | 元 | 验收需求收集 |
|
|
29
|
+
| plan | 编排 | 生成验证计划 + 技能路由 |
|
|
30
|
+
| validate-structure | 静态 | Schema/依赖/引用闭合 |
|
|
31
|
+
| security-scan | 静态 | 漏洞/注入/硬编码密钥 |
|
|
32
|
+
| compliance-audit | 静态 | 等保基线/行业规范 |
|
|
33
|
+
| functional-verify | 动态 | 黄金基准测试 |
|
|
34
|
+
| sandbox-run | 动态 | 沙箱隔离执行 |
|
|
35
|
+
| fuzz-input | 动态 | 模糊测试 + 数据溯源 |
|
|
36
|
+
| perf-benchmark | 动态 | 性能压测 + 回归基线 |
|
|
37
|
+
| intrusive-test | 对抗 | 故障注入/白盒(需授权) |
|
|
38
|
+
| verdict | 汇总 | 终审裁决 |
|
|
39
|
+
|
|
40
|
+
## 终审裁决等级
|
|
41
|
+
|
|
42
|
+
| 等级 | 条件 | 动作 |
|
|
43
|
+
|------|------|------|
|
|
44
|
+
| pass | 一二道防线 0 P0/0 P1 | 允许交付 |
|
|
45
|
+
| pass-with-risk | 0 P0,P1 已登记台账 | 有条件交付 |
|
|
46
|
+
| blocked | 任何 P0 | 阻断 + 自动路由返工 |
|
|
47
|
+
| incomplete | 证据不足 | 不得判 pass |
|
|
48
|
+
|
|
49
|
+
## 与现有技能边界
|
|
50
|
+
|
|
51
|
+
- aimlock:管"变更过程中"门禁(改前),Validator 管"交付前"门禁(成品)
|
|
52
|
+
- blueprint:消费其 ACCEPTANCE-CHECKLIST 做验收对账
|
|
53
|
+
- swarm:返工指令通过 swarm 重新派单
|
|
54
|
+
- calctool:final-gate 为生成方自检,Validator 为独立终审
|
package/skill/skill.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Validator 是技能链的交付前质量门禁——终审法官。三道防线(静态/动态/对抗)递进验证,黄金基准对抗大模型漂移,执行证据杜绝自报伪造,终审裁决分级放行(pass/pass-with-risk/blocked/incomplete)。所有正式判定由确定性引擎执行,模型只做辅助解释。调用顺序为 capabilities、intake、plan、validate-structure、security-scan、functional-verify、verdict。证据缺失时 verdict 最高只能 incomplete——宁可待定不可放水。发现问题自动路由到对应技能返工(blueprint/calctool/aimlock/swarm)。本技能面向真实交付:每一步都有输入、规则与失败面,禁止把加载中、超时或未知状态当成空成功。调用前必须先走 capabilities,再按 nextStep 前进;必填项未回答不得进入下一操作。日志只保存必要元数据,密钥不得写入公开页面。",
|
|
3
|
+
"displayName": "Validator",
|
|
4
|
+
"endpoint": "https://cli.tax/Xx9ZkQmW3p",
|
|
5
|
+
"method": "POST",
|
|
6
|
+
"name": "validator",
|
|
7
|
+
"schemaVersion": "validator.skill.request/1.0",
|
|
8
|
+
"type": "Skill",
|
|
9
|
+
"version": "v7.0.1"
|
|
10
|
+
}
|