@achieveai/hitl-mcp-server 2.9.1 → 2.11.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 (54) hide show
  1. package/dist/cli.d.ts +31 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +133 -5
  4. package/dist/cli.js.map +1 -1
  5. package/dist/git-context.d.ts +41 -2
  6. package/dist/git-context.d.ts.map +1 -1
  7. package/dist/git-context.js +86 -2
  8. package/dist/git-context.js.map +1 -1
  9. package/dist/host-settings.d.ts +68 -0
  10. package/dist/host-settings.d.ts.map +1 -0
  11. package/dist/host-settings.js +136 -0
  12. package/dist/host-settings.js.map +1 -0
  13. package/dist/mcp-server.d.ts +54 -1
  14. package/dist/mcp-server.d.ts.map +1 -1
  15. package/dist/mcp-server.js +421 -70
  16. package/dist/mcp-server.js.map +1 -1
  17. package/dist/ntfy-transport.d.ts +285 -13
  18. package/dist/ntfy-transport.d.ts.map +1 -1
  19. package/dist/ntfy-transport.js +747 -88
  20. package/dist/ntfy-transport.js.map +1 -1
  21. package/dist/payload.d.ts +80 -0
  22. package/dist/payload.d.ts.map +1 -0
  23. package/dist/payload.js +135 -0
  24. package/dist/payload.js.map +1 -0
  25. package/dist/plan-diff.d.ts +29 -0
  26. package/dist/plan-diff.d.ts.map +1 -0
  27. package/dist/plan-diff.js +138 -0
  28. package/dist/plan-diff.js.map +1 -0
  29. package/dist/plan-file.d.ts +24 -0
  30. package/dist/plan-file.d.ts.map +1 -0
  31. package/dist/plan-file.js +98 -0
  32. package/dist/plan-file.js.map +1 -0
  33. package/dist/plan-review.d.ts +54 -0
  34. package/dist/plan-review.d.ts.map +1 -0
  35. package/dist/plan-review.js +110 -0
  36. package/dist/plan-review.js.map +1 -0
  37. package/dist/setup.d.ts +22 -5
  38. package/dist/setup.d.ts.map +1 -1
  39. package/dist/setup.js +52 -15
  40. package/dist/setup.js.map +1 -1
  41. package/dist/snapshot-store.d.ts +104 -0
  42. package/dist/snapshot-store.d.ts.map +1 -0
  43. package/dist/snapshot-store.js +209 -0
  44. package/dist/snapshot-store.js.map +1 -0
  45. package/dist/types.d.ts +114 -1
  46. package/dist/types.d.ts.map +1 -1
  47. package/dist/types.js +2 -0
  48. package/dist/types.js.map +1 -1
  49. package/dist/version.d.ts +13 -0
  50. package/dist/version.d.ts.map +1 -0
  51. package/dist/version.js +15 -0
  52. package/dist/version.js.map +1 -0
  53. package/package.json +64 -61
  54. package/dist/bin/windows-x64/hitl-client.exe +0 -0
