@kylecheng3146/agent-ops 0.1.19 → 0.1.20
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 +16 -9
- package/dist/packages/cli/src/bin.js +59 -9
- package/dist/packages/cli/src/cli.js +1 -1
- package/dist/packages/cli/src/commands/init.js +39 -9
- package/dist/packages/cli/src/commands/review.js +3 -0
- package/dist/packages/cli/src/commands/uninstall.js +20 -3
- package/dist/packages/cli/src/ui.js +3 -1
- package/dist/packages/cli/src/wizard.js +12 -6
- package/dist/runtime/src/adapters/agy/config.js +105 -0
- package/dist/runtime/src/adapters/agy/events.js +27 -0
- package/dist/runtime/src/adapters/agy/input.js +30 -0
- package/dist/runtime/src/adapters/agy/output.js +24 -0
- package/dist/runtime/src/adapters/agy/surfaces.js +9 -0
- package/dist/runtime/src/install/codex-loop.js +6 -2
- package/dist/runtime/src/install/doctor.js +15 -0
- package/dist/runtime/src/install/harness.js +52 -1
- package/dist/runtime/src/install/hooks.js +6 -1
- package/dist/runtime/src/install/ownership.js +8 -4
- package/dist/runtime/src/install/plan.js +2 -2
- package/dist/runtime/src/install/probes.js +61 -0
- package/dist/runtime/src/install/surface-inspection.js +10 -1
- package/dist/runtime/src/install/uninstall.js +337 -6
- package/dist/runtime/src/review/execute.js +23 -20
- package/dist/runtime/src/review/extract.js +5 -11
- package/dist/runtime/src/review/render.js +6 -1
- package/dist/runtime/src/review/roles.js +4 -0
- package/dist/runtime/src/review/runner.js +7 -0
- package/dist/runtime/src/schema/validate.js +2 -2
- package/docs/en/guides/configuration.md +22 -6
- package/docs/en/spec/README.md +5 -2
- package/docs/en/spec/harness-adapters.md +20 -9
- package/docs/en/spec/review.md +6 -0
- package/docs/zh-TW/guides/configuration.md +20 -6
- package/docs/zh-TW/spec/README.md +4 -2
- package/docs/zh-TW/spec/harness-adapters.md +18 -10
- package/docs/zh-TW/spec/review.md +5 -0
- package/package.json +2 -2
- package/schemas/manifest.schema.json +2 -2
|
@@ -134,12 +134,16 @@ export function planLoopContribution(options) {
|
|
|
134
134
|
return { artifacts: [], blocks: [] };
|
|
135
135
|
}
|
|
136
136
|
const harnesses = selectedLoopHarnesses(options.harnesses);
|
|
137
|
-
if (options.scope !== "project" ||
|
|
138
|
-
|
|
137
|
+
if (options.scope !== "project" ||
|
|
138
|
+
(harnesses.length === 0 && !options.harnesses.includes("agy"))) {
|
|
139
|
+
throw new AgentOpsError("LOOP_PROFILE_UNSUPPORTED", "The loop profile requires project scope and the agy, Codex, or Claude harness.");
|
|
139
140
|
}
|
|
140
141
|
if (options.hookRuntimePath === undefined) {
|
|
141
142
|
throw new AgentOpsError("LOOP_RUNTIME_REQUIRED", "The loop profile requires the installed hook runtime path.");
|
|
142
143
|
}
|
|
144
|
+
if (harnesses.length === 0) {
|
|
145
|
+
return { artifacts: [], blocks: [] };
|
|
146
|
+
}
|
|
143
147
|
const artifacts = harnesses.flatMap((harness) => [
|
|
144
148
|
{
|
|
145
149
|
id: loopLauncherArtifactId(harness),
|
|
@@ -309,6 +309,14 @@ function checkLifecycleSummary(manifest, config) {
|
|
|
309
309
|
}
|
|
310
310
|
return check("lifecycle-summary", "PASS", "Lifecycle summary is reachable for every selected harness.");
|
|
311
311
|
}
|
|
312
|
+
function checkProjectLoop(manifest, config) {
|
|
313
|
+
if (manifest === undefined || config === undefined || !config.profiles.includes("loop")) {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
return manifest.harness.includes("agy")
|
|
317
|
+
? check("project-loop", "DEGRADED", "agy loop uses only PreInvocation and PreToolUse(run_command); prompt, permission, compact, and subagent events are unavailable.")
|
|
318
|
+
: check("project-loop", "PASS", "Project loop events are fully registered.");
|
|
319
|
+
}
|
|
312
320
|
async function checkSurfaceInventory(root, manifest, config) {
|
|
313
321
|
if (manifest === undefined || config === undefined) {
|
|
314
322
|
return {
|
|
@@ -420,6 +428,13 @@ export async function doctorInstallation(options) {
|
|
|
420
428
|
await checkRegistrationDrift(options.root, manifest.manifest, config.config),
|
|
421
429
|
await checkProbe("hook-registration", options.probes?.hookRegistration),
|
|
422
430
|
checkLifecycleSummary(manifest.manifest, config.config),
|
|
431
|
+
...(() => {
|
|
432
|
+
const projectLoop = checkProjectLoop(manifest.manifest, config.config);
|
|
433
|
+
return projectLoop === undefined ? [] : [projectLoop];
|
|
434
|
+
})(),
|
|
435
|
+
...(manifest.manifest?.harness.includes("agy") === true
|
|
436
|
+
? [await checkProbe("agy-runtime", options.probes?.agyRuntime)]
|
|
437
|
+
: []),
|
|
423
438
|
await checkProbe("repository-trust", options.probes?.repositoryTrust),
|
|
424
439
|
await checkProbe("smoke-availability", options.probes?.smokeAvailability),
|
|
425
440
|
await checkReviewTargets(config.config, options.probes?.reviewTarget, options.checkReviewTargetAuth === true)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { buildAgyHookSettings, isAgyHookRegistered, isAgyManagedHook, mergeAgyHooks, stripAgyHooks } from "../adapters/agy/config.js";
|
|
2
|
+
import { AGY_CAPABILITY_REGISTRATIONS } from "../adapters/agy/events.js";
|
|
3
|
+
import { normalizeAgyHookInput } from "../adapters/agy/input.js";
|
|
4
|
+
import { agyHookOutput } from "../adapters/agy/output.js";
|
|
5
|
+
import { agySurfaces } from "../adapters/agy/surfaces.js";
|
|
1
6
|
import { buildClaudeHookSettings, isClaudeManagedHandler, mergeClaudeSettings, stripClaudeManagedHooks } from "../adapters/claude/config.js";
|
|
2
7
|
import { CLAUDE_CAPABILITY_REGISTRATIONS } from "../adapters/claude/events.js";
|
|
3
8
|
import { normalizeClaudeHookInput } from "../adapters/claude/input.js";
|
|
@@ -16,6 +21,7 @@ import { opencodeSurfaces } from "../adapters/opencode/surfaces.js";
|
|
|
16
21
|
import { AgentOpsError } from "../fs/paths.js";
|
|
17
22
|
import { findSurfaceById, findSurfaceByPath, isWritableSurface } from "./surfaces.js";
|
|
18
23
|
export const HARNESS_IDS = [
|
|
24
|
+
"agy",
|
|
19
25
|
"codex",
|
|
20
26
|
"claude",
|
|
21
27
|
"opencode"
|
|
@@ -108,7 +114,52 @@ const CLAUDE_ROUTING = {
|
|
|
108
114
|
"## Loop Engineering\n\nUse `.agent-ops/CLAUDE.md` as the canonical Loop Engineering specification for this project.\n"
|
|
109
115
|
]
|
|
110
116
|
};
|
|
117
|
+
const AGY_ROUTING = {
|
|
118
|
+
desired: "## Loop Engineering\n\nLoad `.agent-ops/GEMINI.md` as the agent-ops managed baseline.\nProject-specific instructions in this file remain authoritative.\n",
|
|
119
|
+
legacy: []
|
|
120
|
+
};
|
|
111
121
|
const DESCRIPTORS = {
|
|
122
|
+
agy: {
|
|
123
|
+
id: "agy",
|
|
124
|
+
control: {
|
|
125
|
+
instructionFile: "GEMINI.md",
|
|
126
|
+
routing: AGY_ROUTING,
|
|
127
|
+
hookPath: ".agents/hooks.json",
|
|
128
|
+
hookPathForScope: (scope) => scope === "project" ? ".agents/hooks.json" : ".gemini/config/hooks.json",
|
|
129
|
+
surfaces: agySurfaces,
|
|
130
|
+
ownSettingsKeys: [],
|
|
131
|
+
buildHooks: buildAgyHookSettings,
|
|
132
|
+
mergeHooks: mergeAgyHooks,
|
|
133
|
+
stripHooks: stripAgyHooks,
|
|
134
|
+
isManagedHandler: isAgyManagedHook,
|
|
135
|
+
registrations: AGY_CAPABILITY_REGISTRATIONS,
|
|
136
|
+
hookRegistered: (source, capabilities) => isAgyHookRegistered(parseJsonSource(source), capabilities),
|
|
137
|
+
plan: async (context) => {
|
|
138
|
+
if (context.scope === "project") {
|
|
139
|
+
return await planCommonHarnessContribution("codex", context);
|
|
140
|
+
}
|
|
141
|
+
const descriptor = DESCRIPTORS.agy;
|
|
142
|
+
return {
|
|
143
|
+
artifacts: [{
|
|
144
|
+
id: "gemini-rules",
|
|
145
|
+
path: ".agent-ops/GEMINI.md",
|
|
146
|
+
content: managedRules(descriptor, context)
|
|
147
|
+
}],
|
|
148
|
+
blocks: [{
|
|
149
|
+
id: "agy-routing",
|
|
150
|
+
path: ".gemini/GEMINI.md",
|
|
151
|
+
version: 1,
|
|
152
|
+
content: AGY_ROUTING.desired
|
|
153
|
+
}]
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
runtime: {
|
|
158
|
+
normalizeInput: normalizeAgyHookInput,
|
|
159
|
+
formatOutput: agyHookOutput,
|
|
160
|
+
formatRuntimeFailure: (event, capability, remedy) => agyHookOutput(event, runtimeFailureResult(capability, AGY_CAPABILITY_REGISTRATIONS, remedy))
|
|
161
|
+
}
|
|
162
|
+
},
|
|
112
163
|
codex: createJsonDescriptor({
|
|
113
164
|
id: "codex",
|
|
114
165
|
instructionFile: "AGENTS.md",
|
|
@@ -244,7 +295,7 @@ export function managedRules(descriptor, context) {
|
|
|
244
295
|
""
|
|
245
296
|
];
|
|
246
297
|
if (context.capabilities.includes("rules")) {
|
|
247
|
-
lines.push("For every change:", "", "1. Define two to five mechanically verifiable acceptance criteria.", "2. Inspect the smallest relevant scope and preserve unrelated changes.", "3. Apply the smallest safe change.", "4. Run evidence-producing verification for every criterion.", "5. Obtain independent review before claiming completion, via", " `agent-ops review --yes` (or the CLI's equivalent invocation). Never call a", " review-target CLI (agy, codex, claude) directly — direct calls skip", " the enforced read-only sandbox flags and can hang or fail on command", " permission prompts.", "", "Treat `.agent-ops/config.json` as verifier authority. Discovery output is", "only a proposal until a user confirms it. Repository commands require an", "exact matching trust record. Confirmed project init/update grants it", "automatically when verification commands are configured.", "");
|
|
298
|
+
lines.push("For every change:", "", "1. Define two to five mechanically verifiable acceptance criteria.", "2. Inspect the smallest relevant scope and preserve unrelated changes.", "3. Apply the smallest safe change.", "4. Run evidence-producing verification for every criterion.", "5. Obtain independent review before claiming completion, via", " `agent-ops review --yes` (or the CLI's equivalent invocation). Never call a", " review-target CLI (agy, codex, claude) directly — direct calls skip", " the enforced read-only sandbox flags and can hang or fail on command", " permission prompts.", " Set `AGENT_OPS_HOST` to the current CLI id when invoking review so", " agent-ops tries a different CLI first and uses isolated self-review", " only when no other configured reviewer is usable.", "", "Treat `.agent-ops/config.json` as verifier authority. Discovery output is", "only a proposal until a user confirms it. Repository commands require an", "exact matching trust record. Confirmed project init/update grants it", "automatically when verification commands are configured.", "");
|
|
248
299
|
}
|
|
249
300
|
if (context.capabilities.includes("task")) {
|
|
250
301
|
lines.push("Split a change that exceeds five acceptance criteria into subtasks:", "`agent-ops task create --parent <task-id>` records one, and", "`agent-ops task status --parent <task-id>` lists them. Each subtask", "carries its own criteria, verification, and independent review;", "completing one never completes its parent.", "");
|
|
@@ -35,7 +35,9 @@ export function planHookRegistration(options) {
|
|
|
35
35
|
}
|
|
36
36
|
const path = options.path ?? hookRegistrationPath(options.harness, options.scope);
|
|
37
37
|
const managed = descriptor.control.buildHooks(options.capabilities, options.runtimePath, options.platform);
|
|
38
|
-
const events = Object.keys(managed.hooks)
|
|
38
|
+
const events = Object.keys(managed.hooks).map((event) => options.harness === "agy" && event === "PreInvocation"
|
|
39
|
+
? "SessionStart"
|
|
40
|
+
: event);
|
|
39
41
|
if (events.length === 0) {
|
|
40
42
|
return null;
|
|
41
43
|
}
|
|
@@ -54,6 +56,9 @@ export function planHookRegistration(options) {
|
|
|
54
56
|
};
|
|
55
57
|
}
|
|
56
58
|
function onlyManagedRemains(harness, value) {
|
|
59
|
+
if (harness === "agy") {
|
|
60
|
+
return Object.keys(value).length === 0;
|
|
61
|
+
}
|
|
57
62
|
const ownKeys = new Set(harnessDescriptor(harness).control.ownSettingsKeys ?? []);
|
|
58
63
|
const hooks = value.hooks;
|
|
59
64
|
return (Object.keys(value).every((key) => ownKeys.has(key)) &&
|
|
@@ -4,13 +4,15 @@ import { harnessDescriptor, harnessHookPath, routingBlockId, selectHarnessHookSu
|
|
|
4
4
|
import { isOpencodePluginPath } from "../adapters/opencode/config.js";
|
|
5
5
|
import { LOOP_MARKER_ID, LOOP_MARKER_VERSION, loopIgnoreContent, loopLauncherArtifactId, loopWindowsLauncherArtifactId, loopWindowsLauncherPath, loopLauncherPath, selectedLoopHarnesses } from "./codex-loop.js";
|
|
6
6
|
function expectedMarker(manifest, id, markerId) {
|
|
7
|
-
const descriptor = harnessDescriptor(id);
|
|
7
|
+
const descriptor = harnessDescriptor(id === "agy" && manifest.scope === "project" ? "codex" : id);
|
|
8
8
|
const markers = managedBlockMarkers(markerId, 1, "html");
|
|
9
9
|
return {
|
|
10
10
|
id: markerId,
|
|
11
11
|
path: manifest.scope === "project"
|
|
12
12
|
? descriptor.control.instructionFile
|
|
13
|
-
:
|
|
13
|
+
: id === "agy"
|
|
14
|
+
? ".gemini/GEMINI.md"
|
|
15
|
+
: `.${id}/${descriptor.control.instructionFile}`,
|
|
14
16
|
startMarker: markers.start,
|
|
15
17
|
endMarker: markers.end,
|
|
16
18
|
markerStyle: "html",
|
|
@@ -96,7 +98,7 @@ export function assertSupportedManifestOwnership(manifest, root) {
|
|
|
96
98
|
throw manifestOwnershipError();
|
|
97
99
|
}
|
|
98
100
|
for (const id of harnesses) {
|
|
99
|
-
const descriptor = harnessDescriptor(id);
|
|
101
|
+
const descriptor = harnessDescriptor(id === "agy" && manifest.scope === "project" ? "codex" : id);
|
|
100
102
|
const artifactPath = `.agent-ops/${descriptor.control.instructionFile}`;
|
|
101
103
|
const artifactKey = pathKey(artifactPath);
|
|
102
104
|
const artifactEntry = expectedArtifactPaths.get(artifactKey);
|
|
@@ -112,7 +114,9 @@ export function assertSupportedManifestOwnership(manifest, root) {
|
|
|
112
114
|
requiredArtifactPaths.add(artifactKey);
|
|
113
115
|
const markerPath = manifest.scope === "project"
|
|
114
116
|
? descriptor.control.instructionFile
|
|
115
|
-
:
|
|
117
|
+
: id === "agy"
|
|
118
|
+
? ".gemini/GEMINI.md"
|
|
119
|
+
: `.${id}/${descriptor.control.instructionFile}`;
|
|
116
120
|
const markerKey = pathKey(markerPath);
|
|
117
121
|
expectedMarkerPaths.add(markerKey);
|
|
118
122
|
const currentId = routingBlockId(id, manifest.scope, descriptor);
|
|
@@ -280,8 +280,8 @@ function assertLoopProfileSupport(scope, harness, capabilities) {
|
|
|
280
280
|
return;
|
|
281
281
|
}
|
|
282
282
|
if (scope !== "project" ||
|
|
283
|
-
!harness.some((id) => id === "codex" || id === "claude")) {
|
|
284
|
-
throw new AgentOpsError("LOOP_PROFILE_UNSUPPORTED", "The loop profile requires project scope and the Codex or Claude harness.");
|
|
283
|
+
!harness.some((id) => id === "agy" || id === "codex" || id === "claude")) {
|
|
284
|
+
throw new AgentOpsError("LOOP_PROFILE_UNSUPPORTED", "The loop profile requires project scope and the agy, Codex, or Claude harness.");
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
async function assertCodexLoopConfiguration(root, harness, capabilities) {
|
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
import { harnessDescriptor } from "./harness.js";
|
|
2
2
|
import { resolveCapabilities } from "./profiles.js";
|
|
3
|
+
const MINIMUM_AGY_VERSION = [1, 1, 12];
|
|
4
|
+
export function agyVersionSupported(versionOutput) {
|
|
5
|
+
const match = /\b(\d+)\.(\d+)\.(\d+)\b/u.exec(versionOutput);
|
|
6
|
+
const version = match?.slice(1).map(Number);
|
|
7
|
+
return version !== undefined && !version.some((part, index) => part < MINIMUM_AGY_VERSION[index] &&
|
|
8
|
+
version.slice(0, index).every((prior, priorIndex) => prior === MINIMUM_AGY_VERSION[priorIndex]));
|
|
9
|
+
}
|
|
10
|
+
export function agyRuntimeStatus(versionOutput, hooksOutput, expectedEvents = []) {
|
|
11
|
+
const match = /\b(\d+)\.(\d+)\.(\d+)\b/u.exec(versionOutput);
|
|
12
|
+
if (!agyVersionSupported(versionOutput)) {
|
|
13
|
+
return {
|
|
14
|
+
status: "FAIL",
|
|
15
|
+
message: "agy 1.1.12 or newer is required.",
|
|
16
|
+
remediation: "Update agy, then run `agent-ops doctor` again."
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const parsed = JSON.parse(hooksOutput);
|
|
21
|
+
const hooks = parsed.command?.data?.hooks;
|
|
22
|
+
const loaded = Array.isArray(hooks) && hooks.some((hook) => {
|
|
23
|
+
if (typeof hook !== "object" || hook === null || Array.isArray(hook))
|
|
24
|
+
return false;
|
|
25
|
+
const value = hook;
|
|
26
|
+
const actions = value.actions;
|
|
27
|
+
if (!(value.name === "agent-ops" &&
|
|
28
|
+
value.enabled === true &&
|
|
29
|
+
Array.isArray(actions) &&
|
|
30
|
+
actions.length > 0 &&
|
|
31
|
+
actions.every((action) => typeof action === "object" && action !== null && !Array.isArray(action))))
|
|
32
|
+
return false;
|
|
33
|
+
return expectedEvents.every((expected) => actions.some((action) => {
|
|
34
|
+
const nativeEvent = expected === "SessionStart" ? "PreInvocation" : expected;
|
|
35
|
+
return (typeof action === "object" && action !== null && !Array.isArray(action) &&
|
|
36
|
+
action.event === nativeEvent &&
|
|
37
|
+
typeof action.command === "string" &&
|
|
38
|
+
action.command.endsWith(` agy ${expected} --managed-by=agent-ops`));
|
|
39
|
+
}));
|
|
40
|
+
});
|
|
41
|
+
if (!Array.isArray(hooks) || (expectedEvents.length > 0 && !loaded)) {
|
|
42
|
+
return {
|
|
43
|
+
status: "FAIL",
|
|
44
|
+
message: "agy is installed, but its loaded hook list does not include agent-ops.",
|
|
45
|
+
code: "UPDATE_REQUIRED",
|
|
46
|
+
remediation: "Run `agent-ops update`, restart agy, then run doctor again."
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
status: "PASS",
|
|
51
|
+
message: expectedEvents.length > 0
|
|
52
|
+
? `agy ${match?.[0]} loaded the agent-ops hook.`
|
|
53
|
+
: `agy ${match?.[0]} meets the minimum supported version.`
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return {
|
|
58
|
+
status: "FAIL",
|
|
59
|
+
message: "agy returned an unreadable /hooks response.",
|
|
60
|
+
remediation: "Run `agy -p \"/hooks\" --output-format json` and inspect the result."
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
3
64
|
/**
|
|
4
65
|
* Returns the harness ids missing an agent-ops owned handler for the hook
|
|
5
66
|
* events implied by the installed profiles. Empty when installations without
|
|
@@ -90,6 +90,9 @@ function managedJsonCount(source, isManagedHandler) {
|
|
|
90
90
|
}
|
|
91
91
|
return jsonHandlerCounts(source, isManagedHandler)?.managed ?? 0;
|
|
92
92
|
}
|
|
93
|
+
function desiredHookEvents(harness, events) {
|
|
94
|
+
return events.map((event) => harness === "agy" && event === "PreInvocation" ? "SessionStart" : event);
|
|
95
|
+
}
|
|
93
96
|
export async function inspectHarnessRegistrations(options) {
|
|
94
97
|
const capabilities = desiredCapabilities(options.config);
|
|
95
98
|
const statuses = [];
|
|
@@ -100,7 +103,7 @@ export async function inspectHarnessRegistrations(options) {
|
|
|
100
103
|
const recordedEvents = hookRecord?.events ?? [];
|
|
101
104
|
const desiredEvents = control.buildHooks === undefined
|
|
102
105
|
? []
|
|
103
|
-
: Object.keys(control.buildHooks(capabilities, "probe").hooks);
|
|
106
|
+
: desiredHookEvents(harness, Object.keys(control.buildHooks(capabilities, "probe").hooks));
|
|
104
107
|
if (control.buildHooks !== undefined) {
|
|
105
108
|
const surfaces = harnessSurfaces(harness, options.manifest.scope, options.root);
|
|
106
109
|
const writableJsonSurfaces = surfaces.filter((surface) => isWritableSurface(surface) && surface.representation === "json");
|
|
@@ -186,6 +189,12 @@ function jsonHandlerCounts(source, isManagedHandler) {
|
|
|
186
189
|
if (!isRecord(parsed)) {
|
|
187
190
|
return null;
|
|
188
191
|
}
|
|
192
|
+
if (isManagedHandler?.(parsed) === true) {
|
|
193
|
+
return {
|
|
194
|
+
managed: 1,
|
|
195
|
+
foreign: Math.max(0, Object.keys(parsed).length - 1)
|
|
196
|
+
};
|
|
197
|
+
}
|
|
189
198
|
const hooks = parsed.hooks;
|
|
190
199
|
if (hooks === undefined) {
|
|
191
200
|
return { managed: 0, foreign: 0 };
|