@event4u/agent-config 9.16.0 → 9.18.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 (227) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +80 -130
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/agent-handoff.md +100 -48
  6. package/dist/agent-src/commands/challenge-me/vision.md +5 -0
  7. package/dist/agent-src/commands/commit.md +1 -1
  8. package/dist/agent-src/commands/condense.md +19 -64
  9. package/dist/agent-src/commands/evals/agent-handoff.json +13 -1
  10. package/dist/agent-src/commands/evals/rule-compliance-audit.json +46 -0
  11. package/dist/agent-src/commands/feature/plan.md +20 -0
  12. package/dist/agent-src/commands/feature/roadmap.md +19 -0
  13. package/dist/agent-src/commands/memory/promote.md +14 -1
  14. package/dist/agent-src/commands/mode.md +9 -5
  15. package/dist/agent-src/commands/optimize/deep.md +72 -11
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/roadmap/create.md +29 -0
  18. package/dist/agent-src/commands/rule-compliance-audit.md +28 -5
  19. package/dist/agent-src/contexts/authority/commit-mechanics.md +5 -5
  20. package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
  21. package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
  22. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -1
  23. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +4 -1
  24. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +27 -1
  25. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +133 -0
  26. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +5 -2
  27. package/dist/agent-src/profiles/agency.yml +3 -3
  28. package/dist/agent-src/profiles/content_creator.yml +2 -2
  29. package/dist/agent-src/profiles/developer.yml +2 -2
  30. package/dist/agent-src/profiles/finance.yml +1 -1
  31. package/dist/agent-src/profiles/founder.yml +2 -2
  32. package/dist/agent-src/profiles/ops.yml +2 -2
  33. package/dist/agent-src/rules/active-remediation.md +2 -0
  34. package/dist/agent-src/rules/architecture.md +4 -2
  35. package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
  36. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  37. package/dist/agent-src/rules/brand-consistency.md +10 -62
  38. package/dist/agent-src/rules/brand-source-of-truth.md +62 -11
  39. package/dist/agent-src/rules/broken-access-control.md +4 -0
  40. package/dist/agent-src/rules/cli-output-handling.md +5 -0
  41. package/dist/agent-src/rules/code-comment-discipline.md +3 -0
  42. package/dist/agent-src/rules/commit-conventions.md +2 -0
  43. package/dist/agent-src/rules/context-hygiene.md +3 -2
  44. package/dist/agent-src/rules/cross-source-consistency.md +3 -0
  45. package/dist/agent-src/rules/design-fidelity.md +2 -0
  46. package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
  47. package/dist/agent-src/rules/docker-commands.md +4 -0
  48. package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
  49. package/dist/agent-src/rules/domain-safety-disclaimer.md +5 -2
  50. package/dist/agent-src/rules/domain-safety-pii.md +1 -1
  51. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  52. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +9 -0
  53. package/dist/agent-src/rules/image-likeness-and-rights.md +4 -0
  54. package/dist/agent-src/rules/improve-before-implement.md +4 -0
  55. package/dist/agent-src/rules/laravel-routing.md +4 -0
  56. package/dist/agent-src/rules/legal-safety-floor.md +3 -0
  57. package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
  58. package/dist/agent-src/rules/media-governance-routing.md +5 -0
  59. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  60. package/dist/agent-src/rules/no-roadmap-references.md +2 -0
  61. package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
  62. package/dist/agent-src/rules/output-discipline.md +9 -2
  63. package/dist/agent-src/rules/php-coding.md +2 -0
  64. package/dist/agent-src/rules/prefer-enums-over-literals.md +3 -0
  65. package/dist/agent-src/rules/provider-lifecycle-discipline.md +5 -0
  66. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +2 -0
  67. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  68. package/dist/agent-src/rules/rule-type-governance.md +2 -0
  69. package/dist/agent-src/rules/scale-discipline.md +3 -0
  70. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  71. package/dist/agent-src/rules/security-sensitive-stop.md +9 -2
  72. package/dist/agent-src/rules/senior-engineering-discipline.md +6 -0
  73. package/dist/agent-src/rules/skill-quality.md +2 -0
  74. package/dist/agent-src/rules/source-confidentiality.md +4 -0
  75. package/dist/agent-src/rules/source-discovery-gate.md +3 -0
  76. package/dist/agent-src/rules/source-of-truth.md +2 -0
  77. package/dist/agent-src/rules/strategy-safety-floor.md +2 -0
  78. package/dist/agent-src/rules/think-before-action.md +5 -0
  79. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  80. package/dist/agent-src/rules/ui-audit-gate.md +2 -0
  81. package/dist/agent-src/rules/user-interaction.md +3 -4
  82. package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
  83. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +4 -4
  84. package/dist/agent-src/skills/copilot-config/SKILL.md +1 -1
  85. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +7 -4
  86. package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
  87. package/dist/agent-src/skills/design-intelligence/SKILL.md +6 -180
  88. package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +100 -0
  89. package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +91 -0
  90. package/dist/agent-src/skills/design-review/SKILL.md +4 -141
  91. package/dist/agent-src/skills/design-review/references/review-communication.md +92 -0
  92. package/dist/agent-src/skills/design-review/references/verification-automation.md +61 -0
  93. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -27
  94. package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +31 -0
  95. package/dist/agent-src/skills/fe-design/SKILL.md +4 -289
  96. package/dist/agent-src/skills/fe-design/references/design-patterns.md +227 -0
  97. package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +75 -0
  98. package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
  99. package/dist/agent-src/skills/persona-writing/SKILL.md +2 -2
  100. package/dist/agent-src/skills/readme-reviewer/SKILL.md +2 -1
  101. package/dist/agent-src/skills/roadmap-management/SKILL.md +10 -0
  102. package/dist/agent-src/skills/roadmap-writing/SKILL.md +12 -1
  103. package/dist/agent-src/skills/rule-writing/SKILL.md +4 -2
  104. package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
  105. package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
  106. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +3 -1
  107. package/dist/agent-src/skills/token-optimizer/SKILL.md +2 -2
  108. package/dist/agent-src/skills/ui-component-architect/SKILL.md +6 -3
  109. package/dist/agent-src/templates/agent-settings.md +13 -15
  110. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +6 -2
  111. package/dist/agent-src/templates/persona.md +2 -2
  112. package/dist/agent-src/templates/roadmaps.md +13 -1
  113. package/dist/agent-src/templates/rule.md +1 -1
  114. package/dist/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
  115. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
  116. package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
  117. package/dist/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
  118. package/dist/agent-src/templates/skill.md +1 -1
  119. package/dist/cli/registry.js +3 -0
  120. package/dist/cli/registry.js.map +1 -1
  121. package/dist/cli-delegate/chunk-AIIUU73N.js +2040 -0
  122. package/dist/cli-delegate/{chunk-ZXXJLL5K.js → chunk-BSLUHV2F.js} +127 -1487
  123. package/dist/cli-delegate/{chunk-H574KOUH.js → chunk-ELMFOQPW.js} +35 -1
  124. package/dist/cli-delegate/chunk-LF2KKJ4Q.js +183 -0
  125. package/dist/cli-delegate/chunk-VSYT7OHO.js +1382 -0
  126. package/dist/cli-delegate/chunk-XGA2NTU4.js +3727 -0
  127. package/dist/cli-delegate/cmd_conformance.js +4 -3
  128. package/dist/cli-delegate/cmd_converge.js +3 -3
  129. package/dist/cli-delegate/cmd_doctor.js +3 -2
  130. package/dist/cli-delegate/cmd_explain.js +36 -36
  131. package/dist/cli-delegate/cmd_export.js +4 -4
  132. package/dist/cli-delegate/cmd_handoff.js +1169 -0
  133. package/dist/cli-delegate/cmd_memory_get.js +35 -3754
  134. package/dist/cli-delegate/cmd_migrate.js +2 -2
  135. package/dist/cli-delegate/cmd_prune.js +4 -4
  136. package/dist/cli-delegate/cmd_route_audit.js +249 -0
  137. package/dist/cli-delegate/cmd_route_explain.js +285 -0
  138. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  139. package/dist/cli-delegate/cmd_sync.js +4 -4
  140. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  141. package/dist/cli-delegate/cmd_update.js +3 -3
  142. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  143. package/dist/cli-delegate/cmd_validate.js +4 -4
  144. package/dist/cli-delegate/cmd_versions.js +3 -3
  145. package/dist/discovery/deprecation-report.md +1 -1
  146. package/dist/discovery/discovery-manifest.json +84 -84
  147. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  148. package/dist/discovery/discovery-manifest.summary.md +1 -1
  149. package/dist/discovery/orphan-report.md +1 -1
  150. package/dist/discovery/packs.json +2 -2
  151. package/dist/discovery/trust-report.md +1 -1
  152. package/dist/discovery/workspaces.json +2 -2
  153. package/dist/hooks/dispatch.js +602 -507
  154. package/dist/install/install.mjs +34 -0
  155. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  156. package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
  157. package/dist/mcp/registry-manifest.json +4 -4
  158. package/dist/mcp/server.json +2 -2
  159. package/dist/mcp/server.mjs +25 -3
  160. package/dist/router.json +1 -1
  161. package/dist/scripts/_lib/agent_src.js +39 -6
  162. package/dist/scripts/_lib/agent_src.js.map +1 -1
  163. package/dist/server/schemas/settings.js +25 -0
  164. package/dist/server/schemas/settings.js.map +1 -1
  165. package/docs/guidelines/abstraction-thresholds.md +53 -0
  166. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -1
  167. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +7 -0
  168. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +2 -2
  169. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +5 -3
  170. package/docs/guidelines/agent-infra/size-and-scope.md +8 -2
  171. package/docs/guidelines/component-oriented-and-oop-development.md +11 -6
  172. package/docs/guidelines/docs/readme-size-and-splitting.md +5 -2
  173. package/llms.txt +1 -0
  174. package/package.json +1 -1
  175. package/src/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
  176. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
  177. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
  178. package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
  179. package/src/config/agent-settings.template.yml +68 -0
  180. package/src/config/evaluator-budgets.json +8 -11
  181. package/src/config/gate-coverage.yml +70 -0
  182. package/src/scripts/_cli/cmd_explain.ts +41 -36
  183. package/src/scripts/_cli/cmd_handoff.ts +405 -0
  184. package/src/scripts/_cli/cmd_route_audit.ts +290 -0
  185. package/src/scripts/_cli/cmd_route_explain.ts +269 -0
  186. package/src/scripts/_cli/handoff_generate.ts +453 -0
  187. package/src/scripts/_cli/handoff_sessions.ts +435 -0
  188. package/src/scripts/_dispatch.bash +32 -0
  189. package/src/scripts/_lib/agent_src.ts +36 -6
  190. package/src/scripts/_lib/git_env.ts +40 -0
  191. package/src/scripts/_lib/md_table.ts +27 -0
  192. package/src/scripts/_lib/planning_settings.ts +43 -0
  193. package/src/scripts/_lib/release_material.ts +156 -0
  194. package/src/scripts/_lib/router_match.ts +301 -0
  195. package/src/scripts/annotate_r1_outcomes.ts +258 -0
  196. package/src/scripts/check_completion_review.ts +1140 -0
  197. package/src/scripts/check_condensation.ts +2 -1
  198. package/src/scripts/check_finding_dispositions.ts +278 -0
  199. package/src/scripts/check_pack_size.ts +4 -3
  200. package/src/scripts/check_references.ts +1 -0
  201. package/src/scripts/check_release_highlights.ts +300 -0
  202. package/src/scripts/check_release_pr_shape.ts +4 -0
  203. package/src/scripts/check_release_surface_equality.ts +132 -0
  204. package/src/scripts/check_reply_consistency.ts +187 -138
  205. package/src/scripts/compile_router.ts +20 -3
  206. package/src/scripts/dispatch_r2_reviewer.ts +968 -0
  207. package/src/scripts/eval_ui_triviality.ts +156 -0
  208. package/src/scripts/handoff_context_hook.ts +148 -0
  209. package/src/scripts/hook_manifest.yaml +17 -7
  210. package/src/scripts/hooks/concern_registry.ts +2 -0
  211. package/src/scripts/lint_abstraction_thresholds.ts +327 -0
  212. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  213. package/src/scripts/lint_plan_risk_register.ts +931 -0
  214. package/src/scripts/lint_profile_personas.ts +153 -0
  215. package/src/scripts/lint_token_budget_discipline.ts +148 -0
  216. package/src/scripts/lint_trigger_collisions.ts +257 -0
  217. package/src/scripts/release.ts +156 -32
  218. package/src/scripts/release_drill.ts +491 -0
  219. package/src/scripts/router_telemetry.ts +16 -290
  220. package/src/scripts/schemas/command.schema.json +33 -0
  221. package/src/scripts/schemas/rule.schema.json +10 -0
  222. package/src/scripts/self_review_gate.ts +41 -3
  223. package/src/scripts/skill_linter.ts +3 -1
  224. package/src/scripts/smoke/router.sh +1 -1
  225. package/src/scripts/update_counts.ts +60 -12
  226. package/src/server/schemas/settings.ts +41 -0
  227. package/dist/cli-delegate/{chunk-XX6TOPD7.js → chunk-WR6GTRHJ.js} +3 -3