@@ -0,0 +1,54 @@
1
+ import type { InlineComment, PlanVerdict, PlanReviewResponseBody } from './types.js';
2
+ /**
3
+ * Pure shaping and validation of a plan-review response.
4
+ *
5
+ * Kept out of mcp-server.ts so it can be tested without booting an MCP server,
6
+ * and because the rules here are the authoritative ones — the review window
7
+ * mirrors them for fast feedback, but the server decides.
8
+ */
9
+ /** Every verdict the wire may legitimately carry. */
10
+ export declare const VALID_VERDICTS: readonly PlanVerdict[];
11
+ /** Raised when a response cannot be accepted as submitted. */
12
+ export declare class ReviewResponseError extends Error {
13
+ constructor(message: string);
14
+ }
15
+ /** What ReviewPlan returns to the agent. */
16
+ export interface ReviewPlanResult {
17
+ success: true;
18
+ timestamp: number;
19
+ respondedFrom: string;
20
+ verdict: PlanVerdict;
21
+ overallFeedback: string;
22
+ inlineComments: InlineComment[];
23
+ revision: number;
24
+ isNewPlan: boolean;
25
+ /** 'sha256:<hex>' of the content the human actually reviewed (A-8). */
26
+ snapshotHash: string;
27
+ }
28
+ /**
29
+ * Coerce an unknown verdict string.
30
+ *
31
+ * An unrecognized value is a client that is broken or newer than this server —
32
+ * never a human decision. Reporting it as `'skipped'` would tell the agent the
33
+ * human deliberately declined, and would route around the A-5 gate that makes
34
+ * `changes_requested` say what to change. Refusing is the only honest answer.
35
+ */
36
+ export declare function parseVerdict(raw: unknown): PlanVerdict;
37
+ /**
38
+ * Trim, drop empties, and sort inline comments into a canonical order.
39
+ *
40
+ * The comparator is total — it falls through to the comment text — so the same
41
+ * set of comments serializes byte-identically no matter which order the human
42
+ * clicked them in (A-4). A merely stable sort would not achieve that: two
43
+ * comments on one anchor would keep their insertion order.
44
+ */
45
+ export declare function normalizeInlineComments(raw: unknown, defaultPath: string): InlineComment[];
46
+ /**
47
+ * Normalize a decoded response body and enforce the server-side rules.
48
+ *
49
+ * `changes_requested` and `rejected` need either overall feedback or at least
50
+ * one surviving inline comment; anything else leaves the agent told to change
51
+ * something with no indication of what (A-5).
52
+ */
53
+ export declare function normalizeResponseBody(verdict: PlanVerdict, raw: unknown, defaultPath: string): PlanReviewResponseBody;
54
+ //# sourceMappingURL=plan-review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-review.d.ts","sourceRoot":"","sources":["../src/plan-review.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAErF;;;;;;GAMG;AAEH,qDAAqD;AACrD,eAAO,MAAM,cAAc,EAAE,SAAS,WAAW,EAMhD,CAAC;AAKF,8DAA8D;AAC9D,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,4CAA4C;AAC5C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,IAAI,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,WAAW,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,uEAAuE;IACvE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAKtD;AAgBD;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,aAAa,EAAE,CA+B1F;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,WAAW,EACpB,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,MAAM,GAClB,sBAAsB,CAkBxB"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Pure shaping and validation of a plan-review response.
3
+ *
4
+ * Kept out of mcp-server.ts so it can be tested without booting an MCP server,
5
+ * and because the rules here are the authoritative ones — the review window
6
+ * mirrors them for fast feedback, but the server decides.
7
+ */
8
+ /** Every verdict the wire may legitimately carry. */
9
+ export const VALID_VERDICTS = [
10
+ 'approved',
11
+ 'changes_requested',
12
+ 'rejected',
13
+ 'skipped',
14
+ 'cancelled',
15
+ ];
16
+ /** Verdicts that say "change something" and therefore must say what (A-5). */
17
+ const VERDICTS_REQUIRING_FEEDBACK = ['changes_requested', 'rejected'];
18
+ /** Raised when a response cannot be accepted as submitted. */
19
+ export class ReviewResponseError extends Error {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = 'ReviewResponseError';
23
+ }
24
+ }
25
+ /**
26
+ * Coerce an unknown verdict string.
27
+ *
28
+ * An unrecognized value is a client that is broken or newer than this server —
29
+ * never a human decision. Reporting it as `'skipped'` would tell the agent the
30
+ * human deliberately declined, and would route around the A-5 gate that makes
31
+ * `changes_requested` say what to change. Refusing is the only honest answer.
32
+ */
33
+ export function parseVerdict(raw) {
34
+ if (VALID_VERDICTS.includes(raw))
35
+ return raw;
36
+ throw new ReviewResponseError(`Unrecognized verdict ${JSON.stringify(raw)}; expected one of ${VALID_VERDICTS.join(', ')}.`);
37
+ }
38
+ /**
39
+ * Order two strings by Unicode code point.
40
+ *
41
+ * `localeCompare` is wrong here twice over: it returns 0 for strings that are
42
+ * canonically equivalent but not byte-equal (NFC vs NFD, which is exactly what
43
+ * a macOS and a Windows client produce for the same accented word), and its
44
+ * ordering depends on the host locale. A-4 requires the same comments to
45
+ * serialize to the same bytes on every device, so the comparator has to be
46
+ * decided by the bytes (M3).
47
+ */
48
+ function compareCodePoints(a, b) {
49
+ return a < b ? -1 : a > b ? 1 : 0;
50
+ }
51
+ /**
52
+ * Trim, drop empties, and sort inline comments into a canonical order.
53
+ *
54
+ * The comparator is total — it falls through to the comment text — so the same
55
+ * set of comments serializes byte-identically no matter which order the human
56
+ * clicked them in (A-4). A merely stable sort would not achieve that: two
57
+ * comments on one anchor would keep their insertion order.
58
+ */
59
+ export function normalizeInlineComments(raw, defaultPath) {
60
+ if (!Array.isArray(raw))
61
+ return [];
62
+ const cleaned = [];
63
+ for (const entry of raw) {
64
+ if (typeof entry !== 'object' || entry === null)
65
+ continue;
66
+ const c = entry;
67
+ const comment = typeof c.comment === 'string' ? c.comment.trim() : '';
68
+ if (comment === '')
69
+ continue; // An empty comment anchors nothing.
70
+ const startLine = toLineNumber(c.startLine, 1);
71
+ const endLine = Math.max(startLine, toLineNumber(c.endLine, startLine));
72
+ cleaned.push({
73
+ path: typeof c.path === 'string' && c.path !== '' ? c.path : defaultPath,
74
+ startLine,
75
+ endLine,
76
+ side: c.side === 'old' ? 'old' : 'new',
77
+ comment,
78
+ });
79
+ }
80
+ return cleaned.sort((a, b) => compareCodePoints(a.path, b.path) ||
81
+ a.startLine - b.startLine ||
82
+ a.endLine - b.endLine ||
83
+ compareCodePoints(a.side, b.side) ||
84
+ compareCodePoints(a.comment, b.comment));
85
+ }
86
+ /**
87
+ * Normalize a decoded response body and enforce the server-side rules.
88
+ *
89
+ * `changes_requested` and `rejected` need either overall feedback or at least
90
+ * one surviving inline comment; anything else leaves the agent told to change
91
+ * something with no indication of what (A-5).
92
+ */
93
+ export function normalizeResponseBody(verdict, raw, defaultPath) {
94
+ const body = (typeof raw === 'object' && raw !== null ? raw : {});
95
+ const overallFeedback = typeof body.overallFeedback === 'string' ? body.overallFeedback.trim() : '';
96
+ const inlineComments = normalizeInlineComments(body.inlineComments, defaultPath);
97
+ if (VERDICTS_REQUIRING_FEEDBACK.includes(verdict) &&
98
+ overallFeedback === '' &&
99
+ inlineComments.length === 0) {
100
+ throw new ReviewResponseError(`Verdict '${verdict}' requires either overall feedback or at least one inline comment, ` +
101
+ `but the response carried neither.`);
102
+ }
103
+ return { overallFeedback, inlineComments };
104
+ }
105
+ function toLineNumber(value, fallback) {
106
+ return typeof value === 'number' && Number.isFinite(value) && value >= 1
107
+ ? Math.floor(value)
108
+ : fallback;
109
+ }
110
+ //# sourceMappingURL=plan-review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-review.js","sourceRoot":"","sources":["../src/plan-review.ts"],"names":[],"mappings":"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"}
package/dist/setup.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /** Result of a single setup step. */
2
2
  export interface SetupStepResult {
3
3
  step: string;
4
- status: 'ok' | 'created' | 'launched' | 'already_running' | 'not_found' | 'error';
4
+ status: 'ok' | 'created' | 'launched' | 'already_running' | 'not_found' | 'error' | 'warning';
5
5
  message: string;
6
6
  }
