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
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // mantra-audit — semantic embodiment audit for BYAN personas (Option C, N2).
4
+ //
5
+ // Lives under src/byan-v2/generation/ so it ships with the v2 runtime (the
6
+ // installer copies src/ into the user project) next to the validator and
7
+ // scope-resolver it depends on. Invoke with:
8
+ // node src/byan-v2/generation/mantra-audit.js prepare <agentFile> [--json]
9
+ // node src/byan-v2/generation/mantra-audit.js score <agentFile> <verdicts.json>
10
+ //
11
+ // The pre-commit mantra gate is a fast, deterministic anti-stub FLOOR : it asks
12
+ // "does this persona contain the vocabulary of its domain mantras". That catches
13
+ // empty/zombie files but does NOT measure whether the agent genuinely EMBODIES a
14
+ // mantra. This tool is the deeper, out-of-band layer : it prepares a judgment
15
+ // packet (the applicable mantras + the persona + a rubric) for an LLM judge, then
16
+ // turns the judge's verdicts into an embodiment score. It is deliberately NOT in
17
+ // the commit path : the judgment is semantic (an LLM call), so it runs on demand
18
+ // or in CI, outside the commit path (a non-deterministic check must not block it).
19
+ //
20
+ // verdicts.json : { "<mantraId>": "embodied" | "partial" | "absent", ... }
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+ const MantraValidator = require('./mantra-validator');
25
+ const resolver = require('./scope-resolver');
26
+
27
+ const VERDICT_WEIGHT = { embodied: 1, partial: 0.5, absent: 0 };
28
+
29
+ function buildPacket(agentFile) {
30
+ const content = fs.readFileSync(agentFile, 'utf8');
31
+ const name = path.basename(agentFile).replace(/\.md$/, '');
32
+ const scopes = resolver.resolveAgentScopes({ name, content });
33
+ const applicable = new MantraValidator().applicableMantras(scopes);
34
+ return {
35
+ agent: name,
36
+ file: agentFile,
37
+ scopes,
38
+ mantras: applicable.map(m => ({
39
+ id: m.id,
40
+ title: m.title,
41
+ description: m.description,
42
+ scope: m.scope || 'universal',
43
+ priority: m.priority,
44
+ })),
45
+ persona: content,
46
+ };
47
+ }
48
+
49
+ function buildPrompt(packet) {
50
+ const lines = [];
51
+ lines.push('You are auditing whether a BYAN agent persona EMBODIES its applicable mantras.');
52
+ lines.push('Judge embodiment, not vocabulary : a mantra is embodied when the persona\'s role,');
53
+ lines.push('instructions, and red-lines actually enact the principle, even if the exact keyword');
54
+ lines.push('is absent. For each mantra return one verdict : embodied | partial | absent.');
55
+ lines.push('');
56
+ lines.push(`Agent : ${packet.agent} Scopes : ${packet.scopes.join(', ')}`);
57
+ lines.push('');
58
+ lines.push('Applicable mantras :');
59
+ for (const m of packet.mantras) {
60
+ lines.push(`- ${m.id} (${m.scope}, ${m.priority}) : ${m.title} -- ${m.description}`);
61
+ }
62
+ lines.push('');
63
+ lines.push('Persona under audit :');
64
+ lines.push('"""');
65
+ lines.push(packet.persona.trim());
66
+ lines.push('"""');
67
+ lines.push('');
68
+ lines.push('Return strict JSON mapping every mantra id to its verdict, e.g.');
69
+ lines.push('{ "IA-16": "embodied", "M37": "partial", "IA-2": "absent" }');
70
+ return lines.join('\n');
71
+ }
72
+
73
+ function scoreVerdicts(packet, verdicts) {
74
+ const ids = packet.mantras.map(m => m.id);
75
+ let sum = 0;
76
+ const buckets = { embodied: [], partial: [], absent: [], unjudged: [] };
77
+ for (const id of ids) {
78
+ const v = verdicts[id];
79
+ if (v && Object.prototype.hasOwnProperty.call(VERDICT_WEIGHT, v)) {
80
+ sum += VERDICT_WEIGHT[v];
81
+ buckets[v].push(id);
82
+ } else {
83
+ buckets.unjudged.push(id);
84
+ }
85
+ }
86
+ const total = ids.length;
87
+ const embodimentScore = total > 0 ? Math.round((sum / total) * 100) : 0;
88
+ return {
89
+ agent: packet.agent,
90
+ scopes: packet.scopes,
91
+ total,
92
+ embodimentScore,
93
+ embodied: buckets.embodied,
94
+ partial: buckets.partial,
95
+ absent: buckets.absent,
96
+ unjudged: buckets.unjudged,
97
+ };
98
+ }
99
+
100
+ function main(argv) {
101
+ const [cmd, agentFile, arg3] = argv;
102
+
103
+ if (!cmd || !agentFile || ['-h', '--help'].includes(cmd)) {
104
+ process.stdout.write(
105
+ 'Usage :\n' +
106
+ ' node src/byan-v2/generation/mantra-audit.js prepare <agentFile> [--json]\n' +
107
+ ' node src/byan-v2/generation/mantra-audit.js score <agentFile> <verdicts.json>\n'
108
+ );
109
+ return cmd ? 0 : 1;
110
+ }
111
+
112
+ if (!fs.existsSync(agentFile)) {
113
+ process.stderr.write(`Agent file not found : ${agentFile}\n`);
114
+ return 1;
115
+ }
116
+
117
+ const packet = buildPacket(agentFile);
118
+
119
+ if (cmd === 'prepare') {
120
+ if (arg3 === '--json') {
121
+ process.stdout.write(JSON.stringify(packet, null, 2) + '\n');
122
+ } else {
123
+ process.stdout.write(buildPrompt(packet) + '\n');
124
+ }
125
+ return 0;
126
+ }
127
+
128
+ if (cmd === 'score') {
129
+ if (!arg3 || !fs.existsSync(arg3)) {
130
+ process.stderr.write('A verdicts JSON file is required : score <agentFile> <verdicts.json>\n');
131
+ return 1;
132
+ }
133
+ const verdicts = JSON.parse(fs.readFileSync(arg3, 'utf8'));
134
+ const result = scoreVerdicts(packet, verdicts);
135
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
136
+ return 0;
137
+ }
138
+
139
+ process.stderr.write(`Unknown command : ${cmd}\n`);
140
+ return 1;
141
+ }
142
+
143
+ if (require.main === module) {
144
+ process.exit(main(process.argv.slice(2)));
145
+ }
146
+
147
+ module.exports = { buildPacket, buildPrompt, scoreVerdicts, VERDICT_WEIGHT };
@@ -1,6 +1,10 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
 