@@ -17,15 +17,10 @@
17
17
  * - tier_1 + tier_2 rules: active iff any trigger matches the prompt
18
18
  * text (plus optional open-files / command context).
19
19
  *
20
- * Trigger semantics implemented:
21
- *
22
- * | Type | Match rule |
23
- * |----------------|------------------------------------------------------------------|
24
- * | `keyword` | case-insensitive substring inside the prompt text |
25
- * | `phrase` | case-insensitive substring (multi-word) inside the prompt text |
26
- * | `command` | case-sensitive prefix on `command:` field (optional context) |
27
- * | `path_prefix` | prefix match against any path in `open_files` (optional context) |
28
- * | `file_pattern` | fnmatch against any path in `open_files` (optional context) |
20
+ * Trigger semantics live in `_lib/router_match.ts` — the single matcher
21
+ * implementation (anchored `keyword`, unanchored `phrase`, `command`
22
+ * prefix, `path_prefix` / `file_pattern` over `open_files`); this module
23
+ * re-exports its names for existing importers.
29
24
  *
30
25
  * Reports go to `internal/bench/reports/router-telemetry/<UTC>.json`.
31
26
  *
@@ -36,6 +31,18 @@ import path from "node:path";
36
31
  import { fileURLToPath } from "node:url";
37
32
  import { parse as parseYaml, YAMLError } from "yaml";
