@groundtrack/hooks 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@ repository root:
16
16
 
17
17
  ```bash
18
18
  groundtrack doctor
19
- groundtrack doctor --fix
19
+ groundtrack doctor --fix --client codex
20
20
  ```
21
21
 
22
22
  `doctor` checks the installed package/runtime version, discovers the actual
@@ -24,6 +24,11 @@ Groundtrack MCP server alias, validates Claude/Codex/Cursor hook configuration,
24
24
  checks the managed `AGENTS.md` and `CLAUDE.md` instructions, and simulates a
25
25
  hook invocation. It exits non-zero when repair is needed.
26
26
 
27
+ Pass `--client claude`, `--client codex`, or `--client cursor` when repairing a
28
+ repository. This limits MCP alias discovery and configuration changes to that
29
+ client. If more than one client is detected, `doctor --fix` refuses to guess
30
+ which one to modify.
31
+
27
32
  The Doctor verifies local configuration and enrichment behavior, but it cannot
28
33
  observe whether a coding-agent host loaded or trusted a project hook. A
29
34
  `search_experiences` MCP response is the authoritative live check: Groundtrack
package/dist/doctor.js CHANGED
@@ -53,9 +53,13 @@ function matcherForAliases(aliases) {
53
53
  const server = escaped.length === 1 ? escaped[0] : `(?:${escaped.join("|")})`;
54
54
  return `^mcp__${server}__(store_event|search_experiences)$`;
55
55
  }
