@prestyj/cli 5.2.0 → 5.3.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 (60) hide show
  1. package/dist/app-sidecar.js +288 -49
  2. package/dist/app-sidecar.js.map +1 -1
  3. package/dist/core/agent-session-queue.test.d.ts +2 -0
  4. package/dist/core/agent-session-queue.test.d.ts.map +1 -0
  5. package/dist/core/agent-session-queue.test.js +122 -0
  6. package/dist/core/agent-session-queue.test.js.map +1 -0
  7. package/dist/core/agent-session.d.ts +8 -0
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +7 -0
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/autopilot-cycle.d.ts +67 -0
  12. package/dist/core/autopilot-cycle.d.ts.map +1 -0
  13. package/dist/core/autopilot-cycle.js +50 -0
  14. package/dist/core/autopilot-cycle.js.map +1 -0
  15. package/dist/core/autopilot-cycle.test.d.ts +2 -0
  16. package/dist/core/autopilot-cycle.test.d.ts.map +1 -0
  17. package/dist/core/autopilot-cycle.test.js +179 -0
  18. package/dist/core/autopilot-cycle.test.js.map +1 -0
  19. package/dist/core/autopilot-gate.d.ts +83 -0
  20. package/dist/core/autopilot-gate.d.ts.map +1 -0
  21. package/dist/core/autopilot-gate.js +96 -0
  22. package/dist/core/autopilot-gate.js.map +1 -0
  23. package/dist/core/autopilot-gate.test.d.ts +2 -0
  24. package/dist/core/autopilot-gate.test.d.ts.map +1 -0
  25. package/dist/core/autopilot-gate.test.js +159 -0
  26. package/dist/core/autopilot-gate.test.js.map +1 -0
  27. package/dist/core/autopilot-verdict.d.ts +14 -2
  28. package/dist/core/autopilot-verdict.d.ts.map +1 -1
  29. package/dist/core/autopilot-verdict.js +19 -2
  30. package/dist/core/autopilot-verdict.js.map +1 -1
  31. package/dist/core/autopilot-verdict.test.js +10 -0
  32. package/dist/core/autopilot-verdict.test.js.map +1 -1
  33. package/dist/core/json-mode-flag-parity.test.d.ts +2 -0
  34. package/dist/core/json-mode-flag-parity.test.d.ts.map +1 -0
  35. package/dist/core/json-mode-flag-parity.test.js +66 -0
  36. package/dist/core/json-mode-flag-parity.test.js.map +1 -0
  37. package/dist/core/nolan-context.d.ts +17 -0
  38. package/dist/core/nolan-context.d.ts.map +1 -1
  39. package/dist/core/nolan-context.js +47 -6
  40. package/dist/core/nolan-context.js.map +1 -1
  41. package/dist/core/nolan-context.test.js +122 -1
  42. package/dist/core/nolan-context.test.js.map +1 -1
  43. package/dist/core/nolan-model.d.ts +46 -0
  44. package/dist/core/nolan-model.d.ts.map +1 -0
  45. package/dist/core/nolan-model.js +30 -0
  46. package/dist/core/nolan-model.js.map +1 -0
  47. package/dist/core/nolan-model.test.d.ts +2 -0
  48. package/dist/core/nolan-model.test.d.ts.map +1 -0
  49. package/dist/core/nolan-model.test.js +51 -0
  50. package/dist/core/nolan-model.test.js.map +1 -0
  51. package/dist/core/nolan-prompt.d.ts +3 -3
  52. package/dist/core/nolan-prompt.js +19 -7
  53. package/dist/core/nolan-prompt.js.map +1 -1
  54. package/dist/core/nolan-prompt.test.d.ts +2 -0
  55. package/dist/core/nolan-prompt.test.d.ts.map +1 -0
  56. package/dist/core/nolan-prompt.test.js +43 -0
  57. package/dist/core/nolan-prompt.test.js.map +1 -0
  58. package/dist/core/speed-benchmark.test.js +2 -3
  59. package/dist/core/speed-benchmark.test.js.map +1 -1
  60. package/package.json +5 -5
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Autopilot gate — pure decision logic for whether Nolan's auto-review cycle may
3
+ * start after a finished EZ Coder turn.
4
+ *
5
+ * Autopilot must NOT review every turn. The concrete leak cases this gate
6
+ * closes (each has a matching unit test in autopilot-gate.test.ts):
7
+ *
8
+ * - Workflow slash commands (`/compare`, `/bullet-proof`, `/expand`, custom
9
+ * `.ezcoder/commands/*.md`) end with reports or A/B/C choices that are reserved
10
+ * for the USER. Nolan reviewing them reads "findings" as "something real is
11
+ * wrong" and injects fix prompts the user never approved.
12
+ * - Registry commands (`/help`, `/session`, unknown `/foo`) and failed runs
13
+ * add no assistant work at all — a review would judge the PREVIOUS turn
14
+ * again (Nolan's cycle memory is wiped per turn) and can flip a settled
15
+ * ALL_CLEAR into a fresh PROMPT.
16
+ * - A turn that ended in plan mode has a pending Accept/Reject modal; Nolan must
17
+ * never inject a prompt into a read-only plan-mode session.
18
+ *
19
+ * Kept pure + dependency-light so it's unit-testable without booting the
20
+ * sidecar (which runs `main()` at import time).
21
+ */
22
+ /** A workflow (prompt-template) command: built-in PROMPT_COMMANDS or a custom
23
+ * `.ezcoder/commands/*.md` entry. `prompt` is the full template body the command
24
+ * expands to when run. */
25
+ export interface WorkflowCommandSpec {
26
+ name: string;
27
+ aliases?: readonly string[];
28
+ prompt: string;
29
+ }
30
+ /** The exact separator AgentSession.prompt() inserts between a command's
31
+ * template and the user's extra args (see agent-session.ts prompt expansion).
32
+ * Must stay byte-identical or expanded-command detection silently breaks. */
33
+ export declare const USER_INSTRUCTIONS_HEADER = "\n\n## User Instructions\n\n";
34
+ /**
35
+ * True when `text` invokes a known workflow command (first token, name or
36
+ * alias, case-insensitive). Registry/UI commands and unknown `/foo` return
37
+ * false — those add no assistant work and are caught by the
38
+ * `no-assistant-output` gate instead.
39
+ */
40
+ export declare function isWorkflowCommandText(text: string, commands: readonly WorkflowCommandSpec[]): boolean;
41
+ /**
42
+ * Match a transcript user-message body back to the workflow command it was
43
+ * expanded from. AgentSession stores the EXPANDED template as a plain user
44
+ * message, so without this Nolan's digest renders 400-line templates as
45
+ * `**User:** …` and treats them as user-authored asks.
46
+ *
47
+ * Returns the matched command plus any trailing user args, or null.
48
+ */
49
+ export declare function matchExpandedCommand(text: string, commands: readonly WorkflowCommandSpec[]): {
50
+ command: WorkflowCommandSpec;
51
+ args: string | null;
52
+ } | null;
53
+ /** Count assistant messages — the "did this run produce reviewable work"
54
+ * signal. Compared before/after a run by the sidecar. */
55
+ export declare function countAssistantMessages(messages: ReadonlyArray<{
56
+ role: string;
57
+ }>): number;
58
+ export type AutopilotSkipReason = "disabled" | "cancelled" | "plan-mode" | "workflow-command" | "no-assistant-output";
59
+ export interface AutopilotGateInput {
60
+ /** The window's autopilot toggle. */
61
+ enabled: boolean;
62
+ /** True when /cancel fired during the turn. */
63
+ cancelled: boolean;
64
+ /** True when the session ended the turn in plan mode (plan modal pending). */
65
+ planMode: boolean;
66
+ /** True when the turn was a workflow slash command (see isWorkflowCommandText). */
67
+ workflowCommand: boolean;
68
+ /** Assistant messages ADDED by this turn (after minus before). */
69
+ assistantMessagesAdded: number;
70
+ }
71
+ export type AutopilotGateDecision = {
72
+ start: true;
73
+ } | {
74
+ start: false;
75
+ reason: AutopilotSkipReason;
76
+ };
77
+ /**
78
+ * Decide whether the autopilot cycle may start for a just-finished turn.
79
+ * Checks are ordered cheapest/most-fundamental first; the reason is logged by
80
+ * the sidecar so skips are debuggable from ezcoder-app-sidecar.log.
81
+ */
82
+ export declare function shouldStartAutopilotCycle(input: AutopilotGateInput): AutopilotGateDecision;
83
+ //# sourceMappingURL=autopilot-gate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-gate.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;2BAE2B;AAC3B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;8EAE8E;AAC9E,eAAO,MAAM,wBAAwB,iCAAiC,CAAC;AAWvE;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACvC,OAAO,CAMT;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,SAAS,mBAAmB,EAAE,GACvC;IAAE,OAAO,EAAE,mBAAmB,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAW9D;AAED;0DAC0D;AAC1D,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,MAAM,CAIxF;AAED,MAAM,MAAM,mBAAmB,GAC3B,UAAU,GACV,WAAW,GACX,WAAW,GACX,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B,MAAM,WAAW,kBAAkB;IACjC,qCAAqC;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,8EAA8E;IAC9E,QAAQ,EAAE,OAAO,CAAC;IAClB,mFAAmF;IACnF,eAAe,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,sBAAsB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,MAAM,qBAAqB,GAAG;IAAE,KAAK,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAEpG;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,kBAAkB,GAAG,qBAAqB,CAO1F"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Autopilot gate — pure decision logic for whether Nolan's auto-review cycle may
3
+ * start after a finished EZ Coder turn.
4
+ *
5
+ * Autopilot must NOT review every turn. The concrete leak cases this gate
6
+ * closes (each has a matching unit test in autopilot-gate.test.ts):
7
+ *
8
+ * - Workflow slash commands (`/compare`, `/bullet-proof`, `/expand`, custom
9
+ * `.ezcoder/commands/*.md`) end with reports or A/B/C choices that are reserved
10
+ * for the USER. Nolan reviewing them reads "findings" as "something real is
11
+ * wrong" and injects fix prompts the user never approved.
12
+ * - Registry commands (`/help`, `/session`, unknown `/foo`) and failed runs
13
+ * add no assistant work at all — a review would judge the PREVIOUS turn
14
+ * again (Nolan's cycle memory is wiped per turn) and can flip a settled
15
+ * ALL_CLEAR into a fresh PROMPT.
16
+ * - A turn that ended in plan mode has a pending Accept/Reject modal; Nolan must
17
+ * never inject a prompt into a read-only plan-mode session.
18
+ *
19
+ * Kept pure + dependency-light so it's unit-testable without booting the
20
+ * sidecar (which runs `main()` at import time).
21
+ */
22
+ /** The exact separator AgentSession.prompt() inserts between a command's
23
+ * template and the user's extra args (see agent-session.ts prompt expansion).
24
+ * Must stay byte-identical or expanded-command detection silently breaks. */
25
+ export const USER_INSTRUCTIONS_HEADER = "\n\n## User Instructions\n\n";
26
+ /** Extract the `/name` token from raw input, or null when it isn't a slash
27
+ * invocation. */
28
+ function parseSlashName(text) {
29
+ const trimmed = text.trim();
30
+ if (!trimmed.startsWith("/"))
31
+ return null;
32
+ const name = trimmed.slice(1).split(/\s/, 1)[0]?.toLowerCase() ?? "";
33
+ return name.length > 0 ? name : null;
34
+ }
35
+ /**
36
+ * True when `text` invokes a known workflow command (first token, name or
37
+ * alias, case-insensitive). Registry/UI commands and unknown `/foo` return
38
+ * false — those add no assistant work and are caught by the
39
+ * `no-assistant-output` gate instead.
40
+ */
41
+ export function isWorkflowCommandText(text, commands) {
42
+ const name = parseSlashName(text);
43
+ if (!name)
44
+ return false;
45
+ return commands.some((c) => c.name.toLowerCase() === name || (c.aliases ?? []).some((a) => a.toLowerCase() === name));
46
+ }
47
+ /**
48
+ * Match a transcript user-message body back to the workflow command it was
49
+ * expanded from. AgentSession stores the EXPANDED template as a plain user
50
+ * message, so without this Nolan's digest renders 400-line templates as
51
+ * `**User:** …` and treats them as user-authored asks.
52
+ *
53
+ * Returns the matched command plus any trailing user args, or null.
54
+ */
55
+ export function matchExpandedCommand(text, commands) {
56
+ for (const command of commands) {
57
+ if (!command.prompt)
58
+ continue;
59
+ if (text === command.prompt)
60
+ return { command, args: null };
61
+ const prefix = command.prompt + USER_INSTRUCTIONS_HEADER;
62
+ if (text.startsWith(prefix)) {
63
+ const args = text.slice(prefix.length).trim();
64
+ return { command, args: args.length > 0 ? args : null };
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ /** Count assistant messages — the "did this run produce reviewable work"
70
+ * signal. Compared before/after a run by the sidecar. */
71
+ export function countAssistantMessages(messages) {
72
+ let count = 0;
73
+ for (const m of messages)
74
+ if (m.role === "assistant")
75
+ count++;
76
+ return count;
77
+ }
78
+ /**
79
+ * Decide whether the autopilot cycle may start for a just-finished turn.
80
+ * Checks are ordered cheapest/most-fundamental first; the reason is logged by
81
+ * the sidecar so skips are debuggable from ezcoder-app-sidecar.log.
82
+ */
83
+ export function shouldStartAutopilotCycle(input) {
84
+ if (!input.enabled)
85
+ return { start: false, reason: "disabled" };
86
+ if (input.cancelled)
87
+ return { start: false, reason: "cancelled" };
88
+ if (input.planMode)
89
+ return { start: false, reason: "plan-mode" };
90
+ if (input.workflowCommand)
91
+ return { start: false, reason: "workflow-command" };
92
+ if (input.assistantMessagesAdded <= 0)
93
+ return { start: false, reason: "no-assistant-output" };
94
+ return { start: true };
95
+ }
96
+ //# sourceMappingURL=autopilot-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-gate.js","sourceRoot":"","sources":["../../src/core/autopilot-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=autopilot-gate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-gate.test.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-gate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,159 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isWorkflowCommandText, matchExpandedCommand, countAssistantMessages, shouldStartAutopilotCycle, USER_INSTRUCTIONS_HEADER, } from "./autopilot-gate.js";
3
+ import { PROMPT_COMMANDS } from "./prompt-commands.js";
4
+ const COMMANDS = [
5
+ { name: "compare", aliases: [], prompt: "Compare the code you just created…" },
6
+ { name: "bullet-proof", aliases: ["bp"], prompt: "Audit the project…" },
7
+ ];
8
+ describe("isWorkflowCommandText", () => {
9
+ it("matches a bare command", () => {
10
+ expect(isWorkflowCommandText("/compare", COMMANDS)).toBe(true);
11
+ });
12
+ it("matches a command with trailing args", () => {
13
+ expect(isWorkflowCommandText("/compare src/foo.ts please", COMMANDS)).toBe(true);
14
+ });
15
+ it("matches aliases and is case-insensitive", () => {
16
+ expect(isWorkflowCommandText("/bp", COMMANDS)).toBe(true);
17
+ expect(isWorkflowCommandText("/COMPARE", COMMANDS)).toBe(true);
18
+ expect(isWorkflowCommandText("/Bullet-Proof", COMMANDS)).toBe(true);
19
+ });
20
+ it("tolerates leading whitespace (matches trim semantics of the prompt path)", () => {
21
+ expect(isWorkflowCommandText(" /compare", COMMANDS)).toBe(true);
22
+ });
23
+ it("rejects unknown commands, non-slash text, and prefix collisions", () => {
24
+ expect(isWorkflowCommandText("/help", COMMANDS)).toBe(false);
25
+ expect(isWorkflowCommandText("/comparex", COMMANDS)).toBe(false);
26
+ expect(isWorkflowCommandText("compare the files", COMMANDS)).toBe(false);
27
+ expect(isWorkflowCommandText("run /compare for me", COMMANDS)).toBe(false);
28
+ expect(isWorkflowCommandText("/", COMMANDS)).toBe(false);
29
+ expect(isWorkflowCommandText("", COMMANDS)).toBe(false);
30
+ });
31
+ it("recognizes EVERY built-in workflow command (the real leak set)", () => {
32
+ // The reported bug: /compare fired an autopilot review of a report-only
33
+ // command. Prove the gate recognizes every shipped PROMPT_COMMAND (and its
34
+ // aliases) so none of them can leak into a review.
35
+ for (const cmd of PROMPT_COMMANDS) {
36
+ expect(isWorkflowCommandText(`/${cmd.name}`, PROMPT_COMMANDS)).toBe(true);
37
+ expect(isWorkflowCommandText(`/${cmd.name} with args`, PROMPT_COMMANDS)).toBe(true);
38
+ for (const alias of cmd.aliases) {
39
+ expect(isWorkflowCommandText(`/${alias}`, PROMPT_COMMANDS)).toBe(true);
40
+ }
41
+ }
42
+ });
43
+ });
44
+ describe("matchExpandedCommand", () => {
45
+ it("matches an exact template body", () => {
46
+ const m = matchExpandedCommand("Compare the code you just created…", COMMANDS);
47
+ expect(m?.command.name).toBe("compare");
48
+ expect(m?.args).toBeNull();
49
+ });
50
+ it("matches template + user-instructions suffix and extracts the args", () => {
51
+ const text = `Audit the project…${USER_INSTRUCTIONS_HEADER}only the auth module`;
52
+ const m = matchExpandedCommand(text, COMMANDS);
53
+ expect(m?.command.name).toBe("bullet-proof");
54
+ expect(m?.args).toBe("only the auth module");
55
+ });
56
+ it("uses the exact separator AgentSession.prompt() inserts", () => {
57
+ // Lockstep guard: if agent-session.ts ever changes its expansion format,
58
+ // this literal must change with it or digest labeling silently breaks.
59
+ expect(USER_INSTRUCTIONS_HEADER).toBe("\n\n## User Instructions\n\n");
60
+ });
61
+ it("returns null for ordinary user text and near-misses", () => {
62
+ expect(matchExpandedCommand("compare my code to the repo", COMMANDS)).toBeNull();
63
+ // Template body with arbitrary extra text but no separator is NOT an expansion.
64
+ expect(matchExpandedCommand("Compare the code you just created… and also do X", COMMANDS)).toBeNull();
65
+ expect(matchExpandedCommand("", COMMANDS)).toBeNull();
66
+ });
67
+ it("matches every real built-in template round-tripped through expansion", () => {
68
+ for (const cmd of PROMPT_COMMANDS) {
69
+ // Mirrors AgentSession.prompt(): bare command → template verbatim.
70
+ expect(matchExpandedCommand(cmd.prompt, PROMPT_COMMANDS)?.command.name).toBe(cmd.name);
71
+ // Command with args → template + header + args.
72
+ const withArgs = `${cmd.prompt}${USER_INSTRUCTIONS_HEADER}focus on src/`;
73
+ const m = matchExpandedCommand(withArgs, PROMPT_COMMANDS);
74
+ expect(m?.command.name).toBe(cmd.name);
75
+ expect(m?.args).toBe("focus on src/");
76
+ }
77
+ });
78
+ });
79
+ describe("countAssistantMessages", () => {
80
+ it("counts only assistant-role messages", () => {
81
+ expect(countAssistantMessages([
82
+ { role: "system" },
83
+ { role: "user" },
84
+ { role: "assistant" },
85
+ { role: "tool" },
86
+ { role: "assistant" },
87
+ ])).toBe(2);
88
+ expect(countAssistantMessages([])).toBe(0);
89
+ });
90
+ });
91
+ describe("shouldStartAutopilotCycle", () => {
92
+ const reviewable = {
93
+ enabled: true,
94
+ cancelled: false,
95
+ planMode: false,
96
+ workflowCommand: false,
97
+ assistantMessagesAdded: 1,
98
+ };
99
+ it("starts for a normal reviewable turn", () => {
100
+ expect(shouldStartAutopilotCycle(reviewable)).toEqual({ start: true });
101
+ });
102
+ it("skips when autopilot is off", () => {
103
+ expect(shouldStartAutopilotCycle({ ...reviewable, enabled: false })).toEqual({
104
+ start: false,
105
+ reason: "disabled",
106
+ });
107
+ });
108
+ it("skips when the turn was cancelled", () => {
109
+ expect(shouldStartAutopilotCycle({ ...reviewable, cancelled: true })).toEqual({
110
+ start: false,
111
+ reason: "cancelled",
112
+ });
113
+ });
114
+ it("skips when the turn ended in plan mode (pending Accept/Reject modal)", () => {
115
+ expect(shouldStartAutopilotCycle({ ...reviewable, planMode: true })).toEqual({
116
+ start: false,
117
+ reason: "plan-mode",
118
+ });
119
+ });
120
+ it("skips workflow-command turns (/compare — the reported bug)", () => {
121
+ expect(shouldStartAutopilotCycle({ ...reviewable, workflowCommand: true })).toEqual({
122
+ start: false,
123
+ reason: "workflow-command",
124
+ });
125
+ });
126
+ it("skips turns that added no assistant output (/help, unknown /foo, failed runs)", () => {
127
+ expect(shouldStartAutopilotCycle({ ...reviewable, assistantMessagesAdded: 0 })).toEqual({
128
+ start: false,
129
+ reason: "no-assistant-output",
130
+ });
131
+ // A run that somehow REMOVED messages (defensive) also never reviews.
132
+ expect(shouldStartAutopilotCycle({ ...reviewable, assistantMessagesAdded: -1 })).toEqual({
133
+ start: false,
134
+ reason: "no-assistant-output",
135
+ });
136
+ });
137
+ it("resolves multiple skip conditions in fixed priority order", () => {
138
+ // disabled beats everything; plan-mode beats workflow-command beats no-output.
139
+ expect(shouldStartAutopilotCycle({
140
+ enabled: false,
141
+ cancelled: true,
142
+ planMode: true,
143
+ workflowCommand: true,
144
+ assistantMessagesAdded: 0,
145
+ })).toEqual({ start: false, reason: "disabled" });
146
+ expect(shouldStartAutopilotCycle({
147
+ ...reviewable,
148
+ planMode: true,
149
+ workflowCommand: true,
150
+ assistantMessagesAdded: 0,
151
+ })).toEqual({ start: false, reason: "plan-mode" });
152
+ expect(shouldStartAutopilotCycle({
153
+ ...reviewable,
154
+ workflowCommand: true,
155
+ assistantMessagesAdded: 0,
156
+ })).toEqual({ start: false, reason: "workflow-command" });
157
+ });
158
+ });
159
+ //# sourceMappingURL=autopilot-gate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autopilot-gate.test.js","sourceRoot":"","sources":["../../src/core/autopilot-gate.test.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * Autopilot Nolan's verdict contract.
3
3
  *
4
4
  * In autopilot mode Nolan never talks to the user — he auto-reviews EZ Coder's
5
- * work and replies with exactly one of three machine-parseable verdicts. The
5
+ * work and replies with exactly one of four machine-parseable verdicts. The
6
6
  * first non-empty line carries the keyword; anything after is the payload.
7
7
  *
8
8
  * PROMPT
@@ -10,17 +10,29 @@
10
10
  *
11
11
  * ALL_CLEAR
12
12
  *
13
+ * IGNORE
14
+ *
13
15
  * HUMAN
14
16
  * <one short reason the human is needed>
15
17
  *
18
+ * ALL_CLEAR and IGNORE both stop the cycle with nothing left to do, but they
19
+ * mean different things to the UI: ALL_CLEAR is a verdict on real work ("GG
20
+ * Coder built/changed something and it checks out") and renders a one-line Nolan
21
+ * marker. IGNORE means the turn was never worth reviewing in the first place
22
+ * (small talk, a question, an ack, a mechanical operation with no code
23
+ * changes) — it renders NOTHING, not even a marker, so trivial turns don't
24
+ * spam the transcript with a Nolan bubble that adds no information.
25
+ *
16
26
  * Parsing is forgiving and safe-by-default: any reply we can't confidently map
17
- * to PROMPT or ALL_CLEAR becomes a HUMAN stop, never a blind loop.
27
+ * to PROMPT, ALL_CLEAR, or IGNORE becomes a HUMAN stop, never a blind loop.
18
28
  */
19
29
  export type AutopilotVerdict = {
20
30
  kind: "prompt";
21
31
  body: string;
22
32
  } | {
23
33
  kind: "all_clear";
34
+ } | {
35
+ kind: "ignore";
24
36
  } | {
25
37
  kind: "human";
26
38
  reason: string;
@@ -1 +1 @@
1
- {"version":3,"file":"autopilot-verdict.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA0BtC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAuDrE"}
1
+ {"version":3,"file":"autopilot-verdict.d.ts","sourceRoot":"","sources":["../../src/core/autopilot-verdict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChC;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AA0BtC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,CAiErE"}
@@ -2,7 +2,7 @@
2
2
  * Autopilot Nolan's verdict contract.
3
3
  *
4
4
  * In autopilot mode Nolan never talks to the user — he auto-reviews EZ Coder's
5
- * work and replies with exactly one of three machine-parseable verdicts. The
5
+ * work and replies with exactly one of four machine-parseable verdicts. The
6
6
  * first non-empty line carries the keyword; anything after is the payload.
7
7
  *
8
8
  * PROMPT
@@ -10,11 +10,21 @@
10
10
  *
11
11
  * ALL_CLEAR
12
12
  *
13
+ * IGNORE
14
+ *
13
15
  * HUMAN
14
16
  * <one short reason the human is needed>
15
17
  *
18
+ * ALL_CLEAR and IGNORE both stop the cycle with nothing left to do, but they
19
+ * mean different things to the UI: ALL_CLEAR is a verdict on real work ("GG
20
+ * Coder built/changed something and it checks out") and renders a one-line Nolan
21
+ * marker. IGNORE means the turn was never worth reviewing in the first place
22
+ * (small talk, a question, an ack, a mechanical operation with no code
23
+ * changes) — it renders NOTHING, not even a marker, so trivial turns don't
24
+ * spam the transcript with a Nolan bubble that adds no information.
25
+ *
16
26
  * Parsing is forgiving and safe-by-default: any reply we can't confidently map
17
- * to PROMPT or ALL_CLEAR becomes a HUMAN stop, never a blind loop.
27
+ * to PROMPT, ALL_CLEAR, or IGNORE becomes a HUMAN stop, never a blind loop.
18
28
  */
19
29
  /** Cap on the raw-reply text we echo back as a HUMAN reason when Nolan's output
20
30
  * is unrecognized — keeps a garbage/huge reply from bloating the transcript. */
@@ -66,6 +76,13 @@ export function parseAutopilotVerdict(reply) {
66
76
  if (collapsed === "ALL_CLEAR" || collapsed.startsWith("ALL_CLEAR")) {
67
77
  return { kind: "all_clear" };
68
78
  }
79
+ // IGNORE / SKIP: the turn wasn't real work — nothing to say, nothing to show.
80
+ if (collapsed === "IGNORE" ||
81
+ collapsed.startsWith("IGNORE") ||
82
+ collapsed === "SKIP" ||
83
+ collapsed.startsWith("SKIP")) {
84
+ return { kind: "ignore" };
85
+ }
69
86
  if (collapsed === "PROMPT" || collapsed.startsWith("PROMPT")) {
70
87
  // Prefer the body on the following lines; if the keyword line itself carried
71
88
  // inline text after "PROMPT:", fall back to that.
@@ -1 +1 @@
1
- {"version":3,"file":"autopilot-verdict.js","sourceRoot":"","sources":["../../src/core/autopilot-verdict.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"autopilot-verdict.js","sourceRoot":"","sources":["../../src/core/autopilot-verdict.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,16 @@ describe("parseAutopilotVerdict", () => {
8
8
  expect(parseAutopilotVerdict("all clear")).toEqual({ kind: "all_clear" });
9
9
  expect(parseAutopilotVerdict("All Clear\nlooks good")).toEqual({ kind: "all_clear" });
10
10
  });
11
+ it("parses IGNORE", () => {
12
+ expect(parseAutopilotVerdict("IGNORE")).toEqual({ kind: "ignore" });
13
+ });
14
+ it("parses fuzzy IGNORE (lowercase, trailing colon, extra text)", () => {
15
+ expect(parseAutopilotVerdict("ignore")).toEqual({ kind: "ignore" });
16
+ expect(parseAutopilotVerdict("Ignore:\nnothing to review")).toEqual({ kind: "ignore" });
17
+ });
18
+ it("parses SKIP as an alias for IGNORE", () => {
19
+ expect(parseAutopilotVerdict("SKIP")).toEqual({ kind: "ignore" });
20
+ });
11
21
  it("parses PROMPT with a multi-line body", () => {
12
22
  const reply = "PROMPT\nAdd a test for the login flow.\nRun it and confirm it passes.";
13
23
  expect(parseAutopilotVerdict(reply)).toEqual({
@@ -1 +1 @@
1
- {"version":3,"file":"autopilot-verdict.test.js","sourceRoot":"","sources":["../../src/core/autopilot-verdict.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"autopilot-verdict.test.js","sourceRoot":"","sources":["../../src/core/autopilot-verdict.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=json-mode-flag-parity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-mode-flag-parity.test.d.ts","sourceRoot":"","sources":["../../src/core/json-mode-flag-parity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,66 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
+ /**
7
+ * The desktop app has no sibling `cli.js` next to the bundled sidecar, so
8
+ * subagent.ts's spawn falls back to re-running app-sidecar.mjs itself
9
+ * (see the comment above `runJsonModeIfRequested` in app-sidecar.ts).
10
+ * app-sidecar.ts therefore keeps its OWN `parseArgs` schema that mirrors
11
+ * cli.ts's `values.json` branch, in `strict: true` mode.
12
+ *
13
+ * These two schemas drifted once already (the `tools` flag was added to
14
+ * cli.ts + subagent.ts's spawn args but not to app-sidecar.ts), which broke
15
+ * every subagent call for any named agent with a `tools:` allow-list in the
16
+ * desktop app with "GG_APP_FATAL Unknown option '--tools'". This test reads
17
+ * both option lists back out of source and asserts every flag the JSON-mode
18
+ * branch of cli.ts accepts is also accepted by app-sidecar.ts's JSON-mode
19
+ * parser, so a future flag addition can't silently drift again.
20
+ */
21
+ function extractJsonModeOptionKeys(source, anchor) {
22
+ const anchorIndex = source.indexOf(anchor);
23
+ if (anchorIndex === -1)
24
+ throw new Error(`Anchor not found: ${anchor}`);
25
+ const braceOpen = source.indexOf("{", source.indexOf("options:", anchorIndex));
26
+ // Walk brace depth from the options object's opening `{` to find its
27
+ // matching close — a plain indexOf("},") stops at the FIRST nested
28
+ // option's closing brace instead of the whole block's.
29
+ let depth = 0;
30
+ let optionsEnd = -1;
31
+ for (let i = braceOpen; i < source.length; i++) {
32
+ if (source[i] === "{")
33
+ depth++;
34
+ else if (source[i] === "}") {
35
+ depth--;
36
+ if (depth === 0) {
37
+ optionsEnd = i;
38
+ break;
39
+ }
40
+ }
41
+ }
42
+ if (optionsEnd === -1)
43
+ throw new Error("Unbalanced braces in options block");
44
+ const block = source.slice(braceOpen, optionsEnd);
45
+ const keys = [];
46
+ for (const m of block.matchAll(/^\s*(?:"([\w-]+)"|(\w[\w-]*)):\s*{\s*type:/gm)) {
47
+ keys.push(m[1] ?? m[2]);
48
+ }
49
+ return keys;
50
+ }
51
+ describe("cli.ts / app-sidecar.ts JSON-mode flag parity", () => {
52
+ it("app-sidecar's runJsonModeIfRequested accepts every flag cli.ts's JSON mode does", () => {
53
+ const cliSource = fs.readFileSync(path.join(__dirname, "../cli.ts"), "utf-8");
54
+ const sidecarSource = fs.readFileSync(path.join(__dirname, "../app-sidecar.ts"), "utf-8");
55
+ const cliKeys = extractJsonModeOptionKeys(cliSource, "const { values, positionals } = parseArgs({");
56
+ const sidecarKeys = extractJsonModeOptionKeys(sidecarSource, "const { values, positionals } = parseArgs({");
57
+ // Flags that are meaningless in JSON/sub-agent mode (interactive-only or
58
+ // top-level CLI concerns) are intentionally absent from app-sidecar.ts's
59
+ // narrower schema — exclude them rather than widening the sidecar.
60
+ const notApplicableToJsonMode = new Set(["help", "version", "rpc", "thinking", "resume"]);
61
+ const requiredInSidecar = cliKeys.filter((k) => !notApplicableToJsonMode.has(k));
62
+ const missing = requiredInSidecar.filter((k) => !sidecarKeys.includes(k));
63
+ expect(missing).toEqual([]);
64
+ });
65
+ });
66
+ //# sourceMappingURL=json-mode-flag-parity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-mode-flag-parity.test.js","sourceRoot":"","sources":["../../src/core/json-mode-flag-parity.test.ts"],"names":[],"mappings":"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"}
@@ -11,8 +11,14 @@
11
11
  * (which runs `main()` at import time).
12
12
  */
13
13
  import type { Message } from "@prestyj/ai";
14
+ import { type WorkflowCommandSpec } from "./autopilot-gate.js";
14
15
  /** How many of the most recent build-session messages to inline verbatim. */
15
16
  export declare const NOLAN_RECENT_MESSAGE_LIMIT = 20;
17
+ /** Label for a user-role message that was actually injected by Autopilot Nolan.
18
+ * Without it, multi-round cycles render Nolan's own fix prompts as `**User:**`
19
+ * and he starts reviewing against his own last prompt instead of the user's
20
+ * original ask. Referenced by the autopilot system prompt — keep in sync. */
21
+ export declare const INJECTED_PROMPT_LABEL = "**Nolan autopilot (injected):**";
16
22
  export interface NolanDigestInput {
17
23
  /** The user's `@Nolan …` text (already stripped of the mention). */
18
24
  question: string;
@@ -26,6 +32,17 @@ export interface NolanDigestInput {
26
32
  platform?: string;
27
33
  /** Override the recent-message cap (tests). */
28
34
  recentLimit?: number;
35
+ /** The user prompt that started the turn under review (autopilot). Pinned in
36
+ * its own section so it can never scroll out of the rolling recent-activity
37
+ * window during multi-round cycles. */
38
+ originalRequest?: string;
39
+ /** Prompt bodies Autopilot Nolan injected into the build session. Matching
40
+ * user messages render under {@link INJECTED_PROMPT_LABEL}, not `**User:**`. */
41
+ injectedPrompts?: readonly string[];
42
+ /** Known workflow commands (built-in + custom). Expanded template bodies in
43
+ * the transcript render as a short `[ran workflow command /name]` note
44
+ * instead of hundreds of template lines masquerading as a user ask. */
45
+ workflowCommands?: readonly WorkflowCommandSpec[];
29
46
  }
30
47
  /**
31
48
  * Fixed instruction fed into the digest's `question` slot in autopilot mode.