create-byan-agent 2.25.0 → 2.26.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/CHANGELOG.md +155 -0
- package/README.md +9 -12
- package/install/bin/create-byan-agent-v2.js +29 -169
- package/install/lib/agent-generator.js +5 -5
- package/install/lib/byan-web-integration.js +1 -1
- package/install/lib/claude-native-setup.js +1 -1
- package/install/lib/phase2-chat.js +3 -10
- package/install/lib/platforms/claude-code.js +2 -2
- package/install/lib/platforms/index.js +0 -2
- package/install/lib/project-agents-generator.js +3 -3
- package/install/lib/staging-consent.js +3 -3
- package/install/lib/subagent-generator.js +3 -3
- package/install/lib/yanstaller/agent-launcher.js +1 -27
- package/install/lib/yanstaller/detector.js +4 -4
- package/install/lib/yanstaller/installer.js +0 -2
- package/install/lib/yanstaller/interviewer.js +1 -1
- package/install/lib/yanstaller/platform-selector.js +1 -13
- package/install/package.json +1 -1
- package/install/src/byan-v2/context/session-state.js +2 -2
- package/install/src/byan-v2/index.js +1 -5
- package/install/src/byan-v2/orchestrator/generation-state.js +4 -4
- package/install/src/webui/api.js +0 -2
- package/install/src/webui/chat/bridge.js +1 -13
- package/install/src/webui/chat/cli-detector.js +0 -23
- package/install/src/webui/public/app.js +1 -3
- package/install/src/webui/public/chat.html +0 -2
- package/install/src/webui/public/chat.js +0 -1
- package/install/src/webui/public/index.html +2 -2
- package/install/templates/.claude/CLAUDE.md +13 -2
- package/install/templates/.claude/agents/bmad-byan.md +1 -1
- package/install/templates/.claude/hooks/autobench-stop-guard.js +286 -0
- package/install/templates/.claude/hooks/fact-check-absolutes.js +1 -61
- package/install/templates/.claude/hooks/fact-check-claims.js +69 -0
- package/install/templates/.claude/hooks/fd-response-check.js +37 -46
- package/install/templates/.claude/hooks/inject-soul.js +64 -25
- package/install/templates/.claude/hooks/leantime-fd-sync.js +216 -0
- package/install/templates/.claude/hooks/lib/autobench-config.json +81 -0
- package/install/templates/.claude/hooks/lib/autobench-fc-enrich.js +251 -0
- package/install/templates/.claude/hooks/lib/autobench-ledger-report.js +253 -0
- package/install/templates/.claude/hooks/lib/autobench-runtime.js +199 -0
- package/install/templates/.claude/hooks/lib/fact-check-core.js +69 -0
- package/install/templates/.claude/hooks/lib/transcript-read.js +137 -0
- package/install/templates/.claude/hooks/soul-memory-check.js +49 -25
- package/install/templates/.claude/hooks/soul-memory-triggers.js +27 -8
- package/install/templates/.claude/hooks/stage-to-byan.js +25 -7
- package/install/templates/.claude/hooks/strict-stop-guard.js +4 -16
- package/install/templates/.claude/rules/benchmark.md +251 -0
- package/install/templates/.claude/rules/byan-agents.md +0 -1
- package/install/templates/.claude/rules/byan-api.md +64 -0
- package/install/templates/.claude/rules/fact-check.md +1 -1
- package/install/templates/.claude/rules/strict-mode.md +10 -9
- package/install/templates/.claude/settings.json +12 -0
- package/install/templates/.claude/skills/byan-benchmark/SKILL.md +159 -0
- package/install/templates/.claude/skills/byan-byan/SKILL.md +73 -12
- package/install/templates/.claude/skills/byan-fact-check/SKILL.md +1 -1
- package/install/templates/.claude/skills/byan-hermes-dispatch/SKILL.md +5 -6
- package/install/templates/.claude/skills/byan-orchestrate/SKILL.md +11 -3
- package/install/templates/.claude/skills/byan-strict/SKILL.md +4 -1
- package/install/templates/.claude/workflows/INDEX.md +2 -1
- package/install/templates/.claude/workflows/byan-benchmark.js +328 -0
- package/install/templates/_byan/_config/agent-manifest.csv +1 -1
- package/install/templates/_byan/_config/autobench.yaml +510 -0
- package/install/templates/_byan/_config/strict-mode.yaml +9 -3
- package/install/templates/_byan/_config/workflow-manifest.csv +1 -0
- package/install/templates/_byan/agent/byan/byan.md +1 -3
- package/install/templates/_byan/agent/byan-flat/byan.md +1 -3
- package/install/templates/_byan/agent/byan-test/byan-test.md +2 -2
- package/install/templates/_byan/agent/byan-test-flat/byan-test.md +2 -2
- package/install/templates/_byan/agent/byan.optimized/byan.optimized.md +2 -2
- package/install/templates/_byan/agent/byan.optimized-v2/byan.optimized-v2.md +2 -2
- package/install/templates/_byan/agent/claude/claude.md +0 -2
- package/install/templates/_byan/agent/codex/codex.md +0 -2
- package/install/templates/_byan/agent/rachid/rachid.md +2 -10
- package/install/templates/_byan/agent/rachid-flat/rachid.md +2 -11
- package/install/templates/_byan/agent/turbo-whisper/turbo-whisper.md +2 -5
- package/install/templates/_byan/agent/turbo-whisper-integration/turbo-whisper-integration.md +5 -13
- package/install/templates/_byan/agent/yanstaller/yanstaller.md +2 -24
- package/install/templates/_byan/config.yaml +0 -1
- package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-sync-rules.js +20 -4
- package/install/templates/_byan/mcp/byan-mcp-server/lib/advisory-autofeed.js +13 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/index-generator.js +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/kanban.js +6 -3
- package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js +205 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-sync.js +415 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/precommit-gate.js +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-activation.js +1 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-mode.js +8 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/sync-rules.js +172 -23
- package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-generator.js +1 -0
- package/install/templates/_byan/mcp/byan-mcp-server/server.js +205 -82
- package/install/templates/_byan/worker/launchers/README.md +4 -24
- package/install/templates/_byan/worker/workers.md +0 -2
- package/install/templates/_byan/workflow/simple/bmb/byan-benchmark/workflow.md +86 -0
- package/install/templates/docs/leantime-integration.md +160 -0
- package/package.json +3 -7
- package/src/byan-v2/context/session-state.js +2 -2
- package/src/byan-v2/generation/mantra-validator.js +3 -3
- package/src/byan-v2/index.js +1 -5
- package/src/byan-v2/integration/voice-integration.js +1 -1
- package/src/byan-v2/orchestrator/generation-state.js +4 -4
- package/src/staging/staging.js +20 -6
- package/install/bin/build-copilot-stubs.js +0 -138
- package/install/lib/platforms/copilot-cli.js +0 -123
- package/install/lib/platforms/vscode.js +0 -51
- package/install/src/byan-v2/context/copilot-context.js +0 -79
- package/install/src/webui/chat/copilot-adapter.js +0 -68
- package/install/templates/.claude/agents/bmad-marc.md +0 -25
- package/install/templates/.claude/skills/byan-marc/SKILL.md +0 -20
- package/install/templates/.github/agents/bmad-agent-bmad-master.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmb-agent-builder.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmb-module-builder.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmb-workflow-builder.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-analyst.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-architect.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-dev.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-pm.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-quick-flow-solo-dev.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-quinn.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-sm.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-tech-writer.md +0 -16
- package/install/templates/.github/agents/bmad-agent-bmm-ux-designer.md +0 -16
- package/install/templates/.github/agents/bmad-agent-byan-test.md +0 -33
- package/install/templates/.github/agents/bmad-agent-byan-v2.md +0 -44
- package/install/templates/.github/agents/bmad-agent-byan.md +0 -1062
- package/install/templates/.github/agents/bmad-agent-carmack.md +0 -14
- package/install/templates/.github/agents/bmad-agent-cis-brainstorming-coach.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-creative-problem-solver.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-design-thinking-coach.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-innovation-strategist.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-presentation-master.md +0 -16
- package/install/templates/.github/agents/bmad-agent-cis-storyteller.md +0 -16
- package/install/templates/.github/agents/bmad-agent-claude.md +0 -49
- package/install/templates/.github/agents/bmad-agent-codex.md +0 -49
- package/install/templates/.github/agents/bmad-agent-drawio.md +0 -45
- package/install/templates/.github/agents/bmad-agent-fact-checker.md +0 -16
- package/install/templates/.github/agents/bmad-agent-forgeron.md +0 -15
- package/install/templates/.github/agents/bmad-agent-jimmy.md +0 -15
- package/install/templates/.github/agents/bmad-agent-marc.md +0 -49
- package/install/templates/.github/agents/bmad-agent-mike.md +0 -15
- package/install/templates/.github/agents/bmad-agent-patnote.md +0 -49
- package/install/templates/.github/agents/bmad-agent-rachid.md +0 -48
- package/install/templates/.github/agents/bmad-agent-skeptic.md +0 -16
- package/install/templates/.github/agents/bmad-agent-tao.md +0 -14
- package/install/templates/.github/agents/bmad-agent-tea-tea.md +0 -16
- package/install/templates/.github/agents/bmad-agent-test-dynamic.md +0 -22
- package/install/templates/.github/agents/bmad-agent-yanstaller-interview.md +0 -50
- package/install/templates/.github/agents/bmad-agent-yanstaller-phase2.md +0 -189
- package/install/templates/.github/agents/bmad-agent-yanstaller.md +0 -350
- package/install/templates/.github/agents/expert-merise-agile.md +0 -178
- package/install/templates/.github/agents/franck.md +0 -379
- package/install/templates/.github/agents/hermes.md +0 -575
- package/install/templates/.github/extensions/byan-staging/extension.mjs +0 -169
- package/install/templates/.github/extensions/byan-staging/package.json +0 -8
- package/install/templates/_byan/agent/marc/marc-soul.md +0 -47
- package/install/templates/_byan/agent/marc/marc-tao.md +0 -77
- package/install/templates/_byan/agent/marc/marc.md +0 -324
- package/install/templates/_byan/agent/marc-flat/marc.md +0 -387
- package/install/templates/_byan/mcp/byan-mcp-server/lib/copilot.js +0 -148
- package/install/templates/_byan/worker/launchers/launch-yanstaller-copilot.md +0 -173
- package/install/templates/workers/cost-optimizer.js +0 -169
- package/src/byan-v2/context/copilot-context.js +0 -79
|
@@ -9,7 +9,6 @@ import yaml from 'js-yaml';
|
|
|
9
9
|
// - .claude/skills/byan-strict/SKILL.md (owned, full-file)
|
|
10
10
|
// - .claude/hooks/lib/strict-config.json (owned, full-file)
|
|
11
11
|
// - AGENTS.md (upsert block, Codex)
|
|
12
|
-
// - .github/copilot-instructions.md (upsert block, Copilot)
|
|
13
12
|
//
|
|
14
13
|
// Owned files are rewritten wholesale (they carry a generated-by header).
|
|
15
14
|
// Shared files get a block upserted between BYAN-STRICT markers, leaving the
|
|
@@ -19,6 +18,10 @@ const BEGIN = 'BYAN-STRICT:BEGIN';
|
|
|
19
18
|
const END = 'BYAN-STRICT:END';
|
|
20
19
|
const DEFAULT_CONFIG_REL = path.join('_byan', '_config', 'strict-mode.yaml');
|
|
21
20
|
|
|
21
|
+
const AUTOBENCH_BEGIN = 'BYAN-AUTOBENCH:BEGIN';
|
|
22
|
+
const AUTOBENCH_END = 'BYAN-AUTOBENCH:END';
|
|
23
|
+
const AUTOBENCH_CONFIG_REL = path.join('_byan', '_config', 'autobench.yaml');
|
|
24
|
+
|
|
22
25
|
export function resolveRoot(projectRoot) {
|
|
23
26
|
return projectRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
24
27
|
}
|
|
@@ -39,6 +42,31 @@ export function loadConfig({ projectRoot, configPath } = {}) {
|
|
|
39
42
|
return cfg;
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
// Loads and validates the auto-benchmark source of truth. Mirrors loadConfig:
|
|
46
|
+
// throws on a missing file, a non-object parse, an empty mantras list, or a
|
|
47
|
+
// missing doctrine brick — so a malformed YAML fails the generator loudly
|
|
48
|
+
// rather than emitting a half-rendered pointer block.
|
|
49
|
+
export function loadAutobenchConfig({ projectRoot, configPath } = {}) {
|
|
50
|
+
const root = resolveRoot(projectRoot);
|
|
51
|
+
const file = configPath || path.join(root, AUTOBENCH_CONFIG_REL);
|
|
52
|
+
if (!fs.existsSync(file)) {
|
|
53
|
+
throw new Error(`autobench config not found at ${file}`);
|
|
54
|
+
}
|
|
55
|
+
const cfg = yaml.load(fs.readFileSync(file, 'utf8'));
|
|
56
|
+
if (!cfg || typeof cfg !== 'object') {
|
|
57
|
+
throw new Error(`autobench config at ${file} did not parse to an object`);
|
|
58
|
+
}
|
|
59
|
+
if (!Array.isArray(cfg.mantras) || cfg.mantras.length === 0) {
|
|
60
|
+
throw new Error('autobench config must define a non-empty mantras list');
|
|
61
|
+
}
|
|
62
|
+
for (const brick of ['trigger', 'scaler', 'format']) {
|
|
63
|
+
if (!cfg[brick] || typeof cfg[brick] !== 'object') {
|
|
64
|
+
throw new Error(`autobench config must define the '${brick}' brick`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return cfg;
|
|
68
|
+
}
|
|
69
|
+
|
|
42
70
|
// ---------------------------------------------------------------------------
|
|
43
71
|
// Renderers — pure functions config -> string.
|
|
44
72
|
// ---------------------------------------------------------------------------
|
|
@@ -46,6 +74,9 @@ export function loadConfig({ projectRoot, configPath } = {}) {
|
|
|
46
74
|
const GENERATED_NOTE =
|
|
47
75
|
'Generated by byan-sync-rules from _byan/_config/strict-mode.yaml. Do not hand-edit.';
|
|
48
76
|
|
|
77
|
+
const AUTOBENCH_GENERATED_NOTE =
|
|
78
|
+
'Generated by byan-sync-rules from _byan/_config/autobench.yaml. Do not hand-edit.';
|
|
79
|
+
|
|
49
80
|
export function renderStrictConfig(cfg) {
|
|
50
81
|
return {
|
|
51
82
|
_generated_by: 'byan-sync-rules',
|
|
@@ -125,7 +156,10 @@ complete. Downgrading the scope is the failure this mode exists to prevent.
|
|
|
125
156
|
|
|
126
157
|
1. **Lock the scope** with \`byan_strict_lock_scope\` before building. Provide a
|
|
127
158
|
verbatim restatement of the request and testable \`acceptanceCriteria\`. The
|
|
128
|
-
locked scope is the contract.
|
|
159
|
+
locked scope is the contract. When one technical domain clearly dominates the
|
|
160
|
+
task, also pass \`domain\` (e.g. security, performance, javascript) — a
|
|
161
|
+
successful completion then feeds one VALIDATED tick to the ELO loop. Explicit
|
|
162
|
+
only; omit when no single domain is clear.
|
|
129
163
|
2. **Build the full scope.** Do not substitute an MVP, a stub, or a simplified
|
|
130
164
|
version. If a part cannot be done, surface it as a gap — do not cut silently.
|
|
131
165
|
3. **Self-verify at least ${cfg.self_verify.min_passes} times** with
|
|
@@ -160,19 +194,84 @@ Hard mantras:
|
|
|
160
194
|
${mantraLines(cfg)}`;
|
|
161
195
|
}
|
|
162
196
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
197
|
+
// Maps the enriched cfg.hooks section to the EXACT runtime shape read by
|
|
198
|
+
// autobench-stop-guard.js. Every key and every {source, flags} pair structure
|
|
199
|
+
// must stay in sync with what compileRegex / hasChoiceLanguage / hasMarker /
|
|
200
|
+
// hasNeverListed / readMarkerFields / escapeHatchActive / ledgerPath consume.
|
|
201
|
+
//
|
|
202
|
+
// This is the function that closes the single-source-of-truth gap: before it
|
|
203
|
+
// existed, autobench-config.json was hand-authored and the YAML toggle
|
|
204
|
+
// (escape_hatch.disabled) was never propagated to the runtime file.
|
|
205
|
+
export function renderAutobenchConfig(cfg) {
|
|
206
|
+
const h = cfg.hooks;
|
|
207
|
+
const mp = h.marker_patterns;
|
|
208
|
+
const mf = h.marker_fields;
|
|
209
|
+
const eh = h.escape_hatch;
|
|
172
210
|
|
|
173
|
-
|
|
211
|
+
return {
|
|
212
|
+
_generated_by: 'byan-sync-rules',
|
|
213
|
+
_note:
|
|
214
|
+
'Runtime subset read by autobench-stop-guard.js. Edit _byan/_config/autobench.yaml and regenerate; do not hand-edit. Regexes are {source, flags} pairs reconstructed into RegExp at load time.',
|
|
215
|
+
version: cfg.version,
|
|
216
|
+
marker_patterns: {
|
|
217
|
+
any: { source: mp.any.source, flags: mp.any.flags },
|
|
218
|
+
done: { source: mp.done.source, flags: mp.done.flags },
|
|
219
|
+
skip: { source: mp.skip.source, flags: mp.skip.flags },
|
|
220
|
+
},
|
|
221
|
+
marker_fields: {
|
|
222
|
+
g1: { source: mf.g1.source, flags: mf.g1.flags },
|
|
223
|
+
g2: { source: mf.g2.source, flags: mf.g2.flags },
|
|
224
|
+
scope: { source: mf.scope.source, flags: mf.scope.flags },
|
|
225
|
+
},
|
|
226
|
+
never_list: h.never_list.map((entry) => ({ source: entry.source, flags: entry.flags })),
|
|
227
|
+
choice_language: h.choice_language.map((entry) => {
|
|
228
|
+
const out = { source: entry.source, flags: entry.flags };
|
|
229
|
+
// Preserve optional threshold fields only when present; omitting them
|
|
230
|
+
// keeps the config lean and matches the runtime's typeof checks.
|
|
231
|
+
if (typeof entry.min_matches === 'number') out.min_matches = entry.min_matches;
|
|
232
|
+
if (typeof entry.requires_candidates === 'number') out.requires_candidates = entry.requires_candidates;
|
|
233
|
+
return out;
|
|
234
|
+
}),
|
|
235
|
+
candidate_token: { source: h.candidate_token.source, flags: h.candidate_token.flags },
|
|
236
|
+
escape_hatch: {
|
|
237
|
+
// session_flag is read by autobench-runtime.js but not stored in
|
|
238
|
+
// config.json (the runtime hardcodes the path). Carry only the two
|
|
239
|
+
// fields the runtime actually reads from config.json.
|
|
240
|
+
session_flag: eh.session_flag,
|
|
241
|
+
disabled: eh.disabled,
|
|
242
|
+
},
|
|
243
|
+
enforcement: {
|
|
244
|
+
// Disarmed-by-default (approach C): the Stop hook observes and ledgers but
|
|
245
|
+
// does not block until armed. Arming is config-only (armed: true in the
|
|
246
|
+
// YAML); there is no loose flag file, so a stray file cannot silently arm a
|
|
247
|
+
// machine. Defaulting armed to false keeps an older source inert by default.
|
|
248
|
+
armed: Boolean(h.enforcement && h.enforcement.armed === true),
|
|
249
|
+
},
|
|
250
|
+
ledger: {
|
|
251
|
+
path: h.ledger_path,
|
|
252
|
+
},
|
|
253
|
+
banners: {
|
|
254
|
+
stop_block: h.stop_block,
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
}
|
|
174
258
|
|
|
175
|
-
|
|
259
|
+
// The lean auto-benchmark pointer block. One shared block for both
|
|
260
|
+
// platform files (CLAUDE.md / AGENTS.md): names the
|
|
261
|
+
// feature, states the marker one-liner the agent must emit, and points to the
|
|
262
|
+
// full doctrine. Kept short on purpose — CLAUDE.md stays lean via pointers, and
|
|
263
|
+
// the full rule lives in .claude/rules/benchmark.md (owned, authored elsewhere).
|
|
264
|
+
export function renderAutobenchPointerBlock(cfg) {
|
|
265
|
+
const name = (cfg.name || 'BYAN Auto-Benchmark').trim();
|
|
266
|
+
return `## ${name}
|
|
267
|
+
|
|
268
|
+
Before asking the user to choose between options, benchmark the fork: render
|
|
269
|
+
ONE compact table (Option | <= 4 criteria | Niv + a best-first reco line) when
|
|
270
|
+
both gates hold (>= 2 non-substitutable options diverging on >= 1 weighted
|
|
271
|
+
criterion). Emit the marker verbatim before the table:
|
|
272
|
+
\`<!-- BYAN-BENCH:done g1=<#options> g2=<#divergent-criteria> scope=<internal|external> conf=<assertive|lean> -->\`.
|
|
273
|
+
A confirm, a destructive prompt, or an obvious default is not a fork — emit
|
|
274
|
+
\`<!-- BYAN-BENCH:skip reason=.. -->\` instead. Full doctrine: see @.claude/rules/benchmark.md`;
|
|
176
275
|
}
|
|
177
276
|
|
|
178
277
|
// ---------------------------------------------------------------------------
|
|
@@ -191,11 +290,29 @@ function writeIfChanged(filePath, content) {
|
|
|
191
290
|
return existing === null ? 'created' : 'updated';
|
|
192
291
|
}
|
|
193
292
|
|
|
293
|
+
// Escapes a marker string for safe embedding in a RegExp source. Markers are
|
|
294
|
+
// authored as plain identifiers today, but a literal-safe regex keeps the
|
|
295
|
+
// generalized signature robust if a future marker carries a regex metacharacter.
|
|
296
|
+
function escapeRegex(s) {
|
|
297
|
+
return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
298
|
+
}
|
|
299
|
+
|
|
194
300
|
// Insert or replace a block delimited by HTML-comment markers. Preserves
|
|
195
301
|
// everything outside the markers. If the file does not exist, creates it with
|
|
196
302
|
// just the block.
|
|
197
|
-
|
|
198
|
-
|
|
303
|
+
//
|
|
304
|
+
// markers defaults to the module-level STRICT pair so every existing strict
|
|
305
|
+
// callsite keeps working unchanged. Passing a distinct pair (e.g. the
|
|
306
|
+
// AUTOBENCH markers) scopes the replace-regex to that pair only, so a STRICT
|
|
307
|
+
// block and an AUTOBENCH block coexist in one file without clobbering each
|
|
308
|
+
// other.
|
|
309
|
+
export function upsertBlock({
|
|
310
|
+
filePath,
|
|
311
|
+
block,
|
|
312
|
+
markers = { begin: BEGIN, end: END },
|
|
313
|
+
note = GENERATED_NOTE,
|
|
314
|
+
}) {
|
|
315
|
+
const wrapped = `<!-- ${markers.begin} (${note}) -->\n${block}\n<!-- ${markers.end} -->`;
|
|
199
316
|
const existing = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
|
|
200
317
|
|
|
201
318
|
if (existing === null) {
|
|
@@ -205,7 +322,7 @@ export function upsertBlock({ filePath, block }) {
|
|
|
205
322
|
}
|
|
206
323
|
|
|
207
324
|
const re = new RegExp(
|
|
208
|
-
`<!-- ${
|
|
325
|
+
`<!-- ${escapeRegex(markers.begin)}[\\s\\S]*?${escapeRegex(markers.end)} -->`,
|
|
209
326
|
'm'
|
|
210
327
|
);
|
|
211
328
|
let next;
|
|
@@ -216,7 +333,7 @@ export function upsertBlock({ filePath, block }) {
|
|
|
216
333
|
}
|
|
217
334
|
if (next === existing) return 'unchanged';
|
|
218
335
|
fs.writeFileSync(filePath, next);
|
|
219
|
-
return existing.includes(
|
|
336
|
+
return existing.includes(markers.begin) ? 'updated' : 'appended';
|
|
220
337
|
}
|
|
221
338
|
|
|
222
339
|
// ---------------------------------------------------------------------------
|
|
@@ -241,12 +358,6 @@ export function syncRules({ projectRoot, configPath } = {}) {
|
|
|
241
358
|
const agentsPath = path.join(root, 'AGENTS.md');
|
|
242
359
|
report['AGENTS.md'] = upsertBlock({ filePath: agentsPath, block: renderAgentsBlock(cfg) });
|
|
243
360
|
|
|
244
|
-
const copilotPath = path.join(root, '.github', 'copilot-instructions.md');
|
|
245
|
-
report['.github/copilot-instructions.md'] = upsertBlock({
|
|
246
|
-
filePath: copilotPath,
|
|
247
|
-
block: renderCopilotBlock(cfg),
|
|
248
|
-
});
|
|
249
|
-
|
|
250
361
|
const mantrasPath = path.join(root, 'src', 'byan-v2', 'data', 'strict-mantras.json');
|
|
251
362
|
if (fs.existsSync(path.dirname(mantrasPath))) {
|
|
252
363
|
report['src/byan-v2/data/strict-mantras.json'] = writeIfChanged(
|
|
@@ -258,4 +369,42 @@ export function syncRules({ projectRoot, configPath } = {}) {
|
|
|
258
369
|
return report;
|
|
259
370
|
}
|
|
260
371
|
|
|
372
|
+
// Auto-benchmark orchestrator. Kept SEPARATE from syncRules so the strict
|
|
373
|
+
// generator stays untouched and independently testable. Upserts the lean
|
|
374
|
+
// pointer block into the three platform files and writes the runtime config
|
|
375
|
+
// that the Stop hook reads. The config write closes the single-source-of-truth
|
|
376
|
+
// gap: escape_hatch.disabled toggled in the YAML now reaches the runtime file.
|
|
377
|
+
// Returns a {file: action} report like syncRules.
|
|
378
|
+
export function syncAutobench({ projectRoot, configPath } = {}) {
|
|
379
|
+
const root = resolveRoot(projectRoot);
|
|
380
|
+
const cfg = loadAutobenchConfig({ projectRoot: root, configPath });
|
|
381
|
+
|
|
382
|
+
const report = {};
|
|
383
|
+
const block = renderAutobenchPointerBlock(cfg);
|
|
384
|
+
const markers = { begin: AUTOBENCH_BEGIN, end: AUTOBENCH_END };
|
|
385
|
+
|
|
386
|
+
// CLAUDE.md uses the lean pointer convention (.claude/CLAUDE.md holds the
|
|
387
|
+
// other rule pointers: strict, fact-check, ELO). AGENTS.md (Codex) is the
|
|
388
|
+
// other cross-platform mechanism target.
|
|
389
|
+
const targets = {
|
|
390
|
+
'.claude/CLAUDE.md': path.join(root, '.claude', 'CLAUDE.md'),
|
|
391
|
+
'AGENTS.md': path.join(root, 'AGENTS.md'),
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
for (const [rel, filePath] of Object.entries(targets)) {
|
|
395
|
+
report[rel] = upsertBlock({ filePath, block, markers, note: AUTOBENCH_GENERATED_NOTE });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Write the runtime config that autobench-stop-guard.js reads. Mirrors the
|
|
399
|
+
// strict pattern (syncRules writes strict-config.json from strict-mode.yaml).
|
|
400
|
+
const autobenchCfgPath = path.join(root, '.claude', 'hooks', 'lib', 'autobench-config.json');
|
|
401
|
+
report['.claude/hooks/lib/autobench-config.json'] = writeIfChanged(
|
|
402
|
+
autobenchCfgPath,
|
|
403
|
+
JSON.stringify(renderAutobenchConfig(cfg), null, 2) + '\n'
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
return report;
|
|
407
|
+
}
|
|
408
|
+
|
|
261
409
|
export const MARKERS = { BEGIN, END };
|
|
410
|
+
export const AUTOBENCH_MARKERS = { BEGIN: AUTOBENCH_BEGIN, END: AUTOBENCH_END };
|
|
@@ -11,9 +11,8 @@ import {
|
|
|
11
11
|
import { dispatch } from './lib/dispatch.js';
|
|
12
12
|
import { harvest as harvestInsights, renderDigest as renderInsightDigest } from './lib/insight-harvest.js';
|
|
13
13
|
import { appendOutcome } from './lib/outcome-buffer.js';
|
|
14
|
-
import { validateForLog } from './lib/advisory-autofeed.js';
|
|
14
|
+
import { validateForLog, eloOutcomeForStrictComplete } from './lib/advisory-autofeed.js';
|
|
15
15
|
import { readSoul, appendSoulMemory } from './lib/soul.js';
|
|
16
|
-
import { listSessions, readSessionEvents, searchSessions } from './lib/copilot.js';
|
|
17
16
|
import {
|
|
18
17
|
start as fdStart,
|
|
19
18
|
status as fdStatus,
|
|
@@ -72,6 +71,17 @@ import {
|
|
|
72
71
|
syncEnabled as strictSyncEnabled,
|
|
73
72
|
resolveProjectId as strictResolveProjectId,
|
|
74
73
|
} from './lib/strict-sync.js';
|
|
74
|
+
import {
|
|
75
|
+
syncEnabled as leantimeEnabled,
|
|
76
|
+
rpc as leantimeRpc,
|
|
77
|
+
ensureProject as leantimeEnsureProject,
|
|
78
|
+
createTask as leantimeCreateTask,
|
|
79
|
+
moveTask as leantimeMoveTask,
|
|
80
|
+
assignTask as leantimeAssignTask,
|
|
81
|
+
getTask as leantimeGetTask,
|
|
82
|
+
getBoard as leantimeGetBoard,
|
|
83
|
+
METHODS as LEANTIME_METHODS,
|
|
84
|
+
} from './lib/leantime-sync.js';
|
|
75
85
|
|
|
76
86
|
// Compact view of a best-effort strict-sync result for tool responses.
|
|
77
87
|
function syncResult(sync) {
|
|
@@ -113,6 +123,14 @@ function requireToken() {
|
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
|
|
126
|
+
// Leantime uses its OWN env pair (LEANTIME_API_URL/LEANTIME_API_TOKEN), kept
|
|
127
|
+
// distinct from BYAN_API_URL so the two backends never get crossed.
|
|
128
|
+
function requireLeantime() {
|
|
129
|
+
if (!process.env.LEANTIME_API_URL || !process.env.LEANTIME_API_TOKEN) {
|
|
130
|
+
throw new Error('LEANTIME_API_URL + LEANTIME_API_TOKEN env vars are required for byan_leantime_* tools.');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
116
134
|
async function apiRequest(path, options = {}) {
|
|
117
135
|
const url = `${BYAN_API_URL}${path}`;
|
|
118
136
|
const headers = {
|
|
@@ -407,39 +425,6 @@ const tools = [
|
|
|
407
425
|
additionalProperties: false,
|
|
408
426
|
},
|
|
409
427
|
},
|
|
410
|
-
{
|
|
411
|
-
name: 'byan_copilot_sessions',
|
|
412
|
-
description:
|
|
413
|
-
'List GitHub Copilot CLI sessions stored locally at ~/.copilot/session-state/. Returns sessionId, start/end time, cwd, branch, agent name, message and tool call counts. Sorted most-recent-first. Use to discover past Copilot CLI conversations for reference or import.',
|
|
414
|
-
inputSchema: {
|
|
415
|
-
type: 'object',
|
|
416
|
-
properties: {
|
|
417
|
-
limit: { type: 'number', description: 'Max sessions to return (default 20).' },
|
|
418
|
-
sinceIso: { type: 'string', description: 'ISO timestamp filter — only sessions started after this.' },
|
|
419
|
-
cwdFilter: { type: 'string', description: 'Substring match on session cwd (e.g. "byan_web").' },
|
|
420
|
-
},
|
|
421
|
-
additionalProperties: false,
|
|
422
|
-
},
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
name: 'byan_copilot_session_events',
|
|
426
|
-
description:
|
|
427
|
-
'Read events of a specific Copilot CLI session (events.jsonl). Optionally filter by event type (user.message, assistant.message, tool.execution_start, etc.). Useful to inspect the flow of a past session.',
|
|
428
|
-
inputSchema: {
|
|
429
|
-
type: 'object',
|
|
430
|
-
properties: {
|
|
431
|
-
sessionId: { type: 'string', description: 'Session UUID from byan_copilot_sessions.' },
|
|
432
|
-
types: {
|
|
433
|
-
type: 'array',
|
|
434
|
-
items: { type: 'string' },
|
|
435
|
-
description: 'Filter to these event types only.',
|
|
436
|
-
},
|
|
437
|
-
limit: { type: 'number', description: 'Max events (default 200).' },
|
|
438
|
-
},
|
|
439
|
-
required: ['sessionId'],
|
|
440
|
-
additionalProperties: false,
|
|
441
|
-
},
|
|
442
|
-
},
|
|
443
428
|
{
|
|
444
429
|
name: 'byan_fd_start',
|
|
445
430
|
description:
|
|
@@ -597,6 +582,10 @@ const tools = [
|
|
|
597
582
|
items: { type: 'string' },
|
|
598
583
|
description: 'Glob patterns of paths the agent may modify.',
|
|
599
584
|
},
|
|
585
|
+
domain: {
|
|
586
|
+
type: 'string',
|
|
587
|
+
description: 'Optional explicit ELO domain (e.g. security, performance, javascript). When set, a successful byan_strict_complete feeds one VALIDATED outcome to the ELO learning loop. Recorded verbatim (your explicit input, never inferred from text); omit to feed nothing.',
|
|
588
|
+
},
|
|
600
589
|
force: { type: 'boolean', description: 'Relock with different scope.' },
|
|
601
590
|
projectId: {
|
|
602
591
|
type: 'string',
|
|
@@ -658,7 +647,7 @@ const tools = [
|
|
|
658
647
|
{
|
|
659
648
|
name: 'byan_strict_suggest',
|
|
660
649
|
description:
|
|
661
|
-
'Check whether a piece of text (user request, feature name) signals a production-grade deliverable that should be built under strict mode. Reads activation keywords from _byan/_config/strict-mode.yaml. Returns { suggested, matched, message }. Use on any platform (Codex
|
|
650
|
+
'Check whether a piece of text (user request, feature name) signals a production-grade deliverable that should be built under strict mode. Reads activation keywords from _byan/_config/strict-mode.yaml. Returns { suggested, matched, message }. Use on any platform (Codex has no in-session hook) to decide whether to lock strict mode.',
|
|
662
651
|
inputSchema: {
|
|
663
652
|
type: 'object',
|
|
664
653
|
properties: {
|
|
@@ -848,25 +837,6 @@ const tools = [
|
|
|
848
837
|
additionalProperties: false,
|
|
849
838
|
},
|
|
850
839
|
},
|
|
851
|
-
{
|
|
852
|
-
name: 'byan_copilot_search',
|
|
853
|
-
description:
|
|
854
|
-
'Full-text search across all Copilot CLI sessions. Finds messages (user + assistant by default) containing the query string. Returns sessionId + timestamp + excerpt. Use to recall past discussions without knowing which session they were in.',
|
|
855
|
-
inputSchema: {
|
|
856
|
-
type: 'object',
|
|
857
|
-
properties: {
|
|
858
|
-
query: { type: 'string', description: 'Substring to search for (case-insensitive).' },
|
|
859
|
-
types: {
|
|
860
|
-
type: 'array',
|
|
861
|
-
items: { type: 'string' },
|
|
862
|
-
description: 'Event types to scan (default: user.message, assistant.message).',
|
|
863
|
-
},
|
|
864
|
-
limit: { type: 'number', description: 'Max matches (default 50).' },
|
|
865
|
-
},
|
|
866
|
-
required: ['query'],
|
|
867
|
-
additionalProperties: false,
|
|
868
|
-
},
|
|
869
|
-
},
|
|
870
840
|
|
|
871
841
|
// ─── Projects ─────────────────────────────────────────────────────────
|
|
872
842
|
{
|
|
@@ -1198,6 +1168,101 @@ const tools = [
|
|
|
1198
1168
|
additionalProperties: false,
|
|
1199
1169
|
},
|
|
1200
1170
|
},
|
|
1171
|
+
|
|
1172
|
+
// ─── Leantime (project-management mirror) ─────────────────────────────
|
|
1173
|
+
// Client-side automation of the self-hosted Leantime JSON-RPC API. Used by
|
|
1174
|
+
// the FD workflow to create a project + a task per feature and move task
|
|
1175
|
+
// status across phases. Needs LEANTIME_API_URL + LEANTIME_API_TOKEN.
|
|
1176
|
+
{
|
|
1177
|
+
name: 'byan_leantime_ping',
|
|
1178
|
+
description:
|
|
1179
|
+
'Healthcheck the Leantime integration: reports api_url, token presence, and (if configured) whether the JSON-RPC API is reachable. Surfaces the wrong-host guard (HTML instead of JSON). No required args.',
|
|
1180
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
name: 'byan_leantime_project_ensure',
|
|
1184
|
+
description:
|
|
1185
|
+
'Idempotent create-or-fetch of a Leantime project from the FD project_context. Matches an existing project by name first (no duplicate on FD re-run). Returns { id, created }. Requires LEANTIME_API_*.',
|
|
1186
|
+
inputSchema: {
|
|
1187
|
+
type: 'object',
|
|
1188
|
+
properties: {
|
|
1189
|
+
name: { type: 'string', description: 'Project name (defaults to slug).' },
|
|
1190
|
+
slug: { type: 'string', description: 'Project slug (fallback name).' },
|
|
1191
|
+
clientId: { type: 'number', description: 'Owning Leantime client id. Resolved if omitted.' },
|
|
1192
|
+
details: { type: 'string', description: 'Optional project description.' },
|
|
1193
|
+
},
|
|
1194
|
+
additionalProperties: false,
|
|
1195
|
+
},
|
|
1196
|
+
},
|
|
1197
|
+
{
|
|
1198
|
+
name: 'byan_leantime_task_create',
|
|
1199
|
+
description:
|
|
1200
|
+
'Create one Leantime task (ticket) from an FD backlog item. Returns the new task id to store back in fd-state (caller owns idempotency: create only if the item has no leantime_task_id). Requires LEANTIME_API_*.',
|
|
1201
|
+
inputSchema: {
|
|
1202
|
+
type: 'object',
|
|
1203
|
+
properties: {
|
|
1204
|
+
projectId: { type: 'number', description: 'Leantime project id.' },
|
|
1205
|
+
headline: { type: 'string', description: 'Task title.' },
|
|
1206
|
+
description: { type: 'string' },
|
|
1207
|
+
status: { type: 'number', description: 'Leantime status id (optional).' },
|
|
1208
|
+
priority: { type: 'number' },
|
|
1209
|
+
editorId: { type: 'number', description: 'Assignee/editor user id.' },
|
|
1210
|
+
tags: { type: 'string' },
|
|
1211
|
+
type: { type: 'string', description: "Ticket type, default 'task'." },
|
|
1212
|
+
},
|
|
1213
|
+
required: ['projectId', 'headline'],
|
|
1214
|
+
additionalProperties: false,
|
|
1215
|
+
},
|
|
1216
|
+
},
|
|
1217
|
+
{
|
|
1218
|
+
name: 'byan_leantime_task_move',
|
|
1219
|
+
description:
|
|
1220
|
+
'Move a Leantime task to a lifecycle column (todo|doing|blocked|review|done). Resolves the column to the project status id, then updates the ticket. Requires LEANTIME_API_*.',
|
|
1221
|
+
inputSchema: {
|
|
1222
|
+
type: 'object',
|
|
1223
|
+
properties: {
|
|
1224
|
+
taskId: { type: 'number', description: 'Leantime ticket id.' },
|
|
1225
|
+
projectId: { type: 'number', description: 'Project id (for status resolution).' },
|
|
1226
|
+
column: { type: 'string', enum: ['todo', 'doing', 'blocked', 'review', 'done'] },
|
|
1227
|
+
status: { type: 'number', description: 'Explicit status id (bypasses column resolution).' },
|
|
1228
|
+
},
|
|
1229
|
+
required: ['taskId'],
|
|
1230
|
+
additionalProperties: false,
|
|
1231
|
+
},
|
|
1232
|
+
},
|
|
1233
|
+
{
|
|
1234
|
+
name: 'byan_leantime_task_assign',
|
|
1235
|
+
description: 'Set the assignee/editor of a Leantime task. Requires LEANTIME_API_*.',
|
|
1236
|
+
inputSchema: {
|
|
1237
|
+
type: 'object',
|
|
1238
|
+
properties: {
|
|
1239
|
+
taskId: { type: 'number', description: 'Leantime ticket id.' },
|
|
1240
|
+
editorId: { type: 'number', description: 'Assignee/editor user id.' },
|
|
1241
|
+
},
|
|
1242
|
+
required: ['taskId', 'editorId'],
|
|
1243
|
+
additionalProperties: false,
|
|
1244
|
+
},
|
|
1245
|
+
},
|
|
1246
|
+
{
|
|
1247
|
+
name: 'byan_leantime_task_get',
|
|
1248
|
+
description: 'Fetch a single Leantime task by id. Requires LEANTIME_API_*.',
|
|
1249
|
+
inputSchema: {
|
|
1250
|
+
type: 'object',
|
|
1251
|
+
properties: { taskId: { type: 'number', description: 'Leantime ticket id.' } },
|
|
1252
|
+
required: ['taskId'],
|
|
1253
|
+
additionalProperties: false,
|
|
1254
|
+
},
|
|
1255
|
+
},
|
|
1256
|
+
{
|
|
1257
|
+
name: 'byan_leantime_board_get',
|
|
1258
|
+
description: "List a Leantime project's tasks grouped by lifecycle column. Requires LEANTIME_API_*.",
|
|
1259
|
+
inputSchema: {
|
|
1260
|
+
type: 'object',
|
|
1261
|
+
properties: { projectId: { type: 'number', description: 'Leantime project id.' } },
|
|
1262
|
+
required: ['projectId'],
|
|
1263
|
+
additionalProperties: false,
|
|
1264
|
+
},
|
|
1265
|
+
},
|
|
1201
1266
|
];
|
|
1202
1267
|
|
|
1203
1268
|
const server = new Server(
|
|
@@ -1340,33 +1405,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1340
1405
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1341
1406
|
}
|
|
1342
1407
|
|
|
1343
|
-
if (name === 'byan_copilot_sessions') {
|
|
1344
|
-
const result = listSessions({
|
|
1345
|
-
limit: args.limit,
|
|
1346
|
-
sinceIso: args.sinceIso,
|
|
1347
|
-
cwdFilter: args.cwdFilter,
|
|
1348
|
-
});
|
|
1349
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
if (name === 'byan_copilot_session_events') {
|
|
1353
|
-
const result = readSessionEvents({
|
|
1354
|
-
sessionId: args.sessionId,
|
|
1355
|
-
types: args.types,
|
|
1356
|
-
limit: args.limit,
|
|
1357
|
-
});
|
|
1358
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
if (name === 'byan_copilot_search') {
|
|
1362
|
-
const result = searchSessions({
|
|
1363
|
-
query: args.query,
|
|
1364
|
-
types: args.types,
|
|
1365
|
-
limit: args.limit,
|
|
1366
|
-
});
|
|
1367
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
1408
|
if (name === 'byan_fd_start') {
|
|
1371
1409
|
const state = fdStart({ featureName: args.featureName, force: args.force, strict: args.strict });
|
|
1372
1410
|
return { content: [{ type: 'text', text: JSON.stringify(state, null, 2) }] };
|
|
@@ -1446,6 +1484,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1446
1484
|
scopeText: args.scopeText,
|
|
1447
1485
|
acceptanceCriteria: args.acceptanceCriteria,
|
|
1448
1486
|
allowedPaths: args.allowedPaths,
|
|
1487
|
+
domain: args.domain,
|
|
1449
1488
|
force: args.force,
|
|
1450
1489
|
});
|
|
1451
1490
|
const st = strictGetStatus();
|
|
@@ -1490,6 +1529,18 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1490
1529
|
if (name === 'byan_strict_complete') {
|
|
1491
1530
|
const r = strictComplete();
|
|
1492
1531
|
const st = strictGetStatus();
|
|
1532
|
+
// C3 learning loop: a completed strict session with an EXPLICIT ELO domain
|
|
1533
|
+
// is a VALIDATED outcome. eloOutcomeForStrictComplete builds the line (the
|
|
1534
|
+
// SAME helper the test exercises, so handler and test cannot drift); we
|
|
1535
|
+
// append it to the buffer drain-advisory drains. The domain is the user's
|
|
1536
|
+
// explicit lock_scope input, never inferred. Best-effort: a feed failure
|
|
1537
|
+
// must not break completion.
|
|
1538
|
+
try {
|
|
1539
|
+
const eloLine = eloOutcomeForStrictComplete(r);
|
|
1540
|
+
if (eloLine) appendOutcome(eloLine, { rootDir: process.env.CLAUDE_PROJECT_DIR || process.cwd() });
|
|
1541
|
+
} catch {
|
|
1542
|
+
// the learning feed must not break completion.
|
|
1543
|
+
}
|
|
1493
1544
|
const sync = await strictPushComplete({
|
|
1494
1545
|
sessionId: st.strict_session_id,
|
|
1495
1546
|
auditToken: r.audit_token,
|
|
@@ -1846,6 +1897,78 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1846
1897
|
return { content: [{ type: 'text', text: JSON.stringify(instructions, null, 2) }] };
|
|
1847
1898
|
}
|
|
1848
1899
|
|
|
1900
|
+
// ─── Leantime tools ───────────────────────────────────────────────
|
|
1901
|
+
if (name === 'byan_leantime_ping') {
|
|
1902
|
+
const status = {
|
|
1903
|
+
api_url: process.env.LEANTIME_API_URL || null,
|
|
1904
|
+
token_configured: Boolean(process.env.LEANTIME_API_TOKEN),
|
|
1905
|
+
assign_user_configured: Boolean(process.env.LEANTIME_ASSIGN_USER_ID),
|
|
1906
|
+
enabled: leantimeEnabled(),
|
|
1907
|
+
};
|
|
1908
|
+
if (status.enabled) {
|
|
1909
|
+
const probe = await leantimeRpc(LEANTIME_METHODS.getAllProjects, {});
|
|
1910
|
+
status.reachable = probe.ok;
|
|
1911
|
+
if (!probe.ok) status.reason = probe.reason;
|
|
1912
|
+
if (probe.hint) status.hint = probe.hint;
|
|
1913
|
+
}
|
|
1914
|
+
return { content: [{ type: 'text', text: JSON.stringify(status, null, 2) }] };
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
if (name === 'byan_leantime_project_ensure') {
|
|
1918
|
+
requireLeantime();
|
|
1919
|
+
const r = await leantimeEnsureProject({
|
|
1920
|
+
name: args.name,
|
|
1921
|
+
slug: args.slug,
|
|
1922
|
+
clientId: args.clientId,
|
|
1923
|
+
details: args.details,
|
|
1924
|
+
});
|
|
1925
|
+
return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
if (name === 'byan_leantime_task_create') {
|
|
1929
|
+
requireLeantime();
|
|
1930
|
+
const r = await leantimeCreateTask({
|
|
1931
|
+
projectId: args.projectId,
|
|
1932
|
+
headline: args.headline,
|
|
1933
|
+
description: args.description,
|
|
1934
|
+
status: args.status,
|
|
1935
|
+
priority: args.priority,
|
|
1936
|
+
editorId: args.editorId,
|
|
1937
|
+
tags: args.tags,
|
|
1938
|
+
...(args.type !== undefined ? { type: args.type } : {}),
|
|
1939
|
+
});
|
|
1940
|
+
return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
if (name === 'byan_leantime_task_move') {
|
|
1944
|
+
requireLeantime();
|
|
1945
|
+
const r = await leantimeMoveTask({
|
|
1946
|
+
taskId: args.taskId,
|
|
1947
|
+
projectId: args.projectId,
|
|
1948
|
+
column: args.column,
|
|
1949
|
+
status: args.status,
|
|
1950
|
+
});
|
|
1951
|
+
return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
if (name === 'byan_leantime_task_assign') {
|
|
1955
|
+
requireLeantime();
|
|
1956
|
+
const r = await leantimeAssignTask({ taskId: args.taskId, editorId: args.editorId });
|
|
1957
|
+
return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
if (name === 'byan_leantime_task_get') {
|
|
1961
|
+
requireLeantime();
|
|
1962
|
+
const r = await leantimeGetTask({ taskId: args.taskId });
|
|
1963
|
+
return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
if (name === 'byan_leantime_board_get') {
|
|
1967
|
+
requireLeantime();
|
|
1968
|
+
const r = await leantimeGetBoard({ projectId: args.projectId });
|
|
1969
|
+
return { content: [{ type: 'text', text: JSON.stringify(r, null, 2) }] };
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1849
1972
|
throw new Error(`Unknown tool: ${name}`);
|
|
1850
1973
|
} catch (err) {
|
|
1851
1974
|
return {
|