cli-aimlock 7.0.19 → 7.0.28

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 CHANGED
@@ -2,29 +2,33 @@
2
2
  * 八个官方技能共用这一份安装器。packages/*-cli/installer.mjs 必须与本文件字节一致。
3
3
  * 禁止第二套超时、第二套版本来源、第二套 bin 名。
4
4
  */
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'
5
+ import { existsSync, readFileSync } from 'node:fs'
6
+ import { cp, mkdir, rm, writeFile } from 'node:fs/promises'
8
7
  import { dirname, join, resolve } from 'node:path'
9
8
  import { stdin, stdout } from 'node:process'
10
9
  import { createInterface } from 'node:readline/promises'
11
10
  import { fileURLToPath } from 'node:url'
11
+ import {
12
+ LOOKUP_TIMEOUT_MS,
13
+ brokerCommandInput,
14
+ invokeCommandInput,
15
+ invokeOfficialSkill,
16
+ } from './broker.mjs'
17
+
18
+ export {
19
+ CALL_TIMEOUT_MS,
20
+ LOOKUP_TIMEOUT_MS,
21
+ authoritativeEvaluation,
22
+ brainClientAuthorization,
23
+ brainClientTokenPath,
24
+ brokerCommandInput,
25
+ callOfficialSkill,
26
+ invokeCommandInput,
27
+ invokeOfficialSkill,
28
+ } from './broker.mjs'
12
29
 
13
- export const LOOKUP_TIMEOUT_MS = 8000
14
- export const CALL_TIMEOUT_MS = 120_000
15
30
  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})$/
31
+ const BROKER_STDIN_MAX_BYTES = 1_048_576
28
32
 
29
33
  function asObject(value, label) {
30
34
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
@@ -94,138 +98,6 @@ export async function fetchLatestVersion(context) {
94
98
  }
95
99
  }
96
100
 
97
- export async function callOfficialSkill(context, operation, input) {
98
- const requestId = `${context.npmName}-${Date.now()}`
99
- const response = await fetch(context.endpoint, {
100
- method: 'POST',
101
- headers: { 'Content-Type': 'application/json' },
102
- body: JSON.stringify({
103
- input: {
104
- schemaVersion: context.schemaVersion,
105
- requestId,
106
- operation,
107
- input,
108
- },
109
- }),
110
- signal: AbortSignal.timeout(CALL_TIMEOUT_MS),
111
- })
112
- let payload
113
- try {
114
- payload = await response.json()
115
- } catch {
116
- throw new Error(`${context.displayName} ${operation} failed: non-JSON response (HTTP ${response.status}). Check ${context.endpoint}.`)
117
- }
118
- if (!response.ok || payload?.ok !== true) {
119
- const message = payload?.error?.message
120
- if (typeof message !== 'string' || !message.trim()) {
121
- throw new Error(`${context.displayName} ${operation} failed: HTTP ${response.status}`)
122
- }
123
- throw new Error(`${context.displayName} ${operation} failed: ${message}`)
124
- }
125
- return payload
126
- }
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
-
229
101
  export async function installOfficialSkill(context, explicit) {
230
102
  const target = installTarget(context.skillName, explicit)
231
103
  await mkdir(target, { recursive: true })
@@ -283,21 +155,52 @@ export function defaultUsage(context, extraLines) {
283
155
  ` npx ${context.npmName}@latest check [directory]`,
284
156
  ' Check whether the installed skill has a newer version.',
285
157
  ` npx ${context.npmName}@latest run`,
286
- ' Run the skill handshake: discover capabilities and collect intake answers.',
158
+ " Run this skill's applicability or onboarding flow; only a real HTTP invocation can trigger automatic evaluation.",
159
+ ` npx ${context.npmName}@latest invoke <operation> <JSON-object>`,
160
+ ' Invoke through the restricted local broker; a valid real HTTP invocation submits one authority-bound evaluation.',
161
+ ` npx ${context.npmName}@latest broker`,
162
+ ' Read one {"operation":"...","input":{...}} request from JSON stdin.',
163
+ 'Credential: CLITAX_BRAIN_CLIENT_TOKEN_FILE (the broker reads it; never pass the token).',
287
164
  `Endpoint: ${context.endpoint}`,
288
165
  ]
289
166
  if (extraLines?.length) lines.push('', ...extraLines)
290
167
  return lines.join('\n')
291
168
  }
292
169
 
