@kontextmind/kxm 0.6.0 → 0.7.10
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/.claude-plugin/marketplace.json +1 -1
- package/.kxm/agents/coordinator.yaml +9 -0
- package/.kxm/agents/critic-arch.yaml +13 -0
- package/.kxm/agents/critic-cli.yaml +13 -0
- package/.kxm/agents/implementer.yaml +13 -0
- package/.kxm/gates.yaml +8 -0
- package/.kxm/producers.yaml +22 -0
- package/.kxm/project.yaml +15 -0
- package/.kxm/roles/writer.yaml +7 -0
- package/.kxm/workflows/default.yaml +47 -0
- package/CHANGELOG.md +39 -7
- package/README.md +1 -0
- package/docs/README.md +5 -0
- package/docs/adr/ADR-0002-browser-automation-steel-doks.md +103 -0
- package/docs/agent-skills.md +135 -0
- package/docs/architecture.md +1 -1
- package/docs/assignment-runner.md +21 -8
- package/docs/browser-automation.md +116 -0
- package/docs/configuration.md +11 -2
- package/docs/getting-started.md +21 -0
- package/docs/kb/how-credentials-retrieved-safely.md +31 -0
- package/docs/kb/how-to-capture-and-annotate-section.md +60 -0
- package/docs/kb/how-to-connect-playwright-to-steel.md +54 -0
- package/docs/kb/how-to-recover-expired-session-or-orphan.md +54 -0
- package/docs/kb/how-to-resume-after-mfa.md +28 -0
- package/docs/kb/how-to-take-over-session.md +32 -0
- package/docs/kb/why-authentication-disappeared.md +32 -0
- package/docs/kb/why-automation-opened-different-browser.md +32 -0
- package/docs/kb/why-session-viewer-cannot-control.md +31 -0
- package/docs/kxm-handbook.md +3 -3
- package/docs/operations.md +24 -0
- package/docs/operator-pi-packages.md +63 -0
- package/docs/prompts/browser-annotate-feedback.md +41 -0
- package/docs/prompts/browser-diagnose-recover.md +38 -0
- package/docs/prompts/browser-explore.md +42 -0
- package/docs/prompts/browser-repro-fix.md +48 -0
- package/docs/prompts/browser-start.md +41 -0
- package/docs/prompts/browser-takeover.md +50 -0
- package/docs/skills/repo-work-delivery.md +107 -0
- package/docs/skills.md +2 -0
- package/docs/test-matrix.md +4 -3
- package/docs/troubleshooting.md +41 -1
- package/docs/vnext/validation.md +9 -0
- package/docs/webhook-workflows.md +2 -2
- package/examples/README.md +1 -1
- package/package.json +16 -17
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/README.md +1 -1
- package/plugins/kxm/dist/cli.js +41620 -35578
- package/plugins/kxm/dist/core.js +271 -34
- package/plugins/kxm/dist/extension.js +7759 -86
- package/plugins/kxm/dist/mcp-server.js +75 -21
- package/plugins/kxm/dist/runtime.js +8218 -2328
- package/plugins/kxm/dist/server.js +3125 -2260
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +5961 -661
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/SUITE.md +5 -0
- package/plugins/kxm/skills/hints.json +103 -0
- package/plugins/kxm/skills/kxm/SKILL.md +30 -83
- package/plugins/kxm/skills/kxm-browser-annotate/SKILL.md +90 -0
- package/plugins/kxm/skills/kxm-browser-auth/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-browser-diagnostics/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-explore/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-browser-session/SKILL.md +94 -0
- package/plugins/kxm/skills/kxm-browser-takeover/SKILL.md +87 -0
- package/plugins/kxm/skills/kxm-browser-verify/SKILL.md +71 -0
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
- package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
- package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +42 -0
- package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
- package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
- package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
- package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
- package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
- package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
- package/plugins/kxm/src/autocomplete.ts +9 -3
- package/plugins/kxm/src/browser.ts +603 -0
- package/plugins/kxm/src/cli/context-skills.ts +373 -0
- package/plugins/kxm/src/cli/hub.ts +614 -0
- package/plugins/kxm/src/cli/roles.ts +615 -0
- package/plugins/kxm/src/cli/system.ts +906 -0
- package/plugins/kxm/src/cli/tasks.ts +364 -0
- package/plugins/kxm/src/cli/types.ts +270 -0
- package/plugins/kxm/src/cli/vnext.ts +698 -0
- package/plugins/kxm/src/cli/workflows.ts +699 -0
- package/plugins/kxm/src/cli.ts +362 -2849
- package/plugins/kxm/src/commands.ts +150 -8
- package/plugins/kxm/src/completion-install.ts +223 -0
- package/plugins/kxm/src/config.ts +7 -4
- package/plugins/kxm/src/context-packet.ts +172 -0
- package/plugins/kxm/src/database.ts +1 -1
- package/plugins/kxm/src/extension.ts +36 -1
- package/plugins/kxm/src/external-effects.ts +357 -8
- package/plugins/kxm/src/hub-env.ts +193 -0
- package/plugins/kxm/src/hub.ts +2 -4
- package/plugins/kxm/src/improve.ts +72 -0
- package/plugins/kxm/src/init-guide-setup.ts +547 -0
- package/plugins/kxm/src/local-snapshot.ts +1 -1
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/model-inventory.ts +127 -0
- package/plugins/kxm/src/modes.ts +348 -0
- package/plugins/kxm/src/policy-draft.d.mts +55 -0
- package/plugins/kxm/src/policy-draft.mjs +565 -0
- package/plugins/kxm/src/price-calc.ts +17 -18
- package/plugins/kxm/src/prices.ts +32 -16
- package/plugins/kxm/src/producers.ts +71 -0
- package/plugins/kxm/src/protocol.ts +111 -0
- package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
- package/plugins/kxm/src/restricted-yaml.mjs +145 -0
- package/plugins/kxm/src/role.ts +710 -0
- package/plugins/kxm/src/routing.ts +99 -1
- package/plugins/kxm/src/runtime.ts +4 -0
- package/plugins/kxm/src/safety-integrity.ts +76 -0
- package/plugins/kxm/src/session-work.ts +9 -2
- package/plugins/kxm/src/sqlite.ts +76 -0
- package/plugins/kxm/src/ssh-remote.ts +560 -0
- package/plugins/kxm/src/store.ts +1 -1
- package/plugins/kxm/src/studio-layout.ts +660 -17
- package/plugins/kxm/src/subagent-control.ts +312 -0
- package/plugins/kxm/src/suggest.ts +7 -13
- package/plugins/kxm/src/telemetry.ts +82 -0
- package/plugins/kxm/src/tui.ts +140 -0
- package/plugins/kxm/src/vnext-bindings.ts +1 -1
- package/plugins/kxm/src/vnext-config.ts +53 -111
- package/plugins/kxm/src/vnext-engine-command.ts +2 -0
- package/plugins/kxm/src/vnext-engine.ts +214 -62
- package/plugins/kxm/src/vnext-harness.ts +336 -84
- package/plugins/kxm/src/vnext-oneshot-evidence.ts +117 -0
- package/plugins/kxm/src/vnext-oneshot-process.ts +187 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +182 -224
- package/plugins/kxm/src/vnext-pi-producer.ts +11 -7
- package/plugins/kxm/src/vnext-runtime-store.ts +36 -2
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +122 -5
- package/plugins/kxm/src/vnext-runtime.ts +14 -0
- package/plugins/kxm/src/workflow-manager.ts +392 -0
- package/plugins/kxm/src/workflow-tui.ts +255 -0
- package/plugins/kxm/src/workflow.ts +144 -0
- package/schemas/policy-draft/README.md +17 -0
- package/schemas/policy-draft/model.v2.schema.json +140 -0
- package/schemas/policy-draft/role.v2.schema.json +91 -0
- package/schemas/vnext/modes.schema.json +56 -0
- package/schemas/vnext/role.schema.json +76 -0
- package/schemas/vnext/run-event.schema.json +1 -0
- package/scripts/assignment-run.d.mts +1 -1
- package/scripts/assignment-run.mjs +44 -35
- package/scripts/check-generated.mjs +33 -9
- package/scripts/emit-codex-artifacts.mjs +255 -11
- package/scripts/harness-run.d.mts +12 -4
- package/scripts/harness-run.mjs +65 -17
- package/scripts/kxm-bump-version.mjs +146 -0
- package/scripts/kxm-hub.mjs +150 -2
- package/scripts/kxm-publish-npm.mjs +3 -1
- package/scripts/kxm-release-github.mjs +3 -1
- package/scripts/kxm.mjs +0 -0
- package/scripts/native-critic.d.mts +5 -0
- package/scripts/native-critic.mjs +60 -0
- package/.kxm/config/README.md +0 -5
- package/.kxm/config/agents.json +0 -43
- package/.kxm/config/env.example +0 -56
- package/.kxm/config/update.example.yaml +0 -9
- package/.kxm/config/workflows/fix.json +0 -160
- package/.kxm/config/workflows/jira-development.json +0 -116
- package/.kxm/config/workflows/provenance-quorum.json +0 -150
- package/.kxm/config/workflows/v04-dogfood.json +0 -72
package/scripts/kxm-hub.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
|
-
import {
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
import { closeSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
5
7
|
import { join, resolve } from "node:path";
|
|
6
8
|
import { fileURLToPath } from "node:url";
|
|
7
9
|
|
|
@@ -15,6 +17,19 @@ const startedAt = new Date().toISOString();
|
|
|
15
17
|
const controlFile = "hub.stop";
|
|
16
18
|
const controlPath = join(stateDir, controlFile);
|
|
17
19
|
|
|
20
|
+
// --- Persistent hub credentials (kxm.hub-env.v1) -------------------------
|
|
21
|
+
// Precedence: KXM_AUTH_TOKEN/KXM_PROJECT_TOKENS env, then the persisted
|
|
22
|
+
// user-state file, then a generated admin token that is persisted so later
|
|
23
|
+
// restarts, workers, and dashboards reuse the same credential.
|
|
24
|
+
function resolveUserStateRoot() {
|
|
25
|
+
const explicit = process.env.KXM_STATE_HOME?.trim();
|
|
26
|
+
if (explicit) return resolve(explicit);
|
|
27
|
+
const home = homedir();
|
|
28
|
+
if (process.platform === "win32") return resolve(process.env.LOCALAPPDATA?.trim() || join(home, "AppData", "Local"), "KXM");
|
|
29
|
+
if (process.platform === "darwin") return resolve(home, "Library", "Application Support", "KXM");
|
|
30
|
+
return resolve(process.env.XDG_STATE_HOME?.trim() || join(home, ".local", "state"), "kxm");
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
function processExists(pid) {
|
|
19
34
|
try {
|
|
20
35
|
process.kill(pid, 0);
|
|
@@ -24,6 +39,119 @@ function processExists(pid) {
|
|
|
24
39
|
}
|
|
25
40
|
}
|
|
26
41
|
|
|
42
|
+
function parseHubEnvFile() {
|
|
43
|
+
const file = join(resolveUserStateRoot(), "hub-env.json");
|
|
44
|
+
let raw;
|
|
45
|
+
try {
|
|
46
|
+
raw = readFileSync(file, "utf8");
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error?.code === "ENOENT") return { file, record: undefined };
|
|
49
|
+
throw new Error(`KXM hub could not read the persisted hub env at ${file}: ${error.message}`);
|
|
50
|
+
}
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = JSON.parse(raw);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw new Error(`KXM hub env file is malformed at ${file}; fix or remove it (it holds only KXM_AUTH_TOKEN/KXM_PROJECT_TOKENS values): ${error.message}`);
|
|
56
|
+
}
|
|
57
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || parsed.schema !== "kxm.hub-env.v1") {
|
|
58
|
+
throw new Error(`KXM hub env file at ${file} does not use schema kxm.hub-env.v1`);
|
|
59
|
+
}
|
|
60
|
+
return { file, record: parsed };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function generateAdminToken() {
|
|
64
|
+
return `kxm_admin_${randomBytes(24).toString("base64url")}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function persistHubEnv(file, record) {
|
|
68
|
+
mkdirSync(resolve(file, ".."), { recursive: true });
|
|
69
|
+
const temporary = join(resolve(file, ".."), `.hub-env-${process.pid}.tmp`);
|
|
70
|
+
writeFileSync(temporary, `${JSON.stringify(record, undefined, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
71
|
+
try {
|
|
72
|
+
renameSync(temporary, file);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
rmSync(temporary, { force: true });
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function parseProjectTokensEnv(raw) {
|
|
80
|
+
const trimmed = raw?.trim();
|
|
81
|
+
if (!trimmed) return undefined;
|
|
82
|
+
let parsed;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(trimmed);
|
|
85
|
+
} catch {
|
|
86
|
+
throw new Error("KXM_PROJECT_TOKENS must be a JSON object of project names to tokens");
|
|
87
|
+
}
|
|
88
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
89
|
+
throw new Error("KXM_PROJECT_TOKENS must be a JSON object of project names to tokens");
|
|
90
|
+
}
|
|
91
|
+
for (const [project, token] of Object.entries(parsed)) {
|
|
92
|
+
if (!project.trim() || typeof token !== "string" || !token.trim()) {
|
|
93
|
+
throw new Error("KXM_PROJECT_TOKENS must contain non-empty project names and token strings");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Resolve credentials, persisting generated/env values into the user-state file. */
|
|
100
|
+
function resolveCredentials() {
|
|
101
|
+
const { file, record } = parseHubEnvFile();
|
|
102
|
+
const envToken = process.env.KXM_AUTH_TOKEN?.trim();
|
|
103
|
+
const envProjectTokens = parseProjectTokensEnv(process.env.KXM_PROJECT_TOKENS);
|
|
104
|
+
let authToken = envToken || record?.authToken;
|
|
105
|
+
let authTokenSource = envToken ? "env" : record?.authToken ? "file" : "none";
|
|
106
|
+
if (!authToken) {
|
|
107
|
+
authToken = generateAdminToken();
|
|
108
|
+
authTokenSource = "generated";
|
|
109
|
+
}
|
|
110
|
+
const projectTokens = envProjectTokens || record?.projectTokens;
|
|
111
|
+
const projectTokensSource = envProjectTokens ? "env" : record?.projectTokens ? "file" : "none";
|
|
112
|
+
const envChanged = (authTokenSource === "env" && record?.authToken !== authToken)
|
|
113
|
+
|| (projectTokensSource === "env" && JSON.stringify(record?.projectTokens ?? undefined) !== JSON.stringify(projectTokens ?? undefined));
|
|
114
|
+
if (authTokenSource === "generated" || envChanged) {
|
|
115
|
+
persistHubEnv(file, {
|
|
116
|
+
schema: "kxm.hub-env.v1",
|
|
117
|
+
createdAt: record?.createdAt || new Date().toISOString(),
|
|
118
|
+
...(authToken ? { authToken } : {}),
|
|
119
|
+
...(projectTokens ? { projectTokens } : {}),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return { authToken, authTokenSource, projectTokens, projectTokensSource, file };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let credentials;
|
|
126
|
+
try {
|
|
127
|
+
credentials = resolveCredentials();
|
|
128
|
+
} catch (error) {
|
|
129
|
+
process.stderr.write(`kxm hub: ${error.message}\n`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
const launchEnv = { ...process.env };
|
|
133
|
+
if (credentials.authToken) launchEnv.KXM_AUTH_TOKEN = credentials.authToken;
|
|
134
|
+
if (credentials.projectTokens) launchEnv.KXM_PROJECT_TOKENS = JSON.stringify(credentials.projectTokens);
|
|
135
|
+
if (credentials.authTokenSource !== "env") {
|
|
136
|
+
process.stdout.write(`kxm hub: using ${credentials.authTokenSource === "generated" ? "newly generated" : "persisted"} KXM_AUTH_TOKEN from ${credentials.file}\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function terminateTree(pid) {
|
|
140
|
+
// Best-effort cleanup of an orphaned hub server whose wrapper died
|
|
141
|
+
// without reclaiming (e.g. SIGKILL). The wrapper could not be stopped
|
|
142
|
+
// gracefully in that state, so the server is asked to shut down first and
|
|
143
|
+
// forced after a short grace period.
|
|
144
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
145
|
+
try { process.kill(pid, "SIGTERM"); } catch { /* already gone */ }
|
|
146
|
+
const deadline = Date.now() + 3000;
|
|
147
|
+
while (Date.now() < deadline) {
|
|
148
|
+
if (!processExists(pid)) return;
|
|
149
|
+
const wait = Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
150
|
+
if (wait !== "timed-out") return;
|
|
151
|
+
}
|
|
152
|
+
try { process.kill(pid, "SIGKILL"); } catch { /* already gone */ }
|
|
153
|
+
}
|
|
154
|
+
|
|
27
155
|
function claimPidFile() {
|
|
28
156
|
const record = { version: 1, pid: process.pid, role: "hub", startedAt, controlFile };
|
|
29
157
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
@@ -50,6 +178,19 @@ function claimPidFile() {
|
|
|
50
178
|
if (existing?.version === 1 && existing.role === "hub" && Number.isInteger(existing.pid) && processExists(existing.pid)) {
|
|
51
179
|
throw new Error(`KXM hub is already managed by PID ${existing.pid}`);
|
|
52
180
|
}
|
|
181
|
+
// A well-formed claim whose wrapper is dead is safe to reclaim. Keep
|
|
182
|
+
// malformed claims fail-closed because they may belong to another tool.
|
|
183
|
+
if (existing?.version === 1 && existing.role === "hub" && Number.isInteger(existing.pid) && !processExists(existing.pid)) {
|
|
184
|
+
// The wrapper is gone. An orphaned server child recorded by the dead
|
|
185
|
+
// wrapper is cleaned up before reclaiming so a SIGKILLed wrapper does
|
|
186
|
+
// not leave a port-holding server behind.
|
|
187
|
+
if (Number.isInteger(existing.serverPid) && existing.serverPid !== existing.pid && processExists(existing.serverPid)) {
|
|
188
|
+
process.stderr.write(`kxm hub: cleaning up orphaned hub server PID ${existing.serverPid} from a dead wrapper\n`);
|
|
189
|
+
terminateTree(existing.serverPid);
|
|
190
|
+
}
|
|
191
|
+
rmSync(pidPath, { force: true });
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
53
194
|
throw new Error(
|
|
54
195
|
`KXM hub PID claim is stale at ${pidPath}; remove it only after verifying no hub process is running`,
|
|
55
196
|
);
|
|
@@ -73,8 +214,15 @@ const child = spawn(process.execPath, [
|
|
|
73
214
|
...process.argv.slice(2),
|
|
74
215
|
], {
|
|
75
216
|
stdio: ["inherit", "inherit", "inherit", "ipc"],
|
|
76
|
-
env:
|
|
217
|
+
env: launchEnv,
|
|
77
218
|
});
|
|
219
|
+
// Record the server child PID inside the claim so a later wrapper can clean
|
|
220
|
+
// up an orphaned server if this wrapper is killed without running cleanupPid.
|
|
221
|
+
if (child.pid) {
|
|
222
|
+
try {
|
|
223
|
+
writeFileSync(pidPath, `${JSON.stringify({ version: 1, pid: process.pid, serverPid: child.pid, role: "hub", startedAt, controlFile })}\n`, "utf8");
|
|
224
|
+
} catch { /* claim already written; serverPid tracking is best-effort */ }
|
|
225
|
+
}
|
|
78
226
|
let stopping = false;
|
|
79
227
|
|
|
80
228
|
child.once("error", (error) => {
|
|
@@ -275,7 +275,9 @@ export async function main(env = process.env, stdout = process.stdout, stderr =
|
|
|
275
275
|
const token = env.GITHUB_TOKEN || env.GH_TOKEN;
|
|
276
276
|
const npmToken = env.NODE_AUTH_TOKEN || env.NPM_TOKEN;
|
|
277
277
|
const repo = env.GITHUB_REPOSITORY;
|
|
278
|
-
|
|
278
|
+
// KXM_RELEASE_TAG is explicit for workflow_dispatch runs; GITHUB_REF_NAME
|
|
279
|
+
// is the branch name for that event, not the requested release tag.
|
|
280
|
+
const tag = env.KXM_RELEASE_TAG || env.TAG || env.GITHUB_REF_NAME;
|
|
279
281
|
const assetName = env.KXM_ASSET;
|
|
280
282
|
const assetPath = env.KXM_ASSET_PATH;
|
|
281
283
|
const declaredSha256 = env.KXM_ASSET_SHA256;
|
|
@@ -427,7 +427,9 @@ export function fileSha256(path) {
|
|
|
427
427
|
export async function main(env = process.env, stdout = process.stdout, stderr = process.stderr) {
|
|
428
428
|
const token = env.GITHUB_TOKEN || env.GH_TOKEN;
|
|
429
429
|
const repo = env.GITHUB_REPOSITORY;
|
|
430
|
-
|
|
430
|
+
// KXM_RELEASE_TAG is explicit for workflow_dispatch runs; GITHUB_REF_NAME
|
|
431
|
+
// is the branch name for that event, not the requested release tag.
|
|
432
|
+
const tag = env.KXM_RELEASE_TAG || env.TAG || env.GITHUB_REF_NAME;
|
|
431
433
|
const assetName = env.KXM_ASSET;
|
|
432
434
|
const assetPath = env.KXM_ASSET_PATH;
|
|
433
435
|
const declared = env.KXM_ASSET_SHA256;
|
package/scripts/kxm.mjs
CHANGED
|
File without changes
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
import { HubClient } from "../plugins/kxm/dist/client.js";
|
|
7
|
+
import { buildArgv } from "./harness-run.mjs";
|
|
8
|
+
|
|
9
|
+
// Launch intent only: these labels are not observed model identity or admission.
|
|
10
|
+
export function nativeCriticLaunch(harness) {
|
|
11
|
+
if (!["fable", "astra"].includes(harness)) {
|
|
12
|
+
throw new Error("usage: native-critic.mjs <fable|astra> <prompt>");
|
|
13
|
+
}
|
|
14
|
+
const model = harness === "fable" ? "fable" : "gpt-6-astra";
|
|
15
|
+
const command = harness === "fable" ? "claude" : "codex";
|
|
16
|
+
const args = harness === "fable"
|
|
17
|
+
? ["-p", "--model", model, "--output-format", "json", "--no-session-persistence", "--tools", "Read,Glob,Grep", "--setting-sources", "user"]
|
|
18
|
+
: buildArgv({ harness: "codex", model, effort: "low", permission: "read-only", prompt_file: "-" });
|
|
19
|
+
return { model, command, args };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const [harness, ...promptParts] = process.argv.slice(2);
|
|
24
|
+
let launch;
|
|
25
|
+
try {
|
|
26
|
+
launch = nativeCriticLaunch(harness);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error(error.message);
|
|
29
|
+
process.exitCode = 2;
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const { model, command, args } = launch;
|
|
33
|
+
const env = process.env;
|
|
34
|
+
const serverUrl = env.KXM_SERVER_URL || "http://127.0.0.1:7331";
|
|
35
|
+
const outDir = env.KXM_CRITIC_DIR || join(process.cwd(), ".kxm", "assets", "critic-reviews");
|
|
36
|
+
mkdirSync(outDir, { recursive: true });
|
|
37
|
+
const client = new HubClient({ serverUrl, authToken: env.KXM_AUTH_TOKEN, name: `${harness}-critic`, purpose: "independent CLI critic", project: env.KXM_PROJECT || ".kxm", model });
|
|
38
|
+
const prompt = promptParts.join(" ");
|
|
39
|
+
const agent = await client.start(() => {});
|
|
40
|
+
const result = await new Promise((resolve) => {
|
|
41
|
+
const child = spawn(command, args, { env, stdio: ["pipe", "pipe", "pipe"], shell: false });
|
|
42
|
+
let stdout = "", stderr = "";
|
|
43
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
44
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
45
|
+
child.on("error", (error) => resolve({ code: null, stdout, stderr: String(error) }));
|
|
46
|
+
child.on("close", (code, signal) => resolve({ code, signal, stdout, stderr }));
|
|
47
|
+
child.stdin.end(prompt);
|
|
48
|
+
});
|
|
49
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
50
|
+
const path = join(outDir, `${harness}-${stamp}.json`);
|
|
51
|
+
writeFileSync(path, JSON.stringify({ schema: "kxm.native-critic-review.v1", harness, model, agentId: agent.id, completedAt: new Date().toISOString(), ...result }, null, 2) + "\n", { mode: 0o600 });
|
|
52
|
+
try { await client.send({ target: env.KXM_REVIEW_TARGET || agent.id, content: `${harness} review completed; artifact=${path}; exit=${String(result.code)}`, delivery: "followUp", idempotencyKey: `critic:${harness}:${stamp}` }); } catch { /* Transport artifact remains, not approval or hub peer-reply evidence. */ }
|
|
53
|
+
await client.stop();
|
|
54
|
+
if (result.code !== 0) process.exit(result.code ?? 1);
|
|
55
|
+
console.log(path);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
59
|
+
await main();
|
|
60
|
+
}
|
package/.kxm/config/README.md
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
# Workspace configuration
|
|
2
|
-
|
|
3
|
-
Store reviewable, repository-specific harness and workflow configuration here. `env.example` documents supported environment settings. `workflows/` contains the complete Jira development lifecycle, the v0.4 dogfood workflow, and the provenance/quorum workflow used by parser and evidence-policy tests.
|
|
4
|
-
|
|
5
|
-
Configuration files may reference secret environment-variable names such as `JIRA_WEBHOOK_SECRET`; they must never contain the secret values. Set `KXM_WEBHOOK_WORKFLOWS_FILE` to the chosen workflow file.
|
package/.kxm/config/agents.json
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schema": "kxm.agents.v1",
|
|
3
|
-
"project": "kxm",
|
|
4
|
-
"host": "local",
|
|
5
|
-
"agents": [
|
|
6
|
-
{
|
|
7
|
-
"name": "grok",
|
|
8
|
-
"kind": "agent",
|
|
9
|
-
"driver": "ai",
|
|
10
|
-
"model": "xai/grok-4.6",
|
|
11
|
-
"thinking": "xhigh",
|
|
12
|
-
"purpose": "Sole writer; independent critic of kxm CLI and envelopes"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"name": "fable",
|
|
16
|
-
"kind": "agent",
|
|
17
|
-
"driver": "ai",
|
|
18
|
-
"model": "anthropic/claude-fable-5-1",
|
|
19
|
-
"thinking": "xhigh",
|
|
20
|
-
"purpose": "Independent critic: architecture and permissions"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"name": "gpt-sol",
|
|
24
|
-
"kind": "agent",
|
|
25
|
-
"driver": "ai",
|
|
26
|
-
"model": "openai-codex/gpt-5.6-sol",
|
|
27
|
-
"thinking": "xhigh",
|
|
28
|
-
"purpose": "Independent critic: CLI contracts and documentation"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"name": "kimi",
|
|
32
|
-
"kind": "agent",
|
|
33
|
-
"driver": "ai",
|
|
34
|
-
"model": "kimi-coding/kimi-for-coding",
|
|
35
|
-
"thinking": "max",
|
|
36
|
-
"purpose": "Independent critic: mapping gaps and portability"
|
|
37
|
-
}
|
|
38
|
-
],
|
|
39
|
-
"ownership": {
|
|
40
|
-
"writeAgent": "grok",
|
|
41
|
-
"peerAgents": ["fable", "gpt-sol", "kimi"]
|
|
42
|
-
}
|
|
43
|
-
}
|
package/.kxm/config/env.example
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
KXM_HOST=127.0.0.1
|
|
2
|
-
KXM_PORT=7331
|
|
3
|
-
KXM_AUTH_TOKEN=replace-with-a-long-random-token
|
|
4
|
-
KXM_WORKSPACE_DIR=.kxm
|
|
5
|
-
# KXM_CONFIG_DIR=.kxm/config
|
|
6
|
-
# KXM_LOGS_DIR=.kxm/logs
|
|
7
|
-
# KXM_ASSETS_DIR=.kxm/assets
|
|
8
|
-
# KXM_STATE_DIR=.kxm/state
|
|
9
|
-
# KXM_DATA_PATH=.kxm/state/kxm.db
|
|
10
|
-
# KXM_LOG_PATH=.kxm/logs/kxm-hub.jsonl
|
|
11
|
-
KXM_PROJECT_TOKENS={"my-project":"replace-with-a-project-token"}
|
|
12
|
-
KXM_MESSAGE_TTL_MS=86400000
|
|
13
|
-
KXM_MESSAGE_RETENTION_MS=604800000
|
|
14
|
-
KXM_RATE_LIMIT_MAX=600
|
|
15
|
-
KXM_RATE_LIMIT_WINDOW_MS=60000
|
|
16
|
-
KXM_WEBHOOK_WORKFLOWS_FILE=.kxm/config/workflows/jira-development.json
|
|
17
|
-
JIRA_WEBHOOK_SECRET=replace-with-a-high-entropy-webhook-secret
|
|
18
|
-
WORKFLOW_SIGNAL_SECRET=replace-with-a-separate-callback-secret
|
|
19
|
-
|
|
20
|
-
# Client / Pi extension settings
|
|
21
|
-
KXM_SERVER_URL=http://127.0.0.1:7331
|
|
22
|
-
KXM_PROJECT=my-project
|
|
23
|
-
KXM_AGENT_NAME=planner
|
|
24
|
-
KXM_AGENT_PURPOSE=Plans work and reviews architecture
|
|
25
|
-
|
|
26
|
-
# Long-lived headless Pi worker
|
|
27
|
-
KXM_WORKDIR=C:\path\to\project
|
|
28
|
-
KXM_WORKER_CONTINUE=true
|
|
29
|
-
# Opt in after reviewing the fresh scoped-history upgrade behavior:
|
|
30
|
-
# workflow = stable default Pi session plus one session per durable workflow run
|
|
31
|
-
# KXM_WORKER_SESSION_ISOLATION=workflow
|
|
32
|
-
# KXM_WORKER_MAX_RUN_SESSIONS=128
|
|
33
|
-
# KXM_WORKER_INITIAL_CONTINUE=false
|
|
34
|
-
# KXM_WORKER_LOG_PATH=.kxm/logs/kxm-worker-planner.jsonl
|
|
35
|
-
# KXM_AGENT_LOG_PATH=.kxm/logs/pi-agent-planner.log
|
|
36
|
-
# KXM_WORKER_MAX_RESTARTS=20
|
|
37
|
-
# KXM_WORKER_MODEL=provider/model
|
|
38
|
-
# KXM_WORKER_FALLBACK_MODELS=provider/fallback-one,provider/fallback-two
|
|
39
|
-
# KXM_WORKER_PROVIDER_RETRY_MS=60000
|
|
40
|
-
# KXM_WORKER_TOOL_TIMEOUT_MS=1860000
|
|
41
|
-
# KXM_WORKER_ACTIVATION_TIMEOUT_MS=60000
|
|
42
|
-
# KXM_WORKER_TOOLS=read,grep,find,ls,kxm_fanout,kxm_workflow_get
|
|
43
|
-
# KXM_WORKER_EXTENSION_PATHS=plugins/kxm/src/extension.ts;C:\path\to\provider\extension.ts
|
|
44
|
-
# KXM_WORKER_SKILL_PATHS=plugins/kxm/skills/kxm
|
|
45
|
-
|
|
46
|
-
# Optional proposed-improvement telemetry classification override: cli or project
|
|
47
|
-
# KXM_IMPROVE_TARGET=project
|
|
48
|
-
|
|
49
|
-
# Opt-in Nous Pi providers (unset = no fetch, no registration)
|
|
50
|
-
# KXM_NOUS_PROVIDERS=direct
|
|
51
|
-
# KXM_NOUS_PROVIDERS=proxy
|
|
52
|
-
# KXM_NOUS_PROVIDERS=direct,proxy
|
|
53
|
-
# NOUS_API_KEY=
|
|
54
|
-
# KXM_NOUS_PROXY_URL=http://127.0.0.1:8645/v1
|
|
55
|
-
# KXM_NOUS_DISCOVERY_TIMEOUT_MS=5000
|
|
56
|
-
# KXM_NOUS_CATALOG_FILE=
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Copy this file to the per-user state root. kxm never reads a project
|
|
2
|
-
# .kxm/update.yaml (it prints a warning and continues). Locations:
|
|
3
|
-
# Windows: %LOCALAPPDATA%\KXM\update.yaml
|
|
4
|
-
# macOS: ~/Library/Application Support/KXM/update.yaml
|
|
5
|
-
# Linux: ${XDG_STATE_HOME:-~/.local/state}/kxm/update.yaml
|
|
6
|
-
# Override the state root with an absolute KXM_STATE_HOME for tests.
|
|
7
|
-
schema: kxm.update.v1
|
|
8
|
-
auto: false
|
|
9
|
-
source: github
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": "fix",
|
|
4
|
-
"source": "generic",
|
|
5
|
-
"project": "kxm",
|
|
6
|
-
"target": "coordinator",
|
|
7
|
-
"secretEnv": "FIX_WEBHOOK_SECRET",
|
|
8
|
-
"signalSecretEnv": "WORKFLOW_SIGNAL_SECRET",
|
|
9
|
-
"delivery": "followUp",
|
|
10
|
-
"maxTransitions": 28,
|
|
11
|
-
"reproOracle": { "stageId": "repro-review", "evidenceKey": "repro" },
|
|
12
|
-
"planHash": { "stageId": "final-plan", "evidenceKey": "plan" },
|
|
13
|
-
"requirePlanHash": ["implement", "local-verify", "delivery"],
|
|
14
|
-
"promptTemplate": "Bug report: {{task}}\n\nRun the /fix workflow: two-phase reproduction before any production edit, approved plan before implementation, bounded rework on failure. The confirmed reproduction is an immutable oracle.\n\nHard rules: (1) The oracle must invoke the diagnosis-named production callables on the reporter path — a sibling API or newer stack is invalid even if it fails. (2) After repro_invalidated, rewrite the draft against that named seam; never checkpoint blocked because the previous seam was wrong or because a method news its own DbContext. (3) Search existing test hosts (controller fixtures, HttpContext helpers, TransactionScope, read-only call against existing data) before declaring a seam untestable.",
|
|
15
|
-
"stages": [
|
|
16
|
-
{
|
|
17
|
-
"id": "intake",
|
|
18
|
-
"label": "Intake",
|
|
19
|
-
"area": "workflow",
|
|
20
|
-
"instructions": "Classify the bug report: expected vs actual behavior, affected surface, severity, and reproduction hints. Record a journal 'plan' entry with the classification. If the report is not actionable, checkpoint with outcome 'blocked'.",
|
|
21
|
-
"requiredEvidence": ["classification"],
|
|
22
|
-
"maxAttempts": 2,
|
|
23
|
-
"on": { "passed": "repro-explore", "blocked": "$terminal" }
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"id": "repro-explore",
|
|
27
|
-
"label": "Repro explore (read-only)",
|
|
28
|
-
"area": "implementation",
|
|
29
|
-
"instructions": "Read and search only: locate the bug, note prior reproductions, test conventions, and similar incidents via kxm context. No source or test edits. Produce a diagnosis that names the exact production callables on the reporter path (handler, data method, or rendered component), root-cause hypothesis, expected/actual behavior, reproduction strategy, reference tests in existing area folders, and target assertions. Secondary surfaces (other API versions, other tabs) are notes only — they are not substitute oracles. Record the diagnosis with journal category 'hypothesis'.",
|
|
30
|
-
"requiredEvidence": ["diagnosis"],
|
|
31
|
-
"maxAttempts": 3,
|
|
32
|
-
"on": { "passed": "repro-write", "blocked": "$terminal" }
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"id": "repro-write",
|
|
36
|
-
"label": "Repro write (tests only)",
|
|
37
|
-
"area": "implementation",
|
|
38
|
-
"instructions": "Write and execute the failing reproduction. Tests and test helpers only — production and configuration stay byte-for-byte unchanged. Every target assertion must call the diagnosis-named production handler, production seam, or rendered production component and fail for the reported reason rather than compilation/setup. A sibling API, newer stack, or 'also used by some admins' handler is invalid even if it fails. A method that news its own DbContext is not grounds for blocked: use existing test hosts (controller fixtures, HttpContext helpers, TransactionScope so a new context still rolls back) or a read-only call against existing data with no seed. After repro_invalidated, write a new draft against the named seam; do not checkpoint outcome blocked because the previous draft was the wrong seam. Checkpoint failed (outcome failed) to consume an attempt and rewrite. blocked is only when the ticket is not a code defect. Cite evidence key 'repro' with every test path, sha256, exact command, and failed/passed counts. This is a reviewable draft; it is not immutable until independent repro-review passes.",
|
|
39
|
-
"requiredEvidence": ["repro"],
|
|
40
|
-
"maxAttempts": 3,
|
|
41
|
-
"on": { "passed": "repro-review", "failed": "repro-write", "blocked": "$terminal" }
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
"id": "repro-review",
|
|
45
|
-
"label": "Independent reproduction review",
|
|
46
|
-
"area": "gates",
|
|
47
|
-
"instructions": "Before the oracle is captured, send the diagnosis, test files/hashes, exact failing results, and production/config git status to both independent critics. They must verify production coupling to the diagnosis-named reporter-path seam (a sibling API is invalid), full expected-behavior coverage, failure causality, immutability feasibility, and absence of production/config edits. Caller-authored evidence cannot satisfy peer review. If either critic finds an omission or invalid test, checkpoint status 'failed' with outcome 'repro_invalidated' and return to repro-write. Only unanimous validation may pass; cite the unchanged draft under evidence key 'repro'. Passing this stage captures the immutable oracle.",
|
|
48
|
-
"requiredEvidence": ["repro", "review"],
|
|
49
|
-
"maxAttempts": 3,
|
|
50
|
-
"evidencePolicies": {
|
|
51
|
-
"review": {
|
|
52
|
-
"kind": "peer-reply",
|
|
53
|
-
"minProducers": 2,
|
|
54
|
-
"eligibleAgents": ["critic-1", "critic-2"],
|
|
55
|
-
"acceptedStatuses": ["replied"]
|
|
56
|
-
}
|
|
57
|
-
},
|
|
58
|
-
"on": { "passed": "plan", "repro_invalidated": "repro-write", "blocked": "$terminal" }
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
"id": "plan",
|
|
62
|
-
"label": "Plan",
|
|
63
|
-
"area": "workflow",
|
|
64
|
-
"instructions": "Using the diagnosis and the confirmed reproduction, write the implementation plan: changed files, approach, risks, and how the reproduction proves the fix. Cite evidence key 'plan' with the plan artifact path and content hash.",
|
|
65
|
-
"requiredEvidence": ["plan"],
|
|
66
|
-
"maxAttempts": 3,
|
|
67
|
-
"on": { "passed": "critics", "blocked": "$terminal" }
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
"id": "critics",
|
|
71
|
-
"label": "Independent critics",
|
|
72
|
-
"area": "gates",
|
|
73
|
-
"instructions": "Two independent critics review the plan for correctness, scope creep, and risk. Both must reply through the mesh; caller-authored evidence cannot satisfy this policy. If the plan is invalidated, checkpoint with outcome 'plan_invalidated'.",
|
|
74
|
-
"requiredEvidence": ["critique"],
|
|
75
|
-
"maxAttempts": 3,
|
|
76
|
-
"evidencePolicies": {
|
|
77
|
-
"critique": {
|
|
78
|
-
"kind": "peer-reply",
|
|
79
|
-
"minProducers": 2,
|
|
80
|
-
"eligibleAgents": ["critic-1", "critic-2"],
|
|
81
|
-
"acceptedStatuses": ["replied"]
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
"on": { "passed": "final-plan", "plan_invalidated": "plan", "blocked": "$terminal" }
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"id": "final-plan",
|
|
88
|
-
"label": "Final plan",
|
|
89
|
-
"area": "workflow",
|
|
90
|
-
"instructions": "Fold critic feedback into the final plan. Re-cite evidence key 'plan' with the final artifact path and content hash. The hash of this stage's plan evidence becomes the approved plan hash required by implementation.",
|
|
91
|
-
"requiredEvidence": ["plan"],
|
|
92
|
-
"maxAttempts": 2,
|
|
93
|
-
"on": { "passed": "approval", "plan_invalidated": "plan", "blocked": "$terminal" }
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"id": "approval",
|
|
97
|
-
"label": "Human approval",
|
|
98
|
-
"area": "security",
|
|
99
|
-
"instructions": "Human operator approves the plan. Cite evidence key 'signoff' with the approver identity and approval reference. A 'rejected' outcome routes back to planning; this stage cannot be bypassed by any transition.",
|
|
100
|
-
"requiredEvidence": ["signoff"],
|
|
101
|
-
"maxAttempts": 8,
|
|
102
|
-
"on": { "passed": "implement", "rejected": "plan", "blocked": "$terminal" }
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
"id": "implement",
|
|
106
|
-
"label": "Implement",
|
|
107
|
-
"area": "implementation",
|
|
108
|
-
"instructions": "Implement the approved plan. The reproduction oracle is immutable: local verification must fail before the fix and pass after it. Cite evidence keys 'diff' and 'repro' (same value as the oracle).",
|
|
109
|
-
"requiredEvidence": ["diff"],
|
|
110
|
-
"maxAttempts": 4,
|
|
111
|
-
"on": { "passed": "local-verify", "blocked": "$terminal" }
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
"id": "local-verify",
|
|
115
|
-
"label": "Local verify",
|
|
116
|
-
"area": "gates",
|
|
117
|
-
"instructions": "Run the repo-defined gates: unit, lint, typecheck, build, and the confirmed reproduction test. Cite evidence key 'gates' with per-gate results. 'implementation_failure' reworks implementation; 'plan_invalidated' routes back to planning rather than expanding scope.",
|
|
118
|
-
"requiredEvidence": ["gates"],
|
|
119
|
-
"maxAttempts": 4,
|
|
120
|
-
"on": {
|
|
121
|
-
"passed": "delivery",
|
|
122
|
-
"implementation_failure": "implement",
|
|
123
|
-
"plan_invalidated": "plan",
|
|
124
|
-
"blocked": "$terminal"
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
"id": "delivery",
|
|
129
|
-
"label": "Delivery",
|
|
130
|
-
"area": "workflow",
|
|
131
|
-
"instructions": "Create and push the branch and merge request with the reproduction, plan, and diff references. Delivery never auto-merges: CI and review outcomes are watched in the next stage.",
|
|
132
|
-
"requiredEvidence": ["mr"],
|
|
133
|
-
"maxAttempts": 2,
|
|
134
|
-
"on": { "passed": "ci-watch", "blocked": "$terminal" }
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
"id": "ci-watch",
|
|
138
|
-
"label": "CI and review watch",
|
|
139
|
-
"area": "gates",
|
|
140
|
-
"instructions": "Watch required CI checks and review status. On failure, checkpoint with outcome 'failure' to rework through bounded transitions. On success, continue to human acceptance.",
|
|
141
|
-
"requiredEvidence": ["ci"],
|
|
142
|
-
"maxAttempts": 6,
|
|
143
|
-
"on": {
|
|
144
|
-
"passed": "ready-for-human-acceptance",
|
|
145
|
-
"failure": "implement",
|
|
146
|
-
"blocked": "$terminal"
|
|
147
|
-
}
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
"id": "ready-for-human-acceptance",
|
|
151
|
-
"label": "Ready for human acceptance",
|
|
152
|
-
"area": "workflow",
|
|
153
|
-
"instructions": "Summarize the fix: reproduction (immutable oracle), approved plan hash, diff, gate results, CI outcome, and the full claim-to-evidence trail from the run journal. Checkpoint 'passed' to complete the run; a human performs final acceptance outside the workflow.",
|
|
154
|
-
"requiredEvidence": ["summary"],
|
|
155
|
-
"maxAttempts": 2,
|
|
156
|
-
"on": { "passed": "$terminal", "blocked": "$terminal" }
|
|
157
|
-
}
|
|
158
|
-
]
|
|
159
|
-
}
|
|
160
|
-
]
|