cli-archguard 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/cli.mjs CHANGED
@@ -2,6 +2,13 @@
2
2
  import { dirname } from 'node:path'
3
3
  import { fileURLToPath } from 'node:url'
4
4
  import { dispatchOfficialSkillCli, runIntakeHandshake } from './installer.mjs'
5
+ import {
6
+ checkpointFileAndRollback,
7
+ createBlockSnapshot,
8
+ initializeCheckpointLedger,
9
+ } from './archguard-local-runner.mjs'
10
+
11
+ const LOCAL_COMMANDS = new Set(['ledger-init', 'snapshot', 'checkpoint'])
5
12
 
6
13
  const INTAKE_QUESTIONS = [
7
14
  {
@@ -24,14 +31,72 @@ const INTAKE_QUESTIONS = [
24
31
  },
25
32
  ]
26
33
 
27
- await dispatchOfficialSkillCli({
28
- packageRoot: dirname(fileURLToPath(import.meta.url)),
29
- runCommand: (context) => runIntakeHandshake(context, {
30
- questions: INTAKE_QUESTIONS,
31
- outputFile: 'ARCHGUARD-REQUIREMENTS.json',
32
- afterCapabilities(output) {
33
- const instruction = output.nextStep?.instruction
34
- if (typeof instruction === 'string' && instruction.trim()) console.log(instruction)
35
- },
36
- }),
37
- })
34
+ function requiredArgument(args, index, name) {
35
+ const value = args[index]?.trim()
36
+ if (!value) throw new Error(`${name} is required`)
37
+ return value
38
+ }
39
+
40
+ async function runLocalCommand(command, args) {
41
+ if (command === 'ledger-init') {
42
+ return initializeCheckpointLedger({
43
+ repositoryRoot: requiredArgument(args, 0, 'repositoryRoot'),
44
+ ledgerPath: requiredArgument(args, 1, 'ledgerPath'),
45
+ })
46
+ }
47
+ if (command === 'snapshot') {
48
+ return createBlockSnapshot({
49
+ repositoryRoot: requiredArgument(args, 0, 'repositoryRoot'),
50
+ targetPath: requiredArgument(args, 1, 'targetPath'),
51
+ snapshotPath: requiredArgument(args, 2, 'snapshotPath'),
52
+ })
53
+ }
54
+ return checkpointFileAndRollback({
55
+ repositoryRoot: requiredArgument(args, 0, 'repositoryRoot'),
56
+ contractPath: requiredArgument(args, 1, 'contractPath'),
57
+ targetPath: requiredArgument(args, 2, 'targetPath'),
58
+ snapshotPath: requiredArgument(args, 3, 'snapshotPath'),
59
+ ledgerPath: requiredArgument(args, 4, 'ledgerPath'),
60
+ blockId: requiredArgument(args, 5, 'blockId'),
61
+ chainId: requiredArgument(args, 6, 'chainId'),
62
+ gatePassPath: requiredArgument(args, 7, 'gatePassPath'),
63
+ })
64
+ }
65
+
66
+ function localCommandExitCode(command, result) {
67
+ if (command !== 'checkpoint') return 0
68
+ return result?.status === 'succeeded'
69
+ && result?.output?.checkpoint?.writeAllowed === true ? 0 : 2
70
+ }
71
+
72
+ const command = process.argv[2] ?? 'help'
73
+ if (LOCAL_COMMANDS.has(command)) {
74
+ try {
75
+ const result = await runLocalCommand(command, process.argv.slice(3))
76
+ console.log(JSON.stringify(result))
77
+ process.exitCode = localCommandExitCode(command, result)
78
+ } catch (error) {
79
+ console.error(error instanceof Error ? error.message : error)
80
+ process.exitCode = 1
81
+ }
82
+ } else {
83
+ await dispatchOfficialSkillCli({
84
+ packageRoot: dirname(fileURLToPath(import.meta.url)),
85
+ extraUsageLines: [
86
+ 'Local trusted execution:',
87
+ ' cli-archguard ledger-init <repositoryRoot> <ledgerPath>',
88
+ ' cli-archguard snapshot <repositoryRoot> <targetPath> <snapshotPath>',
89
+ ' cli-archguard checkpoint <repositoryRoot> <contractPath> <targetPath> <snapshotPath> <ledgerPath> <blockId> <chainId> <gatePassPath>',
90
+ ],
91
+ runCommand: (context) => runIntakeHandshake(context, {
92
+ questions: INTAKE_QUESTIONS,
93
+ outputFile: 'ARCHGUARD-REQUIREMENTS.json',
94
+ afterCapabilities(output) {
95
+ const instruction = output.nextStep?.instruction
96
+ if (typeof instruction === 'string' && instruction.trim()) console.log(instruction)
97
+ },
98
+ }),
99
+ })
100
+ }
101
+
102
+ export { localCommandExitCode }
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,22 @@
2
2
  "bin": {
3
3
  "cli-archguard": "./cli.mjs"
4
4
  },
5
+ "dependencies": {
6
+ "@babel/parser": "^8.0.4",
7
+ "cli-aimlock": "7.0.28",
8
+ "yaml": "^2.8.1"
9
+ },
5
10
  "description": "ArchGuard skill installer for CLI.Tax: architecture contracts, chunk checkpoints, drift control, and auditable rollback.",
6
11
  "files": [
7
12
  "cli.mjs",
8
13
  "installer.mjs",
14
+ "broker.mjs",
9
15
  "README.md",
10
16
  "skill/SKILL.md",
11
- "skill/skill.json"
17
+ "skill/skill.json",
18
+ "archguard-contextbase-hook.mjs",
19
+ "archguard-runtime.mjs",
20
+ "archguard-local-runner.mjs"
12
21
  ],
13
22
  "license": "UNLICENSED",
14
23
  "name": "cli-archguard",
@@ -17,5 +26,5 @@
17
26
  "url": "https://github.com/88208555/archguard-clitax.git"
18
27
  },
