@kylecheng3146/agent-ops 0.1.3 → 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.
Files changed (80) hide show
  1. package/README.md +88 -13
  2. package/dist/packages/cli/src/args.js +39 -6
  3. package/dist/packages/cli/src/bin.js +33 -11
  4. package/dist/packages/cli/src/cli.js +8 -8
  5. package/dist/packages/cli/src/commands/doctor.js +31 -9
  6. package/dist/packages/cli/src/commands/hook.js +18 -16
  7. package/dist/packages/cli/src/commands/init.js +29 -7
  8. package/dist/packages/cli/src/commands/review.js +5 -1
  9. package/dist/packages/cli/src/commands/uninstall.js +6 -5
  10. package/dist/packages/cli/src/commands/update.js +13 -5
  11. package/dist/packages/cli/src/context.js +9 -3
  12. package/dist/packages/cli/src/hook-process.js +109 -7
  13. package/dist/packages/cli/src/plan-output.js +9 -4
  14. package/dist/packages/cli/src/public-plan.js +62 -0
  15. package/dist/packages/cli/src/ui.js +242 -1
  16. package/dist/packages/cli/src/version.js +1 -1
  17. package/dist/packages/cli/src/wizard.js +68 -2
  18. package/dist/runtime/src/adapters/claude/config.js +0 -8
  19. package/dist/runtime/src/adapters/claude/events.js +26 -0
  20. package/dist/runtime/src/adapters/claude/output.js +6 -6
  21. package/dist/runtime/src/adapters/claude/surfaces.js +70 -0
  22. package/dist/runtime/src/adapters/codex/config.js +29 -11
  23. package/dist/runtime/src/adapters/codex/events.js +26 -0
  24. package/dist/runtime/src/adapters/codex/output.js +10 -0
  25. package/dist/runtime/src/adapters/codex/surfaces.js +12 -0
  26. package/dist/runtime/src/adapters/opencode/config.js +170 -0
  27. package/dist/runtime/src/adapters/opencode/events.js +49 -0
  28. package/dist/runtime/src/adapters/opencode/input.js +32 -0
  29. package/dist/runtime/src/adapters/opencode/output.js +23 -0
  30. package/dist/runtime/src/adapters/opencode/surfaces.js +23 -0
  31. package/dist/runtime/src/config/explain.js +7 -0
  32. package/dist/runtime/src/config/hash.js +24 -0
  33. package/dist/runtime/src/config/merge.js +6 -2
  34. package/dist/runtime/src/config/migrate.js +19 -4
  35. package/dist/runtime/src/contracts.js +10 -1
  36. package/dist/runtime/src/fs/manifest.js +32 -1
  37. package/dist/runtime/src/fs/transaction.js +14 -2
  38. package/dist/runtime/src/hooks/advisory.js +16 -0
  39. package/dist/runtime/src/hooks/stop-service.js +70 -0
  40. package/dist/runtime/src/hooks/stop-verify.js +4 -1
  41. package/dist/runtime/src/install/doctor.js +119 -9
  42. package/dist/runtime/src/install/harness.js +296 -35
  43. package/dist/runtime/src/install/hooks.js +22 -17
  44. package/dist/runtime/src/install/ownership.js +110 -34
  45. package/dist/runtime/src/install/plan.js +207 -28
  46. package/dist/runtime/src/install/probes.js +9 -43
  47. package/dist/runtime/src/install/profiles.js +8 -1
  48. package/dist/runtime/src/install/surface-inspection.js +296 -0
  49. package/dist/runtime/src/install/surfaces.js +11 -0
  50. package/dist/runtime/src/install/uninstall.js +10 -3
  51. package/dist/runtime/src/install/update.js +8 -2
  52. package/dist/runtime/src/schema/validate.js +37 -18
  53. package/dist/runtime/src/task/service.js +3 -3
  54. package/dist/runtime/src/task/store.js +12 -3
  55. package/dist/runtime/src/verify/command-executor.js +113 -0
  56. package/dist/runtime/src/verify/evidence.js +4 -24
  57. package/dist/runtime/src/verify/service.js +20 -92
  58. package/dist/runtime/src/verify/spawn.js +6 -1
  59. package/docs/en/guides/configuration.md +83 -0
  60. package/docs/en/guides/quickstart.md +9 -0
  61. package/docs/en/guides/security.md +5 -0
  62. package/docs/en/spec/README.md +9 -0
  63. package/docs/en/spec/harness-adapters.md +66 -2
  64. package/docs/en/spec/maintenance.md +11 -0
  65. package/docs/en/spec/review.md +11 -0
  66. package/docs/zh-TW/guides/configuration.md +77 -0
  67. package/docs/zh-TW/guides/quickstart.md +9 -0
  68. package/docs/zh-TW/guides/security.md +5 -0
  69. package/docs/zh-TW/spec/README.md +9 -0
  70. package/docs/zh-TW/spec/harness-adapters.md +57 -3
  71. package/docs/zh-TW/spec/maintenance.md +11 -1
  72. package/docs/zh-TW/spec/review.md +10 -0
  73. package/package.json +8 -4
  74. package/postinstall.cjs +102 -0
  75. package/schemas/config.schema.json +55 -1
  76. package/schemas/manifest.schema.json +7 -2
  77. package/templates/common/AGENTS.block.md +2 -1
  78. package/templates/common/CLAUDE.block.md +2 -1
  79. package/dist/runtime/src/review/claude-runner.js +0 -4
  80. package/dist/runtime/src/review/codex-runner.js +0 -4
