@kontextmind/kxm 0.7.92 → 0.7.93
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/workflows/default.yaml +1 -1
- package/CHANGELOG.md +204 -0
- package/README.md +3 -0
- package/docs/README.md +3 -0
- package/docs/agent-skills.md +123 -60
- package/docs/architecture.md +5 -2
- package/docs/cli-reference.md +3527 -0
- package/docs/config-reference.md +1943 -0
- package/docs/configuration.md +29 -3
- package/docs/continuous-improvement.md +122 -10
- package/docs/contracts/routing.md +95 -11
- package/docs/harness-routing.md +616 -0
- package/docs/kxm-handbook.md +106 -19
- package/docs/templates/README.md +1 -1
- package/docs/test-matrix.md +12 -6
- package/docs/troubleshooting.md +2 -2
- package/examples/project/.kxm/workflows/fix.yaml +1 -1
- package/examples/project/.kxm/workflows/improve.yaml +1 -1
- package/package.json +1 -1
- package/plugins/kxm/.claude-plugin/plugin.json +9 -10
- package/plugins/kxm/README.md +238 -56
- package/plugins/kxm/dist/claude-hook.js +10083 -0
- package/plugins/kxm/dist/cli.js +3068 -2446
- package/plugins/kxm/dist/client.js +64 -0
- package/plugins/kxm/dist/core.js +102 -9
- package/plugins/kxm/dist/extension.js +210 -68
- package/plugins/kxm/dist/mcp-server.js +217 -40
- package/plugins/kxm/dist/runtime-supervisor.js +1628 -157
- package/plugins/kxm/dist/runtime.js +1874 -298
- package/plugins/kxm/dist/server.js +416 -82
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/hints.json +1 -1
- package/plugins/kxm/skills/kxm/SKILL.md +48 -24
- package/plugins/kxm/skills/kxm/references/protocol.md +3 -3
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +61 -21
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +9 -0
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +82 -16
- package/plugins/kxm/skills/kxm-harvest/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +55 -27
- package/plugins/kxm/skills/kxm-insights/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-mind/SKILL.md +2 -2
- package/plugins/kxm/skills/{kxm-setup → kxm-mind-setup}/SKILL.md +4 -4
- package/plugins/kxm/skills/kxm-peer/SKILL.md +68 -93
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +156 -23
- package/plugins/kxm/skills/kxm-projects/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-query/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +74 -15
- package/plugins/kxm/skills/kxm-runs/SKILL.md +46 -17
- package/plugins/kxm/skills/kxm-session/SKILL.md +64 -36
- package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +44 -15
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +16 -4
- package/plugins/kxm/skills/kxm-triage/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-work/SKILL.md +1 -1
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +60 -19
- package/plugins/kxm/src/arbiter.ts +67 -22
- package/plugins/kxm/src/autocomplete.ts +1 -1
- package/plugins/kxm/src/claude-hook.ts +192 -0
- package/plugins/kxm/src/cli/project.ts +11 -5
- package/plugins/kxm/src/cli/system.ts +85 -13
- package/plugins/kxm/src/cli/types.ts +4 -1
- package/plugins/kxm/src/cli/workflows.ts +18 -16
- package/plugins/kxm/src/cli.ts +23 -13
- package/plugins/kxm/src/client.ts +15 -4
- package/plugins/kxm/src/commands.ts +19 -9
- package/plugins/kxm/src/config.ts +42 -7
- package/plugins/kxm/src/context-packet.ts +14 -2
- package/plugins/kxm/src/context.ts +16 -5
- package/plugins/kxm/src/dispatch-context.ts +286 -0
- package/plugins/kxm/src/engine-plan.ts +40 -0
- package/plugins/kxm/src/engine.ts +138 -6
- package/plugins/kxm/src/hub-env.ts +17 -1
- package/plugins/kxm/src/hub.ts +92 -29
- package/plugins/kxm/src/improve-sources.ts +228 -0
- package/plugins/kxm/src/improve.ts +325 -140
- package/plugins/kxm/src/local-snapshot.ts +101 -42
- package/plugins/kxm/src/mcp-server.ts +129 -30
- package/plugins/kxm/src/project-config.ts +25 -0
- package/plugins/kxm/src/protocol.ts +11 -0
- package/plugins/kxm/src/relevance.ts +138 -0
- package/plugins/kxm/src/retrospective.ts +16 -10
- package/plugins/kxm/src/runtime-service.ts +8 -1
- package/plugins/kxm/src/runtime-supervisor.ts +16 -2
- package/plugins/kxm/src/session-token-hint.ts +17 -0
- package/plugins/kxm/src/suggest.ts +7 -7
- package/plugins/kxm/src/workflow-manager.ts +80 -78
- package/plugins/kxm/src/workflow.ts +202 -12
- package/scripts/build-runtime.mjs +7 -1
- package/scripts/check-generated.mjs +1 -0
- package/scripts/emit-codex-artifacts.mjs +1 -1
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
canonicalWorkflowEvidenceKey,
|
|
6
6
|
journalPromotionState,
|
|
7
7
|
normalizeVerifiedWorkflowEvidence,
|
|
8
|
+
rankImprovementSignals,
|
|
8
9
|
type WorkflowJournalEntry,
|
|
9
10
|
type WorkflowRun,
|
|
10
11
|
} from "./workflow.ts";
|
|
@@ -248,12 +249,14 @@ export function buildRetrospective(
|
|
|
248
249
|
const byCategory: Record<string, number> = {};
|
|
249
250
|
const byArea: Record<string, number> = {};
|
|
250
251
|
const byClass: Record<string, number> = {};
|
|
252
|
+
const byErrorClass: Record<string, number> = {};
|
|
251
253
|
for (const entry of entries) {
|
|
252
254
|
increment(byCategory, entry.category);
|
|
253
255
|
increment(byArea, entry.area);
|
|
254
256
|
increment(byClass, classFromEvidence(entry.evidence));
|
|
257
|
+
if (entry.category === "error") increment(byErrorClass, classFromEvidence(entry.evidence));
|
|
255
258
|
}
|
|
256
|
-
const recurringErrorClasses = Object.entries(
|
|
259
|
+
const recurringErrorClasses = Object.entries(byErrorClass)
|
|
257
260
|
.map(([errorClass, count]) => ({ class: errorClass, count }))
|
|
258
261
|
.sort((left, right) => right.count - left.count || left.class.localeCompare(right.class));
|
|
259
262
|
const resolvedContradictions = new Set(entries.filter((entry) => entry.category === "decision" || entry.category === "lesson").flatMap((entry) => entry.relatedEntryIds));
|
|
@@ -263,15 +266,18 @@ export function buildRetrospective(
|
|
|
263
266
|
const decisions = entries
|
|
264
267
|
.filter((entry) => entry.category === "decision")
|
|
265
268
|
.map((entry) => ({ id: entry.id, summary: entry.summary, area: entry.area }));
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
269
|
+
// Ranked, merged error and lesson signals over this run's raw entries. The
|
|
270
|
+
// category filter runs before the limit so up to 12 of them survive.
|
|
271
|
+
const proposedImprovements = rankImprovementSignals(
|
|
272
|
+
journal.filter((entry) => entry.runId === run.id && (entry.category === "error" || entry.category === "lesson")),
|
|
273
|
+
new Map([[run.id, run]]),
|
|
274
|
+
12,
|
|
275
|
+
).map((signal) => ({
|
|
276
|
+
area: signal.area,
|
|
277
|
+
summary: signal.summary,
|
|
278
|
+
successMeasure: redactSecrets(`no recurrence of ${signal.key} in the next ${run.definitionId} run`),
|
|
279
|
+
status: "proposed" as const,
|
|
280
|
+
}));
|
|
275
281
|
const evidenceAudit = buildEvidenceAudit(run);
|
|
276
282
|
const degradedStageIds = run.stages
|
|
277
283
|
.filter((stage) => stage.degraded)
|
|
@@ -117,7 +117,7 @@ function collectMemoryFiles(dir: string, baseDir: string, ignoreSubdirs: Set<str
|
|
|
117
117
|
* the promoted-state snapshot.
|
|
118
118
|
*/
|
|
119
119
|
export function computeKxmMemoryRevision(
|
|
120
|
-
bundle: KxmProjectBundle,
|
|
120
|
+
bundle: Pick<KxmProjectBundle, "projectRoot">,
|
|
121
121
|
options: KxmMemoryRevisionOptions = {},
|
|
122
122
|
): string {
|
|
123
123
|
const hash = createHash("sha256");
|
|
@@ -248,6 +248,11 @@ export interface KxmRuntimeContext {
|
|
|
248
248
|
* freeze a budget by supplying a stale clock.
|
|
249
249
|
*/
|
|
250
250
|
readonly budgetClock?: () => string;
|
|
251
|
+
/**
|
|
252
|
+
* Optional structured logger (the Runtime supervisor's). The engine writes
|
|
253
|
+
* metadata-only lines here, ids and counts, never task or memory text.
|
|
254
|
+
*/
|
|
255
|
+
readonly logger?: (entry: Record<string, unknown>) => void;
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
const closedRuntimeContexts = new WeakSet<KxmRuntimeContext>();
|
|
@@ -286,6 +291,7 @@ export function openKxmRuntimeContext(
|
|
|
286
291
|
now?: string;
|
|
287
292
|
homeRuntimeId: string;
|
|
288
293
|
budgetClock?: () => string;
|
|
294
|
+
logger?: (entry: Record<string, unknown>) => void;
|
|
289
295
|
},
|
|
290
296
|
): KxmRuntimeContext {
|
|
291
297
|
const paths = kxmRuntimePaths(options.stateRoot !== undefined ? { stateRoot: options.stateRoot } : {});
|
|
@@ -310,6 +316,7 @@ export function openKxmRuntimeContext(
|
|
|
310
316
|
eventStore,
|
|
311
317
|
configRevision: bundle.configRevision,
|
|
312
318
|
...(options.budgetClock ? { budgetClock: options.budgetClock } : {}),
|
|
319
|
+
...(options.logger ? { logger: options.logger } : {}),
|
|
313
320
|
};
|
|
314
321
|
} catch (error) {
|
|
315
322
|
registry.close();
|
|
@@ -683,7 +683,7 @@ async function startKxmRuntimeSupervisorInner(
|
|
|
683
683
|
const key = projectRuntimeKey(projectRoot);
|
|
684
684
|
const existing = contexts.get(key);
|
|
685
685
|
if (existing) return existing;
|
|
686
|
-
const context = openKxmRuntimeContext(projectRoot, { homeRuntimeId: activeRuntimeId, stateRoot: paths.stateRoot });
|
|
686
|
+
const context = openKxmRuntimeContext(projectRoot, { homeRuntimeId: activeRuntimeId, stateRoot: paths.stateRoot, logger });
|
|
687
687
|
registerSyncCredentials(context);
|
|
688
688
|
contexts.set(key, context);
|
|
689
689
|
return context;
|
|
@@ -1315,7 +1315,21 @@ export async function kxmRuntimeRequest(
|
|
|
1315
1315
|
if (!response.ok) {
|
|
1316
1316
|
const code = typeof payload.error === "string" ? payload.error : "runtime_request_failed";
|
|
1317
1317
|
const message = typeof payload.message === "string" ? payload.message : `runtime request failed with HTTP ${response.status}`;
|
|
1318
|
-
throw runtimeError(code, path, message);
|
|
1318
|
+
throw runtimeError(code, path, `${message}${handoffSuffix(payload.handoff)}`);
|
|
1319
1319
|
}
|
|
1320
1320
|
return payload;
|
|
1321
1321
|
}
|
|
1322
|
+
|
|
1323
|
+
const HANDOFF_TEXT_MAX = 200;
|
|
1324
|
+
|
|
1325
|
+
/** Why the Runtime handed a run off, from a `run_handoff_required` body: the
|
|
1326
|
+
* reason, field and detail, each capped so a refusal stays one readable line. */
|
|
1327
|
+
function handoffSuffix(handoff: unknown): string {
|
|
1328
|
+
if (!handoff || typeof handoff !== "object" || Array.isArray(handoff)) return "";
|
|
1329
|
+
const parts: string[] = [];
|
|
1330
|
+
for (const key of ["reason", "field", "detail"] as const) {
|
|
1331
|
+
const value = (handoff as Record<string, unknown>)[key];
|
|
1332
|
+
if (typeof value === "string" && value.length > 0) parts.push(`${key} ${value.slice(0, HANDOFF_TEXT_MAX)}`);
|
|
1333
|
+
}
|
|
1334
|
+
return parts.length > 0 ? ` (handoff ${parts.join("; ")})` : "";
|
|
1335
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** User-directed fix for a `session_token_invalid` tool-policy denial.
|
|
2
|
+
*
|
|
3
|
+
* `enforceToolPolicy` (commands.ts) returns that error from three places: an
|
|
4
|
+
* invalid `KXM_SESSION_TOKEN` in the environment (detail starts with
|
|
5
|
+
* `KXM_SESSION_TOKEN`), and an unreadable or malformed/expired session token
|
|
6
|
+
* file on disk. The Claude SessionStart hook and the MCP server both use this
|
|
7
|
+
* text, so a session sees one explanation. Neither text prints or reissues a
|
|
8
|
+
* token: `kxm session token --issue` and bare `kxm session token` print one. */
|
|
9
|
+
|
|
10
|
+
const ENV_TEXT = "KXM_SESSION_TOKEN in the environment Claude Code was launched from is malformed or expired, so every kxm_* tool fails with tool_policy_denied. Ask the user to unset or replace KXM_SESSION_TOKEN in the environment Claude Code was launched from, then restart Claude Code.";
|
|
11
|
+
|
|
12
|
+
const DISK_TEXT = "The KXM session token file on this machine is expired, malformed or unreadable, so every kxm_* tool fails with tool_policy_denied. Ask the user to run `kxm session token --clear` in their own terminal. `kxm session token --status` reports No active session token found for an expired file even though the file still blocks tools. The kxm plugin no longer refreshes that 24-hour token.";
|
|
13
|
+
|
|
14
|
+
export function sessionTokenFixHint(policy: { error?: string | undefined; detail?: string | undefined }): string | undefined {
|
|
15
|
+
if (policy.error !== "session_token_invalid") return undefined;
|
|
16
|
+
return policy.detail?.startsWith("KXM_SESSION_TOKEN") ? ENV_TEXT : DISK_TEXT;
|
|
17
|
+
}
|
|
@@ -32,49 +32,49 @@ const WORKFLOW_PATTERNS: WorkflowPattern[] = [
|
|
|
32
32
|
id: "software-engineering/bug-fix",
|
|
33
33
|
area: "software-engineering",
|
|
34
34
|
keywords: ["fix", "bug", "flaky", "failure", "timeout", "error", "repro", "crash", "broken", "hang"],
|
|
35
|
-
skills: ["
|
|
35
|
+
skills: ["kxm-workflow", "kxm-runs", "kxm-context-memory"],
|
|
36
36
|
defaultCommand: (p) => `kxm run software-engineering/bug-fix "${p}"`,
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
id: "software-engineering/feature-implementation",
|
|
40
40
|
area: "software-engineering",
|
|
41
41
|
keywords: ["feature", "implement", "add", "build", "create", "develop", "support", "endpoint", "ui", "tui"],
|
|
42
|
-
skills: ["
|
|
42
|
+
skills: ["kxm-workflow", "kxm-peer", "kxm-context-memory"],
|
|
43
43
|
defaultCommand: (p) => `kxm run software-engineering/feature-implementation "${p}"`,
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
id: "software-engineering/refactoring",
|
|
47
47
|
area: "software-engineering",
|
|
48
48
|
keywords: ["refactor", "cleanup", "reorganize", "modularize", "deduplicate", "split", "simplify", "deprecate"],
|
|
49
|
-
skills: ["kxm
|
|
49
|
+
skills: ["kxm-workflow", "kxm-runs"],
|
|
50
50
|
defaultCommand: (p) => `kxm run software-engineering/refactoring "${p}"`,
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
id: "security-reliability/vulnerability-remediation",
|
|
54
54
|
area: "security-reliability",
|
|
55
55
|
keywords: ["cve", "vulnerability", "security", "exploit", "sanitize", "leak", "secret", "injection", "redact", "auth"],
|
|
56
|
-
skills: ["kxm
|
|
56
|
+
skills: ["kxm-workflow", "kxm-definitions"],
|
|
57
57
|
defaultCommand: (p) => `kxm run security-reliability/vulnerability-remediation "${p}"`,
|
|
58
58
|
},
|
|
59
59
|
{
|
|
60
60
|
id: "security-reliability/reliability-hardening",
|
|
61
61
|
area: "security-reliability",
|
|
62
62
|
keywords: ["idempotency", "retry", "circuit-breaker", "cas", "lock", "concurrency", "deadlock", "race", "crash-recovery"],
|
|
63
|
-
skills: ["kxm", "kxm-
|
|
63
|
+
skills: ["kxm-workflow", "kxm-peer"],
|
|
64
64
|
defaultCommand: (p) => `kxm run security-reliability/reliability-hardening "${p}"`,
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
67
|
id: "data-analytics/pipeline-migration",
|
|
68
68
|
area: "data-analytics",
|
|
69
69
|
keywords: ["database", "sqlite", "migration", "pipeline", "schema", "transform", "table", "wal", "foreign", "cascading"],
|
|
70
|
-
skills: ["kxm", "kxm-
|
|
70
|
+
skills: ["kxm-runs", "kxm-context-memory"],
|
|
71
71
|
defaultCommand: (p) => `kxm run data-analytics/pipeline-migration "${p}"`,
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
id: "research-strategy/architecture-spike",
|
|
75
75
|
area: "research-strategy",
|
|
76
76
|
keywords: ["spike", "investigate", "prototype", "research", "feasibility", "benchmark", "explore", "evaluate"],
|
|
77
|
-
skills: ["kxm-session", "kxm-
|
|
77
|
+
skills: ["kxm-session", "kxm-context-memory", "kxm-routing-improve"],
|
|
78
78
|
defaultCommand: (p) => `kxm run research-strategy/architecture-spike "${p}"`,
|
|
79
79
|
},
|
|
80
80
|
];
|
|
@@ -18,10 +18,40 @@ export interface WorkflowDefSummary {
|
|
|
18
18
|
roles: string[];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Fresh objects per use: YAML stringify turns a repeated object into an
|
|
22
|
+
// anchor and alias, which the restricted project loader refuses.
|
|
23
|
+
const completed = () => ({ target: "$terminal", terminalStatus: "completed" });
|
|
24
|
+
const failed = () => ({ target: "$terminal", terminalStatus: "failed" });
|
|
25
|
+
|
|
26
|
+
/** The project's `test` gate from `kxm init`. A gate step settles on `passed` or
|
|
27
|
+
* `implementation-failure`, never `failed`. `maxAttempts` counts entries into a
|
|
28
|
+
* step, so every step a back-edge re-enters allows one entry per transition. */
|
|
29
|
+
function verifyStep(retryStep: string): Record<string, unknown> {
|
|
30
|
+
return {
|
|
31
|
+
id: "verify",
|
|
32
|
+
kind: "gate",
|
|
33
|
+
gate: "test",
|
|
34
|
+
expect: "pass",
|
|
35
|
+
maxAttempts: 3,
|
|
36
|
+
repositories: { control: "write" },
|
|
37
|
+
on: {
|
|
38
|
+
passed: completed(),
|
|
39
|
+
"implementation-failure": { target: retryStep, maxTransitions: 2 },
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Built-in `kxm workflow add --template` definitions. Each is a complete
|
|
46
|
+
* kxm.workflow.v1 document (the file name is the workflow id) that uses only
|
|
47
|
+
* what `kxm init` creates: the coordinator and implementer agents, the control
|
|
48
|
+
* repository, and the `test` gate. Agent steps declare `failed` because the
|
|
49
|
+
* producer falls back to it.
|
|
50
|
+
*/
|
|
21
51
|
export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
|
|
22
52
|
"implement-and-verify": {
|
|
23
53
|
schema: "kxm.workflow.v1",
|
|
24
|
-
description: "
|
|
54
|
+
description: "Implement a change, then run the project's test gate; a failing gate sends the work back to implement.",
|
|
25
55
|
coordinator: "coordinator",
|
|
26
56
|
limits: {
|
|
27
57
|
maxTransitions: 8,
|
|
@@ -30,38 +60,20 @@ export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
|
|
|
30
60
|
{
|
|
31
61
|
id: "implement",
|
|
32
62
|
kind: "agent",
|
|
33
|
-
|
|
34
|
-
maxAttempts:
|
|
63
|
+
agent: "implementer",
|
|
64
|
+
maxAttempts: 3,
|
|
65
|
+
repositories: { control: "write" },
|
|
35
66
|
on: {
|
|
36
67
|
passed: "verify",
|
|
37
|
-
failed:
|
|
38
|
-
target: "$terminal",
|
|
39
|
-
terminalStatus: "failed",
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
id: "verify",
|
|
45
|
-
kind: "gate",
|
|
46
|
-
gate: "verify-gate",
|
|
47
|
-
expect: "pass",
|
|
48
|
-
maxAttempts: 1,
|
|
49
|
-
on: {
|
|
50
|
-
passed: {
|
|
51
|
-
target: "$terminal",
|
|
52
|
-
terminalStatus: "completed",
|
|
53
|
-
},
|
|
54
|
-
failed: {
|
|
55
|
-
target: "implement",
|
|
56
|
-
maxTransitions: 2,
|
|
57
|
-
},
|
|
68
|
+
failed: failed(),
|
|
58
69
|
},
|
|
59
70
|
},
|
|
71
|
+
verifyStep("implement"),
|
|
60
72
|
],
|
|
61
73
|
},
|
|
62
74
|
"dual-critic-review": {
|
|
63
75
|
schema: "kxm.workflow.v1",
|
|
64
|
-
description: "
|
|
76
|
+
description: "Implement, review twice, then run the project's test gate. Both reviews run as the coordinator agent; for independent critics, add agents under .kxm/agents and point review-arch and review-cli at them.",
|
|
65
77
|
coordinator: "coordinator",
|
|
66
78
|
limits: {
|
|
67
79
|
maxTransitions: 12,
|
|
@@ -70,102 +82,92 @@ export const WORKFLOW_TEMPLATES: Record<string, Record<string, unknown>> = {
|
|
|
70
82
|
{
|
|
71
83
|
id: "implement",
|
|
72
84
|
kind: "agent",
|
|
73
|
-
|
|
74
|
-
maxAttempts:
|
|
85
|
+
agent: "implementer",
|
|
86
|
+
maxAttempts: 3,
|
|
87
|
+
repositories: { control: "write" },
|
|
75
88
|
on: {
|
|
76
89
|
passed: "review-arch",
|
|
77
|
-
failed:
|
|
78
|
-
target: "$terminal",
|
|
79
|
-
terminalStatus: "failed",
|
|
80
|
-
},
|
|
90
|
+
failed: failed(),
|
|
81
91
|
},
|
|
82
92
|
},
|
|
83
93
|
{
|
|
84
94
|
id: "review-arch",
|
|
85
95
|
kind: "agent",
|
|
86
|
-
|
|
87
|
-
maxAttempts:
|
|
96
|
+
agent: "coordinator",
|
|
97
|
+
maxAttempts: 3,
|
|
98
|
+
repositories: { control: "read" },
|
|
88
99
|
on: {
|
|
89
100
|
passed: "review-cli",
|
|
90
|
-
failed: {
|
|
91
|
-
target: "implement",
|
|
92
|
-
maxTransitions: 2,
|
|
93
|
-
},
|
|
101
|
+
failed: { target: "implement", maxTransitions: 2 },
|
|
94
102
|
},
|
|
95
103
|
},
|
|
96
104
|
{
|
|
97
105
|
id: "review-cli",
|
|
98
106
|
kind: "agent",
|
|
99
|
-
|
|
100
|
-
maxAttempts:
|
|
107
|
+
agent: "coordinator",
|
|
108
|
+
maxAttempts: 3,
|
|
109
|
+
repositories: { control: "read" },
|
|
101
110
|
on: {
|
|
102
111
|
passed: "verify",
|
|
103
|
-
failed: {
|
|
104
|
-
target: "implement",
|
|
105
|
-
maxTransitions: 2,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
id: "verify",
|
|
111
|
-
kind: "gate",
|
|
112
|
-
gate: "verify-gate",
|
|
113
|
-
expect: "pass",
|
|
114
|
-
maxAttempts: 1,
|
|
115
|
-
on: {
|
|
116
|
-
passed: {
|
|
117
|
-
target: "$terminal",
|
|
118
|
-
terminalStatus: "completed",
|
|
119
|
-
},
|
|
120
|
-
failed: {
|
|
121
|
-
target: "implement",
|
|
122
|
-
maxTransitions: 2,
|
|
123
|
-
},
|
|
112
|
+
failed: { target: "implement", maxTransitions: 2 },
|
|
124
113
|
},
|
|
125
114
|
},
|
|
115
|
+
verifyStep("implement"),
|
|
126
116
|
],
|
|
127
117
|
},
|
|
128
118
|
"spec-and-plan": {
|
|
129
119
|
schema: "kxm.workflow.v1",
|
|
130
|
-
description: "
|
|
120
|
+
description: "Plan a change, then review the plan. Both steps run as the coordinator agent and only read the repository.",
|
|
131
121
|
coordinator: "coordinator",
|
|
132
122
|
limits: {
|
|
133
|
-
maxTransitions:
|
|
123
|
+
maxTransitions: 8,
|
|
134
124
|
},
|
|
135
125
|
steps: [
|
|
136
126
|
{
|
|
137
127
|
id: "plan",
|
|
138
128
|
kind: "agent",
|
|
139
|
-
|
|
140
|
-
maxAttempts:
|
|
129
|
+
agent: "coordinator",
|
|
130
|
+
maxAttempts: 3,
|
|
131
|
+
repositories: { control: "read" },
|
|
141
132
|
on: {
|
|
142
133
|
passed: "review-arch",
|
|
143
|
-
failed:
|
|
144
|
-
target: "$terminal",
|
|
145
|
-
terminalStatus: "failed",
|
|
146
|
-
},
|
|
134
|
+
failed: failed(),
|
|
147
135
|
},
|
|
148
136
|
},
|
|
149
137
|
{
|
|
150
138
|
id: "review-arch",
|
|
151
139
|
kind: "agent",
|
|
152
|
-
|
|
153
|
-
maxAttempts:
|
|
140
|
+
agent: "coordinator",
|
|
141
|
+
maxAttempts: 3,
|
|
142
|
+
repositories: { control: "read" },
|
|
154
143
|
on: {
|
|
155
|
-
passed:
|
|
156
|
-
|
|
157
|
-
terminalStatus: "completed",
|
|
158
|
-
},
|
|
159
|
-
failed: {
|
|
160
|
-
target: "plan",
|
|
161
|
-
maxTransitions: 2,
|
|
162
|
-
},
|
|
144
|
+
passed: completed(),
|
|
145
|
+
failed: { target: "plan", maxTransitions: 2 },
|
|
163
146
|
},
|
|
164
147
|
},
|
|
165
148
|
],
|
|
166
149
|
},
|
|
167
150
|
};
|
|
168
151
|
|
|
152
|
+
/** The one-step definition `kxm workflow add <id>` writes without a template or file. */
|
|
153
|
+
export function scaffoldWorkflowDefinition(description: string): Record<string, unknown> {
|
|
154
|
+
return {
|
|
155
|
+
schema: "kxm.workflow.v1",
|
|
156
|
+
description,
|
|
157
|
+
coordinator: "coordinator",
|
|
158
|
+
limits: { maxTransitions: 8 },
|
|
159
|
+
steps: [
|
|
160
|
+
{
|
|
161
|
+
id: "step-1",
|
|
162
|
+
kind: "agent",
|
|
163
|
+
agent: "implementer",
|
|
164
|
+
repositories: { control: "write" },
|
|
165
|
+
on: { passed: completed(), failed: failed() },
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
169
171
|
export const DEFAULT_WORKFLOW_TEMPLATE = WORKFLOW_TEMPLATES["implement-and-verify"]!;
|
|
170
172
|
|
|
171
173
|
export function workflowsDirectory(
|