@oisincoveney/pipeline 1.27.18 → 1.28.0
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 +1 -1
- package/dist/argo-workflow.js +6 -0
- package/dist/codex-auth-sync.js +98 -0
- package/dist/config.d.ts +9 -9
- package/dist/config.js +1 -1
- package/dist/index.js +10 -0
- package/dist/install-commands.js +12 -6
- package/dist/moka-submit.d.ts +1 -1
- package/dist/run-state/git-refs.js +74 -11
- package/dist/runner-command/run.js +162 -7
- package/dist/runner-command-contract.d.ts +2 -2
- package/dist/schedule-planner.js +22 -2
- package/docs/operator-guide.md +2 -2
- package/docs/slash-command-adapter-contract.md +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -241,7 +241,7 @@ runner.
|
|
|
241
241
|
|
|
242
242
|
| Host | Generated files | Invocation |
|
|
243
243
|
| --- | --- | --- |
|
|
244
|
-
| OpenCode | `.opencode/commands
|
|
244
|
+
| OpenCode | `.opencode/commands/moka-<entrypoint>.md`, `.opencode/agents/*.md`, `.opencode/plugins/*.ts`, `.opencode/opencode.json` | `/moka-quick <task>`, `/moka-execute <task>`, `/moka-inspect <task>` |
|
|
245
245
|
|
|
246
246
|
The installer is idempotent, supports `--check` and `--dry-run`, and refuses to
|
|
247
247
|
overwrite manually edited files unless `--force` is supplied.
|
package/dist/argo-workflow.js
CHANGED
|
@@ -11,6 +11,10 @@ const RUNNER_WORKFLOW_ENTRYPOINT = "pipeline";
|
|
|
11
11
|
const RUNNER_WORKFLOW_PAYLOAD_PATH = "/etc/pipeline/payload.json";
|
|
12
12
|
const RUNNER_WORKFLOW_SCHEDULE_PATH = "/etc/pipeline/schedule.yaml";
|
|
13
13
|
const RUNNER_GIT_CREDENTIALS_PATH = "/etc/pipeline/git-credentials";
|
|
14
|
+
const RUNNER_OPENCODE_ENV = [{
|
|
15
|
+
name: "CODEX_AUTH_PER_PROJECT_ACCOUNTS",
|
|
16
|
+
value: "0"
|
|
17
|
+
}];
|
|
14
18
|
const kubernetesNameSchema = z.string().min(1);
|
|
15
19
|
const labelValueSchema = z.string().min(1);
|
|
16
20
|
const stringMapSchema = z.record(z.string().min(1), z.string().min(1));
|
|
@@ -346,6 +350,7 @@ function runnerCommandTemplate(task, options, volumeMounts) {
|
|
|
346
350
|
RUNNER_WORKFLOW_SCHEDULE_PATH
|
|
347
351
|
],
|
|
348
352
|
command: ["moka"],
|
|
353
|
+
env: [...RUNNER_OPENCODE_ENV],
|
|
349
354
|
image: options.image,
|
|
350
355
|
imagePullPolicy: options.imagePullPolicy,
|
|
351
356
|
name: "runner",
|
|
@@ -368,6 +373,7 @@ function runnerFinalizerTemplate(options, volumeMounts) {
|
|
|
368
373
|
"{{workflow.status}}"
|
|
369
374
|
],
|
|
370
375
|
command: ["moka"],
|
|
376
|
+
env: [...RUNNER_OPENCODE_ENV],
|
|
371
377
|
image: options.image,
|
|
372
378
|
imagePullPolicy: options.imagePullPolicy,
|
|
373
379
|
name: "runner",
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { parse } from "jsonc-parser";
|
|
6
|
+
//#region src/codex-auth-sync.ts
|
|
7
|
+
const CODEX_MULTI_AUTH_PLUGIN = "oc-codex-multi-auth";
|
|
8
|
+
const GLOBAL_CODEX_AUTH_CONFIG_PATH = join(homedir(), ".opencode/openai-codex-auth-config.json");
|
|
9
|
+
function syncLocalCodexAuth(options) {
|
|
10
|
+
const items = [syncGlobalPluginConfig(options.globalConfigPath ?? GLOBAL_CODEX_AUTH_CONFIG_PATH, options), ...discoverGitRepositories(options.root).map((repo) => syncProjectOpenCodeConfig(repo, options))];
|
|
11
|
+
const hasRequiredChanges = items.some((item) => item.action === "create" || item.action === "update");
|
|
12
|
+
const hasErrors = items.some((item) => item.action === "error");
|
|
13
|
+
const checkFailed = options.check === true && hasRequiredChanges;
|
|
14
|
+
return {
|
|
15
|
+
items,
|
|
16
|
+
ok: !(hasErrors || checkFailed)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function formatCodexAuthSyncResult(result) {
|
|
20
|
+
const lines = result.items.map((item) => {
|
|
21
|
+
const suffix = item.message ? `: ${item.message}` : "";
|
|
22
|
+
return `${item.action} ${item.path}${suffix}`;
|
|
23
|
+
});
|
|
24
|
+
if (!result.ok) lines.push("codex-auth sync-local check failed");
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
function syncGlobalPluginConfig(path, options) {
|
|
28
|
+
const currentText = existsSync(path) ? readFileSync(path, "utf8") : void 0;
|
|
29
|
+
const parsed = parseJsonObject(currentText ?? "{}");
|
|
30
|
+
if (!parsed.ok) return {
|
|
31
|
+
action: "error",
|
|
32
|
+
message: formatParseErrors(parsed.errors),
|
|
33
|
+
path
|
|
34
|
+
};
|
|
35
|
+
return writeIfChanged(path, currentText, `${JSON.stringify({
|
|
36
|
+
...parsed.value,
|
|
37
|
+
perProjectAccounts: false
|
|
38
|
+
}, null, 2)}\n`, options);
|
|
39
|
+
}
|
|
40
|
+
function syncProjectOpenCodeConfig(repo, options) {
|
|
41
|
+
const path = join(repo, ".opencode/opencode.json");
|
|
42
|
+
const currentText = existsSync(path) ? readFileSync(path, "utf8") : void 0;
|
|
43
|
+
const merged = mergeOpenCodeProjectConfig(currentText, { plugin: [CODEX_MULTI_AUTH_PLUGIN] });
|
|
44
|
+
if (!merged.ok) return {
|
|
45
|
+
action: "error",
|
|
46
|
+
message: formatParseErrors(merged.errors),
|
|
47
|
+
path
|
|
48
|
+
};
|
|
49
|
+
return writeIfChanged(path, currentText, merged.content, options);
|
|
50
|
+
}
|
|
51
|
+
function writeIfChanged(path, currentText, nextText, options) {
|
|
52
|
+
if (currentText === nextText) return {
|
|
53
|
+
action: "unchanged",
|
|
54
|
+
path
|
|
55
|
+
};
|
|
56
|
+
const action = currentText === void 0 ? "create" : "update";
|
|
57
|
+
if (!(options.check || options.dryRun)) {
|
|
58
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
59
|
+
writeFileSync(path, nextText);
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
action,
|
|
63
|
+
path
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function discoverGitRepositories(root) {
|
|
67
|
+
return readdirSync(root).map((name) => join(root, name)).filter((path) => isDirectory(path) && existsSync(join(path, ".git"))).sort((a, b) => a.localeCompare(b));
|
|
68
|
+
}
|
|
69
|
+
function isDirectory(path) {
|
|
70
|
+
try {
|
|
71
|
+
return statSync(path).isDirectory();
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function parseJsonObject(content) {
|
|
77
|
+
const errors = [];
|
|
78
|
+
const value = parse(content, errors, {
|
|
79
|
+
allowTrailingComma: true,
|
|
80
|
+
disallowComments: false
|
|
81
|
+
});
|
|
82
|
+
if (errors.length > 0 || !isRecord(value)) return {
|
|
83
|
+
errors,
|
|
84
|
+
ok: false
|
|
85
|
+
};
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
value
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function formatParseErrors(errors) {
|
|
92
|
+
return errors.length > 0 ? `invalid JSONC (${errors.length} parse error${errors.length === 1 ? "" : "s"})` : "expected a JSON object";
|
|
93
|
+
}
|
|
94
|
+
function isRecord(value) {
|
|
95
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { formatCodexAuthSyncResult, syncLocalCodexAuth };
|
package/dist/config.d.ts
CHANGED
|
@@ -362,8 +362,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
362
362
|
policy: z.ZodOptional<z.ZodObject<{
|
|
363
363
|
commands: z.ZodOptional<z.ZodEnum<{
|
|
364
364
|
allow: "allow";
|
|
365
|
-
deny: "deny";
|
|
366
365
|
"trusted-only": "trusted-only";
|
|
366
|
+
deny: "deny";
|
|
367
367
|
}>>;
|
|
368
368
|
modules: z.ZodOptional<z.ZodEnum<{
|
|
369
369
|
allow: "allow";
|
|
@@ -387,8 +387,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
387
387
|
}, z.core.$strict>>>;
|
|
388
388
|
default_profile: z.ZodOptional<z.ZodString>;
|
|
389
389
|
mode: z.ZodEnum<{
|
|
390
|
-
local: "local";
|
|
391
390
|
hosted: "hosted";
|
|
391
|
+
local: "local";
|
|
392
392
|
}>;
|
|
393
393
|
provider: z.ZodLiteral<"toolhive">;
|
|
394
394
|
authorization_env: z.ZodDefault<z.ZodString>;
|
|
@@ -431,10 +431,10 @@ declare const configSchema: z.ZodObject<{
|
|
|
431
431
|
}, z.core.$strict>>;
|
|
432
432
|
output: z.ZodOptional<z.ZodObject<{
|
|
433
433
|
format: z.ZodEnum<{
|
|
434
|
-
json_schema: "json_schema";
|
|
435
434
|
text: "text";
|
|
436
435
|
json: "json";
|
|
437
436
|
jsonl: "jsonl";
|
|
437
|
+
json_schema: "json_schema";
|
|
438
438
|
}>;
|
|
439
439
|
repair: z.ZodOptional<z.ZodObject<{
|
|
440
440
|
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -452,7 +452,6 @@ declare const configSchema: z.ZodObject<{
|
|
|
452
452
|
skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
453
453
|
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
454
454
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
455
|
-
task: "task";
|
|
456
455
|
read: "read";
|
|
457
456
|
list: "list";
|
|
458
457
|
grep: "grep";
|
|
@@ -460,6 +459,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
460
459
|
bash: "bash";
|
|
461
460
|
edit: "edit";
|
|
462
461
|
write: "write";
|
|
462
|
+
task: "task";
|
|
463
463
|
}>>>;
|
|
464
464
|
}, z.core.$strict>>>;
|
|
465
465
|
runner_command: z.ZodDefault<z.ZodObject<{
|
|
@@ -485,8 +485,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
485
485
|
rules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
486
486
|
path: z.ZodString;
|
|
487
487
|
source_root: z.ZodDefault<z.ZodEnum<{
|
|
488
|
-
project: "project";
|
|
489
488
|
package: "package";
|
|
489
|
+
project: "project";
|
|
490
490
|
}>>;
|
|
491
491
|
}, z.core.$strict>>>;
|
|
492
492
|
runners: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
@@ -503,15 +503,14 @@ declare const configSchema: z.ZodObject<{
|
|
|
503
503
|
disabled: "disabled";
|
|
504
504
|
}>>>;
|
|
505
505
|
output_formats: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
506
|
-
json_schema: "json_schema";
|
|
507
506
|
text: "text";
|
|
508
507
|
json: "json";
|
|
509
508
|
jsonl: "jsonl";
|
|
509
|
+
json_schema: "json_schema";
|
|
510
510
|
}>>>;
|
|
511
511
|
rules: z.ZodOptional<z.ZodBoolean>;
|
|
512
512
|
skills: z.ZodOptional<z.ZodBoolean>;
|
|
513
513
|
tools: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
514
|
-
task: "task";
|
|
515
514
|
read: "read";
|
|
516
515
|
list: "list";
|
|
517
516
|
grep: "grep";
|
|
@@ -519,6 +518,7 @@ declare const configSchema: z.ZodObject<{
|
|
|
519
518
|
bash: "bash";
|
|
520
519
|
edit: "edit";
|
|
521
520
|
write: "write";
|
|
521
|
+
task: "task";
|
|
522
522
|
}>>>;
|
|
523
523
|
}, z.core.$strict>;
|
|
524
524
|
command: z.ZodOptional<z.ZodString>;
|
|
@@ -613,8 +613,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
613
613
|
schedules: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
614
614
|
description: z.ZodOptional<z.ZodString>;
|
|
615
615
|
baseline: z.ZodEnum<{
|
|
616
|
-
quick: "quick";
|
|
617
616
|
execute: "execute";
|
|
617
|
+
quick: "quick";
|
|
618
618
|
}>;
|
|
619
619
|
max_parallel_nodes: z.ZodOptional<z.ZodNumber>;
|
|
620
620
|
node_catalog: z.ZodOptional<z.ZodString>;
|
|
@@ -626,8 +626,8 @@ declare const configSchema: z.ZodObject<{
|
|
|
626
626
|
skills: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
627
627
|
path: z.ZodString;
|
|
628
628
|
source_root: z.ZodDefault<z.ZodEnum<{
|
|
629
|
-
project: "project";
|
|
630
629
|
package: "package";
|
|
630
|
+
project: "project";
|
|
631
631
|
}>>;
|
|
632
632
|
}, z.core.$strict>>>;
|
|
633
633
|
task_context: z.ZodOptional<z.ZodObject<{
|
package/dist/config.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { PipelineConfigError, loadPipelineConfig } from "./config.js";
|
|
|
4
4
|
import { createOrchestratorLaunchPlan, createRunnerLaunchPlan } from "./runner.js";
|
|
5
5
|
import { compileWorkflowPlan } from "./workflow-planner.js";
|
|
6
6
|
import { compileScheduleArtifact, generateScheduleArtifact, parseScheduleArtifact } from "./schedule-planner.js";
|
|
7
|
+
import { formatCodexAuthSyncResult, syncLocalCodexAuth } from "./codex-auth-sync.js";
|
|
7
8
|
import { BUILTIN_PIPE_COMMANDS, registerConfiguredEntrypointCommands } from "./commands/pipeline-command.js";
|
|
8
9
|
import { configureGatewayHosts, localGatewayStatus, reconcileGateway, renderGatewayConfig, runGatewayDoctor, startLocalGateway } from "./mcp/gateway.js";
|
|
9
10
|
import { resolvePackageAssetPath } from "./package-assets.js";
|
|
@@ -289,6 +290,15 @@ function createCliProgram() {
|
|
|
289
290
|
});
|
|
290
291
|
console.log(formatInstallCommandsResult(result));
|
|
291
292
|
});
|
|
293
|
+
program.command("codex-auth").description("Manage local Codex multi-auth integration").command("sync-local").description("Use one local oc-codex account pool and declare the plugin in dev repos").option("--root <path>", "directory containing repositories to sync").option("--dry-run", "show planned changes without writing files").option("--check", "fail if local Codex auth config is not synced").action((flags) => {
|
|
294
|
+
const result = syncLocalCodexAuth({
|
|
295
|
+
check: flags.check,
|
|
296
|
+
dryRun: flags.dryRun,
|
|
297
|
+
root: resolve(flags.root ?? process.env.PIPELINE_TARGET_PATH ?? process.cwd())
|
|
298
|
+
});
|
|
299
|
+
console.log(formatCodexAuthSyncResult(result));
|
|
300
|
+
if (!result.ok) process.exitCode = 1;
|
|
301
|
+
});
|
|
292
302
|
addMokaSubmitOptions(program.command("submit").description("Submit work to Momokaya as an Argo Workflow").argument("[input...]", "task description, or command argv with --command")).action(async (input, flags) => {
|
|
293
303
|
const result = await runMokaSubmitFromCli(input, flags);
|
|
294
304
|
console.log(`Workflow submitted: ${result.workflowName} in ${result.namespace}`);
|
package/dist/install-commands.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { DEFAULT_OPENCODE_ECOSYSTEM_MANIFEST, loadPipelineConfig } from "./config.js";
|
|
2
2
|
import { compileWorkflowPlan } from "./workflow-planner.js";
|
|
3
|
+
import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
|
|
3
4
|
import { renderOpenCodeGatewayConfig } from "./mcp/gateway.js";
|
|
4
5
|
import { resolvePackageAssetPath } from "./package-assets.js";
|
|
5
|
-
import { mergeOpenCodeProjectConfig } from "./opencode-project-config.js";
|
|
6
6
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { basename, dirname, join, relative } from "node:path";
|
|
8
8
|
import matter from "gray-matter";
|
|
@@ -18,7 +18,8 @@ const AGENTS_MD_START = "<!-- @oisincoveney/pipeline:agents:start -->";
|
|
|
18
18
|
const AGENTS_MD_END = "<!-- @oisincoveney/pipeline:agents:end -->";
|
|
19
19
|
const SINGLE_OPENCODE_PLUGIN_ARRAY_RE = /\n {2}"plugin": \[\n {4}("[^"]+")\n {2}\]/;
|
|
20
20
|
const OPENCODE_PROJECT_CONFIG_PATH = ".opencode/opencode.json";
|
|
21
|
-
const
|
|
21
|
+
const OPENCODE_COMMAND_PREFIX = "moka-";
|
|
22
|
+
const ENTRYPOINT_PATH_PATTERNS = { opencode: [/^\.opencode\/commands\/(?:moka-)?([^/]+)\.md$/] };
|
|
22
23
|
const COMMAND_HOSTS = ["opencode"];
|
|
23
24
|
const OPENCODE_ORCHESTRATOR_AGENT_ID = "MoKa Orchestrator";
|
|
24
25
|
const MOKA_PROFILE_PREFIX = "moka-";
|
|
@@ -162,6 +163,7 @@ function dispatchBlock(host, config, workflowId = config.default_workflow) {
|
|
|
162
163
|
"Only package-configured gates are blocking. Do not invent RED, GREEN, full-suite, typecheck, or unrelated-drift gates.",
|
|
163
164
|
"If a node returns targeted evidence and has no configured blocking gate, advance to the next node.",
|
|
164
165
|
"Do not bypass configured runner subprocesses or package-configured gates when executing nodes.",
|
|
166
|
+
"Use the listed Task tool routes for native nodes, and run nodes with satisfied dependencies in parallel whenever the host supports concurrent subagent work.",
|
|
165
167
|
hostSpecificDispatchGuard(host, nativeRoutes, cliRoutes)
|
|
166
168
|
].filter((line) => Boolean(line)).join("\n");
|
|
167
169
|
}
|
|
@@ -334,7 +336,7 @@ function opencodeDefinitions(config, cwd) {
|
|
|
334
336
|
]).join("\n")),
|
|
335
337
|
host: "opencode",
|
|
336
338
|
invocation: invocationForHost("opencode", id),
|
|
337
|
-
path: `.opencode/commands/${id}.md`
|
|
339
|
+
path: `.opencode/commands/${commandIdForHost("opencode", id)}.md`
|
|
338
340
|
})),
|
|
339
341
|
{
|
|
340
342
|
content: renderOpenCodeProjectConfig(config),
|
|
@@ -364,7 +366,7 @@ function opencodeDefinitions(config, cwd) {
|
|
|
364
366
|
name: nativeAgentIdForHost("opencode", id),
|
|
365
367
|
description: profile.description ?? id,
|
|
366
368
|
hidden: false,
|
|
367
|
-
mode: "
|
|
369
|
+
mode: "all",
|
|
368
370
|
...opencodeModelProjection(config, profile),
|
|
369
371
|
permission: opencodePermission(profile)
|
|
370
372
|
}, [
|
|
@@ -401,7 +403,7 @@ function projectAgentsMdDefinition(cwd, host) {
|
|
|
401
403
|
"",
|
|
402
404
|
"This repository uses package-owned `@oisincoveney/pipeline` config.",
|
|
403
405
|
"",
|
|
404
|
-
"- Use `/quick`, `/execute`, or `/inspect` for OpenCode slash-command entrypoints when available.",
|
|
406
|
+
"- Use `/moka-quick`, `/moka-execute`, or `/moka-inspect` for OpenCode slash-command entrypoints when available.",
|
|
405
407
|
"- Load and follow the relevant skill from `.agents/skills` before doing specialized work.",
|
|
406
408
|
"- Prefer the package-defined pipeline profiles and generated command surfaces over ad hoc subagent prompts.",
|
|
407
409
|
"",
|
|
@@ -485,7 +487,11 @@ function entrypointIdFromGeneratedPath(host, path) {
|
|
|
485
487
|
}
|
|
486
488
|
}
|
|
487
489
|
function invocationForHost(host, entrypointId = "execute") {
|
|
488
|
-
return `${{ opencode: "/" }[host]}${entrypointId} <task description>`;
|
|
490
|
+
return `${{ opencode: "/" }[host]}${commandIdForHost(host, entrypointId)} <task description>`;
|
|
491
|
+
}
|
|
492
|
+
function commandIdForHost(host, entrypointId) {
|
|
493
|
+
if (host === "opencode") return `${OPENCODE_COMMAND_PREFIX}${entrypointId}`;
|
|
494
|
+
return entrypointId;
|
|
489
495
|
}
|
|
490
496
|
function resolveDefinitionContent(definition, target) {
|
|
491
497
|
if (definition.path !== OPENCODE_PROJECT_CONFIG_PATH || !existsSync(target)) return {
|
package/dist/moka-submit.d.ts
CHANGED
|
@@ -161,8 +161,8 @@ declare const mokaSubmitOptionsSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
161
161
|
}, z.core.$strict>>;
|
|
162
162
|
serviceAccountName: z.ZodOptional<z.ZodString>;
|
|
163
163
|
mode: z.ZodEnum<{
|
|
164
|
-
full: "full";
|
|
165
164
|
quick: "quick";
|
|
165
|
+
full: "full";
|
|
166
166
|
}>;
|
|
167
167
|
schedulePath: z.ZodOptional<z.ZodString>;
|
|
168
168
|
scheduleYaml: z.ZodOptional<z.ZodString>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
@@ -102,18 +102,26 @@ async function configureGitCommitter(worktreePath, committer) {
|
|
|
102
102
|
committer.email
|
|
103
103
|
]);
|
|
104
104
|
}
|
|
105
|
-
function runnerGitCommandArgs(args) {
|
|
106
|
-
return [...gitCredentialConfigArgs(
|
|
105
|
+
function runnerGitCommandArgs(args, remoteUrl) {
|
|
106
|
+
return [...gitCredentialConfigArgs(remoteUrl), ...args];
|
|
107
107
|
}
|
|
108
108
|
async function runGit(cwd, args) {
|
|
109
|
-
const remoteUrl = remoteUrlFromGitArgs(args);
|
|
110
|
-
|
|
109
|
+
const remoteUrl = await remoteUrlFromGitArgs(cwd, args);
|
|
110
|
+
assertSshCredentialsAvailable(remoteUrl);
|
|
111
|
+
const { stdout } = await execGit("git", runnerGitCommandArgs(args, remoteUrl), {
|
|
111
112
|
cwd,
|
|
112
113
|
encoding: "utf8",
|
|
113
114
|
env: runnerGitEnv(remoteUrl)
|
|
114
115
|
});
|
|
115
116
|
return stdout;
|
|
116
117
|
}
|
|
118
|
+
function assertSshCredentialsAvailable(remoteUrl) {
|
|
119
|
+
if (!(remoteUrl && isSshRemote(remoteUrl))) return;
|
|
120
|
+
const credentialsDir = gitCredentialsDir();
|
|
121
|
+
const missing = [["identity", resolve(credentialsDir, "identity")], ["known_hosts", resolve(credentialsDir, "known_hosts")]].filter(([, filePath]) => !existsSync(filePath)).map(([name]) => name);
|
|
122
|
+
if (missing.length === 0) return;
|
|
123
|
+
throw new Error(`SSH git remote ${remoteUrl} requires mounted git credential file(s): ${missing.join(", ")}`);
|
|
124
|
+
}
|
|
117
125
|
function gitCredentialConfigArgs(remoteUrl) {
|
|
118
126
|
const writablePath = prepareWritableGitCredentialStore(remoteUrl);
|
|
119
127
|
if (!writablePath) return [];
|
|
@@ -125,6 +133,8 @@ function gitCredentialConfigArgs(remoteUrl) {
|
|
|
125
133
|
];
|
|
126
134
|
}
|
|
127
135
|
function prepareWritableGitCredentialStore(remoteUrl) {
|
|
136
|
+
if (!remoteUrl) return existingPreparedBasicAuthCredentialStore(writableGitCredentialStore());
|
|
137
|
+
if (!isHttpRemote(remoteUrl)) return;
|
|
128
138
|
const writablePath = writableGitCredentialStore();
|
|
129
139
|
const basicAuth = availableBasicAuthCredentials();
|
|
130
140
|
if (basicAuth) return prepareBasicAuthCredentialStore(basicAuth, writablePath, remoteUrl);
|
|
@@ -172,10 +182,8 @@ function runnerGitEnv(remoteUrl) {
|
|
|
172
182
|
...process.env,
|
|
173
183
|
GIT_TERMINAL_PROMPT: "0"
|
|
174
184
|
};
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (sshCommand) env.GIT_SSH_COMMAND = sshCommand;
|
|
178
|
-
}
|
|
185
|
+
const sshCommand = gitSshCommand();
|
|
186
|
+
if (sshCommand && remoteUrl && isSshRemote(remoteUrl)) env.GIT_SSH_COMMAND = sshCommand;
|
|
179
187
|
return env;
|
|
180
188
|
}
|
|
181
189
|
function gitSshCommand() {
|
|
@@ -183,7 +191,7 @@ function gitSshCommand() {
|
|
|
183
191
|
const identityPath = resolve(credentialsDir, "identity");
|
|
184
192
|
const knownHostsPath = resolve(credentialsDir, "known_hosts");
|
|
185
193
|
if (!(existsSync(identityPath) && existsSync(knownHostsPath))) return;
|
|
186
|
-
|
|
194
|
+
ensureSshIdentityPermissions(identityPath);
|
|
187
195
|
return [
|
|
188
196
|
"ssh",
|
|
189
197
|
"-i",
|
|
@@ -196,11 +204,63 @@ function gitSshCommand() {
|
|
|
196
204
|
"StrictHostKeyChecking=yes"
|
|
197
205
|
].join(" ");
|
|
198
206
|
}
|
|
207
|
+
function ensureSshIdentityPermissions(identityPath) {
|
|
208
|
+
try {
|
|
209
|
+
chmodSync(identityPath, 256);
|
|
210
|
+
return;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (!(isReadOnlyFileSystemError(error) && isOwnerReadOnly(identityPath))) throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function isReadOnlyFileSystemError(error) {
|
|
216
|
+
return error instanceof Error && "code" in error && error.code === "EROFS";
|
|
217
|
+
}
|
|
218
|
+
function isOwnerReadOnly(path) {
|
|
219
|
+
const permissions = statSync(path).mode.toString(8).slice(-3);
|
|
220
|
+
return [
|
|
221
|
+
"4",
|
|
222
|
+
"5",
|
|
223
|
+
"6",
|
|
224
|
+
"7"
|
|
225
|
+
].includes(permissions[0] ?? "") && permissions.slice(1) === "00";
|
|
226
|
+
}
|
|
199
227
|
function readCredentialFile(path) {
|
|
200
228
|
return readFileSync(path, "utf8").trim();
|
|
201
229
|
}
|
|
202
|
-
function remoteUrlFromGitArgs(args) {
|
|
230
|
+
async function remoteUrlFromGitArgs(cwd, args) {
|
|
231
|
+
const literalRemoteUrl = literalRemoteUrlFromGitArgs(args);
|
|
232
|
+
if (literalRemoteUrl) return literalRemoteUrl;
|
|
233
|
+
const remoteName = remoteNameFromGitArgs(args);
|
|
234
|
+
if (!remoteName) return;
|
|
235
|
+
return await gitRemoteUrl(cwd, remoteName);
|
|
236
|
+
}
|
|
237
|
+
function literalRemoteUrlFromGitArgs(args) {
|
|
203
238
|
if (args[0] === "clone") return args.find((arg) => isRemoteUrl(arg));
|
|
239
|
+
if (args[0] === "fetch" || args[0] === "push" || args[0] === "ls-remote") {
|
|
240
|
+
const remoteArg = args[1];
|
|
241
|
+
return remoteArg && isRemoteUrl(remoteArg) ? remoteArg : void 0;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function remoteNameFromGitArgs(args) {
|
|
245
|
+
if (args[0] === "fetch" || args[0] === "push" || args[0] === "ls-remote") {
|
|
246
|
+
const remoteArg = args[1];
|
|
247
|
+
if (remoteArg && !remoteArg.startsWith("-") && !isRemoteUrl(remoteArg)) return remoteArg;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async function gitRemoteUrl(cwd, remoteName) {
|
|
251
|
+
const { stdout } = await execGit("git", [
|
|
252
|
+
"remote",
|
|
253
|
+
"get-url",
|
|
254
|
+
remoteName
|
|
255
|
+
], {
|
|
256
|
+
cwd,
|
|
257
|
+
encoding: "utf8",
|
|
258
|
+
env: {
|
|
259
|
+
...process.env,
|
|
260
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return stdout.trim() || void 0;
|
|
204
264
|
}
|
|
205
265
|
function isRemoteUrl(value) {
|
|
206
266
|
return value.startsWith("http://") || value.startsWith("https://") || value.startsWith("ssh://") || isScpLikeSshRemote(value);
|
|
@@ -208,6 +268,9 @@ function isRemoteUrl(value) {
|
|
|
208
268
|
function isSshRemote(value) {
|
|
209
269
|
return value.startsWith("ssh://") || isScpLikeSshRemote(value);
|
|
210
270
|
}
|
|
271
|
+
function isHttpRemote(value) {
|
|
272
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
273
|
+
}
|
|
211
274
|
function isScpLikeSshRemote(value) {
|
|
212
275
|
return SCP_LIKE_SSH_REMOTE_RE.test(value);
|
|
213
276
|
}
|
|
@@ -9,6 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
import { readFileSync } from "node:fs";
|
|
10
10
|
import { resolve } from "node:path";
|
|
11
11
|
import { execa } from "execa";
|
|
12
|
+
import pino from "pino";
|
|
12
13
|
//#region src/runner-command/run.ts
|
|
13
14
|
const runnerCommandOptionsSchema = z.object({
|
|
14
15
|
cwd: z.string().min(1).optional(),
|
|
@@ -17,6 +18,7 @@ const runnerCommandOptionsSchema = z.object({
|
|
|
17
18
|
payloadFile: z.string().min(1),
|
|
18
19
|
scheduleFile: z.string().min(1),
|
|
19
20
|
stderr: z.custom((value) => isOutputStream(value)).optional(),
|
|
21
|
+
stdout: z.custom((value) => isOutputStream(value)).optional(),
|
|
20
22
|
taskDescriptorFile: z.string().min(1).optional()
|
|
21
23
|
}).strict();
|
|
22
24
|
const EXIT_PASS = 0;
|
|
@@ -25,15 +27,36 @@ const EXIT_VALIDATION = 64;
|
|
|
25
27
|
const EXIT_STARTUP = 70;
|
|
26
28
|
async function runRunnerCommand(rawOptions = {}) {
|
|
27
29
|
const parsedOptions = runnerCommandOptionsSchema.safeParse(rawOptions);
|
|
28
|
-
const
|
|
30
|
+
const logger = createRunnerLogger({
|
|
31
|
+
stderr: isOutputStream(rawOptions.stderr) ? rawOptions.stderr : process.stderr,
|
|
32
|
+
stdout: isOutputStream(rawOptions.stdout) ? rawOptions.stdout : process.stdout
|
|
33
|
+
});
|
|
29
34
|
if (!parsedOptions.success) {
|
|
30
|
-
|
|
35
|
+
logger.error({
|
|
36
|
+
error: parsedOptions.error.message,
|
|
37
|
+
phase: "options.validate"
|
|
38
|
+
}, "runner options validation failed");
|
|
31
39
|
return EXIT_VALIDATION;
|
|
32
40
|
}
|
|
33
41
|
const options = parsedOptions.data;
|
|
34
42
|
try {
|
|
43
|
+
logger.info({
|
|
44
|
+
phase: "payload.load",
|
|
45
|
+
status: "start"
|
|
46
|
+
}, "payload.load start");
|
|
35
47
|
const payload = parseRunnerCommandPayload(readFileSync(options.payloadFile, "utf8"));
|
|
36
48
|
const descriptor = readRunnerTaskDescriptor(options.taskDescriptorFile ?? "/etc/pipeline/task.json");
|
|
49
|
+
logger.info({
|
|
50
|
+
nodeId: descriptor.nodeId,
|
|
51
|
+
phase: "payload.load",
|
|
52
|
+
runId: payload.run.id,
|
|
53
|
+
status: "finish",
|
|
54
|
+
workflowId: payload.workflow.id
|
|
55
|
+
}, "payload.load finish");
|
|
56
|
+
logger.info({
|
|
57
|
+
phase: "event.sink.configure",
|
|
58
|
+
status: "start"
|
|
59
|
+
}, "event.sink.configure start");
|
|
37
60
|
const authToken = resolveRunnerEventSinkAuthToken({ authTokenFile: payload.events.authTokenFile });
|
|
38
61
|
const sink = createRunnerEventSink({
|
|
39
62
|
authHeader: payload.events.authHeader,
|
|
@@ -42,27 +65,86 @@ async function runRunnerCommand(rawOptions = {}) {
|
|
|
42
65
|
runId: payload.run.id,
|
|
43
66
|
url: payload.events.url
|
|
44
67
|
});
|
|
68
|
+
logger.info({
|
|
69
|
+
phase: "event.sink.configure",
|
|
70
|
+
status: "finish"
|
|
71
|
+
}, "event.sink.configure finish");
|
|
72
|
+
logger.info({
|
|
73
|
+
hasProvidedCwd: Boolean(options.cwd),
|
|
74
|
+
phase: "git.workspace.prepare",
|
|
75
|
+
status: "start"
|
|
76
|
+
}, "git.workspace.prepare start");
|
|
45
77
|
const worktreePath = await prepareRunnerGitWorkspace(payload, { cwd: options.cwd });
|
|
78
|
+
logger.info({
|
|
79
|
+
phase: "git.workspace.prepare",
|
|
80
|
+
status: "finish"
|
|
81
|
+
}, "git.workspace.prepare finish");
|
|
82
|
+
logger.info({
|
|
83
|
+
phase: "config.load",
|
|
84
|
+
status: "start"
|
|
85
|
+
}, "config.load start");
|
|
46
86
|
const baseConfig = loadPipelineConfig(worktreePath, { allowMissingLintFileReferences: true });
|
|
87
|
+
logger.info({
|
|
88
|
+
phase: "config.load",
|
|
89
|
+
status: "finish"
|
|
90
|
+
}, "config.load finish");
|
|
91
|
+
logger.info({
|
|
92
|
+
phase: "schedule.compile",
|
|
93
|
+
status: "start"
|
|
94
|
+
}, "schedule.compile start");
|
|
47
95
|
const compiled = compileScheduleArtifact(baseConfig, parseScheduleArtifact(readFileSync(options.scheduleFile, "utf8"), options.scheduleFile), worktreePath);
|
|
96
|
+
logger.info({
|
|
97
|
+
phase: "schedule.compile",
|
|
98
|
+
status: "finish",
|
|
99
|
+
workflowId: compiled.workflowId
|
|
100
|
+
}, "schedule.compile finish");
|
|
48
101
|
if (payload.workflow.id !== compiled.workflowId) throw new Error(`Runner payload workflow '${payload.workflow.id}' does not match schedule workflow '${compiled.workflowId}'`);
|
|
49
102
|
const node = findPlannedNode(compiled.plan.topologicalOrder, descriptor.nodeId);
|
|
50
103
|
if (!node) throw new Error(`Argo task '${descriptor.nodeId}' is not declared in workflow '${compiled.workflowId}'`);
|
|
104
|
+
logger.info({
|
|
105
|
+
dependencyCount: node.needs.length,
|
|
106
|
+
nodeId: descriptor.nodeId,
|
|
107
|
+
phase: "dependency.merge",
|
|
108
|
+
status: "start"
|
|
109
|
+
}, "dependency.merge start");
|
|
51
110
|
await mergeDependencyRefs({
|
|
52
111
|
committer: compiled.config.runner_command.git.committer,
|
|
53
112
|
dependencyNodeIds: node.needs,
|
|
54
113
|
payload,
|
|
55
114
|
worktreePath
|
|
56
115
|
});
|
|
116
|
+
logger.info({
|
|
117
|
+
dependencyCount: node.needs.length,
|
|
118
|
+
nodeId: descriptor.nodeId,
|
|
119
|
+
phase: "dependency.merge",
|
|
120
|
+
status: "finish"
|
|
121
|
+
}, "dependency.merge finish");
|
|
122
|
+
logger.info({
|
|
123
|
+
commandCount: baseConfig.runner_command.environment.setup.length,
|
|
124
|
+
phase: "setup.commands",
|
|
125
|
+
status: "start"
|
|
126
|
+
}, "setup.commands start");
|
|
57
127
|
await runSetupCommands(baseConfig.runner_command.environment.setup, {
|
|
58
128
|
env: options.env ?? process.env,
|
|
129
|
+
logger,
|
|
59
130
|
worktreePath
|
|
60
131
|
});
|
|
132
|
+
logger.info({
|
|
133
|
+
commandCount: baseConfig.runner_command.environment.setup.length,
|
|
134
|
+
phase: "setup.commands",
|
|
135
|
+
status: "finish"
|
|
136
|
+
}, "setup.commands finish");
|
|
61
137
|
sink.recordRunnerCommandPhase("task.start", `Starting ${descriptor.nodeId}`, {
|
|
62
138
|
kind: node.kind,
|
|
63
139
|
taskId: descriptor.nodeId,
|
|
64
140
|
workflowId: payload.workflow.id
|
|
65
141
|
});
|
|
142
|
+
logger.info({
|
|
143
|
+
kind: node.kind,
|
|
144
|
+
nodeId: descriptor.nodeId,
|
|
145
|
+
phase: "task.run",
|
|
146
|
+
status: "start"
|
|
147
|
+
}, "task.run start");
|
|
66
148
|
const result = await runScheduledWorkflowTask({
|
|
67
149
|
config: compiled.config,
|
|
68
150
|
hookPolicy: payload.hookPolicy,
|
|
@@ -73,12 +155,29 @@ async function runRunnerCommand(rawOptions = {}) {
|
|
|
73
155
|
workflowId: compiled.workflowId,
|
|
74
156
|
worktreePath
|
|
75
157
|
});
|
|
158
|
+
logger.info({
|
|
159
|
+
exitCode: result.exitCode,
|
|
160
|
+
nodeId: descriptor.nodeId,
|
|
161
|
+
phase: "task.run",
|
|
162
|
+
resultStatus: result.status,
|
|
163
|
+
status: "finish"
|
|
164
|
+
}, "task.run finish");
|
|
165
|
+
logger.info({
|
|
166
|
+
nodeId: descriptor.nodeId,
|
|
167
|
+
phase: "git.node-ref.push",
|
|
168
|
+
status: "start"
|
|
169
|
+
}, "git.node-ref.push start");
|
|
76
170
|
await commitAndPushNodeRef({
|
|
77
171
|
committer: compiled.config.runner_command.git.committer,
|
|
78
172
|
nodeId: descriptor.nodeId,
|
|
79
173
|
payload,
|
|
80
174
|
worktreePath
|
|
81
175
|
});
|
|
176
|
+
logger.info({
|
|
177
|
+
nodeId: descriptor.nodeId,
|
|
178
|
+
phase: "git.node-ref.push",
|
|
179
|
+
status: "finish"
|
|
180
|
+
}, "git.node-ref.push finish");
|
|
82
181
|
sink.recordRunnerCommandPhase("task.finish", `Finished ${descriptor.nodeId}`, {
|
|
83
182
|
evidence: result.evidence,
|
|
84
183
|
exitCode: result.exitCode,
|
|
@@ -86,11 +185,14 @@ async function runRunnerCommand(rawOptions = {}) {
|
|
|
86
185
|
taskId: descriptor.nodeId,
|
|
87
186
|
workflowId: payload.workflow.id
|
|
88
187
|
});
|
|
89
|
-
await flushAndReport(sink,
|
|
188
|
+
await flushAndReport(sink, logger);
|
|
90
189
|
return result.status === "passed" ? EXIT_PASS : EXIT_FAIL;
|
|
91
190
|
} catch (error) {
|
|
92
191
|
const message = error instanceof Error ? error.message : String(error);
|
|
93
|
-
|
|
192
|
+
logger.error({
|
|
193
|
+
error: message,
|
|
194
|
+
phase: "runner-command"
|
|
195
|
+
}, message);
|
|
94
196
|
return error instanceof RunnerCommandPayloadValidationError || error instanceof z.ZodError ? EXIT_VALIDATION : EXIT_STARTUP;
|
|
95
197
|
}
|
|
96
198
|
}
|
|
@@ -102,12 +204,26 @@ function findPlannedNode(nodes, nodeId) {
|
|
|
102
204
|
}
|
|
103
205
|
}
|
|
104
206
|
async function runSetupCommands(commands, options) {
|
|
105
|
-
for (const command of commands) {
|
|
207
|
+
for (const [index, command] of commands.entries()) {
|
|
208
|
+
options.logger.info({
|
|
209
|
+
command: command.command,
|
|
210
|
+
index: index + 1,
|
|
211
|
+
phase: "setup.command",
|
|
212
|
+
status: "start"
|
|
213
|
+
}, "setup.command start");
|
|
106
214
|
const result = await execa(command.command, command.args, {
|
|
107
215
|
cwd: options.worktreePath,
|
|
108
216
|
env: options.env,
|
|
109
217
|
reject: false
|
|
110
218
|
});
|
|
219
|
+
options.logger.info({
|
|
220
|
+
command: command.command,
|
|
221
|
+
exitCode: result.exitCode,
|
|
222
|
+
index: index + 1,
|
|
223
|
+
phase: "setup.command",
|
|
224
|
+
required: command.required,
|
|
225
|
+
status: "finish"
|
|
226
|
+
}, "setup.command finish");
|
|
111
227
|
if (result.exitCode !== 0 && command.required) throw new Error(`runner setup command '${command.command}' failed with exit ${result.exitCode}`);
|
|
112
228
|
}
|
|
113
229
|
}
|
|
@@ -119,13 +235,52 @@ function runnerTaskText(task, worktreePath) {
|
|
|
119
235
|
function isOutputStream(value) {
|
|
120
236
|
return typeof value === "object" && value !== null && "write" in value && typeof value.write === "function";
|
|
121
237
|
}
|
|
122
|
-
async function flushAndReport(sink,
|
|
238
|
+
async function flushAndReport(sink, logger) {
|
|
239
|
+
logger.info({
|
|
240
|
+
phase: "event.flush",
|
|
241
|
+
status: "start"
|
|
242
|
+
}, "event.flush start");
|
|
123
243
|
try {
|
|
124
244
|
await sink.flush();
|
|
245
|
+
logger.info({
|
|
246
|
+
phase: "event.flush",
|
|
247
|
+
status: "finish"
|
|
248
|
+
}, "event.flush finish");
|
|
125
249
|
} catch (error) {
|
|
126
250
|
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
-
|
|
251
|
+
logger.error({
|
|
252
|
+
error: message,
|
|
253
|
+
phase: "event.flush"
|
|
254
|
+
}, `runner event flush failed: ${message}`);
|
|
128
255
|
}
|
|
129
256
|
}
|
|
257
|
+
function createRunnerLogger(options) {
|
|
258
|
+
const streams = [{
|
|
259
|
+
level: "info",
|
|
260
|
+
stream: options.stdout
|
|
261
|
+
}, {
|
|
262
|
+
level: "error",
|
|
263
|
+
stream: options.stderr
|
|
264
|
+
}];
|
|
265
|
+
return pino({
|
|
266
|
+
base: void 0,
|
|
267
|
+
level: "info",
|
|
268
|
+
name: "moka-runner",
|
|
269
|
+
redact: {
|
|
270
|
+
censor: "[redacted]",
|
|
271
|
+
paths: [
|
|
272
|
+
"authToken",
|
|
273
|
+
"*.authToken",
|
|
274
|
+
"token",
|
|
275
|
+
"*.token",
|
|
276
|
+
"password",
|
|
277
|
+
"*.password",
|
|
278
|
+
"identity",
|
|
279
|
+
"*.identity"
|
|
280
|
+
]
|
|
281
|
+
},
|
|
282
|
+
timestamp: pino.stdTimeFunctions.isoTime
|
|
283
|
+
}, pino.multistream(streams, { dedupe: true }));
|
|
284
|
+
}
|
|
130
285
|
//#endregion
|
|
131
286
|
export { runRunnerCommand };
|
|
@@ -43,8 +43,8 @@ declare const runnerDeliverySchema: z.ZodObject<{
|
|
|
43
43
|
declare const mokaSubmissionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
44
44
|
kind: z.ZodLiteral<"graph">;
|
|
45
45
|
mode: z.ZodEnum<{
|
|
46
|
-
full: "full";
|
|
47
46
|
quick: "quick";
|
|
47
|
+
full: "full";
|
|
48
48
|
}>;
|
|
49
49
|
}, z.core.$strict>, z.ZodObject<{
|
|
50
50
|
argv: z.ZodArray<z.ZodString>;
|
|
@@ -104,8 +104,8 @@ declare const runnerCommandPayloadSchema: z.ZodObject<{
|
|
|
104
104
|
submission: z.ZodDefault<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
105
105
|
kind: z.ZodLiteral<"graph">;
|
|
106
106
|
mode: z.ZodEnum<{
|
|
107
|
-
full: "full";
|
|
108
107
|
quick: "quick";
|
|
108
|
+
full: "full";
|
|
109
109
|
}>;
|
|
110
110
|
}, z.core.$strict>, z.ZodObject<{
|
|
111
111
|
argv: z.ZodArray<z.ZodString>;
|
package/dist/schedule-planner.js
CHANGED
|
@@ -472,13 +472,33 @@ function executeBaselineWorkflow() {
|
|
|
472
472
|
],
|
|
473
473
|
id: "verification",
|
|
474
474
|
kind: "agent",
|
|
475
|
-
needs: [
|
|
475
|
+
needs: [
|
|
476
|
+
"mechanical-green-tests",
|
|
477
|
+
"mechanical-green-typecheck",
|
|
478
|
+
"mechanical-green-lint",
|
|
479
|
+
"mechanical-green-fallow"
|
|
480
|
+
],
|
|
476
481
|
profile: "moka-verifier"
|
|
477
482
|
},
|
|
483
|
+
{
|
|
484
|
+
id: "code-quality-review",
|
|
485
|
+
kind: "agent",
|
|
486
|
+
needs: [
|
|
487
|
+
"mechanical-green-tests",
|
|
488
|
+
"mechanical-green-typecheck",
|
|
489
|
+
"mechanical-green-lint",
|
|
490
|
+
"mechanical-green-fallow"
|
|
491
|
+
],
|
|
492
|
+
profile: "moka-thermo-nuclear-reviewer"
|
|
493
|
+
},
|
|
478
494
|
{
|
|
479
495
|
id: "learn",
|
|
480
496
|
kind: "agent",
|
|
481
|
-
needs: [
|
|
497
|
+
needs: [
|
|
498
|
+
"acceptance-review",
|
|
499
|
+
"verification",
|
|
500
|
+
"code-quality-review"
|
|
501
|
+
],
|
|
482
502
|
profile: "moka-learner"
|
|
483
503
|
}
|
|
484
504
|
]
|
package/docs/operator-guide.md
CHANGED
|
@@ -287,13 +287,13 @@ Troubleshooting:
|
|
|
287
287
|
Generated invocations include:
|
|
288
288
|
|
|
289
289
|
```text
|
|
290
|
-
OpenCode: /quick, /execute, /inspect
|
|
290
|
+
OpenCode: /moka-quick, /moka-execute, /moka-inspect
|
|
291
291
|
Codex: $quick, $execute, $inspect
|
|
292
292
|
```
|
|
293
293
|
|
|
294
294
|
`moka init` and `moka install-commands --host opencode` generate:
|
|
295
295
|
|
|
296
|
-
- `.opencode/commands
|
|
296
|
+
- `.opencode/commands/moka-<entrypoint>.md` for `/moka-quick`, `/moka-execute`, and `/moka-inspect`
|
|
297
297
|
- `.opencode/agents/*.md` for primary and subagent profiles with explicit
|
|
298
298
|
`permission` maps, `task` grants, and denied ungranted tools
|
|
299
299
|
- `.opencode/skills/*/SKILL.md` from `npx skills add`, not Moka generation
|
|
@@ -15,7 +15,7 @@ moka install-commands --host all --check
|
|
|
15
15
|
|
|
16
16
|
| Host | Generated resources | Invocation | Mechanical path |
|
|
17
17
|
| -------- | -------------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
18
|
-
| OpenCode | `.opencode/commands
|
|
18
|
+
| OpenCode | `.opencode/commands/moka-<entrypoint>.md`, `.opencode/agents/*.md`, `.opencode/opencode.json` | `/moka-quick <task>`, `/moka-execute <task>`, `/moka-inspect <task>` | Project commands run a primary orchestrator and OpenCode native subagents with package-owned skill, MCP, permission, and LSP projection. |
|
|
19
19
|
|
|
20
20
|
## Projection Rules
|
|
21
21
|
|
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"micromatch": "^4.0.8",
|
|
14
14
|
"p-limit": "^7.3.0",
|
|
15
15
|
"package-manager-detector": "^1.6.0",
|
|
16
|
+
"pino": "^10.3.1",
|
|
16
17
|
"secure-json-parse": "^4.1.0",
|
|
17
18
|
"simple-git": "^3.36.0",
|
|
18
19
|
"xstate": "^5.31.1",
|
|
@@ -122,7 +123,7 @@
|
|
|
122
123
|
"prepack": "bun run build:cli"
|
|
123
124
|
},
|
|
124
125
|
"type": "module",
|
|
125
|
-
"version": "1.
|
|
126
|
+
"version": "1.28.0",
|
|
126
127
|
"description": "Config-driven multi-agent pipeline runner for repository work",
|
|
127
128
|
"main": "./dist/index.js",
|
|
128
129
|
"types": "./dist/index.d.ts",
|