@event4u/agent-config 9.12.0 → 9.13.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 +80 -256
- package/README.md +1 -1
- package/dist/agent-src/rules/active-remediation.md +0 -2
- package/dist/agent-src/rules/architecture.md +0 -1
- package/dist/agent-src/rules/artifact-drafting-protocol.md +0 -4
- package/dist/agent-src/rules/autonomous-execution.md +1 -2
- package/dist/agent-src/rules/broken-access-control.md +0 -2
- package/dist/agent-src/rules/code-comment-discipline.md +0 -3
- package/dist/agent-src/rules/code-provenance.md +0 -3
- package/dist/agent-src/rules/communication-through-line.md +2 -3
- package/dist/agent-src/rules/content-quoting-floor.md +0 -2
- package/dist/agent-src/rules/context-hygiene.md +1 -3
- package/dist/agent-src/rules/decision-revisit-gate.md +0 -2
- package/dist/agent-src/rules/design-fidelity.md +29 -0
- package/dist/agent-src/rules/domain-adoption-policy.md +0 -3
- package/dist/agent-src/rules/downstream-changes.md +10 -1
- package/dist/agent-src/rules/external-reference-deep-dive.md +0 -7
- package/dist/agent-src/rules/fast-path-marker-visibility.md +0 -1
- package/dist/agent-src/rules/git-history-discipline.md +0 -7
- package/dist/agent-src/rules/guidelines.md +0 -2
- package/dist/agent-src/rules/improve-before-implement.md +4 -2
- package/dist/agent-src/rules/invite-challenge.md +0 -4
- package/dist/agent-src/rules/linked-projects-onboarding-gate.md +0 -2
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +0 -1
- package/dist/agent-src/rules/minimal-safe-diff.md +2 -2
- package/dist/agent-src/rules/missing-tool-handling.md +0 -1
- package/dist/agent-src/rules/no-attribution-footers.md +0 -3
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +8 -5
- package/dist/agent-src/rules/no-pr-progress-comments.md +1 -3
- package/dist/agent-src/rules/no-roadmap-references.md +0 -2
- package/dist/agent-src/rules/output-discipline.md +0 -1
- package/dist/agent-src/rules/prefer-enums-over-literals.md +0 -2
- package/dist/agent-src/rules/preservation-guard.md +0 -3
- package/dist/agent-src/rules/role-mode-adherence.md +0 -1
- package/dist/agent-src/rules/secret-vcs-guard.md +0 -2
- package/dist/agent-src/rules/senior-engineering-discipline.md +0 -2
- package/dist/agent-src/rules/session-canary.md +0 -2
- package/dist/agent-src/rules/size-enforcement.md +0 -5
- package/dist/agent-src/rules/source-confidentiality.md +0 -3
- package/dist/agent-src/rules/source-discovery-gate.md +0 -2
- package/dist/agent-src/rules/think-before-action.md +3 -3
- package/dist/agent-src/rules/token-budget-discipline.md +0 -1
- package/dist/agent-src/rules/token-efficiency.md +1 -2
- package/dist/agent-src/rules/tool-safety.md +0 -1
- package/dist/agent-src/rules/user-interaction.md +0 -3
- package/dist/agent-src/rules/user-interrupt-priority.md +0 -1
- package/dist/agent-src/skills/agent-docs-writing/SKILL.md +8 -0
- package/dist/agent-src/skills/analysis-skill-router/SKILL.md +55 -86
- package/dist/agent-src/skills/character-consistency/SKILL.md +7 -4
- package/dist/agent-src/skills/decision-record/SKILL.md +8 -0
- package/dist/agent-src/skills/design-review/SKILL.md +11 -0
- package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +8 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +34 -3
- package/dist/agent-src/skills/fe-design/SKILL.md +10 -0
- package/dist/agent-src/skills/motion-choreographer/SKILL.md +7 -4
- package/dist/agent-src/skills/overbuild-review-lens/SKILL.md +201 -0
- package/dist/agent-src/skills/performance/SKILL.md +14 -0
- package/dist/agent-src/skills/pixar-storyteller/SKILL.md +8 -5
- package/dist/agent-src/skills/scene-expander/SKILL.md +9 -6
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +10 -0
- package/dist/agent-src/skills/universal-project-analysis/SKILL.md +14 -37
- package/dist/agent-src/skills/video-director/SKILL.md +9 -6
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +154 -13
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +118 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +40 -0
- package/dist/agent-src/templates/scripts/work_engine/state.ts +55 -0
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +89 -67
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +174 -13
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +750 -507
- package/dist/router.json +1 -1
- package/dist/scripts/condense.js +2 -2
- package/docs/MIGRATION.md +1 -1
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +71 -0
- package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +24 -1
- package/docs/guidelines/code-clarity.md +9 -2
- package/docs/guidelines/design-fidelity-mechanics.md +44 -0
- package/llms.txt +63 -27
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +154 -13
- package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +118 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +40 -0
- package/src/agent-src/templates/scripts/work_engine/state.ts +55 -0
- package/src/config/agent-settings.template.yml +8 -0
- package/src/scripts/_lib/counted_probe.ts +142 -0
- package/src/scripts/_lib/overbuild_lens_contract.ts +196 -0
- package/src/scripts/_lib/release_scope.ts +207 -0
- package/src/scripts/ai_council/stance_tally.ts +37 -2
- package/src/scripts/audit_auto_rules.ts +9 -9
- package/src/scripts/audit_likelihood.ts +3 -3
- package/src/scripts/audit_overlap.ts +2 -2
- package/src/scripts/audit_skill_overlap.ts +233 -95
- package/src/scripts/audit_skill_overlap_allowlist.json +4 -0
- package/src/scripts/build_proof.ts +15 -1
- package/src/scripts/check_dependency_floors.ts +16 -6
- package/src/scripts/check_gate_coverage.ts +6 -6
- package/src/scripts/check_kernel_rule_bundle.ts +5 -4
- package/src/scripts/check_token_quality_golden.ts +0 -18
- package/src/scripts/code_graph/loader.ts +35 -6
- package/src/scripts/compile_router.ts +3 -8
- package/src/scripts/condense.ts +2 -2
- package/src/scripts/explain_run.ts +0 -1
- package/src/scripts/generate_pack_manifests.ts +3 -20
- package/src/scripts/hooks/block_kernel_rule_writes.ts +145 -10
- package/src/scripts/hooks/block_no_verify.ts +101 -0
- package/src/scripts/lint_agent_security.ts +5 -4
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_regression.ts +92 -1
- package/src/scripts/lint_skill_descriptions.ts +93 -5
- package/src/scripts/project_thin_rules.ts +2 -2
- package/src/scripts/release.ts +107 -3
- package/src/scripts/resolve_lint_scope.ts +53 -0
- package/src/scripts/router_telemetry.ts +0 -5
- package/src/scripts/runtime_dispatcher.ts +40 -0
- package/src/scripts/schemas/pack.schema.json +1 -5
- package/src/scripts/schemas/rule.schema.json +1 -2
- package/src/scripts/self_review_gate.ts +15 -73
- package/src/scripts/skill_linter.ts +93 -11
- package/src/scripts/sweep_dead_scan_roots.ts +550 -0
- package/src/scripts/trigger_coverage.ts +11 -37
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release-aware diff scope for content checks.
|
|
3
|
+
*
|
|
4
|
+
* A release PR's own diff is version, changelog, pack metadata and marketplace
|
|
5
|
+
* files: the substantive work arrived in the commits *before* the cut. Any
|
|
6
|
+
* check scoped to "files changed in this PR" therefore measures the release
|
|
7
|
+
* wrapper, not the release. The visible consequence was a 179-commit release
|
|
8
|
+
* whose skill lint reported **"0 skills checked, INCONCLUSIVE"** and merged.
|
|
9
|
+
*
|
|
10
|
+
* The fix is a range, not a new gate: on a release PR the content checks
|
|
11
|
+
* resolve their scope to `previous_release_tag...HEAD`. Everywhere else the
|
|
12
|
+
* base is unchanged.
|
|
13
|
+
*
|
|
14
|
+
* The three release-detection primitives below were written for
|
|
15
|
+
* `self_review_gate.ts`, which already switched its own analysis range this
|
|
16
|
+
* way. They live here so the second consumer reuses the precedent instead of
|
|
17
|
+
* re-deriving it — two copies of "is this a release PR?" is exactly the drift
|
|
18
|
+
* this package has paid for before. `self_review_gate.ts` re-exports them, so
|
|
19
|
+
* its public surface is unchanged.
|
|
20
|
+
*/
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const REPO_ROOT = path.resolve(__dirname, '..', '..', '..');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A release PR is detected from its own packaging diff (baseRef...HEAD,
|
|
30
|
+
* scoped to CHANGELOG.md + package.json): an added changelog heading and an
|
|
31
|
+
* added package.json version bump, agreeing on the same version. Pure over
|
|
32
|
+
* the patch text — no git call — so it is unit-testable with synthetic
|
|
33
|
+
* patches. See docs/design/release-pr-review-mode.md.
|
|
34
|
+
*/
|
|
35
|
+
export function detectReleaseVersion(patchText: string): string | null {
|
|
36
|
+
const addedLines = patchText
|
|
37
|
+
.split('\n')
|
|
38
|
+
.filter((l) => l.startsWith('+') && !l.startsWith('+++'))
|
|
39
|
+
.map((l) => l.slice(1));
|
|
40
|
+
|
|
41
|
+
let changelogVersion: string | null = null;
|
|
42
|
+
let packageVersion: string | null = null;
|
|
43
|
+
for (const line of addedLines) {
|
|
44
|
+
const changelogMatch = /^##\s*\[(\d+\.\d+\.\d+)\]/.exec(line);
|
|
45
|
+
if (changelogMatch) changelogVersion = changelogMatch[1] ?? null;
|
|
46
|
+
const packageMatch = /^\s*"version"\s*:\s*"(\d+\.\d+\.\d+)"/.exec(line);
|
|
47
|
+
if (packageMatch) packageVersion = packageMatch[1] ?? null;
|
|
48
|
+
}
|
|
49
|
+
if (changelogVersion && packageVersion && changelogVersion === packageVersion) {
|
|
50
|
+
return changelogVersion;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function releaseDetectionPatch(baseRef: string, cwd: string): string {
|
|
56
|
+
const r = spawnSync('git', ['diff', `${baseRef}...HEAD`, '--', 'CHANGELOG.md', 'package.json'], {
|
|
57
|
+
cwd,
|
|
58
|
+
encoding: 'utf8',
|
|
59
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
60
|
+
});
|
|
61
|
+
return (r.stdout ?? '').toString();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Impure wrapper: collects the packaging patch via git, then detects. */
|
|
65
|
+
export function detectReleaseVersionFromGit(
|
|
66
|
+
baseRef: string,
|
|
67
|
+
cwd: string = REPO_ROOT,
|
|
68
|
+
): string | null {
|
|
69
|
+
return detectReleaseVersion(releaseDetectionPatch(baseRef, cwd));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** `major.minor.patch`, tolerating an optional `v` prefix; null if not semver-shaped. */
|
|
73
|
+
function parseSemver(raw: string): [number, number, number] | null {
|
|
74
|
+
const m = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(raw.trim());
|
|
75
|
+
if (!m) return null;
|
|
76
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function semverLessThan(a: [number, number, number], b: [number, number, number]): boolean {
|
|
80
|
+
const [aMajor, aMinor, aPatch] = a;
|
|
81
|
+
const [bMajor, bMinor, bPatch] = b;
|
|
82
|
+
if (aMajor !== bMajor) return aMajor < bMajor;
|
|
83
|
+
if (aMinor !== bMinor) return aMinor < bMinor;
|
|
84
|
+
return aPatch < bPatch;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Highest semver-shaped tag strictly below `version`, or null if none. Pure
|
|
89
|
+
* over the supplied tag list — no git call. Non-semver tags (branch-backup
|
|
90
|
+
* names etc.) are ignored rather than throwing.
|
|
91
|
+
*
|
|
92
|
+
* The filtering is load-bearing, not defensive: this repository carries 152
|
|
93
|
+
* semver tags alongside names like `rebase-backup-pre-squash`, and those sort
|
|
94
|
+
* *above* every version under `git tag --sort=-v:refname`. A naive "newest
|
|
95
|
+
* tag" would resolve the scope to a backup ref.
|
|
96
|
+
*/
|
|
97
|
+
export function pickPreviousTag(version: string, tags: readonly string[]): string | null {
|
|
98
|
+
const target = parseSemver(version);
|
|
99
|
+
if (!target) return null;
|
|
100
|
+
let best: { raw: string; parsed: [number, number, number] } | null = null;
|
|
101
|
+
for (const raw of tags) {
|
|
102
|
+
const parsed = parseSemver(raw);
|
|
103
|
+
if (!parsed) continue;
|
|
104
|
+
if (!semverLessThan(parsed, target)) continue;
|
|
105
|
+
if (!best || semverLessThan(best.parsed, parsed)) best = { raw, parsed };
|
|
106
|
+
}
|
|
107
|
+
return best ? best.raw : null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Local semver tags, newest first is not assumed — `pickPreviousTag` orders them. */
|
|
111
|
+
function localTags(cwd: string): string[] {
|
|
112
|
+
const r = spawnSync('git', ['tag', '--list'], { cwd, encoding: 'utf8', maxBuffer: 8 * 1024 * 1024 });
|
|
113
|
+
if ((r.status ?? 1) !== 0) return [];
|
|
114
|
+
return (r.stdout ?? '')
|
|
115
|
+
.split('\n')
|
|
116
|
+
.map((l) => l.trim())
|
|
117
|
+
.filter((l) => l.length > 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export interface ContentLintScope {
|
|
121
|
+
/** The ref a diff-scoped content check should compare HEAD against. */
|
|
122
|
+
readonly base: string;
|
|
123
|
+
/** True when the current head is a release PR whose scope was widened. */
|
|
124
|
+
readonly isRelease: boolean;
|
|
125
|
+
/** The release version, when detected. */
|
|
126
|
+
readonly version: string | null;
|
|
127
|
+
/** The tag the scope widened to, when resolved. */
|
|
128
|
+
readonly previousTag: string | null;
|
|
129
|
+
/** One line naming what was resolved and why — for the check's own output. */
|
|
130
|
+
readonly reason: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface ResolveOptions {
|
|
134
|
+
/** Branch a normal PR is compared against. */
|
|
135
|
+
readonly baseRef?: string;
|
|
136
|
+
readonly cwd?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Explicit override, highest precedence. Set by a caller that already
|
|
139
|
+
* knows the range (a workflow input, a `--since` flag).
|
|
140
|
+
*/
|
|
141
|
+
readonly since?: string | null | undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve the diff base a content check should use.
|
|
146
|
+
*
|
|
147
|
+
* Precedence: explicit `since` → `RELEASE_LINT_BASE` env → release-PR widening
|
|
148
|
+
* → the ordinary base ref. The release branch is *not* inferred from the
|
|
149
|
+
* branch name alone: the packaging-diff detector is what proves a release, so
|
|
150
|
+
* a branch merely named `release/...` with no version bump stays on the
|
|
151
|
+
* ordinary base rather than silently widening to a whole tag span.
|
|
152
|
+
*
|
|
153
|
+
* Never throws. A release whose previous tag cannot be resolved (the very
|
|
154
|
+
* first release, or a shallow clone with no tags) falls back to the ordinary
|
|
155
|
+
* base and says so in `reason` — a check that cannot widen its scope should
|
|
156
|
+
* report a small scope honestly, not crash the release.
|
|
157
|
+
*/
|
|
158
|
+
export function resolveContentLintScope(opts: ResolveOptions = {}): ContentLintScope {
|
|
159
|
+
const cwd = opts.cwd ?? REPO_ROOT;
|
|
160
|
+
const baseRef = opts.baseRef ?? 'origin/main';
|
|
161
|
+
|
|
162
|
+
const explicit = opts.since ?? process.env['RELEASE_LINT_BASE'] ?? null;
|
|
163
|
+
if (explicit !== null && explicit.trim() !== '') {
|
|
164
|
+
const base = explicit.trim();
|
|
165
|
+
return {
|
|
166
|
+
base,
|
|
167
|
+
isRelease: false,
|
|
168
|
+
version: null,
|
|
169
|
+
previousTag: null,
|
|
170
|
+
reason: `scope pinned to ${base} by an explicit override`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const version = detectReleaseVersionFromGit(baseRef, cwd);
|
|
175
|
+
if (version === null) {
|
|
176
|
+
return {
|
|
177
|
+
base: baseRef,
|
|
178
|
+
isRelease: false,
|
|
179
|
+
version: null,
|
|
180
|
+
previousTag: null,
|
|
181
|
+
reason: `ordinary PR — scope is ${baseRef}...HEAD`,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const previousTag = pickPreviousTag(version, localTags(cwd));
|
|
186
|
+
if (previousTag === null) {
|
|
187
|
+
return {
|
|
188
|
+
base: baseRef,
|
|
189
|
+
isRelease: true,
|
|
190
|
+
version,
|
|
191
|
+
previousTag: null,
|
|
192
|
+
reason:
|
|
193
|
+
`release ${version} detected, but no earlier semver tag is reachable — ` +
|
|
194
|
+
`scope stays ${baseRef}...HEAD and therefore sees only the release wrapper`,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
base: previousTag,
|
|
200
|
+
isRelease: true,
|
|
201
|
+
version,
|
|
202
|
+
previousTag,
|
|
203
|
+
reason:
|
|
204
|
+
`release ${version} detected — scope widened to ${previousTag}...HEAD ` +
|
|
205
|
+
`so the check sees the release, not the version bump`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -51,7 +51,14 @@ export interface StanceTallyResult {
|
|
|
51
51
|
consensus: OptionTally | null; // the option clearing the threshold, or null
|
|
52
52
|
split: boolean; // true when no option clears the threshold
|
|
53
53
|
abstain_count: number;
|
|
54
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Members whose stance line was missing or unparseable. They COUNT toward
|
|
56
|
+
* `w_total` (they responded) and back no option — see the
|
|
57
|
+
* refusal-preservation invariant in `tally_stances`. Surfaced by
|
|
58
|
+
* `render_vote_tally` so a shrunken-signal quorum is visible rather than
|
|
59
|
+
* merely computed.
|
|
60
|
+
*/
|
|
61
|
+
needs_repair: string[];
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
// Tolerant of whitespace and case; requires the three explicit fields in order.
|
|
@@ -150,7 +157,23 @@ export function tally_stances(
|
|
|
150
157
|
parsed.push({ member, ...p });
|
|
151
158
|
}
|
|
152
159
|
|
|
153
|
-
|
|
160
|
+
// ── Refusal-preservation invariant (road-to-governance-invariants P2) ──
|
|
161
|
+
//
|
|
162
|
+
// Selection may never rank an option higher *because* a member refused
|
|
163
|
+
// less. A safety refusal is not a scored-down property, and — measured by
|
|
164
|
+
// spike S0.1 — the way this engine could be steered was not a weight but a
|
|
165
|
+
// DENOMINATOR: `w_total` once counted only members whose stance line
|
|
166
|
+
// parsed, so a refusal phrased as prose ("I will not answer this") vanished
|
|
167
|
+
// from the quorum and made consensus EASIER. Same two backers, margin
|
|
168
|
+
// −0.25 and no consensus when the refusal parsed as an abstention;
|
|
169
|
+
// +0.4167 and consensus when it did not parse. Δ 0.6667, outcome flipped.
|
|
170
|
+
//
|
|
171
|
+
// The fix is structural rather than a new gate: a member who RESPONDED
|
|
172
|
+
// counts toward the quorum whether or not its stance line parsed. It backs
|
|
173
|
+
// no option either way, so an unparseable response now behaves exactly like
|
|
174
|
+
// an abstention — the fail-safe direction. Members who never responded are
|
|
175
|
+
// dropped upstream (`!r.error`) and are correctly absent from `members`.
|
|
176
|
+
const w_total = parsed.length + needs_repair.length; // responded, abstain + unparseable included
|
|
154
177
|
const threshold = CONSENSUS_FRACTION * w_total;
|
|
155
178
|
let abstain_count = 0;
|
|
156
179
|
|
|
@@ -208,6 +231,18 @@ export function render_vote_tally(result: StanceTallyResult): string {
|
|
|
208
231
|
if (result.abstain_count > 0) {
|
|
209
232
|
lines.push(`- abstain — ${result.abstain_count} member(s) (raises the bar)`);
|
|
210
233
|
}
|
|
234
|
+
// Refusal divergence, as an OBSERVATION and never a selection input
|
|
235
|
+
// (road-to-governance-invariants P2). A member whose stance did not parse
|
|
236
|
+
// still counts toward the quorum; saying so here is what makes a shrunken
|
|
237
|
+
// signal visible to a reader instead of merely present in a struct field
|
|
238
|
+
// no caller reads. Nothing in the tally consumes this string.
|
|
239
|
+
if (result.needs_repair.length > 0) {
|
|
240
|
+
lines.push(
|
|
241
|
+
`- unparsed — ${result.needs_repair.length} member(s) responded without a ` +
|
|
242
|
+
`readable stance (${result.needs_repair.join(', ')}); counted in the ` +
|
|
243
|
+
'quorum, backing nothing',
|
|
244
|
+
);
|
|
245
|
+
}
|
|
211
246
|
lines.push(`Threshold: ⅔ × ${result.w_total} = ${result.threshold.toFixed(2)}`);
|
|
212
247
|
lines.push(
|
|
213
248
|
result.consensus !== null
|
|
@@ -136,14 +136,14 @@ export function _split_frontmatter(text: string): [Record<string, Json>, string]
|
|
|
136
136
|
interface TriggerSummary {
|
|
137
137
|
path_prefixes: string[];
|
|
138
138
|
keywords: string[];
|
|
139
|
-
|
|
139
|
+
phrases: string[];
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
/** Mirror `_trigger_summary`. */
|
|
143
143
|
export function _trigger_summary(triggers: Json): TriggerSummary {
|
|
144
144
|
const paths: string[] = [];
|
|
145
145
|
const keywords: string[] = [];
|
|
146
|
-
const
|
|
146
|
+
const phrases: string[] = [];
|
|
147
147
|
const list: Json[] = Array.isArray(triggers) ? triggers : [];
|
|
148
148
|
for (const entry of list) {
|
|
149
149
|
if (entry === null || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
@@ -156,11 +156,11 @@ export function _trigger_summary(triggers: Json): TriggerSummary {
|
|
|
156
156
|
if ('keyword' in e) {
|
|
157
157
|
keywords.push(String(e['keyword']));
|
|
158
158
|
}
|
|
159
|
-
if ('
|
|
160
|
-
|
|
159
|
+
if ('phrase' in e) {
|
|
160
|
+
phrases.push(String(e['phrase']));
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
-
return { path_prefixes: paths, keywords,
|
|
163
|
+
return { path_prefixes: paths, keywords, phrases };
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
interface RuleEntry {
|
|
@@ -208,7 +208,7 @@ export function collect(): RuleEntry[] {
|
|
|
208
208
|
description_chars: pyLen(desc),
|
|
209
209
|
triggers,
|
|
210
210
|
trigger_count:
|
|
211
|
-
triggers.path_prefixes.length + triggers.keywords.length + triggers.
|
|
211
|
+
triggers.path_prefixes.length + triggers.keywords.length + triggers.phrases.length,
|
|
212
212
|
routes_to,
|
|
213
213
|
body_chars: pyLen(body),
|
|
214
214
|
file_chars: pyLen(text),
|
|
@@ -292,7 +292,7 @@ export function render_markdown(rules: RuleEntry[]): string {
|
|
|
292
292
|
const triggers =
|
|
293
293
|
`${r.triggers.path_prefixes.length}p / ` +
|
|
294
294
|
`${r.triggers.keywords.length}k / ` +
|
|
295
|
-
`${r.triggers.
|
|
295
|
+
`${r.triggers.phrases.length}ph`;
|
|
296
296
|
const routes = r.routes_to.map((x) => String(x)).join(', ') || '—';
|
|
297
297
|
lines.push(
|
|
298
298
|
`| ${i} | \`${r.name}\` | ${_pyTruthy(r.tier) ? String(r.tier) : '—'} | ` +
|
|
@@ -302,7 +302,7 @@ export function render_markdown(rules: RuleEntry[]): string {
|
|
|
302
302
|
i++;
|
|
303
303
|
}
|
|
304
304
|
lines.push('');
|
|
305
|
-
lines.push('Trigger key: `Np` = path-prefix, `Nk` = keyword, `
|
|
305
|
+
lines.push('Trigger key: `Np` = path-prefix, `Nk` = keyword, `Nph` = phrase.');
|
|
306
306
|
lines.push('');
|
|
307
307
|
return lines.join('\n');
|
|
308
308
|
}
|
|
@@ -393,7 +393,7 @@ function _ruleToJson(r: RuleEntry): Record<string, Json> {
|
|
|
393
393
|
triggers: {
|
|
394
394
|
path_prefixes: r.triggers.path_prefixes,
|
|
395
395
|
keywords: r.triggers.keywords,
|
|
396
|
-
|
|
396
|
+
phrases: r.triggers.phrases,
|
|
397
397
|
},
|
|
398
398
|
trigger_count: r.trigger_count,
|
|
399
399
|
routes_to: r.routes_to,
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* For every auto-rule from `agents/reports/auto-rules-audit.json`:
|
|
17
17
|
*
|
|
18
18
|
* 1. Build a token set from `description`, `triggers[].keyword`,
|
|
19
|
-
* `triggers[].
|
|
19
|
+
* `triggers[].phrase`, and the rule name itself.
|
|
20
20
|
* 2. Index a corpus of skills (`SKILL.md`), contexts, guidelines, and
|
|
21
21
|
* command files.
|
|
22
22
|
* 3. Score `corpus_hits = sum(1 for token in tokens if token in corpus)`.
|
|
@@ -197,9 +197,9 @@ function _ruleTokens(rule: Json): Set<string> {
|
|
|
197
197
|
add(tokens(String(rule['name'] ?? '').replaceAll('-', ' ')));
|
|
198
198
|
const trig = rule['triggers'] ?? {};
|
|
199
199
|
const kw: Json[] = Array.isArray(trig['keywords']) ? trig['keywords'] : [];
|
|
200
|
-
const
|
|
200
|
+
const phrases: Json[] = Array.isArray(trig['phrases']) ? trig['phrases'] : [];
|
|
201
201
|
add(tokens(kw.map((x) => String(x)).join(' ')));
|
|
202
|
-
add(tokens(
|
|
202
|
+
add(tokens(phrases.map((x) => String(x)).join(' ')));
|
|
203
203
|
return out;
|
|
204
204
|
}
|
|
205
205
|
|
|
@@ -54,7 +54,7 @@ const _ALL_DIGITS = /^[0-9]+$/;
|
|
|
54
54
|
interface RuleTriggers {
|
|
55
55
|
path_prefixes: string[];
|
|
56
56
|
keywords: string[];
|
|
57
|
-
|
|
57
|
+
phrases: string[];
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
interface RuleEntry {
|
|
@@ -194,7 +194,7 @@ function analyse(rules: RuleEntry[]): Pair[] {
|
|
|
194
194
|
for (const x of keyword_set(r.triggers.keywords.join(' '))) {
|
|
195
195
|
kw.add(x);
|
|
196
196
|
}
|
|
197
|
-
for (const x of keyword_set(r.triggers.
|
|
197
|
+
for (const x of keyword_set(r.triggers.phrases.join(' '))) {
|
|
198
198
|
kw.add(x);
|
|
199
199
|
}
|
|
200
200
|
keywords.push(kw);
|