@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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +90 -0
  3. package/dist/agent-runner.d.ts +64 -0
  4. package/dist/agent-runner.d.ts.map +1 -0
  5. package/dist/agent-runner.js +128 -0
  6. package/dist/agent-runner.js.map +1 -0
  7. package/dist/agent-summary.d.ts +35 -0
  8. package/dist/agent-summary.d.ts.map +1 -0
  9. package/dist/agent-summary.js +95 -0
  10. package/dist/agent-summary.js.map +1 -0
  11. package/dist/cli.d.ts +3 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +18 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/config.d.ts +74 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +62 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/detect-package-manager.d.ts +15 -0
  20. package/dist/detect-package-manager.d.ts.map +1 -0
  21. package/dist/detect-package-manager.js +78 -0
  22. package/dist/detect-package-manager.js.map +1 -0
  23. package/dist/findings.d.ts +106 -0
  24. package/dist/findings.d.ts.map +1 -0
  25. package/dist/findings.js +107 -0
  26. package/dist/findings.js.map +1 -0
  27. package/dist/graph.d.ts +30 -0
  28. package/dist/graph.d.ts.map +1 -0
  29. package/dist/graph.js +87 -0
  30. package/dist/graph.js.map +1 -0
  31. package/dist/index.d.ts +10 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +10 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/program-memory.d.ts +252 -0
  36. package/dist/program-memory.d.ts.map +1 -0
  37. package/dist/program-memory.js +385 -0
  38. package/dist/program-memory.js.map +1 -0
  39. package/dist/project-manifest.d.ts +23 -0
  40. package/dist/project-manifest.d.ts.map +1 -0
  41. package/dist/project-manifest.js +78 -0
  42. package/dist/project-manifest.js.map +1 -0
  43. package/dist/prompt.d.ts +70 -0
  44. package/dist/prompt.d.ts.map +1 -0
  45. package/dist/prompt.js +237 -0
  46. package/dist/prompt.js.map +1 -0
  47. package/dist/worktree-guard.d.ts +40 -0
  48. package/dist/worktree-guard.d.ts.map +1 -0
  49. package/dist/worktree-guard.js +91 -0
  50. package/dist/worktree-guard.js.map +1 -0
  51. 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,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -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
@@ -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"}
@@ -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