cli-blueprint 5.0.3 → 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/installer.mjs +241 -0
- package/package.json +2 -2
- package/skill/SKILL.md +260 -0
- package/skill/skill.json +2 -2
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
CHANGED
|
@@ -5,17 +5,17 @@
|
|
|
5
5
|
"description": "Blueprint skill installer for CLI.Tax: compile one goal into an executable, verifiable engineering blueprint.",
|
|
6
6
|
"files": [
|
|
7
7
|
"cli.mjs",
|
|
8
|
+
"installer.mjs",
|
|
8
9
|
"README.md",
|
|
9
10
|
"skill/SKILL.md",
|
|
10
11
|
"skill/skill.json"
|
|
11
12
|
],
|
|
12
13
|
"license": "UNLICENSED",
|
|
13
14
|
"name": "cli-blueprint",
|
|
14
|
-
"private": false,
|
|
15
15
|
"repository": {
|
|
16
16
|
"type": "git",
|
|
17
17
|
"url": "https://github.com/88208555/blueprint-clitax.git"
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
20
|
-
"version": "
|
|
20
|
+
"version": "7.0.1"
|
|
21
21
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -5,6 +5,8 @@ description: '把一个目标编译为可执行、可验证、可追溯的工程
|
|
|
5
5
|
|
|
6
6
|
# Blueprint Skill
|
|
7
7
|
|
|
8
|
+
版本:v6.0.0
|
|
9
|
+
|
|
8
10
|
Endpoint: https://cli.tax/wvz6zmRWmX
|
|
9
11
|
Request schema: blueprint.skill.request/1.0
|
|
10
12
|
Response schema: blueprint.skill.response/1.0
|
|
@@ -49,3 +51,261 @@ After `capabilities`, read `officialCatalog`. Default allowlist is official skil
|
|
|
49
51
|
- Never send credentials, model keys, provider endpoints, or personal secrets inside the request envelope or `input`.
|
|
50
52
|
- The response `status` must be `succeeded`; a `failed` response is an error, not a result.
|
|
51
53
|
- Public responses never prove that code was developed, tested, or deployed.
|
|
54
|
+
|
|
55
|
+
## IR Schema 完整文档(v6.0.0)
|
|
56
|
+
|
|
57
|
+
### 顶层结构
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"schemaVersion": "blueprint.ir/1.0", // 必填,必须是这个值
|
|
62
|
+
"blueprintId": "string", // 必填,kebab-case
|
|
63
|
+
"title": "string", // 必填
|
|
64
|
+
"revision": 0, // 必填,非负整数(0, 1, 2...)
|
|
65
|
+
"entryNodeId": "string", // 必填,指向 nodes 中 entry:true 的节点
|
|
66
|
+
"baseline": {
|
|
67
|
+
"summary": "string", // 必填
|
|
68
|
+
"facts": [ // 必填,对象数组
|
|
69
|
+
{
|
|
70
|
+
"id": "string", // 每个 fact 必须有 id
|
|
71
|
+
"statement": "string", // 必填
|
|
72
|
+
"status": "confirmed" // 必填枚举,见下方
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"domains": [ // 必填
|
|
77
|
+
{
|
|
78
|
+
"id": "string",
|
|
79
|
+
"name": "string",
|
|
80
|
+
"summary": "string" // 可选
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"modules": [ // 必填
|
|
84
|
+
{
|
|
85
|
+
"id": "string", // 必填
|
|
86
|
+
"domainId": "string", // 必填,引用 domains.id
|
|
87
|
+
"name": "string"
|
|
88
|
+
}
|
|
89
|
+
],
|
|
90
|
+
"nodes": [ // 必填,非空
|
|
91
|
+
{
|
|
92
|
+
"id": "string", // 注意:是 id 不是 nodeId
|
|
93
|
+
"entry": true, // true 标记入口节点(且仅一个)
|
|
94
|
+
"moduleId": "string", // 引用 modules.id
|
|
95
|
+
"title": "string", // 必填
|
|
96
|
+
"inputs": [ // 必须是命名对象数组,字符串数组被拒
|
|
97
|
+
{ "name": "string" }
|
|
98
|
+
],
|
|
99
|
+
"outputs": [ // 同上
|
|
100
|
+
{ "name": "string", "exposed": true }
|
|
101
|
+
],
|
|
102
|
+
"requirementRefs": ["string"] // 引用 baseline.facts.id
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"edges": [ // 必填
|
|
106
|
+
{
|
|
107
|
+
"id": "string",
|
|
108
|
+
"fromNodeId": "string", // 引用 nodes.id
|
|
109
|
+
"toNodeId": "string", // 引用 nodes.id
|
|
110
|
+
"type": "data", // 必填枚举,见下方
|
|
111
|
+
"fromOutput": "string", // 数据边必须精确到端口名
|
|
112
|
+
"toInput": "string", // 数据边必须精确到端口名
|
|
113
|
+
"allowCycle": true, // 环边必须 true
|
|
114
|
+
"loopGuard": "string", // 环边必须有文字说明
|
|
115
|
+
"loopLimit": { // 环边必须有迭代上限
|
|
116
|
+
"maxIterations": 10
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
"acceptanceCriteria": [ // 必填
|
|
121
|
+
{
|
|
122
|
+
"id": "string",
|
|
123
|
+
"statement": "string",
|
|
124
|
+
"nodeRefs": ["string"] // 引用 nodes.id,无 nodeRefs 视为未链接(P1)
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### 字段枚举值
|
|
131
|
+
|
|
132
|
+
**fact.status**:`"confirmed"` | `"inferred"` | `"defaulted"` | `"unknown"` | `"conflicted"` | `"rejected"`
|
|
133
|
+
|
|
134
|
+
**edge.type**:`"data"` | `"control"` | `"success"` | `"error"` | `"trace"` | `"event"` | `"approval"` | `"recovery"` | `"audit"` | `"optional"` | `"compensation"`
|
|
135
|
+
|
|
136
|
+
> ⚠️ `"depends-on"` 不被接受,必须使用上述合法枚举值。
|
|
137
|
+
|
|
138
|
+
### 校验规则
|
|
139
|
+
|
|
140
|
+
- **节点覆盖**:每个节点必须被至少一条 acceptanceCriteria 覆盖(通过 `nodeRefs`)
|
|
141
|
+
- **事实追溯**:每个 fact 必须追溯到节点(通过 `nodes.requirementRefs`)
|
|
142
|
+
- **入口节点**:仅一个节点 `entry: true`,且必须是 `entryNodeId` 指向的节点
|
|
143
|
+
- **数据边**:必须 `fromOutput` / `toInput` 精确匹配端口名
|
|
144
|
+
- **控制边**:不需要端口级连线
|
|
145
|
+
- **环边**:`type` 必须是 `"control"` 或 `"optional"`,必须同时包含 `allowCycle: true` + `loopGuard`(文字说明)+ `loopLimit`(含 `maxIterations` 数字)
|
|
146
|
+
|
|
147
|
+
### 最小合法示例
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"schemaVersion": "blueprint.ir/1.0",
|
|
152
|
+
"blueprintId": "demo-pipeline",
|
|
153
|
+
"title": "Demo Pipeline",
|
|
154
|
+
"revision": 0,
|
|
155
|
+
"entryNodeId": "step-a",
|
|
156
|
+
"baseline": {
|
|
157
|
+
"summary": "A minimal 2-node linear pipeline",
|
|
158
|
+
"facts": [
|
|
159
|
+
{
|
|
160
|
+
"id": "f-input",
|
|
161
|
+
"statement": "System must accept user input",
|
|
162
|
+
"status": "confirmed"
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"domains": [
|
|
167
|
+
{
|
|
168
|
+
"id": "d-core",
|
|
169
|
+
"name": "Core"
|
|
170
|
+
}
|
|
171
|
+
],
|
|
172
|
+
"modules": [
|
|
173
|
+
{
|
|
174
|
+
"id": "m-impl",
|
|
175
|
+
"domainId": "d-core",
|
|
176
|
+
"name": "Implementation"
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
"nodes": [
|
|
180
|
+
{
|
|
181
|
+
"id": "step-a",
|
|
182
|
+
"entry": true,
|
|
183
|
+
"moduleId": "m-impl",
|
|
184
|
+
"title": "Step A – Receive Input",
|
|
185
|
+
"inputs": [],
|
|
186
|
+
"outputs": [
|
|
187
|
+
{ "name": "data" }
|
|
188
|
+
],
|
|
189
|
+
"requirementRefs": ["f-input"]
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
"id": "step-b",
|
|
193
|
+
"moduleId": "m-impl",
|
|
194
|
+
"title": "Step B – Process",
|
|
195
|
+
"inputs": [
|
|
196
|
+
{ "name": "data" }
|
|
197
|
+
],
|
|
198
|
+
"outputs": []
|
|
199
|
+
}
|
|
200
|
+
],
|
|
201
|
+
"edges": [
|
|
202
|
+
{
|
|
203
|
+
"id": "e-a-to-b",
|
|
204
|
+
"fromNodeId": "step-a",
|
|
205
|
+
"toNodeId": "step-b",
|
|
206
|
+
"type": "data",
|
|
207
|
+
"fromOutput": "data",
|
|
208
|
+
"toInput": "data"
|
|
209
|
+
}
|
|
210
|
+
],
|
|
211
|
+
"acceptanceCriteria": [
|
|
212
|
+
{
|
|
213
|
+
"id": "ac-step-a",
|
|
214
|
+
"statement": "Input is received and forwarded",
|
|
215
|
+
"nodeRefs": ["step-a"]
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
"id": "ac-step-b",
|
|
219
|
+
"statement": "Processing completes successfully",
|
|
220
|
+
"nodeRefs": ["step-b"]
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## 粗粒度模式(Coarse Mode)
|
|
227
|
+
|
|
228
|
+
在请求 `input` 中设置 `coarseMode: true` 即可启用粗粒度模式:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"input": {
|
|
233
|
+
"schemaVersion": "blueprint.skill.request/1.0",
|
|
234
|
+
"requestId": "unique-id",
|
|
235
|
+
"operation": "compile-inline",
|
|
236
|
+
"coarseMode": true,
|
|
237
|
+
"input": { "blueprint": { ... } }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
启用后的行为差异:
|
|
243
|
+
|
|
244
|
+
- **节点端口可省略**:`inputs` 和 `outputs` 可以为空数组 `[]`,无需声明具体端口
|
|
245
|
+
- **边无需端口级连线**:`edges` 中不提供 `fromOutput` / `toInput` 仍可通过校验,节点级连接即满足输入源 / 输出消费检查
|
|
246
|
+
- **端口校验仅在显式声明时生效**:只有当节点明确声明了 `inputs` 或 `outputs`(非空数组)时,才强制要求数据边绑定端口名
|
|
247
|
+
- **适用场景**:推荐用于单文件、单实现者的快速原型场景,无需声明详细端口拓扑
|
|
248
|
+
|
|
249
|
+
## 新操作文档
|
|
250
|
+
|
|
251
|
+
### `acceptance-report`
|
|
252
|
+
|
|
253
|
+
接受实施后验证结果,关闭验收标准闭环。
|
|
254
|
+
|
|
255
|
+
**输入**:
|
|
256
|
+
|
|
257
|
+
```json
|
|
258
|
+
{
|
|
259
|
+
"blueprintId": "your-blueprint-id",
|
|
260
|
+
"results": [
|
|
261
|
+
{
|
|
262
|
+
"acId": "ac-step-a",
|
|
263
|
+
"passed": true,
|
|
264
|
+
"evidence": "单元测试覆盖,运行通过" // 可选,提供通过证据
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
"acId": "ac-step-b",
|
|
268
|
+
"passed": false,
|
|
269
|
+
"evidence": "缺少边界条件测试" // 可选,记录失败原因
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
- `blueprintId`:目标蓝图 ID
|
|
276
|
+
- `results`:数组,每项包含 `acId`(对应 `acceptanceCriteria.id`)、`passed`(布尔)、`evidence`(可选字符串)
|
|
277
|
+
|
|
278
|
+
### `answer-questions`
|
|
279
|
+
|
|
280
|
+
关闭 OPEN-QUESTIONS 循环,回答蓝图中的开放问题。
|
|
281
|
+
|
|
282
|
+
**输入**:
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"blueprintId": "your-blueprint-id",
|
|
287
|
+
"answers": [
|
|
288
|
+
{
|
|
289
|
+
"questionId": "q-storage-backend",
|
|
290
|
+
"answer": "使用 PostgreSQL 作为持久化存储,因为团队已有运维经验"
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
- `blueprintId`:目标蓝图 ID
|
|
297
|
+
- `answers`:数组,每项包含 `questionId`(问题标识)和 `answer`(回答内容)
|
|
298
|
+
|
|
299
|
+
### IR 模板
|
|
300
|
+
|
|
301
|
+
以下是系统提供的常用 IR 模板,可作为构建蓝图的起点:
|
|
302
|
+
|
|
303
|
+
| 模板名称 | 说明 | 适用场景 |
|
|
304
|
+
|---------|------|---------|
|
|
305
|
+
| `linear-pipeline` | 线性流水线,节点依次执行 | 数据处理、ETL 任务 |
|
|
306
|
+
| `fan-out-fan-in` | 分叉汇聚模式,多路并行后合并 | 批量处理、并行计算 |
|
|
307
|
+
| `state-machine` | 状态机模式,带状态转移控制 | 流程审批、工作流引擎 |
|
|
308
|
+
| `event-driven` | 事件驱动模式,基于事件触发节点 | 微服务、异步任务编排 |
|
|
309
|
+
| `recursive-loop` | 递归循环模式,带环边与迭代守卫 | 迭代优化、爬虫、搜索 |
|
|
310
|
+
|
|
311
|
+
使用模板时,在 `compile-inline` 的 `input` 中传入 `template: "linear-pipeline"` 即可自动填充基础结构。
|
package/skill/skill.json
CHANGED