7
7
  /** Aggregated result of the full setup process. */
@@ -24,18 +24,35 @@ export declare function isProcessRunning(processName: string): boolean;
24
24
  export declare function findClientBinary(serverDir: string): string | null;
25
25
  /**
26
26
  * Launch the client binary as a fully detached background process.
27
+ *
28
+ * The `'error'` listener is not optional. Node reports a failed exec (ENOENT,
29
+ * EACCES) by emitting `'error'` asynchronously, and an unhandled `'error'` on
30
+ * an EventEmitter is rethrown as an uncaught exception — which, from inside a
31
+ * tool call, kills the whole MCP server instead of returning a message the
32
+ * agent can act on (H9).
27
33
  */
28
34
  export declare function launchClient(binaryPath: string): void;
35
+ /** Outcome of ensureClientRunning — the caller must not publish when `ok` is false. */
36
+ export interface ClientRunningResult {
37
+ ok: boolean;
38
+ /** Why the client is unavailable. Present only when `ok` is false. */
39
+ reason?: string;
40
+ }
29
41
  /**
30
42
  * Ensure the HITL client is running. Finds and launches it if needed.
31
- * Logs to stderr only — safe to call from the hot path of ask_human.
43
+ *
44
+ * Returns the outcome rather than only logging it: with timeouts removed, a
45
+ * caller that publishes to a topic nobody is subscribed to blocks forever and
46
+ * the human never sees anything (A-10). Both "binary not found" and "launch
47
+ * failed" must reach the agent as an error before anything is published.
32
48
  */
