@groundtrack/hooks 0.1.2 → 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,18 @@ 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
+
32
+ The Doctor verifies local configuration and enrichment behavior, but it cannot
33
+ observe whether a coding-agent host loaded or trusted a project hook. A
34
+ `search_experiences` MCP response is the authoritative live check: Groundtrack
35
+ returns a `hook_context_missing` warning when hook-injected session context is
36
+ absent. In Codex, use `/hooks` to review and trust the Groundtrack hook when that
37
+ warning appears.
38
+
27
39
  `doctor --fix` previews the diagnosis, asks before changing anything, updates
28
40
  only Groundtrack-owned hook entries and versioned managed instruction blocks,
29
41
  then repeats the verification. Use `--yes` for an explicitly authorized
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) {
@@ -510,18 +517,26 @@ export function runDoctor(options) {
510
517
  updated.clientEventId === "claude:doctor-session:doctor-tool";
511
518
  checks.push({
512
519
  id: "hook-simulation",
513
- label: "Hook simulation",
520
+ label: "Local hook simulation",
514
521
  status: simulationHealthy ? "ok" : "issue",
515
522
  detail: simulationHealthy
516
- ? "session, model, and invocation context enriched"
523
+ ? "session, model, and invocation context enriched locally"
517
524
  : "enrichment failed",
518
525
  });
526
+ checks.push({
527
+ id: "live-hook-activation",
528
+ label: "Live hook activation",
529
+ status: "advisory",
530
+ detail: clients.includes("codex")
531
+ ? "not verifiable outside Codex; open /hooks and review and trust the Groundtrack hook"
532
+ : "not verifiable outside the coding-agent host; Groundtrack MCP calls report missing hook context",
533
+ });
519
534
  return {
520
535
  version,
521
536
  clients,
522
537
  aliases,
523
538
  checks,
524
539
  changedFiles,
525
- healthy: checks.every((check) => check.status === "ok" || check.status === "fixed"),
540
+ healthy: checks.every((check) => check.status === "ok" || check.status === "fixed" || check.status === "advisory"),
526
541
  };
527
542
  }
@@ -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,41 +31,58 @@ 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
- function printResult(result) {
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`);
50
68
  for (const check of result.checks) {
51
69
  const icon = check.status === "ok"
52
70
  ? "✓"
53
- : check.status === "fixed"
54
- ? "◆"
55
- : check.status === "blocked"
56
- ? "×"
57
- : "!";
71
+ : check.status === "advisory"
72
+ ? "i"
73
+ : check.status === "fixed"
74
+ ? "◆"
75
+ : check.status === "blocked"
76
+ ? "×"
77
+ : "!";
58
78
  stdout.write(`${icon} ${check.label}: ${check.detail}${check.file ? ` (${check.file})` : ""}\n`);
59
79
  }
60
80
  if (result.changedFiles.length > 0) {
61
81
  stdout.write(`\nUpdated ${result.changedFiles.length} file(s).\n`);
62
82
  }
63
83
  stdout.write(result.healthy
64
- ? "\nGroundtrack integration is healthy.\n"
65
- : "\nGroundtrack integration needs attention.\n");
84
+ ? "\nGroundtrack local configuration is healthy. Live hook activation is verified by Groundtrack MCP calls.\n"
85
+ : `\nGroundtrack local configuration needs attention.${fixHint}\n`);
66
86
  }
67
87
  async function confirmFix() {
68
88
  const prompt = createInterface({ input: stdin, output: stdout });
@@ -130,6 +150,7 @@ async function main() {
130
150
  const base = {
131
151
  repoDir: args.repoDir,
132
152
  homeDir: args.homeDir,
153
+ clients: args.client ? [args.client] : undefined,
133
154
  skipPackageInstall: process.env.GROUNDTRACK_DOCTOR_SKIP_INSTALL === "1",
134
155
  latestVersion: await latestPackageVersion(),
135
156
  };
@@ -138,10 +159,13 @@ async function main() {
138
159
  if (args.json)
139
160
  stdout.write(`${JSON.stringify(diagnosis, null, 2)}\n`);
140
161
  else
141
- printResult(diagnosis);
162
+ printResult(diagnosis, { showFixHint: true, client: args.client });
142
163
  process.exitCode = diagnosis.healthy ? 0 : 1;
143
164
  return;
144
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
+ }
145
169
  if (diagnosis.healthy) {
146
170
  if (args.json)
147
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.2",
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.