@@ -1,32 +1,12 @@
1
1
  import { join } from "node:path";
2
- import { SCHEMA_VERSION } from "../contracts.js";
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: SCHEMA_VERSION,
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 spawned = this.#options.trusted
124
- ? await runVerificationCommand(command, {
125
- cwd: await this.#commandCwd(command),
126
- runner: this.#options.processRunner
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: classified.failureClass,
147
- exitCategory: spawned.timedOut
74
+ failureClass: result.failureClass,
75
+ exitCategory: result.timedOut
148
76
  ? "timeout"
149
- : spawned.signal !== null
77
+ : result.signal !== null
150
78
  ? "signal-exit"
151
- : spawned.exitCode === null
79
+ : result.exitCode === null
152
80
  ? "no-exit"
153
- : spawned.exitCode === 0
81
+ : result.exitCode === 0
154
82
  ? "exit-zero"
155
83
  : "nonzero-exit",
156
- diagnostics: diagnostics(spawned)
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, classified, spawned.exitCode);
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: classified.status,
164
- failureClass: classified.failureClass,
165
- exitCode: spawned.exitCode,
166
- timedOut: spawned.timedOut,
167
- testCount: classified.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 = overallStatus(results);
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.
@@ -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 label unsupported behavior as UNKNOWN.
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 and unsupported outcomes.
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.`
@@ -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)。
@@ -0,0 +1,9 @@
1
+ # 快速開始
2
+
3
+ 1. 定義 2–5 個驗收條件。
4
+ 2. 檢查最小相關範圍。
5
+ 3. 實作前先讓測試失敗。
6
+ 4. 做最小變更,再執行 typecheck 與測試。
7
+ 5. 記錄證據,交接前請求獨立審查。
8
+
9
+ 請參考[規範迴圈](../../en/spec/loop-engineering.md)與[審查規則](../../en/spec/review.md)。
@@ -0,0 +1,5 @@
1
+ # 安全
2
+
3
+ 將命令、log、設定與外部輸入視為不可信。遮罩憑證、預設阻擋破壞性命令,並讓本機狀態留在 tracked project path 之外。
4
+
5
+ 修改政策或 hook 前,閱讀[防護規則](../../en/spec/guardrails.md)、[判斷規則](../../en/spec/judgment.md)與[維護規則](../../en/spec/maintenance.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-23. Revalidate: when the English specification changes.
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 語意,並將未支援行為標示為 UNKNOWN。
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。