33
- export declare function ensureClientRunning(serverDir: string): void;
49
+ export declare function ensureClientRunning(serverDir: string): ClientRunningResult;
34
50
  /**
35
51
  * Perform the full HITL client setup:
36
52
  * 1. Ensure ~/.hitl/config.json exists
37
- * 2. Check if the client process is running
38
- * 3. Find and launch the client binary if needed
53
+ * 2. Non-fatal diagnostic: warn if the auto-background env guard isn't active
54
+ * 3. Check if the client process is running
55
+ * 4. Find and launch the client binary if needed
39
56
  *
40
57
  * @param serverDir - The directory of the running server JS (used to resolve relative binary paths)
41
58
  */
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAMA,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,GAAG,iBAAiB,GAAG,WAAW,GAAG,OAAO,CAAC;IAClF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAe7D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BjE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAMrD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAgB3D;AAqBD;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmD1E"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAOA,qCAAqC;AACrC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,IAAI,GAAG,SAAS,GAAG,UAAU,GAAG,iBAAiB,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9F,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,mDAAmD;AACnD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAe7D;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA0BjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CASrD;AAED,uFAAuF;AACvF,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,OAAO,CAAC;IACZ,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,mBAAmB,CA0B1E;AAqBD;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAmE1E"}
package/dist/setup.js CHANGED
@@ -1,8 +1,9 @@
1
- import { existsSync, chmodSync } from 'fs';
1
+ import { existsSync, chmodSync, accessSync, constants } from 'fs';
2
2
  import { execSync, spawn } from 'child_process';
3
3
  import { homedir, arch } from 'os';
4
4
  import path from 'path';
5
5
  import { saveConfig, generateDefaultConfig, getConfigPath } from './config.js';
6
+ import { detectAutoBackgroundStatus, buildAutoBackgroundRemediationText } from './host-settings.js';
6
7
  /**
7
8
  * Get the platform-specific subdirectory name for bundled binaries.
8
9
  * Maps Node.js platform/arch to the artifact names used in CI.
@@ -73,32 +74,54 @@ export function findClientBinary(serverDir) {
73
74
  }
74
75
  /**
75
76
  * Launch the client binary as a fully detached background process.
77
+ *
78
+ * The `'error'` listener is not optional. Node reports a failed exec (ENOENT,
79
+ * EACCES) by emitting `'error'` asynchronously, and an unhandled `'error'` on
80
+ * an EventEmitter is rethrown as an uncaught exception — which, from inside a
81
+ * tool call, kills the whole MCP server instead of returning a message the
82
+ * agent can act on (H9).
76
83
  */
77
84
  export function launchClient(binaryPath) {
78
85
  const child = spawn(binaryPath, [], {
79
86
  detached: true,
80
87
  stdio: 'ignore',
81
88
  });
89
+ child.on('error', (err) => {
90
+ console.error(`HITL client at ${binaryPath} failed to start: ${err.message}`);
91
+ });
82
92
  child.unref();
83
93
  }
84
94
  /**
85
95
  * Ensure the HITL client is running. Finds and launches it if needed.
86
- * Logs to stderr only — safe to call from the hot path of ask_human.
96
+ *
97
+ * Returns the outcome rather than only logging it: with timeouts removed, a
98
+ * caller that publishes to a topic nobody is subscribed to blocks forever and
99
+ * the human never sees anything (A-10). Both "binary not found" and "launch
100
+ * failed" must reach the agent as an error before anything is published.
87
101
  */
