@event4u/agent-config 1.13.0 → 1.15.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 (291) hide show
  1. package/.agent-src/commands/agent-handoff.md +4 -1
  2. package/.agent-src/commands/agent-status.md +3 -0
  3. package/.agent-src/commands/agents-audit.md +4 -0
  4. package/.agent-src/commands/agents-cleanup.md +6 -1
  5. package/.agent-src/commands/agents-prepare.md +3 -0
  6. package/.agent-src/commands/analyze-reference-repo.md +4 -0
  7. package/.agent-src/commands/bug-fix.md +7 -3
  8. package/.agent-src/commands/bug-investigate.md +4 -0
  9. package/.agent-src/commands/chat-history-checkpoint.md +126 -0
  10. package/.agent-src/commands/chat-history-clear.md +6 -1
  11. package/.agent-src/commands/chat-history-resume.md +7 -2
  12. package/.agent-src/commands/chat-history.md +7 -2
  13. package/.agent-src/commands/check-current-md.md +137 -0
  14. package/.agent-src/commands/commit-in-chunks.md +118 -0
  15. package/.agent-src/commands/commit.md +4 -0
  16. package/.agent-src/commands/compress.md +37 -2
  17. package/.agent-src/commands/context-create.md +4 -0
  18. package/.agent-src/commands/context-refactor.md +4 -0
  19. package/.agent-src/commands/copilot-agents-init.md +3 -0
  20. package/.agent-src/commands/copilot-agents-optimize.md +3 -0
  21. package/.agent-src/commands/create-pr-description.md +4 -0
  22. package/.agent-src/commands/create-pr.md +4 -0
  23. package/.agent-src/commands/do-and-judge.md +4 -1
  24. package/.agent-src/commands/do-in-steps.md +3 -0
  25. package/.agent-src/commands/e2e-heal.md +4 -0
  26. package/.agent-src/commands/e2e-plan.md +4 -0
  27. package/.agent-src/commands/estimate-ticket.md +4 -1
  28. package/.agent-src/commands/feature-dev.md +4 -0
  29. package/.agent-src/commands/feature-explore.md +4 -0
  30. package/.agent-src/commands/feature-plan.md +4 -0
  31. package/.agent-src/commands/feature-refactor.md +4 -0
  32. package/.agent-src/commands/feature-roadmap.md +6 -0
  33. package/.agent-src/commands/fix-ci.md +4 -0
  34. package/.agent-src/commands/fix-portability.md +5 -2
  35. package/.agent-src/commands/fix-pr-bot-comments.md +4 -0
  36. package/.agent-src/commands/fix-pr-comments.md +4 -0
  37. package/.agent-src/commands/fix-pr-developer-comments.md +4 -0
  38. package/.agent-src/commands/fix-references.md +3 -0
  39. package/.agent-src/commands/fix-seeder.md +4 -0
  40. package/.agent-src/commands/implement-ticket.md +39 -13
  41. package/.agent-src/commands/jira-ticket.md +4 -0
  42. package/.agent-src/commands/judge.md +3 -0
  43. package/.agent-src/commands/memory-add.md +5 -3
  44. package/.agent-src/commands/memory-full.md +5 -2
  45. package/.agent-src/commands/memory-promote.md +7 -6
  46. package/.agent-src/commands/mode.md +3 -0
  47. package/.agent-src/commands/module-create.md +4 -0
  48. package/.agent-src/commands/module-explore.md +4 -0
  49. package/.agent-src/commands/onboard.md +33 -0
  50. package/.agent-src/commands/optimize-agents.md +4 -0
  51. package/.agent-src/commands/optimize-augmentignore.md +12 -0
  52. package/.agent-src/commands/optimize-rtk-filters.md +3 -0
  53. package/.agent-src/commands/optimize-skills.md +4 -0
  54. package/.agent-src/commands/override-create.md +4 -0
  55. package/.agent-src/commands/override-manage.md +4 -0
  56. package/.agent-src/commands/package-reset.md +3 -0
  57. package/.agent-src/commands/package-test.md +3 -0
  58. package/.agent-src/commands/prepare-for-review.md +4 -0
  59. package/.agent-src/commands/project-analyze.md +4 -0
  60. package/.agent-src/commands/project-health.md +4 -0
  61. package/.agent-src/commands/propose-memory.md +6 -8
  62. package/.agent-src/commands/quality-fix.md +4 -0
  63. package/.agent-src/commands/refine-ticket.md +12 -7
  64. package/.agent-src/commands/review-changes.md +39 -8
  65. package/.agent-src/commands/review-routing.md +4 -0
  66. package/.agent-src/commands/roadmap-create.md +18 -0
  67. package/.agent-src/commands/roadmap-execute.md +14 -1
  68. package/.agent-src/commands/rule-compliance-audit.md +4 -0
  69. package/.agent-src/commands/set-cost-profile.md +11 -0
  70. package/.agent-src/commands/sync-agent-settings.md +12 -0
  71. package/.agent-src/commands/sync-gitignore.md +3 -0
  72. package/.agent-src/commands/tests-create.md +4 -0
  73. package/.agent-src/commands/tests-execute.md +6 -3
  74. package/.agent-src/commands/threat-model.md +4 -0
  75. package/.agent-src/commands/update-form-request-messages.md +4 -0
  76. package/.agent-src/commands/upstream-contribute.md +4 -0
  77. package/.agent-src/commands/work.md +161 -0
  78. package/.agent-src/guidelines/agent-infra/engineering-memory-data-format.md +2 -6
  79. package/.agent-src/guidelines/agent-infra/layered-settings.md +0 -1
  80. package/.agent-src/guidelines/agent-infra/memory-access.md +0 -7
  81. package/.agent-src/guidelines/agent-infra/role-contracts.md +2 -4
  82. package/.agent-src/guidelines/agent-infra/self-improvement-pipeline.md +0 -1
  83. package/.agent-src/guidelines/php/patterns/strategy.md +180 -2
  84. package/.agent-src/personas/README.md +0 -1
  85. package/.agent-src/rules/artifact-drafting-protocol.md +7 -2
  86. package/.agent-src/rules/artifact-engagement-recording.md +133 -0
  87. package/.agent-src/rules/ask-when-uncertain.md +18 -13
  88. package/.agent-src/rules/augment-portability.md +64 -37
  89. package/.agent-src/rules/autonomous-execution.md +158 -0
  90. package/.agent-src/rules/chat-history-cadence.md +109 -0
  91. package/.agent-src/rules/chat-history-ownership.md +123 -0
  92. package/.agent-src/rules/chat-history-visibility.md +96 -0
  93. package/.agent-src/rules/cli-output-handling.md +27 -4
  94. package/.agent-src/rules/command-suggestion.md +134 -0
  95. package/.agent-src/rules/commit-policy.md +109 -0
  96. package/.agent-src/rules/direct-answers.md +114 -0
  97. package/.agent-src/rules/docs-sync.md +36 -0
  98. package/.agent-src/rules/downstream-changes.md +10 -9
  99. package/.agent-src/rules/improve-before-implement.md +9 -6
  100. package/.agent-src/rules/language-and-tone.md +85 -6
  101. package/.agent-src/rules/non-destructive-by-default.md +117 -0
  102. package/.agent-src/rules/package-ci-checks.md +4 -0
  103. package/.agent-src/rules/preservation-guard.md +20 -0
  104. package/.agent-src/rules/roadmap-progress-sync.md +159 -27
  105. package/.agent-src/rules/role-mode-adherence.md +1 -1
  106. package/.agent-src/rules/scope-control.md +42 -1
  107. package/.agent-src/rules/size-enforcement.md +2 -3
  108. package/.agent-src/rules/skill-quality.md +3 -8
  109. package/.agent-src/rules/ui-audit-before-build.md +106 -0
  110. package/.agent-src/rules/user-interaction.md +107 -51
  111. package/.agent-src/scripts/update_roadmap_progress.py +73 -9
  112. package/.agent-src/skills/blade-ui/SKILL.md +47 -3
  113. package/.agent-src/skills/command-routing/SKILL.md +32 -0
  114. package/.agent-src/skills/command-writing/SKILL.md +52 -2
  115. package/.agent-src/skills/description-assist/SKILL.md +21 -0
  116. package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
  117. package/.agent-src/skills/existing-ui-audit/SKILL.md +202 -0
  118. package/.agent-src/skills/fe-design/SKILL.md +78 -61
  119. package/.agent-src/skills/file-editor/SKILL.md +9 -0
  120. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
  121. package/.agent-src/skills/flux/SKILL.md +31 -4
  122. package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
  123. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
  124. package/.agent-src/skills/livewire/SKILL.md +49 -4
  125. package/.agent-src/skills/md-language-check/SKILL.md +103 -0
  126. package/.agent-src/skills/php-coder/SKILL.md +24 -0
  127. package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
  128. package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
  129. package/.agent-src/skills/refine-ticket/SKILL.md +32 -28
  130. package/.agent-src/skills/roadmap-management/SKILL.md +24 -11
  131. package/.agent-src/skills/rule-writing/SKILL.md +23 -1
  132. package/.agent-src/skills/skill-writing/SKILL.md +3 -5
  133. package/.agent-src/skills/upstream-contribute/SKILL.md +3 -3
  134. package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
  135. package/.agent-src/templates/AGENTS.md +24 -6
  136. package/.agent-src/templates/agent-settings.md +149 -0
  137. package/.agent-src/templates/roadmaps.md +11 -4
  138. package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
  139. package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
  140. package/.agent-src/templates/scripts/memory_lookup.py +1 -1
  141. package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
  142. package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
  143. package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
  144. package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
  145. package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
  146. package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
  147. package/.agent-src/templates/scripts/telemetry_record.py +166 -0
  148. package/.agent-src/templates/scripts/telemetry_report.py +161 -0
  149. package/.agent-src/templates/scripts/telemetry_status.py +142 -0
  150. package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
  151. package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
  152. package/.agent-src/templates/scripts/work_engine/cli.py +195 -0
  153. package/.agent-src/templates/scripts/work_engine/cli_args.py +116 -0
  154. package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +10 -3
  155. package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
  156. package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
  157. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
  158. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +3 -3
  159. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +2 -2
  160. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +2 -2
  161. package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
  162. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +37 -5
  163. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
  164. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
  165. package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
  166. package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
  167. package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
  168. package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
  169. package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
  170. package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
  171. package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
  172. package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
  173. package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
  174. package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
  175. package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
  176. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
  177. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
  178. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
  179. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
  180. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
  181. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
  182. package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
  183. package/.agent-src/templates/scripts/work_engine/emitters.py +43 -0
  184. package/.agent-src/templates/scripts/work_engine/errors.py +19 -0
  185. package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +76 -0
  186. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
  187. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
  188. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
  189. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
  190. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
  191. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
  192. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
  193. package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
  194. package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
  195. package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
  196. package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
  197. package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
  198. package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
  199. package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
  200. package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
  201. package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
  202. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
  203. package/.agent-src/templates/scripts/work_engine/input_builders.py +163 -0
  204. package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
  205. package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
  206. package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
  207. package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +231 -0
  208. package/.agent-src/templates/scripts/{implement_ticket → work_engine}/persona_policy.py +1 -1
  209. package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
  210. package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
  211. package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
  212. package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
  213. package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
  214. package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
  215. package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
  216. package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
  217. package/.agent-src/templates/scripts/work_engine/state.py +641 -0
  218. package/.agent-src/templates/scripts/work_engine/state_io.py +202 -0
  219. package/.claude-plugin/marketplace.json +105 -2
  220. package/AGENTS.md +38 -8
  221. package/CHANGELOG.md +609 -0
  222. package/README.md +136 -14
  223. package/config/agent-settings.template.yml +45 -0
  224. package/config/gitignore-block.txt +4 -0
  225. package/docs/MIGRATION.md +122 -0
  226. package/docs/architecture.md +111 -35
  227. package/docs/contracts/STABILITY.md +95 -0
  228. package/docs/contracts/adr-chat-history-split.md +132 -0
  229. package/docs/contracts/adr-command-suggestion.md +146 -0
  230. package/docs/contracts/adr-implement-ticket-runtime.md +122 -0
  231. package/docs/contracts/adr-product-ui-track.md +384 -0
  232. package/docs/contracts/adr-prompt-driven-execution.md +187 -0
  233. package/docs/contracts/agent-memory-contract.md +149 -0
  234. package/docs/contracts/artifact-engagement-flow.md +262 -0
  235. package/docs/contracts/command-clusters.md +126 -0
  236. package/docs/contracts/command-suggestion-flow.md +148 -0
  237. package/docs/contracts/implement-ticket-flow.md +628 -0
  238. package/docs/contracts/linear-ai-rules-inclusion.md +143 -0
  239. package/docs/contracts/linear-ai-three-layers.md +131 -0
  240. package/docs/contracts/rule-interactions.md +107 -0
  241. package/docs/contracts/rule-interactions.yml +142 -0
  242. package/docs/contracts/ui-stack-extension.md +236 -0
  243. package/docs/contracts/ui-track-flow.md +338 -0
  244. package/docs/development.md +1 -1
  245. package/docs/getting-started.md +3 -3
  246. package/docs/installation.md +124 -2
  247. package/docs/migrations/commands-1.15.0.md +112 -0
  248. package/docs/showcase.md +204 -0
  249. package/docs/ui-track-mental-model.md +121 -0
  250. package/package.json +1 -1
  251. package/scripts/agent-config +199 -0
  252. package/scripts/audit_cloud_compatibility.py +288 -0
  253. package/scripts/build_cloud_bundle.py +458 -0
  254. package/scripts/build_linear_digest.py +263 -0
  255. package/scripts/chat_history.py +796 -7
  256. package/scripts/check_compression.py +139 -0
  257. package/scripts/check_iron_law_prominence.py +143 -0
  258. package/scripts/check_md_language.py +159 -0
  259. package/scripts/check_portability.py +38 -0
  260. package/scripts/check_public_links.py +185 -0
  261. package/scripts/check_references.py +1 -0
  262. package/scripts/check_reply_consistency.py +140 -0
  263. package/scripts/command_suggester/__init__.py +51 -0
  264. package/scripts/command_suggester/cooldown.py +132 -0
  265. package/scripts/command_suggester/loader.py +70 -0
  266. package/scripts/command_suggester/match.py +180 -0
  267. package/scripts/command_suggester/rank.py +120 -0
  268. package/scripts/command_suggester/render.py +86 -0
  269. package/scripts/command_suggester/sanitize.py +113 -0
  270. package/scripts/command_suggester/settings.py +125 -0
  271. package/scripts/command_suggester/types.py +78 -0
  272. package/scripts/hooks/augment-chat-history.sh +56 -0
  273. package/scripts/install-hooks.sh +67 -0
  274. package/scripts/install.py +150 -33
  275. package/scripts/lint_marketplace.py +27 -0
  276. package/scripts/lint_no_new_atomic_commands.py +179 -0
  277. package/scripts/lint_rule_interactions.py +149 -0
  278. package/scripts/memory_lookup.py +1 -1
  279. package/scripts/migrate_command_suggestions.py +151 -0
  280. package/scripts/release.py +297 -64
  281. package/scripts/schemas/command.schema.json +41 -0
  282. package/scripts/skill_linter.py +81 -0
  283. package/scripts/sync_agent_settings.py +42 -12
  284. package/scripts/update_counts.py +10 -0
  285. package/templates/consumer-settings/augment-cli-hooks.json +54 -0
  286. package/templates/consumer-settings/claude-settings.json +55 -1
  287. package/.agent-src/rules/chat-history.md +0 -171
  288. package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
  289. package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
  290. package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
  291. package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