56
- function groundtrackAliases(repoDir) {
56
+ function groundtrackAliases(repoDir, clients) {
57
57
  const aliases = new Set();
58
- for (const relative of [".mcp.json", ".cursor/mcp.json"]) {
58
+ const jsonConfigs = [
59
+ ...(clients.includes("claude") ? [".mcp.json"] : []),
60
+ ...(clients.includes("cursor") ? [".cursor/mcp.json"] : []),
61
+ ];
62
+ for (const relative of jsonConfigs) {
59
63
  const path = join(repoDir, relative);
60
64
  try {
61
65
  const config = readJson(path);
@@ -73,11 +77,13 @@ function groundtrackAliases(repoDir) {
73
77
  // The owning adapter reports malformed config; alias discovery stays fail-open.
74
78
  }
75
79
  }
76
- const codex = readText(join(repoDir, ".codex/config.toml")) ?? "";
77
- const table = /^\[mcp_servers\.([^\]]+)\]([\s\S]*?)(?=^\[|(?![\s\S]))/gm;
78
- for (const match of codex.matchAll(table)) {
79
- if (match[1]?.toLowerCase().includes("groundtrack") || match[2]?.includes("groundtrack")) {
80
- aliases.add(match[1]);
80
+ if (clients.includes("codex")) {
81
+ const codex = readText(join(repoDir, ".codex/config.toml")) ?? "";
82
+ const table = /^\[mcp_servers\.([^\]]+)\]([\s\S]*?)(?=^\[|(?![\s\S]))/gm;
83
+ for (const match of codex.matchAll(table)) {
84
+ if (match[1]?.toLowerCase().includes("groundtrack") || match[2]?.includes("groundtrack")) {
85
+ aliases.add(match[1]);
86
+ }
81
87
  }
82
88
  }
83
89
  if (aliases.size === 0)
@@ -360,7 +366,7 @@ function addFileCheck(checks, changedFiles, options, input) {
360
366
  export function runDoctor(options) {
361
367
  const version = packageVersion();
362
368
  const clients = options.clients ?? detectClients(options.repoDir);
363
- const aliases = groundtrackAliases(options.repoDir);
369
+ const aliases = groundtrackAliases(options.repoDir, clients);
364
370
  const matcher = matcherForAliases(aliases);
365
371
  const checks = [];
366
372
  const changedFiles = [];
@@ -474,7 +480,8 @@ export function runDoctor(options) {
474
480
  }
475
481
  }
476
482
  const instructionFiles = new Map([["AGENTS.md", AGENT_INSTRUCTION_BODY]]);
477
- if (clients.includes("claude") || existsSync(join(options.repoDir, "CLAUDE.md"))) {
483
+ if (clients.includes("claude") ||
484
+ (options.clients === undefined && existsSync(join(options.repoDir, "CLAUDE.md")))) {
478
485
  instructionFiles.set("CLAUDE.md", CLAUDE_INSTRUCTION_BODY);
479
486
  }
480
487
  for (const [name, desired] of instructionFiles) {
@@ -10,13 +10,16 @@ function usage() {
10
10
  return `Groundtrack CLI
11
11
 
12
12
  Usage:
13
- groundtrack doctor [--fix] [--yes] [--json] [--repo PATH]
13
+ groundtrack doctor [--fix] [--yes] [--json] [--client CLIENT] [--repo PATH]
14
14
  groundtrack version [--json]
15
15
 
16
16
  Commands:
17
17
  doctor Diagnose Groundtrack hooks and managed agent instructions
18
18
  doctor --fix Repair deterministic configuration drift
19
19
  version Print the installed CLI/runtime version
20
+
21
+ Options:
22
+ --client Limit diagnosis and repair to claude, codex, or cursor
20
23
  `;
21
24
  }
22
25
  function parseArguments(argv) {
@@ -28,22 +31,37 @@ function parseArguments(argv) {
28
31
  const command = argv[0] === "version" || argv[0] === "--version" ? "version" : "doctor";
29
32
  let repoDir = process.cwd();
30
33
  let homeDir = homedir();
34
+ let client;
31
35
  for (let index = 0; index < argv.length; index += 1) {
32
36
  if (argv[index] === "--repo" && argv[index + 1])
33
37
  repoDir = resolve(argv[++index]);
34
38
  if (argv[index] === "--home" && argv[index + 1])
35
39
  homeDir = resolve(argv[++index]);
40
+ if (argv[index] === "--client") {
41
+ const value = argv[++index];
42
+ if (value !== "claude" && value !== "codex" && value !== "cursor") {
43
+ throw new Error("--client must be one of: claude, codex, cursor");
44
+ }
45
+ client = value;
46
+ }
36
47
  }
37
48
  return {
38
49
  command,
39
50
  fix: argv.includes("--fix"),
40
51
  yes: argv.includes("--yes") || argv.includes("-y"),
41
52
  json: argv.includes("--json"),
53
+ client,
42
54
  repoDir,
43
55
  homeDir,
44
56
  };
45
57
  }
46
58
  function printResult(result, options = {}) {
59
+ const selectedClient = options.client ?? (result.clients.length === 1 ? result.clients[0] : null);
60
+ const fixHint = !options.showFixHint
61
+ ? ""
62
+ : selectedClient
63
+ ? ` Run: groundtrack doctor --fix --client ${selectedClient}`
64
+ : " Re-run with --client claude, --client codex, or --client cursor.";
47
65
  stdout.write(`Groundtrack doctor ${result.version}\n`);
48
66
  stdout.write(`Clients: ${result.clients.join(", ")}\n`);
49
67
  stdout.write(`MCP aliases: ${result.aliases.join(", ")}\n\n`);
@@ -64,7 +82,7 @@ function printResult(result, options = {}) {
64
82
  }
65
83
  stdout.write(result.healthy
66
84
  ? "\nGroundtrack local configuration is healthy. Live hook activation is verified by Groundtrack MCP calls.\n"
67
- : `\nGroundtrack local configuration needs attention.${options.showFixHint ? " Run: groundtrack doctor --fix" : ""}\n`);
85
+ : `\nGroundtrack local configuration needs attention.${fixHint}\n`);
68
86
  }
69
87
  async function confirmFix() {
70
88
  const prompt = createInterface({ input: stdin, output: stdout });
@@ -132,6 +150,7 @@ async function main() {
132
150
  const base = {
133
151
  repoDir: args.repoDir,
134
152
  homeDir: args.homeDir,
153
+ clients: args.client ? [args.client] : undefined,
135
154
  skipPackageInstall: process.env.GROUNDTRACK_DOCTOR_SKIP_INSTALL === "1",
136
155
  latestVersion: await latestPackageVersion(),
137
156
  };
@@ -140,10 +159,13 @@ async function main() {
140
159
  if (args.json)
141
160
  stdout.write(`${JSON.stringify(diagnosis, null, 2)}\n`);
142
161
  else
143
- printResult(diagnosis, { showFixHint: true });
162
+ printResult(diagnosis, { showFixHint: true, client: args.client });
144
163
  process.exitCode = diagnosis.healthy ? 0 : 1;
145
164
  return;
146
165
  }
166
+ if (!args.client && diagnosis.clients.length > 1) {
167
+ throw new Error(`Multiple coding-agent clients detected: ${diagnosis.clients.join(", ")}. Re-run with --client claude, --client codex, or --client cursor so Doctor does not modify unrelated client configuration.`);
168
+ }
147
169
  if (diagnosis.healthy) {
148
170
  if (args.json)
149
171
  stdout.write(`${JSON.stringify(diagnosis, null, 2)}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundtrack/hooks",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "Lifecycle hook utilities for Groundtrack MCP clients.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,4 +1,4 @@
1
- <!-- groundtrack:start version=1 -->
1
+ <!-- groundtrack:start version=2 -->
2
2
 
3
3
  ## Groundtrack
4
4
 
@@ -8,6 +8,7 @@ Use Groundtrack as the shared engineering memory for this repository.
8
8
  - When a relevant experience needs more context or verification, call `get_experience_events` with its id to inspect the underlying raw evidence and provenance.
9
9
  - Apply relevant current experiences and heed any caveats that an experience was contradicted, superseded, or expired.
10
10
  - Once the effect of a search is known, call `report_experience_use` with its retrieval id. Report when a result changed the approach or avoided a failure, and report `not_helpful` when none applied.
11
+ - After a task reaches a concrete result, call `record_outcome` with a short title, a concise summary, and the retrieval ids for searches that contributed. Include measurements only for sourced, quantified impact on time, cost, reliability, quality, performance, or throughput; tests, typechecks, lint, and builds are validation rather than impact. Include avoided work only when it was explicitly estimated, and never infer unknown values.
11
12
  - After a non-obvious discovery, decision, workaround, correction, or failed approach that would help a future agent, call `store_event` promptly with a concise title and an explanation of what happened and why it matters.
12
13
  - Do not store routine implementation details, obvious facts, unrestricted transcripts, or secrets.
13
14
  - Keep the raw-event ids returned by `store_event`. When the related work lands in a commit or pull request, call `link_outcomes` with those ids and the explicit commit SHA and/or pull-request number. Never infer an outcome from worktree HEAD.