create-byan-agent 2.35.0 → 2.38.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 (27) hide show
  1. package/CHANGELOG.md +139 -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/punt-guard.js +126 -0
  13. package/install/templates/.claude/hooks/strict-stop-guard.js +29 -6
  14. package/install/templates/.claude/settings.json +8 -0
  15. package/install/templates/_byan/_config/delivery-default.json +22 -0
  16. package/install/templates/_byan/mcp/byan-mcp-server/channel-entry.js +46 -0
  17. package/install/templates/_byan/mcp/byan-mcp-server/lib/channel-poll.js +128 -0
  18. package/install/templates/_byan/mcp/byan-mcp-server/lib/channel-server.js +234 -0
  19. package/install/templates/_byan/mcp/byan-mcp-server/lib/completeness-evidence.js +159 -0
  20. package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js +60 -2
  21. package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-sync.js +4 -1
  22. package/install/templates/_byan/mcp/byan-mcp-server/lib/precommit-gate.js +68 -2
  23. package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-mode.js +78 -1
  24. package/install/templates/docs/leantime-integration.md +11 -1
  25. package/node_modules/byan-platform-config/lib/mcp-config.js +71 -5
  26. package/package.json +1 -1
  27. package/install/templates/.mcp.json.tmpl +0 -8
package/CHANGELOG.md CHANGED
@@ -9,6 +9,145 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.38.0] - 2026-06-30
13
+
14
+ ### Added - yanstaller installs the byan-channel MCP entry by default (inert)
15
+
16
+ `npx create-byan-agent` now registers a second MCP server, `byan-channel`,
17
+ alongside `byan` in the project `.mcp.json`. It is a Claude Code RESEARCH PREVIEW
18
+ channel (v2.1.80+) and ships INERT: registering it enables nothing on its own --
19
+ it is only loaded when the user launches
20
+ `claude --dangerously-load-development-channels server:byan-channel`. No
21
+ `channelsEnabled`, no `allowedChannelPlugins`, no auto-activation flag is written.
22
+ Codex is not covered by this feature.
23
+
24
+ - **Port (read-only from byan_web)**: `channel-entry.js`, `lib/channel-server.js`,
25
+ `lib/channel-poll.js` brought into `_byan/mcp/byan-mcp-server/`. The entry
26
+ resolves its own config at boot via `resolve-config.js` (env ->
27
+ `~/.byan/credentials.json` -> defaults), so no secret is needed in `.mcp.json`.
28
+ - **Wiring -- one shape, every writer**: the byan + byan-channel entry shape has a
29
+ single source of truth, `mcpConfig.mergeByanEntry`, with no `.mcp.json` template
30
+ to drift out of sync. Both writers route through it -- `generateMcpConfig` (the
31
+ primary native-setup path, a pure delegation) and `installDirectMCP` -- so they
32
+ are byte-coherent. Each writes a project-relative path (not absolute, not
33
+ `{{PROJECT_ROOT}}`), preserves an existing custom `command` and non-byan env,
34
+ strips `BYAN_API_URL`/`BYAN_API_TOKEN`, and merges into an existing `.mcp.json`
35
+ without clobbering other servers. Additional default MCP servers are registered
36
+ via `addMcpEntry` (mcp-extensions), not a template.
37
+ - **Portable + secret-free**: relative paths survive a moved / npm-shipped repo;
38
+ the channel env is forced empty; no token shape can land in tracked `.mcp.json`.
39
+ - **Shipped**: the 3 runtime files + 2 tests are in `TARGET_ADDITIONS` and
40
+ mirrored to `install/templates`, parity enforced by `byan-sync-template --check`.
41
+ - **Honest post-install message**: states research-preview, version gate, inert
42
+ default, the activation flag, and that Codex is not covered.
43
+
44
+ Files: `install/packages/platform-config/lib/mcp-config.js` (mergeByanEntry /
45
+ mergeChannelEntry / mergeLeantimeRefs -- relative args, command preserved),
46
+ `install/lib/claude-native-setup.js` (generateMcpConfig is a pure delegation to
47
+ the shared merge), `install/lib/platforms/claude-code.js` (installDirectMCP),
48
+ `install/bin/create-byan-agent-v2.js` (message),
49
+ `_byan/mcp/byan-mcp-server/{channel-entry,lib/channel-server,lib/channel-poll}.js`,
50
+ `_byan/mcp/byan-mcp-server/lib/template-sync.js`,
51
+ `_byan/mcp/byan-mcp-server/test/{channel,channel-resolve}.test.js` (+ install
52
+ templates). Adversarial review (bmad-compliance) caught 3 message/coherence gaps,
53
+ all fixed before merge; a follow-up adversarial pass drove removal of the now-dead
54
+ `.mcp.json.tmpl` so the byan/byan-channel shape has a single source of truth.
55
+ jest root 2473/2473, MCP node --test 723/723.
56
+
57
+ ## [2.37.1] - 2026-06-30
58
+
59
+ ### Fixed - strict-stop-guard false positive on mentioned completion markers
60
+
61
+ `claimsCompletion` matched a completion marker ANYWHERE in the assistant message,
62
+ so a turn was blocked even when a marker was merely MENTIONED, not claimed:
63
+ `complete` inside `byan_strict_complete`, `done` inside the `BYAN-BENCH:done`
64
+ marker comment, or an accented marker embedded in another word (`fini` in
65
+ `indefini`, `termine` in `determine`). Hit repeatedly in one session. Hardened: a
66
+ working copy is denoised first (fenced + inline code, HTML comments, snake_case /
67
+ namespaced identifiers stripped), then markers match only as a standalone claim
68
+ with Unicode-aware boundaries and a permissive trailing inflection (`livre` ->
69
+ `livree` / `livres`). Bias toward fewer false blocks -- the pre-commit gate stays
70
+ the hard net for a real premature completion. Genuine claims (`done`,
71
+ `c'est termine`, `feature livree`, `the build is complete`) still fire. +4
72
+ regression tests.
73
+
74
+ Files: `.claude/hooks/strict-stop-guard.js` (+ install template),
75
+ `_byan/mcp/byan-mcp-server/test/strict-hooks.test.js`. MCP node --test 714/714,
76
+ root jest 2466/2466.
77
+
78
+ ## [2.37.0] - 2026-06-30
79
+
80
+ ### Added - Prod-grade + maximal scope as the mechanical default (anti-downgrade)
81
+
82
+ A 4-lens diagnosis workflow established WHY the agent kept drifting toward MVP /
83
+ half-work despite the whole anti-downgrade arsenal: it was OPT-IN (every guard a
84
+ no-op until a scope is locked), the strict loop was self-judged (agent writes the
85
+ contract, grades its own verdict), and the cost model was anchored on human-2010
86
+ time, generating the MVP-split reflex. This ships the mechanical, default-on fix.
87
+
88
+ - **F1 -- delivery-contract anchor (LIVE).** New `inject-delivery-default.js`
89
+ (UserPromptSubmit) + pure lib `delivery-contract.js` re-inject a contract every
90
+ turn: grade=PROD, scope=MAXIMAL (proposing an MVP / short-deliverable /
91
+ dont-block-the-heavy split is forbidden unless the user types an opt-out word
92
+ THIS message), cost yardstick=AI-2026 (estimate in agent-time x10, not
93
+ human-by-hand). Opt-out wordlist in `_byan/_config/delivery-default.json`. The
94
+ opt-out parser is biased toward PROD: a single opt-out word counts only as a
95
+ short directive or with a go-cheap cue, is dropped when negated ("pas de mvp"),
96
+ and a mere mention in a long meta message leaves the anchor armed. Preserved
97
+ across compaction (CLAUDE.md) and mirrored to AGENTS.md (Codex).
98
+ - **F2 -- non-agent completeness judge (built, DISARMED).** `completeness-evidence.js`
99
+ wired additively into strict `complete()` + the pre-commit gate: a "done" claim
100
+ is backed by a non-fabricable artifact (a real test-runner exit, a `git diff`
101
+ vs the locked paths, a file that exists), breaking the judge=defendant loop.
102
+ Ships behind `completenessGate.armed=false` (collect + ledger only) so it does
103
+ not change behavior or self-lock until the ledger validates arming.
104
+ - **F3 -- punt-guard (built, DISARMED).** Stop hook + pure `punt-detect.js`
105
+ flags handing the user a runnable command the agent could run itself, with a
106
+ creds carve-out (git push / npm publish). Ships behind `puntGuard.armed=false`
107
+ (observe + ledger).
108
+
109
+ Caught in build: F1's first live turn revealed a false positive (the anchor
110
+ self-disabled on any message that merely mentioned an opt-out word). Fixed in
111
+ place with the negation + directive-context hardening above + 3 regression tests.
112
+
113
+ Files: `.claude/hooks/inject-delivery-default.js`, `.claude/hooks/lib/delivery-contract.js`,
114
+ `.claude/hooks/punt-guard.js`, `.claude/hooks/lib/punt-detect.js`,
115
+ `_byan/mcp/byan-mcp-server/lib/completeness-evidence.js`, wired into `strict-mode.js`
116
+ + `precommit-gate.js`, `_byan/_config/delivery-default.json`, `.claude/settings.json`,
117
+ CLAUDE.md + AGENTS.md (+ install templates) + tests. MCP node --test 710/710, root
118
+ jest 2466/2466. Strict scope 9f8c6048.
119
+
120
+ ## [2.36.0] - 2026-06-29
121
+
122
+ ### Added - Leantime auto-sync: complexity, priority and description on task creation
123
+
124
+ The FD->Leantime auto-sync (which already creates tasks and drives the
125
+ todo/doing/review/done lifecycle) now enriches each created task with effort,
126
+ priority and a traceable description -- no manual board work.
127
+
128
+ - **F1 -- pure resolvers + enriched intent** (`leantime-fd-core.js`). Two
129
+ exported pure functions: `priorityToLeantime` (P1/P2/P3 -> 3/2/1, omitted when
130
+ unknown) and `complexityToStorypoints` (a finite `item.complexity` bucketed on
131
+ the Fibonacci scale <=15->2 / 16-39->5 / 40-69->8 / >=70->13, else derived from
132
+ priority P1->8 / P2->5 / P3->3, default 3 -- returns a numeric estimate).
133
+ `decideActions` now emits `{ priority?, storypoints, description }` in the
134
+ `task_create` intent (`description` = `BYAN FD <id> -- <headline>` +
135
+ ` [complexity:N]` when finite).
136
+ - **F2 -- carrier + passthrough** (`leantime-sync.js` + the `leantime-fd-sync`
137
+ hook, source + install template). `createTask` accepts and sends `storypoints`;
138
+ the hook passes `description` / `priority` / `storypoints` through to it. The
139
+ lifecycle sync is untouched.
140
+
141
+ Note: `storypoints` is the presumed Leantime effort field, `[UNVERIFIED]` against
142
+ a live instance (Leantime unreachable at build time). An unknown key is at worst
143
+ ignored by `addTicket` (no break), and the complexity is also carried in the
144
+ description as a fallback. Flagged for a live-verify when reachable.
145
+
146
+ Files: `_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js`, `lib/leantime-sync.js`,
147
+ `.claude/hooks/leantime-fd-sync.js` (+ install template), `test/leantime-fd-core.test.js`
148
+ + `test/leantime-sync.test.js` (+7 tests). MCP node --test 696/696, root jest
149
+ 2441/2441. Strict scope 80405c59.
150
+
12
151
  ## [2.35.0] - 2026-06-26
13
152
 
14
153
  ### 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) {