@@ -0,0 +1,73 @@
1
+ """``HookRunner`` — single emit point for hook callbacks.
2
+
3
+ Implements the three-tier error contract documented in
4
+ ``exceptions.py``:
5
+
6
+ - ``HookError`` from a callback → caught, ``warnings.warn`` is emitted,
7
+ the runner continues with the next callback for the same event.
8
+ Returns ``None`` once the event is fully drained.
9
+ - ``HookHalt`` from a callback → caught, **returned** to the caller
10
+ with no further callbacks invoked for this event. The caller
11
+ decides how to surface the halt (engine halt, CLI exit 2). Never
12
+ re-raised through the dispatch loop.
13
+ - any other ``Exception`` → propagates unchanged. Treated as a hook
14
+ bug; dispatch unwinds.
15
+
16
+ The runner is intentionally tiny. Behavior changes belong here so
17
+ ``dispatcher.py`` and ``cli.py`` stay free of hook bookkeeping.
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import warnings
22
+
23
+ from .context import HookContext
24
+ from .events import HookEvent
25
+ from .exceptions import HookError, HookHalt
26
+ from .registry import HookRegistry
27
+
28
+
29
+ class HookRunner:
30
+ """Emit hook events through a :class:`HookRegistry`.
31
+
32
+ Construct once per CLI invocation, share between the CLI and the
33
+ dispatcher. ``emit`` is the only public method on the hot path.
34
+ """
35
+
36
+ def __init__(self, registry: HookRegistry | None = None) -> None:
37
+ self._registry = registry if registry is not None else HookRegistry()
38
+
39
+ @property
40
+ def registry(self) -> HookRegistry:
41
+ """Return the underlying registry.
42
+
43
+ Exposed so callers can register additional hooks after
44
+ construction (e.g. in tests). Not used on the hot path.
45
+ """
46
+ return self._registry
47
+
48
+ def emit(self, event: HookEvent, ctx: HookContext) -> HookHalt | None:
49
+ """Fire all callbacks registered for ``event``.
50
+
51
+ Returns ``None`` when every callback completed (with or without
52
+ a swallowed :class:`HookError`). Returns the first
53
+ :class:`HookHalt` raised, after which no further callbacks are
54
+ invoked for this event. Any other exception propagates.
55
+ """
56
+ callbacks = self._registry.for_event(event)
57
+ if not callbacks:
58
+ return None
59
+ for callback in callbacks:
60
+ try:
61
+ callback(ctx)
62
+ except HookHalt as halt:
63
+ return halt
64
+ except HookError as err:
65
+ warnings.warn(
66
+ f"hook {event.value} raised HookError: {err}",
67
+ stacklevel=2,
68
+ )
69
+ continue
70
+ return None
71
+
72
+
73
+ __all__ = ["HookRunner"]
@@ -0,0 +1,141 @@
1
+ """Read ``hooks.*`` from ``.agent-settings.yml`` into :class:`HookSettings`.
2
+
3
+ Mirror of the chat-history settings pattern (``scripts/command_suggester/
4
+ settings.py``):
5
+
6
+ * Lazy PyYAML import — the engine works without yaml installed when no
7
+ settings file is present (test fixtures, cloud bundles).
8
+ * Default-permissive: a missing file or missing ``hooks:`` block returns
9
+ :class:`HookSettings` with ``enabled=False`` — every hook off, every
10
+ golden replay safe by construction.
11
+ * Malformed YAML / unreadable file → defaults; the engine degrades
12
+ silently rather than crashing the CLI.
13
+ * Chat-history hooks gate on **two** switches: ``hooks.chat_history.
14
+ enabled`` AND the global ``chat_history.enabled``. Either off → no
15
+ chat-history hook registers.
16
+ """
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+ from typing import Any
22
+
23
+ DEFAULT_SETTINGS_FILE = ".agent-settings.yml"
24
+ DEFAULT_CHAT_HISTORY_SCRIPT = "scripts/chat_history.py"
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class HookSettings:
29
+ """Resolved view of the ``hooks:`` block.
30
+
31
+ ``enabled`` is the master switch. When ``False`` the registry stays
32
+ empty regardless of the per-hook fields; this is the default when no
33
+ settings file exists or no ``hooks`` block is declared, and it is
34
+ what keeps golden-replay tests byte-stable.
35
+ """
36
+
37
+ enabled: bool = False
38
+ trace: bool = False
39
+ halt_surface_audit: bool = False
40
+ state_shape_validation: bool = False
41
+ directive_set_guard: bool = False
42
+ chat_history_enabled: bool = False
43
+ chat_history_script: str = DEFAULT_CHAT_HISTORY_SCRIPT
44
+
45
+
46
+ _DEFAULT = HookSettings()
47
+
48
+
49
+ def load_hook_settings(
50
+ settings_path: Path | str | None = None,
51
+ ) -> HookSettings:
52
+ """Return :class:`HookSettings` hydrated from ``.agent-settings.yml``.
53
+
54
+ ``settings_path`` defaults to ``./.agent-settings.yml`` relative to
55
+ the current working directory — same convention as chat-history.
56
+ """
57
+ path = Path(settings_path) if settings_path else Path(DEFAULT_SETTINGS_FILE)
58
+ raw = _read_yaml(path)
59
+ if raw is None:
60
+ return _DEFAULT
61
+ return _settings_from_raw(raw)
62
+
63
+
64
+ def _read_yaml(path: Path) -> dict[str, Any] | None:
65
+ if not path.is_file():
66
+ return None
67
+ try:
68
+ import yaml # type: ignore[import-untyped]
69
+ except ImportError:
70
+ return None
71
+ try:
72
+ with path.open(encoding="utf-8") as fh:
73
+ data = yaml.safe_load(fh) or {}
74
+ except (OSError, yaml.YAMLError):
75
+ return None
76
+ if not isinstance(data, dict):
77
+ return None
78
+ return data
79
+
80
+
81
+ def _settings_from_raw(data: dict[str, Any]) -> HookSettings:
82
+ hooks = data.get("hooks")
83
+ if not isinstance(hooks, dict):
84
+ return _DEFAULT
85
+ enabled = _coerce_bool(hooks.get("enabled"), False)
86
+ if not enabled:
87
+ return HookSettings(enabled=False)
88
+
89
+ chat_section = hooks.get("chat_history")
90
+ if isinstance(chat_section, dict):
91
+ chat_block_enabled = _coerce_bool(chat_section.get("enabled"), True)
92
+ chat_script = str(
93
+ chat_section.get("script") or DEFAULT_CHAT_HISTORY_SCRIPT
94
+ )
95
+ else:
96
+ chat_block_enabled = True
97
+ chat_script = DEFAULT_CHAT_HISTORY_SCRIPT
98
+
99
+ global_chat = data.get("chat_history")
100
+ global_chat_on = (
101
+ isinstance(global_chat, dict)
102
+ and _coerce_bool(global_chat.get("enabled"), False)
103
+ )
104
+
105
+ return HookSettings(
106
+ enabled=True,
107
+ trace=_coerce_bool(hooks.get("trace"), False),
108
+ halt_surface_audit=_coerce_bool(
109
+ hooks.get("halt_surface_audit"), True
110
+ ),
111
+ state_shape_validation=_coerce_bool(
112
+ hooks.get("state_shape_validation"), True
113
+ ),
114
+ directive_set_guard=_coerce_bool(
115
+ hooks.get("directive_set_guard"), True
116
+ ),
117
+ chat_history_enabled=chat_block_enabled and global_chat_on,
118
+ chat_history_script=chat_script,
119
+ )
120
+
121
+
122
+ def _coerce_bool(value: Any, default: bool) -> bool:
123
+ if isinstance(value, bool):
124
+ return value
125
+ if value is None:
126
+ return default
127
+ if isinstance(value, str):
128
+ s = value.strip().lower()
129
+ if s in ("true", "yes", "on", "1"):
130
+ return True
131
+ if s in ("false", "no", "off", "0"):
132
+ return False
133
+ return default
134
+
135
+
136
+ __all__ = [
137
+ "DEFAULT_CHAT_HISTORY_SCRIPT",
138
+ "DEFAULT_SETTINGS_FILE",
139
+ "HookSettings",
140
+ "load_hook_settings",
141
+ ]
@@ -0,0 +1,163 @@
1
+ """File-based input builders and the load-or-build dispatch helper.
2
+
3
+ Extracted from ``cli.py`` in P2.3 of
4
+ ``road-to-post-pr29-optimize.md``. Owns the CLI's "first run" path:
5
+ when no state file exists, build a fresh :class:`WorkState` from
6
+ ``--ticket-file``, ``--prompt-file``, ``--diff-file`` or
7
+ ``--file-file``. Every builder is byte-identical in behaviour to the
8
+ pre-split version — the resolvers it calls and the persona / routing
9
+ post-processing did not move.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ from pathlib import Path
15
+
16
+ from .cli_args import _FMT_V0, _FMT_V1
17
+ from .errors import _CLIError
18
+ from .intent import populate_routing
19
+ from .resolvers.diff import DiffResolverError, build_envelope as _build_diff_envelope
20
+ from .resolvers.file import FileResolverError, build_envelope as _build_file_envelope
21
+ from .resolvers.prompt import PromptResolverError, build_envelope as _build_prompt_envelope
22
+ from .state import Input, WorkState
23
+ from .state_io import _load, _maybe_raise_legacy_hint, _read_json
24
+
25
+
26
+ def _load_or_build(
27
+ state_file: Path,
28
+ args: argparse.Namespace,
29
+ ) -> tuple[WorkState, str]:
30
+ """Return the WorkState to dispatch against plus its wire format.
31
+
32
+ Either loaded from ``state_file`` (format-preserving) or freshly
33
+ built from ``--ticket-file`` (R1), ``--prompt-file`` (R2),
34
+ ``--diff-file`` (R3) or ``--file-file`` (R3). Fresh ticket files
35
+ default to v0 wire format so that newly captured Goldens stay
36
+ byte-equal with the pre-Phase-4 baseline; the prompt / diff / file
37
+ paths emit v1 directly (v0 has no envelope concept for these
38
+ kinds). v1 round-trips for state files already on disk in v1 shape.
39
+ """
40
+ if state_file.exists():
41
+ return _load(state_file)
42
+ _maybe_raise_legacy_hint(state_file)
43
+ inputs = [
44
+ ("--ticket-file", args.ticket_file),
45
+ ("--prompt-file", args.prompt_file),
46
+ ("--diff-file", args.diff_file),
47
+ ("--file-file", args.file_file),
48
+ ]
49
+ supplied = [name for name, value in inputs if value is not None]
50
+ if len(supplied) > 1:
51
+ raise _CLIError(
52
+ f"{', '.join(supplied)} are mutually exclusive; pass exactly "
53
+ "one when building an initial state.",
54
+ )
55
+ if not supplied:
56
+ raise _CLIError(
57
+ f"No state file at {state_file} and no --ticket-file, "
58
+ "--prompt-file, --diff-file, or --file-file given; cannot "
59
+ "build an initial state.",
60
+ )
61
+ if args.prompt_file is not None:
62
+ return _build_from_prompt_file(args), _FMT_V1
63
+ if args.diff_file is not None:
64
+ return _build_from_diff_file(args), _FMT_V1
65
+ if args.file_file is not None:
66
+ return _build_from_file_file(args), _FMT_V1
67
+ ticket = _read_json(args.ticket_file)
68
+ if not isinstance(ticket, dict):
69
+ raise _CLIError(
70
+ f"--ticket-file must carry a JSON object; got {type(ticket).__name__}.",
71
+ )
72
+ work = WorkState(input=Input(kind="ticket", data=ticket))
73
+ if args.persona:
74
+ work.persona = args.persona
75
+ populate_routing(work)
76
+ return work, _FMT_V0
77
+
78
+
79
+ def _build_from_prompt_file(args: argparse.Namespace) -> WorkState:
80
+ """Read ``--prompt-file`` as raw text and wrap it in a prompt envelope.
81
+
82
+ The file is read verbatim (UTF-8) and handed to the prompt resolver,
83
+ which validates non-emptiness and returns the canonical
84
+ ``Input(kind="prompt", data={raw, reconstructed_ac, assumptions})``
85
+ envelope. Persona is honoured the same way as the ticket path.
86
+ """
87
+ try:
88
+ raw = args.prompt_file.read_text(encoding="utf-8")
89
+ except OSError as exc:
90
+ raise _CLIError(f"Cannot read {args.prompt_file}: {exc}") from exc
91
+ try:
92
+ envelope = _build_prompt_envelope(raw)
93
+ except PromptResolverError as exc:
94
+ raise _CLIError(f"--prompt-file is not a valid prompt: {exc}") from exc
95
+ work = WorkState(input=envelope)
96
+ if args.persona:
97
+ work.persona = args.persona
98
+ populate_routing(work)
99
+ return work
100
+
101
+
102
+ def _build_from_diff_file(args: argparse.Namespace) -> WorkState:
103
+ """Read ``--diff-file`` as raw text and wrap it in a diff envelope.
104
+
105
+ The file is read verbatim (UTF-8) and handed to the diff resolver,
106
+ which validates the unified-diff header heuristic and returns the
107
+ canonical
108
+ ``Input(kind="diff", data={raw, reconstructed_ac, assumptions})``
109
+ envelope. ``populate_routing`` then routes the envelope to the
110
+ UI-improve directive set without running the prose classifier — see
111
+ :mod:`work_engine.intent.classify` for the routing contract.
112
+ """
113
+ try:
114
+ raw = args.diff_file.read_text(encoding="utf-8")
115
+ except OSError as exc:
116
+ raise _CLIError(f"Cannot read {args.diff_file}: {exc}") from exc
117
+ try:
118
+ envelope = _build_diff_envelope(raw)
119
+ except DiffResolverError as exc:
120
+ raise _CLIError(f"--diff-file is not a valid diff: {exc}") from exc
121
+ work = WorkState(input=envelope)
122
+ if args.persona:
123
+ work.persona = args.persona
124
+ populate_routing(work)
125
+ return work
126
+
127
+
128
+ def _build_from_file_file(args: argparse.Namespace) -> WorkState:
129
+ """Read ``--file-file`` as a single-line path and wrap it in a file envelope.
130
+
131
+ The file is read verbatim (UTF-8); the first non-empty line is taken
132
+ as the path reference and handed to the file resolver, which
133
+ validates path shape (non-empty, NUL-free, not a URL) and returns
134
+ the canonical
135
+ ``Input(kind="file", data={path, reconstructed_ac, assumptions})``
136
+ envelope. Trailing whitespace and additional lines are ignored —
137
+ the resolver treats the file's content as the path itself, not as
138
+ structured payload.
139
+ """
140
+ try:
141
+ raw = args.file_file.read_text(encoding="utf-8")
142
+ except OSError as exc:
143
+ raise _CLIError(f"Cannot read {args.file_file}: {exc}") from exc
144
+ path = raw.strip().splitlines()[0] if raw.strip() else ""
145
+ try:
146
+ envelope = _build_file_envelope(path)
147
+ except FileResolverError as exc:
148
+ raise _CLIError(
149
+ f"--file-file does not carry a valid path: {exc}",
150
+ ) from exc
151
+ work = WorkState(input=envelope)
152
+ if args.persona:
153
+ work.persona = args.persona
154
+ populate_routing(work)
155
+ return work
156
+
157
+
158
+ __all__ = [
159
+ "_build_from_diff_file",
160
+ "_build_from_file_file",
161
+ "_build_from_prompt_file",
162
+ "_load_or_build",
163
+ ]
@@ -0,0 +1,47 @@
1
+ """Intent classification for the universal engine (R3 Phase 1 Step 2).
2
+
3
+ The :mod:`work_engine.intent.classify` module turns a raw user prompt
4
+ (or a ticket's title + body) into one of the five labels the dispatcher
5
+ routes against:
6
+
7
+ - ``ui-build`` — new screen, page, or component.
8
+ - ``ui-improve`` — change to an existing screen / component.
9
+ - ``ui-trivial`` — single-file, single-concern micro-edit (color, copy,
10
+ one class, one prop). Hard preconditions are enforced again at apply
11
+ time; the classifier only labels the *intent*, not the safety floor.
12
+ - ``mixed`` — both UI and backend signals; routes to the mixed track.
13
+ - ``backend-coding`` — default; no UI signal.
14
+
15
+ The classifier is intentionally heuristic-only — it consumes nothing
16
+ beyond the prompt text and optional ticket title. Confidence-band
17
+ gating, AC reconstruction, and assumption surfacing all stay in
18
+ ``directives/backend/refine.py`` (R2). This module only owns the
19
+ *label*; the dispatcher owns the routing.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from . import classify
24
+ from .classify import (
25
+ INTENT_BACKEND,
26
+ INTENT_MIXED,
27
+ INTENT_UI_BUILD,
28
+ INTENT_UI_IMPROVE,
29
+ INTENT_UI_TRIVIAL,
30
+ KNOWN_INTENTS,
31
+ classify_intent,
32
+ directive_set_for,
33
+ populate_routing,
34
+ )
35
+
36
+ __all__ = [
37
+ "INTENT_BACKEND",
38
+ "INTENT_MIXED",
39
+ "INTENT_UI_BUILD",
40
+ "INTENT_UI_IMPROVE",
41
+ "INTENT_UI_TRIVIAL",
42
+ "KNOWN_INTENTS",
43
+ "classify",
44
+ "classify_intent",
45
+ "directive_set_for",
46
+ "populate_routing",
47
+ ]