cli-aimlock 7.0.34 → 7.0.35
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/aimlock-runtime.mjs +1 -1
- package/brain-client-files.mjs +88 -0
- package/brain-client.mjs +170 -0
- package/cli.mjs +4 -1
- package/package.json +6 -3
- package/skill/SKILL.md +10 -1
- package/skill/skill.json +1 -1
package/aimlock-runtime.mjs
CHANGED
|
@@ -209,7 +209,7 @@ const RESPONSE_SCHEMA = "aimlock.skill.response/1.1";
|
|
|
209
209
|
const ERROR_SCHEMA = "aimlock.skill.error/1.0";
|
|
210
210
|
const CONTRACT_SCHEMA = "aimlock.scope-contract/1.0";
|
|
211
211
|
const COMPILER_NAME = "aimlock";
|
|
212
|
-
const COMPILER_VERSION = "v7.0.
|
|
212
|
+
const COMPILER_VERSION = "v7.0.35";
|
|
213
213
|
const KEEP_ALIVE_SECONDS = 90;
|
|
214
214
|
const KEEP_ALIVE_MESSAGE = "智能目标持续执行中,请勿关闭!";
|
|
215
215
|
const BYPASS_LINE_BUDGET = 500;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { constants } from 'node:fs'
|
|
3
|
+
import { lstat, open, realpath, mkdir, writeFile, readFile } from 'node:fs/promises'
|
|
4
|
+
import { relative, resolve, isAbsolute } from 'node:path'
|
|
5
|
+
|
|
6
|
+
const MAX_FILE_BYTES = 2_000_000
|
|
7
|
+
const MAX_CONTEXT_CHARACTERS = 6_000
|
|
8
|
+
export function validateBrainPath(path) {
|
|
9
|
+
if (typeof path !== 'string' || !path || path.length > 500 || isAbsolute(path)
|
|
10
|
+
|| /[\\:\u0000-\u001f\u007f]/.test(path) || path.split('/').some((part) => !part || part === '.' || part === '..')
|
|
11
|
+
|| /(?:^|\/)(?:\.env(?:\.[^/]*)?|\.git|\.ssh|node_modules|id_rsa|id_ed25519)(?:\/|$)|\.(?:pem|p12|key)$/i.test(path)) {
|
|
12
|
+
throw new Error('Brain target must be a safe relative project file')
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function checkedAncestors(root, path) {
|
|
17
|
+
const canonicalRoot = await realpath(root)
|
|
18
|
+
let current = canonicalRoot
|
|
19
|
+
for (const part of path.split('/').slice(0, -1)) {
|
|
20
|
+
current = resolve(current, part)
|
|
21
|
+
let status
|
|
22
|
+
try { status = await lstat(current) }
|
|
23
|
+
catch (error) { if (error.code === 'ENOENT') continue; throw error }
|
|
24
|
+
if (!status.isDirectory() || status.isSymbolicLink()) throw new Error('Brain target has an unsafe parent')
|
|
25
|
+
}
|
|
26
|
+
return canonicalRoot
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function inspectBrainTarget(root, path) {
|
|
30
|
+
validateBrainPath(path)
|
|
31
|
+
const canonicalRoot = await checkedAncestors(root, path)
|
|
32
|
+
const target = resolve(canonicalRoot, path)
|
|
33
|
+
let status
|
|
34
|
+
try { status = await lstat(target) }
|
|
35
|
+
catch (error) { if (error.code === 'ENOENT') return { path, sha256: null, context: '' }; throw error }
|
|
36
|
+
if (!status.isFile() || status.isSymbolicLink() || status.size > MAX_FILE_BYTES) throw new Error('Brain target is not a bounded regular file')
|
|
37
|
+
const file = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW)
|
|
38
|
+
try {
|
|
39
|
+
const opened = await file.stat()
|
|
40
|
+
if (opened.dev !== status.dev || opened.ino !== status.ino || !opened.isFile()) throw new Error('Brain target changed during inspection')
|
|
41
|
+
const bytes = Buffer.alloc(MAX_FILE_BYTES + 1)
|
|
42
|
+
let length = 0
|
|
43
|
+
while (length < bytes.length) {
|
|
44
|
+
const chunk = await file.read(bytes, length, bytes.length - length, length)
|
|
45
|
+
if (!chunk.bytesRead) break
|
|
46
|
+
length += chunk.bytesRead
|
|
47
|
+
}
|
|
48
|
+
if (length > MAX_FILE_BYTES) throw new Error('Brain target exceeds the file limit')
|
|
49
|
+
const final = await lstat(target)
|
|
50
|
+
const inside = relative(canonicalRoot, await realpath(target))
|
|
51
|
+
if (inside === '..' || inside.startsWith('../') || isAbsolute(inside)
|
|
52
|
+
|| final.dev !== opened.dev || final.ino !== opened.ino || final.mtimeMs !== opened.mtimeMs) {
|
|
53
|
+
throw new Error('Brain target changed during inspection')
|
|
54
|
+
}
|
|
55
|
+
const contents = bytes.subarray(0, length)
|
|
56
|
+
if (contents.includes(0)) throw new Error('Brain context must be a text file')
|
|
57
|
+
return { path, sha256: createHash('sha256').update(contents).digest('hex'),
|
|
58
|
+
context: contents.toString('utf8').slice(0, MAX_CONTEXT_CHARACTERS) }
|
|
59
|
+
} finally { await file.close() }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function brainStateDirectory(root, parts) {
|
|
63
|
+
const canonicalRoot = await realpath(root)
|
|
64
|
+
let directory = canonicalRoot
|
|
65
|
+
for (const part of ['.aimlock', ...parts]) {
|
|
66
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(part) && part !== '.aimlock') throw new Error('Brain state path is invalid')
|
|
67
|
+
directory = resolve(directory, part)
|
|
68
|
+
try { await mkdir(directory, { mode: 0o700 }) }
|
|
69
|
+
catch (error) { if (error.code !== 'EEXIST') throw error }
|
|
70
|
+
const status = await lstat(directory)
|
|
71
|
+
if (!status.isDirectory() || status.isSymbolicLink()) throw new Error('Brain state directory is unsafe')
|
|
72
|
+
}
|
|
73
|
+
return directory
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function saveBrainRequest(root, request, digest) {
|
|
77
|
+
const directory = await brainStateDirectory(root, ['brain-requests'])
|
|
78
|
+
const path = resolve(directory, request.requestId + '.json')
|
|
79
|
+
try { await writeFile(path, JSON.stringify(request), { mode: 0o600, flag: 'wx' }) }
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error.code !== 'EEXIST') throw error
|
|
82
|
+
const status = await lstat(path)
|
|
83
|
+
if (!status.isFile() || status.isSymbolicLink() || status.size > MAX_FILE_BYTES) throw new Error('Saved Brain request is unsafe')
|
|
84
|
+
if (digest(JSON.parse(await readFile(path, 'utf8'))) !== digest(request)) {
|
|
85
|
+
throw new Error('Brain request ID is already bound to different local input')
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
package/brain-client.mjs
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto'
|
|
2
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
3
|
+
import { inspectBrainTarget, brainStateDirectory, saveBrainRequest } from './brain-client-files.mjs'
|
|
4
|
+
export { inspectBrainTarget } from './brain-client-files.mjs'
|
|
5
|
+
import { resolve } from 'node:path'
|
|
6
|
+
import { brainClientAuthorization, transportFailureCode } from './broker.mjs'
|
|
7
|
+
import { executeCommand } from './aimlock-chain-process.mjs'
|
|
8
|
+
|
|
9
|
+
export const BRAIN_ENDPOINT = 'https://cli.tax/api/v1/brain'
|
|
10
|
+
const REQUEST_SCHEMA = 'brain.planning-request/1.0'
|
|
11
|
+
const RESPONSE_SCHEMA = 'brain.planning-response/1.0'
|
|
12
|
+
const PLAN_SCHEMA = 'brain.execution-plan/1.0'
|
|
13
|
+
const MAX_INPUT_BYTES = 256_000
|
|
14
|
+
const TIMEOUT_MS = 120_000
|
|
15
|
+
const HASH_PATTERN = /^[0-9a-f]{64}$/
|
|
16
|
+
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
17
|
+
const OPERATIONS = new Set(['plan', 'status', 'report', 'validate'])
|
|
18
|
+
|
|
19
|
+
export function canonicalBrainJson(value) {
|
|
20
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value)
|
|
21
|
+
if (typeof value === 'number' && Number.isFinite(value)) return JSON.stringify(value)
|
|
22
|
+
if (Array.isArray(value)) return '[' + value.map(canonicalBrainJson).join(',') + ']'
|
|
23
|
+
if (!value || typeof value !== 'object') throw new Error('Brain protocol value must be JSON')
|
|
24
|
+
return '{' + Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
25
|
+
.map(([key, item]) => JSON.stringify(key) + ':' + canonicalBrainJson(item)).join(',') + '}'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function brainClientDigest(value) {
|
|
29
|
+
return createHash('sha256').update(canonicalBrainJson(value)).digest('hex')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function exact(value, keys, label) {
|
|
33
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)
|
|
34
|
+
|| Object.keys(value).some((key) => !keys.includes(key))
|
|
35
|
+
|| keys.some((key) => !Object.hasOwn(value, key))) throw new Error(label + ' has invalid fields')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function invokeBrain(operation, input, dependencies = {}) {
|
|
39
|
+
if (!OPERATIONS.has(operation)) throw new Error('Unknown Brain operation')
|
|
40
|
+
const endpoint = dependencies.endpoint ?? BRAIN_ENDPOINT
|
|
41
|
+
const context = { endpoint, displayName: 'Brain planning' }
|
|
42
|
+
const environment = dependencies.environment ?? process.env
|
|
43
|
+
const authorization = await brainClientAuthorization(context, environment, dependencies.credentialAccess)
|
|
44
|
+
const body = JSON.stringify({ operation, input })
|
|
45
|
+
if (Buffer.byteLength(body) > MAX_INPUT_BYTES) throw new Error('Brain request exceeds the size limit')
|
|
46
|
+
let response
|
|
47
|
+
try {
|
|
48
|
+
response = await (dependencies.request ?? fetch)(endpoint, {
|
|
49
|
+
method: 'POST', redirect: 'error', headers: { Authorization: authorization, 'Content-Type': 'application/json' },
|
|
50
|
+
body, signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
51
|
+
})
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new Error('Brain transport failed (' + transportFailureCode(error) + '); query status before submitting another plan')
|
|
54
|
+
}
|
|
55
|
+
const payload = await readBrainResponse(response)
|
|
56
|
+
if (!response.ok) {
|
|
57
|
+
const code = typeof payload?.code === 'string' ? payload.code : 'BRAIN_HTTP_ERROR'
|
|
58
|
+
throw new Error('Brain request failed: HTTP ' + response.status + ' (' + code + ')')
|
|
59
|
+
}
|
|
60
|
+
if (payload?.schemaVersion !== RESPONSE_SCHEMA || !UUID_PATTERN.test(payload.planId)
|
|
61
|
+
|| !UUID_PATTERN.test(payload.requestId)
|
|
62
|
+
|| !['planning', 'ready', 'reported', 'verified', 'failed', 'expired'].includes(payload.status)) {
|
|
63
|
+
throw new Error('Brain response envelope is invalid')
|
|
64
|
+
}
|
|
65
|
+
if ((operation === 'plan' && payload.requestId !== input.requestId)
|
|
66
|
+
|| (operation !== 'plan' && (input.planId ? payload.planId !== input.planId : payload.requestId !== input.requestId))) throw new Error('Brain response identity does not match')
|
|
67
|
+
if (payload.plan !== null && (payload.plan?.schemaVersion !== PLAN_SCHEMA
|
|
68
|
+
|| payload.plan.planId !== payload.planId || !HASH_PATTERN.test(payload.planDigest)
|
|
69
|
+
|| brainClientDigest(payload.plan) !== payload.planDigest)) throw new Error('Brain response plan digest is invalid')
|
|
70
|
+
return payload
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function prepareBrainRequest(root, specification) {
|
|
74
|
+
exact(specification, ['requestId', 'goal', 'maxChangedLines', 'targets', 'checks'], 'Brain specification')
|
|
75
|
+
if (!UUID_PATTERN.test(specification.requestId) || typeof specification.goal !== 'string' || !specification.goal.trim()
|
|
76
|
+
|| !Number.isSafeInteger(specification.maxChangedLines) || specification.maxChangedLines < 1
|
|
77
|
+
|| !Array.isArray(specification.targets) || !specification.targets.length || specification.targets.length > 64
|
|
78
|
+
|| new Set(specification.targets).size !== specification.targets.length) throw new Error('Brain specification is invalid')
|
|
79
|
+
const targets = []
|
|
80
|
+
for (const path of specification.targets) targets.push(await inspectBrainTarget(root, path))
|
|
81
|
+
return { schemaVersion: REQUEST_SCHEMA, ...specification, targets }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function validateBrainHandoff(request, response) {
|
|
85
|
+
const plan = response.plan
|
|
86
|
+
if (response.status !== 'ready' || plan?.schemaVersion !== PLAN_SCHEMA || response.requestId !== request.requestId
|
|
87
|
+
|| response.planId !== plan.planId || brainClientDigest(plan) !== response.planDigest) throw new Error('Brain plan is not ready')
|
|
88
|
+
const targets = request.targets.map(({ path, sha256 }) => ({ path, sha256 }))
|
|
89
|
+
if (brainClientDigest(plan.targets) !== brainClientDigest(targets)
|
|
90
|
+
|| brainClientDigest(plan.checks) !== brainClientDigest(request.checks)
|
|
91
|
+
|| brainClientDigest(plan.contract.allowedPaths) !== brainClientDigest(targets.map((target) => target.path))
|
|
92
|
+
|| plan.contract.maxChangedLines !== request.maxChangedLines || plan.contract.allowDeleteFiles !== false) {
|
|
93
|
+
throw new Error('Brain plan changed the local authorization')
|
|
94
|
+
}
|
|
95
|
+
if (!Array.isArray(plan.nodes) || !plan.nodes.length
|
|
96
|
+
|| plan.nodes.some((node) => !targets.some((target) => target.path === node.path))) throw new Error('Brain plan targets are invalid')
|
|
97
|
+
return plan
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function collectBrainReport(root, handoff, environment = process.env) {
|
|
101
|
+
exact(handoff, ['request', 'response'], 'Brain handoff')
|
|
102
|
+
const plan = validateBrainHandoff(handoff.request, handoff.response)
|
|
103
|
+
if (typeof environment.PATH !== 'string' || !environment.PATH) throw new Error('Execution PATH is required')
|
|
104
|
+
const reportId = randomUUID()
|
|
105
|
+
const directory = await brainStateDirectory(root, ['brain-reports', reportId])
|
|
106
|
+
const evidence = []
|
|
107
|
+
for (const check of plan.checks) {
|
|
108
|
+
exact(check, ['id', 'executable', 'args', 'timeoutMs'], 'Brain check')
|
|
109
|
+
if (typeof check.id !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,79}$/.test(check.id)
|
|
110
|
+
|| typeof check.executable !== 'string' || !check.executable
|
|
111
|
+
|| !Array.isArray(check.args) || check.args.some((arg) => typeof arg !== 'string')
|
|
112
|
+
|| !Number.isSafeInteger(check.timeoutMs) || check.timeoutMs < 1_000 || check.timeoutMs > 3_600_000) {
|
|
113
|
+
throw new Error('Brain check command is invalid')
|
|
114
|
+
}
|
|
115
|
+
const result = await executeCommand(root, {
|
|
116
|
+
executable: check.executable, args: check.args, timeoutMs: check.timeoutMs,
|
|
117
|
+
environment: { PATH: environment.PATH }, workingDirectory: '.', evidenceKind: 'test',
|
|
118
|
+
}, reportId + ':' + check.id, async () => {})
|
|
119
|
+
const digest = (value) => createHash('sha256').update(value).digest('hex')
|
|
120
|
+
evidence.push({ checkId: check.id, exitCode: result.status !== 'succeeded' && result.exitCode === 0 ? null : result.exitCode, durationMs: result.durationMs,
|
|
121
|
+
stdoutSha256: digest(result.stdout), stderrSha256: digest(result.stderr) })
|
|
122
|
+
await writeFile(resolve(directory, check.id + '.json'), JSON.stringify(result), { mode: 0o600 })
|
|
123
|
+
if (result.status === 'uncertain') throw new Error('Check process cleanup is unconfirmed; execution stopped')
|
|
124
|
+
}
|
|
125
|
+
const files = []
|
|
126
|
+
for (const node of plan.nodes) {
|
|
127
|
+
const actual = await inspectBrainTarget(root, node.path)
|
|
128
|
+
if (actual.sha256 === null) throw new Error('Planned file is missing: ' + node.path)
|
|
129
|
+
files.push({ path: node.path, sha256: actual.sha256 })
|
|
130
|
+
}
|
|
131
|
+
return { planId: plan.planId, planDigest: handoff.response.planDigest, reportId, files, evidence }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function runBrainCli(args) {
|
|
135
|
+
const [operation, root, file] = args
|
|
136
|
+
if (!['plan', 'check', 'status', 'validate'].includes(operation) || !root || !file || args.length !== 3) {
|
|
137
|
+
throw new Error('Usage: cli-aimlock brain <plan|check|status|validate> <repositoryRoot> <jsonFile>')
|
|
138
|
+
}
|
|
139
|
+
const source = await readFile(resolve(file), 'utf8')
|
|
140
|
+
if (Buffer.byteLength(source) > MAX_INPUT_BYTES) throw new Error('Brain input file exceeds the size limit')
|
|
141
|
+
const input = JSON.parse(source)
|
|
142
|
+
if (operation === 'plan') {
|
|
143
|
+
const request = await prepareBrainRequest(root, input)
|
|
144
|
+
await saveBrainRequest(root, request, brainClientDigest)
|
|
145
|
+
const response = await invokeBrain('plan', request)
|
|
146
|
+
if (response.status === 'ready') validateBrainHandoff(request, response)
|
|
147
|
+
process.stdout.write(JSON.stringify({ request, response }) + '\n')
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
const payload = operation === 'check' ? await collectBrainReport(root, input) : input
|
|
151
|
+
const response = await invokeBrain(operation === 'check' ? 'report' : operation, payload)
|
|
152
|
+
process.stdout.write(JSON.stringify(response) + '\n')
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function readBrainResponse(response) {
|
|
156
|
+
if (!response.body) throw new Error('Brain response body is missing')
|
|
157
|
+
const reader = response.body.getReader()
|
|
158
|
+
const chunks = []
|
|
159
|
+
let bytes = 0
|
|
160
|
+
try {
|
|
161
|
+
for (;;) {
|
|
162
|
+
const chunk = await reader.read()
|
|
163
|
+
if (chunk.done) break
|
|
164
|
+
bytes += chunk.value.byteLength
|
|
165
|
+
if (bytes > MAX_INPUT_BYTES) { await reader.cancel(); throw new Error('Brain response exceeds the size limit') }
|
|
166
|
+
chunks.push(Buffer.from(chunk.value))
|
|
167
|
+
}
|
|
168
|
+
} finally { reader.releaseLock() }
|
|
169
|
+
return JSON.parse(Buffer.concat(chunks).toString('utf8'))
|
|
170
|
+
}
|
package/cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname, resolve } from 'node:path'
|
|
|
4
4
|
import { cwd, stdin, stdout } from 'node:process'
|
|
5
5
|
import { createInterface } from 'node:readline/promises'
|
|
6
6
|
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { runBrainCli } from './brain-client.mjs'
|
|
7
8
|
import { CHAIN_USAGE, runChainCli } from './aimlock-chain-cli.mjs'
|
|
8
9
|
import { defaultUsage, dispatchOfficialSkillCli, runIntakeHandshake } from './installer.mjs'
|
|
9
10
|
import {
|
|
@@ -185,7 +186,9 @@ async function dispatchLocal(args) {
|
|
|
185
186
|
|
|
186
187
|
const cliPath = fileURLToPath(import.meta.url)
|
|
187
188
|
if (process.argv[1] && realpathSync(resolve(process.argv[1])) === cliPath) {
|
|
188
|
-
if (process.argv[2] === '
|
|
189
|
+
if (process.argv[2] === 'brain') {
|
|
190
|
+
await runBrainCli(process.argv.slice(3))
|
|
191
|
+
} else if (process.argv[2] === 'chain') {
|
|
189
192
|
await runChainCli(process.argv.slice(3))
|
|
190
193
|
} else if (process.argv[2] === 'local') {
|
|
191
194
|
try {
|
package/package.json
CHANGED
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
"cli-aimlock": "./cli.mjs"
|
|
4
4
|
},
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"cli-swarm": "7.0.
|
|
6
|
+
"cli-swarm": "7.0.35"
|
|
7
7
|
},
|
|
8
8
|
"description": "Aimlock skill installer for CLI.Tax: lock a user request into an executable aim and route Blueprint, Swarm, and Calctool.",
|
|
9
9
|
"exports": {
|
|
10
10
|
"./coordination": "./aimlock-coordination.mjs",
|
|
11
11
|
"./local-runner": "./aimlock-local-runner.mjs",
|
|
12
12
|
"./runtime": "./aimlock-runtime.mjs",
|
|
13
|
-
"./chain-executor": "./aimlock-chain-executor.mjs"
|
|
13
|
+
"./chain-executor": "./aimlock-chain-executor.mjs",
|
|
14
|
+
"./brain-client": "./brain-client.mjs"
|
|
14
15
|
},
|
|
15
16
|
"files": [
|
|
16
17
|
"cli.mjs",
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
"aimlock-local-gate.mjs",
|
|
34
35
|
"aimlock-local-runner.mjs",
|
|
35
36
|
"aimlock-runtime.mjs",
|
|
37
|
+
"brain-client.mjs",
|
|
38
|
+
"brain-client-files.mjs",
|
|
36
39
|
"skill/references/chain-executor.md"
|
|
37
40
|
],
|
|
38
41
|
"license": "UNLICENSED",
|
|
@@ -42,5 +45,5 @@
|
|
|
42
45
|
"url": "https://github.com/88208555/aimlock-clitax.git"
|
|
43
46
|
},
|
|
44
47
|
"type": "module",
|
|
45
|
-
"version": "7.0.
|
|
48
|
+
"version": "7.0.35"
|
|
46
49
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: "Aimlock 仅用于大型、深度、跨模块、高风险、需要
|
|
|
5
5
|
|
|
6
6
|
# Aimlock Skill
|
|
7
7
|
|
|
8
|
-
Package version: v7.0.
|
|
8
|
+
Package version: v7.0.35
|
|
9
9
|
|
|
10
10
|
Endpoint: https://cli.tax/R3mQ8kWpXn
|
|
11
11
|
|
|
@@ -165,3 +165,12 @@ Aimlock returns the protocol; it does not start a timer.
|
|
|
165
165
|
## 宿主持久执行
|
|
166
166
|
|
|
167
167
|
使用 [chain-executor.md](references/chain-executor.md) 的显式 `chain init/resume/status/answer` 协议驱动本地持久步骤。`run` 的需求采集、远端 `nextStep` 与 `completed` 均不等于已执行。只有真实 broker/协调器/命令结果及绑定证据能推进;未答复人工裁决禁止恢复,发送后结果不确定禁止自动重发。CLI 终端不提供 OS 隔离或独立可信 runner。
|
|
168
|
+
|
|
169
|
+
## 服务端沙箱规划与 IDE 执行
|
|
170
|
+
|
|
171
|
+
1. 用户在模型设置中启用自己的模型地址、API Key 和模型名后,规划优先使用该配置;未启用个人模型时使用官方模型并执行有限套餐额度。个人模型失败必须明确报错,禁止自动切换模型或消耗官方额度。
|
|
172
|
+
2. 准备请求 JSON,明确 requestId、目标、允许文件、最大修改行数和批准的检查命令;运行 `npx cli-aimlock@latest brain plan <repositoryRoot> <request.json>`。服务端调用模型规划,再由隔离沙箱编译结构化计划;保留返回的 request/response 交接包。
|
|
173
|
+
3. 审查返回计划的允许范围、基线哈希和检查命令;完成现有 Aimlock 范围、快照和写入门禁后,由 IDE 修改代码。计划本身不授权扩大范围,不替代写入门禁。
|
|
174
|
+
4. 运行 `npx cli-aimlock@latest brain check <repositoryRoot> <handoff.json>` 执行批准的检查并回传产物哈希和结果。普通 IDE 回传属于 client-reported,不能据此声称可信验证通过。
|
|
175
|
+
5. 只有已批准的可信 runner 生成与本次计划和报告绑定的签名收据后,才运行 `npx cli-aimlock@latest brain validate <repositoryRoot> <validation.json>`。没有可信收据时保持已回传状态,不伪造验证。
|
|
176
|
+
6. 请求发送后结果不确定时,先用 `brain status <repositoryRoot> <status.json>` 按 requestId 或 planId 查询;禁止自动重发规划或重复计费。
|
package/skill/skill.json
CHANGED