@bridge_gpt/mcp-server 0.1.17 → 0.2.1
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 +334 -196
- package/build/agent-capabilities/cli.js +152 -0
- package/build/agent-capabilities/default-deps.js +45 -0
- package/build/agent-capabilities/probe-context.js +111 -0
- package/build/agent-capabilities/probes.js +278 -0
- package/build/agent-capabilities/reporter.js +50 -0
- package/build/agent-capabilities/runner.js +56 -0
- package/build/agent-capabilities/types.js +10 -0
- package/build/agent-launchers/claude.js +25 -17
- package/build/agent-launchers/cursor.js +65 -0
- package/build/agent-launchers/index.js +23 -8
- package/build/agent-registry.js +68 -0
- package/build/agents.generated.js +1 -1
- package/build/brainstorm-files.js +89 -0
- package/build/bridge-config.js +404 -0
- package/build/chain-orchestrator.js +247 -33
- package/build/command-catalog.js +376 -0
- package/build/commands.generated.js +10 -7
- package/build/credential-materialization.js +128 -0
- package/build/credential-store.js +232 -0
- package/build/decision-page-schema.js +39 -6
- package/build/decision-page-template.js +54 -18
- package/build/doctor.js +18 -2
- package/build/git-ignore-utils.js +63 -0
- package/build/index.js +1707 -557
- package/build/mcp-invoke.js +417 -0
- package/build/mcp-provisioning.js +342 -0
- package/build/mcp-registration-doctor.js +96 -0
- package/build/pipeline-orchestrator.js +9 -1
- package/build/pipelines.generated.js +5 -3
- package/build/schedule-run.js +440 -92
- package/build/schedule-store.js +41 -1
- package/build/scheduled-prompt.js +109 -0
- package/build/scheduler-backends/at-fallback.js +5 -10
- package/build/scheduler-backends/escaping.js +40 -10
- package/build/scheduler-backends/launchd.js +23 -14
- package/build/scheduler-backends/systemd-user.js +32 -19
- package/build/scheduler-backends/task-scheduler.js +8 -13
- package/build/start-tickets-prereqs.js +90 -1
- package/build/start-tickets.js +563 -42
- package/build/third-party-mcp-targets.js +75 -0
- package/build/version.generated.js +1 -1
- package/package.json +4 -3
- package/pipelines/full-automation.json +3 -1
- package/smoke-test/SMOKE-TEST.md +62 -17
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Non-throwing probe runner. For each selected agent it builds a probe context,
|
|
3
|
+
* filters probes by `--only` and per-agent applicability, applies the skip-gate
|
|
4
|
+
* (binary absent, or an `env`-auth agent whose credential is unset → skip BEFORE
|
|
5
|
+
* spawning), runs each probe catching any throw as `fail`, and cleans up temp dirs.
|
|
6
|
+
*/
|
|
7
|
+
import { resolveAgentSpec } from "../agent-registry.js";
|
|
8
|
+
import { createProbeContext } from "./probe-context.js";
|
|
9
|
+
import { ALL_PROBES } from "./probes.js";
|
|
10
|
+
import { AGENT_AUTH, } from "./types.js";
|
|
11
|
+
/** Apply the skip-gate, then run the probe (catching throws as `fail`). */
|
|
12
|
+
async function runOneProbe(deps, agentName, probe, ctx) {
|
|
13
|
+
if (probe.spawnsAgent) {
|
|
14
|
+
if (ctx.resolvedBinary === null) {
|
|
15
|
+
return { status: "skip", detail: `${ctx.agent.command} not on PATH — see binary-resolves` };
|
|
16
|
+
}
|
|
17
|
+
const auth = AGENT_AUTH[agentName];
|
|
18
|
+
if (auth.kind === "env") {
|
|
19
|
+
const value = deps.env[auth.varName];
|
|
20
|
+
if (!value || value.length === 0) {
|
|
21
|
+
return { status: "skip", detail: `${auth.varName} not set — set it to run this probe` };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
// interactive auth (claude) cannot be verified from env — run and report the outcome.
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
return await probe.run(ctx);
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return { status: "fail", detail: err instanceof Error ? err.message : String(err) };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export async function collectCapabilityResults(deps, options) {
|
|
34
|
+
const records = [];
|
|
35
|
+
for (const agentName of options.agents) {
|
|
36
|
+
const agent = resolveAgentSpec(agentName);
|
|
37
|
+
if (!agent)
|
|
38
|
+
continue; // defensive: agent names are validated by the CLI parser
|
|
39
|
+
const applicable = ALL_PROBES.filter((p) => p.appliesTo.includes(agentName) && (!options.only || options.only.includes(p.id)));
|
|
40
|
+
const { ctx, cleanup } = await createProbeContext(deps, agent, options.timeoutMs);
|
|
41
|
+
try {
|
|
42
|
+
for (const probe of applicable) {
|
|
43
|
+
const result = await runOneProbe(deps, agentName, probe, ctx);
|
|
44
|
+
records.push({ agent: agentName, probeId: probe.id, title: probe.title, result });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
await cleanup();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { records };
|
|
52
|
+
}
|
|
53
|
+
/** True when any probe failed or hung — drives the nonzero CLI exit code. */
|
|
54
|
+
export function hasFailureOrHang(collection) {
|
|
55
|
+
return collection.records.some((r) => r.result.status === "fail" || r.result.status === "hang");
|
|
56
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Per-agent auth requirement used by the runner's skip-gate and the reporter. */
|
|
2
|
+
export const AGENT_AUTH = {
|
|
3
|
+
claude: {
|
|
4
|
+
kind: "interactive",
|
|
5
|
+
note: "Claude Code authenticates via keychain/OAuth — cannot be verified from the environment.",
|
|
6
|
+
},
|
|
7
|
+
"cursor-agent": { kind: "env", varName: "CURSOR_API_KEY" },
|
|
8
|
+
};
|
|
9
|
+
/** Default hard timeout per headless probe run (ms). The cursor `-p` hang bug is version-sensitive. */
|
|
10
|
+
export const DEFAULT_PROBE_TIMEOUT_MS = 90_000;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The `claude` agent launcher (BAPI-327, generalized in BAPI-351).
|
|
3
3
|
*
|
|
4
4
|
* Resolves the `claude` binary against the *baked schedule-time PATH* (not the
|
|
5
|
-
* ambient process default)
|
|
6
|
-
*
|
|
7
|
-
* prompt
|
|
5
|
+
* ambient process default) and builds the scheduled-run invocation by delegating
|
|
6
|
+
* to the shared `renderScheduledPrompt` renderer (no hard-coded `/full-automation`
|
|
7
|
+
* prompt). It emits `{ exe, args: ["-p", prompt] }`. Claude Code has no
|
|
8
8
|
* working-directory flag, so the cwd is always set by the scheduler unit — this
|
|
9
|
-
* adapter must never add a
|
|
9
|
+
* adapter must never add a working-directory argument to the invocation.
|
|
10
10
|
*/
|
|
11
11
|
import { pathApiForPlatform } from "../scheduler-backends/types.js";
|
|
12
12
|
import { posixShellQuote, windowsCmdQuote } from "../scheduler-backends/escaping.js";
|
|
13
|
+
import { renderScheduledPrompt } from "../scheduled-prompt.js";
|
|
14
|
+
import { quotePromptToken } from "../scheduled-prompt.js";
|
|
13
15
|
/**
|
|
14
16
|
* Resolve a bare command to an absolute path using the platform PATH-probe,
|
|
15
17
|
* forcing the baked PATH so the schedule resolves the same binary the user had
|
|
@@ -40,24 +42,30 @@ export async function resolveCommandOnPath(command, envPath, deps) {
|
|
|
40
42
|
return pathApi.normalize(candidate);
|
|
41
43
|
}
|
|
42
44
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* absolute path is additionally baked into `BRIDGE_GPT_IDEA_FILE` by every
|
|
48
|
-
* backend as a robust environment fallback.
|
|
45
|
+
* Backward-compatibility shim for callers that quoted an idea-file path for a
|
|
46
|
+
* `/full-automation` prompt. Generalized prompt-token quoting now lives in
|
|
47
|
+
* `quotePromptToken`; this preserves the original quote semantics (always wrap in
|
|
48
|
+
* double quotes, escape embedded quotes) for any remaining path callers.
|
|
49
49
|
*/
|
|
50
50
|
export function quoteIdeaFileForPrompt(ideaFile) {
|
|
51
51
|
return `"${ideaFile.replace(/"/g, '\\"')}"`;
|
|
52
52
|
}
|
|
53
|
+
/** Re-export the generalized token quoter so launcher consumers have one import. */
|
|
54
|
+
export { quotePromptToken };
|
|
53
55
|
/**
|
|
54
|
-
* Build the
|
|
55
|
-
*
|
|
56
|
+
* Build the scheduled-run prompt for the Claude launcher by delegating to the
|
|
57
|
+
* shared renderer. Exposed for focused unit testing of the launcher wiring.
|
|
56
58
|
*/
|
|
57
59
|
export function buildClaudePrompt(input) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
return renderScheduledPrompt({
|
|
61
|
+
scheduleId: input.scheduleId,
|
|
62
|
+
scheduledAt: input.runAtIso,
|
|
63
|
+
autoApprove: input.autoApprove,
|
|
64
|
+
commandName: input.commandName,
|
|
65
|
+
args: input.args,
|
|
66
|
+
commandBody: input.commandBody,
|
|
67
|
+
schema: input.schema,
|
|
68
|
+
});
|
|
61
69
|
}
|
|
62
70
|
const CLAUDE_CAPABILITY = {
|
|
63
71
|
name: "claude",
|
|
@@ -65,7 +73,7 @@ const CLAUDE_CAPABILITY = {
|
|
|
65
73
|
supportsCwdFlag: false,
|
|
66
74
|
promptFlag: "-p",
|
|
67
75
|
};
|
|
68
|
-
/** Create the
|
|
76
|
+
/** Create the Claude agent launcher. */
|
|
69
77
|
export function createClaudeLauncher() {
|
|
70
78
|
return {
|
|
71
79
|
capability: CLAUDE_CAPABILITY,
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `cursor-agent` agent launcher (BAPI-351).
|
|
3
|
+
*
|
|
4
|
+
* A near-mirror of the Claude launcher: it resolves the `cursor-agent` binary
|
|
5
|
+
* against the baked schedule-time PATH and builds the scheduled-run prompt via
|
|
6
|
+
* the shared `renderScheduledPrompt` renderer (so the drift gate and command body
|
|
7
|
+
* are identical across agents). A local spike confirmed cursor-agent resolves the
|
|
8
|
+
* same `.claude/commands` catalog and exits cleanly in headless mode.
|
|
9
|
+
*
|
|
10
|
+
* Cursor differences vs. Claude:
|
|
11
|
+
* - headless invocation needs `--output-format text`, `--trust`, and a
|
|
12
|
+
* `--workspace <repoPath>` flag (Cursor's working-directory flag), with the
|
|
13
|
+
* prompt as the final positional token;
|
|
14
|
+
* - headless auth is via the `CURSOR_API_KEY` environment variable. This adapter
|
|
15
|
+
* never reads or prints that value — auth readiness is a prerequisite check.
|
|
16
|
+
*/
|
|
17
|
+
import { posixShellQuote, windowsCmdQuote } from "../scheduler-backends/escaping.js";
|
|
18
|
+
import { renderScheduledPrompt } from "../scheduled-prompt.js";
|
|
19
|
+
import { resolveCommandOnPath } from "./claude.js";
|
|
20
|
+
/** Build the scheduled-run prompt for the Cursor launcher via the shared renderer. */
|
|
21
|
+
export function buildCursorPrompt(input) {
|
|
22
|
+
return renderScheduledPrompt({
|
|
23
|
+
scheduleId: input.scheduleId,
|
|
24
|
+
scheduledAt: input.runAtIso,
|
|
25
|
+
autoApprove: input.autoApprove,
|
|
26
|
+
commandName: input.commandName,
|
|
27
|
+
args: input.args,
|
|
28
|
+
commandBody: input.commandBody,
|
|
29
|
+
schema: input.schema,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const CURSOR_CAPABILITY = {
|
|
33
|
+
name: "cursor-agent",
|
|
34
|
+
command: "cursor-agent",
|
|
35
|
+
supportsCwdFlag: true,
|
|
36
|
+
promptFlag: "-p",
|
|
37
|
+
};
|
|
38
|
+
/** Create the cursor-agent launcher. */
|
|
39
|
+
export function createCursorLauncher() {
|
|
40
|
+
return {
|
|
41
|
+
capability: CURSOR_CAPABILITY,
|
|
42
|
+
resolveBinary(envPath, deps) {
|
|
43
|
+
return resolveCommandOnPath("cursor-agent", envPath, deps);
|
|
44
|
+
},
|
|
45
|
+
buildInvocation(exe, input) {
|
|
46
|
+
const prompt = buildCursorPrompt(input);
|
|
47
|
+
// Cursor takes the working directory via --workspace and the prompt as the
|
|
48
|
+
// final positional token; CURSOR_API_KEY (auth) is never placed in argv.
|
|
49
|
+
const args = [
|
|
50
|
+
CURSOR_CAPABILITY.promptFlag,
|
|
51
|
+
"--output-format",
|
|
52
|
+
"text",
|
|
53
|
+
"--trust",
|
|
54
|
+
"--workspace",
|
|
55
|
+
input.repoPath,
|
|
56
|
+
prompt,
|
|
57
|
+
];
|
|
58
|
+
return { exe, args, prompt };
|
|
59
|
+
},
|
|
60
|
+
formatInvocationLine(invocation, platform) {
|
|
61
|
+
const quote = platform === "win32" ? windowsCmdQuote : posixShellQuote;
|
|
62
|
+
return [invocation.exe, ...invocation.args].map((part) => quote(part)).join(" ");
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -1,17 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Agent-launcher registry (BAPI-327
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Agent-launcher registry (BAPI-327, extended in BAPI-351).
|
|
3
|
+
*
|
|
4
|
+
* Maps a launcher name to its adapter. BAPI-351 added `cursor-agent` beside the
|
|
5
|
+
* default `claude`; the lookup returns `null` for every other name so the CLI can
|
|
6
|
+
* reject unsupported agents with a clear message rather than silently falling
|
|
7
|
+
* back. The supported names are kept aligned with `AgentName` from
|
|
8
|
+
* `agent-registry.ts` to avoid registry drift.
|
|
5
9
|
*/
|
|
6
10
|
import { createClaudeLauncher } from "./claude.js";
|
|
11
|
+
import { createCursorLauncher } from "./cursor.js";
|
|
7
12
|
export { createClaudeLauncher } from "./claude.js";
|
|
13
|
+
export { createCursorLauncher } from "./cursor.js";
|
|
8
14
|
const CLAUDE_LAUNCHER = createClaudeLauncher();
|
|
9
|
-
|
|
15
|
+
const CURSOR_LAUNCHER = createCursorLauncher();
|
|
16
|
+
/** All supported launcher names, in deterministic order (claude first/default). */
|
|
17
|
+
const LAUNCHER_NAMES = ["claude", "cursor-agent"];
|
|
18
|
+
/** Return the launcher for a name, or `null` when unsupported. */
|
|
10
19
|
export function getAgentLauncher(name) {
|
|
11
|
-
|
|
20
|
+
switch (name) {
|
|
21
|
+
case "claude":
|
|
22
|
+
return CLAUDE_LAUNCHER;
|
|
23
|
+
case "cursor-agent":
|
|
24
|
+
return CURSOR_LAUNCHER;
|
|
25
|
+
default:
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
12
28
|
}
|
|
13
|
-
/** Comma-separated list of valid agent-launcher names (
|
|
29
|
+
/** Comma-separated list of valid agent-launcher names (`claude, cursor-agent`). */
|
|
14
30
|
export function formatValidAgentLauncherNames() {
|
|
15
|
-
|
|
16
|
-
return names.join(", ");
|
|
31
|
+
return LAUNCHER_NAMES.join(", ");
|
|
17
32
|
}
|
package/build/agent-registry.js
CHANGED
|
@@ -11,6 +11,19 @@
|
|
|
11
11
|
* `start-tickets.ts` / `start-tickets-prereqs.ts` / `doctor.ts`) so every other
|
|
12
12
|
* module can import it without risking a circular dependency.
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* Conservative allowlist pattern for any model alias before it can reach shell
|
|
16
|
+
* construction. Mirrors the backend `_MODEL_ALIAS_PATTERN` in jira_api.py.
|
|
17
|
+
*/
|
|
18
|
+
export const MODEL_ALIAS_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
19
|
+
/** True only when `value` is a non-empty string matching the alias pattern. */
|
|
20
|
+
export function isValidModelAlias(value) {
|
|
21
|
+
return typeof value === "string" && value.length > 0 && MODEL_ALIAS_PATTERN.test(value);
|
|
22
|
+
}
|
|
23
|
+
/** Type guard: true only for the three known tier names. */
|
|
24
|
+
export function isModelTier(value) {
|
|
25
|
+
return value === "cheap" || value === "basic" || value === "premium";
|
|
26
|
+
}
|
|
14
27
|
/**
|
|
15
28
|
* The registry: the ONLY place mapping an agent name to its command/spec. Seeded
|
|
16
29
|
* with exactly `claude` (default) and `cursor-agent`. `as const satisfies` keeps
|
|
@@ -28,6 +41,12 @@ export const AGENT_REGISTRY = {
|
|
|
28
41
|
win32: "npm install -g @anthropic-ai/claude-code",
|
|
29
42
|
},
|
|
30
43
|
authNote: "Claude Code authenticates interactively on first run — follow its login/auth prompt if asked.",
|
|
44
|
+
supportsModelOverride: true,
|
|
45
|
+
modelFlag: "--model",
|
|
46
|
+
// Claude family aliases float to the latest release and never drift, so they
|
|
47
|
+
// can be validated against a static allowlist.
|
|
48
|
+
tierModels: { cheap: "haiku", basic: "sonnet", premium: "opus" },
|
|
49
|
+
staticModelAliasAllowlist: ["haiku", "sonnet", "opus"],
|
|
31
50
|
},
|
|
32
51
|
"cursor-agent": {
|
|
33
52
|
name: "cursor-agent",
|
|
@@ -39,6 +58,24 @@ export const AGENT_REGISTRY = {
|
|
|
39
58
|
win32: "irm 'https://cursor.com/install?win32=true' | iex",
|
|
40
59
|
},
|
|
41
60
|
authNote: "Run cursor-agent login to authenticate; doctor checks PATH presence only, not login state.",
|
|
61
|
+
supportsModelOverride: true,
|
|
62
|
+
modelFlag: "--model",
|
|
63
|
+
// NOTE: Cursor model strings are version-sensitive and DRIFT between
|
|
64
|
+
// releases — unlike claude's stable family aliases. The ids are not even
|
|
65
|
+
// internally consistent across versions (Cursor advertises Sonnet/Opus 4.6
|
|
66
|
+
// as `claude-4.6-sonnet-…`/`claude-4.6-opus-…` but Opus 4.7/4.8 as
|
|
67
|
+
// `claude-opus-4-7-…`/`claude-opus-4-8-…`), so these defaults WILL go stale.
|
|
68
|
+
// There is therefore NO staticModelAliasAllowlist here; any resolved cursor
|
|
69
|
+
// alias is validated non-interactively at runtime (against
|
|
70
|
+
// `cursor-agent --list-models`) before injection, and an unadvertised id
|
|
71
|
+
// fail-opens to the cursor default. To pin a current id without a release,
|
|
72
|
+
// use the per-repo `difficulty_model_tier_overrides` config.
|
|
73
|
+
// Last verified against `cursor-agent --list-models` on 2026-06-15.
|
|
74
|
+
tierModels: {
|
|
75
|
+
cheap: "auto",
|
|
76
|
+
basic: "claude-4.6-sonnet-medium",
|
|
77
|
+
premium: "claude-opus-4-8-thinking-high",
|
|
78
|
+
},
|
|
42
79
|
},
|
|
43
80
|
};
|
|
44
81
|
/** The default agent used when `--agent` is omitted. */
|
|
@@ -66,3 +103,34 @@ export function resolveAgentSpec(name) {
|
|
|
66
103
|
export function formatValidAgentNames() {
|
|
67
104
|
return listAgentNames().join(", ");
|
|
68
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Resolve the concrete model alias to inject for a given agent + tier, applying
|
|
108
|
+
* an optional per-repo override. Pure and dependency-free: it never spawns a
|
|
109
|
+
* subprocess and never imports from `start-tickets.ts`.
|
|
110
|
+
*
|
|
111
|
+
* Returns `null` (meaning "omit `--model`, use the agent default") when:
|
|
112
|
+
* - the agent does not support a model override,
|
|
113
|
+
* - no tier is provided,
|
|
114
|
+
* - the resolved candidate fails the alias-pattern validation, or
|
|
115
|
+
* - the agent has a `staticModelAliasAllowlist` and the candidate is not in it.
|
|
116
|
+
*
|
|
117
|
+
* A non-empty override for the selected tier takes precedence over the registry
|
|
118
|
+
* default. For agents without a static allowlist (e.g. cursor-agent), the caller
|
|
119
|
+
* is still responsible for live-validating the returned alias before injection.
|
|
120
|
+
*/
|
|
121
|
+
export function resolveModelAlias(agent, tier, overrides) {
|
|
122
|
+
if (!agent.supportsModelOverride)
|
|
123
|
+
return null;
|
|
124
|
+
if (!tier)
|
|
125
|
+
return null;
|
|
126
|
+
const override = overrides?.[tier];
|
|
127
|
+
const candidate = typeof override === "string" && override.trim().length > 0
|
|
128
|
+
? override.trim()
|
|
129
|
+
: agent.tierModels[tier];
|
|
130
|
+
if (typeof candidate !== "string" || !isValidModelAlias(candidate))
|
|
131
|
+
return null;
|
|
132
|
+
if (agent.staticModelAliasAllowlist && !agent.staticModelAliasAllowlist.includes(candidate)) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return candidate;
|
|
136
|
+
}
|
|
@@ -8,6 +8,6 @@ export const AGENTS = {
|
|
|
8
8
|
"model": "opus",
|
|
9
9
|
"color": "blue"
|
|
10
10
|
},
|
|
11
|
-
"body": "\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis is the most critical phase. You MUST spend significant time here before writing anything. Do NOT rush this phase.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user's problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the key source files thoroughly — do not skim\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Read at least 5-15 relevant source files in full, more if the problem is complex\n- Follow import chains to understand dependencies\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific — reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` — `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` — [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[
|
|
11
|
+
"body": "\nYou are an elite software engineering project manager and technical analyst with deep expertise in codebase archaeology and Jira ticket crafting. You excel at understanding complex codebases, identifying relevant existing code, and translating problem descriptions into precisely-scoped, actionable Jira tickets that engineers can pick up and execute with minimal ambiguity.\n\n## Your Mission\n\nGiven a problem description from the user, you will:\n1. Conduct thorough codebase research to understand the existing architecture, patterns, and relevant code\n2. Write a structured Jira ticket as a new markdown file that references specific files, functions, and patterns from the codebase\n\n## Phase 1: Deep Codebase Research\n\nThis is the most critical phase. You MUST spend significant time here before writing anything. Do NOT rush this phase.\n\n### Research Protocol\n\n1. **Understand the Problem Space**: Re-read the user's problem description carefully. Identify the domain, the affected areas, and the type of change needed (new feature, bug fix, refactor, enhancement).\n\n2. **Map the Relevant Architecture**: \n - Search for files, modules, and directories related to the problem domain\n - Read the key source files thoroughly — do not skim\n - Trace code paths: how does data flow through the relevant parts of the system?\n - Identify controller -> helper -> service -> model chains if applicable\n\n3. **Identify Extension Points**:\n - What existing code can be reused or extended?\n - What patterns does the codebase already use for similar functionality?\n - Are there helper functions, utilities, or base classes that should be leveraged?\n - Are there configuration files, metadata definitions, or templates that need modification?\n\n4. **Identify Constraints**:\n - What conventions does the project follow? (Check CLAUDE.md, README, existing patterns)\n - What testing patterns are used?\n - Are there ES5 limitations, specific framework patterns, or platform constraints?\n\n5. **Catalog Your Findings**: Keep mental notes of every relevant file path, function name, pattern, and architectural decision you discover. You will reference these in the ticket.\n\n### Research Depth Guidelines\n- Read at least 5-15 relevant source files in full, more if the problem is complex\n- Follow import chains to understand dependencies\n- Check test files to understand expected behaviors and testing patterns\n- Review configuration and metadata files if relevant\n- Search for TODO comments, known limitations, or related existing issues in the code\n\n## Phase 2: Write the Jira Ticket\n\nAfter completing research, create a new markdown file with the ticket. Use the naming convention `tickets/TICKET-<short-descriptive-name>.md`. If the `tickets/` directory does not exist, create it.\n\n### Ticket Structure\n\nThe markdown file MUST contain exactly these sections:\n\n```markdown\n# [Concise Title Describing the Task]\n\n## Summary\n\n[2-4 sentences describing what this task is about, why it matters, and the high-level approach. Be specific — reference the actual system components involved.]\n\n## Requirements\n\n[Numbered list of specific, actionable requirements. Each requirement should be a clear unit of work.]\n\n1. **[Requirement Title]**: [Description of what needs to be done.]\n - *Relevant code*: `path/to/file.js` — `functionName()` [brief note on how this code relates]\n - *Relevant code*: `path/to/other/file.js` — [brief note]\n\n2. **[Requirement Title]**: [Description]\n - *Relevant code*: ...\n\n[Continue for all requirements]\n\n## Acceptance Criteria\n\n[Bullet list. Each criterion is a testable, verifiable condition.]\n\n- [Specific, testable criterion]\n- [Another criterion]\n- [Continue as needed]\n```\n\n### Writing Guidelines\n\n**Summary**:\n- Be concrete, not abstract. Name the actual components, cartridges, or subsystems involved.\n- State the \"why\" — what problem does this solve or what value does it add?\n- Mention the general technical approach if it's clear from the research.\n\n**Requirements**:\n- Each requirement should represent a logical unit of work\n- Order requirements in a logical implementation sequence when possible\n- ALWAYS cite relevant existing files and functions when they exist. Use exact file paths relative to the project root.\n- Explain HOW the existing code relates: \"extend this function\", \"follow this pattern\", \"reuse this helper\", \"modify this configuration\"\n- If a requirement involves creating new files, suggest where they should live based on existing project structure conventions\n- Be specific about what needs to change vs. what needs to be created new\n- Include requirements for tests, documentation, and configuration/metadata changes if applicable\n\n**Acceptance Criteria**:\n- Every criterion must be independently verifiable\n- Cover functional requirements, edge cases, testing, and non-functional requirements\n- Include criteria for backwards compatibility if relevant\n- Include criteria for test coverage\n- Use plain `-` bullets (Jira's ADF has no native checkbox, so `- [ ]` renders as literal text)\n\n### Output Formatting (Jira upload)\n\nThe ticket is uploaded to Jira, which converts the Markdown to Atlassian Document Format (ADF) and hard-caps the description at **32,767 characters**. Keep the output clean and within budget:\n\n- **Length**: aim for under ~30,000 characters. If the scope genuinely needs more, split into a parent ticket plus sub-tickets rather than one oversized ticket.\n- **Acceptance Criteria**: plain `-` bullets, not `- [ ]` (ADF has no native checkbox).\n- **No images**: do not embed images or use relative image links.\n- **No empty headings**: every heading must have text on its line.\n- **Placeholders**: prefer `{placeholder}` over `<placeholder>`.\n\n## Quality Standards\n\n- **No vague language**: Replace \"should handle errors properly\" with \"should catch LLM provider timeouts and return a normalized error response with errorType 'TimeoutError'\"\n- **No assumptions without evidence**: Only reference code you actually read during research. If you're unsure about something, say so explicitly in the ticket.\n- **Appropriate scope**: The ticket should represent a coherent, deliverable unit of work. If the problem is too large, note that it may need to be broken into sub-tasks, but still write the parent ticket.\n- **Developer empathy**: Write as if the developer picking this up has general project knowledge but hasn't recently worked on this specific area. Give them enough context to get started quickly.\n\n## Important Reminders\n\n- Do NOT skip or abbreviate the research phase. The quality of the ticket depends entirely on the depth of your codebase understanding.\n- Do NOT make up file paths or function names. Only reference code you have actually found and read.\n- DO create the markdown file — do not just output the content to the chat. Write it to disk.\n- If the project has specific conventions (from CLAUDE.md or similar), ensure your ticket's requirements align with those conventions.\n"
|
|
12
12
|
}
|
|
13
13
|
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// brainstorm-files
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Pure, side-effect-free helpers for naming and writing brainstorm result
|
|
5
|
+
// files. Extracted from ``index.ts`` so they can be unit-tested directly —
|
|
6
|
+
// ``index.ts`` self-executes its CLI/server at module top level and therefore
|
|
7
|
+
// cannot be imported from a test. ``index.ts`` imports these as the single
|
|
8
|
+
// source of truth (no logic is duplicated there).
|
|
9
|
+
import { writeFile, mkdir } from "fs/promises";
|
|
10
|
+
import path from "path";
|
|
11
|
+
/**
|
|
12
|
+
* Lowercase, strip non-alphanumeric runs, collapse to single hyphens, cap to
|
|
13
|
+
* ``maxLength`` characters, and trim a trailing hyphen. Shared generic helper
|
|
14
|
+
* (also used for deep-research query slugs in ``index.ts``).
|
|
15
|
+
*/
|
|
16
|
+
export function slugify(text, maxLength = 60) {
|
|
17
|
+
return text
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
20
|
+
.trim()
|
|
21
|
+
.replace(/\s+/g, "-")
|
|
22
|
+
.replace(/-+/g, "-")
|
|
23
|
+
.slice(0, maxLength)
|
|
24
|
+
.replace(/-$/, "");
|
|
25
|
+
}
|
|
26
|
+
export function sanitizeProviderForFilename(provider) {
|
|
27
|
+
// Defensive: allow letters/digits/hyphen/underscore only; collapse runs and
|
|
28
|
+
// trim trailing punctuation. Falls back to "provider" for an empty result.
|
|
29
|
+
const cleaned = provider
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
32
|
+
.replace(/-+/g, "-")
|
|
33
|
+
.replace(/^-+|-+$/g, "");
|
|
34
|
+
return cleaned || "provider";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the on-disk filename for one brainstorm result row.
|
|
38
|
+
*
|
|
39
|
+
* When the original task ``subject`` is available and slugifies to a non-empty
|
|
40
|
+
* string, use a human-readable semantic name:
|
|
41
|
+
* ``{slugified-subject}-{short_brainstorm_id}-{provider}.md``
|
|
42
|
+
* where ``short_brainstorm_id`` is the first 8 chars of the brainstorm UUID
|
|
43
|
+
* (enough for practical uniqueness / idempotent overwrite on re-runs).
|
|
44
|
+
*
|
|
45
|
+
* The UUID-only pattern ``{brainstorm_id}-{provider}.md`` is the intentional
|
|
46
|
+
* backward-compatible fallback, used only when no subject is provided, the
|
|
47
|
+
* subject is whitespace-only, or ``slugify(subject)`` returns an empty string
|
|
48
|
+
* (e.g. punctuation-only). This keeps callers without a subject — notably the
|
|
49
|
+
* ``get_brainstorm`` retrieval path, whose result envelope does not echo
|
|
50
|
+
* ``task_description`` — safe and unchanged.
|
|
51
|
+
*/
|
|
52
|
+
export function buildBrainstormResultFilename(envelope, row, subject) {
|
|
53
|
+
const providerSegment = sanitizeProviderForFilename(row.provider);
|
|
54
|
+
const subjectSlug = subject ? slugify(subject) : "";
|
|
55
|
+
const shortId = envelope.brainstorm_id.slice(0, 8);
|
|
56
|
+
if (subjectSlug) {
|
|
57
|
+
return `${subjectSlug}-${shortId}-${providerSegment}.md`;
|
|
58
|
+
}
|
|
59
|
+
return `${envelope.brainstorm_id}-${providerSegment}.md`;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Write each brainstorm result row's markdown into ``dir`` using
|
|
63
|
+
* ``buildBrainstormResultFilename`` for the name. ``index.ts`` wraps this with
|
|
64
|
+
* the resolved ``BAPI_DOCS_DIR/brainstorm`` directory.
|
|
65
|
+
*
|
|
66
|
+
* Behavior is fail-open: rows without markdown are skipped, the directory is
|
|
67
|
+
* created recursively, markdown is written as UTF-8, and per-row write failures
|
|
68
|
+
* are swallowed so saving never blocks the tool response.
|
|
69
|
+
*/
|
|
70
|
+
export async function saveBrainstormResultsToDir(envelope, dir, subject) {
|
|
71
|
+
const savedPaths = [];
|
|
72
|
+
for (const row of envelope.results) {
|
|
73
|
+
const markdown = row.markdown;
|
|
74
|
+
if (!markdown) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const filename = buildBrainstormResultFilename(envelope, row, subject);
|
|
78
|
+
const filePath = path.join(dir, filename);
|
|
79
|
+
try {
|
|
80
|
+
await mkdir(dir, { recursive: true });
|
|
81
|
+
await writeFile(filePath, markdown, "utf-8");
|
|
82
|
+
savedPaths.push(filePath);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Skip rows that fail to write — never block the response.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return savedPaths;
|
|
89
|
+
}
|