@oisincoveney/pipeline 3.12.3 → 3.12.4

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 CHANGED
@@ -45,10 +45,10 @@ moka init
45
45
 
46
46
  `moka init` installs or refreshes the whole per-machine harness in one step:
47
47
  the package's default skills, generated host command surfaces, the singleton
48
- `pipeline-gateway` MCP entry, copied hook files from the private
49
- `oisin-ee/agent-hooks` repository, and global instruction files. OpenCode is the
50
- package default runtime. The command does not create repo-local `.pipeline`
51
- config files.
48
+ `pipeline-gateway` MCP entry, copied hook files from private
49
+ `oisin-ee/agent/hooks`, and global instruction files from `oisin-ee/agent/rules`.
50
+ OpenCode is the package default runtime. The command does not create repo-local
51
+ `.pipeline` config files.
52
52
 
53
53
  The default MCP gateway can run locally or point at the hosted Momokaya gateway.
54
54
  Set `PIPELINE_MCP_GATEWAY_AUTHORIZATION` to the full HTTP `Authorization` header
@@ -59,7 +59,7 @@ export PIPELINE_MCP_GATEWAY_AUTHORIZATION="Basic $(printf '%s' 'user:password' |
59
59
  ```
60
60
 
61
61
  Verify the generated harness (commands, hooks, rules) is current after package
62
- upgrades or edits to `oisin-ee/agent-hooks`, without writing anything:
62
+ upgrades or edits to `oisin-ee/agent`, without writing anything:
63
63
 
64
64
  ```shell
65
65
  moka init --check
@@ -0,0 +1,7 @@
1
+ //#region src/agent-assets.ts
2
+ const AGENT_ASSET_SOURCE = "oisin-ee/agent";
3
+ const AGENT_SKILL_SOURCE = "oisin-ee/agent/skills";
4
+ const AGENT_HOOKS_DIR = "hooks";
5
+ const AGENT_RULES_DIR = "rules";
6
+ //#endregion
7
+ export { AGENT_ASSET_SOURCE, AGENT_HOOKS_DIR, AGENT_RULES_DIR, AGENT_SKILL_SOURCE };
@@ -61,7 +61,7 @@ function missingFileReferenceWarning(path, value) {
61
61
  }
62
62
  function missingFileReferenceMessage(path, value) {
63
63
  const base = `${path} references missing file '${value}'`;
64
- if (path.startsWith("skills.") && value.startsWith(".agents/skills/")) return `${base}. Run \`moka init\` to install project-local skills with \`npx --yes skills add oisin-ee/skills\`.`;
64
+ if (path.startsWith("skills.") && value.startsWith(".agents/skills/")) return `${base}. Run \`moka init\` to install project-local skills with \`npx --yes skills add oisin-ee/agent/skills\`.`;
65
65
  return base;
66
66
  }
