ghostrail 0.0.0 → 0.1.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 (130) hide show
  1. package/README.md +29 -2
  2. package/dist/agent/claude-code.d.ts +10 -0
  3. package/dist/agent/claude-code.d.ts.map +1 -1
  4. package/dist/agent/claude-code.js +20 -0
  5. package/dist/agent/claude-code.js.map +1 -1
  6. package/dist/agent/result.d.ts.map +1 -1
  7. package/dist/agent/result.js +13 -2
  8. package/dist/agent/result.js.map +1 -1
  9. package/dist/backend/container.d.ts +18 -0
  10. package/dist/backend/container.d.ts.map +1 -1
  11. package/dist/backend/container.js +33 -10
  12. package/dist/backend/container.js.map +1 -1
  13. package/dist/backend/git-credentials.d.ts +18 -0
  14. package/dist/backend/git-credentials.d.ts.map +1 -0
  15. package/dist/backend/git-credentials.js +30 -0
  16. package/dist/backend/git-credentials.js.map +1 -0
  17. package/dist/backend/local-worktree.d.ts.map +1 -1
  18. package/dist/backend/local-worktree.js +21 -0
  19. package/dist/backend/local-worktree.js.map +1 -1
  20. package/dist/backend/types.d.ts +12 -1
  21. package/dist/backend/types.d.ts.map +1 -1
  22. package/dist/claude-profile/classify.d.ts +41 -0
  23. package/dist/claude-profile/classify.d.ts.map +1 -0
  24. package/dist/claude-profile/classify.js +103 -0
  25. package/dist/claude-profile/classify.js.map +1 -0
  26. package/dist/claude-profile/index.d.ts +3 -0
  27. package/dist/claude-profile/index.d.ts.map +1 -0
  28. package/dist/claude-profile/index.js +3 -0
  29. package/dist/claude-profile/index.js.map +1 -0
  30. package/dist/claude-profile/setup.d.ts +33 -0
  31. package/dist/claude-profile/setup.d.ts.map +1 -0
  32. package/dist/claude-profile/setup.js +81 -0
  33. package/dist/claude-profile/setup.js.map +1 -0
  34. package/dist/cli/commands.d.ts +27 -2
  35. package/dist/cli/commands.d.ts.map +1 -1
  36. package/dist/cli/commands.js +275 -17
  37. package/dist/cli/commands.js.map +1 -1
  38. package/dist/cli.d.ts.map +1 -1
  39. package/dist/cli.js +7 -1
  40. package/dist/cli.js.map +1 -1
  41. package/dist/commands/help.d.ts.map +1 -1
  42. package/dist/commands/help.js +9 -2
  43. package/dist/commands/help.js.map +1 -1
  44. package/dist/config/load.d.ts.map +1 -1
  45. package/dist/config/load.js +61 -9
  46. package/dist/config/load.js.map +1 -1
  47. package/dist/config/schema.d.ts +51 -3
  48. package/dist/config/schema.d.ts.map +1 -1
  49. package/dist/config/schema.js +4 -0
  50. package/dist/config/schema.js.map +1 -1
  51. package/dist/factory/build.d.ts +24 -0
  52. package/dist/factory/build.d.ts.map +1 -1
  53. package/dist/factory/build.js +80 -11
  54. package/dist/factory/build.js.map +1 -1
  55. package/dist/loop/concurrency.d.ts +13 -0
  56. package/dist/loop/concurrency.d.ts.map +1 -0
  57. package/dist/loop/concurrency.js +27 -0
  58. package/dist/loop/concurrency.js.map +1 -0
  59. package/dist/loop/engine.d.ts.map +1 -1
  60. package/dist/loop/engine.js +19 -14
  61. package/dist/loop/engine.js.map +1 -1
  62. package/dist/loop/index.d.ts +2 -0
  63. package/dist/loop/index.d.ts.map +1 -1
  64. package/dist/loop/index.js +2 -0
  65. package/dist/loop/index.js.map +1 -1
  66. package/dist/loop/ports.d.ts +37 -1
  67. package/dist/loop/ports.d.ts.map +1 -1
  68. package/dist/loop/watch.d.ts +25 -0
  69. package/dist/loop/watch.d.ts.map +1 -0
  70. package/dist/loop/watch.js +45 -0
  71. package/dist/loop/watch.js.map +1 -0
  72. package/dist/publish/gh.d.ts +20 -1
  73. package/dist/publish/gh.d.ts.map +1 -1
  74. package/dist/publish/gh.js +37 -3
  75. package/dist/publish/gh.js.map +1 -1
  76. package/dist/publish/githost.d.ts +12 -0
  77. package/dist/publish/githost.d.ts.map +1 -1
  78. package/dist/publish/githost.js +14 -0
  79. package/dist/publish/githost.js.map +1 -1
  80. package/dist/publish/github-publisher.d.ts +1 -1
  81. package/dist/publish/github-publisher.d.ts.map +1 -1
  82. package/dist/publish/github-publisher.js +6 -5
  83. package/dist/publish/github-publisher.js.map +1 -1
  84. package/dist/respond/respond.d.ts +10 -1
  85. package/dist/respond/respond.d.ts.map +1 -1
  86. package/dist/respond/respond.js +37 -3
  87. package/dist/respond/respond.js.map +1 -1
  88. package/dist/secrets/env-file.d.ts +23 -0
  89. package/dist/secrets/env-file.d.ts.map +1 -0
  90. package/dist/secrets/env-file.js +70 -0
  91. package/dist/secrets/env-file.js.map +1 -0
  92. package/dist/secrets/index.d.ts +4 -0
  93. package/dist/secrets/index.d.ts.map +1 -0
  94. package/dist/secrets/index.js +4 -0
  95. package/dist/secrets/index.js.map +1 -0
  96. package/dist/secrets/load.d.ts +24 -0
  97. package/dist/secrets/load.d.ts.map +1 -0
  98. package/dist/secrets/load.js +43 -0
  99. package/dist/secrets/load.js.map +1 -0
  100. package/dist/source/linear.d.ts +51 -8
  101. package/dist/source/linear.d.ts.map +1 -1
  102. package/dist/source/linear.js +213 -23
  103. package/dist/source/linear.js.map +1 -1
  104. package/dist/triage/apply.d.ts +28 -0
  105. package/dist/triage/apply.d.ts.map +1 -0
  106. package/dist/triage/apply.js +68 -0
  107. package/dist/triage/apply.js.map +1 -0
  108. package/dist/triage/engine.d.ts +31 -0
  109. package/dist/triage/engine.d.ts.map +1 -0
  110. package/dist/triage/engine.js +122 -0
  111. package/dist/triage/engine.js.map +1 -0
  112. package/dist/triage/index.d.ts +4 -0
  113. package/dist/triage/index.d.ts.map +1 -0
  114. package/dist/triage/index.js +4 -0
  115. package/dist/triage/index.js.map +1 -0
  116. package/dist/triage/plan.d.ts +37 -0
  117. package/dist/triage/plan.d.ts.map +1 -0
  118. package/dist/triage/plan.js +75 -0
  119. package/dist/triage/plan.js.map +1 -0
  120. package/docker/factory.Dockerfile +39 -0
  121. package/package.json +4 -3
  122. package/templates/{fix-bugs-local → code-local}/README.md +3 -3
  123. package/templates/code-local/ghostrail.toml +44 -0
  124. package/templates/code-local/prompts/resolve-issue.md +31 -0
  125. package/templates/code-local/prompts/respond.md +27 -0
  126. package/templates/code-local/prompts/triage.md +51 -0
  127. package/templates/content-loop/ghostrail.toml +5 -2
  128. package/templates/content-loop/prompts/respond.md +25 -0
  129. package/templates/fix-bugs-local/ghostrail.toml +0 -37
  130. package/templates/fix-bugs-local/prompts/fix-bug.md +0 -27
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classify.js","sourceRoot":"","sources":["../../src/claude-profile/classify.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { type ClaudeMdSection, classifyClaudeMdSections, classifySettingsKeys, composeFactoryClaudeMd, composeFactorySettings, type Section, type SettingsKeyReport, splitSections, type Verdict, } from "./classify.js";
2
+ export { buildProposal, type SetupProposal, type SkillReport, writeProfile } from "./setup.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/claude-profile/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,eAAe,EACpB,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,OAAO,EACZ,KAAK,iBAAiB,EACtB,aAAa,EACb,KAAK,OAAO,GACb,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,KAAK,WAAW,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { classifyClaudeMdSections, classifySettingsKeys, composeFactoryClaudeMd, composeFactorySettings, splitSections, } from "./classify.js";
2
+ export { buildProposal, writeProfile } from "./setup.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/claude-profile/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,wBAAwB,EACxB,oBAAoB,EACpB,sBAAsB,EACtB,sBAAsB,EAGtB,aAAa,GAEd,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,aAAa,EAAwC,YAAY,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,33 @@
1
+ import { type ClaudeMdSection, type SettingsKeyReport } from "./classify.js";
2
+ export interface SkillReport {
3
+ name: string;
4
+ pin: boolean;
5
+ }
6
+ /** A proposal for the factory Claude profile, derived from an existing ~/.claude. */
7
+ export interface SetupProposal {
8
+ sourceHome: string;
9
+ profileDir: string;
10
+ claudeMd: {
11
+ present: boolean;
12
+ sections: ClaudeMdSection[];
13
+ };
14
+ settings: {
15
+ present: boolean;
16
+ keys: SettingsKeyReport[];
17
+ };
18
+ skills: SkillReport[];
19
+ /** Skills requested via --pin that are not installed in the source ~/.claude/skills. */
20
+ missingPins: string[];
21
+ }
22
+ /**
23
+ * Inspect the source `~/.claude` and propose a factory profile: which CLAUDE.md
24
+ * sections to keep vs drop (interactive-only), which settings carry over, and
25
+ * which installed skills to pin. Pure inspection; writes nothing.
26
+ */
27
+ export declare function buildProposal(sourceHome: string, profileDir: string, pin: readonly string[]): Promise<SetupProposal>;
28
+ /**
29
+ * Write the profile the proposal describes: the composed factory CLAUDE.md (kept
30
+ * sections only), a minimal settings.json, and a copy of each pinned skill.
31
+ */
32
+ export declare function writeProfile(proposal: SetupProposal): Promise<void>;
33
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/claude-profile/setup.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,iBAAiB,EAMvB,MAAM,eAAe,CAAC;AAEvB,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;CACd;AAED,qFAAqF;AACrF,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,eAAe,EAAE,CAAA;KAAE,CAAC;IAC5D,QAAQ,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,iBAAiB,EAAE,CAAA;KAAE,CAAC;IAC1D,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,wFAAwF;IACxF,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAqBD;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,SAAS,MAAM,EAAE,GACrB,OAAO,CAAC,aAAa,CAAC,CAgCxB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAyBzE"}
@@ -0,0 +1,81 @@
1
+ import { cp, mkdir, readFile, readdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { classifyClaudeMdSections, classifySettingsKeys, composeFactoryClaudeMd, composeFactorySettings, splitSections, } from "./classify.js";
4
+ async function readIfPresent(path) {
5
+ try {
6
+ return await readFile(path, "utf8");
7
+ }
8
+ catch {
9
+ return undefined;
10
+ }
11
+ }
12
+ async function listDirs(path) {
13
+ try {
14
+ return (await readdir(path, { withFileTypes: true }))
15
+ .filter((entry) => entry.isDirectory())
16
+ .map((entry) => entry.name)
17
+ .sort();
18
+ }
19
+ catch {
20
+ return [];
21
+ }
22
+ }
23
+ /**
24
+ * Inspect the source `~/.claude` and propose a factory profile: which CLAUDE.md
25
+ * sections to keep vs drop (interactive-only), which settings carry over, and
26
+ * which installed skills to pin. Pure inspection; writes nothing.
27
+ */
28
+ export async function buildProposal(sourceHome, profileDir, pin) {
29
+ const claudeDir = join(sourceHome, ".claude");
30
+ const mdRaw = await readIfPresent(join(claudeDir, "CLAUDE.md"));
31
+ const sections = mdRaw === undefined ? [] : classifyClaudeMdSections(splitSections(mdRaw));
32
+ const settingsRaw = await readIfPresent(join(claudeDir, "settings.json"));
33
+ let settingsKeys = [];
34
+ if (settingsRaw !== undefined) {
35
+ try {
36
+ const parsed = JSON.parse(settingsRaw);
37
+ if (parsed !== null && typeof parsed === "object") {
38
+ settingsKeys = classifySettingsKeys(parsed);
39
+ }
40
+ }
41
+ catch {
42
+ // malformed settings: report nothing
43
+ }
44
+ }
45
+ const installed = await listDirs(join(claudeDir, "skills"));
46
+ const pinSet = new Set(pin);
47
+ const skills = installed.map((name) => ({ name, pin: pinSet.has(name) }));
48
+ const missingPins = pin.filter((name) => !installed.includes(name));
49
+ return {
50
+ sourceHome,
51
+ profileDir,
52
+ claudeMd: { present: mdRaw !== undefined, sections },
53
+ settings: { present: settingsRaw !== undefined, keys: settingsKeys },
54
+ skills,
55
+ missingPins,
56
+ };
57
+ }
58
+ /**
59
+ * Write the profile the proposal describes: the composed factory CLAUDE.md (kept
60
+ * sections only), a minimal settings.json, and a copy of each pinned skill.
61
+ */
62
+ export async function writeProfile(proposal) {
63
+ const claudeDir = join(proposal.profileDir, ".claude");
64
+ await mkdir(claudeDir, { recursive: true });
65
+ await writeFile(join(claudeDir, "CLAUDE.md"), composeFactoryClaudeMd(proposal.claudeMd.sections), "utf8");
66
+ await writeFile(join(claudeDir, "settings.json"), `${JSON.stringify(composeFactorySettings(), null, 2)}\n`, "utf8");
67
+ const skillsDir = join(claudeDir, "skills");
68
+ await mkdir(skillsDir, { recursive: true });
69
+ for (const skill of proposal.skills) {
70
+ if (!skill.pin)
71
+ continue;
72
+ const source = join(proposal.sourceHome, ".claude", "skills", skill.name);
73
+ try {
74
+ await cp(source, join(skillsDir, skill.name), { recursive: true, force: true });
75
+ }
76
+ catch {
77
+ // a pinned-but-uncopyable skill is reported via missingPins; skip
78
+ }
79
+ }
80
+ }
81
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/claude-profile/setup.ts"],"names":[],"mappings":"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"}
@@ -17,6 +17,20 @@ interface CommandEnv {
17
17
  }
18
18
  /** `ghostrail run`: drain eligible work once. */
19
19
  export declare function cmdRun(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, cwd?: string): Promise<number>;
20
+ interface WatchFlags {
21
+ intervalMs: number;
22
+ intervalLabel: string;
23
+ maxTicks?: number;
24
+ }
25
+ /**
26
+ * Parse `ghostrail watch` options: `--interval`/`-i <duration>` (default `60s`)
27
+ * and an optional `--max-ticks <n>`. Returns undefined for a malformed interval.
28
+ */
29
+ export declare function parseWatchFlags(argv: readonly string[]): WatchFlags | undefined;
30
+ /** `ghostrail watch`: run ticks on an interval until interrupted (no cron needed). */
31
+ export declare function cmdWatch(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, cwd?: string): Promise<number>;
32
+ /** `ghostrail triage`: decompose large issues in the triage queue into sub-issues. */
33
+ export declare function cmdTriage(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, cwd?: string): Promise<number>;
20
34
  /** `ghostrail respond`: fold new human PR comments back into their branches. */
21
35
  export declare function cmdRespond(argv: readonly string[], streams: CommandStreams, env?: CommandEnv, cwd?: string): Promise<number>;
22
36
  interface BoardFlags {
@@ -32,8 +46,19 @@ export declare function cmdBoard(argv: readonly string[], streams: CommandStream
32
46
  /** `ghostrail artifacts`: list the shared artifacts available in the global store. */
33
47
  export declare function cmdArtifacts(argv: readonly string[], streams: CommandStreams, env?: CommandEnv): Promise<number>;
34
48
  /** The quickstart templates bundled with the package. */
35
- export declare const TEMPLATES: readonly ["fix-bugs-local", "content-loop"];
36
- /** `ghostrail init <template>`: scaffold a repo from a quickstart template. */
49
+ export declare const TEMPLATES: readonly ["code-local", "content-loop"];
50
+ /**
51
+ * `ghostrail init <template> [--repo <path>] [--force]`: scaffold `ghostrail.toml`
52
+ * and `prompts/` from a quickstart template. Safe for a fresh OR an existing repo:
53
+ * existing files are left untouched (skipped) unless `--force` is given.
54
+ */
37
55
  export declare function cmdInit(argv: readonly string[], streams: CommandStreams, cwd?: string): Promise<number>;
56
+ /**
57
+ * `ghostrail claude-setup`: inspect the user's ~/.claude and propose the factory
58
+ * Claude profile (which CLAUDE.md sections to keep vs drop, which settings are
59
+ * interactive-only, which skills to pin). Prints the proposal; `--write` composes
60
+ * the profile.
61
+ */
62
+ export declare function cmdClaudeSetup(argv: readonly string[], streams: CommandStreams, env?: CommandEnv): Promise<number>;
38
63
  export {};
39
64
  //# sourceMappingURL=commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAiBA,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;AAkDD,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,CAyBjB;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,CAwBjB;AAED,UAAU,UAAU;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,oEAAoE;AACpE,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,QAAQ,EAAE,UAAU,GAAG,UAAU,CAqBzF;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,CA6BjB;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,yDAAyD;AACzD,eAAO,MAAM,SAAS,6CAA8C,CAAC;AAMrE,+EAA+E;AAC/E,wBAAsB,OAAO,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,EAAE,cAAc,EACvB,GAAG,SAAgB,GAClB,OAAO,CAAC,MAAM,CAAC,CAiCjB"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,21 @@
1
- import { cp, readFile } from "node:fs/promises";
1
+ import { access, copyFile, mkdir, readFile, readdir, stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
- import { isAbsolute, join } from "node:path";
3
+ import { dirname, isAbsolute, join, sep } from "node:path";
4
4
  import process from "node:process";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { createBoardServer } from "../board/server.js";
7
+ import { buildProposal, writeProfile } from "../claude-profile/index.js";
8
+ import { parseDuration } from "../config/duration.js";
7
9
  import { parseMergedConfig } from "../config/load.js";
8
- import { STORE_DIR, buildLoopDeps, buildRespondDeps } from "../factory/build.js";
10
+ import { STORE_DIR, buildLoopDeps, buildRespondDeps, buildTriageDeps, resolveRepoSlug, } from "../factory/build.js";
9
11
  import { GlobalStore, resolveGlobalDir } from "../global/store.js";
10
12
  import { FileLock } from "../lock/file-lock.js";
11
13
  import { runTick } from "../loop/engine.js";
14
+ import { watchLoop } from "../loop/watch.js";
12
15
  import { respondTick } from "../respond/respond.js";
13
- import { redactSecrets } from "../secrets/redact.js";
16
+ import { applyOverrides, loadEnvOverrides, redactSecrets, secretValuesOf, } from "../secrets/index.js";
14
17
  import { FileStore } from "../store/file-store.js";
18
+ import { triageTick } from "../triage/engine.js";
15
19
  /**
16
20
  * Parse the flags shared by `run` and `respond`: `--config`/`-c <path>` and
17
21
  * `--repo`/`-C <path>`, starting from the given defaults. Unknown tokens are
@@ -64,49 +68,183 @@ async function loadConfig(flags, streams, env) {
64
68
  }
65
69
  return { config: parsed.value, globalStore };
66
70
  }
71
+ /**
72
+ * Load the global + repo secret files and apply them over `env` in place, with
73
+ * precedence repo > global > env, then return the secret values for redaction.
74
+ */
75
+ async function applySecrets(flags, env) {
76
+ const globalDir = resolveGlobalDir({ GHOSTRAIL_HOME: env.GHOSTRAIL_HOME, XDG_CONFIG_HOME: env.XDG_CONFIG_HOME }, homedir());
77
+ const { overrides } = await loadEnvOverrides(globalDir, flags.repo);
78
+ applyOverrides(env, overrides);
79
+ return secretValuesOf(env);
80
+ }
81
+ /** The composed ghostrail Claude profile dir, if one exists (has a settings.json). */
82
+ async function resolveClaudeProfile(env) {
83
+ const dir = join(resolveGlobalDir({ GHOSTRAIL_HOME: env.GHOSTRAIL_HOME, XDG_CONFIG_HOME: env.XDG_CONFIG_HOME }, homedir()), "claude");
84
+ try {
85
+ await access(join(dir, ".claude", "settings.json"));
86
+ return dir;
87
+ }
88
+ catch {
89
+ return undefined;
90
+ }
91
+ }
92
+ /**
93
+ * Shared setup for the run/watch loops: apply secrets, load the merged config,
94
+ * require the Linear key, and assemble the loop deps. Prints errors and returns
95
+ * undefined on failure.
96
+ */
97
+ async function prepareLoop(flags, streams, env) {
98
+ const secrets = await applySecrets(flags, env);
99
+ const loaded = await loadConfig(flags, streams, env);
100
+ if (loaded === undefined)
101
+ return undefined;
102
+ const { config, globalStore } = loaded;
103
+ const linearApiKey = env.LINEAR_API_KEY;
104
+ if (linearApiKey === undefined || linearApiKey.length === 0) {
105
+ streams.err("LINEAR_API_KEY is not set\n");
106
+ return undefined;
107
+ }
108
+ const claudeProfileDir = await resolveClaudeProfile(env);
109
+ const repoSlug = await resolveRepoSlug(config, flags.repo);
110
+ const lock = new FileLock(join(flags.repo, ".factory", "lock"), config.guardrails.claimTtlMs);
111
+ const deps = buildLoopDeps(config, {
112
+ repoPath: flags.repo,
113
+ linearApiKey,
114
+ lock,
115
+ globalStore,
116
+ ...(claudeProfileDir === undefined ? {} : { claudeProfileDir }),
117
+ ...(repoSlug === undefined ? {} : { repoSlug }),
118
+ logger: makeLogger(streams, secrets),
119
+ });
120
+ return { config, deps, secrets };
121
+ }
67
122
  /** `ghostrail run`: drain eligible work once. */
68
123
  export async function cmdRun(argv, streams, env = process.env, cwd = process.cwd()) {
69
124
  const flags = parseCommandFlags(argv, { config: "ghostrail.toml", repo: cwd });
125
+ const setup = await prepareLoop(flags, streams, env);
126
+ if (setup === undefined)
127
+ return 1;
128
+ const result = await runTick(setup.deps, setup.config);
129
+ streams.out(`${redactSecrets(JSON.stringify(result, null, 2), setup.secrets)}\n`);
130
+ return result.results.some((r) => r.state === "failed") ? 1 : 0;
131
+ }
132
+ /**
133
+ * Parse `ghostrail watch` options: `--interval`/`-i <duration>` (default `60s`)
134
+ * and an optional `--max-ticks <n>`. Returns undefined for a malformed interval.
135
+ */
136
+ export function parseWatchFlags(argv) {
137
+ let intervalLabel = "60s";
138
+ let maxTicks;
139
+ for (let i = 0; i < argv.length; i++) {
140
+ const arg = argv[i];
141
+ const next = argv[i + 1];
142
+ if ((arg === "--interval" || arg === "-i") && next !== undefined) {
143
+ intervalLabel = next;
144
+ i++;
145
+ }
146
+ else if (arg === "--max-ticks" && next !== undefined) {
147
+ const n = Number(next);
148
+ if (Number.isSafeInteger(n) && n > 0)
149
+ maxTicks = n;
150
+ i++;
151
+ }
152
+ }
153
+ const intervalMs = parseDuration(intervalLabel);
154
+ if (intervalMs === null)
155
+ return undefined;
156
+ return maxTicks === undefined
157
+ ? { intervalMs, intervalLabel }
158
+ : { intervalMs, intervalLabel, maxTicks };
159
+ }
160
+ /** `ghostrail watch`: run ticks on an interval until interrupted (no cron needed). */
161
+ export async function cmdWatch(argv, streams, env = process.env, cwd = process.cwd()) {
162
+ const flags = parseCommandFlags(argv, { config: "ghostrail.toml", repo: cwd });
163
+ const watch = parseWatchFlags(argv);
164
+ if (watch === undefined) {
165
+ streams.err('invalid --interval (use e.g. "30s", "5m", "1h")\n');
166
+ return 1;
167
+ }
168
+ const setup = await prepareLoop(flags, streams, env);
169
+ if (setup === undefined)
170
+ return 1;
171
+ const controller = new AbortController();
172
+ const onSignal = () => controller.abort();
173
+ process.once("SIGINT", onSignal);
174
+ process.once("SIGTERM", onSignal);
175
+ streams.err(`watching every ${watch.intervalLabel}; press Ctrl-C to stop\n`);
176
+ try {
177
+ const ticks = await watchLoop({
178
+ intervalMs: watch.intervalMs,
179
+ signal: controller.signal,
180
+ logger: makeLogger(streams, setup.secrets),
181
+ ...(watch.maxTicks === undefined ? {} : { maxTicks: watch.maxTicks }),
182
+ tick: async () => {
183
+ const result = await runTick(setup.deps, setup.config);
184
+ streams.out(`${redactSecrets(JSON.stringify(result, null, 2), setup.secrets)}\n`);
185
+ },
186
+ });
187
+ streams.err(`stopped after ${ticks} tick(s)\n`);
188
+ return 0;
189
+ }
190
+ finally {
191
+ process.removeListener("SIGINT", onSignal);
192
+ process.removeListener("SIGTERM", onSignal);
193
+ }
194
+ }
195
+ /** `ghostrail triage`: decompose large issues in the triage queue into sub-issues. */
196
+ export async function cmdTriage(argv, streams, env = process.env, cwd = process.cwd()) {
197
+ const flags = parseCommandFlags(argv, { config: "ghostrail.toml", repo: cwd });
198
+ const secrets = await applySecrets(flags, env);
70
199
  const loaded = await loadConfig(flags, streams, env);
71
200
  if (loaded === undefined)
72
201
  return 1;
73
202
  const { config, globalStore } = loaded;
203
+ if (config.triage === undefined) {
204
+ streams.err("no [triage] section in the config; add one to use `ghostrail triage`\n");
205
+ return 1;
206
+ }
74
207
  const linearApiKey = env.LINEAR_API_KEY;
75
208
  if (linearApiKey === undefined || linearApiKey.length === 0) {
76
209
  streams.err("LINEAR_API_KEY is not set\n");
77
210
  return 1;
78
211
  }
79
- const secrets = [linearApiKey];
212
+ const claudeProfileDir = await resolveClaudeProfile(env);
80
213
  const lock = new FileLock(join(flags.repo, ".factory", "lock"), config.guardrails.claimTtlMs);
81
- const deps = buildLoopDeps(config, {
214
+ const deps = buildTriageDeps(config, {
82
215
  repoPath: flags.repo,
83
216
  linearApiKey,
84
217
  lock,
85
218
  globalStore,
219
+ ...(claudeProfileDir === undefined ? {} : { claudeProfileDir }),
86
220
  logger: makeLogger(streams, secrets),
87
221
  });
88
- const result = await runTick(deps, config);
222
+ const result = await triageTick(deps, config);
89
223
  streams.out(`${redactSecrets(JSON.stringify(result, null, 2), secrets)}\n`);
90
224
  return result.results.some((r) => r.state === "failed") ? 1 : 0;
91
225
  }
92
226
  /** `ghostrail respond`: fold new human PR comments back into their branches. */
93
227
  export async function cmdRespond(argv, streams, env = process.env, cwd = process.cwd()) {
94
228
  const flags = parseCommandFlags(argv, { config: "ghostrail.toml", repo: cwd });
229
+ const secrets = await applySecrets(flags, env);
95
230
  const loaded = await loadConfig(flags, streams, env);
96
231
  if (loaded === undefined)
97
232
  return 1;
98
233
  const { config } = loaded;
99
234
  const linearApiKey = env.LINEAR_API_KEY ?? "";
100
- const secrets = linearApiKey.length > 0 ? [linearApiKey] : [];
101
235
  const selfLogins = (env.GHOSTRAIL_SELF_LOGINS ?? "")
102
236
  .split(",")
103
237
  .map((login) => login.trim())
104
238
  .filter((login) => login.length > 0);
239
+ const claudeProfileDir = await resolveClaudeProfile(env);
240
+ const repoSlug = await resolveRepoSlug(config, flags.repo);
105
241
  const { deps, config: respondConfig } = buildRespondDeps(config, {
106
242
  repoPath: flags.repo,
107
243
  linearApiKey,
108
244
  lock: { acquire: async () => true, release: async () => { } },
109
245
  selfLogins,
246
+ ...(claudeProfileDir === undefined ? {} : { claudeProfileDir }),
247
+ ...(repoSlug === undefined ? {} : { repoSlug }),
110
248
  logger: makeLogger(streams, secrets),
111
249
  });
112
250
  const results = await respondTick(deps, respondConfig);
@@ -184,14 +322,47 @@ export async function cmdArtifacts(argv, streams, env = process.env) {
184
322
  return 0;
185
323
  }
186
324
  /** The quickstart templates bundled with the package. */
187
- export const TEMPLATES = ["fix-bugs-local", "content-loop"];
325
+ export const TEMPLATES = ["code-local", "content-loop"];
188
326
  function templatesDir() {
189
327
  return fileURLToPath(new URL("../../templates", import.meta.url));
190
328
  }
191
- /** `ghostrail init <template>`: scaffold a repo from a quickstart template. */
329
+ /** True if a path exists. */
330
+ async function pathExists(path) {
331
+ try {
332
+ await access(path);
333
+ return true;
334
+ }
335
+ catch {
336
+ return false;
337
+ }
338
+ }
339
+ /**
340
+ * The files a template scaffolds into a repo: `ghostrail.toml` and everything
341
+ * under `prompts/`. The template's own README is intentionally excluded, so
342
+ * scaffolding into an existing repo never drops template docs on it.
343
+ */
344
+ async function scaffoldFiles(srcDir) {
345
+ const entries = await readdir(srcDir, { recursive: true });
346
+ const wanted = [];
347
+ for (const rel of entries) {
348
+ const normalized = rel.split(sep).join("/");
349
+ if (normalized !== "ghostrail.toml" && !normalized.startsWith("prompts/"))
350
+ continue;
351
+ if ((await stat(join(srcDir, rel))).isDirectory())
352
+ continue;
353
+ wanted.push(rel);
354
+ }
355
+ return wanted;
356
+ }
357
+ /**
358
+ * `ghostrail init <template> [--repo <path>] [--force]`: scaffold `ghostrail.toml`
359
+ * and `prompts/` from a quickstart template. Safe for a fresh OR an existing repo:
360
+ * existing files are left untouched (skipped) unless `--force` is given.
361
+ */
192
362
  export async function cmdInit(argv, streams, cwd = process.cwd()) {
193
363
  let template;
194
364
  let repo = cwd;
365
+ let force = false;
195
366
  for (let i = 0; i < argv.length; i++) {
196
367
  const arg = argv[i];
197
368
  const next = argv[i + 1];
@@ -199,26 +370,113 @@ export async function cmdInit(argv, streams, cwd = process.cwd()) {
199
370
  repo = next;
200
371
  i++;
201
372
  }
373
+ else if (arg === "--force" || arg === "-f") {
374
+ force = true;
375
+ }
202
376
  else if (arg !== undefined && !arg.startsWith("-")) {
203
377
  template ??= arg;
204
378
  }
205
379
  }
206
380
  if (template === undefined || !TEMPLATES.includes(template)) {
207
- streams.err(`usage: ghostrail init <template> [--repo <path>]\ntemplates: ${TEMPLATES.join(", ")}\n`);
381
+ streams.err(`usage: ghostrail init <template> [--repo <path>] [--force]\ntemplates: ${TEMPLATES.join(", ")}\n`);
208
382
  return 1;
209
383
  }
384
+ const srcDir = join(templatesDir(), template);
385
+ const written = [];
386
+ const skipped = [];
210
387
  try {
211
- await cp(join(templatesDir(), template), repo, {
212
- recursive: true,
213
- force: false,
214
- errorOnExist: false,
215
- });
388
+ for (const rel of await scaffoldFiles(srcDir)) {
389
+ const to = join(repo, rel);
390
+ if (!force && (await pathExists(to))) {
391
+ skipped.push(rel);
392
+ continue;
393
+ }
394
+ await mkdir(dirname(to), { recursive: true });
395
+ await copyFile(join(srcDir, rel), to);
396
+ written.push(rel);
397
+ }
216
398
  }
217
399
  catch (error) {
218
400
  streams.err(`init failed: ${error instanceof Error ? error.message : String(error)}\n`);
219
401
  return 1;
220
402
  }
221
- streams.out(`initialized ${template} in ${repo}\n`);
403
+ streams.out(`initialized ${template} in ${repo}: wrote ${written.length} file(s)`);
404
+ if (skipped.length > 0) {
405
+ streams.out(`, kept ${skipped.length} existing (${skipped.join(", ")}; use --force to overwrite)`);
406
+ }
407
+ streams.out("\n");
408
+ return 0;
409
+ }
410
+ function parseClaudeSetupFlags(argv, defaults) {
411
+ const flags = { ...defaults, pins: [...defaults.pins] };
412
+ for (let i = 0; i < argv.length; i++) {
413
+ const arg = argv[i];
414
+ const next = argv[i + 1];
415
+ if (arg === "--write")
416
+ flags.write = true;
417
+ else if (arg === "--home" && next !== undefined) {
418
+ flags.home = next;
419
+ i++;
420
+ }
421
+ else if (arg === "--profile" && next !== undefined) {
422
+ flags.profile = next;
423
+ i++;
424
+ }
425
+ else if (arg === "--pin" && next !== undefined) {
426
+ flags.pins.push(next);
427
+ i++;
428
+ }
429
+ }
430
+ return flags;
431
+ }
432
+ /**
433
+ * `ghostrail claude-setup`: inspect the user's ~/.claude and propose the factory
434
+ * Claude profile (which CLAUDE.md sections to keep vs drop, which settings are
435
+ * interactive-only, which skills to pin). Prints the proposal; `--write` composes
436
+ * the profile.
437
+ */
438
+ export async function cmdClaudeSetup(argv, streams, env = process.env) {
439
+ const globalDir = resolveGlobalDir({ GHOSTRAIL_HOME: env.GHOSTRAIL_HOME, XDG_CONFIG_HOME: env.XDG_CONFIG_HOME }, homedir());
440
+ const flags = parseClaudeSetupFlags(argv, {
441
+ home: env.HOME ?? homedir(),
442
+ profile: join(globalDir, "claude"),
443
+ write: false,
444
+ pins: ["testbotpro"],
445
+ });
446
+ const proposal = await buildProposal(flags.home, flags.profile, flags.pins);
447
+ streams.out("Claude profile setup\n");
448
+ streams.out(` source: ${join(flags.home, ".claude")}\n`);
449
+ streams.out(` profile: ${join(flags.profile, ".claude")}\n\n`);
450
+ if (!proposal.claudeMd.present) {
451
+ streams.out("CLAUDE.md: none found in source\n");
452
+ }
453
+ else {
454
+ const keep = proposal.claudeMd.sections.filter((s) => s.verdict === "keep");
455
+ const drop = proposal.claudeMd.sections.filter((s) => s.verdict === "drop");
456
+ streams.out(`CLAUDE.md: ${proposal.claudeMd.sections.length} sections (${keep.length} keep, ${drop.length} drop)\n`);
457
+ for (const section of drop)
458
+ streams.out(` drop ${section.heading} (${section.reason})\n`);
459
+ }
460
+ if (proposal.settings.present) {
461
+ const dropped = proposal.settings.keys.filter((k) => !k.carried);
462
+ streams.out(`settings.json: ${proposal.settings.keys.length} keys, ${dropped.length} interactive-only not carried\n`);
463
+ for (const key of dropped)
464
+ streams.out(` skip ${key.key}\n`);
465
+ }
466
+ streams.out("skills:\n");
467
+ for (const skill of proposal.skills) {
468
+ streams.out(` ${skill.pin ? "pin " : "skip"} ${skill.name}\n`);
469
+ }
470
+ for (const missing of proposal.missingPins) {
471
+ streams.out(` MISSING (requested via --pin, not installed): ${missing}\n`);
472
+ }
473
+ if (flags.write) {
474
+ await writeProfile(proposal);
475
+ streams.out(`\nwrote profile to ${join(flags.profile, ".claude")}\n`);
476
+ }
477
+ else {
478
+ streams.out("\ndry run: re-run with --write to compose the profile\n");
479
+ }
222
480
  return 0;
223
481
  }
224
482
  //# sourceMappingURL=commands.js.map