@event4u/agent-config 1.13.0 → 1.14.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 (252) hide show
  1. package/.agent-src/commands/agent-handoff.md +3 -0
  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 +5 -1
  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 +5 -0
  11. package/.agent-src/commands/chat-history-resume.md +5 -0
  12. package/.agent-src/commands/chat-history.md +5 -0
  13. package/.agent-src/commands/check-current-md.md +126 -0
  14. package/.agent-src/commands/commit-in-chunks.md +98 -0
  15. package/.agent-src/commands/commit.md +4 -0
  16. package/.agent-src/commands/compress.md +3 -0
  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 +3 -0
  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 +24 -0
  50. package/.agent-src/commands/optimize-agents.md +4 -0
  51. package/.agent-src/commands/optimize-augmentignore.md +3 -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 +4 -1
  64. package/.agent-src/commands/review-changes.md +4 -0
  65. package/.agent-src/commands/review-routing.md +4 -0
  66. package/.agent-src/commands/roadmap-create.md +7 -0
  67. package/.agent-src/commands/roadmap-execute.md +12 -1
  68. package/.agent-src/commands/rule-compliance-audit.md +4 -0
  69. package/.agent-src/commands/set-cost-profile.md +3 -0
  70. package/.agent-src/commands/sync-agent-settings.md +3 -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 +4 -0
  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 +8 -0
  89. package/.agent-src/rules/autonomous-execution.md +158 -0
  90. package/.agent-src/rules/chat-history.md +147 -118
  91. package/.agent-src/rules/cli-output-handling.md +26 -3
  92. package/.agent-src/rules/command-suggestion.md +133 -0
  93. package/.agent-src/rules/commit-policy.md +99 -0
  94. package/.agent-src/rules/direct-answers.md +114 -0
  95. package/.agent-src/rules/docs-sync.md +36 -0
  96. package/.agent-src/rules/downstream-changes.md +10 -9
  97. package/.agent-src/rules/improve-before-implement.md +9 -6
  98. package/.agent-src/rules/language-and-tone.md +81 -6
  99. package/.agent-src/rules/non-destructive-by-default.md +117 -0
  100. package/.agent-src/rules/package-ci-checks.md +4 -0
  101. package/.agent-src/rules/preservation-guard.md +20 -0
  102. package/.agent-src/rules/roadmap-progress-sync.md +103 -30
  103. package/.agent-src/rules/scope-control.md +42 -1
  104. package/.agent-src/rules/size-enforcement.md +1 -3
  105. package/.agent-src/rules/skill-quality.md +3 -8
  106. package/.agent-src/rules/ui-audit-before-build.md +106 -0
  107. package/.agent-src/rules/user-interaction.md +82 -50
  108. package/.agent-src/scripts/update_roadmap_progress.py +17 -5
  109. package/.agent-src/skills/blade-ui/SKILL.md +30 -5
  110. package/.agent-src/skills/command-routing/SKILL.md +32 -0
  111. package/.agent-src/skills/command-writing/SKILL.md +41 -2
  112. package/.agent-src/skills/description-assist/SKILL.md +21 -0
  113. package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
  114. package/.agent-src/skills/existing-ui-audit/SKILL.md +187 -0
  115. package/.agent-src/skills/fe-design/SKILL.md +72 -60
  116. package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
  117. package/.agent-src/skills/flux/SKILL.md +31 -4
  118. package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
  119. package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
  120. package/.agent-src/skills/livewire/SKILL.md +30 -4
  121. package/.agent-src/skills/md-language-check/SKILL.md +103 -0
  122. package/.agent-src/skills/php-coder/SKILL.md +24 -0
  123. package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
  124. package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
  125. package/.agent-src/skills/refine-ticket/SKILL.md +2 -4
  126. package/.agent-src/skills/roadmap-management/SKILL.md +10 -3
  127. package/.agent-src/skills/rule-writing/SKILL.md +23 -1
  128. package/.agent-src/skills/skill-writing/SKILL.md +1 -3
  129. package/.agent-src/skills/upstream-contribute/SKILL.md +1 -1
  130. package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
  131. package/.agent-src/templates/AGENTS.md +24 -6
  132. package/.agent-src/templates/agent-settings.md +149 -0
  133. package/.agent-src/templates/roadmaps.md +8 -2
  134. package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
  135. package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
  136. package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
  137. package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
  138. package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
  139. package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
  140. package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
  141. package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
  142. package/.agent-src/templates/scripts/telemetry_record.py +166 -0
  143. package/.agent-src/templates/scripts/telemetry_report.py +161 -0
  144. package/.agent-src/templates/scripts/telemetry_status.py +142 -0
  145. package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
  146. package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
  147. package/.agent-src/templates/scripts/work_engine/cli.py +592 -0
  148. package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +7 -0
  149. package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
  150. package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
  151. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
  152. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +2 -2
  153. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +1 -1
  154. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +1 -1
  155. package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
  156. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +36 -4
  157. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
  158. package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
  159. package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
  160. package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
  161. package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
  162. package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
  163. package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
  164. package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
  165. package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
  166. package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
  167. package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
  168. package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
  169. package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
  170. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
  171. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
  172. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
  173. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
  174. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
  175. package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
  176. package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
  177. package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
  178. package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
  179. package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
  180. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
  181. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
  182. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
  183. package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
  184. package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
  185. package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
  186. package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
  187. package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
  188. package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
  189. package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
  190. package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
  191. package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
  192. package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
  193. package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
  194. package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
  195. package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
  196. package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
  197. package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +199 -0
  198. package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
  199. package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
  200. package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
  201. package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
  202. package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
  203. package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
  204. package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
  205. package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
  206. package/.agent-src/templates/scripts/work_engine/state.py +641 -0
  207. package/.claude-plugin/marketplace.json +105 -2
  208. package/AGENTS.md +36 -8
  209. package/CHANGELOG.md +534 -0
  210. package/README.md +125 -4
  211. package/config/agent-settings.template.yml +45 -0
  212. package/config/gitignore-block.txt +4 -0
  213. package/docs/architecture.md +28 -1
  214. package/docs/development.md +1 -1
  215. package/docs/getting-started.md +2 -2
  216. package/docs/installation.md +86 -0
  217. package/docs/showcase.md +204 -0
  218. package/package.json +1 -1
  219. package/scripts/agent-config +199 -0
  220. package/scripts/audit_cloud_compatibility.py +288 -0
  221. package/scripts/build_cloud_bundle.py +458 -0
  222. package/scripts/build_linear_digest.py +263 -0
  223. package/scripts/chat_history.py +796 -7
  224. package/scripts/check_compression.py +139 -0
  225. package/scripts/check_iron_law_prominence.py +143 -0
  226. package/scripts/check_md_language.py +159 -0
  227. package/scripts/check_portability.py +36 -0
  228. package/scripts/check_reply_consistency.py +140 -0
  229. package/scripts/command_suggester/__init__.py +51 -0
  230. package/scripts/command_suggester/cooldown.py +132 -0
  231. package/scripts/command_suggester/loader.py +70 -0
  232. package/scripts/command_suggester/match.py +180 -0
  233. package/scripts/command_suggester/rank.py +120 -0
  234. package/scripts/command_suggester/render.py +86 -0
  235. package/scripts/command_suggester/sanitize.py +113 -0
  236. package/scripts/command_suggester/settings.py +125 -0
  237. package/scripts/command_suggester/types.py +78 -0
  238. package/scripts/hooks/augment-chat-history.sh +56 -0
  239. package/scripts/install-hooks.sh +67 -0
  240. package/scripts/install.py +150 -33
  241. package/scripts/lint_marketplace.py +27 -0
  242. package/scripts/migrate_command_suggestions.py +151 -0
  243. package/scripts/schemas/command.schema.json +41 -0
  244. package/scripts/skill_linter.py +67 -0
  245. package/scripts/sync_agent_settings.py +42 -12
  246. package/templates/consumer-settings/augment-cli-hooks.json +54 -0
  247. package/templates/consumer-settings/claude-settings.json +55 -1
  248. package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
  249. package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
  250. package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
  251. package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
  252. /package/.agent-src/templates/scripts/{implement_ticket → work_engine}/persona_policy.py +0 -0
