ghostrail 0.7.5 → 0.9.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 (59) hide show
  1. package/dist/agent/result.d.ts +2 -1
  2. package/dist/agent/result.d.ts.map +1 -1
  3. package/dist/agent/result.js +7 -1
  4. package/dist/agent/result.js.map +1 -1
  5. package/dist/cli/commands.d.ts +26 -3
  6. package/dist/cli/commands.d.ts.map +1 -1
  7. package/dist/cli/commands.js +426 -7
  8. package/dist/cli/commands.js.map +1 -1
  9. package/dist/cli.d.ts.map +1 -1
  10. package/dist/cli.js +9 -1
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/help.d.ts.map +1 -1
  13. package/dist/commands/help.js +39 -1
  14. package/dist/commands/help.js.map +1 -1
  15. package/dist/global/index.d.ts +1 -0
  16. package/dist/global/index.d.ts.map +1 -1
  17. package/dist/global/index.js +1 -0
  18. package/dist/global/index.js.map +1 -1
  19. package/dist/global/registry.d.ts +63 -0
  20. package/dist/global/registry.d.ts.map +1 -0
  21. package/dist/global/registry.js +120 -0
  22. package/dist/global/registry.js.map +1 -0
  23. package/dist/global/store.d.ts +10 -0
  24. package/dist/global/store.d.ts.map +1 -1
  25. package/dist/global/store.js +25 -1
  26. package/dist/global/store.js.map +1 -1
  27. package/dist/init/drift.d.ts +68 -0
  28. package/dist/init/drift.d.ts.map +1 -0
  29. package/dist/init/drift.js +103 -0
  30. package/dist/init/drift.js.map +1 -0
  31. package/dist/loop/engine.d.ts.map +1 -1
  32. package/dist/loop/engine.js +11 -4
  33. package/dist/loop/engine.js.map +1 -1
  34. package/dist/loop/ports.d.ts +19 -1
  35. package/dist/loop/ports.d.ts.map +1 -1
  36. package/dist/publish/githost.d.ts +9 -3
  37. package/dist/publish/githost.d.ts.map +1 -1
  38. package/dist/publish/githost.js +18 -4
  39. package/dist/publish/githost.js.map +1 -1
  40. package/dist/publish/github-publisher.d.ts +2 -2
  41. package/dist/publish/github-publisher.d.ts.map +1 -1
  42. package/dist/publish/github-publisher.js +3 -3
  43. package/dist/publish/github-publisher.js.map +1 -1
  44. package/dist/service/index.d.ts +3 -0
  45. package/dist/service/index.d.ts.map +1 -0
  46. package/dist/service/index.js +3 -0
  47. package/dist/service/index.js.map +1 -0
  48. package/dist/service/manager.d.ts +69 -0
  49. package/dist/service/manager.d.ts.map +1 -0
  50. package/dist/service/manager.js +107 -0
  51. package/dist/service/manager.js.map +1 -0
  52. package/dist/service/unit.d.ts +60 -0
  53. package/dist/service/unit.d.ts.map +1 -0
  54. package/dist/service/unit.js +111 -0
  55. package/dist/service/unit.js.map +1 -0
  56. package/package.json +1 -1
  57. package/skill/ghostrail/SKILL.md +62 -16
  58. package/templates/code-local/prompts/resolve-issue.md +15 -1
  59. package/templates/content-loop/prompts/draft.md +1 -1
@@ -3,7 +3,8 @@ import type { AgentOutcome } from "../loop/ports.js";
3
3
  * Parse the result document the agent writes at the end of a run into an
4
4
  * {@link AgentOutcome}. The document is JSON with a `status` of `done`,
5
5
  * `blocked`, `failed`, or `noop`:
6
- * - `done` requires a string `summary` (missing/empty becomes "").
6
+ * - `done` requires a string `summary` (missing/empty becomes ""), and may
7
+ * carry an optional `testPlan`.
7
8
  * - `blocked` requires a string `questions`.
8
9
  * - `failed` requires a string `error`.
9
10
  * - `noop` needs nothing else, and may carry a `reason` explaining why.
