create-byan-agent 2.35.0 → 2.39.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 (35) hide show
  1. package/CHANGELOG.md +203 -0
  2. package/install/bin/create-byan-agent-v2.js +15 -0
  3. package/install/lib/claude-native-setup.js +12 -38
  4. package/install/lib/platforms/claude-code.js +28 -19
  5. package/install/package.json +1 -1
  6. package/install/packages/platform-config/lib/mcp-config.js +71 -5
  7. package/install/templates/.claude/CLAUDE.md +1 -0
  8. package/install/templates/.claude/hooks/inject-delivery-default.js +46 -0
  9. package/install/templates/.claude/hooks/leantime-fd-sync.js +12 -1
  10. package/install/templates/.claude/hooks/lib/delivery-contract.js +143 -0
  11. package/install/templates/.claude/hooks/lib/punt-detect.js +143 -0
  12. package/install/templates/.claude/hooks/lib/strict-config.json +14 -0
  13. package/install/templates/.claude/hooks/punt-guard.js +126 -0
  14. package/install/templates/.claude/hooks/strict-stop-guard.js +29 -6
  15. package/install/templates/.claude/settings.json +8 -0
  16. package/install/templates/.claude/skills/byan-strict/SKILL.md +9 -0
  17. package/install/templates/.githooks/pre-commit +18 -0
  18. package/install/templates/_byan/_config/delivery-default.json +22 -0
  19. package/install/templates/_byan/_config/strict-mode.yaml +25 -0
  20. package/install/templates/_byan/agent/byan/byan-soul.md +40 -0
  21. package/install/templates/_byan/mcp/byan-mcp-server/channel-entry.js +46 -0
  22. package/install/templates/_byan/mcp/byan-mcp-server/lib/channel-poll.js +128 -0
  23. package/install/templates/_byan/mcp/byan-mcp-server/lib/channel-server.js +234 -0
  24. package/install/templates/_byan/mcp/byan-mcp-server/lib/completeness-evidence.js +159 -0
  25. package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js +60 -2
  26. package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-sync.js +4 -1
  27. package/install/templates/_byan/mcp/byan-mcp-server/lib/precommit-gate.js +68 -2
  28. package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-mode.js +78 -1
  29. package/install/templates/_byan/mcp/byan-mcp-server/lib/sync-rules.js +40 -2
  30. package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +1 -1
  31. package/install/templates/dist/skill-bundles/byan-strict.zip +0 -0
  32. package/install/templates/docs/leantime-integration.md +11 -1
  33. package/node_modules/byan-platform-config/lib/mcp-config.js +71 -5
  34. package/package.json +1 -1
  35. package/install/templates/.mcp.json.tmpl +0 -8
