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,122 @@
1
+ import { stageArtifacts } from "../global/stage.js";
2
+ import { branchName } from "../loop/branch.js";
3
+ import { mapWithConcurrency } from "../loop/concurrency.js";
4
+ import { applyTriagePlan } from "./apply.js";
5
+ import { parseTriagePlan } from "./plan.js";
6
+ /** Path, relative to the workspace root, where the triage agent writes its plan. */
7
+ export const TRIAGE_PLAN_PATH = ".ghostrail/triage.json";
8
+ const BRANCH_PREFIX = "triage";
9
+ const nullLogger = { info: () => { }, warn: () => { } };
10
+ async function readWorkspaceFile(backend, workspace, path) {
11
+ const result = await backend.exec(workspace, ["cat", path]);
12
+ return result.exitCode === 0 ? result.stdout : undefined;
13
+ }
14
+ async function ignoreErrors(action) {
15
+ try {
16
+ await action();
17
+ }
18
+ catch {
19
+ // best-effort
20
+ }
21
+ }
22
+ /**
23
+ * Run one triage tick: discover issues in the triage queue and, for each (up to
24
+ * the guardrails), claim it, provision a workspace for repo context, run the
25
+ * triage agent (which writes a decomposition plan), and apply that plan through
26
+ * the source. Like the run loop it holds a single global lock and never touches
27
+ * code or PRs. Requires `config.triage` to be set.
28
+ */
29
+ export async function triageTick(deps, config) {
30
+ const logger = deps.logger ?? nullLogger;
31
+ if (config.triage === undefined) {
32
+ throw new Error("triage requires a [triage] config section");
33
+ }
34
+ const triage = config.triage;
35
+ const acquired = await deps.lock.acquire();
36
+ if (!acquired) {
37
+ logger.warn("another tick holds the lock; skipping");
38
+ return { lockAcquired: false, results: [] };
39
+ }
40
+ try {
41
+ const eligible = await deps.source.listEligible();
42
+ const items = eligible.slice(0, config.guardrails.maxItemsPerRun);
43
+ logger.info(`triage: discovered ${eligible.length} item(s), taking ${items.length}`);
44
+ const results = await mapWithConcurrency(items, config.guardrails.concurrency, (item) => triageItem(deps, config, triage.prompt, item, logger));
45
+ return { lockAcquired: true, results };
46
+ }
47
+ finally {
48
+ await deps.lock.release();
49
+ }
50
+ }
51
+ async function triageItem(deps, config, prompt, item, logger) {
52
+ let workspace;
53
+ try {
54
+ const claimed = await deps.source.claim(item);
55
+ if (!claimed) {
56
+ logger.info(`${item.id}: claim lost, skipping`);
57
+ return { itemId: item.id, state: "noop" };
58
+ }
59
+ const source = config.factory.source;
60
+ workspace = await deps.backend.provision({
61
+ repoPath: source ?? deps.repoPath,
62
+ baseBranch: config.factory.baseBranch,
63
+ branch: branchName(BRANCH_PREFIX, item.id, item.title),
64
+ ...(source === undefined ? {} : { clone: true }),
65
+ ...(config.backend.image === undefined ? {} : { image: config.backend.image }),
66
+ });
67
+ if (deps.globalStore !== undefined && config.artifacts.length > 0) {
68
+ await stageArtifacts(deps.backend, workspace, deps.globalStore, config.artifacts, logger);
69
+ }
70
+ const agentConfig = {
71
+ kind: config.agent.kind,
72
+ prompt,
73
+ allowedTools: config.agent.allowedTools,
74
+ };
75
+ const outcome = await deps.agent.run({
76
+ backend: deps.backend,
77
+ workspace,
78
+ item,
79
+ agent: agentConfig,
80
+ guardrails: config.guardrails,
81
+ });
82
+ return await route(deps, item, workspace, outcome, logger);
83
+ }
84
+ catch (error) {
85
+ const message = error instanceof Error ? error.message : String(error);
86
+ logger.warn(`${item.id}: triage errored, resetting: ${message}`);
87
+ await ignoreErrors(() => deps.source.reset(item));
88
+ return { itemId: item.id, state: "failed", error: message };
89
+ }
90
+ finally {
91
+ if (workspace !== undefined) {
92
+ await ignoreErrors(() => deps.backend.teardown(workspace));
93
+ }
94
+ }
95
+ }
96
+ async function route(deps, item, workspace, outcome, logger) {
97
+ if (outcome.status === "blocked") {
98
+ logger.info(`${item.id}: triage blocked, handing back to a human`);
99
+ await deps.source.comment(item, outcome.questions ?? "Triage needs a decision.");
100
+ await deps.source.reassignToHuman(item);
101
+ return { itemId: item.id, state: "noop" };
102
+ }
103
+ if (outcome.status === "failed") {
104
+ await deps.source.comment(item, `Triage failed: ${outcome.error ?? "unknown error"}`);
105
+ await deps.source.reset(item);
106
+ return { itemId: item.id, state: "failed", error: outcome.error ?? "unknown error" };
107
+ }
108
+ const raw = await readWorkspaceFile(deps.backend, workspace, TRIAGE_PLAN_PATH);
109
+ if (raw === undefined || raw.trim().length === 0) {
110
+ await deps.source.comment(item, "Triage produced no plan.");
111
+ await deps.source.reset(item);
112
+ return { itemId: item.id, state: "failed", error: "no triage plan written" };
113
+ }
114
+ const plan = parseTriagePlan(raw);
115
+ if (plan.status === "invalid") {
116
+ await deps.source.comment(item, `Triage plan was invalid: ${plan.error}`);
117
+ await deps.source.reset(item);
118
+ return { itemId: item.id, state: "failed", error: plan.error };
119
+ }
120
+ return applyTriagePlan(deps.source, item, plan, logger);
121
+ }
122
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/triage/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { type TriageResult, applyTriagePlan } from "./apply.js";
2
+ export { type TriageDeps, TRIAGE_PLAN_PATH, type TriageTickResult, triageTick } from "./engine.js";
3
+ export { type ParsedPlan, parseTriagePlan, type SubIssuePlan, type TriagePlan } from "./plan.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/triage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,KAAK,UAAU,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAAE,KAAK,UAAU,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC"}
@@ -0,0 +1,4 @@
1
+ export { applyTriagePlan } from "./apply.js";
2
+ export { TRIAGE_PLAN_PATH, triageTick } from "./engine.js";
3
+ export { parseTriagePlan } from "./plan.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/triage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAmB,gBAAgB,EAAyB,UAAU,EAAE,MAAM,aAAa,CAAC;AACnG,OAAO,EAAmB,eAAe,EAAsC,MAAM,WAAW,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The triage agent's output contract. It writes a JSON plan to a file in the
3
+ * workspace; ghostrail parses and validates it here, then applies it through the
4
+ * {@link TriageSource}. The agent never mutates the tracker directly.
5
+ */
6
+ /** One proposed sub-issue in a split plan. */
7
+ export interface SubIssuePlan {
8
+ title: string;
9
+ description?: string;
10
+ /**
11
+ * Indices (into the same `subIssues` array) of earlier sub-issues that must be
12
+ * done first. Each index must be a non-negative integer strictly less than
13
+ * this sub-issue's own index (dependencies point backward, so no cycles).
14
+ */
15
+ dependsOn?: number[];
16
+ }
17
+ /** A validated triage plan: either split the parent, or leave it as atomic. */
18
+ export type TriagePlan = {
19
+ status: "split";
20
+ reason?: string;
21
+ subIssues: SubIssuePlan[];
22
+ } | {
23
+ status: "noop";
24
+ reason?: string;
25
+ };
26
+ /** The result of parsing a plan: the plan, or a validation error. */
27
+ export type ParsedPlan = TriagePlan | {
28
+ status: "invalid";
29
+ error: string;
30
+ };
31
+ /**
32
+ * Parse and validate the raw plan JSON the agent wrote. A `split` plan needs a
33
+ * non-empty `subIssues` array of `{ title, description?, dependsOn? }`; a `noop`
34
+ * plan needs nothing else. Anything malformed yields `{ status: "invalid" }`.
35
+ */
36
+ export declare function parseTriagePlan(raw: string): ParsedPlan;
37
+ //# sourceMappingURL=plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/triage/plan.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8CAA8C;AAC9C,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,+EAA+E;AAC/E,MAAM,MAAM,UAAU,GAClB;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,YAAY,EAAE,CAAA;CAAE,GAC/D;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC,qEAAqE;AACrE,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAkC3E;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA8BvD"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The triage agent's output contract. It writes a JSON plan to a file in the
3
+ * workspace; ghostrail parses and validates it here, then applies it through the
4
+ * {@link TriageSource}. The agent never mutates the tracker directly.
5
+ */
6
+ function invalid(error) {
7
+ return { status: "invalid", error };
8
+ }
9
+ function isRecord(value) {
10
+ return value !== null && typeof value === "object" && !Array.isArray(value);
11
+ }
12
+ /** Validate one sub-issue at position `index`; return its normalized form or an error message. */
13
+ function parseSubIssue(raw, index) {
14
+ if (!isRecord(raw))
15
+ return `subIssues[${index}] must be an object`;
16
+ const { title, description, dependsOn } = raw;
17
+ if (typeof title !== "string" || title.trim().length === 0) {
18
+ return `subIssues[${index}].title must be a non-empty string`;
19
+ }
20
+ const out = { title };
21
+ if (description !== undefined) {
22
+ if (typeof description !== "string")
23
+ return `subIssues[${index}].description must be a string`;
24
+ out.description = description;
25
+ }
26
+ if (dependsOn !== undefined) {
27
+ if (!Array.isArray(dependsOn))
28
+ return `subIssues[${index}].dependsOn must be an array`;
29
+ for (const dep of dependsOn) {
30
+ if (!Number.isInteger(dep) || dep < 0 || dep >= index) {
31
+ return `subIssues[${index}].dependsOn must be earlier indices (0..${index - 1})`;
32
+ }
33
+ }
34
+ out.dependsOn = dependsOn;
35
+ }
36
+ return out;
37
+ }
38
+ /**
39
+ * Parse and validate the raw plan JSON the agent wrote. A `split` plan needs a
40
+ * non-empty `subIssues` array of `{ title, description?, dependsOn? }`; a `noop`
41
+ * plan needs nothing else. Anything malformed yields `{ status: "invalid" }`.
42
+ */
43
+ export function parseTriagePlan(raw) {
44
+ let value;
45
+ try {
46
+ value = JSON.parse(raw);
47
+ }
48
+ catch (error) {
49
+ return invalid(`not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
50
+ }
51
+ if (!isRecord(value))
52
+ return invalid("plan must be a JSON object");
53
+ const { status, reason } = value;
54
+ if (reason !== undefined && typeof reason !== "string")
55
+ return invalid("reason must be a string");
56
+ const reasonField = reason === undefined ? {} : { reason: reason };
57
+ if (status === "noop")
58
+ return { status: "noop", ...reasonField };
59
+ if (status === "split") {
60
+ const { subIssues } = value;
61
+ if (!Array.isArray(subIssues) || subIssues.length === 0) {
62
+ return invalid('a "split" plan needs a non-empty subIssues array');
63
+ }
64
+ const parsed = [];
65
+ for (let i = 0; i < subIssues.length; i++) {
66
+ const result = parseSubIssue(subIssues[i], i);
67
+ if (typeof result === "string")
68
+ return invalid(result);
69
+ parsed.push(result);
70
+ }
71
+ return { status: "split", subIssues: parsed, ...reasonField };
72
+ }
73
+ return invalid('status must be "split" or "noop"');
74
+ }
75
+ //# sourceMappingURL=plan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/triage/plan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ # Ghostrail factory toolchain image.
2
+ #
3
+ # The agent and the gate run INSIDE the backend workspace, so a container run
4
+ # needs the full toolchain, not a bare shell:
5
+ # - node + pnpm -> the gate (CI-mirror commands)
6
+ # - git + gh -> commit, push, and open the PR
7
+ # - claude -> the agent's inner loop (`claude -p`)
8
+ #
9
+ # Build it (see scripts/build-factory-image.sh) and point the config at it:
10
+ # [backend]
11
+ # kind = "container"
12
+ # image = "ghostrail-factory:local"
13
+ #
14
+ # Credentials are never baked in; they are forwarded per run from the host
15
+ # environment (ANTHROPIC_API_KEY, GH_TOKEN/GITHUB_TOKEN, LINEAR_API_KEY).
16
+ FROM node:22-slim
17
+
18
+ # git, the GitHub CLI, and TLS roots. gh comes from its own apt repository.
19
+ RUN apt-get update \
20
+ && apt-get install -y --no-install-recommends ca-certificates curl git gnupg \
21
+ && install -m 0755 -d /etc/apt/keyrings \
22
+ && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
23
+ -o /etc/apt/keyrings/githubcli-archive-keyring.gpg \
24
+ && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
25
+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
26
+ > /etc/apt/sources.list.d/github-cli.list \
27
+ && apt-get update \
28
+ && apt-get install -y --no-install-recommends gh \
29
+ && rm -rf /var/lib/apt/lists/*
30
+
31
+ # pnpm via corepack (bundled with Node), and the Claude Code CLI for the agent.
32
+ RUN corepack enable \
33
+ && npm install -g @anthropic-ai/claude-code
34
+
35
+ # Route GitHub https auth through gh's credential helper, so an in-container
36
+ # `git push` authenticates from the forwarded GH_TOKEN (no token in any URL).
37
+ RUN git config --system credential."https://github.com".helper "!gh auth git-credential"
38
+
39
+ WORKDIR /work
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ghostrail",
3
- "version": "0.0.0",
3
+ "version": "0.1.0",
4
4
  "description": "Open source software factory for coding agents. Config-driven loops, pluggable isolation backends, human merge gate.",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -24,7 +24,7 @@
24
24
  "bin": {
25
25
  "ghostrail": "dist/bin.js"
26
26
  },
27
- "files": ["dist", "templates", "LICENSE", "NOTICE", "README.md"],
27
+ "files": ["dist", "templates", "docker", "LICENSE", "NOTICE", "README.md"],
28
28
  "engines": {
29
29
  "node": ">=22"
30
30
  },
@@ -37,7 +37,8 @@
37
37
  "format": "biome format --write .",
38
38
  "test": "vitest run",
39
39
  "test:unit": "vitest run",
40
- "test:watch": "vitest"
40
+ "test:watch": "vitest",
41
+ "prepublishOnly": "pnpm lint && pnpm typecheck && pnpm test:unit && pnpm build"
41
42
  },
42
43
  "devDependencies": {
43
44
  "@biomejs/biome": "1.9.4",
@@ -1,12 +1,12 @@
1
- # fix-bugs-local
1
+ # code-local
2
2
 
3
3
  A code factory that runs on your own hardware with zero infrastructure. It drains
4
4
  eligible tracker issues, fixes each in an isolated git worktree, gates the result
5
5
  against your CI, and opens a pull request. It never merges.
6
6
 
7
7
  ## Setup
8
- 1. `ghostrail init fix-bugs-local` in your repo (or copy this folder in).
9
- 2. Edit `ghostrail.toml`: set `source.project` to your Linear team key and the
8
+ 1. `ghostrail init code-local` in your repo (or copy this folder in).
9
+ 2. Edit `ghostrail.toml`: set `source.team` to your Linear team key and the
10
10
  `eligible` filter, and set `gate.commands` to your CI steps.
11
11
  3. Export `LINEAR_API_KEY` (a Linear personal API key) and make sure `gh` is
12
12
  authenticated for your repo.
@@ -0,0 +1,44 @@
1
+ # code-local: a code factory on your own hardware, zero infrastructure.
2
+ # Point it at a repo and a tracker queue; it resolves issues and opens PRs you merge.
3
+
4
+ [factory]
5
+ name = "code-local"
6
+ base_branch = "main"
7
+
8
+ [source]
9
+ kind = "linear" # linear | github-issues (Phase 2)
10
+ team = "TJ" # your Linear team key
11
+ eligible = { state = "Todo", label = "factory", assignee = "unassigned" }
12
+
13
+ [agent]
14
+ kind = "claude-code"
15
+ prompt = "prompts/resolve-issue.md"
16
+ # default allowed_tools = ["Read", "Edit", "Write", "Bash", "Grep", "Glob"]
17
+
18
+ [backend]
19
+ kind = "local-worktree" # zero infrastructure; git worktree in place
20
+
21
+ [gate] # the oracle: mirror your CI
22
+ commands = [
23
+ "pnpm install --frozen-lockfile",
24
+ "pnpm lint",
25
+ "pnpm typecheck",
26
+ "pnpm test:unit",
27
+ "pnpm build",
28
+ ]
29
+
30
+ [guardrails]
31
+ max_items_per_run = 3
32
+ budget_usd_per_item = 3
33
+ timeout = "30m"
34
+
35
+ [output]
36
+ open_pr = true
37
+ auto_merge = false # a human owns every merge
38
+
39
+ [respond] # `ghostrail respond` folds PR + tracker comments back in
40
+ prompt = "prompts/respond.md"
41
+
42
+ [triage] # `ghostrail triage` splits large issues into sub-issues
43
+ prompt = "prompts/triage.md"
44
+ eligible = { state = "Todo", label = "triage", assignee = "unassigned" }
@@ -0,0 +1,31 @@
1
+ # Resolve an issue
2
+
3
+ You are a coding agent working in an isolated checkout of this repository. Resolve
4
+ the issue described below, whatever kind it is: a bug fix, a new feature, a
5
+ refactor, or a docs change. You may only read and write files and run shell
6
+ commands in this workspace. Do not use git: the factory owns commits, branches,
7
+ and PRs.
8
+
9
+ Match the surrounding code: its style, patterns, and test conventions.
10
+
11
+ ## The issue
12
+ - ID: {{id}}
13
+ - Title: {{title}}
14
+ - Link: {{url}}
15
+
16
+ ## What to do
17
+ 1. Locate the relevant code and understand how it is structured.
18
+ 2. Make the smallest correct change that resolves the issue.
19
+ 3. Add or update tests (author them from the intent, not the implementation).
20
+ 4. Run the project's lint, typecheck, and unit-test commands and fix what you touched.
21
+
22
+ ## Reporting your result (required, do this last)
23
+ Write a JSON file at `.ghostrail/result.json` with exactly one of:
24
+
25
+ - `{"status":"done","summary":"<what you changed>","type":"<feat|fix|docs|refactor|chore|...>"}`
26
+ - `{"status":"blocked","questions":"<a real product/design decision you need>"}`
27
+ - `{"status":"failed","error":"<why>"}`
28
+ - `{"status":"noop"}`
29
+
30
+ `type` is the conventional-commit type for the change; it sets the commit and PR
31
+ title. If you omit it, the factory's configured default is used.
@@ -0,0 +1,27 @@
1
+ # Respond to review feedback
2
+
3
+ You are a coding agent in an isolated checkout of this repository, on the branch
4
+ of an open pull request. New human feedback has been left on the PR or its tracker
5
+ issue. Address it. You may only read and write files and run shell commands in
6
+ this workspace. Do not use git: the factory owns commits and pushes.
7
+
8
+ ## The feedback
9
+ Read `.ghostrail/feedback.md`. It contains the new comments (author and time),
10
+ newest work last. Treat it as the review to act on.
11
+
12
+ ## What to do
13
+ 1. Read the feedback and the current state of the branch.
14
+ 2. Make the changes it asks for, matching the surrounding code and tests.
15
+ 3. Run the project's lint, typecheck, and unit-test commands and fix what you touched.
16
+ 4. If a comment is a question rather than a change request, answer it in your result
17
+ (see `blocked`) instead of guessing.
18
+
19
+ ## Reporting your result (required, do this last)
20
+ Write a JSON file at `.ghostrail/result.json` with exactly one of:
21
+
22
+ - `{"status":"done","summary":"<what you changed in response>"}`
23
+ - `{"status":"blocked","questions":"<what you need answered>"}`
24
+ - `{"status":"failed","error":"<why>"}`
25
+ - `{"status":"noop"}`
26
+
27
+ On `done` the factory commits and pushes to the PR branch and posts a summary.
@@ -0,0 +1,51 @@
1
+ # Triage an issue
2
+
3
+ You are triaging an issue in an isolated checkout of this repository. Your job is
4
+ NOT to fix it: it is to decide whether the issue is too large to fix in one pass
5
+ and, if so, propose how to break it into smaller, ordered sub-issues. Do not use
6
+ git and do not change any source files except the two output files below.
7
+
8
+ ## The issue
9
+ - ID: {{id}}
10
+ - Title: {{title}}
11
+ - Link: {{url}}
12
+
13
+ ## What to do
14
+ 1. Read the issue and enough of the codebase to judge its scope.
15
+ 2. Decide:
16
+ - **Atomic** — a single, focused change a coding agent can do in one pass.
17
+ - **Too large** — spans several independent changes, or mixes unrelated
18
+ concerns. Propose 2 to 6 sub-issues, each independently shippable, ordered so
19
+ that dependencies come first.
20
+ 3. Write the plan to `.ghostrail/triage.json` (schema below).
21
+ 4. Write `.ghostrail/result.json` with `{"status":"done","summary":"<one line>"}`
22
+ when you produced a plan, or `{"status":"blocked","questions":"<what you need>"}`
23
+ if you genuinely cannot decide without a human.
24
+
25
+ ## `.ghostrail/triage.json` schema
26
+
27
+ Atomic (no split):
28
+
29
+ ```json
30
+ { "status": "noop", "reason": "<why it is already the right size>" }
31
+ ```
32
+
33
+ Split into sub-issues:
34
+
35
+ ```json
36
+ {
37
+ "status": "split",
38
+ "reason": "<why it needs splitting>",
39
+ "subIssues": [
40
+ { "title": "First, self-contained step", "description": "<what and why>" },
41
+ { "title": "Second step", "description": "<...>", "dependsOn": [0] }
42
+ ]
43
+ }
44
+ ```
45
+
46
+ - `dependsOn` lists the indices of earlier sub-issues (0-based, all strictly
47
+ less than the current one) that must be done first.
48
+ - Keep titles imperative and specific. Each sub-issue should stand on its own.
49
+
50
+ The factory creates the sub-issues (unassigned) and a human assigns each to the
51
+ bot to start work; the parent leaves the triage queue.
@@ -13,8 +13,8 @@ base_branch = "main"
13
13
 
14
14
  [source]
15
15
  kind = "linear"
16
- project = "TJ" # your Linear team key
17
- eligible = { state = "Todo", label = "draft", unassigned = true }
16
+ team = "TJ" # your Linear team key
17
+ eligible = { state = "Todo", label = "draft", assignee = "unassigned" }
18
18
 
19
19
  [agent]
20
20
  kind = "claude-code"
@@ -34,3 +34,6 @@ timeout = "30m"
34
34
  open_pr = true
35
35
  draft = true # content PRs open as drafts
36
36
  auto_merge = false
37
+
38
+ [respond] # fold PR + tracker feedback back into the draft
39
+ prompt = "prompts/respond.md"
@@ -0,0 +1,25 @@
1
+ # Respond to feedback on a draft
2
+
3
+ You are a writing agent in an isolated checkout of this repository, on the branch
4
+ of an open draft pull request. New human feedback has been left on the PR or its
5
+ tracker issue. Revise the draft to address it. You may only read and write files
6
+ and run shell commands in this workspace. Do not use git: the factory owns commits
7
+ and pushes.
8
+
9
+ ## The feedback
10
+ Read `.ghostrail/feedback.md`. It contains the new comments (author and time),
11
+ newest work last. Treat it as the editorial review to act on.
12
+
13
+ ## What to do
14
+ 1. Read the feedback and the current draft on this branch.
15
+ 2. Revise the draft accordingly, keeping the established voice and format.
16
+ 3. If a comment is a question rather than a change, answer it in your result
17
+ (`blocked`) instead of guessing.
18
+
19
+ ## Reporting your result (required, do this last)
20
+ Write a JSON file at `.ghostrail/result.json` with exactly one of:
21
+
22
+ - `{"status":"done","summary":"<what you revised>"}`
23
+ - `{"status":"blocked","questions":"<what you need answered>"}`
24
+ - `{"status":"failed","error":"<why>"}`
25
+ - `{"status":"noop"}`
@@ -1,37 +0,0 @@
1
- # fix-bugs-local: a code factory on your own hardware, zero infrastructure.
2
- # Point it at a repo and a tracker label; it fixes bugs and opens PRs you merge.
3
-
4
- [factory]
5
- name = "fix-bugs"
6
- base_branch = "main"
7
-
8
- [source]
9
- kind = "linear" # linear | github-issues (Phase 2)
10
- project = "TJ" # your Linear team key
11
- eligible = { state = "Todo", label = "factory", unassigned = true }
12
-
13
- [agent]
14
- kind = "claude-code"
15
- prompt = "prompts/fix-bug.md"
16
- # default allowed_tools = ["Read", "Edit", "Write", "Bash", "Grep", "Glob"]
17
-
18
- [backend]
19
- kind = "local-worktree" # zero infrastructure; git worktree in place
20
-
21
- [gate] # the oracle: mirror your CI
22
- commands = [
23
- "pnpm install --frozen-lockfile",
24
- "pnpm lint",
25
- "pnpm typecheck",
26
- "pnpm test:unit",
27
- "pnpm build",
28
- ]
29
-
30
- [guardrails]
31
- max_items_per_run = 3
32
- budget_usd_per_item = 3
33
- timeout = "30m"
34
-
35
- [output]
36
- open_pr = true
37
- auto_merge = false # a human owns every merge
@@ -1,27 +0,0 @@
1
- # Fix a bug
2
-
3
- You are a coding agent working in an isolated checkout of a repository. Fix the
4
- issue described below. You may only read and write files and run shell commands
5
- in this workspace. Do not use git: the factory owns commits, branches, and PRs.
6
-
7
- ## The issue
8
- - ID: {{id}}
9
- - Title: {{title}}
10
- - Link: {{url}}
11
-
12
- ## What to do
13
- 1. Reproduce and locate the bug.
14
- 2. Make the smallest correct fix.
15
- 3. Add or update unit tests that cover it.
16
- 4. Keep the change focused; do not refactor unrelated code.
17
-
18
- ## Reporting your result (required, do this last)
19
- Write a JSON file at `.ghostrail/result.json` with exactly one of these shapes:
20
-
21
- - Success: `{"status":"done","summary":"<what you changed, one paragraph>"}`
22
- - Needs a human decision: `{"status":"blocked","questions":"<the decision you need>"}`
23
- - Could not fix it: `{"status":"failed","error":"<why>"}`
24
- - No change needed: `{"status":"noop"}`
25
-
26
- The factory reads this file to decide what to do, then runs your CI gate and, if
27
- it passes, opens a pull request for a human to review and merge.