@groundtrack/hooks 0.1.4 → 0.1.5

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.
@@ -1,10 +1,47 @@
1
1
  import { readFileSync } from "node:fs";
2
- const VERSION_PATTERN = /^<!-- groundtrack:start version=(\d+) -->/;
2
+ import { join } from "node:path";
3
+ const VERSION_PATTERN = /<!-- groundtrack:start version=(\d+) -->/;
3
4
  /** Canonical managed Groundtrack policy, packaged with the CLI. */
4
5
  export const AGENT_INSTRUCTION_BODY = readFileSync(new URL("../templates/AGENTS.md", import.meta.url), "utf8").trim();
5
6
  const versionMatch = VERSION_PATTERN.exec(AGENT_INSTRUCTION_BODY);
6
7
  if (!versionMatch) {
7
8
  throw new Error("Canonical Groundtrack AGENTS.md template is missing its version marker.");
8
9
  }
10
+ const AGENT_INSTRUCTION_START_MARKER = versionMatch[0];
9
11
  /** Version is derived from the canonical template marker; never define it separately. */
10
12
  export const AGENT_INSTRUCTION_TEMPLATE_VERSION = Number(versionMatch[1]);
13
+ function readInstructions(path) {
14
+ try {
15
+ return readFileSync(path, "utf8");
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ function instructionVersion(content) {
22
+ const match = content ? VERSION_PATTERN.exec(content) : null;
23
+ return match ? Number(match[1]) : null;
24
+ }
25
+ /** Detect the managed policy version available to a client in one repository. */
26
+ export function detectAgentInstructionsVersion(repoDir, client) {
27
+ const agents = readInstructions(join(repoDir, "AGENTS.md"));
28
+ const agentsVersion = instructionVersion(agents);
29
+ if (client !== "claude")
30
+ return agentsVersion;
31
+ const claude = readInstructions(join(repoDir, "CLAUDE.md"));
32
+ if (!claude)
33
+ return null;
34
+ if (/^@AGENTS\.md\s*$/m.test(claude)) {
35
+ const bridgeVersion = instructionVersion(claude);
36
+ if (agentsVersion == null)
37
+ return null;
38
+ return bridgeVersion == null ? agentsVersion : Math.min(bridgeVersion, agentsVersion);
39
+ }
40
+ const inlineVersion = instructionVersion(claude);
41
+ if (inlineVersion != null)
42
+ return inlineVersion;
43
+ const unmarkedCurrentInstructions = AGENT_INSTRUCTION_BODY.replace(AGENT_INSTRUCTION_START_MARKER, "")
44
+ .replace("<!-- groundtrack:end -->", "")
45
+ .trim();
46
+ return claude.includes(unmarkedCurrentInstructions) ? AGENT_INSTRUCTION_TEMPLATE_VERSION : null;
47
+ }
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { closeSync, fstatSync, openSync, readSync } from "node:fs";
3
+ import { detectAgentInstructionsVersion, } from "./agent-instructions.js";
3
4
  const PROVIDERS = [
4
5
  [/^claude(?:[-_.]|$)/, "anthropic"],
5
6
  [/^(?:gpt|o\d|codex)(?:[-_.]|$)/, "openai"],
@@ -120,6 +121,11 @@ function gitContext(cwd) {
120
121
  isDirty: status == null ? null : status.length > 0,
121
122
  };
122
123
  }
124
+ function repositoryRoot(cwd) {
125
+ if (!cwd)
126
+ return null;
127
+ return git(cwd, ["rev-parse", "--show-toplevel"]) ?? cwd;
128
+ }
123
129
  export function enrichInput(hookInput, clientOverride, tool = "store_event") {
124
130
  const toolInput = hookInput.tool_input &&
125
131
  typeof hookInput.tool_input === "object" &&
@@ -133,7 +139,8 @@ export function enrichInput(hookInput, clientOverride, tool = "store_event") {
133
139
  ? (hookInput.model_id ?? hookInput.model)
134
140
  : (hookInput.model ?? hookInput.model_id ?? transcriptModel(hookInput.transcript_path)));
135
141
  const invocationId = nonEmptyString(isCursor ? hookInput.generation_id : hookInput.tool_use_id);
136
- const context = gitContext(workspacePath(hookInput));
142
+ const cwd = workspacePath(hookInput);
143
+ const context = gitContext(cwd);
137
144
  if (hostSessionId)
138
145
  toolInput.sessionId = `${host}:${hostSessionId}`;
139
146
  if (hostModel)
@@ -141,6 +148,11 @@ export function enrichInput(hookInput, clientOverride, tool = "store_event") {
141
148
  if (tool === "store_event" && invocationId) {
142
149
  toolInput.clientEventId = [host, hostSessionId, invocationId].filter(Boolean).join(":");
143
150
  }
151
+ if (tool === "search_experiences") {
152
+ const client = host === "claude" || host === "cursor" ? host : "codex";
153
+ const root = repositoryRoot(cwd);
154
+ toolInput.agentInstructionsVersion = root ? detectAgentInstructionsVersion(root, client) : null;
155
+ }
144
156
  if (context)
145
157
  Object.assign(toolInput, context);
146
158
  return toolInput;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groundtrack/hooks",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
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=2 -->
1
+ <!-- groundtrack:start version=3 -->
2
2
 
3
3
  ## Groundtrack
4
4
 
@@ -12,6 +12,5 @@ Use Groundtrack as the shared engineering memory for this repository.
12
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.
13
13
  - Do not store routine implementation details, obvious facts, unrestricted transcripts, or secrets.
14
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.
15
- - When recurring engineering friction may support or oppose an existing initiative, call `search_initiatives` before storing the event and link it to the matching initiative with the appropriate direction. Propose a new initiative only when explicitly recommending organizational change and no existing initiative matches.
16
15
 
17
16
  <!-- groundtrack:end -->