19
28
  "type": "module",
20
- "version": "7.0.19"
29
+ "version": "7.0.28"
21
30
  }
package/skill/SKILL.md CHANGED
@@ -1,52 +1,84 @@
1
1
  ---
2
2
  name: archguard
3
- description: Lock a project's architecture contract and check each code block for stack drift, rule violations, and complexity overages while code is being written. Use for multi-file code changes when arch.contract.yaml exists or the user asks to lock the stack. Do not use for read-only analysis, pure documents, calculator generation, or one-line low-risk edits.
3
+ description: '在代码写入期间锁定项目架构合同,并逐块检查技术栈漂移、规则违规与复杂度超限;适用于已有 arch.contract.yaml 或用户明确要求锁定技术栈的多文件修改,不用于只读分析、纯文档、计算工具或单行低风险修改。Lock the architecture contract and check each written code block for stack drift, rule violations, and complexity overages; use for multi-file changes with arch.contract.yaml or an explicit stack-lock request, not read-only analysis, documents, calculators, or one-line low-risk edits. Фиксирует архитектурный контракт и проверяет каждый записанный блок кода на дрейф стека, нарушения и превышение сложности; применяется к многофайловым изменениям с arch.contract.yaml или явным запросом фиксации стека, но не к чтению, документам, калькуляторам и однострочным низкорисковым правкам.'
4
4
  ---
5
5
 
6
- # ArchGuard
6
+ # 架构守卫 / ArchGuard / Архитектурный страж
7
7
 
8
- Package version: v7.0.19
8
+ Package version: v7.0.28
9
9
 
10
10
  Endpoint: https://cli.tax/Ag4Ch8Rd2K
11
11
  Request schema: `archguard.skill.request/1.0`
12
12
 
