sinapse-ai 1.20.1 → 1.22.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 (122) hide show
  1. package/.claude/rules/coderabbit-integration.md +6 -0
  2. package/.claude/rules/documentation-first-reference.md +146 -0
  3. package/.claude/rules/documentation-first.md +27 -119
  4. package/.claude/rules/mandatory-delegation-reference.md +93 -0
  5. package/.claude/rules/mandatory-delegation.md +29 -93
  6. package/.claude/rules/project-intelligence-reference.md +159 -0
  7. package/.claude/rules/project-intelligence.md +37 -155
  8. package/.claude/rules/safe-collaboration-reference.md +163 -0
  9. package/.claude/rules/safe-collaboration.md +32 -173
  10. package/.sinapse-ai/core/doctor/checks/index.js +1 -1
  11. package/.sinapse-ai/core/execution/build-orchestrator.js +4 -3
  12. package/.sinapse-ai/core/execution/subagent-dispatcher.js +2 -2
  13. package/.sinapse-ai/core/ideation/ideation-engine.js +2 -1
  14. package/.sinapse-ai/core/orchestration/brownfield-progress.js +219 -0
  15. package/.sinapse-ai/core/orchestration/cli-commands.js +13 -0
  16. package/.sinapse-ai/core/orchestration/doc-first-resolver.js +96 -2
  17. package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +15 -2
  18. package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +3 -1
  19. package/.sinapse-ai/core/orchestration/greenfield-handler.js +97 -0
  20. package/.sinapse-ai/core/orchestration/index.js +5 -0
  21. package/.sinapse-ai/core/orchestration/spec-complexity.js +141 -0
  22. package/.sinapse-ai/core/orchestration/workflow-executor.js +17 -1
  23. package/.sinapse-ai/core-config.yaml +30 -0
  24. package/.sinapse-ai/data/entity-registry.yaml +81 -35
  25. package/.sinapse-ai/development/agents/architect.md +3 -15
  26. package/.sinapse-ai/development/agents/data-engineer.md +3 -15
  27. package/.sinapse-ai/development/agents/developer.md +3 -24
  28. package/.sinapse-ai/development/agents/devops.md +4 -25
  29. package/.sinapse-ai/development/agents/quality-gate.md +3 -25
  30. package/.sinapse-ai/development/knowledge-base/token-economy-guide.md +1 -1
  31. package/.sinapse-ai/development/tasks/execute-epic-plan.md +3 -0
  32. package/.sinapse-ai/development/tasks/resolve-github-issue.md +1 -1
  33. package/.sinapse-ai/development/workflows/brownfield-discovery.yaml +16 -0
  34. package/.sinapse-ai/development/workflows/epic-orchestration.yaml +11 -0
  35. package/.sinapse-ai/docs/standards/SINAPSE-LIVRO-DE-OURO-V2.1-COMPLETE.md +6 -2
  36. package/.sinapse-ai/docs/standards/SINAPSE-LIVRO-DE-OURO-V2.2-SUMMARY.md +4 -1
  37. package/.sinapse-ai/install-manifest.yaml +58 -50
  38. package/.sinapse-ai/product/templates/story-tmpl.yaml +11 -6
  39. package/.sinapse-ai/quality/judge-calibration/README.md +75 -0
  40. package/.sinapse-ai/quality/judge-calibration/calibration-log.md +70 -0
  41. package/.sinapse-ai/quality/judge-calibration/golden-set.json +105 -0
  42. package/.sinapse-ai/quality/judge-calibration/scenarios.json +90 -0
  43. package/CHANGELOG.md +55 -10
  44. package/README.en.md +1 -1
  45. package/README.md +6 -6
  46. package/bin/sinapse-minimal.js +5 -4
  47. package/docs/community/README-community-snippet-core.md +1 -1
  48. package/docs/community/README-community-snippet-mcp.md +2 -2
  49. package/docs/framework/README.md +2 -2
  50. package/docs/framework/architecture-overview.md +2 -2
  51. package/docs/framework/core-architecture.md +1 -1
  52. package/docs/framework/feature-process.md +5 -5
  53. package/docs/framework/guiding-principles.md +1 -1
  54. package/docs/framework/roadmap.md +4 -4
  55. package/docs/framework/source-tree.md +1 -1
  56. package/docs/framework/versioning-and-releases.md +1 -1
  57. package/docs/getting-started.md +1 -1
  58. package/docs/guides/agent-selection-guide.md +1 -1
  59. package/docs/guides/agents/traces/README.md +1 -1
  60. package/docs/guides/config-migration-guide.md +1 -1
  61. package/docs/guides/development-setup.md +1 -1
  62. package/docs/guides/docker-mcp-setup.md +5 -5
  63. package/docs/guides/getting-started.md +1 -1
  64. package/docs/guides/git-workflow-guide.md +3 -3
  65. package/docs/guides/ide-integration.md +3 -3
  66. package/docs/guides/ide-sync-guide.md +1 -1
  67. package/docs/guides/mcp/desktop-commander.md +2 -2
  68. package/docs/guides/mcp/docker-gateway-tutorial.md +1 -1
  69. package/docs/guides/mcp-global-setup.md +3 -3
  70. package/docs/guides/memory-intelligence-system.md +10 -10
  71. package/docs/guides/meta-agent-commands.md +1 -1
  72. package/docs/guides/quality-gates.md +1 -1
  73. package/docs/guides/security-hardening.md +1 -1
  74. package/docs/guides/service-discovery.md +1 -1
  75. package/docs/guides/squad-examples/README.md +1 -1
  76. package/docs/guides/squads-guide.md +1 -1
  77. package/docs/guides/testing-guide.md +2 -2
  78. package/docs/guides/user-guide.md +22 -22
  79. package/docs/guides/workflows/BROWNFIELD-DISCOVERY-WORKFLOW.md +3 -3
  80. package/docs/guides/workflows/BROWNFIELD-SERVICE-WORKFLOW.md +1 -1
  81. package/docs/guides/workflows/GREENFIELD-UI-WORKFLOW.md +2 -2
  82. package/docs/guides/workflows/SPEC-PIPELINE-WORKFLOW.md +3 -3
  83. package/docs/guides/workflows/STORY-DEVELOPMENT-CYCLE-WORKFLOW.md +3 -3
  84. package/docs/guides/workflows/pro-developer-workflow.md +2 -2
  85. package/docs/guides/workflows-guide.md +1 -1
  86. package/docs/guides/workflows-overview.md +1 -1
  87. package/docs/installation/npx-install.md +1 -1
  88. package/docs/installation/uninstallation.md +1 -1
  89. package/docs/installation/v4-quick-start.md +1 -1
  90. package/docs/pt/guides/user-guide.md +18 -18
  91. package/docs/pt/security.md +2 -2
  92. package/docs/security/overview.md +2 -2
  93. package/docs/security/security-best-practices.md +1 -1
  94. package/docs/sinapse-agent-flows/architect-system.md +1 -1
  95. package/docs/sinapse-agent-flows/data-engineer-system.md +1 -1
  96. package/docs/sinapse-agent-flows/dev-system.md +1 -1
  97. package/docs/sinapse-agent-flows/devops-system.md +2 -2
  98. package/docs/sinapse-agent-flows/qa-system.md +1 -1
  99. package/docs/sinapse-agent-flows/sm-system.md +2 -2
  100. package/docs/sinapse-agent-flows/snps-orqx-system.md +4 -4
  101. package/docs/sinapse-agent-flows/squad-creator-system.md +1 -1
  102. package/docs/sinapse-workflows/README.md +2 -2
  103. package/docs/sinapse-workflows/brownfield-discovery-workflow.md +3 -3
  104. package/docs/sinapse-workflows/brownfield-service-workflow.md +3 -3
  105. package/docs/sinapse-workflows/greenfield-ui-workflow.md +2 -2
  106. package/docs/sinapse-workflows/spec-pipeline-workflow.md +3 -3
  107. package/docs/sinapse-workflows/story-development-cycle-workflow.md +3 -3
  108. package/docs/troubleshooting.md +1 -1
  109. package/package.json +7 -1
  110. package/scripts/calibrate-judge.js +134 -0
  111. package/scripts/eval-e2e.js +149 -0
  112. package/scripts/validate-all.js +8 -0
  113. package/scripts/validate-article-iv.js +289 -0
  114. package/scripts/validate-constitution.js +58 -0
  115. package/scripts/validate-evals.js +9 -2
  116. package/scripts/validate-story-acs.js +174 -0
  117. package/scripts/validate-tool-descriptions.js +128 -0
  118. package/scripts/wave-gate.js +207 -0
  119. package/squads/claude-code-mastery/agents/project-integrator.md +3 -10
  120. package/squads/claude-code-mastery/knowledge-base/context-window-optimization.md +2 -2
  121. package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
  122. package/squads/squad-copy/knowledge-base/ai-copy-human-loop-canon.md +1 -1
