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.
- package/README.md +29 -2
- package/dist/agent/claude-code.d.ts +10 -0
- package/dist/agent/claude-code.d.ts.map +1 -1
- package/dist/agent/claude-code.js +20 -0
- package/dist/agent/claude-code.js.map +1 -1
- package/dist/agent/result.d.ts.map +1 -1
- package/dist/agent/result.js +13 -2
- package/dist/agent/result.js.map +1 -1
- package/dist/backend/container.d.ts +18 -0
- package/dist/backend/container.d.ts.map +1 -1
- package/dist/backend/container.js +33 -10
- package/dist/backend/container.js.map +1 -1
- package/dist/backend/git-credentials.d.ts +18 -0
- package/dist/backend/git-credentials.d.ts.map +1 -0
- package/dist/backend/git-credentials.js +30 -0
- package/dist/backend/git-credentials.js.map +1 -0
- package/dist/backend/local-worktree.d.ts.map +1 -1
- package/dist/backend/local-worktree.js +21 -0
- package/dist/backend/local-worktree.js.map +1 -1
- package/dist/backend/types.d.ts +12 -1
- package/dist/backend/types.d.ts.map +1 -1
- package/dist/claude-profile/classify.d.ts +41 -0
- package/dist/claude-profile/classify.d.ts.map +1 -0
- package/dist/claude-profile/classify.js +103 -0
- package/dist/claude-profile/classify.js.map +1 -0
- package/dist/claude-profile/index.d.ts +3 -0
- package/dist/claude-profile/index.d.ts.map +1 -0
- package/dist/claude-profile/index.js +3 -0
- package/dist/claude-profile/index.js.map +1 -0
- package/dist/claude-profile/setup.d.ts +33 -0
- package/dist/claude-profile/setup.d.ts.map +1 -0
- package/dist/claude-profile/setup.js +81 -0
- package/dist/claude-profile/setup.js.map +1 -0
- package/dist/cli/commands.d.ts +27 -2
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +275 -17
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/help.d.ts.map +1 -1
- package/dist/commands/help.js +9 -2
- package/dist/commands/help.js.map +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +61 -9
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +51 -3
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +4 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/factory/build.d.ts +24 -0
- package/dist/factory/build.d.ts.map +1 -1
- package/dist/factory/build.js +80 -11
- package/dist/factory/build.js.map +1 -1
- package/dist/loop/concurrency.d.ts +13 -0
- package/dist/loop/concurrency.d.ts.map +1 -0
- package/dist/loop/concurrency.js +27 -0
- package/dist/loop/concurrency.js.map +1 -0
- package/dist/loop/engine.d.ts.map +1 -1
- package/dist/loop/engine.js +19 -14
- package/dist/loop/engine.js.map +1 -1
- package/dist/loop/index.d.ts +2 -0
- package/dist/loop/index.d.ts.map +1 -1
- package/dist/loop/index.js +2 -0
- package/dist/loop/index.js.map +1 -1
- package/dist/loop/ports.d.ts +37 -1
- package/dist/loop/ports.d.ts.map +1 -1
- package/dist/loop/watch.d.ts +25 -0
- package/dist/loop/watch.d.ts.map +1 -0
- package/dist/loop/watch.js +45 -0
- package/dist/loop/watch.js.map +1 -0
- package/dist/publish/gh.d.ts +20 -1
- package/dist/publish/gh.d.ts.map +1 -1
- package/dist/publish/gh.js +37 -3
- package/dist/publish/gh.js.map +1 -1
- package/dist/publish/githost.d.ts +12 -0
- package/dist/publish/githost.d.ts.map +1 -1
- package/dist/publish/githost.js +14 -0
- package/dist/publish/githost.js.map +1 -1
- package/dist/publish/github-publisher.d.ts +1 -1
- package/dist/publish/github-publisher.d.ts.map +1 -1
- package/dist/publish/github-publisher.js +6 -5
- package/dist/publish/github-publisher.js.map +1 -1
- package/dist/respond/respond.d.ts +10 -1
- package/dist/respond/respond.d.ts.map +1 -1
- package/dist/respond/respond.js +37 -3
- package/dist/respond/respond.js.map +1 -1
- package/dist/secrets/env-file.d.ts +23 -0
- package/dist/secrets/env-file.d.ts.map +1 -0
- package/dist/secrets/env-file.js +70 -0
- package/dist/secrets/env-file.js.map +1 -0
- package/dist/secrets/index.d.ts +4 -0
- package/dist/secrets/index.d.ts.map +1 -0
- package/dist/secrets/index.js +4 -0
- package/dist/secrets/index.js.map +1 -0
- package/dist/secrets/load.d.ts +24 -0
- package/dist/secrets/load.d.ts.map +1 -0
- package/dist/secrets/load.js +43 -0
- package/dist/secrets/load.js.map +1 -0
- package/dist/source/linear.d.ts +51 -8
- package/dist/source/linear.d.ts.map +1 -1
- package/dist/source/linear.js +213 -23
- package/dist/source/linear.js.map +1 -1
- package/dist/triage/apply.d.ts +28 -0
- package/dist/triage/apply.d.ts.map +1 -0
- package/dist/triage/apply.js +68 -0
- package/dist/triage/apply.js.map +1 -0
- package/dist/triage/engine.d.ts +31 -0
- package/dist/triage/engine.d.ts.map +1 -0
- package/dist/triage/engine.js +122 -0
- package/dist/triage/engine.js.map +1 -0
- package/dist/triage/index.d.ts +4 -0
- package/dist/triage/index.d.ts.map +1 -0
- package/dist/triage/index.js +4 -0
- package/dist/triage/index.js.map +1 -0
- package/dist/triage/plan.d.ts +37 -0
- package/dist/triage/plan.d.ts.map +1 -0
- package/dist/triage/plan.js +75 -0
- package/dist/triage/plan.js.map +1 -0
- package/docker/factory.Dockerfile +39 -0
- package/package.json +4 -3
- package/templates/{fix-bugs-local → code-local}/README.md +3 -3
- package/templates/code-local/ghostrail.toml +44 -0
- package/templates/code-local/prompts/resolve-issue.md +31 -0
- package/templates/code-local/prompts/respond.md +27 -0
- package/templates/code-local/prompts/triage.md +51 -0
- package/templates/content-loop/ghostrail.toml +5 -2
- package/templates/content-loop/prompts/respond.md +25 -0
- package/templates/fix-bugs-local/ghostrail.toml +0 -37
- 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 @@
|
|
|
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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"}
|
package/dist/cli/commands.d.ts
CHANGED
|
@@ -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 ["
|
|
36
|
-
/**
|
|
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":"
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/commands.js
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
import {
|
|
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/
|
|
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
|
|
212
|
+
const claudeProfileDir = await resolveClaudeProfile(env);
|
|
80
213
|
const lock = new FileLock(join(flags.repo, ".factory", "lock"), config.guardrails.claimTtlMs);
|
|
81
|
-
const deps =
|
|
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
|
|
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 = ["
|
|
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
|
-
/**
|
|
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
|
|
212
|
-
|
|
213
|
-
force
|
|
214
|
-
|
|
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}
|
|
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
|