package/CHANGELOG.md CHANGED
@@ -9,6 +9,209 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.39.0] - 2026-07-02
13
+
14
+ ### Added - shippable soul stays in sync with the active soul (byan-sync-soul)
15
+
16
+ BYAN's active identity (`_byan/agent/byan/soul.md`, `tao.md`) is shipped to a
17
+ fresh install via the prefixed copies `byan-soul.md` / `byan-tao.md`, which the
18
+ installer copies into place at setup. Those copies were a MANUAL mirror and had
19
+ drifted: `byan-soul.md` had missed the `## Valeurs` section and the 2026-07-02
20
+ couche-vivante revision, so a fresh install shipped a stale identity. The
21
+ 2026-03-27 revision had warned that soul transmission needs maintenance — the
22
+ manual sync did not hold.
23
+
24
+ - **New generator `byan-sync-soul`** (`lib/sync-soul.js` + `bin/byan-sync-soul.js`)
25
+ mirrors the active soul + tao into the shippable prefixed copies. `--check`
26
+ reports drift and exits non-zero.
27
+ - **Pre-commit gate**: `.githooks/pre-commit` runs `byan-sync-soul --check`, so a
28
+ commit whose shippable soul drifted from the active one is blocked. Dev-repo
29
+ tooling — the bin is not shipped, so the gate no-ops in installed projects
30
+ (`[ -f ]` guard), where it is not needed.
31
+ - **Caught up**: `byan-soul.md` now carries the `## Valeurs` section and the
32
+ 2026-07-02 revision; `byan-tao.md` was already in sync.
33
+ - **soul-memory is out of scope**: `byan-soul-memory.md` is a curated seed journal
34
+ (distinct from this repo's living `soul-memory.md`), so it is not mirrored.
35
+
36
+ Files: `_byan/mcp/byan-mcp-server/lib/sync-soul.js` + `bin/byan-sync-soul.js` +
37
+ `test/sync-soul.test.js`, `.githooks/pre-commit` (+ template mirror),
38
+ `_byan/agent/byan/byan-soul.md` (+ template mirror). Adversarial review
39
+ (bmad-compliance) approved, 0 must_fix; it corrected an inaccurate rationale in
40
+ the comments (the installer copies, it does not rename), fixed before merge.
41
+ MCP node --test 733/733, jest root 2473/2473.
42
+
43
+ ### Added - strict self-verify checklist from measured recurring gaps
44
+
45
+ Strict mode now carries a self-verify checklist of BYAN's OWN most frequent
46
+ blind spots, so each self-verify pass checks them on top of the locked
47
+ acceptance criteria. The three themes are not guessed — they were harvested from
48
+ the strict audit log by `byan_insight_digest` (self-verify gap clustering):
49
+ tests/coverage (observed 20x), doc-follows-code (10x), scope-discovery (7x). The
50
+ heterogeneous "other" cluster (18x) was deliberately excluded — too mixed to
51
+ become a single check.
52
+
53
+ - **Single source of truth**: the checklist lives under `self_verify.checklist`
54
+ in `_byan/_config/strict-mode.yaml`. Each item is a testable question plus an
55
+ `observed` count kept as the WHY (a signal, not a target).
56
+ - **Propagated by the generator**: `byan-sync-rules` renders it into the three
57
+ operative surfaces — the `byan-strict` SKILL (a `## Self-verify checklist`
58
+ section), `.claude/hooks/lib/strict-config.json` (`self_verify_checklist`, with
59
+ `observed` stripped as source-only), and the `AGENTS.md` block (Codex parity).
60
+ Idempotent; an older config with no checklist renders an empty list and no
61
+ section.
62
+ - **Anti-drop**: a test asserts the real `strict-mode.yaml` carries the three
63
+ themes and that they survive the generator, so a future edit cannot silently
64
+ drop them. Additive only — `min_passes`, `last_verdict_must_be`, mantras,
65
+ banners and the scope guard are unchanged.
66
+
67
+ This closes the insight loop from the 2026-07-02 soul revision: a measured,
68
+ recurring gap becomes a mechanized check rather than a remembered intention.
69
+
70
+ Files: `_byan/_config/strict-mode.yaml`, `_byan/mcp/byan-mcp-server/lib/sync-rules.js`,
71
+ `_byan/mcp/byan-mcp-server/test/sync-rules.test.js`, regenerated
72
+ `.claude/skills/byan-strict/SKILL.md` + `.claude/hooks/lib/strict-config.json` +
73
+ `AGENTS.md` (+ install template mirrors). Adversarial review (bmad-compliance)
74
+ approved, 0 must_fix, 7/7 criteria. MCP node --test 726/726, jest root 2473/2473.
75
+
76
+ ## [2.38.0] - 2026-06-30
77
+
78
+ ### Added - yanstaller installs the byan-channel MCP entry by default (inert)
79
+
80
+ `npx create-byan-agent` now registers a second MCP server, `byan-channel`,
81
+ alongside `byan` in the project `.mcp.json`. It is a Claude Code RESEARCH PREVIEW
82
+ channel (v2.1.80+) and ships INERT: registering it enables nothing on its own --
83
+ it is only loaded when the user launches
84
+ `claude --dangerously-load-development-channels server:byan-channel`. No
85
+ `channelsEnabled`, no `allowedChannelPlugins`, no auto-activation flag is written.
86
+ Codex is not covered by this feature.
87
+
88
+ - **Port (read-only from byan_web)**: `channel-entry.js`, `lib/channel-server.js`,
89
+ `lib/channel-poll.js` brought into `_byan/mcp/byan-mcp-server/`. The entry
90
+ resolves its own config at boot via `resolve-config.js` (env ->
91
+ `~/.byan/credentials.json` -> defaults), so no secret is needed in `.mcp.json`.
92
+ - **Wiring -- one shape, every writer**: the byan + byan-channel entry shape has a
93
+ single source of truth, `mcpConfig.mergeByanEntry`, with no `.mcp.json` template
94
+ to drift out of sync. Both writers route through it -- `generateMcpConfig` (the
95
+ primary native-setup path, a pure delegation) and `installDirectMCP` -- so they
96
+ are byte-coherent. Each writes a project-relative path (not absolute, not
97
+ `{{PROJECT_ROOT}}`), preserves an existing custom `command` and non-byan env,
98
+ strips `BYAN_API_URL`/`BYAN_API_TOKEN`, and merges into an existing `.mcp.json`
99
+ without clobbering other servers. Additional default MCP servers are registered
100
+ via `addMcpEntry` (mcp-extensions), not a template.
101
+ - **Portable + secret-free**: relative paths survive a moved / npm-shipped repo;
102
+ the channel env is forced empty; no token shape can land in tracked `.mcp.json`.
103
+ - **Shipped**: the 3 runtime files + 2 tests are in `TARGET_ADDITIONS` and
104
+ mirrored to `install/templates`, parity enforced by `byan-sync-template --check`.
105
+ - **Honest post-install message**: states research-preview, version gate, inert
106
+ default, the activation flag, and that Codex is not covered.
107
+
108
+ Files: `install/packages/platform-config/lib/mcp-config.js` (mergeByanEntry /
109
+ mergeChannelEntry / mergeLeantimeRefs -- relative args, command preserved),
110
+ `install/lib/claude-native-setup.js` (generateMcpConfig is a pure delegation to
111
+ the shared merge), `install/lib/platforms/claude-code.js` (installDirectMCP),
112
+ `install/bin/create-byan-agent-v2.js` (message),
113
+ `_byan/mcp/byan-mcp-server/{channel-entry,lib/channel-server,lib/channel-poll}.js`,
114
+ `_byan/mcp/byan-mcp-server/lib/template-sync.js`,
115
+ `_byan/mcp/byan-mcp-server/test/{channel,channel-resolve}.test.js` (+ install
116
+ templates). Adversarial review (bmad-compliance) caught 3 message/coherence gaps,
117
+ all fixed before merge; a follow-up adversarial pass drove removal of the now-dead
118
+ `.mcp.json.tmpl` so the byan/byan-channel shape has a single source of truth.
119
+ jest root 2473/2473, MCP node --test 723/723.
120
+
121
+ ## [2.37.1] - 2026-06-30
122
+
123
+ ### Fixed - strict-stop-guard false positive on mentioned completion markers
124
+
125
+ `claimsCompletion` matched a completion marker ANYWHERE in the assistant message,
126
+ so a turn was blocked even when a marker was merely MENTIONED, not claimed:
127
+ `complete` inside `byan_strict_complete`, `done` inside the `BYAN-BENCH:done`
128
+ marker comment, or an accented marker embedded in another word (`fini` in
129
+ `indefini`, `termine` in `determine`). Hit repeatedly in one session. Hardened: a
130
+ working copy is denoised first (fenced + inline code, HTML comments, snake_case /
131
+ namespaced identifiers stripped), then markers match only as a standalone claim
132
+ with Unicode-aware boundaries and a permissive trailing inflection (`livre` ->
133
+ `livree` / `livres`). Bias toward fewer false blocks -- the pre-commit gate stays
134
+ the hard net for a real premature completion. Genuine claims (`done`,
135
+ `c'est termine`, `feature livree`, `the build is complete`) still fire. +4
136
+ regression tests.
137
+
138
+ Files: `.claude/hooks/strict-stop-guard.js` (+ install template),
139
+ `_byan/mcp/byan-mcp-server/test/strict-hooks.test.js`. MCP node --test 714/714,
140
+ root jest 2466/2466.
141
+
142
+ ## [2.37.0] - 2026-06-30
143
+
144
+ ### Added - Prod-grade + maximal scope as the mechanical default (anti-downgrade)
145
+
146
+ A 4-lens diagnosis workflow established WHY the agent kept drifting toward MVP /
147
+ half-work despite the whole anti-downgrade arsenal: it was OPT-IN (every guard a
148
+ no-op until a scope is locked), the strict loop was self-judged (agent writes the
149
+ contract, grades its own verdict), and the cost model was anchored on human-2010
150
+ time, generating the MVP-split reflex. This ships the mechanical, default-on fix.
151
+
152
+ - **F1 -- delivery-contract anchor (LIVE).** New `inject-delivery-default.js`
153
+ (UserPromptSubmit) + pure lib `delivery-contract.js` re-inject a contract every
154
+ turn: grade=PROD, scope=MAXIMAL (proposing an MVP / short-deliverable /
155
+ dont-block-the-heavy split is forbidden unless the user types an opt-out word
156
+ THIS message), cost yardstick=AI-2026 (estimate in agent-time x10, not
157
+ human-by-hand). Opt-out wordlist in `_byan/_config/delivery-default.json`. The
158
+ opt-out parser is biased toward PROD: a single opt-out word counts only as a
159
+ short directive or with a go-cheap cue, is dropped when negated ("pas de mvp"),
160
+ and a mere mention in a long meta message leaves the anchor armed. Preserved
161
+ across compaction (CLAUDE.md) and mirrored to AGENTS.md (Codex).
162
+ - **F2 -- non-agent completeness judge (built, DISARMED).** `completeness-evidence.js`
163
+ wired additively into strict `complete()` + the pre-commit gate: a "done" claim
164
+ is backed by a non-fabricable artifact (a real test-runner exit, a `git diff`
165
+ vs the locked paths, a file that exists), breaking the judge=defendant loop.
166
+ Ships behind `completenessGate.armed=false` (collect + ledger only) so it does
167
+ not change behavior or self-lock until the ledger validates arming.
168
+ - **F3 -- punt-guard (built, DISARMED).** Stop hook + pure `punt-detect.js`
169
+ flags handing the user a runnable command the agent could run itself, with a
170
+ creds carve-out (git push / npm publish). Ships behind `puntGuard.armed=false`
171
+ (observe + ledger).
172
+
173
+ Caught in build: F1's first live turn revealed a false positive (the anchor
174
+ self-disabled on any message that merely mentioned an opt-out word). Fixed in
175
+ place with the negation + directive-context hardening above + 3 regression tests.
176
+
177
+ Files: `.claude/hooks/inject-delivery-default.js`, `.claude/hooks/lib/delivery-contract.js`,
178
+ `.claude/hooks/punt-guard.js`, `.claude/hooks/lib/punt-detect.js`,
179
+ `_byan/mcp/byan-mcp-server/lib/completeness-evidence.js`, wired into `strict-mode.js`
180
+ + `precommit-gate.js`, `_byan/_config/delivery-default.json`, `.claude/settings.json`,
181
+ CLAUDE.md + AGENTS.md (+ install templates) + tests. MCP node --test 710/710, root
182
+ jest 2466/2466. Strict scope 9f8c6048.
183
+
184
+ ## [2.36.0] - 2026-06-29
185
+
186
+ ### Added - Leantime auto-sync: complexity, priority and description on task creation
187
+
188
+ The FD->Leantime auto-sync (which already creates tasks and drives the
189
+ todo/doing/review/done lifecycle) now enriches each created task with effort,
190
+ priority and a traceable description -- no manual board work.
191
+
192
+ - **F1 -- pure resolvers + enriched intent** (`leantime-fd-core.js`). Two
193
+ exported pure functions: `priorityToLeantime` (P1/P2/P3 -> 3/2/1, omitted when
194
+ unknown) and `complexityToStorypoints` (a finite `item.complexity` bucketed on
195
+ the Fibonacci scale <=15->2 / 16-39->5 / 40-69->8 / >=70->13, else derived from
196
+ priority P1->8 / P2->5 / P3->3, default 3 -- returns a numeric estimate).
197
+ `decideActions` now emits `{ priority?, storypoints, description }` in the
198
+ `task_create` intent (`description` = `BYAN FD <id> -- <headline>` +
199
+ ` [complexity:N]` when finite).
200
+ - **F2 -- carrier + passthrough** (`leantime-sync.js` + the `leantime-fd-sync`
201
+ hook, source + install template). `createTask` accepts and sends `storypoints`;
202
+ the hook passes `description` / `priority` / `storypoints` through to it. The
203
+ lifecycle sync is untouched.
204
+
205
+ Note: `storypoints` is the presumed Leantime effort field, `[UNVERIFIED]` against
206
+ a live instance (Leantime unreachable at build time). An unknown key is at worst
207
+ ignored by `addTicket` (no break), and the complexity is also carried in the
208
+ description as a fallback. Flagged for a live-verify when reachable.
209
+
210
+ Files: `_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js`, `lib/leantime-sync.js`,
211
+ `.claude/hooks/leantime-fd-sync.js` (+ install template), `test/leantime-fd-core.test.js`
212
+ + `test/leantime-sync.test.js` (+7 tests). MCP node --test 696/696, root jest
213
+ 2441/2441. Strict scope 80405c59.
214
+
12
215
  ## [2.35.0] - 2026-06-26