@@ -146,7 +146,14 @@ function isPlainObject(v) {
146
146
  return v !== null && typeof v === 'object' && !Array.isArray(v);
147
147
  }
148
148
 
149
- /** Discover gate folders under tests/evals/ (every dir except _lib / _*). */
149
+ /**
150
+ * Suites that follow their OWN contract (not the IDS evals.json/triggers.json
151
+ * one) and have their own runner + jest wrapper. Onda3-S4: epic-gates uses
152
+ * cases.json validated by scripts/eval-e2e.js.
153
+ */
154
+ const NON_IDS_SUITES = new Set(['epic-gates']);
155
+
156
+ /** Discover gate folders under tests/evals/ (every dir except _lib / _* / non-IDS suites). */
150
157
  function discoverGateDirs() {
151
158
  let entries;
152
159
  try {
@@ -156,7 +163,7 @@ function discoverGateDirs() {
156
163
  process.exit(2);
157
164
  }
158
165
  return entries
159
- .filter((d) => d.isDirectory() && !d.name.startsWith('_'))
166
+ .filter((d) => d.isDirectory() && !d.name.startsWith('_') && !NON_IDS_SUITES.has(d.name))
160
167
  .map((d) => path.join(EVALS_ROOT, d.name))
161
168
  .sort();
162
169
  }
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Advisory guard: acceptance criteria in executable Given/When/Then form
4
+ * (Story mesa2-acs-gwt-guard, AF-20260704 Mesa #3).
5
+ *
6
+ * GWT is the documented "preferred" AC format (story-lifecycle.md), but stories
7
+ * historically copy free-form numbered lists from the epic. This guard flags ACs
8
+ * that are NOT in GWT so authors can tighten them — it is advisory by design:
9
+ *
10
+ * - Exit 0 always (WARN-only), mirroring validate-article-iv, UNLESS
11
+ * STORY_ACS_STRICT=1 is set and at least one non-GWT AC is found.
12
+ * - `docs/stories/` is gitignored (local, unversioned), so in CI there are
13
+ * typically no story files — the guard degrades gracefully to a PASS with
14
+ * "nothing to lint" instead of failing.
15
+ *
16
+ * The core is a pure function (`lintStoryAcs`) so tests can drive it with
17
+ * fixtures; the CLI wrapper walks the default story dir.
18
+ *
19
+ * @module scripts/validate-story-acs
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ const ROOT = process.cwd();
28
+
29
+ /** Default lint target: local story files (gitignored — may be absent in CI). */
30
+ const DEFAULT_STORIES_DIR = 'docs/stories';
31
+
32
+ /** Heading that opens the acceptance-criteria section (case-insensitive). */
33
+ const AC_HEADING = /^#{1,6}\s+acceptance\s+criteria\b/i;
34
+
35
+ /** Any markdown heading — closes the AC section. */
36
+ const ANY_HEADING = /^#{1,6}\s+/;
37
+
38
+ /** A list item (bullet `-`/`*` with optional checkbox, or `N.` numbered). */
39
+ const LIST_ITEM = /^\s*(?:[-*]\s+(?:\[[ xX]\]\s+)?|\d+[.)]\s+)(.+)$/;
40
+
41
+ /**
42
+ * True when a single AC's text is in executable Given/When/Then form — it must
43
+ * mention all three keywords (order-agnostic, case-insensitive).
44
+ *
45
+ * @param {string} text
46
+ * @returns {boolean}
47
+ */
48
+ function isGwt(text) {
49
+ const t = text.toLowerCase();
50
+ return /\bgiven\b/.test(t) && /\bwhen\b/.test(t) && /\bthen\b/.test(t);
51
+ }
52
+
53
+ /**
54
+ * Extract the acceptance-criteria list items from a story's markdown body and
55
+ * classify each as GWT or not.
56
+ *
57
+ * @param {string} content - full markdown of a story file
58
+ * @returns {{ total: number, gwt: number, nonGwt: Array<{ text: string }> }}
59
+ */
60
+ function lintStoryAcs(content) {
61
+ const lines = String(content).split(/\r?\n/);
62
+ const items = [];
63
+
64
+ let inSection = false;
65
+ let current = null; // accumulates a multi-line AC item
66
+
67
+ const flush = () => {
68
+ if (current !== null) {
69
+ items.push(current.trim());
70
+ current = null;
71
+ }
72
+ };
73
+
74
+ for (const line of lines) {
75
+ if (!inSection) {
76
+ if (AC_HEADING.test(line)) inSection = true;
77
+ continue;
78
+ }
79
+ // A new heading ends the AC section.
80
+ if (ANY_HEADING.test(line)) {
81
+ flush();
82
+ break;
83
+ }
84
+ const m = line.match(LIST_ITEM);
85
+ if (m) {
86
+ flush();
87
+ current = m[1];
88
+ } else if (current !== null && line.trim() !== '') {
89
+ // continuation line of the current AC item
90
+ current += ' ' + line.trim();
91
+ } else if (current !== null && line.trim() === '') {
92
+ flush();
93
+ }
94
+ }
95
+ flush();
96
+
97
+ const nonGwt = items.filter((t) => !isGwt(t)).map((text) => ({ text }));
98
+ return { total: items.length, gwt: items.length - nonGwt.length, nonGwt };
99
+ }
100
+
101
+ /** Recursively collect `.md` files under a directory. */
102
+ function walkMarkdown(dir, out = []) {
103
+ let entries;
104
+ try {
105
+ entries = fs.readdirSync(dir, { withFileTypes: true });
106
+ } catch {
107
+ return out;
108
+ }
109
+ for (const e of entries) {
110
+ const p = path.join(dir, e.name);
111
+ if (e.isDirectory()) walkMarkdown(p, out);
112
+ else if (e.name.endsWith('.md')) out.push(p);
113
+ }
114
+ return out;
115
+ }
116
+
117
+ /** CLI entry point. */
118
+ function main() {
119
+ const strict = process.env.STORY_ACS_STRICT === '1';
120
+ const dir = path.join(ROOT, DEFAULT_STORIES_DIR);
121
+ const files = walkMarkdown(dir);
122
+
123
+ if (files.length === 0) {
124
+ console.log(
125
+ `story-acs: no story files under ${DEFAULT_STORIES_DIR}/ (gitignored) — nothing to lint. PASS.`,
126
+ );
127
+ process.exit(0);
128
+ }
129
+
130
+ let totalAcs = 0;
131
+ let totalNonGwt = 0;
132
+ const report = [];
133
+
134
+ for (const file of files) {
135
+ let content;
136
+ try {
137
+ content = fs.readFileSync(file, 'utf8');
138
+ } catch {
139
+ continue;
140
+ }
141
+ const { total, nonGwt } = lintStoryAcs(content);
142
+ if (total === 0) continue; // not an AC-bearing story
143
+ totalAcs += total;
144
+ if (nonGwt.length > 0) {
145
+ totalNonGwt += nonGwt.length;
146
+ report.push({ file: path.relative(ROOT, file), nonGwt });
147
+ }
148
+ }
149
+
150
+ if (totalNonGwt === 0) {
151
+ console.log(`story-acs: ${totalAcs} AC(s) across ${files.length} file(s), all GWT-shaped. PASS.`);
152
+ process.exit(0);
153
+ }
154
+
155
+ const level = strict ? 'FAIL' : 'WARN';
156
+ console.log(`story-acs [${level}]: ${totalNonGwt}/${totalAcs} AC(s) are not in Given/When/Then form:`);
157
+ for (const r of report) {
158
+ console.log(` ${r.file}`);
159
+ for (const { text } of r.nonGwt) {
160
+ const preview = text.length > 90 ? text.slice(0, 87) + '...' : text;
161
+ console.log(` - ${preview}`);
162
+ }
163
+ }
164
+ console.log(
165
+ strict
166
+ ? '\nSTORY_ACS_STRICT=1 → failing. Rewrite the ACs above as "Given <ctx>, When <action>, Then <outcome>."'
167
+ : '\nAdvisory only (exit 0). Prefer "Given <ctx>, When <action>, Then <outcome>." Set STORY_ACS_STRICT=1 to enforce.',
168
+ );
169
+ process.exit(strict ? 1 : 0);
170
+ }
171
+
172
+ if (require.main === module) main();
173
+
174
+ module.exports = { lintStoryAcs, isGwt };
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Lint guard: tool/command descriptions (Story M2-mesa M3, AF-20260704 #9a).
4
+ *
5
+ * A command an agent exposes IS a tool, and its `description:` is the contract
6
+ * that drives selection (KIT-ai-engineering, Tool Use). A vague, empty, or
7
+ * placeholder description silently degrades routing. This guard enforces a
8
+ * minimal clarity contract on every `description:` field in the agent
9
+ * definitions, and is wired into `validate:all`.
10
+ *
11
+ * Exit 0 = all descriptions valid · Exit 1 = one or more violate the contract.
12
+ *
13
+ * The core is a pure function (`lintDescriptions`) so tests can drive it with
14
+ * fixtures; the CLI wrapper globs the default targets.
15
+ *
16
+ * @module scripts/validate-tool-descriptions
17
+ */
18
+
19
+ 'use strict';
20
+
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+
24
+ const ROOT = process.cwd();
25
+
26
+ /** Default lint targets: the framework agent definitions (source of truth). */
27
+ const DEFAULT_TARGET_DIRS = ['.sinapse-ai/development/agents'];
28
+
29
+ /** Minimum characters for a meaningful description (smallest real one is 12). */
30
+ const MIN_LEN = 8;
31
+
32
+ /** Values that are placeholders, not real descriptions. */
33
+ const PLACEHOLDER = /^(todo|tbd|fixme|x{2,}|placeholder|custom|n\/?a|desc|description|\.\.\.|-+|\?+)$/i;
34
+
35
+ /**
36
+ * The WHOLE value is nothing but a template token — e.g. `{description}` or
37
+ * `<desc>`. Inline braces documenting command args (e.g. "Build story
38
+ * (*build {story-id})") are legitimate and must NOT be flagged.
39
+ */
40
+ const TEMPLATE_ONLY = /^[{<][^}>]*[}>]$/;
41
+
42
+ /** A YAML block-scalar indicator — the real text follows on indented lines. */
43
+ const BLOCK_SCALAR = /^[|>][+-]?\d*$/;
44
+
45
+ /** Recursively collect `.md` files under a directory. */
46
+ function walkMarkdown(dir, out = []) {
47
+ let entries;
48
+ try {
49
+ entries = fs.readdirSync(dir, { withFileTypes: true });
50
+ } catch {
51
+ return out;
52
+ }
53
+ for (const e of entries) {
54
+ const p = path.join(dir, e.name);
55
+ if (e.isDirectory()) walkMarkdown(p, out);
56
+ else if (e.name.toLowerCase().endsWith('.md')) out.push(p);
57
+ }
58
+ return out;
59
+ }
60
+
61
+ /** Strip a single layer of matching quotes from a YAML scalar. */
62
+ function unquote(s) {
63
+ const t = s.trim();
64
+ if ((t.startsWith("'") && t.endsWith("'")) || (t.startsWith('"') && t.endsWith('"'))) {
65
+ return t.slice(1, -1).trim();
66
+ }
67
+ return t;
68
+ }
69
+
70
+ /**
71
+ * Lint `description:` fields in the given files.
72
+ * @param {string[]} files
73
+ * @returns {Array<{file:string, line:number, value:string, reason:string}>}
74
+ */
75
+ function lintDescriptions(files) {
76
+ const violations = [];
77
+ for (const file of files) {
78
+ let content;
79
+ try {
80
+ content = fs.readFileSync(file, 'utf8');
81
+ } catch {
82
+ continue;
83
+ }
84
+ const lines = content.split(/\r?\n/);
85
+ lines.forEach((raw, i) => {
86
+ const m = raw.match(/^\s*description:\s*(.*)$/);
87
+ if (!m) return;
88
+ const rawValue = m[1].trim();
89
+ // Block scalars (`description: |`) carry the text on the following indented
90
+ // lines — treat as valid rather than flag the indicator itself.
91
+ if (BLOCK_SCALAR.test(rawValue)) return;
92
+ const value = unquote(rawValue);
93
+ let reason = null;
94
+ if (value === '') reason = 'descrição vazia';
95
+ else if (PLACEHOLDER.test(value)) reason = `placeholder ("${value}")`;
96
+ else if (TEMPLATE_ONLY.test(value)) reason = `template não preenchido ("${value}")`;
97
+ else if (value.length < MIN_LEN) reason = `curta demais (<${MIN_LEN} chars): "${value}"`;
98
+ if (reason) {
99
+ violations.push({ file: path.relative(ROOT, file), line: i + 1, value, reason });
100
+ }
101
+ });
102
+ }
103
+ return violations;
104
+ }
105
+
106
+ /** Resolve the files to lint: explicit args, or the default target dirs. */
107
+ function collectFiles(args) {
108
+ if (args.length) return args;
109
+ return DEFAULT_TARGET_DIRS.flatMap((d) => walkMarkdown(path.join(ROOT, d)));
110
+ }
111
+
112
+ function main() {
113
+ const files = collectFiles(process.argv.slice(2));
114
+ const violations = lintDescriptions(files);
115
+ if (violations.length === 0) {
116
+ console.log(`OK — tool/command descriptions valid (${files.length} files scanned).`);
117
+ process.exit(0);
118
+ }
119
+ console.error(`FAIL — ${violations.length} description(s) violate the clarity contract:`);
120
+ for (const v of violations) {
121
+ console.error(` ${v.file}:${v.line} — ${v.reason}`);
122
+ }
123
+ process.exit(1);
124
+ }
125
+
126
+ if (require.main === module) main();
127
+
128
+ module.exports = { lintDescriptions, unquote, walkMarkdown, MIN_LEN };
@@ -0,0 +1,207 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * wave-gate.js — deterministic wave gate for harness-based epic waves.
5
+ *
6
+ * Story onda3-s5-epic-waves-wrapper-pilot (AF-20260702 item 3.5).
7
+ *
8
+ * The HÍBRIDO verdict (2026-06-30) killed the homegrown multi-story engine.
9
+ * The wave path that remains viable is a THIN WRAPPER over the harness:
10
+ * fan-out per story into isolated workdirs/worktrees, then THIS gate decides
11
+ * whether the wave may advance — by measurement, not by claim:
12
+ *
13
+ * 1. TESTS GREEN: the story's test command exits 0 (default `node --test`).
14
+ * 2. FILES ACTUALLY WRITTEN: the story dir contains real product files
15
+ * (excluding plan/, docs/, node_modules, dotfiles and markdown) — the
16
+ * echo of the empty-build honesty fix: a wave never advances on a story
17
+ * that produced zero code.
18
+ *
19
+ * Verdict per story and for the wave: APPROVED / NEEDS_WORK (exit 0 / 1).
20
+ *
21
+ * Usage:
22
+ * node scripts/wave-gate.js --stories <dir1> <dir2> [...] \
23
+ * [--test-cmd "node --test"] [--json]
24
+ *
25
+ * Adoption status: utility + pilot instrument. Whether the wave wrapper
26
+ * becomes product is decided EXCLUSIVELY by the pre-registered measurement
27
+ * (tests/evals/epic-gates/PROTOCOL.md) — see the checkpoint in
28
+ * docs/epics/epic-onda3-estrutural/.
29
+ */
30
+
31
+ 'use strict';
32
+
33
+ const fs = require('fs');
34
+ const path = require('path');
35
+ const { execSync } = require('child_process');
36
+
37
+ const DEFAULT_TEST_CMD = 'node --test';
38
+
39
+ /** Directory/file names that never count as written product files. */
40
+ const EXCLUDED_DIRS = new Set(['node_modules', '.git', 'plan', 'docs', '.sinapse']);
41
+
42
+ /**
43
+ * Recursively list product files written in a story dir.
44
+ * Markdown and dotfiles are not product; plan/ and docs/ are artifacts of
45
+ * planning, not implementation (a plan is not an implementation).
46
+ *
47
+ * @param {string} dir
48
+ * @param {number} [depth]
49
+ * @returns {string[]} Relative paths of product files
50
+ */
51
+ function scanWrittenFiles(dir, depth = 0) {
52
+ if (depth > 6) return [];
53
+ let entries;
54
+ try {
55
+ entries = fs.readdirSync(dir, { withFileTypes: true });
56
+ } catch {
57
+ return [];
58
+ }
59
+ const out = [];
60
+ for (const entry of entries) {
61
+ if (entry.name.startsWith('.')) continue;
62
+ if (entry.isDirectory()) {
63
+ if (EXCLUDED_DIRS.has(entry.name)) continue;
64
+ out.push(
65
+ ...scanWrittenFiles(path.join(dir, entry.name), depth + 1).map((f) =>
66
+ path.join(entry.name, f),
67
+ ),
68
+ );
69
+ continue;
70
+ }
71
+ if (entry.name.endsWith('.md')) continue;
72
+ out.push(entry.name);
73
+ }
74
+ return out;
75
+ }
76
+
77
+ /**
78
+ * Parse a `node --test` (TAP-ish) summary out of test output. Best effort —
79
+ * the EXIT CODE is the source of truth; counts are telemetry.
80
+ *
81
+ * @param {string} output
82
+ * @returns {{pass: ?number, fail: ?number}}
83
+ */
84
+ function parseNodeTestSummary(output) {
85
+ const grab = (re) => {
86
+ const m = String(output).match(re);
87
+ return m ? parseInt(m[1], 10) : null;
88
+ };
89
+ return {
90
+ pass: grab(/^#\s*pass\s+(\d+)/im),
91
+ fail: grab(/^#\s*fail\s+(\d+)/im),
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Evaluate one story dir: run its tests and verify real files were written.
97
+ *
98
+ * @param {string} storyDir
99
+ * @param {{testCmd?: string}} [options]
100
+ * @returns {{story: string, verdict: string, filesWritten: number, files: string[], tests: {exitCode: ?number, pass: ?number, fail: ?number, skipped?: boolean}, reasons: string[]}}
101
+ */
102
+ function evaluateStory(storyDir, options = {}) {
103
+ const testCmd = options.testCmd || DEFAULT_TEST_CMD;
104
+ const reasons = [];
105
+
106
+ const files = scanWrittenFiles(storyDir);
107
+ if (files.length === 0) {
108
+ reasons.push('zero product files written (a plan is not an implementation)');
109
+ }
110
+
111
+ let tests = { exitCode: null, pass: null, fail: null };
112
+ if (files.length === 0) {
113
+ // Nothing to test — do not mask the real failure with a confusing test error.
114
+ tests = { exitCode: null, pass: null, fail: null, skipped: true };
115
+ } else {
116
+ try {
117
+ const output = execSync(testCmd, {
118
+ cwd: storyDir,
119
+ encoding: 'utf8',
120
+ stdio: ['ignore', 'pipe', 'pipe'],
121
+ timeout: 120000,
122
+ });
123
+ tests = { exitCode: 0, ...parseNodeTestSummary(output) };
124
+ } catch (error) {
125
+ const output = `${error.stdout || ''}\n${error.stderr || ''}`;
126
+ tests = {
127
+ exitCode: typeof error.status === 'number' ? error.status : 1,
128
+ ...parseNodeTestSummary(output),
129
+ };
130
+ reasons.push(`tests failed (exit ${tests.exitCode}${tests.fail != null ? `, ${tests.fail} failing` : ''})`);
131
+ }
132
+ }
133
+
134
+ return {
135
+ story: storyDir,
136
+ verdict: reasons.length === 0 ? 'APPROVED' : 'NEEDS_WORK',
137
+ filesWritten: files.length,
138
+ files,
139
+ tests,
140
+ reasons,
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Evaluate a whole wave (list of story dirs).
146
+ *
147
+ * @param {string[]} storyDirs
148
+ * @param {{testCmd?: string}} [options]
149
+ * @returns {{verdict: string, stories: Array}}
150
+ */
151
+ function evaluateWave(storyDirs, options = {}) {
152
+ const stories = storyDirs.map((dir) => evaluateStory(dir, options));
153
+ return {
154
+ verdict: stories.every((s) => s.verdict === 'APPROVED') ? 'APPROVED' : 'NEEDS_WORK',
155
+ stories,
156
+ };
157
+ }
158
+
159
+ function main() {
160
+ const argv = process.argv.slice(2);
161
+ const json = argv.includes('--json');
162
+
163
+ const testCmdIdx = argv.indexOf('--test-cmd');
164
+ const testCmd = testCmdIdx !== -1 ? argv[testCmdIdx + 1] : DEFAULT_TEST_CMD;
165
+
166
+ const storiesIdx = argv.indexOf('--stories');
167
+ if (storiesIdx === -1) {
168
+ console.error('Usage: node scripts/wave-gate.js --stories <dir1> [dir2 ...] [--test-cmd "..."] [--json]');
169
+ process.exit(2);
170
+ }
171
+ const storyDirs = [];
172
+ for (let i = storiesIdx + 1; i < argv.length && !argv[i].startsWith('--'); i += 1) {
173
+ storyDirs.push(path.resolve(argv[i]));
174
+ }
175
+ if (storyDirs.length === 0) {
176
+ console.error('wave-gate: --stories requires at least one directory.');
177
+ process.exit(2);
178
+ }
179
+
180
+ const wave = evaluateWave(storyDirs, { testCmd });
181
+
182
+ if (json) {
183
+ console.log(JSON.stringify(wave, null, 2));
184
+ } else {
185
+ console.log(`=== wave-gate (${storyDirs.length} story(ies)) ===`);
186
+ for (const s of wave.stories) {
187
+ const testInfo = s.tests.skipped
188
+ ? 'tests skipped (nothing to test)'
189
+ : `tests exit ${s.tests.exitCode}${s.tests.pass != null ? ` (${s.tests.pass} pass/${s.tests.fail} fail)` : ''}`;
190
+ console.log(` ${s.verdict.padEnd(10)} ${path.basename(s.story)} — ${s.filesWritten} file(s), ${testInfo}`);
191
+ for (const r of s.reasons) console.log(` - ${r}`);
192
+ }
193
+ console.log('');
194
+ console.log(`wave verdict: ${wave.verdict}`);
195
+ }
196
+
197
+ process.exit(wave.verdict === 'APPROVED' ? 0 : 1);
198
+ }
199
+
200
+ module.exports = {
201
+ scanWrittenFiles,
202
+ parseNodeTestSummary,
203
+ evaluateStory,
204
+ evaluateWave,
205
+ };
206
+
207
+ if (require.main === module) main();
@@ -651,6 +651,9 @@ dependencies:
651
651
 
652
652
  coderabbit_integration:
653
653
  enabled: true
654
+ # CodeRabbit mechanics (WSL execution, timeout, commands, report location) are
655
+ # single-sourced in .sinapse-ai/core-config.yaml + .claude/rules/coderabbit-integration.md.
656
+ # Only this agent's review focus/policy lives here — see Story rodada2-m6.
654
657
  focus: Integration patterns, configuration consistency, CLAUDE.md quality, hook coverage
655
658
 
656
659
  when_to_use:
@@ -659,16 +662,6 @@ dependencies:
659
662
  - Validating CI/CD workflow configurations
660
663
  - Checking settings.json deny/allow rules
661
664
 
662
- execution_guidelines: |
663
- CRITICAL: CodeRabbit CLI is installed in WSL, not Windows.
664
-
665
- **How to Execute:**
666
- 1. Use 'wsl bash -c' wrapper for all commands
667
- 2. Navigate to project directory in WSL path format (/mnt/c/...)
668
- 3. Use full path to coderabbit binary (~/.local/bin/coderabbit)
669
-
670
- **Timeout:** 15 minutes (900000ms) - CodeRabbit reviews take 7-30 min
671
-
672
665
  # =========================================================================
673
666
  # INTEGRATION ALGORITHM
674
667
  # =========================================================================
@@ -19,7 +19,7 @@ The context window is the scarcest resource in agentic systems. Every token has
19
19
  ### Claude Code Session Anatomy
20
20
 
21
21
  ```
22
- TOTAL WINDOW: ~200,000 tokens (Sonnet/Opus)
22
+ TOTAL WINDOW: ~200,000 tokens (example — current frontier windows reach 1M; see .claude/rules/token-economy.md)
23
23
  ├── System Prompt (static): ~2,000 tokens (1%) [CACHED]
24
24
  ├── CLAUDE.md hierarchy: 1-10,000 tokens (0.5-5%) [CACHED]
25
25
  ├── Rules files (loaded): 500-5,000 tokens (0.25-2.5%) [CACHED]
@@ -253,7 +253,7 @@ Parent system prompt (cached):
253
253
  For SINAPSE agents, recommended allocation:
254
254
 
255
255
  ```
256
- 200K token session:
256
+ 200K-window session (example — current frontier windows reach 1M):
257
257
  Agent persona (CLAUDE.md + rules): ~5K (2.5%)
258
258
  HOT memory (current task): ~20K (10%)
259
259
  WARM memory (retrieved context): ~30K (15%)
@@ -43,7 +43,7 @@ The context window is the scarcest resource in agentic systems. A well-designed
43
43
  ### Token Budget Management
44
44
 
45
45
  ```
46
- TOTAL WINDOW: 200,000 tokens (example)
46
+ TOTAL WINDOW: 200,000 tokens (example — current frontier windows reach 1M)
47
47
  |- System Prompt: ~2,000 tokens (1%)
48
48
  |- Agent Persona: ~1,500 tokens (0.75%)
49
49
  |- Memory Context: ~50,000 tokens (25%) ← MANAGED
@@ -195,7 +195,7 @@ Every AI-assisted copy deliverable MUST record:
195
195
 
196
196
  1. **Gate used** — which of the 4 gates (Trust / Light / Heavy / Scrap)
197
197
  2. **Prompt template(s) used** — from the library in §6
198
- 3. **Model used** — GPT-5, Claude Opus 4.6, Gemini 2.5 Pro, etc.
198
+ 3. **Model used** — e.g. GPT-5, Claude (Opus/Fable family), Gemini 2.5 Pro
199
199
  4. **Final edit depth** — rough % AI vs human in the shipped version
200
200
  5. **Conversion outcome** (if tracked) — linked to the attribution system
201
201