create-byan-agent 2.19.2 → 2.20.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 (49) hide show
  1. package/CHANGELOG.md +148 -0
  2. package/README.md +4 -4
  3. package/install/src/byan-v2/generation/templates/default-agent.md +1 -1
  4. package/install/templates/.claude/CLAUDE.md +1 -1
  5. package/install/templates/.claude/hooks/fd-phase-guard.js +2 -2
  6. package/install/templates/.claude/hooks/mantra-validate.js +16 -8
  7. package/install/templates/.claude/hooks/strict-scope-guard.js +25 -7
  8. package/install/templates/.claude/rules/native-workflows.md +32 -0
  9. package/install/templates/.claude/skills/byan-byan/SKILL.md +5 -5
  10. package/install/templates/.claude/skills/byan-mantra-audit/SKILL.md +53 -0
  11. package/install/templates/.claude/skills/byan-merise-agile/SKILL.md +2 -2
  12. package/install/templates/.claude/skills/byan-native-dev-story/SKILL.md +83 -0
  13. package/install/templates/.claude/workflows/INDEX.md +35 -0
  14. package/install/templates/.claude/workflows/check-implementation-readiness.js +280 -0
  15. package/install/templates/.claude/workflows/code-review.js +179 -0
  16. package/install/templates/.claude/workflows/create-excalidraw-dataflow.js +214 -0
  17. package/install/templates/.claude/workflows/create-excalidraw-diagram.js +188 -0
  18. package/install/templates/.claude/workflows/create-excalidraw-flowchart.js +225 -0
  19. package/install/templates/.claude/workflows/create-excalidraw-wireframe.js +192 -0
  20. package/install/templates/.claude/workflows/create-story.js +216 -0
  21. package/install/templates/.claude/workflows/dev-story.js +100 -0
  22. package/install/templates/.claude/workflows/document-project.js +455 -0
  23. package/install/templates/.claude/workflows/qa-automate.js +169 -0
  24. package/install/templates/.claude/workflows/quick-dev.js +273 -0
  25. package/install/templates/.claude/workflows/sprint-planning.js +261 -0
  26. package/install/templates/.claude/workflows/testarch-atdd.js +287 -0
  27. package/install/templates/.claude/workflows/testarch-automate.js +229 -0
  28. package/install/templates/.claude/workflows/testarch-ci.js +184 -0
  29. package/install/templates/.claude/workflows/testarch-framework.js +267 -0
  30. package/install/templates/.claude/workflows/testarch-nfr.js +316 -0
  31. package/install/templates/.claude/workflows/testarch-test-design.js +293 -0
  32. package/install/templates/.claude/workflows/testarch-test-review.js +321 -0
  33. package/install/templates/.claude/workflows/testarch-trace.js +316 -0
  34. package/install/templates/.githooks/pre-commit +49 -15
  35. package/install/templates/_byan/config.yaml +15 -5
  36. package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-build-workflows.js +20 -0
  37. package/install/templates/_byan/mcp/byan-mcp-server/bin/byan-lint-workflows.js +57 -0
  38. package/install/templates/_byan/mcp/byan-mcp-server/lib/native-loop.js +39 -0
  39. package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-generator.js +149 -0
  40. package/install/templates/_byan/mcp/byan-mcp-server/lib/workflows-lint.js +113 -0
  41. package/install/templates/_byan/workflow/simple/byan/feature-workflow.md +14 -11
  42. package/install/templates/docs/native-workflows-contract.md +84 -0
  43. package/package.json +2 -2
  44. package/src/byan-v2/data/agent-scopes.json +46 -0
  45. package/src/byan-v2/data/mantras.json +194 -8
  46. package/src/byan-v2/generation/mantra-audit.js +147 -0
  47. package/src/byan-v2/generation/mantra-validator.js +56 -6
  48. package/src/byan-v2/generation/scope-resolver.js +102 -0
  49. package/src/byan-v2/generation/templates/default-agent.md +1 -1
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env bash
2
- # BYAN pre-commit hook enforce mantras score >= 80% on staged agent
3
- # and skill files. Blocks the commit if any file drops below the
4
- # threshold so the user can't accidentally push non-compliant artefacts.
2
+ # BYAN pre-commit hook. Three gates run in order:
3
+ # 1. Strict Mode gate : block if a strict session is engaged but not completed.
4
+ # 2. Native-workflow lint : block if a .claude/workflows/*.js couples to state.
5
+ # 3. Mantra floor : block if a Gen3 persona source scores below the floor.
5
6
  #
