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.
@@ -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.3.0";
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.3.0";
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.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"