13
- ## Boundary
13
+ ## 三语能力摘要 / Trilingual capability summary / Трёхъязычное описание
14
+
15
+ 中文:架构守卫只在真实代码写入期间工作。已有架构合同或用户明确要求锁定技术栈的多文件修改才启用;只读分析、纯文档、计算工具和无架构风险的单行修改不启用。每个代码块都必须用同一合同摘要、快照和只追加台账完成检查点;阻断结果必须回滚该块,不能伪报通过。
16
+
17
+ English: ArchGuard runs only while real code is being written. Activate it for multi-file work under an architecture contract or an explicit stack-lock request; skip read-only analysis, documents, calculator generation, and one-line edits without architecture risk. Every block uses the same contract digest, snapshot, and append-only ledger; a blocked result requires rollback and can never be reported as passed.
18
+
19
+ Русский: ArchGuard работает только при реальной записи кода. Он включается для многофайловых изменений по архитектурному контракту или при явном запросе фиксации стека; чтение, документы, генерация калькулятора и однострочная правка без архитектурного риска его не запускают. Каждый блок проверяется с тем же дайджестом контракта, снимком и дописываемым журналом; блокировка требует отката и не может считаться прохождением.
20
+
21
+ ## 边界 / Boundary / Границы
14
22
 
15
23
  ArchGuard supervises code during execution. It does not plan product scope, dispatch agents, merge branches, replace final validation, or implement any model-thinking sandbox.
16
24
 
17
25
  Use it when a task writes multiple code blocks and either `arch.contract.yaml` exists or the user explicitly asks to lock the technical stack. Skip it for read-only analysis, pure documentation, Calctool generation, merge-only work, and a one-line Lock edit without architecture risk.
18
26
 
19
- ## Required sequence
27
+ ## 强制流程 / Required sequence / Обязательная последовательность
20
28
 
21
29
  1. Call `capabilities` first and use the returned `operationSchemas`.
22
30
  2. If the project has no contract and this is a new project, call `template-list`, `intake`, then `contract-create` before Blueprint.
23
31
  3. Before changing an existing contract, call `contract-get`; `contract-update` requires its exact digest, explicit confirmation, actor, and reason.
24
- 4. Before each code-block write, create a block snapshot. After the block is complete, call `checkpoint` with the contract, block, and checkpoint history.
32
+ 4. Before each code-block write, run `cli-archguard snapshot ...`. The write itself must carry the Aimlock chainId and signed gate pass. After the block is complete, run `cli-archguard checkpoint ...` with the same snapshot, contract, append-only ledger, chainId, and gate pass.
25
33
  5. A blocking finding means the block cannot remain: restore that block's snapshot, apply the returned correction, then recheck.
26
34
  6. Three consecutive blocks with the same blocking rule are a red-light interrupt. Stop mutation and request human direction.
27
35
  7. At task end call `rules-scan`, `complexity-report`, and `drift-status`; pass the ledger to Validator as process evidence.
28
36
 
29
- ## Contract authority
37
+ ## 合同权威 / Contract authority / Полномочия контракта
30
38
 
31
39
  - The canonical contract is `arch.contract.yaml` with schema `archguard.contract/1.0`.
32
40
  - A contract locks stack, allowed/forbidden dependencies, rules, and budgets.
41
+ - Forbidden dependencies are checked in source imports and in `package.json`, `requirements*.txt`, `pom.xml`, and `Cargo.toml`.
33
42
  - Never change the contract to make failing code pass. Only an explicitly confirmed versioned `contract-update` may change it.
34
43
  - Aimlock owns the contract-file snapshot and mutation scope. ArchGuard owns block checkpoints and architecture findings.
35
44
 
36
- ## Checkpoint decisions
45
+ ## 检查点裁决 / Checkpoint decisions / Решения контрольной точки
37
46
 
38
47
  - `passed`: the block may remain.
39
48
  - `blocked`: restore the block snapshot when `rollbackRequired` is true; do not report the worker step green.
40
49
  - Standard-level findings are report-only by default. Architecture and complexity findings block. A contract may explicitly make standard rules blocking.
41
50
  - The checkpoint ledger is append-only evidence. Do not fabricate ledger entries or success states.
