sinapse-ai 1.21.0 → 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.
- package/.claude/rules/coderabbit-integration.md +6 -0
- package/.claude/rules/documentation-first-reference.md +146 -0
- package/.claude/rules/documentation-first.md +27 -119
- package/.claude/rules/mandatory-delegation-reference.md +93 -0
- package/.claude/rules/mandatory-delegation.md +29 -93
- package/.claude/rules/project-intelligence-reference.md +159 -0
- package/.claude/rules/project-intelligence.md +37 -155
- package/.claude/rules/safe-collaboration-reference.md +163 -0
- package/.claude/rules/safe-collaboration.md +32 -173
- package/.sinapse-ai/core/doctor/checks/index.js +1 -1
- package/.sinapse-ai/core/execution/build-orchestrator.js +4 -3
- package/.sinapse-ai/core/execution/subagent-dispatcher.js +2 -2
- package/.sinapse-ai/core/ideation/ideation-engine.js +2 -1
- package/.sinapse-ai/core/orchestration/cli-commands.js +13 -0
- package/.sinapse-ai/core/orchestration/doc-first-resolver.js +93 -2
- package/.sinapse-ai/core/orchestration/executors/epic-3-executor.js +15 -2
- package/.sinapse-ai/core/orchestration/executors/epic-4-executor.js +3 -1
- package/.sinapse-ai/core/orchestration/index.js +5 -0
- package/.sinapse-ai/core/orchestration/spec-complexity.js +141 -0
- package/.sinapse-ai/core-config.yaml +30 -0
- package/.sinapse-ai/data/entity-registry.yaml +46 -25
- package/.sinapse-ai/development/agents/architect.md +3 -15
- package/.sinapse-ai/development/agents/data-engineer.md +3 -15
- package/.sinapse-ai/development/agents/developer.md +3 -24
- package/.sinapse-ai/development/agents/devops.md +4 -25
- package/.sinapse-ai/development/agents/quality-gate.md +3 -25
- package/.sinapse-ai/development/knowledge-base/token-economy-guide.md +1 -1
- package/.sinapse-ai/development/tasks/resolve-github-issue.md +1 -1
- package/.sinapse-ai/docs/standards/SINAPSE-LIVRO-DE-OURO-V2.1-COMPLETE.md +6 -2
- package/.sinapse-ai/docs/standards/SINAPSE-LIVRO-DE-OURO-V2.2-SUMMARY.md +4 -1
- package/.sinapse-ai/install-manifest.yaml +44 -40
- package/.sinapse-ai/product/templates/story-tmpl.yaml +11 -6
- package/.sinapse-ai/quality/judge-calibration/README.md +75 -0
- package/.sinapse-ai/quality/judge-calibration/calibration-log.md +70 -0
- package/.sinapse-ai/quality/judge-calibration/golden-set.json +105 -0
- package/.sinapse-ai/quality/judge-calibration/scenarios.json +90 -0
- package/CHANGELOG.md +38 -8
- package/README.en.md +1 -1
- package/README.md +6 -6
- package/bin/sinapse-minimal.js +5 -4
- package/docs/community/README-community-snippet-core.md +1 -1
- package/docs/community/README-community-snippet-mcp.md +2 -2
- package/docs/framework/README.md +2 -2
- package/docs/framework/architecture-overview.md +2 -2
- package/docs/framework/core-architecture.md +1 -1
- package/docs/framework/feature-process.md +5 -5
- package/docs/framework/guiding-principles.md +1 -1
- package/docs/framework/roadmap.md +4 -4
- package/docs/framework/source-tree.md +1 -1
- package/docs/framework/versioning-and-releases.md +1 -1
- package/docs/getting-started.md +1 -1
- package/docs/guides/agent-selection-guide.md +1 -1
- package/docs/guides/agents/traces/README.md +1 -1
- package/docs/guides/config-migration-guide.md +1 -1
- package/docs/guides/development-setup.md +1 -1
- package/docs/guides/docker-mcp-setup.md +5 -5
- package/docs/guides/getting-started.md +1 -1
- package/docs/guides/git-workflow-guide.md +3 -3
- package/docs/guides/ide-integration.md +3 -3
- package/docs/guides/ide-sync-guide.md +1 -1
- package/docs/guides/mcp/desktop-commander.md +2 -2
- package/docs/guides/mcp/docker-gateway-tutorial.md +1 -1
- package/docs/guides/mcp-global-setup.md +3 -3
- package/docs/guides/memory-intelligence-system.md +10 -10
- package/docs/guides/meta-agent-commands.md +1 -1
- package/docs/guides/quality-gates.md +1 -1
- package/docs/guides/security-hardening.md +1 -1
- package/docs/guides/service-discovery.md +1 -1
- package/docs/guides/squad-examples/README.md +1 -1
- package/docs/guides/squads-guide.md +1 -1
- package/docs/guides/testing-guide.md +2 -2
- package/docs/guides/user-guide.md +22 -22
- package/docs/guides/workflows/BROWNFIELD-DISCOVERY-WORKFLOW.md +3 -3
- package/docs/guides/workflows/BROWNFIELD-SERVICE-WORKFLOW.md +1 -1
- package/docs/guides/workflows/GREENFIELD-UI-WORKFLOW.md +2 -2
- package/docs/guides/workflows/SPEC-PIPELINE-WORKFLOW.md +3 -3
- package/docs/guides/workflows/STORY-DEVELOPMENT-CYCLE-WORKFLOW.md +3 -3
- package/docs/guides/workflows/pro-developer-workflow.md +2 -2
- package/docs/guides/workflows-guide.md +1 -1
- package/docs/guides/workflows-overview.md +1 -1
- package/docs/installation/npx-install.md +1 -1
- package/docs/installation/uninstallation.md +1 -1
- package/docs/installation/v4-quick-start.md +1 -1
- package/docs/pt/guides/user-guide.md +18 -18
- package/docs/pt/security.md +2 -2
- package/docs/security/overview.md +2 -2
- package/docs/security/security-best-practices.md +1 -1
- package/docs/sinapse-agent-flows/architect-system.md +1 -1
- package/docs/sinapse-agent-flows/data-engineer-system.md +1 -1
- package/docs/sinapse-agent-flows/dev-system.md +1 -1
- package/docs/sinapse-agent-flows/devops-system.md +2 -2
- package/docs/sinapse-agent-flows/qa-system.md +1 -1
- package/docs/sinapse-agent-flows/sm-system.md +2 -2
- package/docs/sinapse-agent-flows/snps-orqx-system.md +4 -4
- package/docs/sinapse-agent-flows/squad-creator-system.md +1 -1
- package/docs/sinapse-workflows/README.md +2 -2
- package/docs/sinapse-workflows/brownfield-discovery-workflow.md +3 -3
- package/docs/sinapse-workflows/brownfield-service-workflow.md +3 -3
- package/docs/sinapse-workflows/greenfield-ui-workflow.md +2 -2
- package/docs/sinapse-workflows/spec-pipeline-workflow.md +3 -3
- package/docs/sinapse-workflows/story-development-cycle-workflow.md +3 -3
- package/docs/troubleshooting.md +1 -1
- package/package.json +5 -1
- package/scripts/calibrate-judge.js +134 -0
- package/scripts/validate-all.js +5 -0
- package/scripts/validate-constitution.js +58 -0
- package/scripts/validate-story-acs.js +174 -0
- package/scripts/validate-tool-descriptions.js +128 -0
- package/scripts/wave-gate.js +1 -1
- package/squads/claude-code-mastery/agents/project-integrator.md +3 -10
- package/squads/claude-code-mastery/knowledge-base/context-window-optimization.md +2 -2
- package/squads/claude-code-mastery/knowledge-base/memory-systems-reference.md +1 -1
- package/squads/squad-copy/knowledge-base/ai-copy-human-loop-canon.md +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* LLM-judge calibration harness (Story mesa2-llm-judge-calibration, AF-20260704 Mesa #2).
|
|
4
|
+
*
|
|
5
|
+
* The judge that decides "Done" is the @quality-gate agent: it emits free-form
|
|
6
|
+
* prose ending in `VERDICT: X`, and Epic6Executor._parseVerdict turns that prose
|
|
7
|
+
* into the binary verdict (APPROVED → Done). This harness calibrates that
|
|
8
|
+
* DETERMINISTIC seam against a human-labeled golden set:
|
|
9
|
+
*
|
|
10
|
+
* - It runs the REAL _parseVerdict (imported, never re-implemented) over each
|
|
11
|
+
* golden output and checks it agrees with the human-assigned verdict.
|
|
12
|
+
* - _parseVerdict is deterministic, so the gate is 100%: any disagreement means
|
|
13
|
+
* the parser regressed OR a golden label is wrong — both demand action.
|
|
14
|
+
*
|
|
15
|
+
* This does NOT calibrate the LLM's semantic judgment (non-deterministic, costly)
|
|
16
|
+
* — that is the documented LIVE procedure (feed the golden scenarios to the real
|
|
17
|
+
* @quality-gate and compare). See .sinapse-ai/quality/judge-calibration/README.md.
|
|
18
|
+
*
|
|
19
|
+
* `scoreCalibration` is pure so tests can drive it with any parse function.
|
|
20
|
+
*
|
|
21
|
+
* Exit 0 = full agreement · Exit 1 = one or more disagreements.
|
|
22
|
+
*
|
|
23
|
+
* @module scripts/calibrate-judge
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
const fs = require('fs');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
32
|
+
const GOLDEN_SET_PATH = path.join(
|
|
33
|
+
ROOT,
|
|
34
|
+
'.sinapse-ai/quality/judge-calibration/golden-set.json',
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The REAL verdict-decision logic under calibration. `_parseVerdict` uses only
|
|
39
|
+
* its `output` argument and the module-level QAVerdict constants (no `this`), so
|
|
40
|
+
* it can be invoked detached via the prototype — using the exact production
|
|
41
|
+
* function, never a copy.
|
|
42
|
+
*
|
|
43
|
+
* @returns {(output: string) => string}
|
|
44
|
+
*/
|
|
45
|
+
function loadRealParseVerdict() {
|
|
46
|
+
const Epic6Executor = require('../.sinapse-ai/core/orchestration/executors/epic-6-executor.js');
|
|
47
|
+
const fn = Epic6Executor.prototype._parseVerdict;
|
|
48
|
+
return (output) => fn.call(null, output);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Score a parse function against golden cases.
|
|
53
|
+
*
|
|
54
|
+
* @param {Array<{ id: string, output: string, expected: string }>} cases
|
|
55
|
+
* @param {(output: string) => string} parseFn
|
|
56
|
+
* @returns {{
|
|
57
|
+
* total: number,
|
|
58
|
+
* agreed: number,
|
|
59
|
+
* agreementRate: number,
|
|
60
|
+
* disagreements: Array<{ id: string, expected: string, predicted: string }>,
|
|
61
|
+
* confusion: Record<string, Record<string, number>>
|
|
62
|
+
* }}
|
|
63
|
+
*/
|
|
64
|
+
function scoreCalibration(cases, parseFn) {
|
|
65
|
+
const disagreements = [];
|
|
66
|
+
const confusion = {};
|
|
67
|
+
let agreed = 0;
|
|
68
|
+
|
|
69
|
+
for (const c of cases) {
|
|
70
|
+
const predicted = parseFn(c.output);
|
|
71
|
+
confusion[c.expected] = confusion[c.expected] || {};
|
|
72
|
+
confusion[c.expected][predicted] = (confusion[c.expected][predicted] || 0) + 1;
|
|
73
|
+
if (predicted === c.expected) {
|
|
74
|
+
agreed += 1;
|
|
75
|
+
} else {
|
|
76
|
+
disagreements.push({ id: c.id, expected: c.expected, predicted });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const total = cases.length;
|
|
81
|
+
return {
|
|
82
|
+
total,
|
|
83
|
+
agreed,
|
|
84
|
+
agreementRate: total === 0 ? 1 : agreed / total,
|
|
85
|
+
disagreements,
|
|
86
|
+
confusion,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** CLI entry point. */
|
|
91
|
+
function main() {
|
|
92
|
+
let golden;
|
|
93
|
+
try {
|
|
94
|
+
golden = JSON.parse(fs.readFileSync(GOLDEN_SET_PATH, 'utf8'));
|
|
95
|
+
} catch (err) {
|
|
96
|
+
console.error(`calibrate-judge: cannot read golden set at ${GOLDEN_SET_PATH}: ${err.message}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const cases = Array.isArray(golden.cases) ? golden.cases : [];
|
|
101
|
+
if (cases.length === 0) {
|
|
102
|
+
console.error('calibrate-judge: golden set has no cases.');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const parseFn = loadRealParseVerdict();
|
|
107
|
+
const result = scoreCalibration(cases, parseFn);
|
|
108
|
+
|
|
109
|
+
const pct = (result.agreementRate * 100).toFixed(1);
|
|
110
|
+
console.log(`judge calibration: ${result.agreed}/${result.total} agree with the human golden set (${pct}%).`);
|
|
111
|
+
console.log('confusion (expected → predicted):');
|
|
112
|
+
for (const expected of Object.keys(result.confusion)) {
|
|
113
|
+
const preds = result.confusion[expected];
|
|
114
|
+
const parts = Object.keys(preds).map((p) => `${p}:${preds[p]}`).join(', ');
|
|
115
|
+
console.log(` ${expected} → ${parts}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (result.disagreements.length === 0) {
|
|
119
|
+
console.log('PASS — deterministic verdict extraction is calibrated to the human baseline.');
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(`FAIL — ${result.disagreements.length} case(s) disagree with the human label:`);
|
|
125
|
+
for (const d of result.disagreements) {
|
|
126
|
+
console.log(` ${d.id}: expected ${d.expected}, parser produced ${d.predicted}`);
|
|
127
|
+
}
|
|
128
|
+
console.log('\nEither _parseVerdict regressed or the golden label is wrong. Fix the code or the label — do not loosen the gate.');
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (require.main === module) main();
|
|
133
|
+
|
|
134
|
+
module.exports = { scoreCalibration, loadRealParseVerdict, GOLDEN_SET_PATH };
|
package/scripts/validate-all.js
CHANGED
|
@@ -55,6 +55,11 @@ const GUARDS = [
|
|
|
55
55
|
{ name: 'squad-yaml', script: 'validate:squad-yaml' },
|
|
56
56
|
{ name: 'squad-orqx', script: 'validate:squad-orqx' },
|
|
57
57
|
{ name: 'agent-codenames', script: 'validate:agent-codenames' },
|
|
58
|
+
{ name: 'tool-descriptions', script: 'validate:tool-descriptions' },
|
|
59
|
+
{ name: 'constitution', script: 'validate:constitution' },
|
|
60
|
+
// ACs in Given/When/Then — WARN-only (exits 0 unless STORY_ACS_STRICT=1) and
|
|
61
|
+
// a no-op PASS in CI where docs/stories/ (gitignored) is absent.
|
|
62
|
+
{ name: 'story-acs', script: 'validate:story-acs' },
|
|
58
63
|
// Article IV traceability — WARN-only during calibration: the script itself
|
|
59
64
|
// exits 0 on orphans unless ARTICLE_IV_STRICT=1 (see validate-article-iv.js).
|
|
60
65
|
{ name: 'article-iv', script: 'validate:article-iv' },
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Lint guard: Constitution consistency across CLAUDE.md ⇄ AGENTS.md (+ source,
|
|
4
|
+
* install template, rules). Story rodada2-m5 (AF-20260704 Mesa #7).
|
|
5
|
+
*
|
|
6
|
+
* The Mesa item framed CLAUDE.md and AGENTS.md as "twins that drift" and asked
|
|
7
|
+
* to generate both from one source. Verification REFUTED that premise: the two
|
|
8
|
+
* files serve different surfaces (Claude Code vs Codex), share almost no verbatim
|
|
9
|
+
* body, and their only shared canon — the ecosystem counts and the Constitution
|
|
10
|
+
* article set — is already single-sourced. Counts are guarded by
|
|
11
|
+
* `validate:agents-md`; the article agreement is validated by the
|
|
12
|
+
* `constitution-consistency` doctor check.
|
|
13
|
+
*
|
|
14
|
+
* The real, narrow gap this guard closes: that doctor check ran only under
|
|
15
|
+
* `sinapse doctor` and the install matrix — NOT on the PR gate. So article drift
|
|
16
|
+
* between CLAUDE.md and AGENTS.md could merge unnoticed. This wraps the SAME
|
|
17
|
+
* existing check (no new logic, no twin-generator) so it runs on every push/PR.
|
|
18
|
+
*
|
|
19
|
+
* Exit 0 = consistent (PASS/WARN) · Exit 1 = FAIL (a consumer is missing a
|
|
20
|
+
* canonical article, or the source/rules are missing).
|
|
21
|
+
*
|
|
22
|
+
* @module scripts/validate-constitution
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
const path = require('path');
|
|
28
|
+
|
|
29
|
+
const check = require('../.sinapse-ai/core/doctor/checks/constitution-consistency');
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
33
|
+
let result;
|
|
34
|
+
try {
|
|
35
|
+
result = await check.run({ projectRoot });
|
|
36
|
+
} catch (err) {
|
|
37
|
+
// The check declares onError:'fail' — a thrown error is a real problem.
|
|
38
|
+
console.error(`FAIL — constitution-consistency threw: ${err.message}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const status = result.status || 'FAIL';
|
|
43
|
+
if (status === 'PASS') {
|
|
44
|
+
console.log(`OK — ${result.message}`);
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
if (status === 'WARN') {
|
|
48
|
+
// Minor divergence (≤2 issues) — surface it but do not block (matches the
|
|
49
|
+
// doctor severity gradation; Art. XI, avoid over-tightening the gate).
|
|
50
|
+
console.warn(`WARN — ${result.message}`);
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
console.error(`FAIL — ${result.message}`);
|
|
54
|
+
if (result.fixCommand) console.error(` fix: ${result.fixCommand}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
main();
|
|
@@ -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 };
|
package/scripts/wave-gate.js
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
|
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
|
|