6
7
  # Install :
7
8
  # git config core.hooksPath .githooks
@@ -9,16 +10,22 @@
9
10
  # Bypass (emergency only) :
10
11
  # git commit --no-verify
11
12
  #
12
- # Scope : only files matching
13
- # _byan/bmb/agents/*.md, _byan/agents/*.md,
14
- # .github/agents/*.md, .claude/skills/*/SKILL.md,
15
- # .claude/agents/*.md
16
- # are validated. Non-agent files are ignored.
13
+ # Mantra gate scope : the canonical Gen3 persona SOURCES
14
+ # _byan/agent/<name>/<name>.md
15
+ # are validated with DOMAIN-AWARE scoring (each persona is scored only against
16
+ # the mantras applicable to its declared scope, via scope-resolver). Deployed
17
+ # artifacts (.github/agents, .claude/skills, .claude/agents) are derived loader
18
+ # stubs or non-persona procedures; the non-blocking Stop hook (mantra-validate.js)
19
+ # warns on those. THRESHOLD is an anti-stub FLOOR, not a quality bar: it catches
20
+ # near-empty / zombie persona files. Genuine personas score well above it; deep
21
+ # quality is the job of the semantic embodiment audit (src/byan-v2/generation/mantra-audit.js),
22
+ # which runs out-of-band, not at commit time.
17
23
 
18
24
  set -euo pipefail
19
25
 
20
- THRESHOLD=80
26
+ THRESHOLD=30
21
27
  VALIDATOR="src/byan-v2/generation/mantra-validator.js"
28
+ RESOLVER="src/byan-v2/generation/scope-resolver.js"
22
29
 
23
30
  if ! command -v node >/dev/null 2>&1; then
24
31
  echo "[byan pre-commit] node not found, skipping mantra check"
@@ -40,11 +47,27 @@ if [ -f "$STRICT_GATE" ]; then
40
47
  fi
41
48
  fi
42
49
 
50
+ # Native workflow lint — a .claude/workflows/*.js script runs outside the
51
+ # conversation turn where the strict/FD hooks fire, so the surviving net is this
52
+ # gate : a native script must not couple directly to BYAN state internals
53
+ # (import/require lib/fd-state.js or the strict-mode lib). State goes through the
54
+ # byan_fd_* / byan_strict_* MCP tools. No-op if the linter is absent.
55
+ WF_LINT="_byan/mcp/byan-mcp-server/bin/byan-lint-workflows.js"
56
+ if [ -f "$WF_LINT" ]; then
57
+ if ! node "$WF_LINT" --root "$(git rev-parse --show-toplevel)"; then
58
+ echo ""
59
+ echo "Commit blocked : native workflow script couples to BYAN state internals."
60
+ echo "Use the byan_fd_* / byan_strict_* MCP tools instead of importing lib/fd-state.js."
61
+ echo "Bypass with 'git commit --no-verify' (emergency only)."
62
+ exit 1
63
+ fi
64
+ fi
65
+
43
66
  if [ ! -f "$VALIDATOR" ]; then
44
67
  exit 0
45
68
  fi
46
69
 
47
- staged=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^(_byan/bmb/agents/.*\.md|_byan/agents/.*\.md|\.github/agents/.*\.md|\.claude/skills/.*SKILL\.md|\.claude/agents/.*\.md)$' || true)
70
+ staged=$(git diff --cached --name-only --diff-filter=ACM | grep -E '^_byan/agent/[^/]+/[^/]+\.md$' || true)
48
71
 
49
72
  if [ -z "$staged" ]; then
50
73
  exit 0
@@ -55,13 +78,23 @@ while IFS= read -r file; do
55
78
  [ -z "$file" ] && continue
56
79
  [ ! -f "$file" ] && continue
57
80
 
