@kylecheng3146/agent-ops 0.1.4 → 0.1.5
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 +88 -13
- package/dist/packages/cli/src/args.js +39 -6
- package/dist/packages/cli/src/bin.js +33 -11
- package/dist/packages/cli/src/cli.js +6 -1
- package/dist/packages/cli/src/commands/doctor.js +31 -9
- package/dist/packages/cli/src/commands/hook.js +18 -16
- package/dist/packages/cli/src/commands/init.js +9 -5
- package/dist/packages/cli/src/commands/review.js +5 -1
- package/dist/packages/cli/src/commands/uninstall.js +6 -5
- package/dist/packages/cli/src/commands/update.js +13 -5
- package/dist/packages/cli/src/context.js +9 -3
- package/dist/packages/cli/src/hook-process.js +109 -7
- package/dist/packages/cli/src/plan-output.js +9 -4
- package/dist/packages/cli/src/public-plan.js +62 -0
- package/dist/packages/cli/src/version.js +1 -1
- package/dist/packages/cli/src/wizard.js +27 -11
- package/dist/runtime/src/adapters/claude/config.js +0 -8
- package/dist/runtime/src/adapters/claude/events.js +26 -0
- package/dist/runtime/src/adapters/claude/output.js +6 -6
- package/dist/runtime/src/adapters/claude/surfaces.js +70 -0
- package/dist/runtime/src/adapters/codex/config.js +29 -11
- package/dist/runtime/src/adapters/codex/events.js +26 -0
- package/dist/runtime/src/adapters/codex/output.js +10 -0
- package/dist/runtime/src/adapters/codex/surfaces.js +12 -0
- package/dist/runtime/src/adapters/opencode/config.js +170 -0
- package/dist/runtime/src/adapters/opencode/events.js +49 -0
- package/dist/runtime/src/adapters/opencode/input.js +32 -0
- package/dist/runtime/src/adapters/opencode/output.js +23 -0
- package/dist/runtime/src/adapters/opencode/surfaces.js +23 -0
- package/dist/runtime/src/config/explain.js +7 -0
- package/dist/runtime/src/config/hash.js +24 -0
- package/dist/runtime/src/config/merge.js +6 -2
- package/dist/runtime/src/config/migrate.js +19 -4
- package/dist/runtime/src/contracts.js +10 -1
- package/dist/runtime/src/fs/manifest.js +32 -1
- package/dist/runtime/src/fs/transaction.js +14 -2
- package/dist/runtime/src/hooks/advisory.js +16 -0
- package/dist/runtime/src/hooks/stop-service.js +70 -0
- package/dist/runtime/src/hooks/stop-verify.js +4 -1
- package/dist/runtime/src/install/doctor.js +119 -9
- package/dist/runtime/src/install/harness.js +296 -35
- package/dist/runtime/src/install/hooks.js +22 -17
- package/dist/runtime/src/install/ownership.js +110 -34
- package/dist/runtime/src/install/plan.js +207 -28
- package/dist/runtime/src/install/probes.js +9 -43
- package/dist/runtime/src/install/profiles.js +8 -1
- package/dist/runtime/src/install/surface-inspection.js +296 -0
- package/dist/runtime/src/install/surfaces.js +11 -0
- package/dist/runtime/src/install/uninstall.js +10 -3
- package/dist/runtime/src/install/update.js +8 -2
- package/dist/runtime/src/schema/validate.js +37 -18
- package/dist/runtime/src/task/service.js +3 -3
- package/dist/runtime/src/task/store.js +12 -3
- package/dist/runtime/src/verify/command-executor.js +113 -0
- package/dist/runtime/src/verify/evidence.js +4 -24
- package/dist/runtime/src/verify/service.js +20 -92
- package/dist/runtime/src/verify/spawn.js +6 -1
- package/docs/en/guides/configuration.md +83 -0
- package/docs/en/guides/quickstart.md +9 -0
- package/docs/en/guides/security.md +5 -0
- package/docs/en/spec/README.md +9 -0
- package/docs/en/spec/harness-adapters.md +66 -2
- package/docs/en/spec/maintenance.md +11 -0
- package/docs/en/spec/review.md +11 -0
- package/docs/zh-TW/guides/configuration.md +77 -0
- package/docs/zh-TW/guides/quickstart.md +9 -0
- package/docs/zh-TW/guides/security.md +5 -0
- package/docs/zh-TW/spec/README.md +9 -0
- package/docs/zh-TW/spec/harness-adapters.md +57 -3
- package/docs/zh-TW/spec/maintenance.md +11 -1
- package/docs/zh-TW/spec/review.md +10 -0
- package/package.json +4 -2
- package/schemas/config.schema.json +55 -1
- package/schemas/manifest.schema.json +7 -2
- package/templates/common/AGENTS.block.md +2 -1
- package/templates/common/CLAUDE.block.md +2 -1
- package/dist/runtime/src/review/claude-runner.js +0 -4
- package/dist/runtime/src/review/codex-runner.js +0 -4
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { EVIDENCE_SCHEMA_VERSION } from "../contracts.js";
|
|
3
3
|
import { sha256 } from "../fs/hash.js";
|
|
4
4
|
import { AgentOpsError } from "../fs/paths.js";
|
|
5
|
+
import { calculateConfigHash } from "../config/hash.js";
|
|
6
|
+
export { calculateConfigHash } from "../config/hash.js";
|
|
5
7
|
import { validateEvidence } from "../schema/validate.js";
|
|
6
8
|
import { readPrivateFile, writePrivateFile } from "../security/permissions.js";
|
|
7
9
|
import { redactSecrets } from "../security/redact.js";
|
|
8
|
-
function canonicalJson(value) {
|
|
9
|
-
if (value === null ||
|
|
10
|
-
typeof value === "boolean" ||
|
|
11
|
-
typeof value === "number" ||
|
|
12
|
-
typeof value === "string") {
|
|
13
|
-
return JSON.stringify(value);
|
|
14
|
-
}
|
|
15
|
-
if (Array.isArray(value)) {
|
|
16
|
-
return `[${value.map(canonicalJson).join(",")}]`;
|
|
17
|
-
}
|
|
18
|
-
if (typeof value === "object") {
|
|
19
|
-
const record = value;
|
|
20
|
-
return `{${Object.keys(record)
|
|
21
|
-
.sort()
|
|
22
|
-
.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
|
|
23
|
-
.join(",")}}`;
|
|
24
|
-
}
|
|
25
|
-
throw new AgentOpsError("CONFIG_HASH_INVALID", "Configuration contains an unsupported value.");
|
|
26
|
-
}
|
|
27
|
-
export function calculateConfigHash(config) {
|
|
28
|
-
return sha256(canonicalJson(config));
|
|
29
|
-
}
|
|
30
10
|
function redactRecord(record) {
|
|
31
11
|
return Object.fromEntries(Object.entries(record).map(([key, value]) => [
|
|
32
12
|
redactSecrets(key),
|
|
@@ -43,7 +23,7 @@ function validateBuiltEvidence(value) {
|
|
|
43
23
|
}
|
|
44
24
|
export function buildVerificationEvidence(input) {
|
|
45
25
|
return validateBuiltEvidence({
|
|
46
|
-
schemaVersion:
|
|
26
|
+
schemaVersion: EVIDENCE_SCHEMA_VERSION,
|
|
47
27
|
taskId: input.taskId,
|
|
48
28
|
criterionId: input.criterionId,
|
|
49
29
|
commandId: input.command.id,
|
|
@@ -3,55 +3,11 @@ import { validateTaskAgainstConfig } from "../schema/validate.js";
|
|
|
3
3
|
import { collectChangeSurface } from "./change-surface.js";
|
|
4
4
|
import { buildVerificationEvidence } from "./evidence.js";
|
|
5
5
|
import { createFailureFingerprint } from "./fingerprint.js";
|
|
6
|
+
import { aggregateVerificationStatus, executeConfiguredCommand } from "./command-executor.js";
|
|
6
7
|
import { selectVerificationScope } from "./scope.js";
|
|
7
|
-
import { runVerificationCommand } from "./spawn.js";
|
|
8
|
-
import { evaluateTestCount, parseTestCount } from "./test-count.js";
|
|
9
8
|
function verificationError(code, message) {
|
|
10
9
|
return new AgentOpsError(code, message);
|
|
11
10
|
}
|
|
12
|
-
function classifyTestCountCode(code) {
|
|
13
|
-
const classes = {
|
|
14
|
-
TEST_COUNT_BELOW_MINIMUM: "test-count-below-minimum",
|
|
15
|
-
TEST_COUNT_INVALID: "test-count-invalid",
|
|
16
|
-
TEST_COUNT_OK: "none",
|
|
17
|
-
TEST_COUNT_REQUIREMENT_INVALID: "test-count-requirement-invalid",
|
|
18
|
-
TEST_COUNT_UNPARSEABLE: "test-count-unparseable",
|
|
19
|
-
ZERO_TESTS: "zero-tests"
|
|
20
|
-
};
|
|
21
|
-
return classes[code];
|
|
22
|
-
}
|
|
23
|
-
function classifyResult(command, spawned) {
|
|
24
|
-
if (command.evidence.kind === "file") {
|
|
25
|
-
return {
|
|
26
|
-
status: spawned.status === "PASS" ? "UNKNOWN" : spawned.status,
|
|
27
|
-
failureClass: spawned.status === "PASS"
|
|
28
|
-
? "file-evidence-unsupported"
|
|
29
|
-
: spawned.failureClass,
|
|
30
|
-
testCount: null
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
if (command.evidence.kind !== "test-count") {
|
|
34
|
-
return {
|
|
35
|
-
status: spawned.status,
|
|
36
|
-
failureClass: spawned.failureClass,
|
|
37
|
-
testCount: null
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
const testCount = parseTestCount(`${spawned.stdout}\n${spawned.stderr}`);
|
|
41
|
-
if (spawned.status !== "PASS") {
|
|
42
|
-
return {
|
|
43
|
-
status: spawned.status,
|
|
44
|
-
failureClass: spawned.failureClass,
|
|
45
|
-
testCount
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
const evaluation = evaluateTestCount(testCount, command.evidence.minimum);
|
|
49
|
-
return {
|
|
50
|
-
status: evaluation.status,
|
|
51
|
-
failureClass: classifyTestCountCode(evaluation.code),
|
|
52
|
-
testCount: evaluation.testCount
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
11
|
function exitCategory(result) {
|
|
56
12
|
if (result.timedOut) {
|
|
57
13
|
return "timeout";
|
|
@@ -64,17 +20,6 @@ function exitCategory(result) {
|
|
|
64
20
|
}
|
|
65
21
|
return result.exitCode === 0 ? "exit-zero" : "nonzero-exit";
|
|
66
22
|
}
|
|
67
|
-
function overallStatus(results) {
|
|
68
|
-
const required = results.filter((result) => result.required);
|
|
69
|
-
const gating = required.length > 0 ? required : results;
|
|
70
|
-
if (gating.some((result) => result.status === "FAIL")) {
|
|
71
|
-
return "FAIL";
|
|
72
|
-
}
|
|
73
|
-
if (gating.some((result) => result.status === "UNKNOWN")) {
|
|
74
|
-
return "UNKNOWN";
|
|
75
|
-
}
|
|
76
|
-
return "PASS";
|
|
77
|
-
}
|
|
78
23
|
function relevantCriteria(task, commandId) {
|
|
79
24
|
return task.criteria.filter((criterion) => criterion.verifierIds.includes(commandId));
|
|
80
25
|
}
|
|
@@ -85,9 +30,6 @@ function commandById(config, commandId) {
|
|
|
85
30
|
}
|
|
86
31
|
return command;
|
|
87
32
|
}
|
|
88
|
-
function diagnostics(spawned) {
|
|
89
|
-
return spawned.stderr || spawned.stdout || spawned.failureClass;
|
|
90
|
-
}
|
|
91
33
|
export class VerificationService {
|
|
92
34
|
#options;
|
|
93
35
|
constructor(options) {
|
|
@@ -120,51 +62,37 @@ export class VerificationService {
|
|
|
120
62
|
}
|
|
121
63
|
async #runCommand(task, command) {
|
|
122
64
|
const startedAt = (this.#options.now ?? (() => new Date().toISOString()))();
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
commandId: command.id,
|
|
130
|
-
status: "UNKNOWN",
|
|
131
|
-
failureClass: "repository-untrusted",
|
|
132
|
-
exitCode: null,
|
|
133
|
-
signal: null,
|
|
134
|
-
timedOut: false,
|
|
135
|
-
durationMs: 0,
|
|
136
|
-
stdout: "",
|
|
137
|
-
stderr: "",
|
|
138
|
-
stdoutTruncated: false,
|
|
139
|
-
stderrTruncated: false
|
|
140
|
-
};
|
|
141
|
-
const classified = classifyResult(command, spawned);
|
|
142
|
-
const fingerprint = classified.status === "PASS"
|
|
65
|
+
const result = await executeConfiguredCommand(command, {
|
|
66
|
+
cwd: await this.#commandCwd(command),
|
|
67
|
+
runner: this.#options.processRunner,
|
|
68
|
+
trusted: this.#options.trusted
|
|
69
|
+
});
|
|
70
|
+
const fingerprint = result.status === "PASS"
|
|
143
71
|
? null
|
|
144
72
|
: createFailureFingerprint({
|
|
145
73
|
commandId: command.id,
|
|
146
|
-
failureClass:
|
|
147
|
-
exitCategory:
|
|
74
|
+
failureClass: result.failureClass,
|
|
75
|
+
exitCategory: result.timedOut
|
|
148
76
|
? "timeout"
|
|
149
|
-
:
|
|
77
|
+
: result.signal !== null
|
|
150
78
|
? "signal-exit"
|
|
151
|
-
:
|
|
79
|
+
: result.exitCode === null
|
|
152
80
|
? "no-exit"
|
|
153
|
-
:
|
|
81
|
+
: result.exitCode === 0
|
|
154
82
|
? "exit-zero"
|
|
155
83
|
: "nonzero-exit",
|
|
156
|
-
diagnostics:
|
|
84
|
+
diagnostics: result.diagnostic
|
|
157
85
|
});
|
|
158
86
|
const finishedAt = (this.#options.now ?? (() => new Date().toISOString()))();
|
|
159
|
-
const evidenceReferences = await this.#persistEvidence(task, command, startedAt, finishedAt,
|
|
87
|
+
const evidenceReferences = await this.#persistEvidence(task, command, startedAt, finishedAt, result, result.exitCode);
|
|
160
88
|
return {
|
|
161
89
|
commandId: command.id,
|
|
162
90
|
required: command.required,
|
|
163
|
-
status:
|
|
164
|
-
failureClass:
|
|
165
|
-
exitCode:
|
|
166
|
-
timedOut:
|
|
167
|
-
testCount:
|
|
91
|
+
status: result.status,
|
|
92
|
+
failureClass: result.failureClass,
|
|
93
|
+
exitCode: result.exitCode,
|
|
94
|
+
timedOut: result.timedOut,
|
|
95
|
+
testCount: result.testCount,
|
|
168
96
|
diagnostic: fingerprint?.diagnostics ?? "",
|
|
169
97
|
evidenceReferences
|
|
170
98
|
};
|
|
@@ -185,7 +113,7 @@ export class VerificationService {
|
|
|
185
113
|
for (const commandId of selection.verifierIds) {
|
|
186
114
|
results.push(await this.#runCommand(validation.value, commandById(this.#options.config, commandId)));
|
|
187
115
|
}
|
|
188
|
-
const status =
|
|
116
|
+
const status = aggregateVerificationStatus(results);
|
|
189
117
|
let signal = null;
|
|
190
118
|
if (status === "PASS") {
|
|
191
119
|
await this.#options.taskService.clearFailure(taskId);
|
|
@@ -138,6 +138,10 @@ export class NodeVerificationProcessRunner {
|
|
|
138
138
|
const child = spawn(request.command, [...request.args], {
|
|
139
139
|
cwd: request.cwd,
|
|
140
140
|
detached: this.#platform !== "win32",
|
|
141
|
+
env: {
|
|
142
|
+
...process.env,
|
|
143
|
+
...(request.env ?? {})
|
|
144
|
+
},
|
|
141
145
|
shell: request.shell,
|
|
142
146
|
stdio: ["ignore", "pipe", "pipe"],
|
|
143
147
|
windowsHide: true
|
|
@@ -258,7 +262,8 @@ export async function runVerificationCommand(command, options) {
|
|
|
258
262
|
command: command.command,
|
|
259
263
|
args: [...command.args],
|
|
260
264
|
cwd: options.cwd,
|
|
261
|
-
shell: command.shell === true
|
|
265
|
+
shell: command.shell === true,
|
|
266
|
+
...(options.env === undefined ? {} : { env: options.env })
|
|
262
267
|
});
|
|
263
268
|
}
|
|
264
269
|
catch {
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Keep project configuration explicit and layered. Choose scope, harness, and profile deliberately; do not infer trust or security exceptions from `--yes`.
|
|
4
|
+
|
|
5
|
+
Use `--harness all` to select Codex, Claude Code, and opencode, or pass a
|
|
6
|
+
comma-separated subset such as `codex,opencode`. `both` remains an input alias
|
|
7
|
+
for the legacy Codex plus Claude selection.
|
|
8
|
+
|
|
9
|
+
Project Codex and opencode installations share the managed supplemental
|
|
10
|
+
`AGENTS.md` routing block and the `.agent-ops/AGENTS.md` rules artifact. The
|
|
11
|
+
block loads the managed baseline while project-specific instructions remain
|
|
12
|
+
authoritative. Claude uses the corresponding `CLAUDE.md` route and
|
|
13
|
+
`.agent-ops/CLAUDE.md` artifact. Opencode additionally gets
|
|
14
|
+
the agent-ops-owned `.opencode/plugins/agent-ops.js` file; `opencode.json` is
|
|
15
|
+
never modified. The plugin is generated with the installed absolute runtime
|
|
16
|
+
path, so update it through `agent-ops update` rather than editing it manually.
|
|
17
|
+
|
|
18
|
+
At user scope, Codex and opencode keep separate routing files under `.codex/`
|
|
19
|
+
and `.opencode/`; the global opencode plugin is placed under
|
|
20
|
+
`.config/opencode/plugins/`, or under `$XDG_CONFIG_HOME/opencode/plugins/`
|
|
21
|
+
when that variable points inside the managed user root. If OpenCode is
|
|
22
|
+
configured with `$OPENCODE_CONFIG_DIR`, the plugin is placed under its
|
|
23
|
+
`plugins/` directory instead. The installer discovers writable harness
|
|
24
|
+
surfaces and applies the selected target policy; use
|
|
25
|
+
`--hook-target <harness>=<surface-id>` when the managed default is not the
|
|
26
|
+
intended surface. Project-local Claude settings require that explicit target.
|
|
27
|
+
Advisory and guardrail hooks are registered only when the selected profile
|
|
28
|
+
implies them. Advisory runs through the real SessionStart path and is
|
|
29
|
+
fail-open. Claude and Codex lifecycle support is `supported`; OpenCode begins
|
|
30
|
+
at app initialization and is honestly reported as `degraded`.
|
|
31
|
+
|
|
32
|
+
`guardrails` installs command policy but does not enable Stop verification. Stop
|
|
33
|
+
is a separate config-v2 feature and must be explicitly enabled with at least
|
|
34
|
+
one confirmed command:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"features": { "stopVerification": { "enabled": true } },
|
|
39
|
+
"verification": {
|
|
40
|
+
"commands": [
|
|
41
|
+
{
|
|
42
|
+
"id": "unit",
|
|
43
|
+
"command": "npm",
|
|
44
|
+
"args": ["test"],
|
|
45
|
+
"cwd": ".",
|
|
46
|
+
"required": true,
|
|
47
|
+
"evidence": { "kind": "test-count", "minimum": 1 }
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Changing this feature changes native registration. Run:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
agent-ops update
|
|
58
|
+
agent-ops trust grant
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Without `update`, doctor reports `UPDATE_REQUIRED`; without the new trust
|
|
62
|
+
grant, trust-gated hooks remain stale. Stop is report-only: it continues the
|
|
63
|
+
harness for `PASS`, `FAIL`, or `UNKNOWN`, emits only bounded command ID, exit
|
|
64
|
+
code, test-count, config-hash, and timestamp evidence, and never completes a
|
|
65
|
+
task. Config v1 migrates deterministically to v2 with Stop disabled; old
|
|
66
|
+
binaries cannot read the migrated config, and the routing migration is
|
|
67
|
+
one-way after it is applied.
|
|
68
|
+
|
|
69
|
+
To narrow an existing installation, pass the desired list to `agent-ops update
|
|
70
|
+
--harness`; shared paths remain managed while removed harness-owned artifacts,
|
|
71
|
+
markers, and hooks are reconciled.
|
|
72
|
+
|
|
73
|
+
Installations using the previous canonical routing wording are migrated by
|
|
74
|
+
`agent-ops update`. If a managed block was edited, the command fails closed
|
|
75
|
+
until the change is reviewed.
|
|
76
|
+
|
|
77
|
+
Dry-run human and JSON plans omit raw harness settings content. They expose the
|
|
78
|
+
expected hash, content hash, and a safe summary while the internal apply plan
|
|
79
|
+
retains the complete merged settings. The manifest remains schema v2.
|
|
80
|
+
|
|
81
|
+
Use the [acceptance and evidence rules](../spec/acceptance-and-evidence.md)
|
|
82
|
+
when adding verification commands, and the [adapter rules](../spec/harness-adapters.md)
|
|
83
|
+
when configuring Codex, Claude Code, or opencode behavior.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Quickstart
|
|
2
|
+
|
|
3
|
+
1. Define 2–5 acceptance criteria.
|
|
4
|
+
2. Inspect the smallest relevant scope.
|
|
5
|
+
3. Run a failing test before implementation.
|
|
6
|
+
4. Implement the smallest change, then run typecheck and tests.
|
|
7
|
+
5. Record evidence and request independent review before handoff.
|
|
8
|
+
|
|
9
|
+
See the [normative loop](../spec/loop-engineering.md) and [review rules](../spec/review.md).
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
Treat commands, logs, configuration, and external input as untrusted. Redact credentials, block destructive commands by default, and keep local state out of tracked project paths.
|
|
4
|
+
|
|
5
|
+
Read the [guardrails](../spec/guardrails.md), [judgment rules](../spec/judgment.md), and [maintenance rules](../spec/maintenance.md) before changing policy or hook files.
|
package/docs/en/spec/README.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
This is the normative English specification for bounded, evidence-driven work.
|
|
4
4
|
|
|
5
|
+
The harness adapter rules cover Codex, Claude Code, and opencode. The opencode
|
|
6
|
+
integration is a generated local plugin; it does not manage `opencode.json`.
|
|
7
|
+
|
|
8
|
+
Configuration is versioned independently from the manifest. Config v1 migrates
|
|
9
|
+
to config v2 with Stop verification disabled; changing the capability requires
|
|
10
|
+
`agent-ops update` followed by `agent-ops trust grant`. Stop verification is
|
|
11
|
+
explicit, trusted, report-only, and never completes a task. Dry-run plans keep
|
|
12
|
+
foreign settings opaque, and the routing migration is one-way once applied.
|
|
13
|
+
|
|
5
14
|
- [Loop engineering](./loop-engineering.md)
|
|
6
15
|
- [Acceptance and evidence](./acceptance-and-evidence.md)
|
|
7
16
|
- [Judgment](./judgment.md)
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
# Harness Adapters
|
|
2
2
|
|
|
3
|
+
OpenCode plugin behavior in this document was checked against the [official
|
|
4
|
+
plugin documentation](https://opencode.ai/docs/plugins/) and [Bun shell
|
|
5
|
+
documentation](https://bun.sh/docs/runtime/shell) on 2026-07-31. Revalidate:
|
|
6
|
+
when either vendor reference changes.
|
|
7
|
+
|
|
3
8
|
## HARNESS-ADAPTER-001
|
|
4
9
|
|
|
5
|
-
An adapter MUST preserve native harness semantics and MUST
|
|
10
|
+
An adapter MUST preserve native harness semantics and MUST declare each
|
|
11
|
+
capability as supported, degraded, unsupported, or unknown.
|
|
6
12
|
|
|
7
13
|
- Trigger: Mapping portable lifecycle or review behavior into a native harness.
|
|
8
14
|
- Action: Keep ownership narrow, retain user configuration, and document limitations.
|
|
9
|
-
- Evidence: Adapter tests cover existing configuration
|
|
15
|
+
- Evidence: Adapter tests cover existing configuration, support declarations,
|
|
16
|
+
and native failure outcomes.
|
|
10
17
|
- Positive: `A Codex blocking outcome remains UNKNOWN when native denial is unconfirmed.`
|
|
11
18
|
- Negative: `Assume Claude exit semantics apply to Codex.`
|
|
12
19
|
|
|
@@ -19,3 +26,60 @@ An adapter MUST be idempotent and MUST avoid deleting user-owned handlers.
|
|
|
19
26
|
- Evidence: Existing configuration fixtures remain intact after apply and uninstall.
|
|
20
27
|
- Positive: `Managed handler updates while unrelated handlers remain byte-for-byte present.`
|
|
21
28
|
- Negative: `Replace the complete settings file with toolkit defaults.`
|
|
29
|
+
|
|
30
|
+
## HARNESS-ADAPTER-003
|
|
31
|
+
|
|
32
|
+
A file-backed adapter MUST register only hooks implied by the active
|
|
33
|
+
capabilities and MUST track generated source as one whole-file artifact.
|
|
34
|
+
|
|
35
|
+
- Trigger: Installing or probing a harness whose extension point is a plugin file.
|
|
36
|
+
- Action: For opencode, manage `.opencode/plugins/agent-ops.js` in a project or `.config/opencode/plugins/agent-ops.js` at user scope (`$XDG_CONFIG_HOME/opencode/plugins/agent-ops.js` when configured inside the managed user root, or `$OPENCODE_CONFIG_DIR/plugins/agent-ops.js` when that native config directory is configured), leave `opencode.json` untouched, and deduplicate a project `AGENTS.md` contribution by path.
|
|
37
|
+
- Evidence: The manifest contains the plugin hash, the generated source contains only the selected hook registrations, and shared project markers occur once.
|
|
38
|
+
- Positive: `codex,opencode` produces one project AGENTS route and one hashed opencode plugin.
|
|
39
|
+
- Negative: `Add an opencode.json instructions entry or register a plugin for a core-only profile.`
|
|
40
|
+
|
|
41
|
+
## HARNESS-ADAPTER-004
|
|
42
|
+
|
|
43
|
+
The opencode shim MUST invoke the absolute runtime path from the selected
|
|
44
|
+
project directory, MUST fail open for
|
|
45
|
+
advisory events, and MUST fail closed for command-policy events when the
|
|
46
|
+
runtime is unavailable.
|
|
47
|
+
|
|
48
|
+
- Trigger: The generated plugin invokes `agent-ops` or receives an invalid runtime decision.
|
|
49
|
+
- Action: Keep normalization and native output encoding in the runtime adapter,
|
|
50
|
+
throw the policy reason for a deny decision, and run lifecycle-summary through
|
|
51
|
+
the shared advisory implementation. App-scoped plugin initialization remains
|
|
52
|
+
degraded for per-session lifecycle fidelity.
|
|
53
|
+
- Evidence: Shim import tests cover allow, deny, and missing-runtime behavior;
|
|
54
|
+
doctor reports OpenCode lifecycle support as `DEGRADED`.
|
|
55
|
+
- Positive: `A missing runtime does not block SessionStart but blocks a bash tool before execution.`
|
|
56
|
+
- Negative: `Fall back to a PATH-resolved agent-ops executable or claim app initialization is a per-session Stop-equivalent.`
|
|
57
|
+
|
|
58
|
+
## HARNESS-ADAPTER-005
|
|
59
|
+
|
|
60
|
+
Each descriptor MUST expose separate control and runtime adapters. The control
|
|
61
|
+
adapter owns installation planning, routing, ownership, probes, and the
|
|
62
|
+
in-memory capability registration matrix. The runtime adapter owns native input
|
|
63
|
+
decoding, normalized events, native output encoding, and runtime-failure output.
|
|
64
|
+
|
|
65
|
+
- Trigger: Adding a harness surface or a generic capability.
|
|
66
|
+
- Action: Add the capability-to-native registration to the owning harness,
|
|
67
|
+
including its support level and runtime-failure mode; do not add native
|
|
68
|
+
events to a universal union.
|
|
69
|
+
- Evidence: Every declared `supported` registration is exercised through the
|
|
70
|
+
real CLI hook process, and unsupported Stop/lifecycle registrations are not
|
|
71
|
+
reported as enforcement success.
|
|
72
|
+
- Positive: `Claude command-policy reaches a native PreToolUse denial through runHookCommand.`
|
|
73
|
+
- Negative: `Mark SessionStart supported while dispatchHookEvent has no advisory implementation.`
|
|
74
|
+
|
|
75
|
+
The current registration matrix is intentionally asymmetric:
|
|
76
|
+
|
|
77
|
+
| Capability | Codex | Claude Code | OpenCode |
|
|
78
|
+
| --- | --- | --- | --- |
|
|
79
|
+
| lifecycle-summary | supported | supported | degraded |
|
|
80
|
+
| command-policy | unknown | supported | supported |
|
|
81
|
+
| optional-stop-verify | unsupported | supported | degraded |
|
|
82
|
+
|
|
83
|
+
Stop verification is explicit, trusted, report-only, and disabled by default.
|
|
84
|
+
Every Stop result continues the native harness and may carry only bounded
|
|
85
|
+
command evidence; it is never task-completion evidence.
|
|
@@ -19,3 +19,14 @@ Volatile harness facts MUST live in adapter or research documentation and MUST i
|
|
|
19
19
|
- Evidence: The document has a source and `Revalidate:` condition.
|
|
20
20
|
- Positive: `Revalidate: when the vendor hook reference changes.`
|
|
21
21
|
- Negative: `Treat a transient CLI flag as a permanent core rule.`
|
|
22
|
+
|
|
23
|
+
## MAINTAIN-PLUGIN-001
|
|
24
|
+
|
|
25
|
+
Generated harness plugins MUST be treated as managed artifacts and MUST NOT be
|
|
26
|
+
hand-edited in place.
|
|
27
|
+
|
|
28
|
+
- Trigger: Updating the runtime, capabilities, or a vendor plugin contract.
|
|
29
|
+
- Action: Regenerate the opencode shim, verify its manifest hash, and revalidate the vendor behavior before release.
|
|
30
|
+
- Evidence: The artifact hash and shim import tests pass; release documentation records the revalidation condition.
|
|
31
|
+
- Positive: `agent-ops update` rewrites a changed plugin after ownership checks pass.
|
|
32
|
+
- Negative: `Patch the opencode plugin manually and retain the old manifest hash.`
|
package/docs/en/spec/review.md
CHANGED
|
@@ -19,3 +19,14 @@ A review result MUST preserve PASS, FAIL, or NOT_RUN and MUST NOT convert NOT_RU
|
|
|
19
19
|
- Evidence: The result states harness, configured model or limitation, effort, and reason.
|
|
20
20
|
- Positive: `NOT_RUN: login required; prompt is copyable.`
|
|
21
21
|
- Negative: `No reviewer ran, but mark the change PASS.`
|
|
22
|
+
|
|
23
|
+
## REVIEW-HARNESS-001
|
|
24
|
+
|
|
25
|
+
A review invocation MUST resolve to exactly one concrete harness, even when an
|
|
26
|
+
installation supports multiple harnesses.
|
|
27
|
+
|
|
28
|
+
- Trigger: Running `review` with a harness selection.
|
|
29
|
+
- Action: Select one of `codex`, `claude`, or `opencode`; keep multi-harness installation separate from review execution.
|
|
30
|
+
- Evidence: Argument parsing rejects `all`, `both`, and comma-separated multi-harness values for review.
|
|
31
|
+
- Positive: `review --harness opencode` resolves one harness.
|
|
32
|
+
- Negative: `Run one review invocation against every installed harness implicitly.`
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# 設定
|
|
2
|
+
|
|
3
|
+
保持 project 設定明確且分層。請刻意選擇 scope、harness 與 profile;不要從 `--yes` 推論 trust 或安全例外。
|
|
4
|
+
|
|
5
|
+
使用 `--harness all` 選取 Codex、Claude Code 與 opencode,或傳入
|
|
6
|
+
`codex,opencode` 這類逗號分隔的子集。`both` 仍是 legacy Codex 加 Claude
|
|
7
|
+
selection 的 input alias。
|
|
8
|
+
|
|
9
|
+
Project Codex 與 opencode installation 共用 managed supplemental
|
|
10
|
+
`AGENTS.md` routing block 與 `.agent-ops/AGENTS.md` rules artifact。該 block
|
|
11
|
+
只載入 managed baseline,並保留 project-specific instructions 的權威性。
|
|
12
|
+
Claude 使用對應的 `CLAUDE.md` route 與 `.agent-ops/CLAUDE.md` artifact。Opencode 另外取得
|
|
13
|
+
agent-ops 擁有的 `.opencode/plugins/agent-ops.js`;不會修改 `opencode.json`。
|
|
14
|
+
Plugin 使用安裝時的 absolute runtime path 產生,因此請透過
|
|
15
|
+
`agent-ops update` 更新,不要手動編輯。
|
|
16
|
+
|
|
17
|
+
User scope 下,Codex 與 opencode 的 routing file 分別位於 `.codex/` 與
|
|
18
|
+
`.opencode/`;global opencode plugin 預設位於 `.config/opencode/plugins/`,
|
|
19
|
+
若 `$XDG_CONFIG_HOME` 指向 managed user root 內的目錄,則改用
|
|
20
|
+
`$XDG_CONFIG_HOME/opencode/plugins/`。若 OpenCode 設定了
|
|
21
|
+
`$OPENCODE_CONFIG_DIR`,則 plugin 會放在其 `plugins/` 目錄。只有 profile
|
|
22
|
+
有暗示時才會註冊 advisory 與 guardrail hook。Installer 會 discovery 可寫入的
|
|
23
|
+
harness surface 並套用選定的 target policy;若不是 managed default,請使用
|
|
24
|
+
`--hook-target <harness>=<surface-id>` 明確選擇。Project-local Claude settings
|
|
25
|
+
必須明確指定 target。Advisory 會經由真正的 SessionStart path 執行並 fail-open;
|
|
26
|
+
Claude 與 Codex lifecycle support 為 `supported`,OpenCode 從 app initialization
|
|
27
|
+
開始,因此誠實標示為 `degraded`。
|
|
28
|
+
|
|
29
|
+
`guardrails` 只安裝 command policy,不會啟用 Stop verification。Stop 是獨立的
|
|
30
|
+
config v2 feature,必須明確啟用且至少提供一個已確認的 command:
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"features": { "stopVerification": { "enabled": true } },
|
|
35
|
+
"verification": {
|
|
36
|
+
"commands": [
|
|
37
|
+
{
|
|
38
|
+
"id": "unit",
|
|
39
|
+
"command": "npm",
|
|
40
|
+
"args": ["test"],
|
|
41
|
+
"cwd": ".",
|
|
42
|
+
"required": true,
|
|
43
|
+
"evidence": { "kind": "test-count", "minimum": 1 }
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
變更此 feature 會改變 native registration,請依序執行:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
agent-ops update
|
|
54
|
+
agent-ops trust grant
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
未執行 `update` 時,doctor 會回報 `UPDATE_REQUIRED`;未重新 grant trust
|
|
58
|
+
時,trust-gated hook 仍會是 stale。Stop 是 report-only:`PASS`、`FAIL` 與
|
|
59
|
+
`UNKNOWN` 都會讓 harness 繼續,只輸出有界的 command ID、exit code、test-count、
|
|
60
|
+
config-hash 與 timestamp evidence,且永遠不會完成 task。Config v1 會決定性遷移
|
|
61
|
+
為 Stop disabled 的 v2;舊 binary 無法讀取遷移後的 config,routing migration
|
|
62
|
+
一旦套用即為單向,降版前請先閱讀 release notes。
|
|
63
|
+
|
|
64
|
+
若要縮減既有 installation,請將目標清單傳給
|
|
65
|
+
`agent-ops update --harness`;shared path 會繼續受管理,被移除 harness
|
|
66
|
+
擁有的 artifact、marker 與 hook 則會被安全同步。
|
|
67
|
+
|
|
68
|
+
使用舊版 canonical routing wording 的 installation 會由
|
|
69
|
+
`agent-ops update` 遷移;若 managed block 曾被修改,指令會 fail closed,
|
|
70
|
+
直到該變更被檢查。
|
|
71
|
+
|
|
72
|
+
Dry-run 的 human 與 JSON plan 不會輸出原始 harness settings 內容,只提供
|
|
73
|
+
expected hash、content hash 與安全摘要;internal apply plan 仍保留完整合併後的設定。
|
|
74
|
+
Manifest 維持 schema v2。
|
|
75
|
+
|
|
76
|
+
新增驗證命令時使用[驗收與證據規則](../../en/spec/acceptance-and-evidence.md),
|
|
77
|
+
設定 Codex、Claude Code 或 opencode 行為時使用[adapter 規則](../../en/spec/harness-adapters.md)。
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
此目錄是英文規範的繁體中文導讀;規範 rule ID 以英文版本為準。
|
|
4
4
|
|
|
5
|
+
Harness adapter 規則涵蓋 Codex、Claude Code 與 opencode。opencode 整合是
|
|
6
|
+
產生的 local plugin,不管理 `opencode.json`。
|
|
7
|
+
|
|
8
|
+
Configuration 與 manifest 分開版本化。Config v1 會遷移為預設 disabled Stop
|
|
9
|
+
verification 的 config v2;變更 capability 後必須先執行 `agent-ops update`,
|
|
10
|
+
再執行 `agent-ops trust grant`。Stop verification 必須明確啟用、具備 trust、
|
|
11
|
+
為 report-only,且永遠不會完成 task。Dry-run plan 會隱藏 foreign settings
|
|
12
|
+
內容;routing migration 一旦套用即為單向。
|
|
13
|
+
|
|
5
14
|
- [迴圈工程](./loop-engineering.md)
|
|
6
15
|
- [驗收與證據](./acceptance-and-evidence.md)
|
|
7
16
|
- [判斷](./judgment.md)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
# Harness Adapter
|
|
2
2
|
|
|
3
|
-
English source version: 2026-07-
|
|
3
|
+
English source version: 2026-07-31. Revalidate: when the English specification or either vendor reference changes.
|
|
4
|
+
|
|
5
|
+
本文件所述 OpenCode plugin 行為已於 2026-07-31 依據[官方 plugin 文件](https://opencode.ai/docs/plugins/)與[Bun shell 文件](https://bun.sh/docs/runtime/shell)檢查。
|
|
4
6
|
|
|
5
7
|
## HARNESS-ADAPTER-001
|
|
6
8
|
|
|
7
|
-
Adapter MUST 保留原生 harness
|
|
9
|
+
Adapter MUST 保留原生 harness 語意,並為每項 capability 宣告
|
|
10
|
+
supported、degraded、unsupported 或 unknown。
|
|
8
11
|
|
|
9
12
|
- Trigger: 將可攜 lifecycle 或 review 行為映射到原生 harness。
|
|
10
13
|
- Action: 保持 ownership 狹窄、保留使用者設定並記錄限制。
|
|
11
|
-
- Evidence: adapter
|
|
14
|
+
- Evidence: adapter 測試涵蓋既有設定、support 宣告與原生 failure 行為。
|
|
12
15
|
- Positive: `Codex blocking outcome 未確認原生 denial 時保持 UNKNOWN。`
|
|
13
16
|
- Negative: `假設 Claude exit semantics 適用 Codex。`
|
|
14
17
|
|
|
@@ -21,3 +24,54 @@ Adapter MUST 具備冪等性,且 MUST NOT 刪除使用者擁有的 handler。
|
|
|
21
24
|
- Evidence: 既有設定 fixture 在 apply 與 uninstall 後保持完整。
|
|
22
25
|
- Positive: `更新 managed handler,無關 handler 仍逐位元存在。`
|
|
23
26
|
- Negative: `以 toolkit defaults 取代整份 settings。`
|
|
27
|
+
|
|
28
|
+
## HARNESS-ADAPTER-003
|
|
29
|
+
|
|
30
|
+
檔案型 adapter MUST 只註冊 active capabilities 所暗示的 hook,且 MUST 將產生的 source 當成一個 whole-file artifact 管理。
|
|
31
|
+
|
|
32
|
+
- Trigger: 安裝或探測 extension point 是 plugin 檔案的 harness。
|
|
33
|
+
- Action: 對 opencode 在 project 管理 `.opencode/plugins/agent-ops.js`、在 user scope 預設管理 `.config/opencode/plugins/agent-ops.js`(若 `$XDG_CONFIG_HOME` 指向 managed user root 內的目錄,則使用其下的 `opencode/plugins/agent-ops.js`;若設定原生 `$OPENCODE_CONFIG_DIR`,則使用其下的 `plugins/agent-ops.js`),不修改 `opencode.json`;project `AGENTS.md` 的 contribution 依 path 去重。
|
|
34
|
+
- Evidence: manifest 含 plugin hash,產生的 source 只含選定的 hook,shared project marker 只出現一次。
|
|
35
|
+
- Positive: `codex,opencode` 產生一個 project AGENTS route 與一個有 hash 的 opencode plugin。
|
|
36
|
+
- Negative: `新增 opencode.json instructions entry,或在只有 core profile 時註冊 plugin。`
|
|
37
|
+
|
|
38
|
+
## HARNESS-ADAPTER-004
|
|
39
|
+
|
|
40
|
+
OpenCode shim MUST 從選定的 project directory 呼叫 absolute runtime path;runtime 不可用時,MUST 對 advisory event fail open,並對 command-policy event fail closed。
|
|
41
|
+
|
|
42
|
+
- Trigger: 產生的 plugin 呼叫 `agent-ops`,或收到無效的 runtime decision。
|
|
43
|
+
- Action: 將 normalization 與 native output encoding 留在 runtime adapter;deny
|
|
44
|
+
decision 要 throw policy reason;lifecycle-summary 經由 shared advisory
|
|
45
|
+
implementation 執行。Plugin initialization 仍是 app-scoped 而非 per-session,
|
|
46
|
+
因此 per-session lifecycle fidelity 仍為 degraded。
|
|
47
|
+
- Evidence: shim import 測試涵蓋 allow、deny 與 missing-runtime;doctor 對
|
|
48
|
+
OpenCode lifecycle support 回報 `DEGRADED`。
|
|
49
|
+
- Positive: `runtime 不可用時不阻擋 SessionStart,但會在 bash tool 執行前阻擋它。`
|
|
50
|
+
- Negative: `退回 PATH-resolved 的 agent-ops executable,或宣稱 app initialization 等同於 per-session Stop。`
|
|
51
|
+
|
|
52
|
+
## HARNESS-ADAPTER-005
|
|
53
|
+
|
|
54
|
+
每個 descriptor MUST 分離 control 與 runtime adapter。control adapter 負責
|
|
55
|
+
installation plan、routing、ownership、probe 與 in-memory capability
|
|
56
|
+
registration matrix;runtime adapter 負責 native input decode、normalized
|
|
57
|
+
event、native output encode 與 runtime-failure output。
|
|
58
|
+
|
|
59
|
+
- Trigger: 新增 harness surface 或 generic capability。
|
|
60
|
+
- Action: 在所屬 harness 加入 capability-to-native registration,包含 support
|
|
61
|
+
level 與 runtime-failure mode;不得將 native event 加入 universal union。
|
|
62
|
+
- Evidence: 每個宣告為 `supported` 的 registration 都經由真實 CLI hook process
|
|
63
|
+
執行,未支援的 Stop/lifecycle registration 不得回報 enforcement success。
|
|
64
|
+
- Positive: `Claude command-policy 經由 runHookCommand 抵達 native PreToolUse denial。`
|
|
65
|
+
- Negative: `dispatchHookEvent 尚未提供 advisory implementation 卻將 SessionStart 標為 supported。`
|
|
66
|
+
|
|
67
|
+
目前 registration matrix 刻意不對稱:
|
|
68
|
+
|
|
69
|
+
| Capability | Codex | Claude Code | OpenCode |
|
|
70
|
+
| --- | --- | --- | --- |
|
|
71
|
+
| lifecycle-summary | supported | supported | degraded |
|
|
72
|
+
| command-policy | unknown | supported | supported |
|
|
73
|
+
| optional-stop-verify | unsupported | supported | degraded |
|
|
74
|
+
|
|
75
|
+
Stop verification 必須明確啟用、具備 trust、為 report-only 且預設 disabled。
|
|
76
|
+
每個 Stop 結果都會讓 native harness 繼續,最多攜帶有界 command evidence,永遠
|
|
77
|
+
不是 task-completion evidence。
|