67
67
  function resolveLintPathReference(projectRoot, ref) {
@@ -233,8 +233,8 @@ declare const configSchema: z.ZodObject<{
233
233
  policy: z.ZodOptional<z.ZodObject<{
234
234
  commands: z.ZodOptional<z.ZodEnum<{
235
235
  allow: "allow";
236
- deny: "deny";
237
236
  "trusted-only": "trusted-only";
237
+ deny: "deny";
238
238
  }>>;
239
239
  modules: z.ZodOptional<z.ZodEnum<{
240
240
  allow: "allow";
@@ -262,8 +262,8 @@ declare const configSchema: z.ZodObject<{
262
262
  global: "global";
263
263
  }>>;
264
264
  mode: z.ZodEnum<{
265
- local: "local";
266
265
  hosted: "hosted";
266
+ local: "local";
267
267
  }>;
268
268
  provider: z.ZodLiteral<"toolhive">;
269
269
  authorization_env: z.ZodDefault<z.ZodString>;
@@ -306,10 +306,10 @@ declare const configSchema: z.ZodObject<{
306
306
  }, z.core.$strict>>;
307
307
  output: z.ZodOptional<z.ZodObject<{
308
308
  format: z.ZodEnum<{
309
- json_schema: "json_schema";
310
309
  text: "text";
311
310
  json: "json";
312
311
  jsonl: "jsonl";
312
+ json_schema: "json_schema";
313
313
  }>;
314
314
  repair: z.ZodOptional<z.ZodObject<{
315
315
  enabled: z.ZodOptional<z.ZodBoolean>;
@@ -385,10 +385,10 @@ declare const configSchema: z.ZodObject<{
385
385
  disabled: "disabled";
386
386
  }>>>;
387
387
  output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
388
- json_schema: "json_schema";
389
388
  text: "text";
390
389
  json: "json";
391
390
  jsonl: "jsonl";
391
+ json_schema: "json_schema";
392
392
  }>>>;
393
393
  rules: z.ZodOptional<z.ZodBoolean>;
394
394
  skills: z.ZodOptional<z.ZodBoolean>;
@@ -1,4 +1,5 @@
1
1
  import { applyJsonEdit, ensureTrailingNewline, parseJsonRecord } from "./json-config-merge.js";
2
+ import { AGENT_ASSET_SOURCE, AGENT_HOOKS_DIR } from "./agent-assets.js";
2
3
  import { resolveHarnessTarget } from "./install-commands/shared.js";
3
4
  import { existsSync, readFileSync, statSync } from "node:fs";
4
5
  import { tmpdir } from "node:os";
@@ -7,7 +8,7 @@ import { execa } from "execa";
7
8
  import { createHash } from "node:crypto";
8
9
  import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
9
10
  //#region src/install-hooks.ts
10
- const DEFAULT_HOOK_INSTALL_SOURCE = "oisin-ee/agent-hooks";
11
+ const DEFAULT_HOOK_INSTALL_SOURCE = AGENT_ASSET_SOURCE;
11
12
  const HOOK_HOSTS = [
12
13
  "claude-code",
13
14
  "codex",
@@ -19,6 +20,7 @@ const HOST_TARGET_ROOT = {
19
20
  codex: ".codex",
20
21
  opencode: ".opencode"
21
22
  };
23
+ const NON_HOOK_OWNED_TARGETS = new Set([".opencode/opencode.json"]);
22
24
  function hashContent(content) {
23
25
  return createHash("sha256").update(content).digest("hex");
24
26
  }
@@ -63,8 +65,8 @@ async function cloneHookRepository(targetDir) {
63
65
  ], { stdio: "inherit" });
64
66
  }
65
67
  async function withHookSource(useSource) {
66
- const parent = await mkdtemp(join(tmpdir(), "moka-agent-hooks-"));
67
- const source = join(parent, "agent-hooks");
68
+ const parent = await mkdtemp(join(tmpdir(), "moka-agent-"));
69
+ const source = join(parent, "agent");
68
70
  try {
69
71
  await cloneHookRepository(source);
70
72
  return await useSource(source);
@@ -86,17 +88,17 @@ async function listFiles(root) {
86
88
  }
87
89
  async function sourceHookFiles(source) {
88
90
  return (await Promise.all(HOOK_HOSTS.map(async (host) => {
89
- const hostRoot = join(source, host);
90
- return (await listFiles(hostRoot)).map((file) => {
91
+ const hostRoot = join(source, AGENT_HOOKS_DIR, host);
92
+ return (await listFiles(hostRoot)).flatMap((file) => {
91
93
  const relativePath = relative(hostRoot, file).replaceAll("\\", "/");
92
94
  const content = readFileSync(file);
93
95
  const path = `${HOST_TARGET_ROOT[host]}/${relativePath}`;
94
- return {
96
+ return isHookOwnedTarget(path) ? [{
95
97
  content,
96
98
  hash: targetIdentityHash(path, content),
97
99
  host,
98
100
  path
99
- };
101
+ }] : [];
100
102
  });
101
103
  }))).flat().sort((a, b) => a.path.localeCompare(b.path));
102
104
  }
@@ -110,6 +112,9 @@ function emptyManifest() {
110
112
  version: 1
111
113
  };
112
114
  }
115
+ function isHookOwnedTarget(path) {
116
+ return !NON_HOOK_OWNED_TARGETS.has(path);
117
+ }
113
118
  function readManifest(host) {
114
119
  const path = manifestPath(host);
115
120
  if (!existsSync(path)) return emptyManifest();
@@ -153,18 +158,22 @@ function planFiles(files, force, manifests) {
153
158
  function planObsoleteFiles(desiredPaths, force, manifests) {
154
159
  const obsolete = [];
155
160
  for (const [host, manifest] of manifests) for (const [path, entry] of Object.entries(manifest.files)) {
156
- if (desiredPaths.has(path)) continue;
157
- const target = targetPath(path);
158
- if (!existsSync(target)) continue;
159
- const currentHash = hashContent(readFileSync(target));
160
- obsolete.push({
161
- action: force || currentHash === entry.hash ? "delete" : "conflict",
162
- host,
163
- path
164
- });
161
+ const planned = planObsoleteFile(host, path, entry, force, desiredPaths);
162
+ if (planned) obsolete.push(planned);
165
163
  }
166
164
  return obsolete.sort((a, b) => a.path.localeCompare(b.path));
167
165
  }
166
+ function planObsoleteFile(host, path, entry, force, desiredPaths) {
167
+ if (desiredPaths.has(path) || !isHookOwnedTarget(path)) return;
168
+ const target = targetPath(path);
169
+ if (!existsSync(target)) return;
170
+ const currentHash = hashContent(readFileSync(target));
171
+ return {
172
+ action: force || currentHash === entry.hash ? "delete" : "conflict",
173
+ host,
174
+ path
175
+ };
176
+ }
168
177
  async function writePlannedFile(file) {
169
178
  if (file.action === "conflict" || file.action === "unchanged") return;
170
179
  const target = targetPath(file.path);
@@ -1,9 +1,10 @@
1
+ import { AGENT_ASSET_SOURCE, AGENT_RULES_DIR } from "./agent-assets.js";
1
2
  import { homedir, tmpdir } from "node:os";
2
3
  import { join } from "node:path";
3
4
  import { execa } from "execa";
4
5
  import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
6
  //#region src/install-rules.ts
6
- const DEFAULT_RULES_INSTALL_SOURCE = "oisin-ee/rules";
7
+ const DEFAULT_RULES_INSTALL_SOURCE = AGENT_ASSET_SOURCE;
7
8
  const RULESYNC_PACKAGE = "rulesync@8.30.1";
8
9
  const RULESYNC_TARGETS = [
9
10
  "claudecode",
@@ -23,8 +24,8 @@ async function cloneRulesRepository(targetDir) {
23
24
  }
24
25
  async function withRulesSource(sourceOverride, useSource) {
25
26
  if (sourceOverride !== void 0) return useSource(sourceOverride);
26
- const parent = await mkdtemp(join(tmpdir(), "moka-rules-"));
27
- const source = join(parent, "rules");
27
+ const parent = await mkdtemp(join(tmpdir(), "moka-agent-rules-"));
28
+ const source = join(parent, "agent");
28
29
  try {
29
30
  await cloneRulesRepository(source);
30
31
  return await useSource(source);
@@ -52,7 +53,7 @@ async function defaultRulesyncRunner(args, opts) {
52
53
  }
53
54
  }
54
55
  async function buildRootRule(source) {
55
- const rulesDir = join(source, "rules");
56
+ const rulesDir = join(source, AGENT_RULES_DIR);
56
57
  let entries = [];
57
58
  try {
58
59
  entries = (await readdir(rulesDir, { withFileTypes: true })).filter((d) => d.isFile() && d.name.endsWith(".md")).map((d) => d.name).sort((a, b) => a.localeCompare(b));
@@ -5,13 +5,13 @@ import { z } from "zod";
5
5
  //#region src/moka-submit.d.ts
6
6
  declare const mokaSubmitDirectHooksSchema: z.ZodRecord<z.ZodEnum<{
7
7
  "workflow.start": "workflow.start";
8
- "node.finish": "node.finish";
9
- "node.start": "node.start";
10
8
  "workflow.success": "workflow.success";
11
9
  "workflow.failure": "workflow.failure";
12
10
  "workflow.complete": "workflow.complete";
11
+ "node.start": "node.start";
13
12
  "node.success": "node.success";
14
13
  "node.error": "node.error";
14
+ "node.finish": "node.finish";
15
15
  "gate.failure": "gate.failure";
16
16
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
17
17
  failure: z.ZodDefault<z.ZodEnum<{
@@ -98,13 +98,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
98
98
  }, z.core.$strict>>;
99
99
  hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
100
100
  "workflow.start": "workflow.start";
101
- "node.finish": "node.finish";
102
- "node.start": "node.start";
103
101
  "workflow.success": "workflow.success";
104
102
  "workflow.failure": "workflow.failure";
105
103
  "workflow.complete": "workflow.complete";
104
+ "node.start": "node.start";
106
105
  "node.success": "node.success";
107
106
  "node.error": "node.error";
107
+ "node.finish": "node.finish";
108
108
  "gate.failure": "gate.failure";
109
109
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
110
110
  failure: z.ZodDefault<z.ZodEnum<{
@@ -216,13 +216,13 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
216
216
  }, z.core.$strict>>;
217
217
  hooks: z.ZodOptional<z.ZodRecord<z.ZodEnum<{
218
218
  "workflow.start": "workflow.start";
219
- "node.finish": "node.finish";
220
- "node.start": "node.start";
221
219
  "workflow.success": "workflow.success";
222
220
  "workflow.failure": "workflow.failure";
223
221
  "workflow.complete": "workflow.complete";
222
+ "node.start": "node.start";
224
223
  "node.success": "node.success";
225
224
  "node.error": "node.error";
225
+ "node.finish": "node.finish";
226
226
  "gate.failure": "gate.failure";
227
227
  }> & z.core.$partial, z.ZodDiscriminatedUnion<[z.ZodObject<{
228
228
  failure: z.ZodDefault<z.ZodEnum<{
@@ -1,9 +1,10 @@
1
+ import { AGENT_SKILL_SOURCE } from "./agent-assets.js";
1
2
  import { installCommands } from "./install-commands.js";
2
3
  import { installHooks } from "./install-hooks.js";
3
4
  import { installRules } from "./install-rules.js";
4
5
  import { execa } from "execa";
5
6
  //#region src/pipeline-init.ts
6
- const DEFAULT_SKILL_INSTALL_SOURCE = "oisin-ee/skills";
7
+ const DEFAULT_SKILL_INSTALL_SOURCE = AGENT_SKILL_SOURCE;
7
8
  const SKILL_INSTALL_AGENT_ARGS = [
8
9
  "--agent",
9
10
  "opencode",
@@ -84,7 +85,7 @@ function formatPipelineInitResult(result, mode = {}) {
84
85
  const copy = INIT_RESULT_COPY[initResultMode(mode)];
85
86
  return [
86
87
  copy.headline,
87
- "per-machine harness globally (user/global skills + ~/.claude, ~/.config/opencode, ~/.codex); global instruction files generated via rulesync from oisin-ee/rules; inherited by every repo with no per-repo copy",
88
+ "per-machine harness globally (user/global skills + ~/.claude, ~/.config/opencode, ~/.codex); global instruction files generated via rulesync from oisin-ee/agent/rules; inherited by every repo with no per-repo copy",
88
89
  ...result.files.map((path) => `${copy.fileVerb} ${path}`),
89
90
  copy.footer
90
91
  ].join("\n");
@@ -11,8 +11,8 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
11
11
  runId: z.ZodString;
12
12
  sequence: z.ZodNumber;
13
13
  type: z.ZodEnum<{
14
- "workflow.planned": "workflow.planned";
15
14
  "workflow.start": "workflow.start";
15
+ "workflow.planned": "workflow.planned";
16
16
  }>;
17
17
  workflowPlan: z.ZodObject<{
18
18
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -58,10 +58,10 @@ declare const runnerEventRecordSchema: z.ZodUnion<readonly [z.ZodObject<{
58
58
  }>;
59
59
  }, z.core.$strip>;
60
60
  type: z.ZodEnum<{
61
+ "node.start": "node.start";
62
+ "node.finish": "node.finish";
61
63
  "agent.finish": "agent.finish";
62
64
  "agent.start": "agent.start";
63
- "node.finish": "node.finish";
64
- "node.start": "node.start";
65
65
  }>;
66
66
  }, z.core.$strip>, z.ZodObject<{
67
67
  at: z.ZodOptional<z.ZodString>;
@@ -256,8 +256,8 @@ declare const runnerEventBatchSchema: z.ZodObject<{
256
256
  runId: z.ZodString;
257
257
  sequence: z.ZodNumber;
258
258
  type: z.ZodEnum<{
259
- "workflow.planned": "workflow.planned";
260
259
  "workflow.start": "workflow.start";
260
+ "workflow.planned": "workflow.planned";
261
261
  }>;
262
262
  workflowPlan: z.ZodObject<{
263
263
  edges: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -303,10 +303,10 @@ declare const runnerEventBatchSchema: z.ZodObject<{
303
303
  }>;
304
304
  }, z.core.$strip>;
305
305
  type: z.ZodEnum<{
306
+ "node.start": "node.start";
307
+ "node.finish": "node.finish";
306
308
  "agent.finish": "agent.finish";
307
309
  "agent.start": "agent.start";
308
- "node.finish": "node.finish";
309
- "node.start": "node.start";
310
310
  }>;
311
311
  }, z.core.$strip>, z.ZodObject<{
312
312
  at: z.ZodOptional<z.ZodString>;
@@ -29,14 +29,10 @@ function executeOpencodeSession(deps, plan, options) {
29
29
  function boundByAgentTimeout(plan) {
30
30
  return (effect) => {
31
31
  const timeoutMs = plan.timeoutMs;
32
- process.stderr.write(`[agent-timeout] wired nodeId=${plan.nodeId} timeoutMs=${String(timeoutMs)}\n`);
33
32
  if (!timeoutMs || timeoutMs <= 0) return effect;
34
33
  return Effect.timeoutFail(Effect.disconnect(effect), {
35
34
  duration: Duration.millis(timeoutMs),
36
- onTimeout: () => {
37
- process.stderr.write(`[agent-timeout] FIRED nodeId=${plan.nodeId} after ${timeoutMs}ms\n`);
38
- return /* @__PURE__ */ new Error(`agent session timed out after ${timeoutMs}ms`);
39
- }
35
+ onTimeout: () => /* @__PURE__ */ new Error(`agent session timed out after ${timeoutMs}ms`)
40
36
  });
41
37
  };
42
38
  }
@@ -152,7 +152,7 @@ receive explicit grants:
152
152
  - `output`: text, JSON, JSONL, or JSON Schema output.
153
153
 
154
154
  Default skills resolve from project-installed skill files created by
155
- `moka init` via `npx --yes skills add oisin-ee/skills`:
155
+ `moka init` via `npx --yes skills add oisin-ee/agent/skills`:
156
156
 
157
157
  ```yaml
158
158
  skills:
@@ -164,14 +164,13 @@ Project-authored skill and rule paths resolve from the project root and must
164
164
  exist for runtime use. If default skill files are missing, run `moka init` to
165
165
  install them before executing workflows.
166
166
 
167
- Default agent hooks are copied by `moka init` from the private
168
- `oisin-ee/agent-hooks` repository. That source repository has one canonical
169
- host-level layout:
167
+ Default agent hooks are copied by `moka init` from private `oisin-ee/agent`.
168
+ That source repository has one canonical hook layout:
170
169
 
171
170
  ```text
172
- claude-code/
173
- codex/
174
- opencode/
171
+ hooks/claude-code/
172
+ hooks/codex/
173
+ hooks/opencode/
175
174
  ```
176
175
 
177
176
  Moka overlays those folders onto `.claude`, `.codex`, and `.opencode` for
@@ -215,7 +214,7 @@ OpenCode host resources are generated from the same profile registry:
215
214
  - `.opencode/skills/*/SKILL.md` is installed by `skills add`; Moka only
216
215
  generates agents, commands, plugins, and project config.
217
216
  - Additional manually authored OpenCode hook plugins can be copied from
218
- `oisin-ee/agent-hooks/opencode/` by `moka init`.
217
+ `oisin-ee/agent/hooks/opencode/` by `moka init`.
219
218
  - `.opencode/plugins/pipeline-goal-context.ts` projects package-owned
220
219
  continuation context into OpenCode compaction.
221
220
  - `.opencode/opencode.json` contains the gateway MCP config, enables LSP, and
@@ -128,3 +128,45 @@ The package-owned MCP inventory exposed through the ecosystem manifest includes
128
128
  Backlog, GitHub, and Neon. Repo-scoped backends must bind to
129
129
  `PIPELINE_TARGET_PATH` or the current workspace path supplied by the gateway
130
130
  configuration.
131
+
132
+ ## Browser automation backend (Steel)
133
+
134
+ The `Playwright` backend's tools (`playwright_browser_*`) are served by a
135
+ self-hosted **Steel Browser** (Chromium) pool, not a browser launched inside the
136
+ MCP pod. Microsoft's `@playwright/mcp` connects to Steel over CDP, so the tool
137
+ surface is unchanged — agents keep calling the same `playwright_browser_*` tools
138
+ through `pipeline-gateway`.
139
+
140
+ Topology (infra repo, `k8s/charts/pipeline-mcp-gateway`):
141
+
142
+ - A StatefulSet of N backend pods (`playwright.backendReplicas`, default 3). Each
143
+ pod is `mcp` (`@playwright/mcp`) + a private `steel` sidecar (its own Chrome on
144
+ `localhost:3000`) + an `auth-seed` native sidecar. One pod = one isolated,
145
+ verify-bot-authenticated browser.
146
+ - Auth: the seed runs a real headless Zitadel login and POSTs the session into
147
+ the pod's Steel (`POST /v1/sessions`); the pod stays NotReady until the first
148
+ seed lands (fail-closed — an unauthenticated browser is never served) and
149
+ re-seeds every ~3 days, inside the oauth2-proxy 7-day cookie window.
150
+
151
+ Usage:
152
+
153
+ - **One authenticated browser (default).** Call `playwright_browser_*` through
154
+ `pipeline-gateway` (`https://pipeline-mcp.momokaya.ee/mcp/`). You get a single,
155
+ pre-authenticated browser. After a gateway backend restart the vMCP client
156
+ session can drop — reconnect the MCP client (do not bounce pods).
157
+ - **N concurrent isolated browsers.** The single gateway/proxy endpoint does
158
+ **not** auto-distribute sessions across the pool — toolhive pins every session
159
+ to one backend pod (Redis session storage and scaling proxy replicas do not
160
+ change this). To use the pool concurrently, address the backend pods directly:
161
+ each pod's `@playwright/mcp` listens on port `8931` and is a full
162
+ `playwright_browser_*` endpoint (`http://<pod-ip-or-headless-dns>:8931/mcp`).
163
+ Proven: 3 concurrent per-pod sessions, each on a distinct authenticated
164
+ browser.
165
+ - Scale the pool with `playwright.backendReplicas`.
166
+
167
+ Operational notes: Steel runs as root in-pod (its bundled nginx requires it,
168
+ otherwise `nginx [emerg] chown(/var/lib/nginx/body) Operation not permitted`);
169
+ health is `GET /v1/health`; on ARM nodes set `SKIP_FINGERPRINT_INJECTION=true`;
170
+ CDP over a service-DNS host needs `--cdp-header "Host: localhost"` (Chrome's
171
+ anti-DNS-rebinding check), but the in-pod `localhost:3000` path needs no header.
172
+ See infra `INFRA-074`.
@@ -177,8 +177,8 @@ OpenBao, publish Secret values, or mutate ESO resources from this package.
177
177
 
178
178
  Installs or refreshes the whole per-machine harness in one command: the
179
179
  package's default skills, generated host-native command surfaces and MCP
180
- entries, copied agent hooks from the private `oisin-ee/agent-hooks` repository,
181
- and global instruction files generated via rulesync from `oisin-ee/rules`.
180
+ entries, copied agent hooks from private `oisin-ee/agent/hooks`, and global
181
+ instruction files generated via rulesync from `oisin-ee/agent/rules`.
182
182
  OpenCode is the package default runtime. The harness is always installed
183
183
  globally (`~/.claude`, `~/.config/opencode`, `~/.codex`); there is no `--scope`.
184
184
  `moka init` does not create repo-local `.pipeline` config files.
@@ -192,12 +192,10 @@ moka init --force # overwrite manually edited harness files
192
192
 
193
193
  `--check` and `--dry-run` write nothing and skip the network skill install.
194
194
  By default `moka init` refuses to overwrite manually edited hook or command
195
- files; `--force` overwrites them. For agent hooks, Moka clones
196
- `oisin-ee/agent-hooks`, copies files, and tracks installed hashes so later runs
197
- update unchanged owned files, delete removed owned files, and (without `--force`)
198
- refuse to clobber manual edits. The hook source repository has only host folders
199
- (`claude-code/`, `codex/`, `opencode/`); there is no source override flag and no
200
- symlink mode.
195
+ files; `--force` overwrites them. For agent hooks, Moka clones `oisin-ee/agent`,
196
+ copies files from `hooks/<host>`, and tracks installed hashes so later runs update
197
+ unchanged owned files, delete removed owned files, and (without `--force`) refuse
198
+ to clobber manual edits. There is no source override flag and no symlink mode.
201
199
 
202
200
  Use `PIPELINE_TARGET_PATH=/path/to/repo` when invoking `moka` from outside the
203
201
  target worktree.
@@ -384,9 +382,9 @@ Claude Code: /moka-quick, /moka-execute, /moka-inspect
384
382
  - `.opencode/opencode.json` with LSP, the singleton `pipeline-gateway` MCP
385
383
  server, and pinned package-selected plugins
386
384
 
387
- `moka init` also copies hook files from `oisin-ee/agent-hooks` by overlaying
388
- `opencode/`, `claude-code/`, and `codex/` onto the host config roots. Hook files
389
- are authored in the hook repo, not generated by Moka.
385
+ `moka init` also copies hook files from `oisin-ee/agent/hooks` by overlaying
386
+ `hooks/opencode/`, `hooks/claude-code/`, and `hooks/codex/` onto the host config
387
+ roots. Hook files are authored in the agent asset repo, not generated by Moka.
390
388
 
391
389
  For Claude Code, `moka init` generates `.claude/commands/moka-<entrypoint>.md`
392
390
  slash commands.
package/package.json CHANGED
@@ -128,7 +128,7 @@
128
128
  "prepack": "bun run build:cli"
129
129
  },
130
130
  "type": "module",
131
- "version": "3.12.3",
131
+ "version": "3.12.4",
132
132
  "description": "Config-driven multi-agent pipeline runner for repository work",
133
133
  "main": "./dist/index.js",
134
134
  "types": "./dist/index.d.ts",