81
+ # Skip dev/test/variant artifacts that are not shipped personas.
82
+ case "$file" in
83
+ *test*|*optimized*|*turbo-whisper*) continue ;;
84
+ esac
85
+
86
+ # Domain-aware score : resolve the persona's scope set, then score only the
87
+ # applicable mantras (universal + the persona's domain, behavioral excluded).
58
88
  score=$(node -e "
59
89
  const V = require('./$VALIDATOR');
90
+ const R = require('./$RESOLVER');
60
91
  const fs = require('fs');
92
+ const path = require('path');
61
93
  try {
62
94
  const content = fs.readFileSync('$file', 'utf8');
63
- const v = new V();
64
- const res = v.validate(content);
95
+ const name = path.basename('$file').replace(/\\.md\$/, '');
96
+ const scopes = R.resolveAgentScopes({ name, content });
97
+ const res = new V().validate(content, { scope: scopes });
65
98
  const pct = Math.round((res.compliant.length / res.totalMantras) * 100);
66
99
  process.stdout.write(String(pct));
67
100
  } catch (e) {
@@ -75,15 +108,16 @@ while IFS= read -r file; do
75
108
  fi
76
109
 
77
110
  if [ "$score" -lt "$THRESHOLD" ]; then
78
- echo "[byan pre-commit] FAIL $file : mantra score $score% < $THRESHOLD%"
111
+ echo "[byan pre-commit] FAIL $file : mantra score $score% < $THRESHOLD% (anti-stub floor)"
79
112
  failed=1
80
113
  fi
81
114
  done <<< "$staged"
82
115
 
83
116
  if [ "$failed" -eq 1 ]; then
84
117
  echo ""
85
- echo "Commit blocked by BYAN mantra pre-commit hook."
86
- echo "Fix the flagged files above, or bypass with 'git commit --no-verify' (emergency only)."
118
+ echo "Commit blocked by BYAN mantra pre-commit floor."
119
+ echo "The flagged persona scores below the anti-stub floor : flesh it out, or bypass"
120
+ echo "with 'git commit --no-verify' (emergency only)."
87
121
  exit 1
88
122
  fi
89
123
 
@@ -58,15 +58,25 @@ bmad_features:
58
58
  validate_at_phase_end: true
59
59
  auto_summarize: true
60
60
 
61
- # MantraValidator: Validate agents against 64 mantras
61
+ # MantraValidator: Validate agents against 71 mantras
62
62
  mantras:
63
63
  validate: true
64
- min_score: 80
64
+ # Domain-aware anti-stub floor for the pre-commit gate and Stop hook. Each
65
+ # persona is scored only against its applicable mantras (universal + its
66
+ # declared scope) via src/byan-v2/generation/scope-resolver.js. This is a
67
+ # floor that catches empty / zombie personas, not a deep quality bar; deep
68
+ # embodiment is the out-of-band audit (src/byan-v2/generation/mantra-audit.js).
69
+ min_score: 30
65
70
  enforce_on_generation: true
66
71
  fail_on_low_score: false # If true, fails generation on low score
67
- categories:
68
- - merise-agile
69
- - ia
72
+ # Mantra scopes (revived from the former dead `categories` list). The
73
+ # per-agent scope map lives in src/byan-v2/data/agent-scopes.json.
74
+ scopes:
75
+ - universal
76
+ - sdlc-process
77
+ - sdlc-code
78
+ - sdlc-modeling
79
+ - sdlc-test
70
80
 
71
81
  # VoiceIntegration: Turbo Whisper voice input for hands-free interaction
72
82
  voice_integration:
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import { buildWorkflowsRegistry } from '../lib/workflows-generator.js';
3
+
4
+ // Regenerate .claude/workflows/INDEX.md from the workflow manifest.
5
+ // Usage: node bin/byan-build-workflows.js [--root <dir>]
6
+
7
+ function parseArgs(argv) {
8
+ const args = {};
9
+ for (let i = 2; i < argv.length; i++) {
10
+ if (argv[i] === '--root') args.projectRoot = argv[++i];
11
+ }
12
+ return args;
13
+ }
14
+
15
+ const r = buildWorkflowsRegistry(parseArgs(process.argv));
16
+ const c = r.counts;
17
+ process.stdout.write(
18
+ `[byan-build-workflows] ${r.written ? 'wrote' : 'unchanged'} ${r.path} ` +
19
+ `(autonomous ${c.autonomous}, pipeline ${c.pipeline})\n`
20
+ );
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { validateContract } from '../lib/workflows-lint.js';
6
+
7
+ // Validate native workflow scripts under .claude/workflows/ against the full
8
+ // contract (state-coupling + clock/RNG + meta-literal) AND node --check syntax.
9
+ // Exits non-zero on any violation (used by the pre-commit gate).
10
+ // Usage: node bin/byan-lint-workflows.js [--root <dir>]
11
+
12
+ function parseArgs(argv) {
13
+ const args = {};
14
+ for (let i = 2; i < argv.length; i++) {
15
+ if (argv[i] === '--root') args.projectRoot = argv[++i];
16
+ }
17
+ return args;
18
+ }
19
+
20
+ const args = parseArgs(process.argv);
21
+ const root = args.projectRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd();
22
+ const dir = path.join(root, '.claude', 'workflows');
23
+
24
+ let files = [];
25
+ try {
26
+ files = fs.readdirSync(dir, { withFileTypes: true })
27
+ .filter((e) => e.isFile() && e.name.endsWith('.js'))
28
+ .map((e) => path.join(dir, e.name));
29
+ } catch (_e) {
30
+ process.stdout.write('[byan-lint-workflows] no .claude/workflows/ directory - nothing to lint\n');
31
+ process.exit(0);
32
+ }
33
+
34
+ let failed = 0;
35
+ for (const file of files) {
36
+ const violations = validateContract(fs.readFileSync(file, 'utf8'));
37
+
38
+ // Syntax gate: a native script must parse.
39
+ try {
40
+ execFileSync('node', ['--check', file], { stdio: 'pipe' });
41
+ } catch (e) {
42
+ violations.push({ id: 'node-check', msg: `node --check failed: ${String(e.stderr || e.message).split('\n')[0]}` });
43
+ }
44
+
45
+ if (violations.length) {
46
+ failed += 1;
47
+ for (const v of violations) {
48
+ process.stderr.write(`[byan-lint-workflows] ${file}: ${v.id} - ${v.msg}\n`);
49
+ }
50
+ }
51
+ }
52
+
53
+ if (failed === 0) {
54
+ process.stdout.write(`[byan-lint-workflows] OK - ${files.length} native workflows pass the contract (state, clock/RNG, meta, node --check)\n`);
55
+ process.exit(0);
56
+ }
57
+ process.exit(1);
@@ -0,0 +1,39 @@
1
+ // Pure, tested helpers for native-workflow RGR (red-green-refactor) loops.
2
+ //
3
+ // The in-CLI Workflow runtime sandbox forbids import/require/fs INSIDE a
4
+ // .claude/workflows/*.js script, so a native script MUST inline these tiny
5
+ // functions verbatim. This module is the canonical, unit-tested reference; the
6
+ // inlined copies in scripts must mirror it. Keeping the logic here (and tested)
7
+ // is what lets the doc-only "3 consecutive failures -> HALT" rule become a real,
8
+ // verifiable JS counter.
9
+
10
+ export const DEFAULT_MAX_CYCLES = 3;
11
+
12
+ // Decide whether the RGR loop must stop.
13
+ // green=true -> done, not aborted (story task is green)
14
+ // not green, cycles >= cap -> done, aborted (no convergence; hard exit)
15
+ // otherwise -> keep looping
16
+ // Returns { done, abort, reason }.
17
+ export function convergenceGuard({ cycles, green, maxCycles = DEFAULT_MAX_CYCLES }) {
18
+ if (green) return { done: true, abort: false, reason: 'green' };
19
+ if (cycles >= maxCycles) {
20
+ return { done: true, abort: true, reason: `no convergence after ${maxCycles} cycles` };
21
+ }
22
+ return { done: false, abort: false, reason: 'continue' };
23
+ }
24
+
25
+ // Structured verdict a native dev-story run returns to the orchestrating skill
26
+ // for the human gate. Pure constructor — no side effects, no state mutation.
27
+ export function buildVerdict({ storyKey, green, cycles, blocking = [], maxCycles = DEFAULT_MAX_CYCLES }) {
28
+ const aborted = !green && cycles >= maxCycles;
29
+ return {
30
+ workflow: 'dev-story',
31
+ storyKey: storyKey || null,
32
+ status: green ? 'review-ready' : aborted ? 'aborted-no-convergence' : 'in-progress',
33
+ green: Boolean(green),
34
+ cycles,
35
+ maxCycles,
36
+ blocking: Array.isArray(blocking) ? blocking : [],
37
+ needsHumanGate: true,
38
+ };
39
+ }
@@ -0,0 +1,149 @@
1
+ // BYAN native-workflow registry generator + dual-path resolver.
2
+ //
3
+ // Phase 1 of the native-workflow bridge: BYAN's markdown/YAML workflows are
4
+ // LLM-interpreted and human-gated; Claude Code's in-CLI Workflow tool runs a
5
+ // deterministic JS script with no in-run human gate. Only the workflows whose
6
+ // steps run WITHOUT a per-step human gate (autonomous + deterministic pipeline)
7
+ // can be hosted by the native tool. The gated majority stays markdown by design.
8
+ //
9
+ // This module is the single source of truth for "which workflows are portable"
10
+ // and "where does a workflow physically live" (dual-path: native .js preferred,
11
+ // markdown fallback). It is manifest-driven and deterministic so regeneration is
12
+ // idempotent (writeIfChanged). It mirrors the index-generator pattern and reuses
13
+ // its CSV loader — no duplicate parser.
14
+
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ import { loadManifests } from './index-generator.js';
18
+
19
+ function resolveRoot(projectRoot) {
20
+ return projectRoot || process.env.CLAUDE_PROJECT_DIR || process.cwd();
21
+ }
22
+
23
+ // Portable buckets, derived from the read-based classification of the 45-row
24
+ // manifest (see docs/native-workflows-contract.md). Membership is data, not a
25
+ // guess: each name must also exist in the manifest or it is surfaced as drift.
26
+ export const PORTABLE = {
27
+ autonomous: [
28
+ 'dev-story',
29
+ 'create-story',
30
+ 'qa-automate',
31
+ 'testarch-atdd',
32
+ 'testarch-automate',
33
+ 'testarch-ci',
34
+ 'testarch-framework',
35
+ 'testarch-nfr',
36
+ 'testarch-test-design',
37
+ 'testarch-test-review',
38
+ 'testarch-trace',
39
+ ],
40
+ pipeline: [
41
+ 'sprint-planning',
42
+ 'code-review',
43
+ 'document-project',
44
+ 'check-implementation-readiness',
45
+ 'quick-dev',
46
+ 'create-excalidraw-diagram',
47
+ 'create-excalidraw-dataflow',
48
+ 'create-excalidraw-flowchart',
49
+ 'create-excalidraw-wireframe',
50
+ ],
51
+ };
52
+
53
+ // Bucket of a workflow name. 'gated' = stays LLM-interpreted markdown.
54
+ export function classify(name) {
55
+ if (PORTABLE.autonomous.includes(name)) return 'autonomous';
56
+ if (PORTABLE.pipeline.includes(name)) return 'pipeline';
57
+ return 'gated';
58
+ }
59
+
60
+ function nativeRel(name) {
61
+ return `.claude/workflows/${name}.js`;
62
+ }
63
+
64
+ // Dual-path resolver. Prefers the native script .claude/workflows/<name>.js if
65
+ // it exists, else falls back to the markdown workflow path from the manifest.
66
+ // Pure existence check, no side effects. Returns {name, kind, rel, path} or null.
67
+ export function resolveWorkflow(name, { projectRoot, workflows } = {}) {
68
+ const root = resolveRoot(projectRoot);
69
+ const nativeAbs = path.join(root, '.claude', 'workflows', `${name}.js`);
70
+ if (fs.existsSync(nativeAbs)) {
71
+ return { name, kind: 'native', rel: nativeRel(name), path: nativeAbs };
72
+ }
73
+ const rows = workflows || loadManifests(root).workflows;
74
+ const row = rows.find((w) => w.name === name);
75
+ if (row && row.path) {
76
+ return { name, kind: 'markdown', rel: row.path, path: path.join(root, row.path) };
77
+ }
78
+ return null;
79
+ }
80
+
81
+ // Render the human/agent-readable registry of portable workflows. Deterministic:
82
+ // sorted, no timestamp. native/markdown status reflects which .js exist on disk.
83
+ export function renderRegistry({ workflows = [], projectRoot } = {}) {
84
+ const root = resolveRoot(projectRoot);
85
+ const byName = new Map(workflows.map((w) => [w.name, w]));
86
+ const lines = [];
87
+ lines.push('# BYAN Native Workflows');
88
+ lines.push('');
89
+ lines.push("> Registre des workflows portables vers l'outil Workflow natif de Claude Code.");
90
+ lines.push('> Genere automatiquement — ne pas editer a la main. Source : `_byan/_config/workflow-manifest.csv`.');
91
+ lines.push('> Regenerer : `node _byan/mcp/byan-mcp-server/bin/byan-build-workflows.js`.');
92
+ lines.push('>');
93
+ lines.push("> Resolution dual-path : le skill prefere `.claude/workflows/<name>.js` s'il existe,");
94
+ lines.push('> sinon il retombe sur le workflow markdown du manifest. Les workflows gated (a gate');
95
+ lines.push('> humain par etape) restent markdown interprete — ils ne sont pas portables.');
96
+ lines.push('');
97
+
98
+ for (const bucket of ['autonomous', 'pipeline']) {
99
+ const names = PORTABLE[bucket].filter((n) => byName.has(n)).slice().sort();
100
+ lines.push(`## ${bucket} (${names.length})`);
101
+ lines.push('');
102
+ for (const name of names) {
103
+ const nativeAbs = path.join(root, '.claude', 'workflows', `${name}.js`);
104
+ const status = fs.existsSync(nativeAbs) ? 'native' : 'markdown';
105
+ const src = byName.get(name).path || '';
106
+ lines.push(`- \`${name}\` — ${status} — source \`${src}\``);
107
+ }
108
+ lines.push('');
109
+ }
110
+
111
+ // Drift guard: a portable name absent from the manifest is a real problem
112
+ // (renamed/removed upstream). Surface it instead of silently dropping it.
113
+ const declared = [...PORTABLE.autonomous, ...PORTABLE.pipeline];
114
+ const missing = declared.filter((n) => !byName.has(n)).slice().sort();
115
+ if (missing.length) {
116
+ lines.push('## drift (noms portables absents du manifest)');
117
+ lines.push('');
118
+ for (const n of missing) lines.push(`- \`${n}\``);
119
+ lines.push('');
120
+ }
121
+
122
+ return lines.join('\n');
123
+ }
124
+
125
+ function writeIfChanged(filePath, content) {
126
+ const prev = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : null;
127
+ if (prev === content) return false;
128
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
129
+ fs.writeFileSync(filePath, content);
130
+ return true;
131
+ }
132
+
133
+ // Build (or refresh) .claude/workflows/INDEX.md from the manifest. Idempotent.
134
+ export function buildWorkflowsRegistry({ projectRoot } = {}) {
135
+ const root = resolveRoot(projectRoot);
136
+ const { workflows } = loadManifests(root);
137
+ const content = renderRegistry({ workflows, projectRoot: root });
138
+ const indexPath = path.join(root, '.claude', 'workflows', 'INDEX.md');
139
+ const written = writeIfChanged(indexPath, content);
140
+ const inManifest = (n) => workflows.some((w) => w.name === n);
141
+ return {
142
+ written,
143
+ path: indexPath,
144
+ counts: {
145
+ autonomous: PORTABLE.autonomous.filter(inManifest).length,
146
+ pipeline: PORTABLE.pipeline.filter(inManifest).length,
147
+ },
148
+ };
149
+ }
@@ -0,0 +1,113 @@
1
+ // Linter for native workflow scripts (enforcement-bridge F3).
2
+ //
3
+ // A .claude/workflows/*.js runs OUTSIDE the conversation turn, so BYAN's
4
+ // main-thread hooks (strict-scope-guard, strict-stop-guard, fd-phase-guard) do
5
+ // not fire for it. The structural net that survives is this lint + the
6
+ // pre-commit gate: a native script must NOT couple directly to BYAN state
7
+ // internals. State goes through the byan_fd_* / byan_strict_* MCP tools.
8
+ //
9
+ // Forbidden: importing/requiring lib/fd-state.js (or the strict-mode lib).
10
+ // Comments are stripped before matching so the contract comment in a script
11
+ // that NAMES fd-state.js (to explain the rule) does not self-trip.
12
+
13
+ import fs from 'node:fs';
14
+ import path from 'node:path';
15
+
16
+ // Strip /* block */ and // line comments. Preserve "://" inside strings (URLs)
17
+ // by only treating // as a comment when not preceded by a colon.
18
+ export function stripComments(src) {
19
+ let s = String(src).replace(/\/\*[\s\S]*?\*\//g, '');
20
+ s = s.replace(/(^|[^:])\/\/[^\n]*/g, '$1');
21
+ return s;
22
+ }
23
+
24
+ const RULES = [
25
+ {
26
+ id: 'import-fd-state',
27
+ re: /\bimport\b[^\n;]*?from\s*['"][^'"]*fd-state[^'"]*['"]/,
28
+ msg: 'import of fd-state is forbidden; mutate FD state via the byan_fd_* MCP tools',
29
+ },
30
+ {
31
+ id: 'require-fd-state',
32
+ re: /\brequire\s*\(\s*['"][^'"]*fd-state[^'"]*['"]\s*\)/,
33
+ msg: 'require of fd-state is forbidden; mutate FD state via the byan_fd_* MCP tools',
34
+ },
35
+ {
36
+ id: 'dynamic-import-fd-state',
37
+ re: /\bimport\s*\(\s*['"][^'"]*fd-state[^'"]*['"]\s*\)/,
38
+ msg: 'dynamic import of fd-state is forbidden; mutate FD state via the byan_fd_* MCP tools',
39
+ },
40
+ {
41
+ id: 'import-strict-mode-lib',
42
+ re: /\b(?:import\b[^\n;]*?from\s*|require\s*\(\s*|import\s*\(\s*)['"][^'"]*lib\/strict-mode[^'"]*['"]/,
43
+ msg: 'import of the strict-mode lib is forbidden; use the byan_strict_* MCP tools',
44
+ },
45
+ ];
46
+
47
+ // Lint one script's source for state coupling. Comment-stripped so a contract
48
+ // comment that NAMES fd-state does not self-trip. Returns [{ id, msg }].
49
+ export function lintSource(src) {
50
+ const code = stripComments(src);
51
+ const out = [];
52
+ for (const rule of RULES) {
53
+ if (rule.re.test(code)) out.push({ id: rule.id, msg: rule.msg });
54
+ }
55
+ return out;
56
+ }
57
+
58
+ // Wall-clock / RNG primitives break the Workflow runtime's resume (the launch
59
+ // validator rejects them). It scans the RAW text, so a token in a COMMENT or a
60
+ // string literal breaks invocation just the same - we therefore check the raw
61
+ // source, NOT the comment-stripped one. This is the exact failure that a manual
62
+ // review caught while porting; mechanizing it here keeps it from recurring.
63
+ const CLOCK_RNG_RE = /Date\.now|Math\.random|new Date/;
64
+
65
+ export function clockRngViolations(src) {
66
+ const m = String(src).match(CLOCK_RNG_RE);
67
+ if (!m) return [];
68
+ return [{
69
+ id: 'clock-or-rng',
70
+ msg: `wall-clock/RNG token "${m[0]}" is forbidden anywhere in a native workflow (breaks resume; the launch validator scans raw text - even comments and strings). Pass timestamps/ids via args.`,
71
+ }];
72
+ }
73
+
74
+ // A native workflow script must start with a pure `export const meta = {` literal
75
+ // (after an optional shebang and blank lines). Otherwise the launch validator
76
+ // rejects it.
77
+ export function metaLiteralViolations(src) {
78
+ const body = String(src).replace(/^/, '');
79
+ const firstReal = body
80
+ .split('\n')
81
+ .map((l) => l.trim())
82
+ .find((l) => l.length > 0 && !l.startsWith('#!'));
83
+ if (firstReal && /^export const meta\s*=\s*\{/.test(firstReal)) return [];
84
+ return [{
85
+ id: 'meta-literal-first',
86
+ msg: 'a native workflow script must begin with `export const meta = {` (pure literal) after an optional shebang',
87
+ }];
88
+ }
89
+
90
+ // Full native-workflow contract: state-coupling (comment-stripped) + clock/RNG
91
+ // (raw) + meta-literal-first. Returns the combined [{ id, msg }] violations.
92
+ export function validateContract(src) {
93
+ return [...lintSource(src), ...clockRngViolations(src), ...metaLiteralViolations(src)];
94
+ }
95
+
96
+ // Lint every *.js in a directory (non-recursive; native workflows are flat)
97
+ // against the FULL contract. Returns [{ file, violations }] for offending files.
98
+ export function lintWorkflowsDir(dir) {
99
+ let entries;
100
+ try {
101
+ entries = fs.readdirSync(dir, { withFileTypes: true });
102
+ } catch (_e) {
103
+ return [];
104
+ }
105
+ const results = [];
106
+ for (const e of entries) {
107
+ if (!e.isFile() || !e.name.endsWith('.js')) continue;
108
+ const file = path.join(dir, e.name);
109
+ const violations = validateContract(fs.readFileSync(file, 'utf8'));
110
+ if (violations.length) results.push({ file, violations });
111
+ }
112
+ return results;
113
+ }
@@ -27,7 +27,7 @@ INIT
27
27
  → DISPATCH (Worker: EconomicDispatcher — quelle brique BYAN ?)
28
28
  → BUILD (Agent ou Worker selon complexité)
29
29
  → REVIEW (Agent: Quinn — pre-flight humain vs critères VALIDATE)
30
- → VALIDATE (MantraValidator + tests — score ≥ 80%)
30
+ → VALIDATE (MantraValidator domain-aware + tests — score ≥ 30 floor anti-stub)
31
31
  ├─ OK → DOC (Agent: Paige — documenter ce qui a été livré)
32
32
  └─ KO → REFACTOR (boucle vers BUILD avec correctifs ciblés)
33
33
  → COMPLETED
@@ -105,13 +105,16 @@ INIT
105
105
  **Qui :** Worker — EconomicDispatcher logic
106
106
  **Rôle :** Pour chaque feature du backlog, déterminer quelle brique BYAN est impliquée.
107
107
 
108
- **Matrice de dispatch :**
108
+ **Matrice de dispatch** (source de vérité : `byan_dispatch` MCP / `_byan/mcp/byan-mcp-server/lib/dispatch.js`) :
109
109
 
110
- | Score complexité | Type | Exemples |
111
- |-----------------|------|---------|
112
- | < 30 | Worker (existant ou nouveau) | Format, recherche, liste |
113
- | 30–60 | Agent Sonnet (existant ou nouveau) | Implémentation, création |
114
- | 60 | Agent Opus (existant ou nouveau) | Architecture, stratégie, analyse |
110
+ | Score complexité | Route | Stratégie |
111
+ |------------------|-------|-----------|
112
+ | < 15 | `main-thread` | Inline dans le contexte courant, zéro overhead de délégation |
113
+ | < 40 + parallélisable | `agent-subagent-worktree` | Agent tool Claude Code avec isolation worktree |
114
+ | < 40 séquentiel | `mcp-worker-haiku` | Worker Haiku léger via MCP |
115
+ | ≥ 40 | `main-thread-opus` | Garde en main thread, raisonnement Opus |
116
+
117
+ > Le score (0-100) est estimé depuis la complexité de la tâche (longueur si absent). Appeler `byan_dispatch` pour le calcul — ne pas réinventer les seuils ici.
115
118
 
116
119
  **Questions posées pour chaque feature :**
117
120
  1. Un **Agent existant** peut-il gérer ça ? (lister les candidats)
@@ -136,7 +139,7 @@ INIT
136
139
 
137
140
  ## Étape 5 : BUILD
138
141
 
139
- **Qui :** Agent (Sonnet/Opus) ou Worker selon score dispatch
142
+ **Qui :** Agent ou worker selon la route `byan_dispatch` (voir la matrice Étape 4)
140
143
  **Rôle :** Implémenter la feature — code, agent, workflow, ou context.
141
144
 
142
145
  **Règles BUILD :**
@@ -165,7 +168,7 @@ INIT
165
168
  **Protocole :**
166
169
  1. Charger les critères VALIDATE attendus :
167
170
  - Tests prévus pour cette feature (liste TDD de l'étape BUILD)
168
- - Score MantraValidator cible (≥ 80%)
171
+ - Score MantraValidator cible (≥ 30, floor anti-stub domain-aware)
169
172
  - Mantras les plus à risque selon le type de changement
170
173
  2. Quinn (ou reviewer) inspecte le diff :
171
174
  - Lisibilité, nommage, taille des fonctions
@@ -197,13 +200,13 @@ INIT
197
200
 
198
201
  **Protocole :**
199
202
  1. Lancer `npm test` — tous les tests doivent passer (zéro régression)
200
- 2. Score MantraValidator ≥ 80%
203
+ 2. Score MantraValidator domain-aware 30 (floor anti-stub)
201
204
  3. Fact-check final sur tout claim absolu introduit dans la doc
202
205
  4. BYAN challenge la feature une dernière fois :
203
206
  - "Est-ce que c'est la solution la plus simple ?" (mantra #37)
204
207
  - "Quelles sont les conséquences non voulues ?" (mantra #39)
205
208
  5. **Décision binaire :**
206
- - Tests verts ET score ≥ 80% ET fact-check OK → `VALIDATE: OK` → étape DOC
209
+ - Tests verts ET score ≥ 30 (floor anti-stub) ET fact-check OK → `VALIDATE: OK` → étape DOC
207
210
  - Sinon → `VALIDATE: KO` → étape REFACTOR
208
211
 
209
212
  **Output :** Verdict `{ status: "OK" | "KO", tests, mantra_score, blocking_issues }`