38
33
 
34
+ import {
35
+ _asRuleList,
36
+ keyword_matches_anchored,
37
+ match_prompt,
38
+ trigger_matches,
39
+ } from "./_lib/router_match.js";
40
+ import type { JsonValue, Router } from "./_lib/router_match.js";
41
+
42
+ // The matcher lives in `_lib/router_match.ts` (the single implementation —
43
+ // see its header). Re-exported here so existing importers keep working.
44
+ export { keyword_matches_anchored, match_prompt, trigger_matches };
45
+
39
46
  // src/scripts/router_telemetry.ts → two levels up is the repo root
40
47
  // (mirrors the Python module's parent.parent.parent resolution from
41
48
  // src/scripts/router_telemetry.py).
@@ -58,29 +65,6 @@ const DEFAULT_SAMPLE_CAP = 200;
58
65
  // tasks are the attribution map and become the untouchable set.
59
66
  const TRACK_B_CORPUS_REL = "internal/bench/corpora/ab-trackb.yaml";
60
67
 
61
- type JsonValue =
62
- | string
63
- | number
64
- | boolean
65
- | null
66
- | JsonValue[]
67
- | { [key: string]: JsonValue };
68
-
69
- type Trigger = Record<string, JsonValue>;
70
-
71
- interface Rule {
72
- id?: JsonValue;
73
- triggers?: JsonValue;
74
- [key: string]: JsonValue;
75
- }
76
-
77
- interface Router {
78
- kernel?: JsonValue;
79
- tier_1?: JsonValue;
80
- tier_2?: JsonValue;
81
- [key: string]: JsonValue;
82
- }
83
-
84
68
  function _log(msg: string, quiet: boolean, err = false): void {
85
69
  if (err) {
86
70
  process.stderr.write(`${msg}\n`);
@@ -101,264 +85,6 @@ function _utc_iso(): string {
101
85
  return iso.slice(0, 19) + "+00:00";
102
86
  }
103
87
 
104
- // ── fnmatch (Python fnmatch.fnmatch) ─────────────────────────────────────
105
-
106
- /**
107
- * Translate a Unix shell pattern into a RegExp, matching CPython's
108
- * `fnmatch.translate`. `*` → `.*`, `?` → `.`, `[...]` char classes
109
- * preserved (with `!` → `^` negation), everything else escaped.
110
- * Matching is case-sensitive on POSIX (the corpus paths are POSIX).
111
- */
112
- function _fnmatchToRegExp(pat: string): RegExp {
113
- let res = "";
114
- let i = 0;
115
- const n = pat.length;
116
- while (i < n) {
117
- const c = pat[i] as string;
118
- i += 1;
119
- if (c === "*") {
120
- res += ".*";
121
- } else if (c === "?") {
122
- res += ".";
123
- } else if (c === "[") {
124
- let j = i;
125
- if (j < n && pat[j] === "!") {
126
- j += 1;
127
- }
128
- if (j < n && pat[j] === "]") {
129
- j += 1;
130
- }
131
- while (j < n && pat[j] !== "]") {
132
- j += 1;
133
- }
134
- if (j >= n) {
135
- res += "\\[";
136
- } else {
137
- let stuff = pat.slice(i, j);
138
- if (!stuff.includes("-")) {
139
- stuff = stuff.replace(/\\/g, "\\\\");
140
- } else {
141
- // Faithful port of CPython's dash-handling in fnmatch.translate:
142
- // split the class body on `-` (bounded to [i, j)), escaping each
143
- // chunk, then re-join with `-` so ranges survive.
144
- const chunks: string[] = [];
145
- let k = pat[i] === "!" ? i + 2 : i + 1;
146
- let start = i;
147
- while (true) {
148
- const rel = pat.slice(k, j).indexOf("-");
149
- if (rel < 0) {
150
- break;
151
- }
152
- const idx = k + rel;
153
- chunks.push(pat.slice(start, idx));
154
- start = idx + 1;
155
- k = idx + 3;
156
- }
157
- chunks.push(pat.slice(start, j));
158
- stuff = chunks
159
- .map((s) => s.replace(/\\/g, "\\\\").replace(/-/g, "\\-"))
160
- .join("-");
161
- }
162
- // Escape regex set-meta chars except backslash/dash already handled.
163
- stuff = stuff.replace(/([&~|])/g, "\\$1");
164
- i = j + 1;
165
- if (stuff.startsWith("!")) {
166
- stuff = "^" + stuff.slice(1);
167
- } else if (stuff.startsWith("^") || stuff.startsWith("[")) {
168
- stuff = "\\" + stuff;
169
- }
170
- res += `[${stuff}]`;
171
- }
172
- } else {
173
- res += c.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
174
- }
175
- }
176
- return new RegExp(`^(?:${res})$`, "s");
177
- }
178
-
179
- function _fnmatch(name: string, pat: string): boolean {
180
- return _fnmatchToRegExp(pat).test(name);
181
- }
182
-
183
- // ── Trigger matching ─────────────────────────────────────────────────────
184
-
185
- /** Apply one trigger to a prompt + context; return True on match. */
186
- /** Unicode word character — letters, digits, underscore (incl. umlauts). */
187
- const _WORD_CHAR = /[\p{L}\p{N}_]/u;
188
-
189
- function _isWordChar(c: string | undefined): boolean {
190
- return c !== undefined && _WORD_CHAR.test(c);
191
- }
192
-
193
- /**
194
- * Word-boundary-anchored keyword match (road-to-tested-routing Phase 3).
195
- *
196
- * A `keyword` occurrence counts only when its word-character edges sit on
197
- * word boundaries: `ac` no longer fires inside "black", `plan` no longer
198
- * fires inside "planet". Two deliberate reliefs:
199
- *
200
- * - An edge that is itself a NON-word character (e.g. `__()`, `/image:`,
201
- * `trans(`, an emoji) carries no boundary requirement on that side —
202
- * punctuation-shaped keywords keep their exact substring semantics.
203
- * - The right boundary accepts one optional plural `s` ("icons", "options",
204
- * "secrets" still fire their singular keyword). Richer inflection (German
205
- * verb endings: "implementiere", "committen") is a DOCUMENTED accepted
206
- * recall cost of anchoring — the matrices carry standalone-token phrasings
207
- * for those languages instead.
208
- *
209
- * `phrase` deliberately stays unanchored substring; since this change the
210
- * two trigger kinds genuinely differ.
211
- */
212
- export function keyword_matches_anchored(prompt_lower: string, kw_lower: string): boolean {
213
- if (kw_lower === "") {
214
- return false;
215
- }
216
- const needLeft = _isWordChar(kw_lower[0]);
217
- const needRight = _isWordChar(kw_lower[kw_lower.length - 1]);
218
- let idx = prompt_lower.indexOf(kw_lower);
219
- while (idx !== -1) {
220
- const leftOk = !needLeft || !_isWordChar(prompt_lower[idx - 1]);
221
- let rightOk = true;
222
- if (needRight) {
223
- let end = idx + kw_lower.length;
224
- if (prompt_lower[end] === "s") {
225
- end += 1; // optional plural `s`
226
- }
227
- rightOk = !_isWordChar(prompt_lower[end]);
228
- }
229
- if (leftOk && rightOk) {
230
- return true;
231
- }
232
- idx = prompt_lower.indexOf(kw_lower, idx + 1);
233
- }
234
- return false;
235
- }
236
-
237
- export function trigger_matches(
238
- trigger: Trigger,
239
- prompt: string,
240
- open_files?: Iterable<string> | null,
241
- command?: string | null,
242
- ): boolean {
243
- const prompt_lower = prompt.toLowerCase();
244
- if ("keyword" in trigger) {
245
- return keyword_matches_anchored(prompt_lower, String(trigger["keyword"]).toLowerCase());
246
- }
247
- if ("phrase" in trigger) {
248
- return prompt_lower.includes(String(trigger["phrase"]).toLowerCase());
249
- }
250
- if ("command" in trigger) {
251
- if (!command) {
252
- return false;
253
- }
254
- return command.startsWith(String(trigger["command"]));
255
- }
256
- if ("path_prefix" in trigger) {
257
- if (!open_files) {
258
- return false;
259
- }
260
- const pref = String(trigger["path_prefix"]);
261
- for (const p of open_files) {
262
- if (String(p).startsWith(pref)) {
263
- return true;
264
- }
265
- }
266
- return false;
267
- }
268
- if ("file_pattern" in trigger) {
269
- if (!open_files) {
270
- return false;
271
- }
272
- const pat = String(trigger["file_pattern"]);
273
- for (const p of open_files) {
274
- if (_fnmatch(String(p), pat)) {
275
- return true;
276
- }
277
- }
278
- return false;
279
- }
280
- return false;
281
- }
282
-
283
- interface MatchedTrigger {
284
- tier: string;
285
- rule: JsonValue | undefined;
286
- trigger: Trigger;
287
- }
288
-
289
- interface ActivatedRule {
290
- tier: string;
291
- rule: JsonValue | undefined;
292
- }
293
-
294
- interface MatchResult {
295
- matched_triggers: MatchedTrigger[];
296
- activated_rules: ActivatedRule[];
297
- }
298
-
299
- function _asRuleList(value: JsonValue | undefined): Rule[] {
300
- if (!Array.isArray(value)) {
301
- return [];
302
- }
303
- return value as unknown as Rule[];
304
- }
305
-
306
- function _asTriggerList(value: JsonValue | undefined): Trigger[] {
307
- if (!Array.isArray(value)) {
308
- return [];
309
- }
310
- return value as unknown as Trigger[];
311
- }
312
-
313
- /**
314
- * Return the matched-triggers + activated-rules for one prompt.
315
- *
316
- * Kernel rules are always active. tier_1 always considered. tier_2
317
- * only considered when `profile == 'full'`.
318
- */
319
- export function match_prompt(
320
- router: Router,
321
- prompt: string,
322
- profile = "full",
323
- open_files?: Iterable<string> | null,
324
- command?: string | null,
325
- ): MatchResult {
326
- const tiers: Array<[string, Rule[]]> = [
327
- ["tier_1", _asRuleList(router["tier_1"])],
328
- ];
329
- if (profile === "full") {
330
- tiers.push(["tier_2", _asRuleList(router["tier_2"])]);
331
- }
332
-
333
- const matched_triggers: MatchedTrigger[] = [];
334
- const activated_rules: ActivatedRule[] = [];
335
-
336
- for (const [tier_name, rules] of tiers) {
337
- for (const rule of rules) {
338
- const rule_id = rule["id"];
339
- const rule_triggers = _asTriggerList(rule["triggers"]);
340
- let rule_hit = false;
341
- for (const trig of rule_triggers) {
342
- if (trigger_matches(trig, prompt, open_files, command)) {
343
- matched_triggers.push({ tier: tier_name, rule: rule_id, trigger: trig });
344
- rule_hit = true;
345
- }
346
- }
347
- if (rule_hit) {
348
- activated_rules.push({ tier: tier_name, rule: rule_id });
349
- }
350
- }
351
- }
352
-
353
- // Kernel rules are always active.
354
- const kernel = Array.isArray(router["kernel"]) ? router["kernel"] : [];
355
- for (const kid of kernel) {
356
- activated_rules.push({ tier: "kernel", rule: kid });
357
- }
358
-
359
- return { matched_triggers, activated_rules };
360
- }
361
-
362
88
  // ── JSON serialization (Python-faithful) ─────────────────────────────────
363
89
 
364
90
  /**
@@ -128,6 +128,39 @@
128
128
  "enum": ["deep"],
129
129
  "description": "Optional reasoning-depth marker for AI Council invocations triggered by this command. The only accepted value is 'deep'; omit the key for default depth (setting 'standard' is rejected — every frontmatter byte counts against the context window, and 'standard' is the implicit default). 'deep' instructs the host agent to pass --depth deep to council_cli, which floors rounds at max(ai_council.deep_min_rounds, ai_council.min_rounds). Use for architecture, refactoring, or bug-diagnosis commands. See .agent-src.uncondensed/skills/ai-council/SKILL.md."
130
130
  },
131
+ "limits": {
132
+ "type": "object",
133
+ "additionalProperties": false,
134
+ "required": ["mode_default", "max_iterations", "hard_ceiling", "no_gain_stop", "target_metric"],
135
+ "description": "Machine-readable execution limits for autonomous-loop commands (release-truth Phase 4). The command body MUST restate each limit in its flow steps — frontmatter is the pin the deterministic test reads, the body is what the executing agent obeys.",
136
+ "properties": {
137
+ "mode_default": {
138
+ "type": "string",
139
+ "enum": ["plan", "execute"],
140
+ "description": "Default execution mode when the invocation names none. 'plan' = author and present, no push / PR / refinement loop."
141
+ },
142
+ "max_iterations": {
143
+ "type": "integer",
144
+ "minimum": 1,
145
+ "description": "Default refinement-loop budget."
146
+ },
147
+ "hard_ceiling": {
148
+ "type": "integer",
149
+ "minimum": 1,
150
+ "description": "Absolute loop ceiling — no flag may exceed it."
151
+ },
152
+ "no_gain_stop": {
153
+ "type": "integer",
154
+ "minimum": 1,
155
+ "description": "Consecutive loops without measurable gain on the pre-registered target metric that force a stop."
156
+ },
157
+ "target_metric": {
158
+ "type": "string",
159
+ "enum": ["required"],
160
+ "description": "A named target metric with a measured baseline must be recorded before loop 1."
161
+ }
162
+ }
163
+ },
131
164
  "model_tier": {
132
165
  "type": "string",
133
166
  "enum": ["lite", "medium", "high", "inherit"],
@@ -155,6 +155,16 @@
155
155
  },
156
156
  "description": "ADR-013 trust block. Vocabulary mirrored by scripts/lint_artefact_frontmatter.py."
157
157
  },
158
+ "collision_ok": {
159
+ "type": "object",
160
+ "additionalProperties": {"type": "string", "minLength": 8},
161
+ "description": "Per-trigger collision disposition: maps a trigger VALUE (case-insensitive) shared with >=1 other rule to a one-line reason why this rule also firing on it is correct. Enforced by scripts/lint_trigger_collisions.ts."
162
+ },
163
+ "precedence": {
164
+ "type": "object",
165
+ "additionalProperties": {"type": "integer"},
166
+ "description": "Alternative collision disposition: maps a shared trigger VALUE to an integer ordering; a collision is clean when ALL sharers declare pairwise-distinct integers for it. Enforced by scripts/lint_trigger_collisions.ts."
167
+ },
158
168
  "install": {
159
169
  "type": "object",
160
170
  "additionalProperties": false,
@@ -29,7 +29,8 @@
29
29
  * the `check_quality_regression.gateVerdict` pattern.
30
30
  */
31
31
  import { spawnSync } from 'node:child_process';
32
- import { appendFileSync, existsSync, readFileSync } from 'node:fs';
32
+ import { createHash } from 'node:crypto';
33
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
33
34
  import path from 'node:path';
34
35
  import { fileURLToPath } from 'node:url';
35
36
 
@@ -50,6 +51,20 @@ export interface Finding {
50
51
  file?: string;
51
52
  }
52
53
 
54
+ /**
55
+ * Stable identifier for a finding — sha256 of kind|title|file, 12 hex chars
56
+ * (release-truth Phase 3). The id keys the disposition ledger
57
+ * (`agents/evidence/release-findings/<version>.json`): the 9.14.0 symlink
58
+ * finding merged with no traceable disposition precisely because findings
59
+ * carried no identity that outlived the PR comment.
60
+ */
61
+ export function findingId(f: Pick<Finding, 'kind' | 'title' | 'file'>): string {
62
+ return createHash('sha256')
63
+ .update(`${f.kind}|${f.title}|${f.file ?? ''}`)
64
+ .digest('hex')
65
+ .slice(0, 12);
66
+ }
67
+
53
68
  /**
54
69
  * A finding is merge-blocking iff it is security- or claim-affecting AND at
55
70
  * least `high` severity. Style and correctness findings advise; low/medium
@@ -332,7 +347,7 @@ export function renderReview(findings: Finding[], enforce: boolean, escalation:
332
347
  const rows = findings
333
348
  .map((f) => {
334
349
  const marker = classifyBlocking(f) ? 'Blocking' : 'Advisory';
335
- return `| ${f.severity} (${marker}) | ${f.kind} | ${f.file ?? '—'} | ${f.title} |`;
350
+ return `| ${findingId(f)} | ${f.severity} (${marker}) | ${f.kind} | ${f.file ?? '—'} | ${f.title} |`;
336
351
  })
337
352
  .join('\n');
338
353
  const verdictLine = blocking.length
@@ -340,7 +355,14 @@ export function renderReview(findings: Finding[], enforce: boolean, escalation:
340
355
  ? `❌ ${blocking.length} merge-blocking finding(s) (security/claim × high+).`
341
356
  : `⚠️ ${blocking.length} finding(s) WOULD block merge under an enforced gate (advisory now).`
342
357
  : '✅ No merge-blocking findings (style/correctness advise only).';
343
- return `${banner}\n\n${verdictLine}\n\n| severity | kind | file | finding |\n|---|---|---|---|\n${rows}${esc}`;
358
+ // Machine-readable block (release-truth Phase 3): invisible in the rendered
359
+ // comment; `check_finding_dispositions --pr` reads it as the TRIGGER that
360
+ // demands committed dispositions. The comment is transport, never the
361
+ // record — the record is `agents/evidence/release-findings/<version>.json`.
362
+ const machine = `\n\n<!-- release-findings-json: ${JSON.stringify(
363
+ findings.map((f) => ({ finding_id: findingId(f), severity: f.severity, kind: f.kind, title: f.title, file: f.file ?? null })),
364
+ )} -->`;
365
+ return `${banner}\n\n${verdictLine}\n\n| id | severity | kind | file | finding |\n|---|---|---|---|---|\n${rows}${esc}${machine}`;
344
366
  }
345
367
 
346
368
  function postReview(body: string): void {
@@ -432,6 +454,22 @@ export function main(argv: string[]): 0 | 2 {
432
454
  return 0; // no credit / transport / API error → never blocks the merge
433
455
  }
434
456
  const findings = parseFindings(resp.text);
457
+ const outIdx = argv.indexOf('--findings-out');
458
+ if (outIdx >= 0 && argv[outIdx + 1]) {
459
+ // Durable ingestion input for the disposition ledger (release-truth
460
+ // Phase 3): `check_finding_dispositions --ingest <this file>`.
461
+ writeFileSync(
462
+ argv[outIdx + 1]!,
463
+ JSON.stringify(
464
+ {
465
+ schema_version: 1,
466
+ findings: findings.map((f) => ({ finding_id: findingId(f), ...f })),
467
+ },
468
+ null,
469
+ 2,
470
+ ) + '\n',
471
+ );
472
+ }
435
473
  const body = renderReview(findings, enforce, plan.escalation);
436
474
  postReview(body);
437
475
 
@@ -2554,7 +2554,9 @@ function lint_interaction_quality(p: string, text: string): Issue[] {
2554
2554
  ].some((kw) => textLower.includes(kw));
2555
2555
 
2556
2556
  if (hasQuestionContext) {
2557
- const hasSimple = ['simple', 'binary', 'independent'].some((kw) => textLower.includes(kw));
2557
+ // 'decision point' is the ADR-214 cadence boundary ("one decision
2558
+ // point = one question") that replaced the simple-vs-complex split.
2559
+ const hasSimple = ['simple', 'binary', 'independent', 'decision point'].some((kw) => textLower.includes(kw));
2558
2560
  const hasComplex = ['complex', 'one at a time', 'one question'].some((kw) => textLower.includes(kw));
2559
2561
  if (!(hasSimple && hasComplex)) {
2560
2562
  issues.push(
@@ -15,7 +15,7 @@
15
15
 
16
16
  set -euo pipefail
17
17
 
18
- EXPECTED_TOTAL_IDS=68
18
+ EXPECTED_TOTAL_IDS=106
19
19
  EXPECTED_MISSING_CONTRACTS=2
20
20
 
21
21
  quiet="${SMOKE_QUIET:-0}"
@@ -34,25 +34,73 @@ const _HERE = fileURLToPath(import.meta.url);
34
34
  // REPO_ROOT = <file>.parent.parent.parent — two dirs up from src/scripts.
35
35
  export const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
36
36
 
37
- /** Recursively walk a directory, yielding files whose basename matches. */
37
+ /**
38
+ * Recursively walk a directory, yielding files whose basename matches.
39
+ *
40
+ * Symlink confinement (release-truth Phase 3): a symlink — leaf or directory —
41
+ * is honored only when its target resolves inside the walk root; external
42
+ * targets, broken links, and cycles are ignored. Directory symlinks are
43
+ * followed through a visited-realpath set so a self-referential link
44
+ * terminates instead of recursing to stack exhaustion.
45
+ */
38
46
  function* _rglob(dir: string, match: (name: string) => boolean): Generator<string> {
39
- let entries: fs.Dirent[];
47
+ let rootReal: string;
40
48
  try {
41
- entries = fs.readdirSync(dir, { withFileTypes: true });
49
+ rootReal = fs.realpathSync(dir);
42
50
  } catch {
43
51
  return;
44
52
  }
45
- // Sort for determinism (Path.rglob order is filesystem-dependent in
46
- // Python; we only count, so order does not affect the result).
47
- entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
48
- for (const e of entries) {
49
- const full = path.join(dir, e.name);
50
- if (e.isDirectory()) {
51
- yield* _rglob(full, match);
52
- } else if (match(e.name)) {
53
- yield full;
53
+ const confined = (p: string): string | null => {
54
+ try {
55
+ const real = fs.realpathSync(p);
56
+ const rel = path.relative(rootReal, real);
57
+ const inside = real === rootReal || (rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel));
58
+ return inside ? real : null;
59
+ } catch {
60
+ return null; // broken symlink → explicitly not yielded
61
+ }
62
+ };
63
+ const visited = new Set<string>([rootReal]);
64
+ function* walk(current: string): Generator<string> {
65
+ let entries: fs.Dirent[];
66
+ try {
67
+ entries = fs.readdirSync(current, { withFileTypes: true });
68
+ } catch {
69
+ return;
70
+ }
71
+ // Sort for determinism (Path.rglob order is filesystem-dependent in
72
+ // Python; we only count, so order does not affect the result).
73
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
74
+ for (const e of entries) {
75
+ const full = path.join(current, e.name);
76
+ if (e.isDirectory()) {
77
+ yield* walk(full);
78
+ } else if (e.isSymbolicLink()) {
79
+ const real = confined(full);
80
+ if (real === null) {
81
+ continue;
82
+ }
83
+ let st: fs.Stats;
84
+ try {
85
+ st = fs.statSync(full);
86
+ } catch {
87
+ continue;
88
+ }
89
+ if (st.isDirectory()) {
90
+ if (visited.has(real)) {
91
+ continue;
92
+ }
93
+ visited.add(real);
94
+ yield* walk(full);
95
+ } else if (st.isFile() && match(e.name)) {
96
+ yield full;
97
+ }
98
+ } else if (e.isFile() && match(e.name)) {
99
+ yield full;
100
+ }
54
101
  }
55
102
  }
103
+ yield* walk(dir);
56
104
  }
57
105
 
58
106
  /** Yield immediate child files of dir matching `match`. */
@@ -205,6 +205,24 @@ export const settingsSchema = z.object({
205
205
  'Optional planning horizon (weeks) the agent shows in roadmap framing ("next 4 weeks"). Set 0 to omit the horizon — most teams prefer to ship without a hardcoded window.',
206
206
  ),
207
207
  }),
208
+ planning: z.object({
209
+ challenge_on_create: z.boolean().default(true).describe(
210
+ 'Gate C — plan-confidence gate before authoring. true (default) = a plan-artifact ask (/roadmap:create, roadmap-writing, /feature:plan, /feature:roadmap) first checks the four 95%-confidence conditions from /challenge-me vision; any gap routes into the interview (or the inline degrade protocol) before authoring, and a confident pass emits exactly one marker line. false = inert, plan asks author directly. An explicit user bypass always wins for that turn and is counted.',
211
+ ),
212
+ risk_review: z.boolean().default(true).describe(
213
+ 'Gate R1 — plan-risk review. true (default) = every ready (non-draft) plan must carry a schema-valid "## Risk Register" section (ranked risks, mitigation + anchor per row, freshness marker, exact honest-null grammar), enforced by lint_plan_risk_register at pre-push + CI. false = escape hatch, the validator skips.',
214
+ ),
215
+ completion_review: z.boolean().default(true).describe(
216
+ 'Gate R2 — completion review at 100% roadmap completion / pre-PR. true (default) = a findings-before-fixes review by a fresh reviewer context must exist for the current diff hash (or an exact honest-null / skip declaration) before fix commits and PR creation, enforced by check_completion_review at pre-push + CI (CI authoritative; a crashed validator warns and allows). false = escape hatch, the validator skips.',
217
+ ),
218
+ })
219
+ // `.default({})` is load-bearing, not cosmetic: without it the SECTION is
220
+ // required, so any settings payload that omits `planning` fails with
221
+ // `ZodError: planning Required` — which contradicts this gate family's own
222
+ // contract ("missing key = true" for all three) and reds every server
223
+ // test whose fixture predates the section. With the default, an absent
224
+ // section materialises as the three `true` leaves the contract promises.
225
+ .default({}),
208
226
  quality: z.object({
209
227
  local_auto_run: z.boolean().default(false).describe(
210
228
  'Run quality tools (linters, type-checks, formatters) and the local test suite autonomously after edits. Off by default — the agent never runs quality tools proactively and does not ask; the user runs them manually (e.g. /quality-fix) and remote CI is the authoritative gate. The agent only runs a quality tool on an explicit ask, a concrete CI failure, or the new-gate carve-out. Turn on to restore autonomous pipeline runs.',
@@ -247,6 +265,29 @@ export const settingsSchema = z.object({
247
265
  'Whether skills marked token_budget_class: rich may load in full (exempt from telegraph-speak + thin-projector trimming), consumed by the token-budget-discipline rule. on = allowed (default); off = fall back to standard condensed behavior; ask = surface an estimated token delta (tokens, not dollars) and ask once per session before loading.',
248
266
  ),
249
267
  }).default({ rich_skills: 'on' }),
268
+ verbosity: z.object({
269
+ intent_announcements: z.boolean().default(false).describe(
270
+ 'Intent narration before tool batches ("Let me check X…"). Only honored when personal.play_by_play is ALSO true (the direct-answers narration carve-out requires both). false (default) = act and emit the result.',
271
+ ),
272
+ preview_artifacts: z.boolean().default(false).describe(
273
+ 'Show generated commit messages, PR titles/bodies, branch names before acting. false (default) = use generated content directly (/commit terse path).',
274
+ ),
275
+ routine_confirmations: z.boolean().default(false).describe(
276
+ 'Confirmation prompts for routine workflow steps with one obvious answer. Iron-Law gates (commit-policy, scope-control git-ops, Hard Floor) ALWAYS ask regardless.',
277
+ ),
278
+ offer_council_in_delivery: z.boolean().default(false).describe(
279
+ 'Offer "run AI Council on this?" inside delivery commands (/feature-plan, /review-changes, /roadmap-create). Council commands themselves are unaffected.',
280
+ ),
281
+ post_action_reports: z.enum(['off', 'minimal', 'full']).default('minimal').describe(
282
+ 'Status blocks after a successful action. off = errors only; minimal (default) = one-line confirmation; full = bullet list.',
283
+ ),
284
+ }).default({
285
+ intent_announcements: false,
286
+ preview_artifacts: false,
287
+ routine_confirmations: false,
288
+ offer_council_in_delivery: false,
289
+ post_action_reports: 'minimal',
290
+ }),
250
291
  code_style: z.object({
251
292
  docblocks: z.enum(['minimal', 'full']).default('minimal').describe(
252
293
  '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 = the exported public surface of a library package may carry one-line summary docblocks; the redundancy ban still holds.',
@@ -1,10 +1,10 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
- import {
3
- resolve_project_root
4
- } from "./chunk-HJ6DAJHR.js";
5
2
  import {
6
3
  resolvePackageRoot
7
4
  } from "./chunk-MCJYCQPF.js";
5
+ import {
6
+ resolve_project_root
7
+ } from "./chunk-HJ6DAJHR.js";
8
8
 
9
9
  // src/scripts/_cli/cmd_migrate.ts
10
10
  import { spawnSync } from "node:child_process";