@@ -1,40 +1,77 @@
1
- """/implement-ticket orchestrator linear step dispatcher.
2
-
3
- Shipped to consumer projects via the installer. Consumer code imports
4
- ``DeliveryState``, ``dispatch``, and the ``Outcome`` enum from this
5
- package; step implementations land in Phase 2 under
6
- ``.agent-src.uncompressed/skills/implement-ticket/`` and plug into the
7
- ``Step`` protocol exposed here.
8
-
9
- Architectural constraints (from
10
- ``agents/contexts/adr-implement-ticket-runtime.md`` and
11
- ``agents/contexts/implement-ticket-flow.md``):
12
-
13
- - Runtime is Python 3.10+.
14
- - The dispatcher is linear, not a DAG. Eight fixed steps, fixed order.
15
- - ``DeliveryState`` is the only object shared between steps — no
16
- hidden state, no side channels.
17
- - Every step terminates in ``success | blocked | partial``. ``blocked``
18
- and ``partial`` halt the flow and surface numbered questions.
19
- - The dispatcher never calls git, writes commits, or opens PRs.
1
+ """``implement_ticket``deprecated shim, retained for one release.
2
+
3
+ The engine moved to :mod:`work_engine` in R1 Phase 3. This module
4
+ re-exports the public surface so existing imports keep working, but
5
+ emits :class:`DeprecationWarning` on import. The Golden-Transcript
6
+ freeze-guard pins ``./agent-config implement-ticket`` against the
7
+ locked baseline; the shim keeps that path byte-stable while internal
8
+ callers migrate to ``work_engine``.
9
+
10
+ Remove after the next public release of this package.
20
11
  """
21
12
  from __future__ import annotations
22
13
 
23
- from .delivery_state import (
14
+ import sys as _sys
15
+ import warnings as _warnings
16
+
17
+ _warnings.warn(
18
+ "implement_ticket has moved to work_engine; importing implement_ticket "
19
+ "is deprecated and will be removed in a future release. Update imports "
20
+ "to `from work_engine import …`.",
21
+ DeprecationWarning,
22
+ stacklevel=2,
23
+ )
24
+
25
+ # Register submodule aliases so `from implement_ticket.steps.plan import …`
26
+ # and friends keep resolving to the work_engine implementation. The legacy
27
+ # package no longer ships these submodules on disk; sys.modules entries
28
+ # keep dotted-path imports working until the shim is removed.
29
+ import work_engine as _we_pkg # noqa: E402
30
+ import work_engine.cli as _we_cli # noqa: E402
31
+ import work_engine.delivery_state as _we_delivery_state # noqa: E402
32
+ import work_engine.dispatcher as _we_dispatcher # noqa: E402
33
+ import work_engine.persona_policy as _we_persona_policy # noqa: E402
34
+ import work_engine.directives.backend as _we_steps # noqa: E402
35
+ from work_engine.directives.backend import ( # noqa: E402
36
+ analyze as _we_step_analyze,
37
+ implement as _we_step_implement,
38
+ memory as _we_step_memory,
39
+ plan as _we_step_plan,
40
+ refine as _we_step_refine,
41
+ report as _we_step_report,
42
+ test as _we_step_test,
43
+ verify as _we_step_verify,
44
+ )
45
+
46
+ _sys.modules.setdefault("implement_ticket.cli", _we_cli)
47
+ _sys.modules.setdefault("implement_ticket.delivery_state", _we_delivery_state)
48
+ _sys.modules.setdefault("implement_ticket.dispatcher", _we_dispatcher)
49
+ _sys.modules.setdefault("implement_ticket.persona_policy", _we_persona_policy)
50
+ _sys.modules.setdefault("implement_ticket.steps", _we_steps)
51
+ _sys.modules.setdefault("implement_ticket.steps.analyze", _we_step_analyze)
52
+ _sys.modules.setdefault("implement_ticket.steps.implement", _we_step_implement)
53
+ _sys.modules.setdefault("implement_ticket.steps.memory", _we_step_memory)
54
+ _sys.modules.setdefault("implement_ticket.steps.plan", _we_step_plan)
55
+ _sys.modules.setdefault("implement_ticket.steps.refine", _we_step_refine)
56
+ _sys.modules.setdefault("implement_ticket.steps.report", _we_step_report)
57
+ _sys.modules.setdefault("implement_ticket.steps.test", _we_step_test)
58
+ _sys.modules.setdefault("implement_ticket.steps.verify", _we_step_verify)
59
+
60
+ from work_engine import ( # noqa: E402,F401 — re-export for backwards compat
24
61
  AGENT_DIRECTIVE_PREFIX,
62
+ DEFAULT_PERSONA,
63
+ DEFAULT_STATE_FILE,
25
64
  DeliveryState,
26
65
  Outcome,
66
+ PersonaPolicy,
67
+ STEP_ORDER,
27
68
  Step,
28
69
  StepResult,
29
70
  agent_directive,
71
+ dispatch,
30
72
  is_agent_directive,
31
- )
32
- from .cli import DEFAULT_STATE_FILE, main
33
- from .dispatcher import STEP_ORDER, dispatch
34
- from .persona_policy import (
35
- DEFAULT_PERSONA,
36
- PersonaPolicy,
37
73
  known_personas,
74
+ main,
38
75
  resolve_policy,
39
76
  )
40
77
 
@@ -1,9 +1,15 @@
1
- """Module entry point — lets ``python3 -m implement_ticket`` run the CLI."""
1
+ """Deprecated CLI entry point — delegates to :mod:`work_engine`.
2
+
3
+ ``python3 -m implement_ticket`` still works because the Golden-Transcript
4
+ freeze-guard pins that invocation. Internally it forwards to
5
+ ``work_engine.cli.main`` after emitting a ``DeprecationWarning`` from
6
+ the package ``__init__``.
7
+ """
2
8
  from __future__ import annotations
3
9
 
4
10
  import sys
5
11
 
6
- from .cli import main
12
+ from work_engine.cli import main
7
13
 
8
14
  if __name__ == "__main__":
9
15
  sys.exit(main())
@@ -0,0 +1,42 @@
1
+ """``telemetry`` — artefact engagement recording (default-off).
2
+
3
+ The package owns the local-only engagement log
4
+ (``.agent-engagement.jsonl``) that records, at task boundaries, which
5
+ artefacts (skills, rules, commands, guidelines, personas) the agent
6
+ ``consulted`` (loaded into context) and ``applied`` (cited or directly
7
+ drove a decision).
8
+
9
+ Architectural constraints (from
10
+ ``agents/roadmaps/road-to-artifact-engagement-telemetry.md`` Phase 1):
11
+
12
+ - Default-off. ``telemetry.artifact_engagement.enabled: false`` in
13
+ ``.agent-settings.yml`` produces zero file IO and zero token cost.
14
+ - Local only. No server-side aggregation, no cross-repo sync.
15
+ - ID-only payloads. No paths, no file contents, no prompts, no
16
+ secrets ever reach the log.
17
+ - Append-only JSONL. One event per task / phase-step boundary.
18
+ - Strict schema. Unknown artefact kinds are rejected.
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from .engagement import (
23
+ ALLOWED_BOUNDARY_KINDS,
24
+ ALLOWED_KINDS,
25
+ SCHEMA_VERSION,
26
+ EngagementEvent,
27
+ EngagementSchemaError,
28
+ append_event,
29
+ now_utc_iso,
30
+ parse_event,
31
+ )
32
+
33
+ __all__ = [
34
+ "ALLOWED_BOUNDARY_KINDS",
35
+ "ALLOWED_KINDS",
36
+ "SCHEMA_VERSION",
37
+ "EngagementEvent",
38
+ "EngagementSchemaError",
39
+ "append_event",
40
+ "now_utc_iso",
41
+ "parse_event",
42
+ ]
@@ -0,0 +1,154 @@
1
+ """Engagement-log aggregator (Phase 4).
2
+
3
+ Pure-stdlib reader: streams ``.agent-engagement.jsonl``, groups events by
4
+ ``(kind, id)``, and returns per-artefact statistics. The renderer in
5
+ ``report_renderer.py`` consumes the dataclasses produced here.
6
+
7
+ Design contract:
8
+
9
+ - **Skip, don't crash.** Malformed JSONL lines are counted in
10
+ ``AggregateResult.skipped_lines`` and dropped. Phase 4 Step 4 locks
11
+ this behaviour: a single corrupt line in a 10k-line log must not
12
+ block the report.
13
+ - **No IO besides the log read.** No network, no settings reads, no
14
+ log creation. Caller (CLI) is responsible for feeding a real path.
15
+ - **``since`` is exclusive on the lower bound** — ``since`` of
16
+ ``2026-04-01T00:00:00Z`` keeps events with ``ts > since``. ``None``
17
+ means "include everything".
18
+ - **Stats are sort-stable.** ``rank_artefacts`` returns a list ordered
19
+ by ``applied`` desc, ``consulted`` desc, then ``(kind, id)`` asc, so
20
+ two reports over the same log render byte-identical.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass, field
25
+ from datetime import datetime, timezone
26
+ from pathlib import Path
27
+ from typing import Iterable, Iterator
28
+
29
+ from .engagement import EngagementEvent, EngagementSchemaError, parse_event
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class ArtefactStat:
34
+ kind: str
35
+ artefact_id: str
36
+ consulted: int
37
+ applied: int
38
+ last_seen_ts: str
39
+
40
+ @property
41
+ def applied_ratio(self) -> float:
42
+ """Applied / consulted. ``0.0`` when never consulted (impossible
43
+ in practice — applied is a strict subset of consulted — but the
44
+ guard keeps the division safe for malformed inputs)."""
45
+ return (self.applied / self.consulted) if self.consulted else 0.0
46
+
47
+
48
+ @dataclass
49
+ class AggregateResult:
50
+ total_events: int = 0
51
+ parsed_events: int = 0
52
+ skipped_lines: int = 0
53
+ earliest_ts: str | None = None
54
+ latest_ts: str | None = None
55
+ artefacts: dict[tuple[str, str], dict[str, object]] = field(default_factory=dict)
56
+
57
+ def stats(self) -> list[ArtefactStat]:
58
+ """Materialise the accumulated buckets as immutable stats."""
59
+ out: list[ArtefactStat] = []
60
+ for (kind, art_id), bucket in self.artefacts.items():
61
+ out.append(
62
+ ArtefactStat(
63
+ kind=kind,
64
+ artefact_id=art_id,
65
+ consulted=int(bucket["consulted"]),
66
+ applied=int(bucket["applied"]),
67
+ last_seen_ts=str(bucket["last_seen_ts"]),
68
+ )
69
+ )
70
+ return out
71
+
72
+
73
+ def _parse_iso(ts: str) -> datetime | None:
74
+ """Parse a ``%Y-%m-%dT%H:%M:%SZ`` stamp into UTC. Returns ``None``
75
+ for malformed stamps so the caller can skip the comparison cleanly.
76
+ """
77
+ if not isinstance(ts, str) or not ts:
78
+ return None
79
+ try:
80
+ # strptime with literal Z handles the ``now_utc_iso`` format.
81
+ return datetime.strptime(ts, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc)
82
+ except ValueError:
83
+ return None
84
+
85
+
86
+ def _iter_events(log_path: Path) -> Iterator[tuple[int, EngagementEvent | None]]:
87
+ """Yield ``(line_number, event_or_None)``. ``None`` signals a skip."""
88
+ if not log_path.is_file():
89
+ return
90
+ with log_path.open("r", encoding="utf-8") as fh:
91
+ for line_no, line in enumerate(fh, start=1):
92
+ stripped = line.strip()
93
+ if not stripped:
94
+ continue
95
+ try:
96
+ event = parse_event(stripped + "\n")
97
+ except EngagementSchemaError:
98
+ yield line_no, None
99
+ continue
100
+ yield line_no, event
101
+
102
+
103
+ def aggregate(
104
+ log_path: Path,
105
+ *,
106
+ since: datetime | None = None,
107
+ ) -> AggregateResult:
108
+ """Stream the JSONL log and compute per-artefact stats."""
109
+ result = AggregateResult()
110
+ for _line_no, event in _iter_events(log_path):
111
+ result.total_events += 1
112
+ if event is None:
113
+ result.skipped_lines += 1
114
+ continue
115
+ ts = _parse_iso(event.ts)
116
+ if since is not None and ts is not None and ts <= since:
117
+ continue
118
+ result.parsed_events += 1
119
+ if result.earliest_ts is None or event.ts < result.earliest_ts:
120
+ result.earliest_ts = event.ts
121
+ if result.latest_ts is None or event.ts > result.latest_ts:
122
+ result.latest_ts = event.ts
123
+ _accumulate(result.artefacts, event.consulted, event.applied, event.ts)
124
+ return result
125
+
126
+
127
+ def _accumulate(
128
+ bucket: dict[tuple[str, str], dict[str, object]],
129
+ consulted: dict[str, list[str]],
130
+ applied: dict[str, list[str]],
131
+ ts: str,
132
+ ) -> None:
133
+ for kind, ids in consulted.items():
134
+ for art_id in ids:
135
+ entry = bucket.setdefault((kind, art_id), {"consulted": 0, "applied": 0, "last_seen_ts": ""})
136
+ entry["consulted"] = int(entry["consulted"]) + 1 # type: ignore[operator]
137
+ if ts > str(entry["last_seen_ts"]):
138
+ entry["last_seen_ts"] = ts
139
+ for kind, ids in applied.items():
140
+ for art_id in ids:
141
+ entry = bucket.setdefault((kind, art_id), {"consulted": 0, "applied": 0, "last_seen_ts": ""})
142
+ entry["applied"] = int(entry["applied"]) + 1 # type: ignore[operator]
143
+ if ts > str(entry["last_seen_ts"]):
144
+ entry["last_seen_ts"] = ts
145
+
146
+
147
+ def rank_artefacts(stats: Iterable[ArtefactStat]) -> list[ArtefactStat]:
148
+ return sorted(
149
+ stats,
150
+ key=lambda s: (-s.applied, -s.consulted, s.kind, s.artefact_id),
151
+ )
152
+
153
+
154
+ __all__ = ["ArtefactStat", "AggregateResult", "aggregate", "rank_artefacts"]
@@ -0,0 +1,171 @@
1
+ """Boundary detection + concurrent-safe recording (Phase 2).
2
+
3
+ Two responsibilities, one module:
4
+
5
+ 1. ``BoundarySession`` — in-process coalescing. Multiple ``add_*`` calls
6
+ within one task / phase-step / tool-call boundary merge into a
7
+ single emitted event (set-union on ``consulted`` / ``applied``).
8
+ Idempotent: calling ``flush()`` twice without new additions is a
9
+ no-op; calling ``add_consulted("skills", ["x"])`` twice records
10
+ ``"x"`` once.
11
+
12
+ 2. ``record_event`` — cross-process durability. Uses ``fcntl.flock``
13
+ (POSIX) so concurrent writers from separate ``./agent-config
14
+ telemetry:record`` invocations cannot interleave inside one JSONL
15
+ line. On non-POSIX (no ``fcntl``) we fall back to a best-effort
16
+ append; the package only ships on POSIX-compatible CI today.
17
+
18
+ The CLI in ``cli.py`` is the only caller that should touch the log
19
+ path directly. Agent-side flows wire through ``BoundarySession``.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import os
24
+ from contextlib import contextmanager
25
+ from dataclasses import dataclass, field
26
+ from pathlib import Path
27
+ from typing import Iterable, Iterator
28
+
29
+ try: # POSIX advisory file locking
30
+ import fcntl # type: ignore[import-not-found]
31
+ _HAS_FCNTL = True
32
+ except ImportError: # pragma: no cover — Windows / sandbox
33
+ _HAS_FCNTL = False
34
+
35
+ from .engagement import (
36
+ ALLOWED_BOUNDARY_KINDS,
37
+ ALLOWED_KINDS,
38
+ EngagementEvent,
39
+ EngagementSchemaError,
40
+ now_utc_iso,
41
+ )
42
+
43
+
44
+ @dataclass
45
+ class BoundarySession:
46
+ """Collect artefact engagements for one boundary, flush once.
47
+
48
+ Use as a context manager — ``__exit__`` flushes on clean exit and
49
+ suppresses on exception (so failed tasks don't pollute the log).
50
+ """
51
+
52
+ task_id: str
53
+ boundary_kind: str
54
+ log_path: Path
55
+ consulted: dict[str, set[str]] = field(default_factory=dict)
56
+ applied: dict[str, set[str]] = field(default_factory=dict)
57
+ _flushed: bool = False
58
+ _has_data: bool = False
59
+
60
+ def __post_init__(self) -> None:
61
+ if self.boundary_kind not in ALLOWED_BOUNDARY_KINDS:
62
+ raise EngagementSchemaError(
63
+ f"boundary_kind must be one of {ALLOWED_BOUNDARY_KINDS!r}"
64
+ )
65
+ if not isinstance(self.task_id, str) or not self.task_id:
66
+ raise EngagementSchemaError("task_id must be a non-empty string")
67
+
68
+ def add_consulted(self, kind: str, ids: Iterable[str]) -> None:
69
+ self._merge(self.consulted, kind, ids)
70
+
71
+ def add_applied(self, kind: str, ids: Iterable[str]) -> None:
72
+ self._merge(self.applied, kind, ids)
73
+
74
+ def _merge(self, bucket: dict[str, set[str]], kind: str, ids: Iterable[str]) -> None:
75
+ if kind not in ALLOWED_KINDS:
76
+ raise EngagementSchemaError(
77
+ f"{kind!r} is not an allowed artefact kind "
78
+ f"(allowed: {ALLOWED_KINDS!r})"
79
+ )
80
+ target = bucket.setdefault(kind, set())
81
+ for art_id in ids:
82
+ if not isinstance(art_id, str) or not art_id:
83
+ raise EngagementSchemaError(
84
+ f"{kind} ids must be non-empty strings"
85
+ )
86
+ target.add(art_id)
87
+ self._has_data = True
88
+
89
+ def to_event(self) -> EngagementEvent:
90
+ return EngagementEvent(
91
+ ts=now_utc_iso(),
92
+ task_id=self.task_id,
93
+ boundary_kind=self.boundary_kind,
94
+ consulted={k: sorted(v) for k, v in self.consulted.items() if v},
95
+ applied={k: sorted(v) for k, v in self.applied.items() if v},
96
+ )
97
+
98
+ def flush(self) -> bool:
99
+ """Write one merged event to the log. Returns True if written.
100
+
101
+ No-op when already flushed or no data was added — keeps the
102
+ boundary idempotent.
103
+ """
104
+ if self._flushed or not self._has_data:
105
+ return False
106
+ record_event(self.log_path, self.to_event())
107
+ self._flushed = True
108
+ return True
109
+
110
+ def __enter__(self) -> "BoundarySession":
111
+ return self
112
+
113
+ def __exit__(self, exc_type: object, exc: object, tb: object) -> None:
114
+ if exc_type is None:
115
+ self.flush()
116
+ # On exception: do nothing — failed boundary, no record.
117
+
118
+
119
+ def record_event(log_path: Path, event: EngagementEvent) -> None:
120
+ """Append one event under an exclusive file lock.
121
+
122
+ The lock guarantees that two concurrent writers append two
123
+ complete, well-formed lines instead of one interleaved line. We
124
+ open with ``"a"`` so each write atomically extends EOF on POSIX
125
+ once the lock is held.
126
+ """
127
+ event.validate()
128
+ payload = event.to_jsonl().encode("utf-8")
129
+ log_path.parent.mkdir(parents=True, exist_ok=True)
130
+ fd = os.open(log_path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o644)
131
+ try:
132
+ if _HAS_FCNTL:
133
+ fcntl.flock(fd, fcntl.LOCK_EX)
134
+ try:
135
+ written = 0
136
+ while written < len(payload):
137
+ written += os.write(fd, payload[written:])
138
+ os.fsync(fd)
139
+ finally:
140
+ if _HAS_FCNTL:
141
+ fcntl.flock(fd, fcntl.LOCK_UN)
142
+ finally:
143
+ os.close(fd)
144
+
145
+
146
+ @contextmanager
147
+ def open_boundary(
148
+ task_id: str,
149
+ boundary_kind: str,
150
+ log_path: Path,
151
+ ) -> Iterator[BoundarySession]:
152
+ """Convenience context manager around ``BoundarySession``.
153
+
154
+ >>> with open_boundary("ticket-1", "task", Path(".agent-engagement.jsonl")) as s:
155
+ ... s.add_consulted("skills", ["php-coder"])
156
+ ... s.add_applied("skills", ["php-coder"])
157
+ """
158
+ session = BoundarySession(
159
+ task_id=task_id,
160
+ boundary_kind=boundary_kind,
161
+ log_path=log_path,
162
+ )
163
+ with session:
164
+ yield session
165
+
166
+
167
+ __all__ = [
168
+ "BoundarySession",
169
+ "open_boundary",
170
+ "record_event",
171
+ ]