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.
- package/CHANGELOG.md +203 -0
- package/install/bin/create-byan-agent-v2.js +15 -0
- package/install/lib/claude-native-setup.js +12 -38
- package/install/lib/platforms/claude-code.js +28 -19
- package/install/package.json +1 -1
- package/install/packages/platform-config/lib/mcp-config.js +71 -5
- package/install/templates/.claude/CLAUDE.md +1 -0
- package/install/templates/.claude/hooks/inject-delivery-default.js +46 -0
- package/install/templates/.claude/hooks/leantime-fd-sync.js +12 -1
- package/install/templates/.claude/hooks/lib/delivery-contract.js +143 -0
- package/install/templates/.claude/hooks/lib/punt-detect.js +143 -0
- package/install/templates/.claude/hooks/lib/strict-config.json +14 -0
- package/install/templates/.claude/hooks/punt-guard.js +126 -0
- package/install/templates/.claude/hooks/strict-stop-guard.js +29 -6
- package/install/templates/.claude/settings.json +8 -0
- package/install/templates/.claude/skills/byan-strict/SKILL.md +9 -0
- package/install/templates/.githooks/pre-commit +18 -0
- package/install/templates/_byan/_config/delivery-default.json +22 -0
- package/install/templates/_byan/_config/strict-mode.yaml +25 -0
- package/install/templates/_byan/agent/byan/byan-soul.md +40 -0
- package/install/templates/_byan/mcp/byan-mcp-server/channel-entry.js +46 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/channel-poll.js +128 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/channel-server.js +234 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/completeness-evidence.js +159 -0
- package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-fd-core.js +60 -2
- package/install/templates/_byan/mcp/byan-mcp-server/lib/leantime-sync.js +4 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/precommit-gate.js +68 -2
- package/install/templates/_byan/mcp/byan-mcp-server/lib/strict-mode.js +78 -1
- package/install/templates/_byan/mcp/byan-mcp-server/lib/sync-rules.js +40 -2
- package/install/templates/_byan/mcp/byan-mcp-server/skill-bundles-manifest.json +1 -1
- package/install/templates/dist/skill-bundles/byan-strict.zip +0 -0
- package/install/templates/docs/leantime-integration.md +11 -1
- package/node_modules/byan-platform-config/lib/mcp-config.js +71 -5
- package/package.json +1 -1
- 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
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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 =
|
|
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(`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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,
|
|
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,
|
package/install/package.json
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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) {
|