xcoder-dna 1.3.1 → 1.4.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/dist/cli/delegate.d.ts +90 -0
- package/dist/cli/delegate.js +220 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +2 -1
- package/package.json +3 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Command } from "commander";
|
|
2
|
+
/** The closed allowlist of agent CLIs that may be reached through `xcoder`. */
|
|
3
|
+
export declare const DELEGABLE_TARGETS: readonly ["jarvis", "litebro", "bugfinder"];
|
|
4
|
+
export type DelegableTarget = (typeof DELEGABLE_TARGETS)[number];
|
|
5
|
+
/** Shell convention for "command not found", identical on every platform. */
|
|
6
|
+
export declare const EXIT_ENOENT = 127;
|
|
7
|
+
/**
|
|
8
|
+
* Interrupted. POSIX would be 128+SIGINT (130); Windows has no signal-number
|
|
9
|
+
* convention, so one deterministic, testable constant is used everywhere.
|
|
10
|
+
*/
|
|
11
|
+
export declare const EXIT_INTERRUPTED = 130;
|
|
12
|
+
export interface DelegateSpec {
|
|
13
|
+
/** Allowlisted target name as typed by the user. */
|
|
14
|
+
readonly target: DelegableTarget;
|
|
15
|
+
/** Executable name on PATH, used when the workspace build is absent. */
|
|
16
|
+
readonly bin: string;
|
|
17
|
+
/** Workspace-relative built entry point, tried before PATH. */
|
|
18
|
+
readonly fallbackModule: string;
|
|
19
|
+
/** One-line description used in command help. */
|
|
20
|
+
readonly summary: string;
|
|
21
|
+
}
|
|
22
|
+
/** Minimal child handle — the subset of ChildProcess this module relies on. */
|
|
23
|
+
export interface ChildHandle {
|
|
24
|
+
once(event: "exit", listener: (code: number | null, signal: string | null) => void): unknown;
|
|
25
|
+
once(event: "error", listener: (error: NodeJS.ErrnoException) => void): unknown;
|
|
26
|
+
kill(signal?: NodeJS.Signals | number): boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface SpawnOptions {
|
|
29
|
+
cwd: string;
|
|
30
|
+
env: NodeJS.ProcessEnv;
|
|
31
|
+
stdio: "inherit";
|
|
32
|
+
shell: false;
|
|
33
|
+
}
|
|
34
|
+
export type SpawnFn = (cmd: string, argv: readonly string[], opts: SpawnOptions) => ChildHandle;
|
|
35
|
+
export interface DelegateDeps {
|
|
36
|
+
readonly spawn?: SpawnFn;
|
|
37
|
+
readonly resolveBin?: (spec: DelegateSpec, cwd: string) => {
|
|
38
|
+
command: string;
|
|
39
|
+
argv: string[];
|
|
40
|
+
} | null;
|
|
41
|
+
readonly env?: NodeJS.ProcessEnv;
|
|
42
|
+
readonly cwd?: string;
|
|
43
|
+
readonly stderr?: NodeJS.WritableStream;
|
|
44
|
+
}
|
|
45
|
+
/** The jarvis spec, shared by the `xcoder` CLI and the aggregator's DISPATCH. */
|
|
46
|
+
export declare const JARVIS_SPEC: DelegateSpec;
|
|
47
|
+
export declare function isDelegableTarget(name: string): name is DelegableTarget;
|
|
48
|
+
/** The spec for an allowlisted target, or `undefined` for anything else. */
|
|
49
|
+
export declare function delegateSpec(target: string): DelegateSpec | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Resolve what to execute.
|
|
52
|
+
*
|
|
53
|
+
* The workspace build is tried first so a developer running from a checkout gets
|
|
54
|
+
* the code they just built; PATH is the declared fallback once published. The
|
|
55
|
+
* choice is returned so callers can log it (Spoofing control, design T4).
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveDelegateCommand(spec: DelegateSpec, cwd: string): {
|
|
58
|
+
command: string;
|
|
59
|
+
argv: string[];
|
|
60
|
+
} | null;
|
|
61
|
+
/**
|
|
62
|
+
* Run the child with `argv` verbatim and return the code the parent must exit with.
|
|
63
|
+
*
|
|
64
|
+
* Never builds a command string, never parses, normalises, reorders or drops an
|
|
65
|
+
* argument, and never throws for an expected condition (spawn failure → 1).
|
|
66
|
+
*/
|
|
67
|
+
export declare function runDelegated(spec: DelegateSpec, argv: readonly string[], deps?: DelegateDeps): Promise<number>;
|
|
68
|
+
/**
|
|
69
|
+
* Register `<target> [args...]` on `parent` with pure passthrough semantics.
|
|
70
|
+
*
|
|
71
|
+
* Registering makes the target discoverable in `xcoder --help`. Argument
|
|
72
|
+
* handling belongs to `extractDelegatedArg`, because commander would otherwise
|
|
73
|
+
* consume root options that the child legitimately accepts.
|
|
74
|
+
*/
|
|
75
|
+
export declare function registerDelegatedCommand(parent: Command, spec: DelegateSpec): void;
|
|
76
|
+
export interface DelegatedInvocation {
|
|
77
|
+
readonly spec: DelegateSpec;
|
|
78
|
+
/** Everything after the target, untouched. */
|
|
79
|
+
readonly args: string[];
|
|
80
|
+
/** Index of the target token in the argv that was scanned. */
|
|
81
|
+
readonly index: number;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Detect a delegation from raw argv, before commander sees anything.
|
|
85
|
+
*
|
|
86
|
+
* Returns `undefined` for every ordinary invocation, so existing commands are
|
|
87
|
+
* parsed exactly as before. When a target is found, everything after it is
|
|
88
|
+
* returned untouched — the parent never validates a child flag.
|
|
89
|
+
*/
|
|
90
|
+
export declare function extractDelegatedArg(argv: readonly string[]): DelegatedInvocation | undefined;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// xcoder:version 6 | xcoder:task task-691585 | xcoder:updated 2026-11-05T05:46:07.113Z | xcoder:subtask code-st-b5251b
|
|
2
|
+
// Cross-CLI delegation seam.
|
|
3
|
+
//
|
|
4
|
+
// `xcoder jarvis <args...>` runs the real `jarvis` binary in a child process
|
|
5
|
+
// and forwards every argument verbatim. This module owns that seam and nothing
|
|
6
|
+
// else: it does not know what jarvis does, and it never interprets a child flag.
|
|
7
|
+
//
|
|
8
|
+
// Why a subprocess and not an import: boundary rule 1 (`scripts/check-boundaries.mjs`)
|
|
9
|
+
// forbids one agent importing another, and the `xcoder` bin lives in
|
|
10
|
+
// `@xcoder/sdlc-agent`. The only legal shared home for this code is this library.
|
|
11
|
+
//
|
|
12
|
+
// Why argv is pre-extracted instead of parsed by commander: commander eagerly
|
|
13
|
+
// consumes root options (`--config`, `--workspace`) it sees after a subcommand,
|
|
14
|
+
// which would silently swallow a child flag. The workspace already solves this
|
|
15
|
+
// class of problem the same way for `--config` and `--wbc` (see `wbc-arg.ts`),
|
|
16
|
+
// so delegation follows that precedent rather than adding a new parsing mode.
|
|
17
|
+
import { spawn } from "node:child_process";
|
|
18
|
+
import { existsSync } from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
/** The closed allowlist of agent CLIs that may be reached through `xcoder`. */
|
|
21
|
+
export const DELEGABLE_TARGETS = ["jarvis", "litebro", "bugfinder"];
|
|
22
|
+
/** Shell convention for "command not found", identical on every platform. */
|
|
23
|
+
export const EXIT_ENOENT = 127;
|
|
24
|
+
/**
|
|
25
|
+
* Interrupted. POSIX would be 128+SIGINT (130); Windows has no signal-number
|
|
26
|
+
* convention, so one deterministic, testable constant is used everywhere.
|
|
27
|
+
*/
|
|
28
|
+
export const EXIT_INTERRUPTED = 130;
|
|
29
|
+
/** The jarvis spec, shared by the `xcoder` CLI and the aggregator's DISPATCH. */
|
|
30
|
+
export const JARVIS_SPEC = {
|
|
31
|
+
target: "jarvis",
|
|
32
|
+
bin: "jarvis",
|
|
33
|
+
fallbackModule: path.join("packages", "jarvis-agent", "dist", "program.js"),
|
|
34
|
+
summary: "Jarvis — ReAct agent with scoped memory and an enforced tool allowlist",
|
|
35
|
+
};
|
|
36
|
+
const SPECS = {
|
|
37
|
+
jarvis: JARVIS_SPEC,
|
|
38
|
+
litebro: {
|
|
39
|
+
target: "litebro",
|
|
40
|
+
bin: "litebro",
|
|
41
|
+
fallbackModule: path.join("packages", "litebro", "dist", "cli.js"),
|
|
42
|
+
summary: "Litebro — tool-less planner/broker",
|
|
43
|
+
},
|
|
44
|
+
bugfinder: {
|
|
45
|
+
target: "bugfinder",
|
|
46
|
+
bin: "bugfinder",
|
|
47
|
+
fallbackModule: path.join("packages", "qa-agent", "dist", "qa-cli.js"),
|
|
48
|
+
summary: "Bugfinder — the QA agent",
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
export function isDelegableTarget(name) {
|
|
52
|
+
return DELEGABLE_TARGETS.includes(name);
|
|
53
|
+
}
|
|
54
|
+
/** The spec for an allowlisted target, or `undefined` for anything else. */
|
|
55
|
+
export function delegateSpec(target) {
|
|
56
|
+
return isDelegableTarget(target) ? SPECS[target] : undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve what to execute.
|
|
60
|
+
*
|
|
61
|
+
* The workspace build is tried first so a developer running from a checkout gets
|
|
62
|
+
* the code they just built; PATH is the declared fallback once published. The
|
|
63
|
+
* choice is returned so callers can log it (Spoofing control, design T4).
|
|
64
|
+
*/
|
|
65
|
+
export function resolveDelegateCommand(spec, cwd) {
|
|
66
|
+
const local = path.resolve(cwd, spec.fallbackModule);
|
|
67
|
+
if (existsSync(local))
|
|
68
|
+
return { command: process.execPath, argv: [local] };
|
|
69
|
+
return { command: spec.bin, argv: [] };
|
|
70
|
+
}
|
|
71
|
+
function validTargetsMessage() {
|
|
72
|
+
return `valid targets: ${DELEGABLE_TARGETS.join(", ")}`;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Run the child with `argv` verbatim and return the code the parent must exit with.
|
|
76
|
+
*
|
|
77
|
+
* Never builds a command string, never parses, normalises, reorders or drops an
|
|
78
|
+
* argument, and never throws for an expected condition (spawn failure → 1).
|
|
79
|
+
*/
|
|
80
|
+
export async function runDelegated(spec, argv, deps = {}) {
|
|
81
|
+
const stderr = deps.stderr ?? process.stderr;
|
|
82
|
+
if (!isDelegableTarget(spec.target)) {
|
|
83
|
+
stderr.write(`xcoder: '${spec.target}' is not a delegable target — ${validTargetsMessage()}\n`);
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
87
|
+
const resolve = deps.resolveBin ?? ((s, c) => resolveDelegateCommand(s, c));
|
|
88
|
+
const launch = resolve(spec, cwd);
|
|
89
|
+
if (launch === null) {
|
|
90
|
+
stderr.write(`xcoder: cannot find '${spec.bin}' — install it or build the workspace (${spec.fallbackModule}). ${validTargetsMessage()}\n`);
|
|
91
|
+
return EXIT_ENOENT;
|
|
92
|
+
}
|
|
93
|
+
if (deps.env?.XCODER_DELEGATE_VERBOSE ?? process.env.XCODER_DELEGATE_VERBOSE) {
|
|
94
|
+
// Counts and identifiers only: never the arguments themselves (no task text,
|
|
95
|
+
// no PII), never the environment.
|
|
96
|
+
stderr.write(`xcoder: delegating to ${spec.target} via ${launch.command} (${argv.length} argument${argv.length === 1 ? "" : "s"})\n`);
|
|
97
|
+
}
|
|
98
|
+
const spawnFn = deps.spawn ?? spawn;
|
|
99
|
+
const env = deps.env ?? process.env;
|
|
100
|
+
return new Promise((resolvePromise) => {
|
|
101
|
+
let child;
|
|
102
|
+
try {
|
|
103
|
+
child = spawnFn(launch.command, [...launch.argv, ...argv], {
|
|
104
|
+
cwd,
|
|
105
|
+
env,
|
|
106
|
+
stdio: "inherit",
|
|
107
|
+
shell: false,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
// Fail closed: a delegation that never started is not a success.
|
|
112
|
+
stderr.write(`xcoder: failed to start '${spec.bin}': ${err.message}\n`);
|
|
113
|
+
resolvePromise(1);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const forward = (signal) => {
|
|
117
|
+
try {
|
|
118
|
+
child.kill(signal);
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
// The child is already gone; the exit handler owns the outcome.
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const onSigint = () => forward("SIGINT");
|
|
125
|
+
const onSigterm = () => forward("SIGTERM");
|
|
126
|
+
process.on("SIGINT", onSigint);
|
|
127
|
+
process.on("SIGTERM", onSigterm);
|
|
128
|
+
const settle = (code) => {
|
|
129
|
+
process.off("SIGINT", onSigint);
|
|
130
|
+
process.off("SIGTERM", onSigterm);
|
|
131
|
+
resolvePromise(code);
|
|
132
|
+
};
|
|
133
|
+
child.once("error", (err) => {
|
|
134
|
+
if (err.code === "ENOENT") {
|
|
135
|
+
stderr.write(`xcoder: cannot find '${spec.bin}' — ${validTargetsMessage()}\n`);
|
|
136
|
+
settle(EXIT_ENOENT);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
stderr.write(`xcoder: '${spec.bin}' failed to start: ${err.message}\n`);
|
|
140
|
+
settle(1);
|
|
141
|
+
});
|
|
142
|
+
child.once("exit", (code, _signal) => {
|
|
143
|
+
if (code !== null) {
|
|
144
|
+
// The child's own exit code is the parent's, unchanged (0/1/2 all pass through).
|
|
145
|
+
settle(code);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
settle(EXIT_INTERRUPTED);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Register `<target> [args...]` on `parent` with pure passthrough semantics.
|
|
154
|
+
*
|
|
155
|
+
* Registering makes the target discoverable in `xcoder --help`. Argument
|
|
156
|
+
* handling belongs to `extractDelegatedArg`, because commander would otherwise
|
|
157
|
+
* consume root options that the child legitimately accepts.
|
|
158
|
+
*/
|
|
159
|
+
export function registerDelegatedCommand(parent, spec) {
|
|
160
|
+
parent
|
|
161
|
+
.command(`${spec.target} [args...]`)
|
|
162
|
+
.description(`${spec.summary}. Every argument is forwarded verbatim.`)
|
|
163
|
+
.helpOption(false)
|
|
164
|
+
.allowUnknownOption(true)
|
|
165
|
+
.allowExcessArguments(true)
|
|
166
|
+
.action(async (_args, _opts, cmd) => {
|
|
167
|
+
const code = await runDelegated(spec, cmd.args);
|
|
168
|
+
process.exitCode = code;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Root options that take a value, so their value is never mistaken for a target.
|
|
173
|
+
*
|
|
174
|
+
* Closed on purpose: a wildcard here would let any argument become a command,
|
|
175
|
+
* and dropping an entry here would make `xcoder -task jarvis` run the `jarvis`
|
|
176
|
+
* binary instead of a task called "jarvis". Options with an *optional* value
|
|
177
|
+
* (`--resume`, `--debug-tools`) are listed too: their value can look exactly
|
|
178
|
+
* like a target, so the conservative reading is "do not delegate".
|
|
179
|
+
*/
|
|
180
|
+
const ROOT_OPTIONS_WITH_VALUE = new Set([
|
|
181
|
+
"--config",
|
|
182
|
+
"-w",
|
|
183
|
+
"--workspace",
|
|
184
|
+
"--wbc",
|
|
185
|
+
"--wbs",
|
|
186
|
+
"-task",
|
|
187
|
+
"--task",
|
|
188
|
+
"--design",
|
|
189
|
+
"--code",
|
|
190
|
+
"--defect",
|
|
191
|
+
"--budget",
|
|
192
|
+
"--max-healing",
|
|
193
|
+
"-c",
|
|
194
|
+
"--concurrency",
|
|
195
|
+
"--model",
|
|
196
|
+
"--spinner",
|
|
197
|
+
"--resume",
|
|
198
|
+
"--debug-tools",
|
|
199
|
+
]);
|
|
200
|
+
/**
|
|
201
|
+
* Detect a delegation from raw argv, before commander sees anything.
|
|
202
|
+
*
|
|
203
|
+
* Returns `undefined` for every ordinary invocation, so existing commands are
|
|
204
|
+
* parsed exactly as before. When a target is found, everything after it is
|
|
205
|
+
* returned untouched — the parent never validates a child flag.
|
|
206
|
+
*/
|
|
207
|
+
export function extractDelegatedArg(argv) {
|
|
208
|
+
for (let i = 2; i < argv.length; i++) {
|
|
209
|
+
const token = argv[i];
|
|
210
|
+
if (ROOT_OPTIONS_WITH_VALUE.has(token)) {
|
|
211
|
+
i++; // skip this option's value
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (token.startsWith("-"))
|
|
215
|
+
continue;
|
|
216
|
+
const spec = delegateSpec(token);
|
|
217
|
+
return spec ? { spec, args: argv.slice(i + 1), index: i } : undefined;
|
|
218
|
+
}
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { SubAgent } from "./agents/sub-agent.js";
|
|
|
7
7
|
export { addBacklogItem, updateBacklogItem, listBacklogItems, getBacklogItem } from "./backlog/backlog-store.js";
|
|
8
8
|
export { runPoPick } from "./backlog/po-agent.js";
|
|
9
9
|
export { runBenchmark, compareReports } from "./bench/run.js";
|
|
10
|
+
export { DELEGABLE_TARGETS, EXIT_ENOENT, EXIT_INTERRUPTED, JARVIS_SPEC, delegateSpec, extractDelegatedArg, isDelegableTarget, registerDelegatedCommand, resolveDelegateCommand, runDelegated, type DelegateDeps, type DelegateSpec, type DelegableTarget } from "./cli/delegate.js";
|
|
10
11
|
export { registerSwarmCommand } from "./cli/swarm.js";
|
|
11
12
|
export { registerTaskCommand } from "./cli/task.js";
|
|
12
13
|
export { extractWbcArg } from "./cli/wbc-arg.js";
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// xcoder:version 2 | xcoder:task task-691585 | xcoder:updated 2026-11-05T05:45:12.986Z | xcoder:subtask code-st-b5251b
|
|
1
2
|
// Public barrel — the only import path other packages may use.
|
|
2
3
|
export { runDebate } from "./agents/ai-chat-debate.js";
|
|
3
4
|
export { writeReport } from "./agents/ai-chat-report-writer.js";
|
|
@@ -8,6 +9,7 @@ export { SubAgent } from "./agents/sub-agent.js";
|
|
|
8
9
|
export { addBacklogItem, updateBacklogItem, listBacklogItems, getBacklogItem } from "./backlog/backlog-store.js";
|
|
9
10
|
export { runPoPick } from "./backlog/po-agent.js";
|
|
10
11
|
export { runBenchmark, compareReports } from "./bench/run.js";
|
|
12
|
+
export { DELEGABLE_TARGETS, EXIT_ENOENT, EXIT_INTERRUPTED, JARVIS_SPEC, delegateSpec, extractDelegatedArg, isDelegableTarget, registerDelegatedCommand, resolveDelegateCommand, runDelegated } from "./cli/delegate.js";
|
|
11
13
|
export { registerSwarmCommand } from "./cli/swarm.js";
|
|
12
14
|
export { registerTaskCommand } from "./cli/task.js";
|
|
13
15
|
export { extractWbcArg } from "./cli/wbc-arg.js";
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const XCODER_VERSION = "1.
|
|
1
|
+
export declare const XCODER_VERSION = "1.4.0";
|
|
2
2
|
export declare const XCODER_BUILD_TIMESTAMP = "2026-10-26T00:00:00.000Z";
|
package/dist/version.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task task-762833 | xcoder:updated 2026-11-05T05:18:45.488Z | xcoder:subtask code-st-bb4289
|
|
1
2
|
// AUTO-GENERATED by scripts/inject-version.mjs — DO NOT EDIT
|
|
2
3
|
// Regenerate: npm run build (or: node scripts/inject-version.mjs)
|
|
3
|
-
export const XCODER_VERSION = "1.
|
|
4
|
+
export const XCODER_VERSION = "1.4.0";
|
|
4
5
|
export const XCODER_BUILD_TIMESTAMP = "2026-10-26T00:00:00.000Z";
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "xcoder-dna",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Reusable Xcoder components: tools, skills, swarm, sub-agent runtime and shared orchestration primitives.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Neural Node Labs",
|
|
5
7
|
"type": "module",
|
|
6
8
|
"engines": {
|
|
7
9
|
"node": ">=20"
|