cli-blueprint 7.0.7 → 7.0.18
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 +2 -0
- package/installer.mjs +123 -6
- package/package.json +1 -2
- package/skill/SKILL.md +30 -75
- package/skill/skill.json +2 -2
package/README.md
CHANGED
|
@@ -23,3 +23,5 @@ Source: https://github.com/88208555/blueprint-clitax.git
|
|
|
23
23
|
The live endpoint is `https://cli.tax/wvz6zmRWmX` and speaks
|
|
24
24
|
`blueprint.skill.request/1.0`.
|
|
25
25
|
# marker
|
|
26
|
+
|
|
27
|
+
Feedback: the skill detail page's "Usage reviews" tab supports like / dislike / daily chat. Likes and dislikes count toward the market reputation (daily marquee cleanup); daily chat messages are kept for 7 days.
|
package/installer.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 八个官方技能共用这一份安装器。packages/*-cli/installer.mjs 必须与本文件字节一致。
|
|
3
3
|
* 禁止第二套超时、第二套版本来源、第二套 bin 名。
|
|
4
4
|
*/
|
|
5
|
-
import {
|
|
6
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
5
|
+
import { randomUUID } from 'node:crypto'
|
|
6
|
+
import { constants, existsSync, readFileSync } from 'node:fs'
|
|
7
|
+
import { cp, lstat, mkdir, open, rm, writeFile } from 'node:fs/promises'
|
|
7
8
|
import { dirname, join, resolve } from 'node:path'
|
|
8
9
|
import { stdin, stdout } from 'node:process'
|
|
9
10
|
import { createInterface } from 'node:readline/promises'
|
|
@@ -12,6 +13,18 @@ import { fileURLToPath } from 'node:url'
|
|
|
12
13
|
export const LOOKUP_TIMEOUT_MS = 8000
|
|
13
14
|
export const CALL_TIMEOUT_MS = 120_000
|
|
14
15
|
const INSTALL_META = 'install-meta.json'
|
|
16
|
+
const FEEDBACK_API_PATH = '/api/v1/telemetry/skill-usage'
|
|
17
|
+
const BRAIN_CLIENT_TOKEN_FILE_ENV = 'CLITAX_BRAIN_CLIENT_TOKEN_FILE'
|
|
18
|
+
const BRAIN_CLIENT_TOKEN_FILE_VERSION = 'member-brain.client-token-file/1.0'
|
|
19
|
+
const BRAIN_CLIENT_AUTH_SCHEME = 'BrainClient'
|
|
20
|
+
const BRAIN_CLIENT_TOKEN_FILE_MAX_BYTES = 16_384
|
|
21
|
+
const BRAIN_CLIENT_TOKEN_FILE_MODE = 0o600
|
|
22
|
+
const FEEDBACK_COMMENT_MAX = 500
|
|
23
|
+
const FEEDBACK_SCORE_MIN = 0
|
|
24
|
+
const FEEDBACK_SCORE_MAX = 100
|
|
25
|
+
const BRAIN_CLIENT_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/
|
|
26
|
+
const FEEDBACK_INVOCATION_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
27
|
+
const FEEDBACK_SCORE_PATTERN = /^(?:0|[1-9]\d{0,2})$/
|
|
15
28
|
|
|
16
29
|
function asObject(value, label) {
|
|
17
30
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -112,12 +125,113 @@ export async function callOfficialSkill(context, operation, input) {
|
|
|
112
125
|
return payload
|
|
113
126
|
}
|
|
114
127
|
|
|
128
|
+
export function feedbackCommandInput(args) {
|
|
129
|
+
const invocationId = requiredString(args[1], 'feedback invocation id')
|
|
130
|
+
if (!FEEDBACK_INVOCATION_PATTERN.test(invocationId)) {
|
|
131
|
+
throw new Error('feedback invocation id must be the UUID returned by a real skill response')
|
|
132
|
+
}
|
|
133
|
+
const scoreText = requiredString(args[2], 'feedback score')
|
|
134
|
+
if (!FEEDBACK_SCORE_PATTERN.test(scoreText)) {
|
|
135
|
+
throw new Error(`feedback score must be an integer between ${FEEDBACK_SCORE_MIN} and ${FEEDBACK_SCORE_MAX}`)
|
|
136
|
+
}
|
|
137
|
+
const score = Number(scoreText)
|
|
138
|
+
if (!Number.isInteger(score) || score < FEEDBACK_SCORE_MIN || score > FEEDBACK_SCORE_MAX) {
|
|
139
|
+
throw new Error(`feedback score must be between ${FEEDBACK_SCORE_MIN} and ${FEEDBACK_SCORE_MAX}`)
|
|
140
|
+
}
|
|
141
|
+
const userComment = args.slice(3).join(' ').trim()
|
|
142
|
+
if (!userComment) throw new Error('feedback comment is required')
|
|
143
|
+
if (userComment.length > FEEDBACK_COMMENT_MAX) {
|
|
144
|
+
throw new Error(`feedback comment must be at most ${FEEDBACK_COMMENT_MAX} characters`)
|
|
145
|
+
}
|
|
146
|
+
return { invocationId, score, userComment }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function brainClientAuthorization(context, environment) {
|
|
150
|
+
const configuredPath = typeof environment[BRAIN_CLIENT_TOKEN_FILE_ENV] === 'string'
|
|
151
|
+
? environment[BRAIN_CLIENT_TOKEN_FILE_ENV].trim() : ''
|
|
152
|
+
if (!configuredPath) throw new Error(`${BRAIN_CLIENT_TOKEN_FILE_ENV} is required`)
|
|
153
|
+
if (process.platform === 'win32' || typeof process.getuid !== 'function') {
|
|
154
|
+
throw new Error('Brain Client token file ownership cannot be verified')
|
|
155
|
+
}
|
|
156
|
+
const tokenFilePath = resolve(configuredPath)
|
|
157
|
+
const linkStatus = await lstat(tokenFilePath)
|
|
158
|
+
if (linkStatus.isSymbolicLink()) throw new Error('Brain Client token file cannot be a symlink')
|
|
159
|
+
const handle = await open(tokenFilePath, constants.O_RDONLY | constants.O_NOFOLLOW)
|
|
160
|
+
try {
|
|
161
|
+
const status = await handle.stat()
|
|
162
|
+
if (!status.isFile() || status.uid !== process.getuid()
|
|
163
|
+
|| (status.mode & 0o777) !== BRAIN_CLIENT_TOKEN_FILE_MODE
|
|
164
|
+
|| status.size < 1 || status.size > BRAIN_CLIENT_TOKEN_FILE_MAX_BYTES) {
|
|
165
|
+
throw new Error('Brain Client token file must be owned by the current user with mode 0600')
|
|
166
|
+
}
|
|
167
|
+
const tokenFile = asObject(JSON.parse(await handle.readFile('utf8')), 'Brain Client token file')
|
|
168
|
+
const expectedKeys = ['authorizationScheme', 'endpoint', 'schemaVersion', 'token']
|
|
169
|
+
if (Object.keys(tokenFile).sort().join('\n') !== expectedKeys.join('\n')) {
|
|
170
|
+
throw new Error('Brain Client token file contains unknown or missing fields')
|
|
171
|
+
}
|
|
172
|
+
const endpoint = new URL(requiredString(tokenFile.endpoint, 'Brain Client endpoint'))
|
|
173
|
+
if (tokenFile.schemaVersion !== BRAIN_CLIENT_TOKEN_FILE_VERSION
|
|
174
|
+
|| tokenFile.authorizationScheme !== BRAIN_CLIENT_AUTH_SCHEME
|
|
175
|
+
|| endpoint.origin !== new URL(context.endpoint).origin
|
|
176
|
+
|| endpoint.pathname !== FEEDBACK_API_PATH || endpoint.search || endpoint.hash
|
|
177
|
+
|| endpoint.username || endpoint.password
|
|
178
|
+
|| !BRAIN_CLIENT_TOKEN_PATTERN.test(tokenFile.token)) {
|
|
179
|
+
throw new Error('Brain Client token file authority is invalid')
|
|
180
|
+
}
|
|
181
|
+
return `${BRAIN_CLIENT_AUTH_SCHEME} ${tokenFile.token}`
|
|
182
|
+
} finally {
|
|
183
|
+
await handle.close()
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function submitOfficialSkillFeedback(context, args, environment, request) {
|
|
188
|
+
const input = feedbackCommandInput(args)
|
|
189
|
+
const authorization = await brainClientAuthorization(context, environment)
|
|
190
|
+
const requestId = `${context.runtimeCode}-${randomUUID()}`
|
|
191
|
+
let response
|
|
192
|
+
try {
|
|
193
|
+
response = await request(new URL(FEEDBACK_API_PATH, context.endpoint), {
|
|
194
|
+
method: 'POST',
|
|
195
|
+
headers: {
|
|
196
|
+
'Content-Type': 'application/json',
|
|
197
|
+
Authorization: authorization,
|
|
198
|
+
},
|
|
199
|
+
body: JSON.stringify({
|
|
200
|
+
requestId,
|
|
201
|
+
skillId: context.runtimeCode,
|
|
202
|
+
invocationId: input.invocationId,
|
|
203
|
+
score: input.score,
|
|
204
|
+
userComment: input.userComment,
|
|
205
|
+
}),
|
|
206
|
+
signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS),
|
|
207
|
+
})
|
|
208
|
+
} catch {
|
|
209
|
+
throw new Error('cli.tax feedback request failed')
|
|
210
|
+
}
|
|
211
|
+
let payload
|
|
212
|
+
try {
|
|
213
|
+
payload = asObject(await response.json(), 'cli.tax feedback response')
|
|
214
|
+
} catch (error) {
|
|
215
|
+
if (error instanceof Error && error.message.startsWith('cli.tax feedback response')) throw error
|
|
216
|
+
throw new Error(`cli.tax feedback failed: non-JSON response (HTTP ${response.status})`)
|
|
217
|
+
}
|
|
218
|
+
if (!response.ok || payload.ok !== true) {
|
|
219
|
+
throw new Error(`cli.tax feedback failed: HTTP ${response.status}`)
|
|
220
|
+
}
|
|
221
|
+
if (payload.requestId !== requestId || typeof payload.id !== 'string'
|
|
222
|
+
|| !FEEDBACK_INVOCATION_PATTERN.test(payload.id)
|
|
223
|
+
|| typeof payload.duplicated !== 'boolean') {
|
|
224
|
+
throw new Error('cli.tax feedback response authority is invalid')
|
|
225
|
+
}
|
|
226
|
+
return { id: payload.id, requestId, duplicated: payload.duplicated }
|
|
227
|
+
}
|
|
228
|
+
|
|
115
229
|
export async function installOfficialSkill(context, explicit) {
|
|
116
230
|
const target = installTarget(context.skillName, explicit)
|
|
117
231
|
await mkdir(target, { recursive: true })
|
|
118
232
|
const previous = readInstallMeta(target)
|
|
119
|
-
await
|
|
120
|
-
await
|
|
233
|
+
await rm(join(target, 'references'), { recursive: true, force: true })
|
|
234
|
+
await cp(context.skillDir, target, { recursive: true, force: true })
|
|
121
235
|
const installed = asObject(JSON.parse(readFileSync(join(target, 'skill.json'), 'utf8')), 'installed skill.json')
|
|
122
236
|
const installedVersion = requiredString(installed.version, 'installed skill.json version')
|
|
123
237
|
await writeFile(join(target, INSTALL_META), `${JSON.stringify({
|
|
@@ -170,7 +284,6 @@ export function defaultUsage(context, extraLines) {
|
|
|
170
284
|
' Check whether the installed skill has a newer version.',
|
|
171
285
|
` npx ${context.npmName}@latest run`,
|
|
172
286
|
' Run the skill handshake: discover capabilities and collect intake answers.',
|
|
173
|
-
'',
|
|
174
287
|
`Endpoint: ${context.endpoint}`,
|
|
175
288
|
]
|
|
176
289
|
if (extraLines?.length) lines.push('', ...extraLines)
|
|
@@ -227,6 +340,10 @@ export async function dispatchOfficialSkillCli(options) {
|
|
|
227
340
|
if (command === 'install') await installOfficialSkill(context, argument)
|
|
228
341
|
else if (command === 'check') await checkOfficialSkill(context, argument)
|
|
229
342
|
else if (command === 'run') await options.runCommand(context)
|
|
343
|
+
else if (command === 'feedback') {
|
|
344
|
+
const receipt = await submitOfficialSkillFeedback(context, args, process.env, fetch)
|
|
345
|
+
console.log(`${context.displayName} feedback accepted: ${receipt.id}`)
|
|
346
|
+
}
|
|
230
347
|
else if (command === 'help' || command === '--help' || command === '-h') {
|
|
231
348
|
console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
|
|
232
349
|
} else {
|
package/package.json
CHANGED
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
],
|
|
13
13
|
"license": "UNLICENSED",
|
|
14
14
|
"name": "cli-blueprint",
|
|
15
|
-
"private": false,
|
|
16
15
|
"repository": {
|
|
17
16
|
"type": "git",
|
|
18
17
|
"url": "https://github.com/88208555/blueprint-clitax.git"
|
|
19
18
|
},
|
|
20
19
|
"type": "module",
|
|
21
|
-
"version": "7.0.
|
|
20
|
+
"version": "7.0.18"
|
|
22
21
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -5,7 +5,9 @@ description: '把一个目标编译为可执行、可验证、可追溯的工程
|
|
|
5
5
|
|
|
6
6
|
# Blueprint Skill
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Package version: v7.0.18
|
|
9
|
+
|
|
10
|
+
远端 Hermes 编译器版本:0.4.0(独立于 npm 包版本)
|
|
9
11
|
|
|
10
12
|
Endpoint: https://cli.tax/wvz6zmRWmX
|
|
11
13
|
Request schema: blueprint.skill.request/1.0
|
|
@@ -38,6 +40,7 @@ POST JSON to the endpoint with an `input` wrapper:
|
|
|
38
40
|
|
|
39
41
|
1. Call `capabilities` first and read the returned `nextStep`.
|
|
40
42
|
2. Call `intake` and ask the user the returned questions one at a time, waiting for each answer.
|
|
43
|
+
For a new code project, require the ArchGuard contract digest created before planning. For an existing project, preserve an existing `arch.contract.yaml` digest in the Blueprint inputs; if no contract exists, record a non-blocking recommendation instead of inventing one.
|
|
41
44
|
3. Do not compile a Blueprint until all required questions are answered.
|
|
42
45
|
4. Build a Blueprint conforming to `blueprint.ir/1.0`, then call `validate`.
|
|
43
46
|
5. Fix every validation finding until the report is green, then call `compile-inline` and save the artifacts.
|
|
@@ -52,7 +55,7 @@ After `capabilities`, read `officialCatalog`. Default allowlist is official skil
|
|
|
52
55
|
- The response `status` must be `succeeded`; a `failed` response is an error, not a result.
|
|
53
56
|
- Public responses never prove that code was developed, tested, or deployed.
|
|
54
57
|
|
|
55
|
-
## IR Schema 完整文档(
|
|
58
|
+
## IR Schema 完整文档(blueprint.ir/1.0)
|
|
56
59
|
|
|
57
60
|
### 顶层结构
|
|
58
61
|
|
|
@@ -223,89 +226,41 @@ After `capabilities`, read `officialCatalog`. Default allowlist is official skil
|
|
|
223
226
|
}
|
|
224
227
|
```
|
|
225
228
|
|
|
226
|
-
##
|
|
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
|
-
接受实施后验证结果,关闭验收标准闭环。
|
|
229
|
+
## Finding 修复循环
|
|
254
230
|
|
|
255
|
-
|
|
231
|
+
`validate` 与 `compile-inline` 的确定性报告位于 `validation.findings`。每条 Finding 包含 `ruleId`、`severity`、`entityRef`、`message`、`evidence` 与 `recommendedAction`。调用方必须按 `recommendedAction` 修复对应实体并重新 `validate`,不得把 `blocked` 当成编译结果。
|
|
256
232
|
|
|
257
233
|
```json
|
|
258
234
|
{
|
|
259
|
-
"
|
|
260
|
-
"
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
"acId": "ac-step-b",
|
|
268
|
-
"passed": false,
|
|
269
|
-
"evidence": "缺少边界条件测试" // 可选,记录失败原因
|
|
270
|
-
}
|
|
271
|
-
]
|
|
235
|
+
"ruleId": "IR_REQUIRED_FIELD",
|
|
236
|
+
"severity": "P0",
|
|
237
|
+
"entityRef": "blueprint.title",
|
|
238
|
+
"message": "title is required.",
|
|
239
|
+
"evidence": {},
|
|
240
|
+
"recommendedAction": "Add a human-readable title."
|
|
272
241
|
}
|
|
273
242
|
```
|
|
274
243
|
|
|
275
|
-
|
|
276
|
-
- `results`:数组,每项包含 `acId`(对应 `acceptanceCriteria.id`)、`passed`(布尔)、`evidence`(可选字符串)
|
|
277
|
-
|
|
278
|
-
### `answer-questions`
|
|
279
|
-
|
|
280
|
-
关闭 OPEN-QUESTIONS 循环,回答蓝图中的开放问题。
|
|
281
|
-
|
|
282
|
-
**输入**:
|
|
244
|
+
## 实现状态
|
|
283
245
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
"answer": "使用 PostgreSQL 作为持久化存储,因为团队已有运维经验"
|
|
291
|
-
}
|
|
292
|
-
]
|
|
293
|
-
}
|
|
294
|
-
```
|
|
246
|
+
| ID | 能力 | 状态 | 边界 |
|
|
247
|
+
|---|---|---|---|
|
|
248
|
+
| B1 | 结构校验与可修复 Finding | 已实现 | `evidence` 与 `recommendedAction` 已由远端 Hermes 0.4.0 返回。 |
|
|
249
|
+
| B2 | 增量规划/修订 | 部分实现 | IR 支持调用方维护 `revision`;服务端不保存蓝图,也没有增量更新操作。 |
|
|
250
|
+
| B3 | 业务模板库与粗粒度模式 | 规划中 | 当前没有模板操作,`template` 与 `coarseMode` 均不是受支持输入。 |
|
|
251
|
+
| B4 | 验收回传、开放问题闭环、Validator 桥接 | 规划中 | 当前没有 `acceptance-report`、`answer-questions` 或 Validator 桥接操作。 |
|
|
295
252
|
|
|
296
|
-
|
|
297
|
-
- `answers`:数组,每项包含 `questionId`(问题标识)和 `answer`(回答内容)
|
|
253
|
+
只调用 `capabilities` 返回的五个操作。不要根据规划中条目构造请求,也不要把 npm 包版本 `v7.0.18` 与远端 Hermes 编译器版本 `0.4.0` 混为一谈。
|
|
298
254
|
|
|
299
|
-
|
|
255
|
+
## 反馈通道
|
|
300
256
|
|
|
301
|
-
|
|
257
|
+
评价只能由 IDE / 智能体自动回传,严禁人类干预:
|
|
302
258
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
| `recursive-loop` | 递归循环模式,带环边与迭代守卫 | 迭代优化、爬虫、搜索 |
|
|
259
|
+
1. 仅当一次真实技能 HTTP 调用成功,并且响应顶层包含 `feedbackInvocationId` 时,调用方才可进入评价流程。
|
|
260
|
+
2. 调用方必须使用本次响应的原始 ID,按真实执行结果生成整数分数(0–100)和真实评语,再自动执行:
|
|
261
|
+
`npx cli-blueprint@latest feedback <feedbackInvocationId> <score> <comment...>`
|
|
262
|
+
3. 自动回传必须使用会员中心 Brain 客户端生成的同一身份文件,并通过 `CLITAX_BRAIN_CLIENT_TOKEN_FILE` 指向该 `0600` 文件;服务端以会员账号实名入库,不接受匿名评价。
|
|
263
|
+
4. 禁止伪造、猜测、复用或跨调用转移 `feedbackInvocationId`;禁止让人类选择分数、填写评价或代为执行命令。
|
|
264
|
+
5. 缺少会员客户端身份或 ID、分数越界、空评语、响应不合法及任何 HTTP 失败都必须视为回传失败,不得记为成功。
|
|
310
265
|
|
|
311
|
-
|
|
266
|
+
日常交流走技能详情页的独立聊天通道,不使用 `feedback` 命令,也不计入评价、评分或首页跑马灯。
|
package/skill/skill.json
CHANGED