51
+ - Checkpoint history is mandatory. Red drift requires three consecutive blocked checkpoints sharing the same blocking rule; unrelated or non-consecutive findings never trigger it.
52
+
53
+ ## 可信本地执行 / Trusted local execution / Доверенное локальное выполнение
54
+
55
+ Remote JSON `checkpoint` never accepts caller-supplied `astFindings`. When the contract contains AST rules, a remote checkpoint is blocked with `ARCH-AST-LOCAL-RUNNER-REQUIRED`.
56
+
57
+ Use the bundled CLI in the repository being checked:
58
+
59
+ ```bash
60
+ cli-archguard ledger-init . .archguard/ledger.json
61
+ cli-archguard snapshot . src/example.ts .archguard/example.snapshot.json
62
+ cli-archguard checkpoint . arch.contract.yaml src/example.ts .archguard/example.snapshot.json .archguard/ledger.json block-1 <chainId> <gatePassPath>
63
+ ```
42
64
 
43
- ## Operations
65
+ 中文:本地运行器把每个快照和台账绑定到真实仓库根目录,只接受 `.archguard/` 下不含符号链接或目录联接的相对路径;绝对路径、父级穿越和覆盖非受管文件均被拒绝。
66
+
67
+ English: The local runner binds every snapshot and ledger to the real repository root and accepts only relative `.archguard/` paths without symlinks or junctions. It refuses absolute or parent-traversing paths and never overwrites an unmanaged file.
68
+
69
+ Русский: Локальный исполнитель привязывает каждый снимок и журнал к реальному корню репозитория и принимает только относительные пути в `.archguard/` без символических ссылок и соединений каталогов. Абсолютные пути, выход к родителю и перезапись неуправляемых файлов отклоняются.
70
+
71
+ The runner verifies the Aimlock Ed25519 pass against the real repository before evaluating a block. A missing, forged, expired, wrong-chain, or out-of-scope pass rejects the block and restores its snapshot. It then reads the file and contract without following symlinks, computes AST findings in-process, restores a blocked snapshot, appends its ledger entry, and emits a ContextBase invalidation event when that derived cache is initialized. Missing local evidence is blocked, never treated as an empty AST result.
72
+
73
+ The local CLI exits with code `0` only when a checkpoint permits the write. A blocked checkpoint that has been rolled back exits with code `2`; an execution error exits with code `1`. Automation must read the JSON evidence and the exit code.
74
+
75
+ ## 操作 / Operations / Операции
44
76
 
45
77
  `capabilities`, `help`, `intake`, `contract-create`, `contract-get`, `contract-update`, `template-list`, `checkpoint`, `rules-scan`, `complexity-report`, `drift-status`, `ledger-query`.
46
78
 
47
79
  Built-in templates: React + TypeScript + Vite, Vue 3 + TypeScript + Vite, Next.js, NestJS, FastAPI, Spring Boot, WeChat Mini Program, and Tauri desktop.
48
80
 
49
- ## Chain routing
81
+ ## 链路路由 / Chain routing / Маршрутизация цепочки
50
82
 
51
83
  - Existing code with contract: Aimlock → Blueprint → Swarm → ArchGuard checkpoints → MergeGuard when a merge is requested → Validator.
52
84
  - Existing code without contract: Aimlock → Blueprint → Swarm → Validator; recommend contract creation without blocking the current task.
@@ -54,12 +86,15 @@ Built-in templates: React + TypeScript + Vite, Vue 3 + TypeScript + Vite, Next.j
54
86
 
55
87
  Before handing off, preserve the exact contract digest, checkpoint ledger, full findings, rollback outcome, and drift light. Missing evidence is not success.
56
88
 
57
- ## Automated feedback
58
-
59
- Only after a real ArchGuard HTTP call succeeds and returns `feedbackInvocationId`, the IDE may automatically submit an honest score and comment with:
60
-
61
- `npx cli-archguard@latest feedback <feedbackInvocationId> <score> <comment...>`
89
+ ## 受限调用与自动评价闭环
62
90
 
