@groundtrack/hooks 0.1.16 → 0.1.18
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 +6 -1
- package/dist/doctor.js +14 -6
- package/dist/setup/cli.d.ts +1 -0
- package/dist/setup/cli.js +75 -5
- package/package.json +1 -1
- package/templates/AGENTS.md +2 -2
package/README.md
CHANGED
|
@@ -88,7 +88,12 @@ live activation needs attention. No synthetic engineering event is required.
|
|
|
88
88
|
## Doctor compatibility
|
|
89
89
|
|
|
90
90
|
`groundtrack doctor` is an alias for `status`; `groundtrack doctor --fix` uses the
|
|
91
|
-
same setup and repair flow.
|
|
91
|
+
same setup and repair flow. Doctor checks for a newer published hooks package;
|
|
92
|
+
when one is available, upgrade before repairing with an older bundled policy.
|
|
93
|
+
If the registry is unavailable, Doctor still checks local configuration and
|
|
94
|
+
preserves managed instructions newer than its own template. When invoked through
|
|
95
|
+
`npx`, Doctor checks the globally installed hook runtime rather than the
|
|
96
|
+
temporary executable provided by `npx`. Existing single-client commands continue to work:
|
|
92
97
|
|
|
93
98
|
```sh
|
|
94
99
|
groundtrack doctor --fix --client codex
|
package/dist/doctor.js
CHANGED
|
@@ -228,9 +228,10 @@ function upsertCodexHook(content, matcher) {
|
|
|
228
228
|
function managedContentIsCurrent(content, desired) {
|
|
229
229
|
if (!content)
|
|
230
230
|
return false;
|
|
231
|
-
const start = content
|
|
232
|
-
const
|
|
233
|
-
return
|
|
231
|
+
const managed = /<!-- groundtrack:start version=(\d+) -->[\s\S]*?<!-- groundtrack:end -->/.exec(content);
|
|
232
|
+
const desiredVersion = /<!-- groundtrack:start version=(\d+) -->/.exec(desired)?.[1];
|
|
233
|
+
return ((managed != null && desiredVersion != null && Number(managed[1]) > Number(desiredVersion)) ||
|
|
234
|
+
managed?.[0] === desired);
|
|
234
235
|
}
|
|
235
236
|
export function claudeInstructionsAreCurrent(content) {
|
|
236
237
|
if (!content)
|
|
@@ -271,8 +272,12 @@ function removeLegacyAgentInstructions(content) {
|
|
|
271
272
|
}
|
|
272
273
|
export function upsertManagedContent(content, desired, migrateLegacyAgentInstructions = false) {
|
|
273
274
|
const existing = content ?? "";
|
|
274
|
-
const managed = /<!-- groundtrack:start version
|
|
275
|
-
|
|
275
|
+
const managed = /<!-- groundtrack:start version=(\d+) -->[\s\S]*?<!-- groundtrack:end -->/;
|
|
276
|
+
const match = managed.exec(existing);
|
|
277
|
+
if (match) {
|
|
278
|
+
const desiredVersion = /<!-- groundtrack:start version=(\d+) -->/.exec(desired)?.[1];
|
|
279
|
+
if (desiredVersion && Number(match[1]) > Number(desiredVersion))
|
|
280
|
+
return existing;
|
|
276
281
|
return `${existing.replace(managed, desired).trimEnd()}\n`;
|
|
277
282
|
}
|
|
278
283
|
const base = (migrateLegacyAgentInstructions ? removeLegacyAgentInstructions(existing) : existing).trimEnd();
|
|
@@ -371,7 +376,8 @@ export function runDoctor(options) {
|
|
|
371
376
|
const matcher = matcherForAliases(aliases);
|
|
372
377
|
const checks = [];
|
|
373
378
|
const changedFiles = [];
|
|
374
|
-
|
|
379
|
+
const upgradeNeeded = options.latestVersion && compareVersions(options.latestVersion, version) > 0;
|
|
380
|
+
if (upgradeNeeded) {
|
|
375
381
|
checks.push({
|
|
376
382
|
id: "cli-version",
|
|
377
383
|
label: "Groundtrack package",
|
|
@@ -387,6 +393,8 @@ export function runDoctor(options) {
|
|
|
387
393
|
detail: options.latestVersion ? `${version} (latest)` : `${version} (offline bundled state)`,
|
|
388
394
|
});
|
|
389
395
|
}
|
|
396
|
+
if (upgradeNeeded && options.fix)
|
|
397
|
+
return { version, clients, aliases, checks, changedFiles, healthy: false };
|
|
390
398
|
let runtimeVersion = options.installedHookVersion;
|
|
391
399
|
if (runtimeVersion === undefined)
|
|
392
400
|
runtimeVersion = installedHookVersion();
|
package/dist/setup/cli.d.ts
CHANGED
package/dist/setup/cli.js
CHANGED
|
@@ -36,6 +36,7 @@ export function parseArguments(input) {
|
|
|
36
36
|
const argv = [...input];
|
|
37
37
|
const result = {
|
|
38
38
|
command: "status",
|
|
39
|
+
doctor: false,
|
|
39
40
|
agents: [],
|
|
40
41
|
allDetected: false,
|
|
41
42
|
yes: false,
|
|
@@ -51,6 +52,7 @@ export function parseArguments(input) {
|
|
|
51
52
|
command = "version";
|
|
52
53
|
if (!["setup", "status", "doctor", "version"].includes(command))
|
|
53
54
|
throw new Error(`Unknown command: ${command}. Run groundtrack --help.`);
|
|
55
|
+
result.doctor = command === "doctor";
|
|
54
56
|
result.command = command === "doctor" ? "status" : command;
|
|
55
57
|
const take = (index, flag) => {
|
|
56
58
|
const value = argv[index + 1];
|
|
@@ -105,6 +107,23 @@ export function parseArguments(input) {
|
|
|
105
107
|
return result;
|
|
106
108
|
}
|
|
107
109
|
function runtimeVersion(context) {
|
|
110
|
+
if (context.env.npm_command === "exec") {
|
|
111
|
+
const result = spawnSync("npm", ["ls", "--global", "@groundtrack/hooks", "--depth=0", "--json"], {
|
|
112
|
+
env: context.env,
|
|
113
|
+
encoding: "utf8",
|
|
114
|
+
timeout: 5_000,
|
|
115
|
+
windowsHide: true,
|
|
116
|
+
shell: context.platform === "win32",
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
const value = JSON.parse(result.stdout);
|
|
120
|
+
const version = value.dependencies?.["@groundtrack/hooks"]?.version;
|
|
121
|
+
return typeof version === "string" ? version : null;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
108
127
|
const binary = executable("groundtrack-hook", context);
|
|
109
128
|
if (!binary)
|
|
110
129
|
return null;
|
|
@@ -129,6 +148,26 @@ function runtimeVersion(context) {
|
|
|
129
148
|
return null;
|
|
130
149
|
}
|
|
131
150
|
}
|
|
151
|
+
function publishedVersion(context) {
|
|
152
|
+
if (context.env.GROUNDTRACK_DOCTOR_LATEST_VERSION !== undefined)
|
|
153
|
+
return context.env.GROUNDTRACK_DOCTOR_LATEST_VERSION || null;
|
|
154
|
+
const result = spawnSync("npm", ["view", "@groundtrack/hooks", "version", "--json"], {
|
|
155
|
+
env: context.env,
|
|
156
|
+
encoding: "utf8",
|
|
157
|
+
timeout: 5_000,
|
|
158
|
+
windowsHide: true,
|
|
159
|
+
shell: context.platform === "win32",
|
|
160
|
+
});
|
|
161
|
+
if (result.status !== 0)
|
|
162
|
+
return null;
|
|
163
|
+
try {
|
|
164
|
+
const version = JSON.parse(result.stdout);
|
|
165
|
+
return typeof version === "string" ? version : null;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
132
171
|
function run(binary, args, context, inherit = false) {
|
|
133
172
|
return new Promise((resolve) => {
|
|
134
173
|
const child = spawn(binary, args, {
|
|
@@ -143,7 +182,7 @@ function run(binary, args, context, inherit = false) {
|
|
|
143
182
|
child.once("close", (code) => resolve(code === 0));
|
|
144
183
|
});
|
|
145
184
|
}
|
|
146
|
-
function printPlan(plan, context, runtimeReady) {
|
|
185
|
+
function printPlan(plan, context, runtimeReady, installedVersion) {
|
|
147
186
|
process.stdout.write(`\nGroundtrack · ${basename(context.repoDir)}\nProject: ${context.repoDir}\n\n`);
|
|
148
187
|
for (const agent of planSummary(plan, context)) {
|
|
149
188
|
process.stdout.write(` ${agent.status === "blocked" ? "!" : agent.status === "configured" ? "✓" : "+"} ${agent.name}: ${agent.status === "configured" ? "already configured" : agent.status === "blocked" ? agent.issue : "ready to set up"}\n`);
|
|
@@ -153,7 +192,9 @@ function printPlan(plan, context, runtimeReady) {
|
|
|
153
192
|
if (plan.shared.length)
|
|
154
193
|
process.stdout.write(" + Update shared AGENTS.md instructions\n");
|
|
155
194
|
if (!runtimeReady)
|
|
156
|
-
process.stdout.write(
|
|
195
|
+
process.stdout.write(installedVersion
|
|
196
|
+
? ` + Upgrade the shared Groundtrack hook runtime from ${installedVersion} to ${packageVersion()}\n`
|
|
197
|
+
: " + Install the shared Groundtrack hook runtime once\n");
|
|
157
198
|
process.stdout.write("\nScope: this project. Existing agent connections are kept.\n");
|
|
158
199
|
}
|
|
159
200
|
export async function runCli(argv) {
|
|
@@ -168,6 +209,33 @@ export async function runCli(argv) {
|
|
|
168
209
|
}
|
|
169
210
|
const context = environment({ repoDir: args.repoDir, homeDir: args.homeDir });
|
|
170
211
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !args.json);
|
|
212
|
+
const latestVersion = args.doctor ? publishedVersion(context) : null;
|
|
213
|
+
const upgradeNeeded = latestVersion != null && compareVersions(latestVersion, packageVersion()) > 0;
|
|
214
|
+
if (upgradeNeeded) {
|
|
215
|
+
const command = "npm install --global @groundtrack/hooks@latest";
|
|
216
|
+
const retry = args.command === "setup" ? "groundtrack doctor --fix" : "groundtrack doctor";
|
|
217
|
+
if (!args.json)
|
|
218
|
+
process.stdout.write(`Groundtrack Doctor ${packageVersion()} is outdated; ${latestVersion} is available. Upgrade before checking or repairing this project.\n`);
|
|
219
|
+
if (args.command === "setup" && interactive && !args.yes) {
|
|
220
|
+
const approved = await prompts.confirm({
|
|
221
|
+
message: `Upgrade Groundtrack globally to ${latestVersion} now?`,
|
|
222
|
+
initialValue: true,
|
|
223
|
+
});
|
|
224
|
+
if (!prompts.isCancel(approved) && approved) {
|
|
225
|
+
const installed = await run("npm", ["install", "--global", "@groundtrack/hooks@latest"], context, true);
|
|
226
|
+
if (installed) {
|
|
227
|
+
process.stdout.write(`Upgrade complete. Rerun ${retry} to check this project.\n`);
|
|
228
|
+
process.exitCode = 1;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
process.stdout.write(args.json
|
|
234
|
+
? `${JSON.stringify({ installedVersion: packageVersion(), latestVersion, upgradeCommand: command, applied: false })}\n`
|
|
235
|
+
: `Run \`${command}\`, then rerun \`${retry}\`. No project files changed.\n`);
|
|
236
|
+
process.exitCode = 1;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
171
239
|
if (args.connect && !interactive)
|
|
172
240
|
throw new Error("--connect needs an interactive terminal. Run setup there, or use the per-agent sign-in instructions after setup.");
|
|
173
241
|
const discovered = discoverAgents(context, args.url);
|
|
@@ -231,7 +299,7 @@ export async function runCli(argv) {
|
|
|
231
299
|
if (args.json)
|
|
232
300
|
process.stdout.write(`${JSON.stringify({ agents: planSummary(plan, context), detected: discovered, runtimeReady, sharedIssue: plan.sharedIssue, sharedFiles: plan.shared.map((change) => change.path), dryRun: args.dryRun }, null, 2)}\n`);
|
|
233
301
|
else {
|
|
234
|
-
printPlan(plan, context, runtimeReady);
|
|
302
|
+
printPlan(plan, context, runtimeReady, installedVersion);
|
|
235
303
|
const other = discovered.filter((agent) => agent.detected && !targets.includes(agent.id));
|
|
236
304
|
if (other.length)
|
|
237
305
|
process.stdout.write(`\nAlso detected: ${other.map((agent) => agent.name).join(", ")}. Add them with groundtrack setup.\n`);
|
|
@@ -250,7 +318,7 @@ export async function runCli(argv) {
|
|
|
250
318
|
if (!args.yes && !interactive)
|
|
251
319
|
throw new Error("Setup needs confirmation. Use --yes with your selected --agent values, or run groundtrack setup in an interactive terminal. No files changed.");
|
|
252
320
|
if (!args.json)
|
|
253
|
-
printPlan(plan, context, runtimeReady);
|
|
321
|
+
printPlan(plan, context, runtimeReady, installedVersion);
|
|
254
322
|
const ready = plan.agents.filter((agent) => !agent.issue);
|
|
255
323
|
if (plan.sharedIssue || !ready.length) {
|
|
256
324
|
if (args.json)
|
|
@@ -269,6 +337,7 @@ export async function runCli(argv) {
|
|
|
269
337
|
return;
|
|
270
338
|
}
|
|
271
339
|
}
|
|
340
|
+
let runtimeUpdated = false;
|
|
272
341
|
if (!runtimeReady && ready.some((agent) => requiresHookRuntime(agent.agent))) {
|
|
273
342
|
if (context.env.GROUNDTRACK_DOCTOR_SKIP_INSTALL === "1")
|
|
274
343
|
throw new Error("The Groundtrack hook runtime is missing or outdated. Install @groundtrack/hooks before applying this setup.");
|
|
@@ -279,6 +348,7 @@ export async function runCli(argv) {
|
|
|
279
348
|
const version = runtimeVersion(context);
|
|
280
349
|
if (!installed || !version || compareVersions(version, packageVersion()) < 0)
|
|
281
350
|
throw new Error("The hook runtime could not be installed or is not on PATH. Run npm install --global @groundtrack/hooks, ensure groundtrack-hook is on PATH, then retry. Agent configuration was not changed.");
|
|
351
|
+
runtimeUpdated = true;
|
|
282
352
|
}
|
|
283
353
|
const preferences = picked ? targets : [...new Set([...(saved ?? []), ...targets])];
|
|
284
354
|
const changes = [
|
|
@@ -296,7 +366,7 @@ export async function runCli(argv) {
|
|
|
296
366
|
process.stdout.write(`${JSON.stringify({ agents: planSummary(after, context), localReady, applied: true, changedFiles: changes.map((change) => change.path), backupDir }, null, 2)}\n`);
|
|
297
367
|
}
|
|
298
368
|
else {
|
|
299
|
-
process.stdout.write(`\n${ready.length} ${ready.length === 1 ? "agent configured" : "agents configured"}. ${changes.length ? `${changes.length} files updated.` : "Everything is already current."}\n`);
|
|
369
|
+
process.stdout.write(`\n${ready.length} ${ready.length === 1 ? "agent configured" : "agents configured"}. ${changes.length ? `${changes.length} files updated.` : runtimeUpdated ? "Hook runtime upgraded. No project files changed." : "Everything is already current."}\n`);
|
|
300
370
|
let connections = [];
|
|
301
371
|
if (interactive) {
|
|
302
372
|
const spinner = prompts.spinner();
|
package/package.json
CHANGED
package/templates/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- groundtrack:start version=
|
|
1
|
+
<!-- groundtrack:start version=5 -->
|
|
2
2
|
|
|
3
3
|
## Groundtrack
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ Use Groundtrack as the shared engineering memory for this repository.
|
|
|
8
8
|
- Before non-trivial investigation or implementation where prior decisions, failures, or constraints could matter, call `search_experiences`. Start with a focused description of the work, then inspect the results and search one or two materially different adjacent concepts when related components, constraints, failure modes, or prior decisions could change the approach. Do not stop merely because the first search has no exact match; try a meaningfully broader or neighboring query. Keep this expansion bounded, and also search before repeating an approach that may already have been tried.
|
|
9
9
|
- When a relevant experience needs more context or verification, call `get_experience_events` with its id to inspect the underlying raw evidence and provenance.
|
|
10
10
|
- Apply relevant current experiences and heed any caveats that an experience was contradicted, superseded, or expired.
|
|
11
|
-
-
|
|
11
|
+
- Only call `report_experience_use` if `search_experiences` returned experiences; skip it for empty results, even with a retrieval id. For non-empty results, report when one changed the approach or avoided a failure, and report `not_helpful` after review when none applied.
|
|
12
12
|
- 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.
|
|
13
13
|
- 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.
|
|
14
14
|
- Do not store routine implementation details, obvious facts, unrestricted transcripts, or secrets.
|