@@ -1 +1 @@
1
- {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/agent/result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CA4D1D"}
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/agent/result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAkE1D"}
@@ -2,7 +2,8 @@
2
2
  * Parse the result document the agent writes at the end of a run into an
3
3
  * {@link AgentOutcome}. The document is JSON with a `status` of `done`,
4
4
  * `blocked`, `failed`, or `noop`:
5
- * - `done` requires a string `summary` (missing/empty becomes "").
5
+ * - `done` requires a string `summary` (missing/empty becomes ""), and may
6
+ * carry an optional `testPlan`.
6
7
  * - `blocked` requires a string `questions`.
7
8
  * - `failed` requires a string `error`.
8
9
  * - `noop` needs nothing else, and may carry a `reason` explaining why.
@@ -32,10 +33,15 @@ export function parseAgentResult(raw) {
32
33
  const type = typeof rawType === "string" && /^[a-z]+$/.test(rawType.trim().toLowerCase())
33
34
  ? rawType.trim().toLowerCase()
34
35
  : undefined;
36
+ // Optional, and blank counts as absent: the PR body omits the whole
37
+ // section rather than heading an empty one.
38
+ const rawPlan = record.testPlan;
39
+ const testPlan = typeof rawPlan === "string" && rawPlan.trim().length > 0 ? rawPlan.trim() : undefined;
35
40
  return {
36
41
  status: "done",
37
42
  summary: asString(record.summary),
38
43
  ...(type === undefined ? {} : { type }),
44
+ ...(testPlan === undefined ? {} : { testPlan }),
39
45
  };
40
46
  }
41
47
  case "noop": {
@@ -1 +1 @@
1
- {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/agent/result.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/agent/result.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { ExecResult } from "../backend/types.js";
2
+ import { type ServiceManager } from "../service/index.js";
2
3
  import type { Scope } from "../vendor/skilltend/paths.js";
3
4
  import { type Asker } from "./prompt.js";
4
5
  export interface CommandStreams {
@@ -52,15 +53,37 @@ export declare function parseBoardFlags(argv: readonly string[], defaults: Board
52
53
  export declare function cmdBoard(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, cwd?: string): Promise<number>;
53
54
  /** `ghostrail artifacts`: list the shared artifacts available in the global store. */
54
55
  export declare function cmdArtifacts(argv: readonly string[], streams: CommandStreams, env?: CommandEnv): Promise<number>;
56
+ /**
57
+ * How a registered ghostrail stands on disk. A stale entry is reported and
58
+ * never auto-pruned: an unmounted drive or a moved repo does not mean the
59
+ * operator stopped wanting that ghostrail, and quietly deleting their config to
60
+ * tidy a listing would be presumptuous.
61
+ */
62
+ export type GhostrailHealth = "ok" | "no config" | "missing";
63
+ /** Check one registered path without trusting the registry's own claims. */
64
+ export declare function ghostrailHealth(path: string): Promise<GhostrailHealth>;
65
+ /** `ghostrail ls`: the ghostrails this account knows about. */
66
+ export declare function cmdLs(_argv: readonly string[], streams: CommandStreams, env?: CommandEnv): Promise<number>;
67
+ /**
68
+ * `ghostrail start`: choose which ghostrails should be running, record that in
69
+ * the registry, and make the services match.
70
+ */
71
+ export declare function cmdStart(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, ask?: Asker, isTty?: boolean, manager?: ServiceManager): Promise<number>;
72
+ /** `ghostrail stop`: take ghostrails down without changing what should run. */
73
+ export declare function cmdStop(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, manager?: ServiceManager): Promise<number>;
74
+ /** `ghostrail status`: what each ghostrail is doing, and what it should be doing. */
75
+ export declare function cmdStatus(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, manager?: ServiceManager): Promise<number>;
55
76
  /** The quickstart templates bundled with the package. */
56
77
  export declare const TEMPLATES: readonly ["code-local", "content-loop"];
57
78
  /**
58
- * `ghostrail init <template> [--repo <path>] [--force|--diff]`: scaffold
79
+ * `ghostrail init <template> [--repo <path>] [--force|--diff|--update]`: scaffold
59
80
  * `ghostrail.toml` and `prompts/` from a quickstart template. Safe for a fresh OR
60
81
  * an existing repo: existing files are left untouched (skipped) unless `--force`
61
- * is given. `--diff` reports drift instead of writing anything.
82
+ * is given. `--diff` reports drift instead of writing anything, and `--update`
83
+ * takes the template changes this repo never edited while naming what a
84
+ * customized file is missing.
62
85
  */
63
- export declare function cmdInit(argv: readonly string[], streams: CommandStreams, cwd?: string): Promise<number>;
86
+ export declare function cmdInit(argv: readonly string[], streams: CommandStreams, cwd?: string, env?: CommandEnv): Promise<number>;
64
87
  interface SkillFlags {
65
88
  scope: Scope;
66
89
  agents: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AA+DtD,OAAO,EAEL,KAAK,cAAc,EAKpB,MAAM,qBAAqB,CAAC;AAU7B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAE1D,OAAO,EAAE,KAAK,KAAK,EAAqC,MAAM,aAAa,CAAC;AAE5E,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,GAAG,YAAY,CAc/F;AAED,UAAU,UAAU;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACnC;AA2HD,iDAAiD;AACjD,wBAAsB,MAAM,CAC1B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CAQjB;AAED,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,SAAS,CAoB/E;AAED,sFAAsF;AACtF,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CAgCjB;AAED,sFAAsF;AACtF,wBAAsB,SAAS,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAED,gFAAgF;AAChF,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,YAAY,EAAE,OAAO,CAAC;IACtB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,CAyBzF;AAED,iFAAiF;AACjF,wBAAgB,QAAQ,CACtB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CA2EjB;AAED,sFAAsF;AACtF,wBAAsB,YAAY,CAChC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,WAAW,GAAG,SAAS,CAAC;AAE7D,4EAA4E;AAC5E,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAG5E;AAED,+DAA+D;AAC/D,wBAAsB,KAAK,CACzB,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,GAC5B,OAAO,CAAC,MAAM,CAAC,CA+BjB;AAwED;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,GAAE,KAAqB,EAC1B,KAAK,GAAE,OAAsC,EAC7C,OAAO,GAAE,cAAyD,GACjE,OAAO,CAAC,MAAM,CAAC,CAmFjB;AAED,+EAA+E;AAC/E,wBAAsB,OAAO,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,OAAO,GAAE,cAAyD,GACjE,OAAO,CAAC,MAAM,CAAC,CAuCjB;AAED,qFAAqF;AACrF,wBAAsB,SAAS,CAC7B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,OAAO,GAAE,cAAyD,GACjE,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAED,yDAAyD;AACzD,eAAO,MAAM,SAAS,yCAA0C,CAAC;AAkTjE;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,SAAgB,EACnB,GAAG,GAAE,UAAwB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAiHjB;AAED,UAAU,UAAU;IAClB,KAAK,EAAE,KAAK,CAAC;IACb,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,EAAE,OAAO,CAAC;IACnB,GAAG,EAAE,OAAO,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CA8BnE;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CAoDjB;AAiBD;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,GAAG,SAAgB,EACnB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,MAAM,CAAC,CAWjB;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,UAAU,EACf,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,IAAI,CAAC,CA8Cf;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,MAAM,CAAC,CAuDjB;AAqBD,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAClD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IACtD,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IACrD,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5D,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjE;AAiFD;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,GAC5B,OAAO,CAAC,MAAM,CAAC,CAoDjB;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAY,SAAQ,gBAAgB;IACnD,qEAAqE;IACrE,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACzD,+CAA+C;IAC/C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iEAAiE;IACjE,GAAG,CAAC,EAAE,KAAK,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,MAAM,CAAC,CAyBjB;AAwBD,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,GAAE,UAAwB,EAC7B,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,MAAM,CAAC,CAsCjB"}
@@ -1,7 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { access, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
- import { dirname, isAbsolute, join, sep } from "node:path";
4
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
5
5
  import process from "node:process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { spawnCollect } from "../backend/proc.js";
@@ -9,16 +9,19 @@ import { DEFAULT_BOARD_PORT, PORT_FALLBACK_ATTEMPTS, isAddressInUse, isValidPort
9
9
  import { createBoardServer, isLoopback } from "../board/server.js";
10
10
  import { buildProposal, writeProfile } from "../claude-profile/index.js";
11
11
  import { parseDuration } from "../config/duration.js";
12
- import { parseMergedConfig } from "../config/load.js";
12
+ import { parseConfig, parseMergedConfig } from "../config/load.js";
13
13
  import { STORE_DIR, buildLoopDeps, buildRespondDeps, buildTriageDeps, resolveRepoSlug, } from "../factory/build.js";
14
+ import { findByPath, upsert } from "../global/registry.js";
14
15
  import { GlobalStore, resolveGlobalDir } from "../global/store.js";
15
16
  import { DEFAULT_FACTORY_IMAGE, dockerBuildArgs, dockerDir, parseImageFlags, } from "../image/build.js";
17
+ import { extractPlaceholders, isClean, missingSignals, repoPathFor, } from "../init/drift.js";
16
18
  import { classify, hashContent, mergeManifest, readManifest, writeManifest, } from "../init/template.js";
17
19
  import { FileLock } from "../lock/file-lock.js";
18
20
  import { runTick } from "../loop/engine.js";
19
21
  import { watchLoop } from "../loop/watch.js";
20
22
  import { respondTick } from "../respond/respond.js";
21
23
  import { GLOBAL_SECRETS_FILE, appendPlaceholders, applyOverrides, launchEditor as defaultLaunchEditor, loadEnvOverrides, missingSecrets, readSecretsFile, redactSecrets, renderFile, resolveEditor, secretValuesOf, writeSecretsFile, } from "../secrets/index.js";
24
+ import { stopTimeoutSeconds, systemdManager, unitName, } from "../service/index.js";
22
25
  import { applySkillPlan, describePlan, pathCtx, planSkillInstall, uninstallSkill, } from "../skill/install.js";
23
26
  import { FileStore } from "../store/file-store.js";
24
27
  import { triageTick } from "../triage/engine.js";
@@ -374,6 +377,250 @@ export async function cmdArtifacts(argv, streams, env = process.env) {
374
377
  streams.out(`artifacts:\n${names.map((name) => ` ${name}`).join("\n")}\n`);
375
378
  return 0;
376
379
  }
380
+ /** Check one registered path without trusting the registry's own claims. */
381
+ export async function ghostrailHealth(path) {
382
+ if (!(await pathExists(path)))
383
+ return "missing";
384
+ return (await pathExists(join(path, "ghostrail.toml"))) ? "ok" : "no config";
385
+ }
386
+ /** `ghostrail ls`: the ghostrails this account knows about. */
387
+ export async function cmdLs(_argv, streams, env = process.env) {
388
+ const store = globalStoreFor(env);
389
+ const entries = await store.readRegistry();
390
+ if (entries === undefined) {
391
+ streams.err(`cannot parse ${store.registryPath()}\n`);
392
+ streams.err("fix or remove it; a registry that does not parse is treated as none at all\n");
393
+ return 1;
394
+ }
395
+ if (entries.length === 0) {
396
+ streams.out("no ghostrails registered yet\n");
397
+ streams.out("`ghostrail init <template>` registers a new one, and\n");
398
+ streams.out("`ghostrail init <template> --update` adopts a repo set up before the registry\n");
399
+ return 0;
400
+ }
401
+ const rows = await Promise.all(entries.map(async (entry) => ({ entry, health: await ghostrailHealth(entry.path) })));
402
+ const width = Math.max(...rows.map((row) => row.entry.name.length));
403
+ streams.out(`${store.registryPath()}\n\n`);
404
+ for (const { entry, health } of rows) {
405
+ const watch = entry.watch ? "watch" : " off";
406
+ const note = health === "ok" ? "" : ` (${health})`;
407
+ streams.out(` ${watch} ${entry.name.padEnd(width)} ${entry.path}${note}\n`);
408
+ }
409
+ if (rows.some((row) => row.health !== "ok")) {
410
+ streams.out("\nsome entries no longer resolve. remove one by deleting its block from\n");
411
+ streams.out(`${store.registryPath()}\n`);
412
+ }
413
+ return 0;
414
+ }
415
+ /** Resolve the absolute path of the running ghostrail binary, for a unit file. */
416
+ function ghostrailExecPath() {
417
+ return process.argv[1] ?? "ghostrail";
418
+ }
419
+ /** A ghostrail's configured agent timeout, for the unit's stop timeout. */
420
+ async function agentTimeoutMs(repo) {
421
+ const raw = await readIfPresent(join(repo, "ghostrail.toml"));
422
+ if (raw !== undefined) {
423
+ const parsed = parseConfig(raw);
424
+ if (parsed.ok)
425
+ return parsed.value.guardrails.timeoutMs;
426
+ }
427
+ return 30 * 60_000;
428
+ }
429
+ function parseSelectFlags(argv) {
430
+ const flags = { all: false, names: [], interval: "5m", yes: false };
431
+ for (let i = 0; i < argv.length; i++) {
432
+ const arg = argv[i];
433
+ const next = argv[i + 1];
434
+ if (arg === "--all" || arg === "-a")
435
+ flags.all = true;
436
+ else if ((arg === "--name" || arg === "-n") && next !== undefined) {
437
+ flags.names.push(next);
438
+ i++;
439
+ }
440
+ else if ((arg === "--interval" || arg === "-i") && next !== undefined) {
441
+ flags.interval = next;
442
+ i++;
443
+ }
444
+ else if (arg === "--yes" || arg === "-y")
445
+ flags.yes = true;
446
+ }
447
+ return flags;
448
+ }
449
+ /** Load the registry, reporting the two failure shapes distinctly. */
450
+ async function loadRegistry(store, streams) {
451
+ const entries = await store.readRegistry();
452
+ if (entries === undefined) {
453
+ streams.err(`cannot parse ${store.registryPath()}\n`);
454
+ return undefined;
455
+ }
456
+ if (entries.length === 0) {
457
+ streams.out("no ghostrails registered yet\n");
458
+ streams.out("`ghostrail init <template>` registers one, and\n");
459
+ streams.out("`ghostrail init <template> --update` adopts an existing repo\n");
460
+ }
461
+ return entries;
462
+ }
463
+ /** Say what platform we are on when there is no service manager to use. */
464
+ function explainNoManager(kind, streams, interval) {
465
+ if (kind === "launchd") {
466
+ streams.err("this machine uses launchd, which ghostrail cannot install services for yet\n");
467
+ }
468
+ else {
469
+ streams.err("no user service manager found (looked for systemd)\n");
470
+ }
471
+ streams.err(`run a ghostrail by hand instead:\n cd <repo> && ghostrail watch --interval ${interval}\n`);
472
+ }
473
+ /**
474
+ * `ghostrail start`: choose which ghostrails should be running, record that in
475
+ * the registry, and make the services match.
476
+ */
477
+ export async function cmdStart(argv, streams, env = process.env, ask = askOnTerminal, isTty = process.stdin.isTTY === true, manager = systemdManager(env)) {
478
+ const flags = parseSelectFlags(argv);
479
+ const store = globalStoreFor(env);
480
+ const entries = await loadRegistry(store, streams);
481
+ if (entries === undefined)
482
+ return 1;
483
+ if (entries.length === 0)
484
+ return 0;
485
+ // Which ghostrails should be watched after this command.
486
+ let chosen;
487
+ if (flags.all) {
488
+ chosen = [...entries];
489
+ }
490
+ else if (flags.names.length > 0) {
491
+ const wanted = new Set(flags.names);
492
+ chosen = entries.filter((e) => wanted.has(e.name) || wanted.has(e.path));
493
+ const missing = flags.names.filter((n) => !entries.some((e) => e.name === n || e.path === n));
494
+ for (const name of missing)
495
+ streams.err(`no registered ghostrail named ${name}\n`);
496
+ if (missing.length > 0)
497
+ return 1;
498
+ }
499
+ else if (isTty && !flags.yes) {
500
+ streams.out("Which ghostrails do you want to start?\n");
501
+ chosen = [];
502
+ for (const entry of entries) {
503
+ // Pre-answered from `watch`, so the prompt reflects the standing decision
504
+ // rather than asking it fresh every time.
505
+ if (await ask(` ${entry.name} (${entry.path})`, entry.watch))
506
+ chosen.push(entry);
507
+ }
508
+ }
509
+ else {
510
+ chosen = entries.filter((e) => e.watch);
511
+ }
512
+ const chosenPaths = new Set(chosen.map((e) => e.path));
513
+ // The selection IS the watch flag. One piece of state, so the CLI, the
514
+ // dashboard, and the units cannot disagree about what should be running.
515
+ const updated = entries.map((e) => ({ ...e, watch: chosenPaths.has(e.path) }));
516
+ await store.writeRegistry(updated);
517
+ const kind = await manager.kind();
518
+ if (kind !== "systemd") {
519
+ explainNoManager(kind, streams, flags.interval);
520
+ return 1;
521
+ }
522
+ let failed = false;
523
+ for (const entry of updated) {
524
+ const unit = unitName(entry.name, entry.path);
525
+ if (!entry.watch) {
526
+ const error = await manager.disable(unit);
527
+ if (error === undefined)
528
+ streams.out(` stopped ${entry.name}\n`);
529
+ continue;
530
+ }
531
+ if ((await ghostrailHealth(entry.path)) !== "ok") {
532
+ streams.err(` skipped ${entry.name}: ${entry.path} has no ghostrail.toml\n`);
533
+ failed = true;
534
+ continue;
535
+ }
536
+ const spec = {
537
+ name: entry.name,
538
+ path: entry.path,
539
+ exec: ghostrailExecPath(),
540
+ interval: flags.interval,
541
+ stopTimeoutSec: stopTimeoutSeconds(await agentTimeoutMs(entry.path)),
542
+ };
543
+ const { outcome, file } = await manager.install(spec);
544
+ if (outcome === "hand-edited") {
545
+ streams.err(` skipped ${entry.name}: ${file} was edited; remove it to regenerate\n`);
546
+ failed = true;
547
+ continue;
548
+ }
549
+ const error = await manager.enable(unit);
550
+ if (error !== undefined) {
551
+ streams.err(` failed ${entry.name}: ${error}\n`);
552
+ failed = true;
553
+ continue;
554
+ }
555
+ streams.out(` started ${entry.name} (${outcome === "written" ? "new unit" : "unit unchanged"})\n`);
556
+ }
557
+ streams.out("\n`watch` ticks the fix queue only; `respond` and `triage` still need their own schedule\n");
558
+ return failed ? 1 : 0;
559
+ }
560
+ /** `ghostrail stop`: take ghostrails down without changing what should run. */
561
+ export async function cmdStop(argv, streams, env = process.env, manager = systemdManager(env)) {
562
+ const flags = parseSelectFlags(argv);
563
+ const store = globalStoreFor(env);
564
+ const entries = await loadRegistry(store, streams);
565
+ if (entries === undefined)
566
+ return 1;
567
+ if (entries.length === 0)
568
+ return 0;
569
+ const wanted = new Set(flags.names);
570
+ const targets = flags.names.length > 0
571
+ ? entries.filter((e) => wanted.has(e.name) || wanted.has(e.path))
572
+ : entries.filter((e) => flags.all || e.watch);
573
+ if (targets.length === 0) {
574
+ streams.out("nothing to stop\n");
575
+ return 0;
576
+ }
577
+ const kind = await manager.kind();
578
+ if (kind !== "systemd") {
579
+ explainNoManager(kind, streams, flags.interval);
580
+ return 1;
581
+ }
582
+ // Say this before the first stop, not after: an in-flight tick is not
583
+ // interrupted, so this can sit for minutes and look hung.
584
+ streams.out("stopping; a ghostrail mid-tick finishes its run first, which can take minutes\n");
585
+ let failed = false;
586
+ for (const entry of targets) {
587
+ const error = await manager.disable(unitName(entry.name, entry.path));
588
+ if (error !== undefined) {
589
+ streams.err(` failed ${entry.name}: ${error}\n`);
590
+ failed = true;
591
+ continue;
592
+ }
593
+ streams.out(` stopped ${entry.name}\n`);
594
+ }
595
+ // Deliberately does not touch `watch`: "not running now" and "should not run"
596
+ // are different statements, and only the operator makes the second one.
597
+ return failed ? 1 : 0;
598
+ }
599
+ /** `ghostrail status`: what each ghostrail is doing, and what it should be doing. */
600
+ export async function cmdStatus(argv, streams, env = process.env, manager = systemdManager(env)) {
601
+ const store = globalStoreFor(env);
602
+ const entries = await loadRegistry(store, streams);
603
+ if (entries === undefined)
604
+ return 1;
605
+ if (entries.length === 0)
606
+ return 0;
607
+ const kind = await manager.kind();
608
+ const units = entries.map((e) => unitName(e.name, e.path));
609
+ const states = kind === "systemd" ? await manager.states(units) : new Map();
610
+ const width = Math.max(...entries.map((e) => e.name.length));
611
+ for (const [i, entry] of entries.entries()) {
612
+ const unit = units[i];
613
+ const state = kind === "systemd" ? (states.get(unit) ?? "unknown") : "unknown";
614
+ const health = await ghostrailHealth(entry.path);
615
+ const wants = entry.watch ? "watch" : " off";
616
+ const note = health === "ok" ? "" : ` (${health})`;
617
+ streams.out(` ${wants} ${state.padEnd(8)} ${entry.name.padEnd(width)} ${entry.path}${note}\n`);
618
+ }
619
+ if (kind !== "systemd") {
620
+ streams.out("\nno supported service manager here, so nothing is reported as running\n");
621
+ }
622
+ return 0;
623
+ }
377
624
  /** The quickstart templates bundled with the package. */
378
625
  export const TEMPLATES = ["code-local", "content-loop"];
379
626
  function templatesDir() {
@@ -477,17 +724,171 @@ async function reportTemplateDiff(template, srcDir, repo, streams) {
477
724
  }
478
725
  return 0;
479
726
  }
727
+ /** The global store for this environment. */
728
+ function globalStoreFor(env) {
729
+ return new GlobalStore(resolveGlobalDir({ GHOSTRAIL_HOME: env.GHOSTRAIL_HOME, XDG_CONFIG_HOME: env.XDG_CONFIG_HOME }, homedir()));
730
+ }
731
+ /** A ghostrail's display name: `[factory].name`, else the directory. */
732
+ async function ghostrailName(repo) {
733
+ const raw = await readIfPresent(join(repo, "ghostrail.toml"));
734
+ if (raw !== undefined) {
735
+ const parsed = parseConfig(raw);
736
+ if (parsed.ok && parsed.value.factory.name.length > 0)
737
+ return parsed.value.factory.name;
738
+ }
739
+ return basename(repo);
740
+ }
741
+ /**
742
+ * Record this repo in the ghostrails registry, so `start` and the board have
743
+ * something to list. Adding only: an entry that already exists is left exactly
744
+ * as it is, because re-running `init --update` must not switch `watch` back on
745
+ * for a ghostrail the operator deliberately turned off.
746
+ *
747
+ * Never fails the command. Scaffolding the repo is the job; registering it is a
748
+ * convenience, and aborting a good scaffold over an unwritable config dir would
749
+ * be the worse trade.
750
+ */
751
+ async function registerGhostrail(repo, env, streams) {
752
+ const store = globalStoreFor(env);
753
+ const path = resolve(repo);
754
+ try {
755
+ const entries = await store.readRegistry();
756
+ if (entries === undefined) {
757
+ streams.err(`note: ${store.registryPath()} could not be parsed, so this ghostrail was not registered\n`);
758
+ return;
759
+ }
760
+ if (findByPath(entries, path) !== undefined)
761
+ return;
762
+ const entry = {
763
+ name: await ghostrailName(repo),
764
+ path,
765
+ added: new Date().toISOString().slice(0, 10),
766
+ watch: true,
767
+ };
768
+ await store.writeRegistry(upsert(entries, entry));
769
+ streams.out(`registered ghostrail "${entry.name}" (${path})\n`);
770
+ }
771
+ catch (error) {
772
+ streams.err(`note: could not register this ghostrail: ${error instanceof Error ? error.message : String(error)}\n`);
773
+ }
774
+ }
775
+ /**
776
+ * Prompt paths a config file names, by role. `undefined` when the file cannot
777
+ * be read or parsed, which is a different thing from a config that names no
778
+ * prompts, and the two need opposite handling in {@link repoPathFor}.
779
+ */
780
+ async function promptPathsAt(configPath) {
781
+ const raw = await readIfPresent(configPath);
782
+ if (raw === undefined)
783
+ return undefined;
784
+ const parsed = parseConfig(raw);
785
+ if (!parsed.ok)
786
+ return undefined;
787
+ const { agent, respond, triage } = parsed.value;
788
+ return {
789
+ agent: agent.prompt,
790
+ ...(respond === undefined ? {} : { respond: respond.prompt }),
791
+ ...(triage === undefined ? {} : { triage: triage.prompt }),
792
+ };
793
+ }
794
+ /**
795
+ * `ghostrail init <template> --update`: take the template changes this repo
796
+ * never edited, and name what a customized file is missing.
797
+ *
798
+ * Two jobs split by whether the answer is mechanical. A file still matching its
799
+ * recorded baseline was never touched, so a changed template can overwrite it.
800
+ * A file the repo edited is never written; instead the placeholders and result
801
+ * fields the template gained are named, because that is what every hand port has
802
+ * actually consisted of, and the skill places them in the repo's own wording.
803
+ */
804
+ async function applyTemplateUpdate(template, srcDir, repo, streams, env) {
805
+ const manifest = await readManifest(repo);
806
+ const rels = await scaffoldFiles(srcDir);
807
+ const templatePrompts = (await promptPathsAt(join(srcDir, "ghostrail.toml"))) ?? {};
808
+ const repoPrompts = await promptPathsAt(join(repo, "ghostrail.toml"));
809
+ // The installed template is the last PUBLISHED release, so say what was
810
+ // compared against: a repo can be current with the release and behind main.
811
+ streams.out(`comparing against ${template} @ ${VERSION}\n\n`);
812
+ const written = {};
813
+ let pending = 0;
814
+ let took = 0;
815
+ for (const rel of rels) {
816
+ const key = manifestKey(rel);
817
+ const repoRel = repoPathFor(key, templatePrompts, repoPrompts);
818
+ if (repoRel === undefined) {
819
+ // A stage this repo has not enabled. Nothing to carry into it.
820
+ streams.out(` ${key}\n not configured in this repo, skipped\n`);
821
+ continue;
822
+ }
823
+ const templateContent = await readFile(join(srcDir, rel), "utf8");
824
+ const current = await readIfPresent(join(repo, repoRel));
825
+ const status = classify(current === undefined ? undefined : hashContent(current), hashContent(templateContent), manifest?.files[key]);
826
+ if (status === "up-to-date") {
827
+ streams.out(` ${repoRel}\n up to date\n`);
828
+ continue;
829
+ }
830
+ if (status === "missing") {
831
+ // Scaffolding an absent file is what plain `init` is for. Doing it here
832
+ // would make --update write files nobody asked it to carry, and would
833
+ // break its one safety claim: it only ever writes what it can prove is
834
+ // untouched.
835
+ streams.out(` ${repoRel}\n absent; \`ghostrail init ${template}\` scaffolds it\n`);
836
+ continue;
837
+ }
838
+ if (status === "upstream-changed") {
839
+ // Still hashes to its baseline, so the repo never edited it and taking
840
+ // the template destroys nothing. This is the only case that writes.
841
+ await mkdir(dirname(join(repo, repoRel)), { recursive: true });
842
+ await writeFile(join(repo, repoRel), templateContent, "utf8");
843
+ written[key] = hashContent(templateContent);
844
+ took++;
845
+ streams.out(` ${repoRel}\n took the template's version (you had not edited it)\n`);
846
+ continue;
847
+ }
848
+ // Customized, or no baseline to prove otherwise. Never written.
849
+ const signals = missingSignals(current ?? "", templateContent);
850
+ if (isClean(signals)) {
851
+ streams.out(` ${repoRel}\n customized, nothing to take\n`);
852
+ continue;
853
+ }
854
+ streams.out(` ${repoRel}\n customized, and the template gained:\n`);
855
+ for (const name of signals.placeholders) {
856
+ streams.out(` placeholder {{${name}}}\n`);
857
+ pending++;
858
+ }
859
+ for (const name of signals.fields) {
860
+ streams.out(` result field "${name}"\n`);
861
+ pending++;
862
+ }
863
+ }
864
+ if (Object.keys(written).length > 0) {
865
+ await writeManifest(repo, mergeManifest(manifest, template, VERSION, written));
866
+ }
867
+ await registerGhostrail(repo, env, streams);
868
+ streams.out("\n");
869
+ if (took > 0)
870
+ streams.out(`took ${took} file(s) you had not edited\n`);
871
+ if (pending === 0) {
872
+ streams.out("nothing needs placing by hand\n");
873
+ return 0;
874
+ }
875
+ streams.out(`${pending} signal(s) need placing in prompts you have customized.\nRun \`/ghostrail update\` and the skill will port them into your own wording.\n`);
876
+ return 0;
877
+ }
480
878
  /**
481
- * `ghostrail init <template> [--repo <path>] [--force|--diff]`: scaffold
879
+ * `ghostrail init <template> [--repo <path>] [--force|--diff|--update]`: scaffold
482
880
  * `ghostrail.toml` and `prompts/` from a quickstart template. Safe for a fresh OR
483
881
  * an existing repo: existing files are left untouched (skipped) unless `--force`
484
- * is given. `--diff` reports drift instead of writing anything.
882
+ * is given. `--diff` reports drift instead of writing anything, and `--update`
883
+ * takes the template changes this repo never edited while naming what a
884
+ * customized file is missing.
485
885
  */
486
- export async function cmdInit(argv, streams, cwd = process.cwd()) {
886
+ export async function cmdInit(argv, streams, cwd = process.cwd(), env = process.env) {
487
887
  let template;
488
888
  let repo = cwd;
489
889
  let force = false;
490
890
  let diff = false;
891
+ let update = false;
491
892
  for (let i = 0; i < argv.length; i++) {
492
893
  const arg = argv[i];
493
894
  const next = argv[i + 1];
@@ -501,19 +902,37 @@ export async function cmdInit(argv, streams, cwd = process.cwd()) {
501
902
  else if (arg === "--diff") {
502
903
  diff = true;
503
904
  }
905
+ else if (arg === "--update") {
906
+ update = true;
907
+ }
504
908
  else if (arg !== undefined && !arg.startsWith("-")) {
505
909
  template ??= arg;
506
910
  }
507
911
  }
508
912
  if (template === undefined || !TEMPLATES.includes(template)) {
509
- streams.err(`usage: ghostrail init <template> [--repo <path>] [--force|--diff]\ntemplates: ${TEMPLATES.join(", ")}\n`);
913
+ streams.err(`usage: ghostrail init <template> [--repo <path>] [--force|--diff|--update]\ntemplates: ${TEMPLATES.join(", ")}\n`);
510
914
  return 1;
511
915
  }
512
916
  if (diff && force) {
513
917
  streams.err("init: --diff is read-only and cannot be combined with --force\n");
514
918
  return 1;
515
919
  }
920
+ if (update && (force || diff)) {
921
+ // --force overwrites customizations and --diff writes nothing, so neither
922
+ // has a sensible ordering against --update. Refuse rather than pick one.
923
+ streams.err("init: --update cannot be combined with --force or --diff\n");
924
+ return 1;
925
+ }
516
926
  const srcDir = join(templatesDir(), template);
927
+ if (update) {
928
+ try {
929
+ return await applyTemplateUpdate(template, srcDir, repo, streams, env);
930
+ }
931
+ catch (error) {
932
+ streams.err(`init --update failed: ${error instanceof Error ? error.message : String(error)}\n`);
933
+ return 1;
934
+ }
935
+ }
517
936
  if (diff) {
518
937
  try {
519
938
  return await reportTemplateDiff(template, srcDir, repo, streams);
@@ -567,7 +986,7 @@ export async function cmdInit(argv, streams, cwd = process.cwd()) {
567
986
  streams.out(`, kept ${skipped.length} existing (${skipped.join(", ")}; use --force to overwrite)`);
568
987
  }
569
988
  streams.out("\n");
570
- // A pointer, not an action: init does not write outside the repo's own files.
989
+ await registerGhostrail(repo, env, streams);
571
990
  streams.out("next: `ghostrail skill` installs the /ghostrail skill for your coding agent,\n");
572
991
  streams.out(" then run `/ghostrail customize` there to tailor the prompts to this repo\n");
573
992
  return 0;