170
+ function brokerDependencies() {
171
+ return { environment: process.env, request: fetch }
172
+ }
173
+
174
+ async function readBrokerSource(input) {
175
+ let source = ''
176
+ for await (const chunk of input) {
177
+ source += chunk
178
+ if (Buffer.byteLength(source, 'utf8') > BROKER_STDIN_MAX_BYTES) {
179
+ throw new Error(`broker request must be at most ${BROKER_STDIN_MAX_BYTES} bytes`)
180
+ }
181
+ }
182
+ if (!source.trim()) throw new Error('broker request is required on stdin')
183
+ return source
184
+ }
185
+
186
+ async function runBrokerInvocation(context, commandInput) {
187
+ const invocation = await invokeOfficialSkill(
188
+ context, commandInput.operation, commandInput.input, brokerDependencies(),
189
+ )
190
+ console.log(JSON.stringify(invocation))
191
+ return invocation
192
+ }
193
+
293
194
  export async function runIntakeHandshake(context, spec) {
294
- const capabilities = await callOfficialSkill(context, 'capabilities', {})
195
+ const invocation = await invokeOfficialSkill(context, 'capabilities', {}, brokerDependencies())
196
+ const capabilities = invocation.response
295
197
  const output = capabilities.output && typeof capabilities.output === 'object' ? capabilities.output : {}
296
198
  const skill = output.skill && typeof output.skill === 'object' ? output.skill : {}
297
199
  const version = typeof skill.version === 'string' && skill.version.trim()
298
200
  ? skill.version.trim()
299
201
  : context.skillVersion
300
202
  console.log(`${context.displayName} ${version}`)
203
+ console.log(`Automatic feedback accepted: ${invocation.feedback.id}`)
301
204
  if (typeof spec.afterCapabilities === 'function') spec.afterCapabilities(output)
302
205
  const readline = createInterface({ input: stdin, output: stdout })
303
206
  const answers = []
@@ -340,9 +243,9 @@ export async function dispatchOfficialSkillCli(options) {
340
243
  if (command === 'install') await installOfficialSkill(context, argument)
341
244
  else if (command === 'check') await checkOfficialSkill(context, argument)
342
245
  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}`)
246
+ else if (command === 'invoke') await runBrokerInvocation(context, invokeCommandInput(args))
247
+ else if (command === 'broker') {
248
+ await runBrokerInvocation(context, brokerCommandInput(await readBrokerSource(stdin)))
346
249
  }
347
250
  else if (command === 'help' || command === '--help' || command === '-h') {
348
251
  console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
package/package.json CHANGED
@@ -2,13 +2,23 @@
2
2
  "bin": {
3
3
  "cli-aimlock": "./cli.mjs"
4
4
  },
5
+ "dependencies": {
6
+ "cli-swarm": "7.0.28"
7
+ },
5
8
  "description": "Aimlock skill installer for CLI.Tax: lock a user request into an executable aim and route Blueprint, Swarm, and Calctool.",
6
9
  "files": [
7
10
  "cli.mjs",
8
11
  "installer.mjs",
12
+ "broker.mjs",
9
13
  "README.md",
10
14
  "skill/SKILL.md",
11
- "skill/skill.json"
15
+ "skill/skill.json",
16
+ "aimlock-context-map.mjs",
17
+ "aimlock-coordination.mjs",
18
+ "aimlock-local-fs.mjs",
19
+ "aimlock-local-gate.mjs",
20
+ "aimlock-local-runner.mjs",
21
+ "aimlock-runtime.mjs"
12
22
  ],
13
23
  "license": "UNLICENSED",
14
24
  "name": "cli-aimlock",
@@ -17,5 +27,5 @@
17
27
  "url": "https://github.com/88208555/aimlock-clitax.git"
18
28
  },
19
29
  "type": "module",
20
- "version": "7.0.19"
30
+ "version": "7.0.28"
21
31
  }
package/skill/SKILL.md CHANGED
@@ -1,26 +1,55 @@
1
1
  ---
2
2
  name: aimlock
3
- description: "Aimlock 把用户需求锁成可执行的智能目标,阻止思考漂移、执行漂移和范围膨胀。主智能体先读本规范,禁止立刻改代码:先拆 JSON 任务,按范围合同分成 Lock / Probe / Swarm。Lock 档单文件小改由主脑快照后改写;Probe 档只读分析修改节点,确认后再写;Swarm 档才调用蜂群。Blueprint 编规划合同,Swarm 派单执行,Calctool 生成计算工具。改前文件快照,禁止创建 git 分支。插话先判关联再更新任务;强制停止立即停止。目标未完成交出控制权时每 90 秒发送「智能目标持续执行中,请勿关闭!」。交付文档需用户确认。调用前必须 capabilities,再按 nextStep 前进。Locks a user request into an executable aim to stop thought-drift, execution-drift, and scope blow-ups. Read this skill first; do not edit code yet. Split JSON tasks; classify Lock / Probe / Swarm. Blueprint for contracts, Swarm for dispatch, Calctool for calculators. Snapshot files before mutate; never create git branches. Interrupt: correlate first. Keep-alive every 90s while the aim is open. Delivery docs only if the user confirms. Always call capabilities first. Фиксирует запрос в исполняемую цель, чтобы остановить дрейф мысли, дрейф исполнения и раздувание объёма. Сначала эта спецификация, код не трогать. JSON-задачи, режимы Lock / Probe / Swarm. Blueprint — контракт, Swarm — раздача, Calctool — калькулятор. Снимок файлов до правки, без git-веток. Сначала capabilities."
3
+ description: "Aimlock 仅用于大型、深度、跨模块、高风险、需要并行协作或用户明确要求的工程修改,负责锁定目标、范围、快照和写入门禁。低难度、预计不超过 500 行、非跨模块、非高风险、不并行且未明确要求 Aimlock 的需求不要启动完整链;应直接处理或只调用一个匹配的专项技能。Use Aimlock only for large, deep, cross-module, high-risk, parallel, or explicitly requested engineering changes. Bypass low-difficulty work of at most 500 estimated lines only when it is not cross-module, high-risk, parallel, or explicitly assigned to Aimlock; handle it directly or use one matched specialist. Используйте Aimlock только для крупных, сложных, межмодульных, рискованных, параллельных или явно назначенных инженерных изменений. Простую задачу до 500 строк обходите только без межмодульности, высокого риска, параллели и явного требования Aimlock; выполните её напрямую либо одним профильным навыком."
4
4
  ---
5
5
 
6
6
  # Aimlock Skill
7
7
 
8
- Package version: v7.0.19
8
+ Package version: v7.0.28
9
9
 
10
10
  Endpoint: https://cli.tax/R3mQ8kWpXn
11
- Request schema: aimlock.skill.request/1.0
12
- Response schema: aimlock.skill.response/1.0
13
11
 
14
- Aimlock is a policy layer. It does not replace Blueprint, Swarm, or Calctool. Aimlock decides **when to fire, how wide, and how to stop drift**.
12
+ Request schema: `aimlock.skill.request/1.1`(运行时继续接受 `1.0` 存量客户端)
13
+
14
+ Response schema: `aimlock.skill.response/1.1`(`1.0` 请求返回 `1.0` 响应)
15
+
16
+ Aimlock is a high-overhead policy gate for substantial engineering work. It does not replace a specialist skill and must not turn a small change into a full workflow.
17
+
18
+ ## Applicability gate
19
+
20
+ Apply this gate before calling `capabilities`.
21
+
22
+ Return `bypass` when every condition is true:
23
+
24
+ - `difficulty` is `low`;
25
+ - `estimatedChangedLines` is at most `500`;
26
+ - `crossModule` is `false`;
27
+ - risk is not `high`;
28
+ - `needParallel` is `false`;
29
+ - `explicitAimlockRequested` is `false`.
30
+
31
+ When bypassed, do not start intake, scope contracts, snapshots, workers, keep-alive, or a full chain. Tell the user:
32
+
33
+ `需求较小且低风险,不建议使用 Aimlock;请直接处理,或只调用一个匹配的专项技能。`
34
+
35
+ The CLI `run` command derives applicability from real target paths, recent Git diff sizes, package boundaries, and the local import graph. Caller-supplied estimates cannot override this probe. Ambiguous work starts in the smaller mode and upgrades one level at a time while preserving its snapshot and completed changes. A bypass performs no skill HTTP call, creates no automatic evaluation, and writes no requirements file.
36
+
37
+ At most one specialist may be recommended. A normal small code change needs no skill. A calculator request may use Calctool alone; a typed approval may use Confirm Protocol alone.
38
+
39
+ Activate Aimlock when any condition is true: difficulty is medium/high, more than 500 lines are expected, the work crosses modules, risk is high, parallel work is required, or the user explicitly requests Aimlock for the change.
40
+
41
+ English: bypass small low-difficulty work and use at most one matching specialist.
42
+
43
+ Русский: небольшую простую задачу обходите без Aimlock; допускается не более одного профильного навыка.
15
44
 
16
45
  ## Request envelope
17
46
 
18
- POST JSON to the endpoint with an `input` wrapper:
47
+ POST JSON with an `input` wrapper:
19
48
 
20
49
  ```json
21
50
  {
22
51
  "input": {
23
- "schemaVersion": "aimlock.skill.request/1.0",
52
+ "schemaVersion": "aimlock.skill.request/1.1",
24
53
  "requestId": "<unique-id>",
25
54
  "operation": "<operation>",
26
55
  "input": {}
@@ -28,85 +57,71 @@ POST JSON to the endpoint with an `input` wrapper:
28
57
  }
29
58
  ```
30
59
 
31
- ## Operations
60
+ ## Active Aimlock flow
32
61
 
33
- - `capabilities`: modes, sibling skills, keep-alive text, first-use notice.
34
- - `help`: operation catalog.
35
- - `intake`: return required questions or validate a batch of `{id, answer}` values.
36
- - `classify`: choose `lock` | `probe` | `swarm` from explicit facts. Missing facts → `blocked`.
37
- - `scope-contract`: allowed paths, forbidden paths, max changed lines, new-file / delete flags.
38
- - `skill-route`: whether to call Blueprint, Swarm, Calctool.
39
- - `propose-nodes`: validate read-only modification nodes against the contract.
40
- - `accept-nodes`: auto-accept in-scope nodes; escalate worker conflicts.
41
- - `snapshot-plan`: file-copy snapshot. Git branches and worktrees are forbidden.
42
- - `snapshot-verify`: compare caller-computed SHA-256 hashes for every source/snapshot pair.
43
- - `mutate-gate`: mutate only after acceptance and successful snapshot verification.
44
- - `continuity-check`: traffic-light budget, structured TestEvidence, and omission scan.
45
- - `interrupt`: `status` | `fuse` | `spawn` | `stop`.
46
- - `keep-alive`: return the exact 90s protocol message; the caller owns the timer.
47
- - `run-status`: validate caller-supplied run state; without state the result is explicitly unknown.
48
- - `delivery-doc`: write a summary only if the user confirmed.
49
- - `validate-json`: validate an Aimlock run JSON.
50
- - `chain-plan`: generate the execution chain for this demand from `chain` + `risk` (Router).
51
- - `chain-status`: report the current step and completion of an active chain.
52
- - `registry-register`: register a skill hop (whenToCall / whenNotToCall / prerequisites / chainPosition) into the Router registry.
53
- - `feedback`: record a routing decision for rule-table refinement (fromSkill → toSkill + reason).
62
+ Only after the applicability gate activates Aimlock:
54
63
 
55
- ## Required flow
64
+ 1. Call local `capabilities`, then `probe`; use only its filesystem, Git-history, package-boundary, and import-graph facts for the initial mode. Exact `targetSymbols` may resolve through a fresh ContextBase map; missing, ambiguous, or stale map entries block.
65
+ 2. Call remote `capabilities`, `intake`, and `classify`. A fallback `bypass` response stops the Aimlock chain.
66
+ 3. Call `scope-contract`; empty allowed paths are blocked. Initialize the mode's local read budget before exploration.
67
+ 4. Route every source read through local `budget-read`. Exhaustion requires `execute`, `plan`, or `blocked`; only a low-risk Confirm Protocol receipt may extend it.
68
+ 5. Call `skill-route`. The server queries the current published official directory and injects only matched skills.
69
+ 6. For Probe or Swarm, workers inspect read-only and return modification nodes. Lock stays on the current agent.
70
+ 7. Call `propose-nodes`, `accept-nodes`, `snapshot-plan`, and `snapshot-verify` in order.
71
+ 8. Issue a local signed mutation pass after `mutate-gate` permits the verified snapshot. Route each batch write through local `guarded-write` with the same chainId and pass.
72
+ 9. If actual files or changed lines exceed the contract budget, call local `reassess`; upgrade only one level and preserve the current snapshot, changes, and evidence. Tell the user when this occurs.
73
+ 10. Call `continuity-check` with real TestEvidence. Yellow or red means restore the copied snapshot.
74
+ 11. Use `keep-alive` only while an active Aimlock goal is incomplete.
56
75
 
57
- 1. Call `capabilities`. On first use in the conversation, show `firstUseNotice` once.
58
- 2. Call `intake`; collect every **required** answer and submit them as a batch. Do not mutate files.
59
- 3. Call `classify` with the answers. Do not invent file lists or line budgets.
60
- 4. Call `scope-contract`. Empty `allowedPaths` is `blocked`.
61
- 5. Call `skill-route` with `mode`, `goalKind`, `hasBlueprint`, `hasArchitectureContract`, `newProject`, and `useRegistry: true`. Use the built-in Router before considering any user-named extension. Existing projects without a contract continue without blocking and receive a contract-create recommendation; new projects create the ArchGuard contract before Blueprint.
62
- 6. **Probe / Swarm:** workers read code only and return nodes. Call `propose-nodes` then `accept-nodes`.
63
- 7. **Lock:** the main agent still snapshots, then mutates inside the contract. No swarm.
64
- 8. Call `snapshot-plan`. Copy files into `snapshotRoot`. Never `git branch` / `git checkout -b` / worktree.
65
- 9. Compute SHA-256 for every original and copied file, then call `snapshot-verify`. A mismatch is `blocked`.
66
- 10. Call `mutate-gate` with `snapshotVerified: true`. If `blocked`, do not write.
67
- 11. After writes, call `continuity-check` with TestEvidence. Red or yellow → roll back from the snapshot.
68
- 12. Before yielding while the aim is open, call `keep-alive` with `goalComplete: false`; schedule and send its message in the caller.
69
- 13. Reclaim temporary agents after green. Ask about `delivery-doc` only if intake said the user wants it.
76
+ Never create a git branch or worktree. File-copy snapshots are the only isolation method.
70
77
 
71
- ### Classify rules (deterministic)
78
+ For an active demand, `Lock` covers one file through 500 estimated changed lines and `Probe` covers at most three files through 500 estimated changed lines. More than three target files, more than 500 lines, cross-module work, or required parallel work routes to `Swarm`; difficulty and risk still decide whether Aimlock activates at all.
72
79
 
73
- Facts required: `goal`, `targetFiles` (string array), `estimatedChangedLines`, `crossModule`, `needParallel`.
80
+ ## On-demand specialist routing
74
81
 
75
- - **lock**: exactly one file, 20 lines, not cross-module, not parallel.
76
- - **probe**: ≤ 3 files, ≤ 80 lines, not parallel.
77
- - **swarm**: otherwise.
82
+ Aimlock contains no static full-skill registry. `capabilities` does not preload the official catalog. `skill-route` is resolved by the CLI.Tax server from the current published directory, and the result contains matched skills only.
78
83
 
79
- ### Skill routing
84
+ Required routing facts include `mode`, `goalKind`, `risk`, `contractUnclear`, blueprint/architecture state, and explicit booleans for confirmation, calculator, merge, and final validation needs.
80
85
 
81
- Default allowlist is **official skills**. The normal path is `skill-route` with `useRegistry: true`; the caller does not need to echo a catalog. Passing a complete `officialCatalog` remains a compatibility path.
86
+ - Calctool: only for a calculator demand or explicit calculation requirement. It must not appear in a non-calculation result.
87
+ - Confirm Protocol: only when a structured user decision is required.
88
+ - ArchGuard: only for code/mixed work in a new project or under an existing architecture contract.
89
+ - Blueprint: only for active Probe/Swarm work when `contractUnclear=true` and no blueprint exists.
90
+ - Swarm: only for active Swarm mode.
91
+ - Validator: only for high-risk work or an explicit final-validation requirement.
92
+ - MergeGuard: only for an explicit verified-merge requirement.
93
+ - User-named extras are analysis candidates until their own `capabilities` prove a match.
82
94
 
83
- Call a hop only when `call` is true. That means the hop's capability matches this demand and the current chain allows it.
95
+ Caller-supplied full catalogs and local registry flags are forbidden. `serverResolvedSkills` is overwritten by the server; missing server resolution is blocked. Bypass routing returns no more than one recommendation and never constructs a chain.
84
96
 
85
- **Router mode (recommended):** pass `useRegistry: true` (or `registryVersion`) to `skill-route`; Aimlock answers hops from its built-in `SKILL_REGISTRY` (blueprint / swarm / calctool / mergeguard / validator), no catalog round-trip needed. For a full execution chain, call `chain-plan` with `chain` (`code-risky` | `calculator` | `page-new` | `merge` | `probe-only` | `chat`) and `risk`; it returns the ordered step list and the first `ready` step. High-risk demands must include `validator` — skipping it is blocked. Track progress with `chain-status` (chainId + steps + completed).
97
+ ## Operations
86
98
 
87
- - Do not call chain-unrelated skills.
88
- - Do not call self-extended or marketplace extras.
89
- - Extra skills enter the candidate list only when the user names them (`userSpecifiedSkills`). Then call that skill's `capabilities` and invoke only if its capability matches the demand.
90
- - New skills are added to the registry via `registry-register`; routing misses are reported via `feedback`.
99
+ - `capabilities`, `help`, `intake`, `classify`
100
+ - `scope-contract`, `skill-route`
101
+ - `propose-nodes`, `accept-nodes`
102
+ - `snapshot-plan`, `snapshot-verify`, `mutate-gate`
103
+ - `continuity-check`, `interrupt`, `keep-alive`
104
+ - `run-status`, `chain-plan`, `chain-status`
105
+ - `delivery-doc`, `validate-json`, `feedback`
91
106
 
92
- Do not call Calctool unless the aim is a calculator tool.
107
+ Trusted local operations: `capabilities`, `probe`, `reassess`, `budget-init`, `budget-read`, `budget-status`, `budget-extend`, `gate-issue`, `gate-verify`, and `guarded-write`. Invoke them as `cli-aimlock local <operation> <repositoryRoot>` with JSON stdin and call local `capabilities` first for every input Schema.
93
108
 
94
- ### Interrupt
109
+ `chain-plan` accepts only server-resolved skill IDs. High-risk work is blocked if Validator was not resolved. When `swarm` is present, it inserts the internal `coordinator.conflict-scan` step immediately before it; no unrelated external skill is added.
95
110
 
96
- Call `interrupt` with `forceStop`, `isStatusQuery`, `related` as booleans. Do not execute a new request first.
111
+ ## Interrupt and keep-alive
97
112
 
98
- - `stop`: user forced stop.
99
- - `status`: report only.
100
- - `fuse`: related; signal the running agent; update JSON; continue.
101
- - `spawn`: unrelated; new temporary agent; do not hijack the current aim.
113
+ Call `interrupt` before acting on an interruption:
102
114
 
103
- ### Keep-alive
115
+ - forced stop → `stop`;
116
+ - status query → `status`;
117
+ - related addition → `fuse`;
118
+ - unrelated request → `spawn`.
104
119
 
105
- When the aim is incomplete and the IDE is about to yield, send exactly:
120
+ For an active incomplete goal, the caller sends exactly every 90 seconds:
106
121
 
107
122
  `智能目标持续执行中,请勿关闭!`
108
123
 
109
- Interval: 90 seconds. Aimlock is stateless: `armed` remains false and `callerTimerRequired` tells the IDE whether it must schedule the message. Do not claim that the operation started a timer. When `goalComplete` is true, no timer is required.
124
+ Aimlock returns the protocol; it does not start a timer.
110
125
 
111
126
  ## 实现状态
112
127
 
@@ -114,42 +129,34 @@ Interval: 90 seconds. Aimlock is stateless: `armed` remains false and `callerTim
114
129
  |---|---|---|---|
115
130
  | A1 | 90 秒保活协议 | 已实现 | 返回固定消息和间隔;定时器由调用方负责,运行时不会自行推送。 |
116
131
  | A2 | 运行状态查询 | 已实现(无持久化) | 仅验证调用方传入的状态;未传状态时明确返回 `known: false`。 |
117
- | A3 | Router 统一路由 | 已实现 | 内置官方技能注册表;用户点名的扩展仍需单独能力确认。 |
118
- | A4 | 快照写入门禁 | 已实现 | 运行时比较调用方计算的 SHA-256;不读取文件系统,`mutate-gate` 强制要求验证结果。 |
119
-
120
- ## Safety rules
121
-
122
- - Never create a git branch. Isolation is a file-copy snapshot plus a temporary agent context.
123
- - Never mutate before `snapshot-verify` succeeds and `mutate-gate` returns `allowed: true`.
124
- - Never treat missing files, timeouts, or 4xx/5xx as empty success. `blocked` and `failed` are errors.
125
- - Never send credentials in the envelope.
126
- - The response `status` must be `succeeded`; `blocked` and `failed` are not results.
127
- - Do not expand 1 line into 100. Over-budget is red; roll back.
128
- - Delivery documents are optional. Skip unless the user confirmed.
129
-
130
- ## Examples
131
-
132
- ### Lock: one-line constant
133
-
134
- User: 只改税率常量一行.
135
-
136
- `classify` → `lock`. Snapshot that file. Change the one line. `continuity-check` must stay within `maxChangedLines`.
137
-
138
- ### Probe then mutate
139
-
140
- User: 修支付回调的状态机,可能有上下游.
141
-
142
- `classify` `probe`. Worker returns nodes. If a node points outside `allowedPaths`, `propose-nodes` is `blocked`. After accept + snapshot, mutate.
143
-
144
- ## 反馈通道
145
-
146
- 评价只能由 IDE / 智能体自动回传,严禁人类干预:
147
-
148
- 1. 仅当一次真实技能 HTTP 调用成功,并且响应顶层包含 `feedbackInvocationId` 时,调用方才可进入评价流程。
149
- 2. 调用方必须使用本次响应的原始 ID,按真实执行结果生成整数分数(0–100)和真实评语,再自动执行:
150
- `npx cli-aimlock@latest feedback <feedbackInvocationId> <score> <comment...>`
151
- 3. 自动回传必须使用会员中心 Brain 客户端生成的同一身份文件,并通过 `CLITAX_BRAIN_CLIENT_TOKEN_FILE` 指向该 `0600` 文件;服务端以会员账号实名入库,不接受匿名评价。
152
- 4. 禁止伪造、猜测、复用或跨调用转移 `feedbackInvocationId`;禁止让人类选择分数、填写评价或代为执行命令。
153
- 5. 缺少会员客户端身份或 ID、分数越界、空评语、响应不合法及任何 HTTP 失败都必须视为回传失败,不得记为成功。
154
-
155
- 日常交流走技能详情页的独立聊天通道,不使用 `feedback` 命令,也不计入评价、评分或首页跑马灯。
132
+ | A3 | 官方技能按需路由 | 已实现 | 服务端读取当前已发布官方目录,只注入与需求匹配的技能;不加载完整目录。 |
133
+ | A4 | 快照写入门禁 | 已实现(需宿主路由) | 本地运行器重读真实文件副本并签发 Ed25519 短期凭证;凭证绑定 chainId、快照摘要和路径。只有经过 `guarded-write` 的写入能被物理拦截,IDE 宿主必须关闭旁路批量写入口。 |
134
+ | A5 | 真实分档与逐级升级 | 已实现 | 本地读取真实路径、Git 历史、包边界和 import 图;可从新鲜 ContextBase 地图解析精确目标符号;调用方自报复杂度不能覆盖探测,升级继承现有证据。 |
135
+ | A6 | 读取预算与截止 | 已实现(需宿主路由) | Lock/Probe/Swarm 限制 3/10/30 文件与 2/8/15 分钟;Probe/Swarm 另限 30K/100K 估算 token,并用进程间锁阻止并发超额。 |
136
+ | A7 | AutoCoord 物理联锁 | 已实现(需宿主路由) | `gate-issue` 显式选择是否需要协调;协调凭证绑定 Swarm 签名文件租约,`guarded-write` 在同一临界区校验凭证、活动锁和路径范围。活动依赖等待会阻断预算读取。 |
137
+
138
+ ## Safety
139
+
140
+ - Never mutate before accepted nodes and verified file-copy snapshots.
141
+ - Never claim global write interception unless the IDE host routes every batch write through `guarded-write`; the package cannot intercept unrelated operating-system writes by itself.
142
+ - Never read source outside `budget-read` after a budget is initialized. Estimated tokens use the documented UTF-8-bytes/4 ceiling and are not an exact tokenizer count.
143
+ - Never issue a coordinated pass without a current signed `.coord` file lease. Never read while the same chain has an active `dependency-wait`.
144
+ - Never treat missing server routing, files, timeouts, or HTTP errors as empty success.
145
+ - Never send credentials in the request envelope.
146
+ - Aimlock never grants blanket disk, network, account, or operating-system access. The caller must request only the exact resource and action needed, prefer file/directory pickers and read-only or time-bounded grants, and revoke the grant after the task. If an integration only works with full access, stop and require a least-privilege adapter instead of bypassing the operating-system permission model.
147
+ - `blocked` and `failed` are not successful results.
148
+ - Delivery documents are optional and require explicit user confirmation.
149
+ - Do not expand a small request merely to justify Aimlock.
150
+
151
+ ## 受限调用与自动评价闭环
152
+
153
+ - IDE / 智能体必须通过本包 `invoke` 或 JSON-stdin `broker` 调用,不得直接拼装技能 HTTP 请求,也不得读取 BrainClient token。
154
+ - broker 从 `CLITAX_BRAIN_CLIENT_TOKEN_FILE` 读取身份;macOS/Linux 文件必须为当前 broker 账户所有且权限 `0600`,Windows 文件必须位于受限 `%LOCALAPPDATA%\CLI.Tax\broker` 目录。
155
+ - broker 只需要 Brain Client HTTPS、受限身份文件和调用方显式传入的路径,本身不需要完整磁盘访问。若要保证 IDE 无法读取身份文件,必须把 broker 放进独立低权限系统账户或沙箱服务,并只暴露受限 IPC;broker 与 IDE 同账户运行时,`0600` 不能隔离二者,禁止声称令牌已隔离。
156
+ - broker 只用 `Authorization: BrainClient …` 发起一次 runtime 请求。HTTP 成功后必须保留响应顶层原始 `feedbackReceiptId`、`feedbackInvocationId` 和 `feedbackEvaluation.digest`,不得生成、猜测、复用或跨调用转移。
157
+ - Brain Client 服务端必须严格绑定请求/响应的 `requestId` `schemaVersion`,再根据真实状态、验证结果、服务端耗时与 findings 生成并持久化权威评分、评语和摘要。broker 不得生成分数或评语。
158
+ - 同一次 runtime 请求在服务端事务内生成并持久化评价,再返回 `feedbackReceiptId`、`feedbackInvocationId` 和权威摘要;broker 只验证已提交回执,不发起第二次评价写入。`not-reported`、验证不完整、P0/P1 findings、`blocked` 或 `failed` 都不得生成好评。
159
+ - 缺少凭证或 ID、身份不匹配、摘要不匹配、响应非法以及任何 HTTP 失败都必须显式失败,不得静默、不重试成重复评价。
160
+ - 本地 CLI 不提供手工评分或评语提交命令,人类不得选择技能分数或填写技能评价;日常聊天不属于评价协议。
161
+
162
+ 调用示例:`npx cli-aimlock@latest invoke <operation> '<JSON对象>'`。IDE 集成可向 `npx cli-aimlock@latest broker` 的 stdin 发送 `{"operation":"capabilities","input":{}}`。
package/skill/skill.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
- "description": "Aimlock 把用户需求锁成可执行的智能目标,专门阻止思考漂移、执行漂移和范围膨胀。主智能体载入本技能后禁止立刻改代码,必须先把需求拆成 JSON 任务,并用范围合同分成 Lock、Probe、Swarm 三档。Lock 只覆盖单文件且变更预算明确的小改,主脑快照后改写并对账。Probe 先只读分析修改节点与上下游,确认一致后再写。Swarm 才调用蜂群派单认领。规划合同交给 Blueprint,计算工具交给 Calctool,执行编排交给 Swarm;Aimlock 只负责瞄准、闸门、插话融合与无分支快照。每次改前必须备份目标文件,禁止创建 git 分支,禁止用 worktree 冒充隔离。用户中途插入新需求时,主脑先判断是否与在跑任务关联:无关则另派临时智能体,有关则发信号更新 JSON 后继续;用户强制停止则立即停止。IDE 在目标未完成并即将交出控制权时,按九十秒间隔发送固定文案「智能目标持续执行中,请勿关闭!」。交付文档不是默认产物,必须由用户确认后才汇总。调用顺序为 capabilities、intake、classify、scope-contract:缺必填项不得进入下一操作。密钥与凭据不得写入公开页面或任务 JSON。本技能面向真实交付:每一步都有输入、规则与失败面,禁止把加载中、超时或未知状态当成空成功。用户可见说明只讲能力与对话配置方式,不出现外链。调用前必须先走 capabilities,再按 nextStep 前进;必填项未回答不得进入下一操作。日志只保存必要元数据,密钥不得写入公开页面。",
2
+ "description": "Aimlock 仅用于大型、深度、跨模块、高风险、需并行或用户明确要求的工程修改;低难度、预计不超过 500 行、非跨模块、非高风险、不并行且未明确要求 Aimlock 的需求应 bypass,直接处理或只调用一个匹配专项技能。Use Aimlock only for large, deep, cross-module, high-risk, parallel, or explicitly requested engineering changes; bypass low-difficulty work up to 500 lines only when it is not cross-module, high-risk, parallel, or explicitly assigned to Aimlock. Используйте Aimlock только для крупных, сложных, межмодульных, рискованных, параллельных или явно назначенных изменений; простую задачу до 500 строк обходите только без межмодульности, высокого риска, параллели и явного требования Aimlock.",
3
3
  "displayName": "Aimlock",
4
4
  "endpoint": "https://cli.tax/R3mQ8kWpXn",
5
5
  "method": "POST",
6
6
  "name": "aimlock",
7
- "schemaVersion": "aimlock.skill.request/1.0",
7
+ "schemaVersion": "aimlock.skill.request/1.1",
8
8
  "type": "Skill",
9
- "version": "v7.0.19"
9
+ "version": "v7.0.28"
10
10
  }