@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.
Files changed (130) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +80 -256
  3. package/README.md +1 -1
  4. package/dist/agent-src/rules/active-remediation.md +0 -2
  5. package/dist/agent-src/rules/architecture.md +0 -1
  6. package/dist/agent-src/rules/artifact-drafting-protocol.md +0 -4
  7. package/dist/agent-src/rules/autonomous-execution.md +1 -2
  8. package/dist/agent-src/rules/broken-access-control.md +0 -2
  9. package/dist/agent-src/rules/code-comment-discipline.md +0 -3
  10. package/dist/agent-src/rules/code-provenance.md +0 -3
  11. package/dist/agent-src/rules/communication-through-line.md +2 -3
  12. package/dist/agent-src/rules/content-quoting-floor.md +0 -2
  13. package/dist/agent-src/rules/context-hygiene.md +1 -3
  14. package/dist/agent-src/rules/decision-revisit-gate.md +0 -2
  15. package/dist/agent-src/rules/design-fidelity.md +29 -0
  16. package/dist/agent-src/rules/domain-adoption-policy.md +0 -3
  17. package/dist/agent-src/rules/downstream-changes.md +10 -1
  18. package/dist/agent-src/rules/external-reference-deep-dive.md +0 -7
  19. package/dist/agent-src/rules/fast-path-marker-visibility.md +0 -1
  20. package/dist/agent-src/rules/git-history-discipline.md +0 -7
  21. package/dist/agent-src/rules/guidelines.md +0 -2
  22. package/dist/agent-src/rules/improve-before-implement.md +4 -2
  23. package/dist/agent-src/rules/invite-challenge.md +0 -4
  24. package/dist/agent-src/rules/linked-projects-onboarding-gate.md +0 -2
  25. package/dist/agent-src/rules/markdown-safe-codeblocks.md +0 -1
  26. package/dist/agent-src/rules/minimal-safe-diff.md +2 -2
  27. package/dist/agent-src/rules/missing-tool-handling.md +0 -1
  28. package/dist/agent-src/rules/no-attribution-footers.md +0 -3
  29. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +8 -5
  30. package/dist/agent-src/rules/no-pr-progress-comments.md +1 -3
  31. package/dist/agent-src/rules/no-roadmap-references.md +0 -2
  32. package/dist/agent-src/rules/output-discipline.md +0 -1
  33. package/dist/agent-src/rules/prefer-enums-over-literals.md +0 -2
  34. package/dist/agent-src/rules/preservation-guard.md +0 -3
  35. package/dist/agent-src/rules/role-mode-adherence.md +0 -1
  36. package/dist/agent-src/rules/secret-vcs-guard.md +0 -2
  37. package/dist/agent-src/rules/senior-engineering-discipline.md +0 -2
  38. package/dist/agent-src/rules/session-canary.md +0 -2
  39. package/dist/agent-src/rules/size-enforcement.md +0 -5
  40. package/dist/agent-src/rules/source-confidentiality.md +0 -3
  41. package/dist/agent-src/rules/source-discovery-gate.md +0 -2
  42. package/dist/agent-src/rules/think-before-action.md +3 -3
  43. package/dist/agent-src/rules/token-budget-discipline.md +0 -1
  44. package/dist/agent-src/rules/token-efficiency.md +1 -2
  45. package/dist/agent-src/rules/tool-safety.md +0 -1
  46. package/dist/agent-src/rules/user-interaction.md +0 -3
  47. package/dist/agent-src/rules/user-interrupt-priority.md +0 -1
  48. package/dist/agent-src/skills/agent-docs-writing/SKILL.md +8 -0
  49. package/dist/agent-src/skills/analysis-skill-router/SKILL.md +55 -86
  50. package/dist/agent-src/skills/character-consistency/SKILL.md +7 -4
  51. package/dist/agent-src/skills/decision-record/SKILL.md +8 -0
  52. package/dist/agent-src/skills/design-review/SKILL.md +11 -0
  53. package/dist/agent-src/skills/design-system-capture/reference/design-system-json.md +8 -0
  54. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +34 -3
  55. package/dist/agent-src/skills/fe-design/SKILL.md +10 -0
  56. package/dist/agent-src/skills/motion-choreographer/SKILL.md +7 -4
  57. package/dist/agent-src/skills/overbuild-review-lens/SKILL.md +201 -0
  58. package/dist/agent-src/skills/performance/SKILL.md +14 -0
  59. package/dist/agent-src/skills/pixar-storyteller/SKILL.md +8 -5
  60. package/dist/agent-src/skills/scene-expander/SKILL.md +9 -6
  61. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +10 -0
  62. package/dist/agent-src/skills/universal-project-analysis/SKILL.md +14 -37
  63. package/dist/agent-src/skills/video-director/SKILL.md +9 -6
  64. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  65. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +154 -13
  66. package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +118 -0
  67. package/dist/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +40 -0
  68. package/dist/agent-src/templates/scripts/work_engine/state.ts +55 -0
  69. package/dist/discovery/deprecation-report.md +1 -1
  70. package/dist/discovery/discovery-manifest.json +89 -67
  71. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  72. package/dist/discovery/discovery-manifest.summary.md +3 -3
  73. package/dist/discovery/orphan-report.md +1 -1
  74. package/dist/discovery/packs.json +6 -5
  75. package/dist/discovery/trust-report.md +2 -2
  76. package/dist/discovery/workspaces.json +5 -4
  77. package/dist/hooks/dispatch.js +174 -13
  78. package/dist/mcp/registry-manifest.json +3 -3
  79. package/dist/mcp/server.json +2 -2
  80. package/dist/mcp/server.mjs +750 -507
  81. package/dist/router.json +1 -1
  82. package/dist/scripts/condense.js +2 -2
  83. package/docs/MIGRATION.md +1 -1
  84. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +71 -0
  85. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +24 -1
  86. package/docs/guidelines/code-clarity.md +9 -2
  87. package/docs/guidelines/design-fidelity-mechanics.md +44 -0
  88. package/llms.txt +63 -27
  89. package/package.json +4 -4
  90. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +154 -13
  91. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +118 -0
  92. package/src/agent-src/templates/scripts/work_engine/directives/ui/polish.ts +40 -0
  93. package/src/agent-src/templates/scripts/work_engine/state.ts +55 -0
  94. package/src/config/agent-settings.template.yml +8 -0
  95. package/src/scripts/_lib/counted_probe.ts +142 -0
  96. package/src/scripts/_lib/overbuild_lens_contract.ts +196 -0
  97. package/src/scripts/_lib/release_scope.ts +207 -0
  98. package/src/scripts/ai_council/stance_tally.ts +37 -2
  99. package/src/scripts/audit_auto_rules.ts +9 -9
  100. package/src/scripts/audit_likelihood.ts +3 -3
  101. package/src/scripts/audit_overlap.ts +2 -2
  102. package/src/scripts/audit_skill_overlap.ts +233 -95
  103. package/src/scripts/audit_skill_overlap_allowlist.json +4 -0
  104. package/src/scripts/build_proof.ts +15 -1
  105. package/src/scripts/check_dependency_floors.ts +16 -6
  106. package/src/scripts/check_gate_coverage.ts +6 -6
  107. package/src/scripts/check_kernel_rule_bundle.ts +5 -4
  108. package/src/scripts/check_token_quality_golden.ts +0 -18
  109. package/src/scripts/code_graph/loader.ts +35 -6
  110. package/src/scripts/compile_router.ts +3 -8
  111. package/src/scripts/condense.ts +2 -2
  112. package/src/scripts/explain_run.ts +0 -1
  113. package/src/scripts/generate_pack_manifests.ts +3 -20
  114. package/src/scripts/hooks/block_kernel_rule_writes.ts +145 -10
  115. package/src/scripts/hooks/block_no_verify.ts +101 -0
  116. package/src/scripts/lint_agent_security.ts +5 -4
  117. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  118. package/src/scripts/lint_regression.ts +92 -1
  119. package/src/scripts/lint_skill_descriptions.ts +93 -5
  120. package/src/scripts/project_thin_rules.ts +2 -2
  121. package/src/scripts/release.ts +107 -3
  122. package/src/scripts/resolve_lint_scope.ts +53 -0
  123. package/src/scripts/router_telemetry.ts +0 -5
  124. package/src/scripts/runtime_dispatcher.ts +40 -0
  125. package/src/scripts/schemas/pack.schema.json +1 -5
  126. package/src/scripts/schemas/rule.schema.json +1 -2
  127. package/src/scripts/self_review_gate.ts +15 -73
  128. package/src/scripts/skill_linter.ts +93 -11
  129. package/src/scripts/sweep_dead_scan_roots.ts +550 -0
  130. 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
- needs_repair: string[]; // members whose stance line was missing/unparseable
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
- const w_total = parsed.length; // base weight 1.0 each, abstain included
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
- intents: string[];
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 intents: string[] = [];
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 ('intent' in e) {
160
- intents.push(String(e['intent']));
159
+ if ('phrase' in e) {
160
+ phrases.push(String(e['phrase']));
161
161
  }
162
162
  }
163
- return { path_prefixes: paths, keywords, intents };
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.intents.length,
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.intents.length}i`;
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, `Ni` = intent.');
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
- intents: r.triggers.intents,
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[].intent`, and the rule name itself.
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 intents: Json[] = Array.isArray(trig['intents']) ? trig['intents'] : [];
200
+ const phrases: Json[] = Array.isArray(trig['phrases']) ? trig['phrases'] : [];
201
201
  add(tokens(kw.map((x) => String(x)).join(' ')));
202
- add(tokens(intents.map((x) => String(x)).join(' ')));
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
- intents: string[];
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.intents.join(' '))) {
197
+ for (const x of keyword_set(r.triggers.phrases.join(' '))) {
198
198
  kw.add(x);
199
199
  }
200
200
  keywords.push(kw);