63
- The command requires the member IDE client token file through `CLITAX_BRAIN_CLIENT_TOKEN_FILE`. Humans must not choose or submit ratings.
91
+ - IDE / 智能体必须通过本包 `invoke` JSON-stdin `broker` 调用,不得直接拼装技能 HTTP 请求,也不得读取 BrainClient token。
92
+ - broker 从 `CLITAX_BRAIN_CLIENT_TOKEN_FILE` 读取身份;macOS/Linux 文件必须为当前 broker 账户所有且权限 `0600`,Windows 文件必须位于受限 `%LOCALAPPDATA%\CLI.Tax\broker` 目录。
93
+ - broker 只需要 Brain Client HTTPS、受限身份文件和调用方显式传入的路径,本身不需要完整磁盘访问。若要保证 IDE 无法读取身份文件,必须把 broker 放进独立低权限系统账户或沙箱服务,并只暴露受限 IPC;broker 与 IDE 同账户运行时,`0600` 不能隔离二者,禁止声称令牌已隔离。
94
+ - broker 只用 `Authorization: BrainClient …` 发起一次 runtime 请求。HTTP 成功后必须保留响应顶层原始 `feedbackReceiptId`、`feedbackInvocationId` 和 `feedbackEvaluation.digest`,不得生成、猜测、复用或跨调用转移。
95
+ - Brain Client 服务端必须严格绑定请求/响应的 `requestId` 和 `schemaVersion`,再根据真实状态、验证结果、服务端耗时与 findings 生成并持久化权威评分、评语和摘要。broker 不得生成分数或评语。
96
+ - 同一次 runtime 请求在服务端事务内生成并持久化评价,再返回 `feedbackReceiptId`、`feedbackInvocationId` 和权威摘要;broker 只验证已提交回执,不发起第二次评价写入。`not-reported`、验证不完整、P0/P1 findings、`blocked` 或 `failed` 都不得生成好评。
97
+ - 缺少凭证或 ID、身份不匹配、摘要不匹配、响应非法以及任何 HTTP 失败都必须显式失败,不得静默、不重试成重复评价。
98
+ - 本地 CLI 不提供手工评分或评语提交命令,人类不得选择技能分数或填写技能评价;日常聊天不属于评价协议。
64
99
 
65
- 只有真实响应顶层包含 `feedbackInvocationId` 时才允许回传;会员账号实名入库,禁止伪造、猜测、复用调用编号,禁止让人类选择分数,任何 HTTP 失败都必须视为回传失败。
100
+ 调用示例:`npx cli-archguard@latest invoke <operation> '<JSON对象>'`。IDE 集成可向 `npx cli-archguard@latest broker` stdin 发送 `{"operation":"capabilities","input":{}}`。
package/skill/skill.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
- "description": "ArchGuard 是技能链的执行中架构守卫。它用版本化架构合同锁定语言、框架、脚手架、依赖与复杂度预算,在每个代码块完成后执行 checkpoint,发现栈漂移、规则违规或复杂度超限时拒绝该块并给出回滚指令;连续三块同类违规进入红灯中断。支持 8 个栈模板、全操作 JSON Schema、规则扫描、复杂度报告、漂移状态和审计台账。仅用于代码写入过程;纯分析、纯文档、计算工具和无代码落盘任务不调用。",
2
+ "description": "架构守卫在代码写入期间用版本化合同锁定技术栈、依赖与复杂度预算,逐块检查并记录可审计回滚;仅用于有合同或明确锁栈要求的多文件修改。ArchGuard locks stack, dependencies, and complexity budgets with a versioned contract, checks every written block, and records auditable rollback; use only for multi-file work with a contract or explicit stack-lock request. Архитектурный страж фиксирует стек, зависимости и бюджеты сложности версионированным контрактом, проверяет каждый записанный блок и ведёт аудит отката; применяется только к многофайловой работе с контрактом или явной фиксацией стека.",
3
3
  "displayName": "ArchGuard",
4
4
  "endpoint": "https://cli.tax/Ag4Ch8Rd2K",
5
5
  "method": "POST",
6
6
  "name": "archguard",
7
7
  "type": "Skill",
8
- "version": "v7.0.19",
8
+ "version": "v7.0.28",
9
9
  "schemaVersion": "archguard.skill.request/1.0"
10
10
  }