88
102
  export function ensureClientRunning(serverDir) {
89
103
  const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
90
104
  if (isProcessRunning(binaryName)) {
91
- return;
105
+ return { ok: true };
92
106
  }
93
107
  const binaryPath = findClientBinary(serverDir);
94
- if (binaryPath) {
95
- try {
96
- launchClient(binaryPath);
97
- console.error(`Auto-launched HITL client from ${binaryPath}`);
98
- }
99
- catch (err) {
100
- console.error(`Failed to auto-launch HITL client: ${err}`);
101
- }
108
+ if (!binaryPath) {
109
+ return { ok: false, reason: buildNotFoundMessage(serverDir) };
110
+ }
111
+ try {
112
+ // Checked synchronously so an unusable binary becomes a returned reason
113
+ // rather than an async 'error' event that arrives after this function has
114
+ // already told the caller everything was fine.
115
+ accessSync(binaryPath, constants.X_OK);
116
+ launchClient(binaryPath);
117
+ console.error(`Auto-launched HITL client from ${binaryPath}`);
118
+ return { ok: true };
119
+ }
120
+ catch (err) {
121
+ return {
122
+ ok: false,
123
+ reason: `Failed to launch the HITL client at ${binaryPath}: ${err instanceof Error ? err.message : String(err)}`,
124
+ };
102
125
  }
103
126
  }
104
127
  /**
@@ -122,8 +145,9 @@ function buildNotFoundMessage(serverDir) {
122
145
  /**
123
146
  * Perform the full HITL client setup:
124
147
  * 1. Ensure ~/.hitl/config.json exists
125
- * 2. Check if the client process is running
126
- * 3. Find and launch the client binary if needed
148
+ * 2. Non-fatal diagnostic: warn if the auto-background env guard isn't active
149
+ * 3. Check if the client process is running
150
+ * 4. Find and launch the client binary if needed
127
151
  *
128
152
  * @param serverDir - The directory of the running server JS (used to resolve relative binary paths)
129
153
  */
@@ -150,13 +174,26 @@ export async function performSetup(serverDir) {
150
174
  });
151
175
  }
152
176
  }
153
- // Step 2: Check if client is already running
177
+ // Step 2 (non-fatal diagnostic): warn if the auto-background env guard
178
+ // isn't active, so a blocking HITL call can't get yanked into the
179
+ // background before the human answers. This never flips overallSuccess —
180
+ // it's advisory only, and setup never writes to the host settings file
181
+ // itself; the user (or a future explicit command) applies the fix.
182
+ const autoBackgroundStatus = detectAutoBackgroundStatus();
183
+ steps.push({
184
+ step: 'auto-background-env',
185
+ status: autoBackgroundStatus.active ? 'ok' : 'warning',
186
+ message: autoBackgroundStatus.active
187
+ ? `Auto-background guard is active (${autoBackgroundStatus.activeInEnv ? 'current session' : 'configured for next restart'})`
188
+ : buildAutoBackgroundRemediationText(),
189
+ });
190
+ // Step 3: Check if client is already running
154
191
  const binaryName = process.platform === 'win32' ? 'hitl-client.exe' : 'hitl-client';
155
192
  if (isProcessRunning(binaryName)) {
156
193
  steps.push({ step: 'client', status: 'already_running', message: 'HITL client is already running' });
157
194
  return { success: overallSuccess, steps, summary: formatSummary(steps) };
158
195
  }
159
- // Step 3: Find and launch binary
196
+ // Step 4: Find and launch binary
160
197
  const binaryPath = findClientBinary(serverDir);
