@event4u/agent-config 8.9.0 → 8.11.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-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +189 -0
- package/README.md +5 -5
- package/dist/agent-src/commands/challenge-me/vision.md +121 -32
- package/dist/agent-src/commands/challenge-me/with-docs.md +21 -8
- package/dist/agent-src/commands/commit/in-chunks.md +1 -1
- package/dist/agent-src/commands/commit.md +2 -1
- package/dist/agent-src/commands/cost/report.md +14 -3
- package/dist/agent-src/commands/feature/dev.md +4 -4
- package/dist/agent-src/commands/humanize.md +101 -0
- package/dist/agent-src/commands/jira-ticket.md +2 -2
- package/dist/agent-src/commands/memory/load.md +15 -0
- package/dist/agent-src/commands/optimize/project.md +12 -0
- package/dist/agent-src/commands/roadmap/create.md +12 -11
- package/dist/agent-src/commands/tests/create.md +29 -7
- package/dist/agent-src/commands/work.md +33 -0
- package/dist/agent-src/commands/worktree/cleanup.md +83 -0
- package/dist/agent-src/commands/worktree/create.md +64 -0
- package/dist/agent-src/commands/worktree/status.md +75 -0
- package/dist/agent-src/commands/worktree/verify.md +70 -0
- package/dist/agent-src/commands/worktree.md +85 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +66 -0
- package/dist/agent-src/contexts/execution/orchestration-benchmark-gate.md +1 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +91 -34
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +14 -0
- package/dist/agent-src/contexts/execution/roadmap-writing-source-derived.md +65 -0
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +84 -0
- package/dist/agent-src/rules/autonomous-execution.md +7 -37
- package/dist/agent-src/rules/broken-access-control.md +7 -45
- package/dist/agent-src/rules/code-comment-discipline.md +148 -0
- package/dist/agent-src/rules/context-hygiene.md +14 -0
- package/dist/agent-src/rules/delegation-policy.md +10 -8
- package/dist/agent-src/rules/design-fidelity.md +44 -0
- package/dist/agent-src/rules/git-history-discipline.md +4 -79
- package/dist/agent-src/rules/legal-safety-floor.md +11 -55
- package/dist/agent-src/rules/roadmap-progress-sync.md +5 -56
- package/dist/agent-src/rules/spreadsheet-source-quality.md +61 -0
- package/dist/agent-src/rules/think-before-action.md +10 -0
- package/dist/agent-src/rules/verify-before-complete.md +12 -0
- package/dist/agent-src/skills/accessibility-auditor/evals/evals.json +15 -0
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +13 -12
- package/dist/agent-src/skills/ai-council/SKILL.md +19 -9
- package/dist/agent-src/skills/analysis-skill-router/evals/evals.json +13 -0
- package/dist/agent-src/skills/api-design/SKILL.md +7 -5
- package/dist/agent-src/skills/api-testing/SKILL.md +16 -2
- package/dist/agent-src/skills/authz-review/SKILL.md +59 -2
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +19 -16
- package/dist/agent-src/skills/character-consistency/evals/evals.json +16 -0
- package/dist/agent-src/skills/code-refactoring/SKILL.md +9 -9
- package/dist/agent-src/skills/command-routing/evals/evals.json +14 -0
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -0
- package/dist/agent-src/skills/dashboard-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/data-flow-mapper/SKILL.md +1 -1
- package/dist/agent-src/skills/dcf-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/decision-record/evals/evals.json +20 -0
- package/dist/agent-src/skills/defense-in-depth/SKILL.md +2 -2
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +7 -7
- package/dist/agent-src/skills/design-intelligence/SKILL.md +8 -8
- package/dist/agent-src/skills/design-intelligence/evals/evals.json +14 -0
- package/dist/agent-src/skills/design-review/SKILL.md +29 -2
- package/dist/agent-src/skills/design-system-capture/SKILL.md +21 -0
- package/dist/agent-src/skills/design-system-capture/evals/evals.json +15 -0
- package/dist/agent-src/skills/developer-like-execution/evals/evals.json +16 -0
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +29 -0
- package/dist/agent-src/skills/doc-coauthoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -0
- package/dist/agent-src/skills/editorial-calendar/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/editorial-calendar/evals/evals.json +16 -0
- package/dist/agent-src/skills/estimate-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -0
- package/dist/agent-src/skills/existing-ui-audit/evals/triggers.json +47 -0
- package/dist/agent-src/skills/fe-design/SKILL.md +43 -14
- package/dist/agent-src/skills/fe-design/evals/triggers.json +47 -0
- package/dist/agent-src/skills/forecasting/evals/evals.json +20 -0
- package/dist/agent-src/skills/fundraising-narrative/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/fundraising-narrative/evals/evals.json +16 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +86 -0
- package/dist/agent-src/skills/html-deck/SKILL.md +27 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +147 -0
- package/dist/agent-src/skills/humanizer/data/patterns.md +190 -0
- package/dist/agent-src/skills/humanizer/evals/triggers.json +17 -0
- package/dist/agent-src/skills/iconography/SKILL.md +24 -0
- package/dist/agent-src/skills/incident-commander/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/incident-commander/evals/evals.json +20 -0
- package/dist/agent-src/skills/laravel/SKILL.md +14 -1
- package/dist/agent-src/skills/launch-readiness/SKILL.md +20 -19
- package/dist/agent-src/skills/launch-readiness/evals/evals.json +21 -0
- package/dist/agent-src/skills/legal-practice-profile/SKILL.md +126 -0
- package/dist/agent-src/skills/logging-monitoring/SKILL.md +1 -1
- package/dist/agent-src/skills/logging-monitoring/evals/evals.json +16 -0
- package/dist/agent-src/skills/mcp/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +14 -1
- package/dist/agent-src/skills/messaging-architecture/evals/domain-truth.json +16 -0
- package/dist/agent-src/skills/messaging-architecture/evals/evals.json +16 -0
- package/dist/agent-src/skills/migration-architect/SKILL.md +2 -1
- package/dist/agent-src/skills/perf-feedback-craft/evals/evals.json +16 -0
- package/dist/agent-src/skills/performance/SKILL.md +1 -1
- package/dist/agent-src/skills/pest-testing/SKILL.md +1 -1
- package/dist/agent-src/skills/php-debugging/SKILL.md +17 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +16 -0
- package/dist/agent-src/skills/prompt-engineering-patterns/evals/evals.json +16 -0
- package/dist/agent-src/skills/readme-writing/SKILL.md +2 -0
- package/dist/agent-src/skills/readme-writing-package/SKILL.md +2 -0
- package/dist/agent-src/skills/refine-prompt/evals/evals.json +16 -0
- package/dist/agent-src/skills/refine-ticket/evals/evals.json +16 -0
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -0
- package/dist/agent-src/skills/release-comms/evals/evals.json +16 -0
- package/dist/agent-src/skills/rice-prioritization/evals/evals.json +20 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +22 -51
- package/dist/agent-src/skills/runway-cognition/evals/evals.json +20 -0
- package/dist/agent-src/skills/scenario-modeling/evals/evals.json +20 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +6 -9
- package/dist/agent-src/skills/sentry-integration/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +8 -0
- package/dist/agent-src/skills/source-discovery/SKILL.md +26 -24
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +98 -0
- package/dist/agent-src/skills/spreadsheet-authoring/evals/triggers.json +47 -0
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +55 -46
- package/dist/agent-src/skills/subagent-orchestration/evals/evals.json +42 -0
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +10 -0
- package/dist/agent-src/skills/systematic-debugging/evals/evals.json +20 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +118 -0
- package/dist/agent-src/skills/test-case-discovery/evals/triggers.json +52 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +32 -6
- package/dist/agent-src/skills/test-driven-development/evals/evals.json +16 -0
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +7 -6
- package/dist/agent-src/skills/threat-modeling/evals/evals.json +21 -0
- package/dist/agent-src/skills/typography-system/evals/evals.json +14 -0
- package/dist/agent-src/skills/unit-economics-modeling/evals/evals.json +24 -0
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +10 -0
- package/dist/agent-src/skills/verify-completion-evidence/evals/evals.json +16 -0
- package/dist/agent-src/skills/vision-articulation/evals/evals.json +16 -0
- package/dist/agent-src/skills/voice-and-tone-design/evals/evals.json +16 -0
- package/dist/agent-src/skills/worktree-lifecycle/SKILL.md +187 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/evals.json +38 -0
- package/dist/agent-src/skills/worktree-lifecycle/evals/triggers.json +52 -0
- package/dist/agent-src/templates/agent-settings.md +3 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +15 -1
- package/dist/agent-src/templates/roadmaps.md +14 -0
- package/dist/cli/commands/recordTriggerEval.js +4 -14
- package/dist/cli/commands/recordTriggerEval.js.map +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +374 -88
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +9 -9
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +30 -18
- package/dist/discovery/trust-report.md +5 -5
- package/dist/discovery/workspaces.json +31 -15
- package/dist/install/install.mjs +12 -7
- package/dist/mcp/registry-manifest.json +4 -4
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_settings_hooks.js +8 -0
- package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
- package/dist/scripts/_lib/trigger_eval_floors.js +24 -0
- package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -0
- package/dist/scripts/condense.js +12 -2
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +5 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/CLAIMS.md +24 -3
- package/docs/architecture.md +3 -3
- package/docs/benchmark.md +51 -0
- package/docs/catalog.md +23 -9
- package/docs/command-flows.md +7 -1
- package/docs/contracts/CHANGELOG-conventions.md +6 -0
- package/docs/contracts/ai-council-config.md +92 -0
- package/docs/contracts/command-clusters.md +1 -0
- package/docs/contracts/design-artifact-lifecycle.md +93 -0
- package/docs/contracts/design-artifact-verification.md +30 -1
- package/docs/contracts/evidence-based-pruning.md +1 -0
- package/docs/contracts/hook-architecture-v1.md +25 -0
- package/docs/contracts/kernel-membership.md +35 -0
- package/docs/contracts/measurement-baseline.md +1 -0
- package/docs/contracts/release-pr-gating.md +26 -0
- package/docs/contracts/release-sizing.md +107 -0
- package/docs/contracts/surface-agent-contracts.md +153 -0
- package/docs/contracts/write-engine.md +38 -1
- package/docs/customization.md +12 -0
- package/docs/decisions/ADR-002-kernel-bucket-overrides.md +11 -0
- package/docs/decisions/ADR-003-flat-cluster-subs-and-colon-syntax.md +11 -0
- package/docs/decisions/ADR-006-skill-tools-python-pilot.md +1 -1
- package/docs/decisions/ADR-028-root-layout.md +1 -1
- package/docs/decisions/ADR-038-canonical-settings-path.md +10 -0
- package/docs/decisions/ADR-092-defer-command-tier-alias-removal.md +29 -5
- package/docs/decisions/ADR-093-ai-council-config-user-global.md +1 -1
- package/docs/decisions/ADR-100-global-knowledge-card-sharing.md +7 -0
- package/docs/decisions/ADR-103-global-knowledge-default-off-until-measured.md +2 -2
- package/docs/decisions/ADR-105-automatic-subagent-orchestration.md +1 -1
- package/docs/decisions/ADR-114-commit-policy-iron-law-override.md +11 -0
- package/docs/decisions/ADR-115-command-cluster-consolidation-phase-4.md +1 -1
- package/docs/decisions/ADR-116-memory-tripwire-activation-path.md +11 -1
- package/docs/decisions/ADR-117-subagents-auto-default-on.md +2 -1
- package/docs/decisions/ADR-118-loop-engineering-boundaries.md +162 -0
- package/docs/decisions/ADR-119-global-knowledge-default-on.md +104 -0
- package/docs/decisions/ADR-200-python-to-typescript-migration.md +11 -1
- package/docs/decisions/ADR-rule-kernel-and-router.md +11 -0
- package/docs/decisions/INDEX.md +11 -9
- package/docs/design/cross-model-parity-eval.md +82 -0
- package/docs/design/length-neutral-judge-rerun.md +92 -0
- package/docs/distribution/consumer-matrix.md +96 -0
- package/docs/featured-skills.md +2 -2
- package/docs/getting-started-by-role.md +1 -1
- package/docs/getting-started.md +2 -2
- package/docs/governance-advantage.md +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +80 -5
- package/docs/guidelines/code-clarity.md +106 -1
- package/docs/guidelines/design-antipatterns.md +20 -0
- package/docs/guidelines/php/php-coding-patterns.md +12 -2
- package/docs/proof.md +21 -15
- package/docs/release-runbook.md +3 -0
- package/docs/self-review-gate.md +51 -0
- package/docs/wedge/production-validator/production-validator.md +27 -0
- package/package.json +1 -1
- package/src/config/agent-settings.template.yml +29 -9
- package/src/scripts/_cli/cmd_doctor.ts +34 -1
- package/src/scripts/_lib/auto_dispatch.corpus.test.ts +87 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +8 -0
- package/src/scripts/_lib/knowledge_global.ts +6 -2
- package/src/scripts/_lib/knowledge_global_redaction.ts +42 -4
- package/src/scripts/_lib/lexical_index.ts +15 -2
- package/src/scripts/_lib/orchestration_record.ts +27 -0
- package/src/scripts/_lib/orchestration_savings.ts +52 -0
- package/src/scripts/_lib/trigger_eval_floors.ts +31 -0
- package/src/scripts/ai_council/chairman.ts +90 -0
- package/src/scripts/ai_council/clients.ts +18 -3
- package/src/scripts/ai_council/config.ts +157 -0
- package/src/scripts/ai_council/debate_gates.ts +89 -0
- package/src/scripts/ai_council/orchestrator.ts +65 -2
- package/src/scripts/ai_council/prompts.ts +121 -11
- package/src/scripts/ai_council/replay.ts +17 -0
- package/src/scripts/ai_council/stance_tally.ts +184 -0
- package/src/scripts/ai_tells_rules.ts +433 -0
- package/src/scripts/bench_humanizer_eval.ts +240 -0
- package/src/scripts/build_discovery_manifest.ts +6 -0
- package/src/scripts/build_proof.ts +21 -9
- package/src/scripts/check_claims.ts +40 -0
- package/src/scripts/check_release_adjacent_health.ts +121 -0
- package/src/scripts/check_rule_invariants.ts +282 -0
- package/src/scripts/condense.ts +16 -2
- package/src/scripts/consumer_matrix.ts +392 -0
- package/src/scripts/council_cli.ts +147 -0
- package/src/scripts/detect_ai_tells.ts +312 -0
- package/src/scripts/generate_index.ts +3 -2
- package/src/scripts/install.ts +12 -0
- package/src/scripts/knowledge_global_cli.ts +1 -1
- package/src/scripts/lint_changelog_rollback.ts +293 -0
- package/src/scripts/lint_framework_leakage.ts +88 -7
- package/src/scripts/lint_framework_leakage_allowlist.json +59 -572
- package/src/scripts/lint_knowledge_scale.ts +30 -0
- package/src/scripts/lint_originality_shingles.ts +210 -0
- package/src/scripts/orchestration_record.ts +17 -0
- package/src/scripts/orchestration_savings_report.ts +36 -3
- package/src/scripts/schemas/agent-settings.schema.json +11 -0
- package/src/scripts/second_brain_retrieval.ts +44 -2
- package/src/scripts/self_review_gate.test.ts +65 -0
- package/src/scripts/self_review_gate.ts +298 -0
- package/src/scripts/skill_eval_coverage.ts +71 -3
- package/src/scripts/skill_linter.ts +1 -0
- package/src/scripts/trigger_eval_grandfather.json +223 -224
- package/src/scripts/trigger_eval_rotation.ts +344 -0
- package/src/scripts/update_counts.ts +5 -17
- package/src/scripts/worktree_cleanup_check.ts +263 -0
- package/src/server/schemas/settings.ts +8 -3
- package/src/scripts/analysis_freshness.ts +0 -362
- package/src/scripts/export_replay_corpus.ts +0 -208
|
@@ -166,6 +166,36 @@ export function runChecks(root: string): Warning[] {
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
// 6. Contested cards — surface knowledge cards flagged `contested: true`
|
|
170
|
+
// (set when `check_memory_contradiction.ts` fires) so a weak / disputed
|
|
171
|
+
// claim stays visibly weak across sessions instead of silently hardening.
|
|
172
|
+
const cardsDir = path.join(root, 'agents', 'knowledge');
|
|
173
|
+
const contested = listFiles(cardsDir, ['.md'])
|
|
174
|
+
.filter((f) => {
|
|
175
|
+
const base = path.basename(f).toLowerCase();
|
|
176
|
+
return base !== 'readme.md' && base !== 'index.md';
|
|
177
|
+
})
|
|
178
|
+
.filter((f) => {
|
|
179
|
+
let text: string;
|
|
180
|
+
try {
|
|
181
|
+
text = fs.readFileSync(f, 'utf-8');
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
// frontmatter-scoped: `contested: true` on its own line near the top.
|
|
186
|
+
return /^\s*contested:\s*true\s*$/m.test(text.slice(0, 2000));
|
|
187
|
+
})
|
|
188
|
+
.map((f) => path.basename(f));
|
|
189
|
+
if (contested.length > 0) {
|
|
190
|
+
warnings.push({
|
|
191
|
+
rule: 'contested-cards',
|
|
192
|
+
metric: `${contested.length}/0`,
|
|
193
|
+
message:
|
|
194
|
+
`${contested.length} knowledge card(s) carry \`contested: true\`: ${contested.join(', ')}. ` +
|
|
195
|
+
'Resolution path: reconcile each against its `contradictions: [id]` list (human-judged) — a contested claim must not be cited as settled until resolved.',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
169
199
|
// 5. Hot-context budget.
|
|
170
200
|
const hotContext = path.join(root, 'agents', 'runtime', 'state', 'hot-context.md');
|
|
171
201
|
let hotText = '';
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* lint_originality_shingles — mechanical re-skin detector for the artifact
|
|
4
|
+
* corpus (road-to-opt-subagent-harvest P1.3).
|
|
5
|
+
*
|
|
6
|
+
* Compares every pair of skills / personas / subagents by entity-neutralized
|
|
7
|
+
* 8-word shingle overlap. Two artifacts that differ only in the named
|
|
8
|
+
* framework, product, or persona ("re-skins") share almost all shingles once
|
|
9
|
+
* entities are masked — exactly the near-duplicate class a human review
|
|
10
|
+
* misses at 200+ files. Slots beside `persona-governance` / `skill-quality`.
|
|
11
|
+
*
|
|
12
|
+
* Sibling of `lint_skill_originality` (ADR-096), NOT a replacement: that
|
|
13
|
+
* gate runs bag-of-words Jaccard over SKILLS with pack-domain severity;
|
|
14
|
+
* this one masks entities and compares 8-word PHRASES across skills +
|
|
15
|
+
* personas + subagents — the re-skin that only swaps "Laravel" for
|
|
16
|
+
* "Django" scores LOW on plain token-Jaccard (different entity tokens)
|
|
17
|
+
* and HIGH here. Run both.
|
|
18
|
+
*
|
|
19
|
+
* Modes:
|
|
20
|
+
* default report-only — prints the top overlapping pairs,
|
|
21
|
+
* always exits 0 (first-run calibration mode).
|
|
22
|
+
* --threshold <pct> exits 1 when any pair's overlap (Jaccard, %) is
|
|
23
|
+
* >= pct and the pair is not allowlisted below.
|
|
24
|
+
* --top <n> rows to print (default 15).
|
|
25
|
+
* --quiet suppress the report (threshold mode still fails).
|
|
26
|
+
*
|
|
27
|
+
* Known-legitimate pairs (shared scaffolding, sibling carve-outs) are
|
|
28
|
+
* allowlisted inline with reasons — same >20-entries policy as every other
|
|
29
|
+
* allowlist in this repo.
|
|
30
|
+
*/
|
|
31
|
+
import * as fs from 'node:fs';
|
|
32
|
+
import * as path from 'node:path';
|
|
33
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
34
|
+
|
|
35
|
+
const _HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
const REPO = path.resolve(_HERE, '..', '..');
|
|
37
|
+
|
|
38
|
+
const SHINGLE_LEN = 8;
|
|
39
|
+
|
|
40
|
+
/** Entity mask — named stacks/products/personas collapse to one token so a
|
|
41
|
+
* re-skin that only swaps the entity still matches. Lowercase input. */
|
|
42
|
+
const ENTITY_RE = new RegExp(
|
|
43
|
+
'\\b(laravel|symfony|django|rails|react|vue|angular|nextjs|next\\.js|nuxt|' +
|
|
44
|
+
'nestjs|express|fastify|flask|fastapi|spring|eloquent|doctrine|prisma|' +
|
|
45
|
+
'pest|phpunit|jest|vitest|pytest|phpstan|rector|ecs|prettier|eslint|' +
|
|
46
|
+
'mypy|tsc|php|python|typescript|javascript|node|go|rust|ruby|java|' +
|
|
47
|
+
'tailwind|blade|livewire|flux|jira|sentry|grafana|terraform|terragrunt|' +
|
|
48
|
+
'traefik|docker|aws|stripe|openai|anthropic|claude|gemini|gpt)\\b',
|
|
49
|
+
'g',
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
interface Doc {
|
|
53
|
+
id: string;
|
|
54
|
+
shingles: Set<string>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface AllowPair {
|
|
58
|
+
a: string;
|
|
59
|
+
b: string;
|
|
60
|
+
reason: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Sibling artifacts that legitimately share scaffolding. Keep < 20. */
|
|
64
|
+
const ALLOWLIST: AllowPair[] = [
|
|
65
|
+
// Populated from calibration runs; empty on first ship — the default
|
|
66
|
+
// mode is report-only, so no entry is needed until a threshold is armed.
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
function _normalize(body: string): string[] {
|
|
70
|
+
// strip fenced code, frontmatter, markdown syntax; mask entities.
|
|
71
|
+
let text = body.replace(/^---\n[\s\S]*?\n---\n/, '');
|
|
72
|
+
text = text.replace(/```[\s\S]*?```/g, ' ');
|
|
73
|
+
text = text.toLowerCase();
|
|
74
|
+
text = text.replace(ENTITY_RE, '§entity§');
|
|
75
|
+
text = text.replace(/[^a-z0-9§]+/g, ' ');
|
|
76
|
+
return text.split(' ').filter((w) => w.length > 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _shingles(words: string[]): Set<string> {
|
|
80
|
+
const out = new Set<string>();
|
|
81
|
+
for (let i = 0; i + SHINGLE_LEN <= words.length; i++) {
|
|
82
|
+
out.add(words.slice(i, i + SHINGLE_LEN).join(' '));
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function _collect(): Doc[] {
|
|
88
|
+
const docs: Doc[] = [];
|
|
89
|
+
const push = (id: string, file: string): void => {
|
|
90
|
+
let body: string;
|
|
91
|
+
try {
|
|
92
|
+
body = fs.readFileSync(file, 'utf-8');
|
|
93
|
+
} catch {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const sh = _shingles(_normalize(body));
|
|
97
|
+
if (sh.size >= 20) {
|
|
98
|
+
docs.push({ id, shingles: sh });
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const skillsDir = path.join(REPO, 'src', 'skills');
|
|
102
|
+
for (const d of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
103
|
+
if (d.isDirectory()) {
|
|
104
|
+
push(`skill:${d.name}`, path.join(skillsDir, d.name, 'SKILL.md'));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const personasDir = path.join(REPO, 'src', 'personas');
|
|
108
|
+
if (fs.existsSync(personasDir)) {
|
|
109
|
+
const walk = (dir: string, prefix: string): void => {
|
|
110
|
+
for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
111
|
+
const full = path.join(dir, d.name);
|
|
112
|
+
if (d.isDirectory() && !d.name.startsWith('_')) {
|
|
113
|
+
walk(full, prefix);
|
|
114
|
+
} else if (d.isFile() && d.name.endsWith('.md') && d.name !== 'README.md') {
|
|
115
|
+
push(`persona:${path.basename(d.name, '.md')}`, full);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
walk(personasDir, 'persona');
|
|
120
|
+
}
|
|
121
|
+
const subagentsDir = path.join(REPO, 'src', 'subagents');
|
|
122
|
+
if (fs.existsSync(subagentsDir)) {
|
|
123
|
+
for (const f of fs.readdirSync(subagentsDir)) {
|
|
124
|
+
if (f.endsWith('.md') && !f.startsWith('_')) {
|
|
125
|
+
push(`subagent:${path.basename(f, '.md')}`, path.join(subagentsDir, f));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return docs;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function _jaccardPct(a: Set<string>, b: Set<string>): number {
|
|
133
|
+
const [small, large] = a.size <= b.size ? [a, b] : [b, a];
|
|
134
|
+
let inter = 0;
|
|
135
|
+
for (const s of small) {
|
|
136
|
+
if (large.has(s)) inter++;
|
|
137
|
+
}
|
|
138
|
+
const union = a.size + b.size - inter;
|
|
139
|
+
return union === 0 ? 0 : (100 * inter) / union;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function _allowlisted(a: string, b: string): boolean {
|
|
143
|
+
return ALLOWLIST.some(
|
|
144
|
+
(p) => (p.a === a && p.b === b) || (p.a === b && p.b === a),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
149
|
+
let threshold: number | null = null;
|
|
150
|
+
let top = 15;
|
|
151
|
+
let quiet = false;
|
|
152
|
+
for (let i = 0; i < argv.length; i++) {
|
|
153
|
+
const arg = argv[i] as string;
|
|
154
|
+
if (arg === '--threshold') threshold = Number(argv[++i]);
|
|
155
|
+
else if (arg === '--top') top = Number(argv[++i]);
|
|
156
|
+
else if (arg === '--quiet') quiet = true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const docs = _collect();
|
|
160
|
+
const pairs: Array<{ a: string; b: string; pct: number }> = [];
|
|
161
|
+
for (let i = 0; i < docs.length; i++) {
|
|
162
|
+
for (let j = i + 1; j < docs.length; j++) {
|
|
163
|
+
const pct = _jaccardPct((docs[i] as Doc).shingles, (docs[j] as Doc).shingles);
|
|
164
|
+
if (pct > 0.5) {
|
|
165
|
+
pairs.push({ a: (docs[i] as Doc).id, b: (docs[j] as Doc).id, pct });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
pairs.sort((x, y) => y.pct - x.pct);
|
|
170
|
+
|
|
171
|
+
if (!quiet) {
|
|
172
|
+
process.stdout.write(
|
|
173
|
+
`originality-shingles: ${docs.length} docs · ${SHINGLE_LEN}-word entity-masked shingles\n`,
|
|
174
|
+
);
|
|
175
|
+
for (const p of pairs.slice(0, top)) {
|
|
176
|
+
process.stdout.write(` ${p.pct.toFixed(1).padStart(5)}% ${p.a} ↔ ${p.b}\n`);
|
|
177
|
+
}
|
|
178
|
+
if (pairs.length === 0) {
|
|
179
|
+
process.stdout.write(' no pair above 0.5% overlap\n');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (threshold !== null && Number.isFinite(threshold)) {
|
|
184
|
+
const offenders = pairs.filter((p) => p.pct >= (threshold as number) && !_allowlisted(p.a, p.b));
|
|
185
|
+
if (offenders.length > 0) {
|
|
186
|
+
process.stderr.write(
|
|
187
|
+
`❌ originality-shingles: ${offenders.length} pair(s) at or above ${threshold}% overlap:\n`,
|
|
188
|
+
);
|
|
189
|
+
for (const p of offenders.slice(0, top)) {
|
|
190
|
+
process.stderr.write(` ${p.pct.toFixed(1)}% ${p.a} ↔ ${p.b}\n`);
|
|
191
|
+
}
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function _isCliEntry(): boolean {
|
|
199
|
+
const a = process.argv[1];
|
|
200
|
+
if (!a) return false;
|
|
201
|
+
if (pathToFileURL(path.resolve(a)).href === import.meta.url) return true;
|
|
202
|
+
try {
|
|
203
|
+
return fs.realpathSync(a) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
204
|
+
} catch {
|
|
205
|
+
return false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (_isCliEntry()) {
|
|
209
|
+
process.exit(main());
|
|
210
|
+
}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* --spawn-count 1 --token-delta -72000 --provenance measured \
|
|
12
12
|
* --tier-chosen lite --tier-source inferred --task-class read-only-fanout \
|
|
13
13
|
* [--tiers sonnet,opus] [--wall-clock-ms 18500] [--dispatch-outcome DONE] \
|
|
14
|
+
* [--first-pass-success true|false] [--escalated true|false] \
|
|
14
15
|
* [--dir <audit-dir>] [--dry-run]
|
|
15
16
|
*
|
|
16
17
|
* Read by `src/scripts/orchestration_savings_report.ts`.
|
|
@@ -65,6 +66,19 @@ function str(flags: Flags, key: string): string | undefined {
|
|
|
65
66
|
return typeof v === 'string' ? v : undefined;
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
/**
|
|
70
|
+
* Parse an explicit boolean flag value. `--key true|false` → boolean; a bare
|
|
71
|
+
* `--key` counts as `true`; any other value is passed through so the lib's
|
|
72
|
+
* non-boolean validation rejects it with a clear error.
|
|
73
|
+
*/
|
|
74
|
+
function bool(flags: Flags, key: string): boolean | undefined {
|
|
75
|
+
const v = flags[key];
|
|
76
|
+
if (v === undefined) return undefined;
|
|
77
|
+
if (v === true || v === 'true') return true;
|
|
78
|
+
if (v === 'false') return false;
|
|
79
|
+
return v as unknown as boolean; // invalid string → caught by the lib's typeof validation
|
|
80
|
+
}
|
|
81
|
+
|
|
68
82
|
function int(flags: Flags, key: string): number | undefined {
|
|
69
83
|
const v = str(flags, key);
|
|
70
84
|
if (v === undefined) return undefined;
|
|
@@ -98,12 +112,15 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
98
112
|
tier_chosen: str(flags, 'tier-chosen') as TierChosen | undefined,
|
|
99
113
|
tier_source: str(flags, 'tier-source') as TierSource | undefined,
|
|
100
114
|
task_class: str(flags, 'task-class'),
|
|
115
|
+
dispatch_mode: str(flags, 'dispatch-mode') as never,
|
|
101
116
|
task_size_estimate: int(flags, 'task-size-estimate'),
|
|
102
117
|
wall_clock_ms: int(flags, 'wall-clock-ms'),
|
|
103
118
|
dispatch_tokens: int(flags, 'dispatch-tokens'),
|
|
104
119
|
session_tier: str(flags, 'session-tier'),
|
|
105
120
|
dispatch_outcome: str(flags, 'dispatch-outcome') as DispatchOutcome | undefined,
|
|
106
121
|
verify_mode: str(flags, 'verify-mode') as VerifyMode | undefined,
|
|
122
|
+
first_pass_success: bool(flags, 'first-pass-success'),
|
|
123
|
+
escalated: bool(flags, 'escalated'),
|
|
107
124
|
phase: str(flags, 'phase') as LinePhase | undefined,
|
|
108
125
|
outcome: str(flags, 'outcome') as LineOutcome | undefined,
|
|
109
126
|
confidence_band: str(flags, 'confidence-band') as Band | undefined,
|
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
* and prints an aggregate token-savings report. Read-only; no network, no
|
|
6
6
|
* mutation. Handles a missing/empty audit dir gracefully.
|
|
7
7
|
*
|
|
8
|
+
* QUALITY × COST PAIRING (council verdict): the savings figures never render
|
|
9
|
+
* without the quality columns (first_pass_success_rate / escalation_rate).
|
|
10
|
+
* When ≥ 20 lines in the window carry the quality fields the real rates
|
|
11
|
+
* render; below 20 the columns render as "n/a (n=<count>)" beside savings.
|
|
12
|
+
*
|
|
8
13
|
* Usage:
|
|
9
14
|
* ./scripts-run src/scripts/orchestration_savings_report \
|
|
10
15
|
* [--dir <path>] [--format text|json] [--weights lite=1,medium=5,high=15]
|
|
@@ -77,13 +82,41 @@ export function readAuditLines(dir: string): AuditLine[] {
|
|
|
77
82
|
return lines;
|
|
78
83
|
}
|
|
79
84
|
|
|
80
|
-
|
|
85
|
+
/** Dispatches carrying the quality fields at/above this render the real rates; below, "n/a (n=<count>)". */
|
|
86
|
+
export const QUALITY_GATE_MIN_LINES = 20;
|
|
87
|
+
|
|
88
|
+
function pct(v: number): string {
|
|
89
|
+
return `${(v * 100).toFixed(0)}%`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Render the paired cost × quality block. Council verdict: savings NEVER render
|
|
94
|
+
* without the quality columns — at n ≥ QUALITY_GATE_MIN_LINES the real rates
|
|
95
|
+
* render; below, the quality columns render as "n/a (n=<count>)" alongside the
|
|
96
|
+
* savings figures. There is no savings-only render path.
|
|
97
|
+
*/
|
|
98
|
+
function renderPairedCostQuality(r: SavingsReport): string[] {
|
|
99
|
+
const q = r.quality;
|
|
100
|
+
const gated = q.quality_lines >= QUALITY_GATE_MIN_LINES;
|
|
101
|
+
const fps = gated && q.first_pass_success_rate !== null
|
|
102
|
+
? `${pct(q.first_pass_success_rate)} (n=${q.first_pass_lines})`
|
|
103
|
+
: `n/a (n=${q.first_pass_lines})`;
|
|
104
|
+
const esc = gated && q.escalation_rate !== null
|
|
105
|
+
? `${pct(q.escalation_rate)} (n=${q.escalated_lines})`
|
|
106
|
+
: `n/a (n=${q.escalated_lines})`;
|
|
107
|
+
return [
|
|
108
|
+
' cost × quality (paired — never savings alone):',
|
|
109
|
+
` net token_delta: ${r.net_token_delta} (negative = net saved) | first_pass_success_rate: ${fps}`,
|
|
110
|
+
` tokens saved: ${r.tokens_saved} · tokens added: ${r.tokens_added} | escalation_rate: ${esc}`,
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function renderText(r: SavingsReport, dir: string): string {
|
|
81
115
|
const out: string[] = [];
|
|
82
116
|
out.push('Orchestration savings report');
|
|
83
117
|
out.push(` source: ${dir}`);
|
|
84
118
|
out.push(` dispatches: ${r.dispatches} (total spawns: ${r.total_spawns})`);
|
|
85
|
-
out.push(
|
|
86
|
-
out.push(` tokens saved: ${r.tokens_saved} tokens added: ${r.tokens_added}`);
|
|
119
|
+
out.push(...renderPairedCostQuality(r));
|
|
87
120
|
out.push(` provenance: measured ${r.by_provenance.measured.dispatches} (Δ ${r.by_provenance.measured.net_token_delta}) · estimated ${r.by_provenance.estimated.dispatches} (Δ ${r.by_provenance.estimated.net_token_delta})`);
|
|
88
121
|
out.push(` measured share: ${(r.measured_share * 100).toFixed(0)}%`);
|
|
89
122
|
if (Object.keys(r.by_tier).length) {
|
|
@@ -84,6 +84,17 @@
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
},
|
|
87
|
+
"code_style": {
|
|
88
|
+
"type": "object",
|
|
89
|
+
"additionalProperties": true,
|
|
90
|
+
"properties": {
|
|
91
|
+
"docblocks": {
|
|
92
|
+
"type": "string",
|
|
93
|
+
"enum": ["minimal", "full"],
|
|
94
|
+
"description": "Consumed by the code-comment-discipline rule. minimal (default) = no signature-mirroring docblocks; docblocks only for machine-relevant precision (generics, array shapes) or genuine why-context. full = exported public library surface may carry one-line summary docblocks; the redundancy ban still holds."
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
},
|
|
87
98
|
"tokens": {
|
|
88
99
|
"type": "object",
|
|
89
100
|
"additionalProperties": true,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
_setMemoryRoot,
|
|
39
39
|
retrieve,
|
|
40
40
|
} from './memory_lookup.js';
|
|
41
|
+
import { sanitize_text } from './_lib/retrieval_sanitize.js';
|
|
41
42
|
import { scoreTask } from './second_brain_score.js';
|
|
42
43
|
|
|
43
44
|
const _HERE = fileURLToPath(import.meta.url);
|
|
@@ -67,11 +68,19 @@ export function bindStore(): void {
|
|
|
67
68
|
_setIntakeRoot(path.join(STORE_DIR, 'intake-none'));
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
export function loadCorpus(): {
|
|
71
|
+
export function loadCorpus(): {
|
|
72
|
+
type: string;
|
|
73
|
+
k: number;
|
|
74
|
+
tasks: RetrievalTask[];
|
|
75
|
+
stale_ids?: string[];
|
|
76
|
+
poisoned_ids?: string[];
|
|
77
|
+
} {
|
|
71
78
|
const doc = YAML.parse(fs.readFileSync(path.join(STORE_DIR, 'retrieval-corpus.yml'), 'utf-8')) as {
|
|
72
79
|
type: string;
|
|
73
80
|
k: number;
|
|
74
81
|
tasks: RetrievalTask[];
|
|
82
|
+
stale_ids?: string[];
|
|
83
|
+
poisoned_ids?: string[];
|
|
75
84
|
};
|
|
76
85
|
return doc;
|
|
77
86
|
}
|
|
@@ -162,11 +171,17 @@ export function signTestP(wins: number, losses: number): number {
|
|
|
162
171
|
|
|
163
172
|
export async function run(opts: { mode: 'dry-run' | 'live'; host: string; seeds: number }): Promise<Record<string, unknown>> {
|
|
164
173
|
bindStore();
|
|
165
|
-
const { type, k, tasks } = loadCorpus();
|
|
174
|
+
const { type, k, tasks, stale_ids = [], poisoned_ids = [] } = loadCorpus();
|
|
175
|
+
const staleSet = new Set(stale_ids);
|
|
176
|
+
const poisonedSet = new Set(poisoned_ids);
|
|
166
177
|
const apiKey = opts.mode === 'live' ? load_anthropic_key() : '';
|
|
167
178
|
const placebo = _placeboBodies(type);
|
|
168
179
|
|
|
169
180
|
let precisionHits = 0;
|
|
181
|
+
let topkSlots = 0;
|
|
182
|
+
let staleHits = 0;
|
|
183
|
+
let poisonedRetrieved = 0;
|
|
184
|
+
let poisonedNeutralized = 0;
|
|
170
185
|
let tieSetSum = 0;
|
|
171
186
|
const perTask: Array<Record<string, unknown>> = [];
|
|
172
187
|
const armPass: Record<Arm, number> = { 'retrieval-off': 0, 'retrieval-on': 0, placebo: 0 };
|
|
@@ -177,6 +192,18 @@ export async function run(opts: { mode: 'dry-run' | 'live'; host: string; seeds:
|
|
|
177
192
|
for (const task of tasks) {
|
|
178
193
|
const { topk, rank, tieSetSize } = retrieveFor(task, type, k);
|
|
179
194
|
const ids = topk.map((r) => r.id);
|
|
195
|
+
topkSlots += topk.length;
|
|
196
|
+
for (const hit of topk) {
|
|
197
|
+
if (staleSet.has(hit.id)) staleHits += 1;
|
|
198
|
+
if (poisonedSet.has(hit.id)) {
|
|
199
|
+
poisonedRetrieved += 1;
|
|
200
|
+
// Rejection = the sanitize floor strips every hidden-instruction
|
|
201
|
+
// vector before the body could reach a prompt.
|
|
202
|
+
const clean = sanitize_text(hit.body);
|
|
203
|
+
if (!/[---]/u.test(clean)) poisonedNeutralized += 1;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
180
207
|
const neededInTopK = rank >= 0 && rank < k;
|
|
181
208
|
if (neededInTopK) precisionHits += 1;
|
|
182
209
|
tieSetSum += tieSetSize;
|
|
@@ -226,6 +253,21 @@ export async function run(opts: { mode: 'dry-run' | 'live'; host: string; seeds:
|
|
|
226
253
|
k,
|
|
227
254
|
store: 'internal/bench/second-brain/retrieval-store/ (5 needed + 19 distractors, keyword-overlapping confusers)',
|
|
228
255
|
precision_at_k: precisionAtK,
|
|
256
|
+
// road-to-proof-under-real-conditions Phase 5 — retrieval quality pair:
|
|
257
|
+
// stale_hit_rate: fraction of retrieved top-k slots occupied by
|
|
258
|
+
// SUPERSEDED store entries (lower is better; the substrate has no
|
|
259
|
+
// recency/supersede weighting yet, so a non-zero rate is the honest
|
|
260
|
+
// baseline the FTS5/recency activation path improves on).
|
|
261
|
+
stale_hit_rate: topkSlots ? staleHits / topkSlots : 0,
|
|
262
|
+
stale_hits: staleHits,
|
|
263
|
+
topk_slots: topkSlots,
|
|
264
|
+
// poisoned_rejection_rate: of poisoned entries that WERE retrieved,
|
|
265
|
+
// the fraction whose hidden-instruction vectors the sanitize floor
|
|
266
|
+
// strips (target 1.0; null when none was retrieved this run).
|
|
267
|
+
poisoned_rejection_rate: poisonedRetrieved ? poisonedNeutralized / poisonedRetrieved : null,
|
|
268
|
+
poisoned_retrieved: poisonedRetrieved,
|
|
269
|
+
poisoned_neutralized: poisonedNeutralized,
|
|
270
|
+
|
|
229
271
|
precision_hits: precisionHits,
|
|
230
272
|
mean_tie_set_size: meanTieSet,
|
|
231
273
|
discrimination_note:
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
classifyBlocking,
|
|
5
|
+
gateVerdict,
|
|
6
|
+
isReviewablePath,
|
|
7
|
+
renderReview,
|
|
8
|
+
type Finding,
|
|
9
|
+
} from './self_review_gate.js';
|
|
10
|
+
|
|
11
|
+
const f = (severity: Finding['severity'], kind: Finding['kind']): Finding => ({
|
|
12
|
+
severity,
|
|
13
|
+
kind,
|
|
14
|
+
title: `${severity} ${kind}`,
|
|
15
|
+
detail: 'x',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('classifyBlocking', () => {
|
|
19
|
+
it('blocks security + high and claim + critical', () => {
|
|
20
|
+
expect(classifyBlocking(f('high', 'security'))).toBe(true);
|
|
21
|
+
expect(classifyBlocking(f('critical', 'claim'))).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it('does not block low/medium security, or any style/correctness', () => {
|
|
24
|
+
expect(classifyBlocking(f('medium', 'security'))).toBe(false);
|
|
25
|
+
expect(classifyBlocking(f('low', 'claim'))).toBe(false);
|
|
26
|
+
expect(classifyBlocking(f('critical', 'style'))).toBe(false);
|
|
27
|
+
expect(classifyBlocking(f('high', 'correctness'))).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe('gateVerdict', () => {
|
|
32
|
+
const blocking = [f('high', 'security')];
|
|
33
|
+
it('advisory (default shipped mode) never blocks, even with a blocking finding', () => {
|
|
34
|
+
expect(gateVerdict(blocking, { enforce: false })).toBe(0);
|
|
35
|
+
});
|
|
36
|
+
it('enforce blocks only on a merge-blocking finding', () => {
|
|
37
|
+
expect(gateVerdict(blocking, { enforce: true })).toBe(2);
|
|
38
|
+
expect(gateVerdict([f('critical', 'style')], { enforce: true })).toBe(0);
|
|
39
|
+
expect(gateVerdict([], { enforce: true })).toBe(0);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe('isReviewablePath', () => {
|
|
44
|
+
it('skips generated projections + lockfiles, keeps source', () => {
|
|
45
|
+
expect(isReviewablePath('dist/agent-src/skills/x/SKILL.md')).toBe(false);
|
|
46
|
+
expect(isReviewablePath('.augment/rules/x.md')).toBe(false);
|
|
47
|
+
expect(isReviewablePath('.windsurfrules')).toBe(false);
|
|
48
|
+
expect(isReviewablePath('package-lock.json')).toBe(false);
|
|
49
|
+
expect(isReviewablePath('src/rules/x.md')).toBe(true);
|
|
50
|
+
expect(isReviewablePath('src/scripts/y.ts')).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('renderReview', () => {
|
|
55
|
+
it('carries the HUMAN REVIEW REQUIRED banner + the floor-not-human-review caveat', () => {
|
|
56
|
+
const out = renderReview([], false);
|
|
57
|
+
expect(out).toContain('HUMAN REVIEW REQUIRED');
|
|
58
|
+
expect(out).toContain('not** independent human review');
|
|
59
|
+
});
|
|
60
|
+
it('advisory phrasing says WOULD block, enforce phrasing says blocking', () => {
|
|
61
|
+
const blocking = [f('high', 'security')];
|
|
62
|
+
expect(renderReview(blocking, false)).toContain('WOULD block');
|
|
63
|
+
expect(renderReview(blocking, true)).toContain('merge-blocking');
|
|
64
|
+
});
|
|
65
|
+
});
|