4
+ // Score bands, single source of truth (referenced by generateReport + export).
5
+ const PASS_THRESHOLD = 80;
6
+ const WARNING_THRESHOLD = 60;
7
+
4
8
  class MantraValidator {
5
9
  constructor(mantrasData = null) {
6
10
  if (mantrasData) {
@@ -42,7 +46,28 @@ class MantraValidator {
42
46
  return strictIds.length >= 3;
43
47
  }
44
48
 
45
- validate(agentDefinition) {
49
+ // Normalize a caller-supplied scope (string | string[] | Set | null) into a
50
+ // Set, or null when no scope was given. Null means legacy all-mantras scoring.
51
+ _normalizeScope(scope) {
52
+ if (scope === null || scope === undefined) return null;
53
+ if (scope instanceof Set) return scope;
54
+ if (Array.isArray(scope)) return new Set(scope);
55
+ return new Set([scope]);
56
+ }
57
+
58
+ // A mantra is applicable to a scoped run when it is universal (or unscoped)
59
+ // or its scope is in the requested set. Behavioral mantras are runtime-
60
+ // enforced (hooks / fact-check), not declared in a persona file, so they are
61
+ // excluded from scoped scoring. With no scope (null), every mantra applies.
62
+ _isApplicable(mantra, scopeSet) {
63
+ if (scopeSet === null) return true;
64
+ if (mantra.behavioral === true) return false;
65
+ const ms = mantra.scope || 'universal';
66
+ if (ms === 'universal') return true;
67
+ return scopeSet.has(ms);
68
+ }
69
+
70
+ validate(agentDefinition, options = {}) {
46
71
  if (agentDefinition === null || agentDefinition === undefined) {
47
72
  throw new Error('Agent definition is required');
48
73
  }
@@ -59,8 +84,16 @@ class MantraValidator {
59
84
  this.mantras = this.personaMantras;
60
85
  }
61
86
 
87
+ // Domain-aware scoring : when the caller passes a scope, only the mantras
88
+ // applicable to that scope are counted (universal always counts, behavioral
89
+ // runtime-enforced mantras are excluded). With no scope, every mantra in the
90
+ // chosen ruleset is scored, byte-identical to the legacy behavior.
91
+ const scopeSet = this._normalizeScope(options.scope);
92
+ const applicableMantras = this.mantras.filter(m => this._isApplicable(m, scopeSet));
93
+
62
94
  this.results = {
63
- totalMantras: this.mantras.length,
95
+ totalMantras: applicableMantras.length,
96
+ scope: scopeSet ? [...scopeSet] : null,
64
97
  compliant: [],
65
98
  nonCompliant: [],
66
99
  warnings: [],
@@ -70,7 +103,7 @@ class MantraValidator {
70
103
 
71
104
  const startTime = Date.now();
72
105
 
73
- for (const mantra of this.mantras) {
106
+ for (const mantra of applicableMantras) {
74
107
  const result = this.checkMantra(mantra.id, agentDefinition);
75
108
 
76
109
  if (result.compliant) {
@@ -154,8 +187,17 @@ class MantraValidator {
154
187
 
155
188
  _validatePattern(content, validation, mantra) {
156
189
  try {
190
+ // Some forbidden-pattern mantras (e.g. IA-23 no-emoji) must ignore
191
+ // declared zones such as an agent's icon="..." frontmatter attribute,
192
+ // where an emoji is a legitimate display glyph, not pollution.
193
+ let scanContent = content;
194
+ if (Array.isArray(validation.ignoreZones)) {
195
+ for (const zone of validation.ignoreZones) {
196
+ scanContent = scanContent.replace(new RegExp(zone, 'g'), '');
197
+ }
198
+ }
157
199
  const regex = new RegExp(validation.pattern, validation.flags || '');
158
- const matches = content.match(regex);
200
+ const matches = scanContent.match(regex);
159
201
  const hasMatches = matches && matches.length > 0;
160
202
 
161
203
  if (validation.mustNotMatch) {
@@ -255,7 +297,7 @@ class MantraValidator {
255
297
  }
256
298
 
257
299
  const score = this.results.score;
258
- const level = score >= 80 ? 'PASS' : score >= 60 ? 'WARNING' : 'FAIL';
300
+ const level = score >= PASS_THRESHOLD ? 'PASS' : score >= WARNING_THRESHOLD ? 'WARNING' : 'FAIL';
259
301
 
260
302
  let report = '';
261
303
  report += '='.repeat(60) + '\n';
@@ -424,7 +466,7 @@ class MantraValidator {
424
466
  } else if (format === 'summary') {
425
467
  return {
426
468
  score: this.results.score,
427
- status: this.results.score >= 80 ? 'PASS' : this.results.score >= 60 ? 'WARNING' : 'FAIL',
469
+ status: this.results.score >= PASS_THRESHOLD ? 'PASS' : this.results.score >= WARNING_THRESHOLD ? 'WARNING' : 'FAIL',
428
470
  compliant: this.results.compliant.length,
429
471
  nonCompliant: this.results.nonCompliant.length,
430
472
  criticalErrors: this.results.errors.length,
@@ -443,6 +485,14 @@ class MantraValidator {
443
485
  return this.mantras.filter(m => m.category === category);
444
486
  }
445
487
 
488
+ // The mantras that apply to a given scope (universal + the scope's domain,
489
+ // behavioral excluded). With no scope, every mantra in the current ruleset.
490
+ // Shared with the domain-aware path in validate() and the embodiment audit.
491
+ applicableMantras(scope) {
492
+ const scopeSet = this._normalizeScope(scope);
493
+ return this.mantras.filter(m => this._isApplicable(m, scopeSet));
494
+ }
495
+
446
496
  getMantrasByPriority(priority) {
447
497
  return this.mantras.filter(m => m.priority === priority);
448
498
  }
@@ -0,0 +1,102 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // Domain-aware scope resolution for the mantra validator.
5
+ //
6
+ // The validator itself stays pure (it scores against a scope set it is given).
7
+ // This module owns the impure part : turning a persona FILE into its scope set,
8
+ // by reading a centralized map and, where present, an explicit frontmatter
9
+ // override. Callers (the pre-commit gate, the Stop hook, the FD VALIDATE step)
10
+ // resolve scopes here, then pass them to validator.validate(content, { scope }).
11
+
12
+ const VALID_SCOPES = ['universal', 'sdlc-process', 'sdlc-code', 'sdlc-ops', 'sdlc-modeling', 'sdlc-test', 'creative'];
13
+ const KNOWN_MODULES = ['bmm', 'cis', 'tea', 'bmb', 'core'];
14
+
15
+ function loadScopeMap(mapPath) {
16
+ const p = mapPath || path.join(__dirname, '../data/agent-scopes.json');
17
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
18
+ }
19
+
20
+ // An explicit `mantra_scopes: [a, b]` in the persona (frontmatter or body) wins
21
+ // over every derived default. Returns the listed scopes, or null when absent.
22
+ function parseFrontmatterScopes(content) {
23
+ if (typeof content !== 'string') return null;
24
+ const m = content.match(/mantra_scopes\s*:\s*\[([^\]]*)\]/);
25
+ if (!m) return null;
26
+ const list = m[1]
27
+ .split(',')
28
+ .map(s => s.trim().replace(/^['"]|['"]$/g, ''))
29
+ .filter(Boolean);
30
+ return list.length ? list : null;
31
+ }
32
+
33
+ // A Gen3 agent loads exactly one _byan/<module>/config.yaml during activation,
34
+ // so the module is derivable from the persona content. Returns null if none.
35
+ function deriveModule(content) {
36
+ if (typeof content !== 'string') return null;
37
+ const m = content.match(/_byan\/(bmm|cis|tea|bmb|core)\/config\.yaml/);
38
+ return m ? m[1] : null;
39
+ }
40
+
41
+ // The agent slug from a persona file path (basename without extension).
42
+ function agentNameFromPath(filePath) {
43
+ if (!filePath) return null;
44
+ return path.basename(filePath).replace(/\.md$/, '');
45
+ }
46
+
47
+ // Force-union 'universal' and keep only known scope names, order-stable.
48
+ function normalizeScopes(scopes) {
49
+ const seen = new Set();
50
+ const out = [];
51
+ for (const s of ['universal', ...scopes]) {
52
+ if (VALID_SCOPES.includes(s) && !seen.has(s)) {
53
+ seen.add(s);
54
+ out.push(s);
55
+ }
56
+ }
57
+ return out;
58
+ }
59
+
60
+ // Resolve the scope set for a persona. Precedence :
61
+ // 1. explicit frontmatter mantra_scopes
62
+ // 2. agentScopes[name] in the map
63
+ // 3. moduleScopes[module] derived from the content
64
+ // 4. fallback ['universal']
65
+ // 'universal' is always present in the result.
66
+ function resolveAgentScopes({ name = null, content = null, map = null } = {}) {
67
+ const scopeMap = map || loadScopeMap();
68
+
69
+ const explicit = parseFrontmatterScopes(content);
70
+ if (explicit) return normalizeScopes(explicit);
71
+
72
+ if (name && scopeMap.agentScopes && scopeMap.agentScopes[name]) {
73
+ return normalizeScopes(scopeMap.agentScopes[name]);
74
+ }
75
+
76
+ const mod = deriveModule(content);
77
+ if (mod && scopeMap.moduleScopes && scopeMap.moduleScopes[mod]) {
78
+ return normalizeScopes(scopeMap.moduleScopes[mod]);
79
+ }
80
+
81
+ return ['universal'];
82
+ }
83
+
84
+ // Convenience for file-based callers (the gate, the hooks) : read a persona
85
+ // file from disk and resolve its scopes in one call.
86
+ function resolveScopesForFile(filePath, map = null) {
87
+ const content = fs.readFileSync(filePath, 'utf8');
88
+ const name = agentNameFromPath(filePath);
89
+ return resolveAgentScopes({ name, content, map });
90
+ }
91
+
92
+ module.exports = {
93
+ VALID_SCOPES,
94
+ KNOWN_MODULES,
95
+ loadScopeMap,
96
+ parseFrontmatterScopes,
97
+ deriveModule,
98
+ agentNameFromPath,
99
+ normalizeScopes,
100
+ resolveAgentScopes,
101
+ resolveScopesForFile,
102
+ };
@@ -16,7 +16,7 @@ You must fully embody this agent's persona and follow all activation instruction
16
16
  <rules>
17
17
  <r>Communicate in {communication_language}</r>
18
18
  <r>Stay in character until EXIT</r>
19
- <r>Apply Merise Agile + TDD + 64 mantras</r>
19
+ <r>Apply Merise Agile + TDD + 71 mantras</r>
20
20
  </rules>
21
21
  </activation>
22
22