cli-archguard 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 ADDED
@@ -0,0 +1,9 @@
1
+ # cli-archguard
2
+
3
+ Official ArchGuard installer for CLI.Tax.
4
+
5
+ ```bash
6
+ npx cli-archguard@latest install
7
+ ```
8
+
9
+ The installed skill creates and validates architecture contracts, checks each code block, reports drift, and records auditable checkpoint evidence. Runtime and release source: https://gitee.com/Alyr_space/CLITax.git
package/cli.mjs ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+ import { dirname } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { dispatchOfficialSkillCli, runIntakeHandshake } from './installer.mjs'
5
+
6
+ const INTAKE_QUESTIONS = [
7
+ {
8
+ id: 'project',
9
+ prompt: 'Which project must ArchGuard protect?',
10
+ required: true,
11
+ example: 'CLI.Tax web and server workspace',
12
+ },
13
+ {
14
+ id: 'templateId',
15
+ prompt: 'Which stack template matches this project?',
16
+ required: true,
17
+ example: 'react-ts-vite',
18
+ },
19
+ {
20
+ id: 'strictStandard',
21
+ prompt: 'Should standard-level findings block writes? yes or no.',
22
+ required: true,
23
+ example: 'no',
24
+ },
25
+ ]
26
+
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
+ })
package/installer.mjs ADDED
@@ -0,0 +1,358 @@
1
+ /**
2
+ * 八个官方技能共用这一份安装器。packages/*-cli/installer.mjs 必须与本文件字节一致。
3
+ * 禁止第二套超时、第二套版本来源、第二套 bin 名。
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'
8
+ import { dirname, join, resolve } from 'node:path'
9
+ import { stdin, stdout } from 'node:process'
10
+ import { createInterface } from 'node:readline/promises'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ export const LOOKUP_TIMEOUT_MS = 8000
14
+ export const CALL_TIMEOUT_MS = 120_000
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})$/
28
+
29
+ function asObject(value, label) {
30
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
31
+ throw new Error(`${label} must be an object`)
32
+ }
33
+ return value
34
+ }
35
+
36
+ function requiredString(value, label) {
37
+ const text = typeof value === 'string' ? value.trim() : ''
38
+ if (!text) throw new Error(`${label} is required`)
39
+ return text
40
+ }
41
+
42
+ export function loadOfficialSkillContext(packageRoot) {
43
+ const pkg = asObject(JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8')), 'package.json')
44
+ const skill = asObject(JSON.parse(readFileSync(join(packageRoot, 'skill/skill.json'), 'utf8')), 'skill.json')
45
+ const npmName = requiredString(pkg.name, 'package.json name')
46
+ const packageVersion = requiredString(pkg.version, 'package.json version')
47
+ const displayName = requiredString(skill.displayName, 'skill.json displayName')
48
+ const skillName = requiredString(skill.name, 'skill.json name')
49
+ const schemaVersion = requiredString(skill.schemaVersion, 'skill.json schemaVersion')
50
+ const endpoint = requiredString(skill.endpoint, 'skill.json endpoint')
51
+ const skillVersion = requiredString(skill.version, 'skill.json version')
52
+ const runtimeCode = requiredString(endpoint.replace(/^https:\/\/cli\.tax\//, ''), 'runtime code')
53
+ if (!/^[A-Za-z0-9]{10}$/.test(runtimeCode)) {
54
+ throw new Error(`skill.json endpoint must be https://cli.tax/{10-char-code}: ${endpoint}`)
55
+ }
56
+ if (skillVersion.replace(/^v/i, '') !== packageVersion.replace(/^v/i, '')) {
57
+ throw new Error(`skill.json ${skillVersion} must match package.json ${packageVersion}`)
58
+ }
59
+ return {
60
+ packageRoot,
61
+ npmName,
62
+ packageVersion,
63
+ displayName,
64
+ skillName,
65
+ schemaVersion,
66
+ endpoint,
67
+ skillVersion,
68
+ runtimeCode,
69
+ latestEndpoint: `https://cli.tax/api/public/skills/${runtimeCode}`,
70
+ skillDir: join(packageRoot, 'skill'),
71
+ }
72
+ }
73
+
74
+ export function readInstallMeta(target) {
75
+ const path = join(target, INSTALL_META)
76
+ if (!existsSync(path)) return null
77
+ return asObject(JSON.parse(readFileSync(path, 'utf8')), INSTALL_META)
78
+ }
79
+
80
+ export function installTarget(skillName, explicit) {
81
+ if (explicit) return resolve(explicit)
82
+ const codexHome = process.env.CODEX_HOME?.trim()
83
+ if (codexHome) return join(codexHome, 'skills', skillName)
84
+ return join(process.cwd(), '.codex', 'skills', skillName)
85
+ }
86
+
87
+ export async function fetchLatestVersion(context) {
88
+ const response = await fetch(context.latestEndpoint, { signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) })
89
+ if (!response.ok) throw new Error(`cli.tax skill lookup failed: HTTP ${response.status}`)
90
+ const data = asObject(await response.json(), 'cli.tax skill lookup')
91
+ return {
92
+ version: requiredString(data.version, 'cli.tax skill lookup version'),
93
+ displayName: requiredString(data.displayName, 'cli.tax skill lookup displayName'),
94
+ }
95
+ }
96
+
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
+ export async function installOfficialSkill(context, explicit) {
230
+ const target = installTarget(context.skillName, explicit)
231
+ await mkdir(target, { recursive: true })
232
+ const previous = readInstallMeta(target)
233
+ await rm(join(target, 'references'), { recursive: true, force: true })
234
+ await cp(context.skillDir, target, { recursive: true, force: true })
235
+ const installed = asObject(JSON.parse(readFileSync(join(target, 'skill.json'), 'utf8')), 'installed skill.json')
236
+ const installedVersion = requiredString(installed.version, 'installed skill.json version')
237
+ await writeFile(join(target, INSTALL_META), `${JSON.stringify({
238
+ source: context.runtimeCode,
239
+ slug: context.skillName,
240
+ version: installedVersion,
241
+ packageVersion: context.packageVersion,
242
+ endpoint: context.endpoint,
243
+ installedAt: new Date().toISOString(),
244
+ }, null, 2)}\n`)
245
+ if (previous?.version && previous.version !== installedVersion) {
246
+ console.log(`${context.displayName} skill updated: ${target}`)
247
+ console.log(` ${previous.version} → ${installedVersion}`)
248
+ } else {
249
+ console.log(`${context.displayName} skill installed: ${target} (${installedVersion})`)
250
+ }
251
+ console.log('Next: return to your IDE and state the goal. The agent reads the installed SKILL.md.')
252
+ }
253
+
254
+ export async function checkOfficialSkill(context, explicit) {
255
+ const target = installTarget(context.skillName, explicit)
256
+ const current = readInstallMeta(target)
257
+ if (!current) {
258
+ console.log(`${context.displayName} skill is not installed. Run: npx ${context.npmName}@latest install`)
259
+ process.exitCode = 1
260
+ return
261
+ }
262
+ const installedVersion = requiredString(current.version, 'install-meta.json version')
263
+ const packageVersion = requiredString(current.packageVersion, 'install-meta.json packageVersion')
264
+ console.log(`Installed: ${installedVersion} (package ${packageVersion})`)
265
+ const latest = await fetchLatestVersion(context)
266
+ console.log(`Latest on cli.tax: ${latest.version}`)
267
+ if (installedVersion === latest.version) {
268
+ console.log('Up to date.')
269
+ return
270
+ }
271
+ console.log(`Update available: ${installedVersion} → ${latest.version}`)
272
+ console.log(`Run: npx ${context.npmName}@latest install`)
273
+ process.exitCode = 1
274
+ }
275
+
276
+ export function defaultUsage(context, extraLines) {
277
+ const lines = [
278
+ `${context.npmName} — install and run the ${context.displayName} skill from CLI.Tax`,
279
+ '',
280
+ 'Usage:',
281
+ ` npx ${context.npmName}@latest install [directory]`,
282
+ ` Install the ${context.displayName} skill for the current IDE.`,
283
+ ` npx ${context.npmName}@latest check [directory]`,
284
+ ' Check whether the installed skill has a newer version.',
285
+ ` npx ${context.npmName}@latest run`,
286
+ ' Run the skill handshake: discover capabilities and collect intake answers.',
287
+ `Endpoint: ${context.endpoint}`,
288
+ ]
289
+ if (extraLines?.length) lines.push('', ...extraLines)
290
+ return lines.join('\n')
291
+ }
292
+
293
+ export async function runIntakeHandshake(context, spec) {
294
+ const capabilities = await callOfficialSkill(context, 'capabilities', {})
295
+ const output = capabilities.output && typeof capabilities.output === 'object' ? capabilities.output : {}
296
+ const skill = output.skill && typeof output.skill === 'object' ? output.skill : {}
297
+ const version = typeof skill.version === 'string' && skill.version.trim()
298
+ ? skill.version.trim()
299
+ : context.skillVersion
300
+ console.log(`${context.displayName} ${version}`)
301
+ if (typeof spec.afterCapabilities === 'function') spec.afterCapabilities(output)
302
+ const readline = createInterface({ input: stdin, output: stdout })
303
+ const answers = []
304
+ try {
305
+ for (const question of spec.questions) {
306
+ const requiredMark = question.required ? ' (required)' : ''
307
+ console.log(`\n${question.prompt}${requiredMark}`)
308
+ console.log(`Example: ${question.example}`)
309
+ for (;;) {
310
+ const answer = (await readline.question('> ')).trim()
311
+ if (answer) {
312
+ answers.push({ id: question.id, prompt: question.prompt, answer })
313
+ break
314
+ }
315
+ if (!question.required) break
316
+ console.log('This question is required. Please answer before continuing.')
317
+ }
318
+ }
319
+ } finally {
320
+ readline.close()
321
+ }
322
+ const target = join(process.cwd(), spec.outputFile)
323
+ await writeFile(target, `${JSON.stringify({
324
+ schemaVersion: context.schemaVersion,
325
+ endpoint: context.endpoint,
326
+ createdAt: new Date().toISOString(),
327
+ answers,
328
+ }, null, 2)}\n`)
329
+ console.log(`\nRequirements saved: ${target}`)
330
+ console.log('Next: continue in your IDE agent with this file.')
331
+ }
332
+
333
+ export async function dispatchOfficialSkillCli(options) {
334
+ const packageRoot = options.packageRoot ?? dirname(fileURLToPath(options.importMetaUrl))
335
+ const context = loadOfficialSkillContext(packageRoot)
336
+ const args = process.argv.slice(2)
337
+ const command = args[0] ?? 'help'
338
+ const argument = args[1]
339
+ try {
340
+ if (command === 'install') await installOfficialSkill(context, argument)
341
+ else if (command === 'check') await checkOfficialSkill(context, argument)
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
+ }
347
+ else if (command === 'help' || command === '--help' || command === '-h') {
348
+ console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
349
+ } else {
350
+ console.error(`Unknown command: ${command}`)
351
+ console.log(options.usage ? options.usage(context) : defaultUsage(context, options.extraUsageLines))
352
+ process.exitCode = 1
353
+ }
354
+ } catch (error) {
355
+ console.error(error instanceof Error ? error.message : error)
356
+ process.exitCode = 1
357
+ }
358
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "bin": {
3
+ "cli-archguard": "./cli.mjs"
4
+ },
5
+ "description": "ArchGuard skill installer for CLI.Tax: architecture contracts, chunk checkpoints, drift control, and auditable rollback.",
6
+ "files": [
7
+ "cli.mjs",
8
+ "installer.mjs",
9
+ "README.md",
10
+ "skill/SKILL.md",
11
+ "skill/skill.json"
12
+ ],
13
+ "license": "UNLICENSED",
14
+ "name": "cli-archguard",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://gitee.com/Alyr_space/CLITax.git"
18
+ },
19
+ "type": "module",
20
+ "version": "7.0.18"
21
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,65 @@
1
+ ---
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.
4
+ ---
5
+
6
+ # ArchGuard
7
+
8
+ Package version: v7.0.18
9
+
10
+ Endpoint: https://cli.tax/Ag4Ch8Rd2K
11
+ Request schema: `archguard.skill.request/1.0`
12
+
13
+ ## Boundary
14
+
15
+ 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
+
17
+ 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
+
19
+ ## Required sequence
20
+
21
+ 1. Call `capabilities` first and use the returned `operationSchemas`.
22
+ 2. If the project has no contract and this is a new project, call `template-list`, `intake`, then `contract-create` before Blueprint.
23
+ 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.
25
+ 5. A blocking finding means the block cannot remain: restore that block's snapshot, apply the returned correction, then recheck.
26
+ 6. Three consecutive blocks with the same blocking rule are a red-light interrupt. Stop mutation and request human direction.
27
+ 7. At task end call `rules-scan`, `complexity-report`, and `drift-status`; pass the ledger to Validator as process evidence.
28
+
29
+ ## Contract authority
30
+
31
+ - The canonical contract is `arch.contract.yaml` with schema `archguard.contract/1.0`.
32
+ - A contract locks stack, allowed/forbidden dependencies, rules, and budgets.
33
+ - Never change the contract to make failing code pass. Only an explicitly confirmed versioned `contract-update` may change it.
34
+ - Aimlock owns the contract-file snapshot and mutation scope. ArchGuard owns block checkpoints and architecture findings.
35
+
36
+ ## Checkpoint decisions
37
+
38
+ - `passed`: the block may remain.
39
+ - `blocked`: restore the block snapshot when `rollbackRequired` is true; do not report the worker step green.
40
+ - Standard-level findings are report-only by default. Architecture and complexity findings block. A contract may explicitly make standard rules blocking.
41
+ - The checkpoint ledger is append-only evidence. Do not fabricate ledger entries or success states.
42
+
43
+ ## Operations
44
+
45
+ `capabilities`, `help`, `intake`, `contract-create`, `contract-get`, `contract-update`, `template-list`, `checkpoint`, `rules-scan`, `complexity-report`, `drift-status`, `ledger-query`.
46
+
47
+ Built-in templates: React + TypeScript + Vite, Vue 3 + TypeScript + Vite, Next.js, NestJS, FastAPI, Spring Boot, WeChat Mini Program, and Tauri desktop.
48
+
49
+ ## Chain routing
50
+
51
+ - Existing code with contract: Aimlock → Blueprint → Swarm → ArchGuard checkpoints → MergeGuard when a merge is requested → Validator.
52
+ - Existing code without contract: Aimlock → Blueprint → Swarm → Validator; recommend contract creation without blocking the current task.
53
+ - New project: Aimlock → ArchGuard contract-create → Blueprint → Swarm → ArchGuard checkpoints → Validator.
54
+
55
+ Before handing off, preserve the exact contract digest, checkpoint ledger, full findings, rollback outcome, and drift light. Missing evidence is not success.
56
+
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...>`
62
+
63
+ The command requires the member IDE client token file through `CLITAX_BRAIN_CLIENT_TOKEN_FILE`. Humans must not choose or submit ratings.
64
+
65
+ 只有真实响应顶层包含 `feedbackInvocationId` 时才允许回传;会员账号实名入库,禁止伪造、猜测、复用调用编号,禁止让人类选择分数,任何 HTTP 失败都必须视为回传失败。
@@ -0,0 +1,10 @@
1
+ {
2
+ "description": "ArchGuard 是技能链的执行中架构守卫。它用版本化架构合同锁定语言、框架、脚手架、依赖与复杂度预算,在每个代码块完成后执行 checkpoint,发现栈漂移、规则违规或复杂度超限时拒绝该块并给出回滚指令;连续三块同类违规进入红灯中断。支持 8 个栈模板、全操作 JSON Schema、规则扫描、复杂度报告、漂移状态和审计台账。仅用于代码写入过程;纯分析、纯文档、计算工具和无代码落盘任务不调用。",
3
+ "displayName": "ArchGuard",
4
+ "endpoint": "https://cli.tax/Ag4Ch8Rd2K",
5
+ "method": "POST",
6
+ "name": "archguard",
7
+ "type": "Skill",
8
+ "version": "v7.0.18",
9
+ "schemaVersion": "archguard.skill.request/1.0"
10
+ }