161
198
  if (!binaryPath) {
162
199
  overallSuccess = false;
package/dist/setup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AACnC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgB/E;;;GAGG;AACH,SAAS,qBAAqB;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,OAAO,GAAG,IAAI,EAAE,CAAC;IAEvB,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,aAAa,CAAC;IAC/C,IAAI,QAAQ,KAAK,OAAO;QAAE,OAAO,WAAW,CAAC;IAC7C,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC;IAC3D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAClD,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,MAAM,GAAG,QAAQ,CACrB,8BAA8B,WAAW,OAAO,EAChD,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CACvD,CAAC;YACF,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,WAAW,EAAE,CAAC,CAAC;QAClE,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,YAAY,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;YACzE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC;IACpF,MAAM,WAAW,GAAG,qBAAqB,EAAE,CAAC;IAE5C,MAAM,UAAU,GAAG;QACjB,6DAA6D;QAC7D,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,CAAC;QACvD,2DAA2D;QAC3D,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAC;QACzF,gBAAgB;QAChB,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,CAAC;QAC3F,kCAAkC;QAClC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,UAAU,CAAC;KAC1C,CAAC;IAEF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC1B,uCAAuC;YACvC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACjC,IAAI,CAAC;oBAAC,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;gBAAC,CAAC;gBAAC,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB;IAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,EAAE,EAAE;QAClC,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;KAChB,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB;IACnD,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC;IAEpF,IAAI,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,OAAO;IACT,CAAC;IAED,MAAM,UAAU,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,UAAU,EAAE,CAAC;QACf,IAAI,CAAC;YACH,YAAY,CAAC,UAAU,CAAC,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,kCAAkC,UAAU,EAAE,CAAC,CAAC;QAChE,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,sCAAsC,GAAG,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,oBAAoB,CAAC,SAAiB;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC;IACpF,MAAM,WAAW,GAAG,qBAAqB,EAAE,CAAC;IAC5C,OAAO;QACL,mDAAmD;QACnD,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,EAAE,WAAW,EAAE,UAAU,CAAC,YAAY;QAC1E,4CAA4C,UAAU,EAAE;QACxD,8CAA8C,UAAU,EAAE;QAC1D,eAAe,UAAU,EAAE;QAC3B,EAAE;QACF,0CAA0C;QAC1C,wDAAwD;QACxD,kDAAkD;KACnD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAAiB;IAClD,MAAM,KAAK,GAAsB,EAAE,CAAC;IACpC,IAAI,cAAc,GAAG,IAAI,CAAC;IAE1B,+BAA+B;IAC/B,MAAM,UAAU,GAAG,aAAa,EAAE,CAAC;IACnC,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,4BAA4B,UAAU,EAAE,EAAE,CAAC,CAAC;IAClG,CAAC;SAAM,CAAC;QACN,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,qBAAqB,EAAE,CAAC;YACvC,UAAU,CAAC,MAAM,CAAC,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,6BAA6B,UAAU,EAAE,EAAE,CAAC,CAAC;QACxG,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,cAAc,GAAG,KAAK,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;aAC9F,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,6CAA6C;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,aAAa,CAAC;IACpF,IAAI,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,gCAAgC,EAAE,CAAC,CAAC;QACrG,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IAC3E,CAAC;IAED,iCAAiC;IACjC,MAAM,UAAU,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,cAAc,GAAG,KAAK,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,oBAAoB,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;QAC9F,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;IAC3E,CAAC;IAED,IAAI,CAAC;QACH,YAAY,CAAC,UAAU,CAAC,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,6BAA6B,UAAU,EAAE,EAAE,CAAC,CAAC;IACzG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,cAAc,GAAG,KAAK,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,OAAO;YACf,OAAO,EAAE,4BAA4B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;SAC9F,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,yDAAyD;AACzD,SAAS,aAAa,CAAC,KAAwB;IAC7C,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5F,CAAC"}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * On-disk revision history for reviewed plans.
3
+ *
4
+ * ~/.hitl/plans/<sha256(identityKey)>/<sha256(normalizedPath)>/
5
+ * latest.json {displayPath, digest, revision, createdAt}
6
+ * objects/<sha256> immutable content, one file per unique revision
7
+ * drafts/<planId>.json in-flight comments — written by the client, not here
8
+ *
9
+ * Deliberately lock-free. Two MCP server processes reviewing the same plan can
10
+ * race `latest.json`, but `objects/<sha256>` is content-addressed and immutable,
11
+ * so a lost race costs cosmetic staleness (whose revision is called "latest"),
12
+ * never corruption. Last writer wins.
13
+ *
14
+ * Crash safety is the atomic temp-file + rename on `latest.json` (B-9): the
15
+ * object lands first, then the pointer flips in one operation, so an
16
+ * interrupted write leaves the previous `latest.json` byte-identical.
17
+ */
18
+ /** The trusted contents of `latest.json`. */
19
+ export interface SnapshotMeta {
20
+ /** Repo-relative display path — never absolute (F-9). */
21
+ displayPath: string;
22
+ /** Bare lowercase sha256 hex of the content. `snapshotHash` prefixes it with 'sha256:'. */
23
+ digest: string;
24
+ revision: number;
25
+ /** Unix millis. */
26
+ createdAt: number;
27
+ }
28
+ /** Where a given plan file's history lives, and how it is named on the wire. */
29
+ export interface PlanIdentity {
30
+ /** Stable across revisions; keys drafts. */
31
+ planId: string;
32
+ /** Absolute directory holding latest.json / objects / drafts. */
33
+ dir: string;
34
+ /** Repo-relative when the plan is inside a repo, else just the basename (F-9). */
35
+ displayPath: string;
36
+ /** Absolute repo root, or null when the plan is not in a work tree. */
37
+ repoRoot: string | null;
38
+ /** The resolved, symlink-free plan path this identity describes. */
39
+ resolvedPath: string;
40
+ }
41
+ /** Outcome of recording a revision. */
42
+ export interface RecordedRevision {
43
+ revision: number;
44
+ /** Bare sha256 hex of the content just recorded. */
45
+ digest: string;
46
+ isNewPlan: boolean;
47
+ /** The revision this one supersedes, or null on a first review. */
48
+ previous: SnapshotMeta | null;
49
+ /** Content of `previous`, or null when there is none / the object is missing. */
50
+ previousContent: string | null;
51
+ }
52
+ /** A revision whose content is stored but which is not yet the plan's latest. */
53
+ export interface PreparedRevision extends RecordedRevision {
54
+ /**
55
+ * Flip `latest.json` to this revision, making it the baseline the next review
56
+ * diffs against. Until this is called the object sits in `objects/` and the
57
+ * plan's history is unchanged.
58
+ */
59
+ commit(): RecordedRevision;
60
+ }
61
+ /** Root of the snapshot store. Overridable so tests never touch a real home dir. */
62
+ export declare function getPlansRoot(): string;
63
+ /**
64
+ * Locate a plan file's history directory and its display name.
65
+ *
66
+ * Identity is keyed on the repo root when there is one, else on the containing
67
+ * directory — so the same relative path in two different repos gets two
68
+ * identities (B-7), while two spellings of one Windows path collapse to one
69
+ * (B-10). Git is resolved from the plan's own directory, never `process.cwd()`,
70
+ * because the plan usually lives in a sibling repo (B-6).
71
+ */
72
+ export declare function resolvePlanIdentity(resolvedPath: string): PlanIdentity;
73
+ /**
74
+ * Validate a raw `latest.json` body before anything trusts it.
75
+ *
76
+ * Returns null — never a partially-trusted object — when the digest is not 64
77
+ * lowercase hex, the revision is not a positive integer, `createdAt` is zero,
78
+ * or the recorded `displayPath` names a different file than the one being
79
+ * reviewed. A rejected pointer degrades to "no history", which restarts the
80
+ * revision count rather than diffing against something unverified.
81
+ */
82
+ export declare function parseLatest(raw: string, expectedDisplayPath: string): SnapshotMeta | null;
83
+ /** Read and validate `latest.json`, or null when absent or untrustworthy. */
84
+ export declare function readLatest(identity: PlanIdentity): SnapshotMeta | null;
85
+ /** Read a stored revision by digest, or null when the object is missing. */
86
+ export declare function readObject(identity: PlanIdentity, digest: string): string | null;
87
+ /**
88
+ * Stage `content` as the next revision of this plan.
89
+ *
90
+ * Byte-identical content still advances the revision: a resubmit is a distinct
91
+ * review event, and the human must still be able to select every line of an
92
+ * unchanged plan (B-3).
93
+ *
94
+ * The pointer flip is deliberately a separate step. `latest.json` is what the
95
+ * next review diffs against, so writing it before the plan has actually reached
96
+ * the human means a failed publish silently rebases the next review onto a
97
+ * revision nobody ever saw — the change the human was meant to review vanishes
98
+ * from the diff (M6). Callers commit once the revision is out the door.
99
+ *
100
+ * The object is written either way; it is content-addressed and immutable, so
101
+ * an uncommitted one costs a few KB of orphaned disk and nothing else.
102
+ */
103
+ export declare function prepareRevision(identity: PlanIdentity, content: string): PreparedRevision;
104
+ //# sourceMappingURL=snapshot-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-store.d.ts","sourceRoot":"","sources":["../src/snapshot-store.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;GAgBG;AAEH,6CAA6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,mBAAmB;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAC;IACZ,kFAAkF;IAClF,WAAW,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,mEAAmE;IACnE,QAAQ,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,iFAAiF;IACjF,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD;;;;OAIG;IACH,MAAM,IAAI,gBAAgB,CAAC;CAC5B;AAED,oFAAoF;AACpF,wBAAgB,YAAY,IAAI,MAAM,CAErC;AASD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,YAAY,CAgBtE;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,mBAAmB,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAiBzF;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,GAAG,YAAY,GAAG,IAAI,CAStE;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAShF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,GAAG,gBAAgB,CAmCzF"}