13
216
 
14
217
  ### Added - Portable core / native projection doctrine + degradation litmus
@@ -1296,6 +1296,21 @@ async function install(options = {}) {
1296
1296
  console.log(chalk.cyan('Claude Code native setup (hooks, skills, MCP server)'));
1297
1297
  try {
1298
1298
  await setupClaudeNative(projectRoot);
1299
+ // Honest disclosure about the byan-channel entry now in .mcp.json. It is a
1300
+ // Claude Code RESEARCH PREVIEW feature and ships INERT: registering it does
1301
+ // not enable it. We tell the user exactly what it is, what it requires, how
1302
+ // to turn it on, and that Codex is not covered — no overselling.
1303
+ console.log(
1304
+ chalk.gray(
1305
+ ' - byan-channel = Claude Code RESEARCH PREVIEW channel (v2.1.80+), INERT by default.'
1306
+ )
1307
+ );
1308
+ console.log(
1309
+ chalk.gray(
1310
+ ' To activate: claude --dangerously-load-development-channels server:byan-channel'
1311
+ )
1312
+ );
1313
+ console.log(chalk.gray(' Codex is not covered by this feature.'));
1299
1314
  } catch (error) {
1300
1315
  console.log(chalk.red(` ✘ Claude native setup failed: ${error.message}`));
1301
1316
  console.log(
@@ -9,7 +9,8 @@
9
9
  * - _byan/mcp/byan-mcp-server/** (no node_modules)
10
10
  *
11
11
  * Generates:
12
- * - .mcp.json with the absolute path to the target project's MCP server
12
+ * - .mcp.json with the RELATIVE path to the target project's MCP server
13
+ * (byan + inert byan-channel entries, via the shared pure merge)
13
14
  *
14
15
  * Runs:
15
16
  * - npm install inside the copied MCP server dir (with fallback warning)
@@ -22,6 +23,7 @@ const chalk = require('chalk');
22
23
 
23
24
  const TEMPLATE_ROOT = path.resolve(__dirname, '..', 'templates');
24
25
  const { whitelistMcpServer } = require('./settings-local');
26
+ const { mcpConfig } = require('byan-platform-config');
25
27
 
26
28
  async function copyClaudeHooks(projectRoot) {
27
29
  const src = path.join(TEMPLATE_ROOT, '.claude', 'hooks');
@@ -78,30 +80,15 @@ async function copyMcpServer(projectRoot) {
78
80
  }
79
81
 
80
82
  async function generateMcpConfig(projectRoot, options = {}) {
81
- const tmplPath = path.join(TEMPLATE_ROOT, '.mcp.json.tmpl');
82
83
  const dstPath = path.join(projectRoot, '.mcp.json');
83
84
 
84
- let template;
85
- if (await fs.pathExists(tmplPath)) {
86
- template = await fs.readFile(tmplPath, 'utf8');
87
- } else {
88
- template = JSON.stringify(
89
- {
90
- mcpServers: {
91
- byan: {
92
- command: 'node',
93
- args: ['{{PROJECT_ROOT}}/_byan/mcp/byan-mcp-server/server.js'],
94
- env: { BYAN_API_URL: options.apiUrl || 'http://localhost:3737' },
95
- },
96
- },
97
- },
98
- null,
99
- 2
100
- );
101
- }
102
-
103
- const rendered = template.replace(/\{\{PROJECT_ROOT\}\}/g, projectRoot);
104
-
85
+ // Read-merge-write through the single source of truth for the byan + inert
86
+ // byan-channel entry shape: mcpConfig.mergeByanEntry (relative args, secret-free,
87
+ // existing siblings + custom command + non-byan env preserved, channel added
88
+ // inert). This is byte-coherent with installDirectMCP, which routes through the
89
+ // same merge. Additional default MCP servers are registered via addMcpEntry
90
+ // (mcp-extensions), not here -- so there is no .mcp.json template to drift out
91
+ // of sync with the merge.
105
92
  let existing = {};
106
93
  if (await fs.pathExists(dstPath)) {
107
94
  try {
@@ -111,20 +98,7 @@ async function generateMcpConfig(projectRoot, options = {}) {
111
98
  }
112
99
  }
113
100
 
114
- const merged = JSON.parse(rendered);
115
- merged.mcpServers = { ...(existing.mcpServers || {}), ...merged.mcpServers };
116
-
117
- // Security: BYAN_API_TOKEN is never written into .mcp.json (git-tracked); the
118
- // token reaches the MCP server via .claude/settings.local.json env. Also honor
119
- // the caller's apiUrl: the template URL is only a default.
120
- const byanEntry = merged.mcpServers && merged.mcpServers.byan;
121
- if (byanEntry && byanEntry.env) {
122
- delete byanEntry.env.BYAN_API_TOKEN;
123
- if (options.apiUrl) {
124
- byanEntry.env.BYAN_API_URL = options.apiUrl;
125
- }
126
- }
127
-
101
+ const merged = mcpConfig.mergeByanEntry(existing, { apiUrl: options.apiUrl });
128
102
  await fs.writeJson(dstPath, merged, { spaces: 2 });
129
103
  return { path: dstPath };
130
104
  }
@@ -197,7 +171,7 @@ async function setupClaudeNative(projectRoot, options = {}) {
197
171
  );
198
172
 
199
173
  results.mcpConfig = await generateMcpConfig(projectRoot, options);
200
- log(chalk.green(` .mcp.json generated (absolute path)`));
174
+ log(chalk.green(` byan + inert byan-channel entries in .mcp.json (relative paths)`));
201
175
 
202
176
  // Whitelist the byan MCP server: a project .mcp.json entry is inert in Claude
203
177
  // Code until its id is listed in .claude/settings.local.json.
@@ -11,9 +11,19 @@ const path = require('path');
11
11
  const os = require('os');
12
12
  const fileUtils = require('../utils/file-utils');
13
13
  const logger = require('../utils/logger');
14
+ // Single source of truth for the byan + byan-channel entry shape: the same pure
15
+ // merge the canonical .mcp.json writer uses (READ-MERGE-WRITE, relative paths,
16
+ // no secret, byan-channel inert). installDirectMCP delegates to it instead of
17
+ // hand-building a divergent entry with an absolute path that overwrote siblings.
18
+ const { mcpConfig } = require('byan-platform-config');
14
19
 
15
20
  const PLATFORM_NAME = 'Claude Code';
16
- const MCP_SERVER_FILENAME = 'byan-mcp-server.js';
21
+ // Relative path discipline: the byan server entry args[0] is repo-relative
22
+ // (mcpConfig.MCP_SERVER_REL_PATH), NEVER an absolute path. The old
23
+ // MCP_SERVER_FILENAME ('byan-mcp-server.js') was both the wrong filename and
24
+ // joined into an absolute path; both are gone. Kept only as the existence probe
25
+ // target below is the real server file under the relative path.
26
+ const MCP_SERVER_REL_PATH = mcpConfig.MCP_SERVER_REL_PATH;
17
27
 
18
28
  /**
19
29
  * Get config path for current platform
@@ -137,41 +147,40 @@ async function installViaClaudeAgent(projectRoot, agents, config) {
137
147
  */
138
148
  async function installDirectMCP(projectRoot, agents, config) {
139
149
  const configPath = getConfigPath();
140
- const mcpServerPath = path.join(projectRoot, MCP_SERVER_FILENAME);
141
-
150
+ // Existence probe against the REAL server file at its repo-relative path
151
+ // (resolved to absolute only for the fs check). The args written into the
152
+ // config below stay relative — see mcpConfig.MCP_SERVER_REL_PATH.
153
+ const mcpServerPath = path.join(projectRoot, MCP_SERVER_REL_PATH);
154
+
142
155
  // Check if MCP server exists
143
156
  if (!await fileUtils.exists(mcpServerPath)) {
144
157
  logger.warn(`MCP server not found at: ${mcpServerPath}`);
145
158
  logger.warn('Run @bmad-agent-claude to generate MCP server first.');
146
-
159
+
147
160
  return {
148
161
  success: false,
149
162
  installed: 0,
150
163
  method: 'direct-mcp-failed'
151
164
  };
152
165
  }
153
-
166
+
154
167
  // Backup existing config
155
168
  const backupPath = `${configPath}.backup`;
156
169
  await fileUtils.copy(configPath, backupPath);
157
170
  logger.info(`Backed up config to: ${backupPath}`);
158
-
159
- // Read and update config
171
+
172
+ // READ-MERGE-WRITE through the shared pure merge so this writer is byte-for-byte
173
+ // identical to the canonical .mcp.json writer: relative server path, no secret,
174
+ // existing mcpServers.* preserved, and the inert byan-channel entry added.
175
+ // apiUrl is irrelevant to the entry shape (the server resolves its own config),
176
+ // but the merge signature accepts it; a localhost placeholder keeps it valid.
160
177
  const existingConfig = await fileUtils.readJson(configPath);
161
- existingConfig.mcpServers = existingConfig.mcpServers || {};
162
-
163
- existingConfig.mcpServers.byan = {
164
- command: 'node',
165
- args: [mcpServerPath],
166
- env: {
167
- PROJECT_ROOT: projectRoot
168
- }
169
- };
170
-
178
+ const merged = mcpConfig.mergeByanEntry(existingConfig, { apiUrl: 'http://localhost:3737' });
179
+
171
180
  // Write updated config
172
- await fileUtils.writeJson(configPath, existingConfig, { spaces: 2 });
181
+ await fileUtils.writeJson(configPath, merged, { spaces: 2 });
173
182
  logger.info(`Updated MCP config: ${configPath}`);
174
-
183
+
175
184
  return {
176
185
  success: true,
177
186
  installed: agents.length,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-byan-agent",
3
- "version": "2.35.0",
3
+ "version": "2.37.1",
4
4
  "description": "BYAN - Intelligent AI agent installer with multi-platform native support (Claude Code, Codex/OpenCode)",
5
5
  "bin": {
6
6
  "create-byan-agent": "bin/create-byan-agent-v2.js"
@@ -27,6 +27,13 @@ const path = require('path');
27
27
  const fs = require('fs-extra');
28
28
 
29
29
  const MCP_SERVER_REL_PATH = '_byan/mcp/byan-mcp-server/server.js';
30
+ // Channel entrypoint: a SEPARATE MCP server (research preview) spawned by Claude
31
+ // Code via --dangerously-load-development-channels. Same relative-path discipline
32
+ // as the main server (relative to projectRoot, never absolute) so the entry is
33
+ // portable across machines/OSes and stays valid when the repo is moved or shipped
34
+ // via npm. The entry is INERT by default: registering it in .mcp.json does NOT
35
+ // enable the channel (that needs the explicit --dangerously-load flag at launch).
36
+ const MCP_CHANNEL_REL_PATH = '_byan/mcp/byan-mcp-server/channel-entry.js';
30
37
  const TOKEN_PLACEHOLDER = '${BYAN_API_TOKEN}';
31
38
  const LEANTIME_URL_PLACEHOLDER = '${LEANTIME_API_URL}';
32
39
  const LEANTIME_TOKEN_PLACEHOLDER = '${LEANTIME_API_TOKEN}';
@@ -87,15 +94,66 @@ function mergeByanEntry(existingConfig, { apiUrl, token } = {}) {
87
94
  delete env.BYAN_API_URL;
88
95
  delete env.BYAN_API_TOKEN;
89
96
 
97
+ // The canonical RELATIVE path is forced on `args` AFTER ...existing so it always
98
+ // wins -- this REPAIRS a stale entry that carried an absolute path (a pre-2.37.x
99
+ // install): relative survives a moved / npm-shipped repo, absolute does not, and
100
+ // Claude Code spawns the server with cwd=projectRoot so the relative path resolves
101
+ // identically. `command` is only F1-orthogonal (the interpreter, not a path), so a
102
+ // user-chosen command is preserved; we default to 'node' only when absent. Other
103
+ // pre-existing keys are still preserved.
90
104
  const entry = {
91
- command: 'node',
92
- args: [MCP_SERVER_REL_PATH],
93
105
  ...existing,
106
+ command: existing.command || 'node',
107
+ args: [MCP_SERVER_REL_PATH],
94
108
  };
95
109
  if (Object.keys(env).length > 0) entry.env = env;
96
110
  else delete entry.env;
97
111
 
98
112
  cfg.mcpServers.byan = entry;
113
+
114
+ // The channel entry is written alongside byan from the SAME merge so there is
115
+ // a single source of truth for the byan MCP registration. It is inert.
116
+ return mergeChannelEntry(cfg);
117
+ }
118
+
119
+ /**
120
+ * Pure merge — no I/O. Adds the `byan-channel` MCP server entry alongside byan.
121
+ *
122
+ * This entry is a Claude Code research-preview channel (CC v2.1.80+). It is
123
+ * INERT by default: present in .mcp.json but only loaded when the user launches
124
+ * with `claude --dangerously-load-development-channels server:byan-channel`.
125
+ * Registering it does NOT auto-activate anything — this function writes NO
126
+ * channelsEnabled / allowedChannelPlugins / --dangerously-load flag.
127
+ *
128
+ * Same portability discipline as the byan entry: a RELATIVE path (never
129
+ * absolute), no secret in env (the channel resolves its own config at boot via
130
+ * resolve-config.js: env -> ~/.byan/credentials.json -> defaults). An existing
131
+ * byan-channel entry is preserved (command/args/env) so the merge is idempotent.
132
+ *
133
+ * @param {object} existingConfig — current parsed config
134
+ * @returns {object} new merged config
135
+ */
136
+ function mergeChannelEntry(existingConfig) {
137
+ const cfg = existingConfig && typeof existingConfig === 'object' ? { ...existingConfig } : {};
138
+ cfg.mcpServers = { ...(cfg.mcpServers || {}) };
139
+
140
+ const existing = cfg.mcpServers['byan-channel'] || {};
141
+
142
+ // The canonical RELATIVE path + the empty (secret-free) env are forced AFTER
143
+ // ...existing so they always win, normalizing a stale entry (absolute path, or
144
+ // a stray env) while preserving any other pre-existing key. env stays empty: the
145
+ // channel resolves BYAN_API_URL/TOKEN itself via resolve-config.js -- writing a
146
+ // secret here would land it in tracked git, and the channel has no legitimate
147
+ // env contract of its own (Leantime refs live on the byan entry). `command` is
148
+ // F1-orthogonal so a user-chosen interpreter is preserved, 'node' only as the
149
+ // default. Idempotent: re-running yields the same entry.
150
+ cfg.mcpServers['byan-channel'] = {
151
+ ...existing,
152
+ command: existing.command || 'node',
153
+ args: [MCP_CHANNEL_REL_PATH],
154
+ env: {},
155
+ };
156
+
99
157
  return cfg;
100
158
  }
101
159
 
@@ -141,14 +199,20 @@ function mergeLeantimeRefs(existingConfig) {
141
199
  env.LEANTIME_API_URL = LEANTIME_URL_PLACEHOLDER;
142
200
  env.LEANTIME_API_TOKEN = LEANTIME_TOKEN_PLACEHOLDER;
143
201
 
202
+ // Same discipline as mergeByanEntry: args forced to the canonical RELATIVE path
203
+ // (normalizes a stale absolute one), command preserved (F1-orthogonal), env
204
+ // carries the merged Leantime refs.
144
205
  cfg.mcpServers.byan = {
145
- command: 'node',
146
- args: [MCP_SERVER_REL_PATH],
147
206
  ...existing,
207
+ command: existing.command || 'node',
208
+ args: [MCP_SERVER_REL_PATH],
148
209
  env,
149
210
  };
150
211
 
151
- return cfg;
212
+ // Keep the channel entry coherent with the byan entry: ensureLeantimeRefs runs
213
+ // after ensureMcpConfig in the installer, but routing through mergeChannelEntry
214
+ // here makes the byan-channel registration robust to call order (idempotent).
215
+ return mergeChannelEntry(cfg);
152
216
  }
153
217
 
154
218
  /**
@@ -252,12 +316,14 @@ module.exports = {
252
316
  ensureMcpConfig,
253
317
  readMcpConfig,
254
318
  mergeByanEntry,
319
+ mergeChannelEntry,
255
320
  mergeLeantimeRefs,
256
321
  ensureLeantimeRefs,
257
322
  addMcpEntry,
258
323
  removeMcpEntry,
259
324
  looksLikeSecret,
260
325
  MCP_SERVER_REL_PATH,
326
+ MCP_CHANNEL_REL_PATH,
261
327
  TOKEN_PLACEHOLDER,
262
328
  LEANTIME_URL_PLACEHOLDER,
263
329
  LEANTIME_TOKEN_PLACEHOLDER,
@@ -127,6 +127,7 @@ A confirm, a destructive prompt, or an obvious default is not a fork — emit
127
127
  ## Compact instructions
128
128
 
129
129
  Quand tu compactes cette conversation, PRESERVE en priorite :
130
+ - le contrat de livraison par defaut (F1) : grade=PROD, scope=MAXIMAL, etalon de cout=AI-2026 (temps-agent x10, jamais temps-humain-a-la-main). Pas de proposition d'MVP/livrable-court/decoupage-pour-ne-pas-bloquer-le-lourd sauf opt-out explicite du message courant. Source : `_byan/_config/delivery-default.json` ; reinjecte chaque tour par `inject-delivery-default`.
130
131
  - l'etat FD BYAN actif s'il existe : phase courante, feature_name, le backlog avec le statut par item, le dernier verdict review/validate (source : `_byan-output/fd-state.json`).
131
132
  - la session Strict Mode active s'il y en a une : scope_hash, criteres d'acceptation, nombre de passes, completion (source : `.byan-strict/state.json` + API byan_web).
132
133
  - l'identite BYAN : le noyau immuable du soul + la voix tao (registre, signatures, tutoiement, zero emoji). `inject-tao` la reinjecte au SessionStart, mais garde la voix active dans le resume aussi.
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * UserPromptSubmit hook — BYAN delivery-default contract (F1).
4
+ *
5
+ * Injects, every turn, the baseline delivery posture: grade=PROD, scope=MAXIMAL,
6
+ * the AI-2026 cost yardstick (estimate in agent-time x10, never human-by-hand
7
+ * time), and the explicit ban on quietly proposing an MVP / short deliverable /
8
+ * a split that exists only to avoid doing the heavy part. This is the proactive
9
+ * twin of strict mode: strict locks a scope on demand, this sets the DEFAULT.
10
+ *
11
+ * Ships LIVE (unlike the F2 completeness reject and the F3 punt-guard, which
12
+ * ship disarmed): the anchor is pure injected context, it blocks nothing, so a
13
+ * false positive costs a few tokens — never a trapped turn or a blocked commit.
14
+ *
15
+ * The only escape is an explicit opt-out word the user types THIS message (mvp,
16
+ * quick, brouillon, ...). On opt-out the hook emits a single descope-authorized
17
+ * line instead of the full anchor, so the agent is told the contract is relaxed
18
+ * for this one request.
19
+ *
20
+ * Always exits 0; never blocks prompt submission.
21
+ */
22
+
23
+ const { loadConfig, decideContext } = require('./lib/delivery-contract');
24
+ const { readStdin, parseJson } = require('./lib/strict-runtime');
25
+
26
+ if (require.main === module) {
27
+ (async () => {
28
+ let additionalContext = '';
29
+ try {
30
+ const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
31
+ const config = loadConfig(projectRoot);
32
+ const payload = parseJson(await readStdin());
33
+ const userMsg = payload.prompt || payload.user_prompt || payload.userPrompt || '';
34
+ additionalContext = decideContext({ userMsg, config }).text;
35
+ } catch {
36
+ // Never block prompt submission — degrade to no context.
37
+ additionalContext = '';
38
+ }
39
+ process.stdout.write(
40
+ JSON.stringify({
41
+ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext },
42
+ })
43
+ );
44
+ process.exit(0);
45
+ })();
46
+ }
@@ -162,7 +162,18 @@ function logLine(entry) {
162
162
  if (createIntents.length && sidecar.projectId && timeLeft() > 0) {
163
163
  const results = await Promise.allSettled(
164
164
  createIntents.map((i) =>
165
- lt.createTask({ projectId: sidecar.projectId, headline: i.headline }, opts).then((r) => ({ i, r })),
165
+ lt
166
+ .createTask(
167
+ {
168
+ projectId: sidecar.projectId,
169
+ headline: i.headline,
170
+ description: i.description,
171
+ priority: i.priority,
172
+ storypoints: i.storypoints,
173
+ },
174
+ opts,
175
+ )
176
+ .then((r) => ({ i, r })),
166
177
  ),
167
178
  );
168
179
  for (const s of results) {