@wildorder/nightshift 0.1.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/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/agent-runner.d.ts +64 -0
- package/dist/agent-runner.d.ts.map +1 -0
- package/dist/agent-runner.js +128 -0
- package/dist/agent-runner.js.map +1 -0
- package/dist/agent-summary.d.ts +35 -0
- package/dist/agent-summary.d.ts.map +1 -0
- package/dist/agent-summary.js +95 -0
- package/dist/agent-summary.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +18 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +74 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +62 -0
- package/dist/config.js.map +1 -0
- package/dist/detect-package-manager.d.ts +15 -0
- package/dist/detect-package-manager.d.ts.map +1 -0
- package/dist/detect-package-manager.js +78 -0
- package/dist/detect-package-manager.js.map +1 -0
- package/dist/findings.d.ts +106 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +107 -0
- package/dist/findings.js.map +1 -0
- package/dist/graph.d.ts +30 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +87 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/program-memory.d.ts +252 -0
- package/dist/program-memory.d.ts.map +1 -0
- package/dist/program-memory.js +385 -0
- package/dist/program-memory.js.map +1 -0
- package/dist/project-manifest.d.ts +23 -0
- package/dist/project-manifest.d.ts.map +1 -0
- package/dist/project-manifest.js +78 -0
- package/dist/project-manifest.js.map +1 -0
- package/dist/prompt.d.ts +70 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +237 -0
- package/dist/prompt.js.map +1 -0
- package/dist/worktree-guard.d.ts +40 -0
- package/dist/worktree-guard.d.ts.map +1 -0
- package/dist/worktree-guard.js +91 -0
- package/dist/worktree-guard.js.map +1 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wing It Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# nightshift
|
|
2
|
+
|
|
3
|
+
**The crew that builds while you're gone.**
|
|
4
|
+
|
|
5
|
+
Nightshift runs walk-away, CI-dispatched, multi-workstream engineering
|
|
6
|
+
programs: a human plans interactively, launches, and leaves. The run always
|
|
7
|
+
terminates — in either a draft PR with a run report and a decision ledger,
|
|
8
|
+
or a partial build with a diagnosis, a rollback point, and a one-command
|
|
9
|
+
retry. Nothing blocks silently; nothing waits mid-run for a human who is
|
|
10
|
+
not there.
|
|
11
|
+
|
|
12
|
+
Trust comes from **reversibility and legibility, not prevention**: every
|
|
13
|
+
decision an agent makes is journaled with its alternatives, tagged to the
|
|
14
|
+
commit before it applied, and revocable by replay. You come back to a shift
|
|
15
|
+
handover note — what got built, what was decided along the way and why, and
|
|
16
|
+
how to flip anything you disagree with.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
Published as
|
|
21
|
+
[`@wildorder/nightshift`](https://www.npmjs.com/package/@wildorder/nightshift);
|
|
22
|
+
the executable is named `nightshift`. Always spell the full scoped name when
|
|
23
|
+
using `npx` outside a project that has it installed — `npx nightshift` looks
|
|
24
|
+
up the unscoped name on the registry, which is not this package (and an
|
|
25
|
+
unrelated `night-shift` package exists, so a typo installs someone else's
|
|
26
|
+
code):
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
npx --yes @wildorder/nightshift --help
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Status
|
|
33
|
+
|
|
34
|
+
Pre-alpha. The repository currently contains the founding charter
|
|
35
|
+
([docs/learnings.md](docs/learnings.md) — what its predecessor,
|
|
36
|
+
`@wildorder/program-pipeline`, taught us) and the harvested mechanical
|
|
37
|
+
core: headless agent spawning, the dependency graph, the manifest, the
|
|
38
|
+
append-only program memory that becomes the decision ledger, worktree
|
|
39
|
+
capture/restore, and package-manager detection. All ported with their
|
|
40
|
+
tests.
|
|
41
|
+
|
|
42
|
+
## The shape of a run
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
/plan-program HUMAN design, scope, decompose (interactive)
|
|
46
|
+
nightshift run ┬ author one clean agent per workstream, by dependency level
|
|
47
|
+
├ review critic→writer rounds (2–3), fail-open into the ledger
|
|
48
|
+
├ build implement, verify, commit; decisions journaled
|
|
49
|
+
└ report run report + decision ledger on the draft PR
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Four agent roles, four jobs (`nightshift.config.json`):
|
|
53
|
+
|
|
54
|
+
| Block | Job |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| `agent` | Implements workstreams. A cheaper model is usually right. |
|
|
57
|
+
| `deciderAgent` | Makes, ratifies, or escalates decisions — and composes the human ping when one is warranted. Expensive model, different provider than `agent`. |
|
|
58
|
+
| `reviewerAgent` | Independent second opinion: spec critique, test critique, whole-program review. Never the implementer. |
|
|
59
|
+
| `recoveryAgent` | Retries failed workstreams; a distinct provider also survives primary quota/session failures. |
|
|
60
|
+
|
|
61
|
+
Core rules, inherited from hard experience:
|
|
62
|
+
|
|
63
|
+
- **Decide-and-continue.** Agents surface decisions as fenced blocks; the
|
|
64
|
+
runner journals them, tags the pre-decision commit, and proceeds on the
|
|
65
|
+
best default. Two-way doors are ratified post-hoc by the decider;
|
|
66
|
+
one-way doors are decided before they apply. Escalation pings the human
|
|
67
|
+
but never blocks a reversible path.
|
|
68
|
+
- **Bounded loops fail open into the ledger, never closed into a gate.**
|
|
69
|
+
- **No self-grading.** Reviewer and decider are different providers than
|
|
70
|
+
the implementer; the runner runs verification itself and owns commits.
|
|
71
|
+
- **Failure isolation.** A failed workstream parks; everything outside its
|
|
72
|
+
downstream cone still builds. Partial + diagnosis is a valid outcome.
|
|
73
|
+
- **Every artifact is written for a human first.** Specs are narrative
|
|
74
|
+
documents; machine data lives in the manifest.
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
Requires Node.js 20+.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
npm ci
|
|
82
|
+
npm run build
|
|
83
|
+
npm run typecheck
|
|
84
|
+
npm test
|
|
85
|
+
npm run lint
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## License
|
|
89
|
+
|
|
90
|
+
MIT © 2026 Wing It Labs
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AgentConfig, NightshiftConfig } from "./config.js";
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
exitCode: number;
|
|
4
|
+
output: string;
|
|
5
|
+
/** Set when the prompt could not be fully delivered to the process stdin. */
|
|
6
|
+
inputError?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AgentInvocation {
|
|
9
|
+
command: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
prompt: string;
|
|
12
|
+
promptMode: "stdin" | "argument";
|
|
13
|
+
cwd: string;
|
|
14
|
+
onOutput?: (chunk: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export type AgentRunner = (invocation: AgentInvocation) => Promise<CommandResult>;
|
|
17
|
+
export type VerifyRunner = (command: string, cwd: string) => Promise<CommandResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Environment for spawned agents: the parent may itself be an agent session
|
|
20
|
+
* (Claude Code, Cursor), and inherited session markers can make the child CLI
|
|
21
|
+
* behave as if attached to that session instead of running as a clean
|
|
22
|
+
* headless agent.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sanitizedEnvironment(base?: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
|
|
25
|
+
export declare function runProcess(command: string, args: string[], options: {
|
|
26
|
+
cwd: string;
|
|
27
|
+
input?: string;
|
|
28
|
+
shell: boolean;
|
|
29
|
+
onOutput?: (chunk: string) => void;
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
}): Promise<CommandResult>;
|
|
32
|
+
export declare const defaultAgentRunner: AgentRunner;
|
|
33
|
+
export declare const defaultVerifyRunner: VerifyRunner;
|
|
34
|
+
export declare function tail(output: string, limit?: number): string;
|
|
35
|
+
/**
|
|
36
|
+
* The implementer: the `agent` block, or the environment fallback for hosts
|
|
37
|
+
* that have no config file yet.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveAgent(config: NightshiftConfig): AgentConfig | undefined;
|
|
40
|
+
export interface ResolvedRecoveryAgent {
|
|
41
|
+
agent: AgentConfig;
|
|
42
|
+
/** True when no dedicated recovery agent was configured. */
|
|
43
|
+
borrowedImplementer: boolean;
|
|
44
|
+
}
|
|
45
|
+
/** Recovery agent, falling back explicitly to the implementer. */
|
|
46
|
+
export declare function resolveRecoveryAgent(config: NightshiftConfig): ResolvedRecoveryAgent | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* The decider: makes, ratifies, or escalates decisions. Deliberately never
|
|
49
|
+
* falls back to the implementer — an absent decider means implementer
|
|
50
|
+
* defaults stand unratified, which is a policy the caller must surface, not
|
|
51
|
+
* a substitution to make silently. The implementer grading its own decisions
|
|
52
|
+
* is exactly the conflict of interest this role exists to remove.
|
|
53
|
+
*/
|
|
54
|
+
export declare function resolveDeciderAgent(config: NightshiftConfig): AgentConfig | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* The reviewer: the independent second opinion for spec critique, test
|
|
57
|
+
* critique, and whole-program review. Never falls back to the implementer —
|
|
58
|
+
* a reviewer that is the same process as the author defeats the point, so an
|
|
59
|
+
* absent block is reported to the caller rather than silently substituted.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveReviewerAgent(config: NightshiftConfig): AgentConfig | undefined;
|
|
62
|
+
/** Human-readable form of a resolved agent, for plan and report output. */
|
|
63
|
+
export declare function describeAgent(agent: AgentConfig): string;
|
|
64
|
+
//# sourceMappingURL=agent-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-runner.d.ts","sourceRoot":"","sources":["../src/agent-runner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,GAAG,UAAU,CAAC;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,UAAU,EAAE,eAAe,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAClF,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAMpF;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,GAAE,MAAM,CAAC,UAAwB,GACpC,MAAM,CAAC,UAAU,CAQnB;AAED,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,EAAE;IACP,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACzB,GACA,OAAO,CAAC,aAAa,CAAC,CAiCxB;AAED,eAAO,MAAM,kBAAkB,EAAE,WAqB9B,CAAC;AAEJ,eAAO,MAAM,mBAAmB,EAAE,YACa,CAAC;AAEhD,wBAAgB,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAO,GAAG,MAAM,CAEzD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,WAAW,GAAG,SAAS,CAO9E;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,WAAW,CAAC;IACnB,4DAA4D;IAC5D,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,GACvB,qBAAqB,GAAG,SAAS,CAOnC;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,gBAAgB,GACvB,WAAW,GAAG,SAAS,CAEzB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,gBAAgB,GACvB,WAAW,GAAG,SAAS,CAOzB;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAExD"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
// In-memory output is kept to a bounded tail; full output belongs in the
|
|
3
|
+
// per-workstream log via onOutput streaming.
|
|
4
|
+
const OUTPUT_TAIL_LIMIT = 200_000;
|
|
5
|
+
/**
|
|
6
|
+
* Environment for spawned agents: the parent may itself be an agent session
|
|
7
|
+
* (Claude Code, Cursor), and inherited session markers can make the child CLI
|
|
8
|
+
* behave as if attached to that session instead of running as a clean
|
|
9
|
+
* headless agent.
|
|
10
|
+
*/
|
|
11
|
+
export function sanitizedEnvironment(base = process.env) {
|
|
12
|
+
const env = {};
|
|
13
|
+
for (const [key, value] of Object.entries(base)) {
|
|
14
|
+
if (key === "CLAUDECODE" || key.startsWith("CLAUDE_CODE_"))
|
|
15
|
+
continue;
|
|
16
|
+
if (key === "CURSOR_AGENT" || key.startsWith("CURSOR_TRACE_"))
|
|
17
|
+
continue;
|
|
18
|
+
env[key] = value;
|
|
19
|
+
}
|
|
20
|
+
return env;
|
|
21
|
+
}
|
|
22
|
+
export function runProcess(command, args, options) {
|
|
23
|
+
return new Promise((resolvePromise, rejectPromise) => {
|
|
24
|
+
const child = spawn(command, args, {
|
|
25
|
+
cwd: options.cwd,
|
|
26
|
+
shell: options.shell,
|
|
27
|
+
windowsHide: true,
|
|
28
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
29
|
+
...(options.env ? { env: options.env } : {}),
|
|
30
|
+
});
|
|
31
|
+
let buffered = "";
|
|
32
|
+
const push = (chunk) => {
|
|
33
|
+
options.onOutput?.(chunk);
|
|
34
|
+
buffered = (buffered + chunk).slice(-OUTPUT_TAIL_LIMIT);
|
|
35
|
+
};
|
|
36
|
+
child.stdout.setEncoding("utf8");
|
|
37
|
+
child.stderr.setEncoding("utf8");
|
|
38
|
+
child.stdout.on("data", push);
|
|
39
|
+
child.stderr.on("data", push);
|
|
40
|
+
child.on("error", rejectPromise);
|
|
41
|
+
let inputError;
|
|
42
|
+
child.stdin.on("error", (error) => {
|
|
43
|
+
inputError = error;
|
|
44
|
+
});
|
|
45
|
+
child.on("close", (code) => resolvePromise({
|
|
46
|
+
exitCode: code ?? 1,
|
|
47
|
+
output: buffered,
|
|
48
|
+
...(inputError ? { inputError: inputError.message } : {}),
|
|
49
|
+
}));
|
|
50
|
+
if (options.input !== undefined)
|
|
51
|
+
child.stdin.end(options.input);
|
|
52
|
+
else
|
|
53
|
+
child.stdin.end();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export const defaultAgentRunner = (invocation) => runProcess(invocation.command, invocation.promptMode === "argument"
|
|
57
|
+
? [...invocation.args, invocation.prompt]
|
|
58
|
+
: invocation.args, {
|
|
59
|
+
cwd: invocation.cwd,
|
|
60
|
+
// A shell re-parses the argument list, and on Windows cmd.exe mangles
|
|
61
|
+
// multiline prompt arguments — so argument mode always spawns the
|
|
62
|
+
// command directly. The shell is only used on Windows for stdin mode,
|
|
63
|
+
// where it is needed to launch .cmd shims and cannot corrupt the
|
|
64
|
+
// prompt (which travels via stdin, not argv).
|
|
65
|
+
shell: process.platform === "win32" && invocation.promptMode !== "argument",
|
|
66
|
+
env: sanitizedEnvironment(),
|
|
67
|
+
...(invocation.onOutput ? { onOutput: invocation.onOutput } : {}),
|
|
68
|
+
...(invocation.promptMode === "stdin"
|
|
69
|
+
? { input: invocation.prompt }
|
|
70
|
+
: {}),
|
|
71
|
+
});
|
|
72
|
+
export const defaultVerifyRunner = (command, cwd) => runProcess(command, [], { cwd, shell: true });
|
|
73
|
+
export function tail(output, limit = 2000) {
|
|
74
|
+
return output.length > limit ? output.slice(-limit) : output;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The implementer: the `agent` block, or the environment fallback for hosts
|
|
78
|
+
* that have no config file yet.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveAgent(config) {
|
|
81
|
+
if (config.agent)
|
|
82
|
+
return config.agent;
|
|
83
|
+
const command = process.env.NIGHTSHIFT_AGENT_COMMAND;
|
|
84
|
+
if (command && command.trim().length > 0) {
|
|
85
|
+
return { command, args: [], promptMode: "stdin" };
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
/** Recovery agent, falling back explicitly to the implementer. */
|
|
90
|
+
export function resolveRecoveryAgent(config) {
|
|
91
|
+
if (config.recoveryAgent) {
|
|
92
|
+
return { agent: config.recoveryAgent, borrowedImplementer: false };
|
|
93
|
+
}
|
|
94
|
+
const implementer = resolveAgent(config);
|
|
95
|
+
if (implementer)
|
|
96
|
+
return { agent: implementer, borrowedImplementer: true };
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The decider: makes, ratifies, or escalates decisions. Deliberately never
|
|
101
|
+
* falls back to the implementer — an absent decider means implementer
|
|
102
|
+
* defaults stand unratified, which is a policy the caller must surface, not
|
|
103
|
+
* a substitution to make silently. The implementer grading its own decisions
|
|
104
|
+
* is exactly the conflict of interest this role exists to remove.
|
|
105
|
+
*/
|
|
106
|
+
export function resolveDeciderAgent(config) {
|
|
107
|
+
return config.deciderAgent;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The reviewer: the independent second opinion for spec critique, test
|
|
111
|
+
* critique, and whole-program review. Never falls back to the implementer —
|
|
112
|
+
* a reviewer that is the same process as the author defeats the point, so an
|
|
113
|
+
* absent block is reported to the caller rather than silently substituted.
|
|
114
|
+
*/
|
|
115
|
+
export function resolveReviewerAgent(config) {
|
|
116
|
+
if (config.reviewerAgent)
|
|
117
|
+
return config.reviewerAgent;
|
|
118
|
+
const command = process.env.NIGHTSHIFT_REVIEWER_COMMAND;
|
|
119
|
+
if (command && command.trim().length > 0) {
|
|
120
|
+
return { command, args: [], promptMode: "stdin" };
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
/** Human-readable form of a resolved agent, for plan and report output. */
|
|
125
|
+
export function describeAgent(agent) {
|
|
126
|
+
return [agent.command, ...agent.args].join(" ");
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=agent-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-runner.js","sourceRoot":"","sources":["../src/agent-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** The contract text, appended verbatim to every brief the runner composes. */
|
|
2
|
+
export declare function summaryContract(): string;
|
|
3
|
+
export interface AgentSummary {
|
|
4
|
+
/** The agent's own words, or an output tail when it emitted no block. */
|
|
5
|
+
text: string;
|
|
6
|
+
/** False when no block was found and `text` is a fallback tail. */
|
|
7
|
+
available: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The last summary block in the output. Last rather than first for the same
|
|
11
|
+
* reason {@link extractJson} takes the last json block: an agent that echoes
|
|
12
|
+
* the contract back before answering would otherwise have its echo parsed as
|
|
13
|
+
* the answer.
|
|
14
|
+
*/
|
|
15
|
+
export declare function extractSummary(output: string): string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* The summary for an agent reply, degrading to an output tail when the agent
|
|
18
|
+
* emitted no block. Never throws and never signals failure: callers record
|
|
19
|
+
* what they got and carry on.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveSummary(output: string, tailLimit?: number): AgentSummary;
|
|
22
|
+
/**
|
|
23
|
+
* Event payload fields, so every emitter names them the same way. `role` is
|
|
24
|
+
* the job the agent held (`build`, `critic`, `writer`, `author`, `validator`,
|
|
25
|
+
* `reviewer`), not the command it ran.
|
|
26
|
+
*
|
|
27
|
+
* `promptBytes` is deliberate telemetry: brief size is the input to every
|
|
28
|
+
* decision about how much context an agent can be given, and estimating it
|
|
29
|
+
* from spec line counts is guesswork. Recording it per invocation turns that
|
|
30
|
+
* into a measurement after one real run.
|
|
31
|
+
*/
|
|
32
|
+
export declare function summaryEventData(role: string, prompt: string, summary: AgentSummary): Record<string, unknown>;
|
|
33
|
+
/** Single-line form for progress output; the events log keeps the full text. */
|
|
34
|
+
export declare function summaryLine(summary: AgentSummary, limit?: number): string;
|
|
35
|
+
//# sourceMappingURL=agent-summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-summary.d.ts","sourceRoot":"","sources":["../src/agent-summary.ts"],"names":[],"mappings":"AAuCA,+EAA+E;AAC/E,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,MAAM,WAAW,YAAY;IAC3B,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAOjE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,SAAM,GAAG,YAAY,CAQ5E;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,GACpB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAOzB;AAED,gFAAgF;AAChF,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,SAAM,GAAG,MAAM,CAKtE"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { tail } from "./agent-runner.js";
|
|
2
|
+
/**
|
|
3
|
+
* Every spawned agent ends its reply with a summary block, and the runner
|
|
4
|
+
* reads it back verbatim.
|
|
5
|
+
*
|
|
6
|
+
* Before this, agent output had exactly two destinations: a machine parse
|
|
7
|
+
* (findings, verdicts) or a failure path (`tail()` inside an error message).
|
|
8
|
+
* A workstream that passed surfaced nothing about what the agent actually
|
|
9
|
+
* concluded — the only record was the raw stdout firehose in the
|
|
10
|
+
* per-workstream log, which is the entire transcript and therefore unread.
|
|
11
|
+
* And when the pipeline was driven from inside an orchestrating agent
|
|
12
|
+
* session, that orchestrator paraphrased whatever little did surface.
|
|
13
|
+
*
|
|
14
|
+
* The fix is the one the brief composer already applies in the other
|
|
15
|
+
* direction: the package owns the contract. Every brief the runner composes
|
|
16
|
+
* ends with the block below, every runner reads it back, and the text lands
|
|
17
|
+
* in the events log as the agent wrote it — not summarized, not rewritten.
|
|
18
|
+
*
|
|
19
|
+
* A missing block is never fatal. An agent that forgets the fence still did
|
|
20
|
+
* the work, and failing a verified workstream over a formatting slip would
|
|
21
|
+
* trade a real result for a cosmetic one.
|
|
22
|
+
*/
|
|
23
|
+
const CONTRACT = `
|
|
24
|
+
## Summary (required)
|
|
25
|
+
|
|
26
|
+
End your reply with this block. It is the only part of your output a human
|
|
27
|
+
will read, so write it for a human:
|
|
28
|
+
|
|
29
|
+
\`\`\`summary
|
|
30
|
+
Two to five sentences: what you did, the decisions you made and why, and
|
|
31
|
+
anything you were unsure about or had to assume. Do not restate these
|
|
32
|
+
instructions.
|
|
33
|
+
\`\`\`
|
|
34
|
+
|
|
35
|
+
Put it last. If you also return a \`\`\`json block, both must be present.
|
|
36
|
+
`.trim();
|
|
37
|
+
/** The contract text, appended verbatim to every brief the runner composes. */
|
|
38
|
+
export function summaryContract() {
|
|
39
|
+
return CONTRACT;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The last summary block in the output. Last rather than first for the same
|
|
43
|
+
* reason {@link extractJson} takes the last json block: an agent that echoes
|
|
44
|
+
* the contract back before answering would otherwise have its echo parsed as
|
|
45
|
+
* the answer.
|
|
46
|
+
*/
|
|
47
|
+
export function extractSummary(output) {
|
|
48
|
+
const matches = [...output.matchAll(/```summary[^\S\r\n]*\r?\n([\s\S]*?)```/gu)];
|
|
49
|
+
for (const match of matches.reverse()) {
|
|
50
|
+
const text = match[1]?.trim();
|
|
51
|
+
if (text !== undefined && text !== "")
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The summary for an agent reply, degrading to an output tail when the agent
|
|
58
|
+
* emitted no block. Never throws and never signals failure: callers record
|
|
59
|
+
* what they got and carry on.
|
|
60
|
+
*/
|
|
61
|
+
export function resolveSummary(output, tailLimit = 600) {
|
|
62
|
+
const text = extractSummary(output);
|
|
63
|
+
if (text !== undefined)
|
|
64
|
+
return { text, available: true };
|
|
65
|
+
const fallback = tail(output, tailLimit).trim();
|
|
66
|
+
return {
|
|
67
|
+
text: fallback === "" ? "(no output)" : fallback,
|
|
68
|
+
available: false,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Event payload fields, so every emitter names them the same way. `role` is
|
|
73
|
+
* the job the agent held (`build`, `critic`, `writer`, `author`, `validator`,
|
|
74
|
+
* `reviewer`), not the command it ran.
|
|
75
|
+
*
|
|
76
|
+
* `promptBytes` is deliberate telemetry: brief size is the input to every
|
|
77
|
+
* decision about how much context an agent can be given, and estimating it
|
|
78
|
+
* from spec line counts is guesswork. Recording it per invocation turns that
|
|
79
|
+
* into a measurement after one real run.
|
|
80
|
+
*/
|
|
81
|
+
export function summaryEventData(role, prompt, summary) {
|
|
82
|
+
return {
|
|
83
|
+
role,
|
|
84
|
+
summary: summary.text,
|
|
85
|
+
summaryAvailable: summary.available,
|
|
86
|
+
promptBytes: Buffer.byteLength(prompt, "utf8"),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/** Single-line form for progress output; the events log keeps the full text. */
|
|
90
|
+
export function summaryLine(summary, limit = 240) {
|
|
91
|
+
const collapsed = summary.text.replace(/\s+/gu, " ").trim();
|
|
92
|
+
const clipped = collapsed.length > limit ? `${collapsed.slice(0, limit - 1)}…` : collapsed;
|
|
93
|
+
return summary.available ? clipped : `(no summary block) ${clipped}`;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=agent-summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-summary.js","sourceRoot":"","sources":["../src/agent-summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,QAAQ,GAAG;;;;;;;;;;;;;CAahB,CAAC,IAAI,EAAE,CAAC;AAET,+EAA+E;AAC/E,MAAM,UAAU,eAAe;IAC7B,OAAO,QAAQ,CAAC;AAClB,CAAC;AASD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,OAAO,GAAG,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,0CAA0C,CAAC,CAAC,CAAC;IACjF,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;QAC9B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;IACrD,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,SAAS,GAAG,GAAG;IAC5D,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACpC,IAAI,IAAI,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACzD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,OAAO;QACL,IAAI,EAAE,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ;QAChD,SAAS,EAAE,KAAK;KACjB,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,MAAc,EACd,OAAqB;IAErB,OAAO;QACL,IAAI;QACJ,OAAO,EAAE,OAAO,CAAC,IAAI;QACrB,gBAAgB,EAAE,OAAO,CAAC,SAAS;QACnC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC;KAC/C,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,WAAW,CAAC,OAAqB,EAAE,KAAK,GAAG,GAAG;IAC5D,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,MAAM,OAAO,GACX,SAAS,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7E,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,sBAAsB,OAAO,EAAE,CAAC;AACvE,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
|
+
async function packageVersion() {
|
|
8
|
+
const raw = await readFile(resolve(packageRoot, "package.json"), "utf8");
|
|
9
|
+
return JSON.parse(raw).version;
|
|
10
|
+
}
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name("nightshift")
|
|
14
|
+
.description("The crew that builds while you're gone: walk-away engineering programs " +
|
|
15
|
+
"with a decision ledger instead of gates.")
|
|
16
|
+
.version(await packageVersion());
|
|
17
|
+
program.parse();
|
|
18
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;AAE3E,KAAK,UAAU,cAAc;IAC3B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAC;IACzE,OAAQ,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC,OAAO,CAAC;AAC1D,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,YAAY,CAAC;KAClB,WAAW,CACV,yEAAyE;IACvE,0CAA0C,CAC7C;KACA,OAAO,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;AAEnC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const NIGHTSHIFT_CONFIG_FILE = "nightshift.config.json";
|
|
3
|
+
declare const agentSchema: z.ZodObject<{
|
|
4
|
+
command: z.ZodString;
|
|
5
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
6
|
+
promptMode: z.ZodDefault<z.ZodEnum<{
|
|
7
|
+
stdin: "stdin";
|
|
8
|
+
argument: "argument";
|
|
9
|
+
}>>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type AgentConfig = z.infer<typeof agentSchema>;
|
|
12
|
+
/**
|
|
13
|
+
* Four roles, four jobs:
|
|
14
|
+
*
|
|
15
|
+
* - `agent` implements workstreams. A cheaper model is usually the right call.
|
|
16
|
+
* - `deciderAgent` makes, ratifies, or escalates decisions. Decisions are the
|
|
17
|
+
* highest-leverage, lowest-token work in a run — put the expensive model
|
|
18
|
+
* here, and by convention a different provider than `agent`.
|
|
19
|
+
* - `reviewerAgent` is the independent second opinion: spec critique, test
|
|
20
|
+
* critique, whole-program review. Never falls back to `agent` — a reviewer
|
|
21
|
+
* that is the same process as the author defeats the point.
|
|
22
|
+
* - `recoveryAgent` retries failed workstreams. A distinct provider also lets
|
|
23
|
+
* the runner recover from a primary token, quota, or session failure.
|
|
24
|
+
*/
|
|
25
|
+
declare const configSchema: z.ZodObject<{
|
|
26
|
+
agent: z.ZodOptional<z.ZodObject<{
|
|
27
|
+
command: z.ZodString;
|
|
28
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
29
|
+
promptMode: z.ZodDefault<z.ZodEnum<{
|
|
30
|
+
stdin: "stdin";
|
|
31
|
+
argument: "argument";
|
|
32
|
+
}>>;
|
|
33
|
+
}, z.core.$strip>>;
|
|
34
|
+
deciderAgent: z.ZodOptional<z.ZodObject<{
|
|
35
|
+
command: z.ZodString;
|
|
36
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
37
|
+
promptMode: z.ZodDefault<z.ZodEnum<{
|
|
38
|
+
stdin: "stdin";
|
|
39
|
+
argument: "argument";
|
|
40
|
+
}>>;
|
|
41
|
+
}, z.core.$strip>>;
|
|
42
|
+
reviewerAgent: z.ZodOptional<z.ZodObject<{
|
|
43
|
+
command: z.ZodString;
|
|
44
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
45
|
+
promptMode: z.ZodDefault<z.ZodEnum<{
|
|
46
|
+
stdin: "stdin";
|
|
47
|
+
argument: "argument";
|
|
48
|
+
}>>;
|
|
49
|
+
}, z.core.$strip>>;
|
|
50
|
+
recoveryAgent: z.ZodOptional<z.ZodObject<{
|
|
51
|
+
command: z.ZodString;
|
|
52
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
53
|
+
promptMode: z.ZodDefault<z.ZodEnum<{
|
|
54
|
+
stdin: "stdin";
|
|
55
|
+
argument: "argument";
|
|
56
|
+
}>>;
|
|
57
|
+
}, z.core.$strip>>;
|
|
58
|
+
verify: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
export type NightshiftConfig = z.infer<typeof configSchema>;
|
|
61
|
+
export declare const DEFAULT_CONFIG: NightshiftConfig;
|
|
62
|
+
export interface LoadedConfig {
|
|
63
|
+
config: NightshiftConfig;
|
|
64
|
+
/** Absolute path of the file that was read, when one existed. */
|
|
65
|
+
path?: string;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Read `nightshift.config.json` from the project root. A missing file is the
|
|
69
|
+
* defaults; a malformed file is an error — silently substituting defaults for
|
|
70
|
+
* a config the user wrote would run the wrong agents.
|
|
71
|
+
*/
|
|
72
|
+
export declare function loadConfig(cwd: string): Promise<LoadedConfig>;
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAE/D,QAAA,MAAM,WAAW;;;;;;;iBAIf,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD;;;;;;;;;;;;GAYG;AACH,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAOhB,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAE5D,eAAO,MAAM,cAAc,EAAE,gBAAyC,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,gBAAgB,CAAC;IACzB,iEAAiE;IACjE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAyBnE"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
export const NIGHTSHIFT_CONFIG_FILE = "nightshift.config.json";
|
|
5
|
+
const agentSchema = z.object({
|
|
6
|
+
command: z.string().min(1),
|
|
7
|
+
args: z.array(z.string()).default([]),
|
|
8
|
+
promptMode: z.enum(["stdin", "argument"]).default("stdin"),
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* Four roles, four jobs:
|
|
12
|
+
*
|
|
13
|
+
* - `agent` implements workstreams. A cheaper model is usually the right call.
|
|
14
|
+
* - `deciderAgent` makes, ratifies, or escalates decisions. Decisions are the
|
|
15
|
+
* highest-leverage, lowest-token work in a run — put the expensive model
|
|
16
|
+
* here, and by convention a different provider than `agent`.
|
|
17
|
+
* - `reviewerAgent` is the independent second opinion: spec critique, test
|
|
18
|
+
* critique, whole-program review. Never falls back to `agent` — a reviewer
|
|
19
|
+
* that is the same process as the author defeats the point.
|
|
20
|
+
* - `recoveryAgent` retries failed workstreams. A distinct provider also lets
|
|
21
|
+
* the runner recover from a primary token, quota, or session failure.
|
|
22
|
+
*/
|
|
23
|
+
const configSchema = z.object({
|
|
24
|
+
agent: agentSchema.optional(),
|
|
25
|
+
deciderAgent: agentSchema.optional(),
|
|
26
|
+
reviewerAgent: agentSchema.optional(),
|
|
27
|
+
recoveryAgent: agentSchema.optional(),
|
|
28
|
+
/** Named verification commands run by the runner itself, never by agents. */
|
|
29
|
+
verify: z.record(z.string(), z.string()).default({}),
|
|
30
|
+
});
|
|
31
|
+
export const DEFAULT_CONFIG = configSchema.parse({});
|
|
32
|
+
/**
|
|
33
|
+
* Read `nightshift.config.json` from the project root. A missing file is the
|
|
34
|
+
* defaults; a malformed file is an error — silently substituting defaults for
|
|
35
|
+
* a config the user wrote would run the wrong agents.
|
|
36
|
+
*/
|
|
37
|
+
export async function loadConfig(cwd) {
|
|
38
|
+
const path = join(cwd, NIGHTSHIFT_CONFIG_FILE);
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = await readFile(path, "utf8");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { config: DEFAULT_CONFIG };
|
|
45
|
+
}
|
|
46
|
+
let parsed;
|
|
47
|
+
try {
|
|
48
|
+
parsed = JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
throw new Error(`${NIGHTSHIFT_CONFIG_FILE} is not valid JSON: ${error.message}`, { cause: error });
|
|
52
|
+
}
|
|
53
|
+
const result = configSchema.safeParse(parsed);
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
const issues = result.error.issues
|
|
56
|
+
.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`)
|
|
57
|
+
.join("; ");
|
|
58
|
+
throw new Error(`${NIGHTSHIFT_CONFIG_FILE} is invalid: ${issues}`);
|
|
59
|
+
}
|
|
60
|
+
return { config: result.data, path };
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAE/D,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;CAC3D,CAAC,CAAC;AAIH;;;;;;;;;;;;GAYG;AACH,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,KAAK,EAAE,WAAW,CAAC,QAAQ,EAAE;IAC7B,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,aAAa,EAAE,WAAW,CAAC,QAAQ,EAAE;IACrC,aAAa,EAAE,WAAW,CAAC,QAAQ,EAAE;IACrC,6EAA6E;IAC7E,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACrD,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,cAAc,GAAqB,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAQvE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;IAC/C,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACpC,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,GAAG,sBAAsB,uBAAwB,KAAe,CAAC,OAAO,EAAE,EAC1E,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM;aAC/B,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;aACvE,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,KAAK,CAAC,GAAG,sBAAsB,gBAAgB,MAAM,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const PACKAGE_MANAGERS: readonly ["npm", "pnpm", "yarn", "bun"];
|
|
2
|
+
export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
|
|
3
|
+
export declare function parsePackageManager(value: string): PackageManager;
|
|
4
|
+
/**
|
|
5
|
+
* Detect the project's package manager, walking up from `cwd` because in
|
|
6
|
+
* workspaces the lockfile and `packageManager` field live at the repo root,
|
|
7
|
+
* not necessarily in the package directory. Falls back to npm.
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectPackageManager(cwd: string): PackageManager;
|
|
10
|
+
/** pnpm workspaces are always declared by a pnpm-workspace.yaml at the root. */
|
|
11
|
+
export declare function isPnpmWorkspaceRoot(directory: string): boolean;
|
|
12
|
+
export declare function addDevDependencyCommand(manager: PackageManager, packageName: string, options?: {
|
|
13
|
+
pnpmWorkspaceRoot?: boolean;
|
|
14
|
+
}): string;
|
|
15
|
+
//# sourceMappingURL=detect-package-manager.d.ts.map
|