@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
@@ -0,0 +1,106 @@
1
+ """Diff resolver — wrap a unified-diff payload as an :class:`Input` envelope.
2
+
3
+ The resolver is the R3 Phase 1 entry point for the "improve this screen via
4
+ diff/PR" surface: a user (or `/work` adapter) hands the engine a patch text,
5
+ the resolver normalises it, and the dispatcher routes it through the UI-track
6
+ ``ui-improve`` directive set.
7
+
8
+ Like :mod:`work_engine.resolvers.prompt`, this module is intentionally thin:
9
+ it normalises and rejects garbage payloads, nothing more. Reconstruction of
10
+ acceptance criteria + assumptions + confidence is the job of the
11
+ ``refine-prompt`` skill (R2 Phase 3) running against the diff once the engine
12
+ hits the ``refine`` step. Keeping the split sharp means the envelope shape
13
+ stays cheap to round-trip through state and the heavy lifting stays with the
14
+ agent-directive halt where it belongs.
15
+
16
+ The envelope mirrors the prompt resolver's shape so a single refiner code
17
+ path can read both — ``{raw, reconstructed_ac, assumptions}``. The only
18
+ material difference is the heuristic header check that rejects payloads
19
+ that obviously are not unified-diff text.
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import re
24
+
25
+ from ..state import Input
26
+
27
+ KIND = "diff"
28
+ """Wire value carried in :attr:`work_engine.state.Input.kind`."""
29
+
30
+ _MIN_DIFF_LEN = 1
31
+ """Minimum non-whitespace character count before the heuristic runs.
32
+
33
+ The resolver is not a *quality* gate; it only rejects literally empty payloads
34
+ and obvious non-diffs. A semantically empty diff (e.g., headers but no hunks)
35
+ is still accepted so the refiner can score its tractability and surface a
36
+ ``low``-band halt where appropriate."""
37
+
38
+ _DIFF_MARKERS = (
39
+ re.compile(r"^diff --git ", re.MULTILINE),
40
+ re.compile(r"^--- ", re.MULTILINE),
41
+ re.compile(r"^\+\+\+ ", re.MULTILINE),
42
+ re.compile(r"^@@ ", re.MULTILINE),
43
+ re.compile(r"^Index: ", re.MULTILINE),
44
+ )
45
+ """Heuristic markers that flag a payload as a unified or git-style diff.
46
+
47
+ A payload qualifies if **any** marker matches — the resolver accepts unified
48
+ diffs (``--- ``/``+++ ``/``@@ ``), ``git diff`` output (``diff --git``), and
49
+ the legacy ``Index: `` SVN/CVS header. The match is anchored at line start so
50
+ quoted snippets inside prose ("the function `--- foo` failed") do not pass
51
+ the gate."""
52
+
53
+
54
+ class DiffResolverError(ValueError):
55
+ """Raised when a payload cannot be resolved into a diff envelope."""
56
+
57
+
58
+ def build_envelope(raw: str) -> Input:
59
+ """Return an :class:`Input` carrying the raw diff + empty refinement slots.
60
+
61
+ Parameters
62
+ ----------
63
+ raw:
64
+ The user-supplied diff text. Whitespace is preserved verbatim — the
65
+ refiner reads original spacing/casing when scoring goal clarity, and
66
+ a unified-diff round-trip cannot tolerate normalised whitespace.
67
+
68
+ Returns
69
+ -------
70
+ Input
71
+ Envelope of shape
72
+ ``{"kind": "diff", "data": {"raw": <raw>, "reconstructed_ac": [],
73
+ "assumptions": []}}``. The two empty lists are placeholders the
74
+ ``refine-prompt`` skill writes into on the rebound from ``refine``.
75
+
76
+ Raises
77
+ ------
78
+ DiffResolverError
79
+ If ``raw`` is not a string, contains no non-whitespace characters,
80
+ or does not match any :data:`_DIFF_MARKERS`. The marker check guards
81
+ against accidentally routing free-form prose through the diff path.
82
+ """
83
+ if not isinstance(raw, str):
84
+ raise DiffResolverError(
85
+ f"diff must be a string; got {type(raw).__name__}",
86
+ )
87
+ if len(raw.strip()) < _MIN_DIFF_LEN:
88
+ raise DiffResolverError(
89
+ "diff is empty or whitespace-only — nothing to resolve",
90
+ )
91
+ if not any(marker.search(raw) for marker in _DIFF_MARKERS):
92
+ raise DiffResolverError(
93
+ "payload does not look like a unified diff — expected one of "
94
+ "'diff --git', '--- ', '+++ ', '@@ ', or 'Index: ' headers",
95
+ )
96
+ return Input(
97
+ kind=KIND,
98
+ data={
99
+ "raw": raw,
100
+ "reconstructed_ac": [],
101
+ "assumptions": [],
102
+ },
103
+ )
104
+
105
+
106
+ __all__ = ["KIND", "DiffResolverError", "build_envelope"]
@@ -0,0 +1,113 @@
1
+ """File resolver — wrap a path reference as an :class:`Input` envelope.
2
+
3
+ The resolver is the R3 Phase 1 entry point for the "improve this existing
4
+ component/page" surface: a user hands the engine a path (e.g.,
5
+ ``resources/views/dashboard.blade.php`` or ``src/components/Sidebar.tsx``),
6
+ the resolver normalises it, and the dispatcher routes the envelope through
7
+ the UI-track ``ui-improve`` directive set.
8
+
9
+ Like :mod:`work_engine.resolvers.prompt` and :mod:`.diff`, this module is
10
+ intentionally thin: it normalises and rejects garbage payloads, nothing
11
+ more. Existence checks, mtime caching, and content reads are deferred to
12
+ the ``analyze`` step / the audit directive — a resolver doing I/O at the
13
+ command-shell boundary would couple the envelope build to filesystem state
14
+ and break replay-against-state-files.
15
+
16
+ The envelope mirrors the prompt and diff resolvers so a single refiner code
17
+ path can read all three — ``{path, reconstructed_ac, assumptions}``. The
18
+ only material difference is the path-shape check that rejects values that
19
+ are obviously not paths (absolute URLs, empty strings, control chars).
20
+ """
21
+ from __future__ import annotations
22
+
23
+ import os
24
+
25
+ from ..state import Input
26
+
27
+ KIND = "file"
28
+ """Wire value carried in :attr:`work_engine.state.Input.kind`."""
29
+
30
+ _MIN_PATH_LEN = 1
31
+ """Minimum non-whitespace character count for a resolvable path.
32
+
33
+ A 1-char path is rare but legal (``a``); the bar exists only to reject
34
+ literal empty / whitespace-only payloads which carry no signal at all."""
35
+
36
+ _FORBIDDEN_PREFIXES = ("http://", "https://", "ftp://", "file://")
37
+ """Prefixes that signal the caller passed a URL, not a filesystem path.
38
+
39
+ The diff resolver handles patch URLs at a future R3 layer; the file
40
+ resolver only accepts on-disk references. Rejecting URLs explicitly
41
+ keeps misuse loud instead of letting the audit step discover it later.
42
+ The check is case-insensitive."""
43
+
44
+
45
+ class FileResolverError(ValueError):
46
+ """Raised when a payload cannot be resolved into a file envelope."""
47
+
48
+
49
+ def build_envelope(path: str) -> Input:
50
+ """Return an :class:`Input` carrying the path + empty refinement slots.
51
+
52
+ Parameters
53
+ ----------
54
+ path:
55
+ The user-supplied path reference. The resolver normalises only by
56
+ stripping leading/trailing whitespace; case, separators, and the
57
+ relative-vs-absolute distinction are all preserved verbatim so the
58
+ downstream audit step reads exactly what the user wrote.
59
+
60
+ Returns
61
+ -------
62
+ Input
63
+ Envelope of shape
64
+ ``{"kind": "file", "data": {"path": <path>, "reconstructed_ac": [],
65
+ "assumptions": []}}``. The two empty lists are placeholders the
66
+ ``refine-prompt`` skill writes into on the rebound from ``refine``;
67
+ they are kept to preserve a single-shape envelope across all
68
+ prompt-like resolvers.
69
+
70
+ Raises
71
+ ------
72
+ FileResolverError
73
+ If ``path`` is not a string, is empty / whitespace-only, contains a
74
+ NUL byte (filesystem-illegal everywhere), or is a URL (use the
75
+ diff resolver for remote-PR / patch URLs in a future R3 phase).
76
+ """
77
+ if not isinstance(path, str):
78
+ raise FileResolverError(
79
+ f"path must be a string; got {type(path).__name__}",
80
+ )
81
+ stripped = path.strip()
82
+ if len(stripped) < _MIN_PATH_LEN:
83
+ raise FileResolverError(
84
+ "path is empty or whitespace-only — nothing to resolve",
85
+ )
86
+ if "\x00" in stripped:
87
+ raise FileResolverError(
88
+ "path contains a NUL byte; filesystem references must be "
89
+ "NUL-free",
90
+ )
91
+ lowered = stripped.lower()
92
+ if any(lowered.startswith(prefix) for prefix in _FORBIDDEN_PREFIXES):
93
+ raise FileResolverError(
94
+ f"path looks like a URL ({stripped[:32]!r}); the file resolver "
95
+ "only accepts on-disk references — use the diff resolver for "
96
+ "PR or patch URLs",
97
+ )
98
+ # Normalise separators *only* on Windows-style backslashes so
99
+ # ``resources\\views\\foo.blade.php`` round-trips as POSIX. Native
100
+ # POSIX paths are returned untouched so the audit step's identity
101
+ # comparison against directory listings stays trivial.
102
+ normalised = stripped.replace("\\", "/") if os.sep == "/" else stripped
103
+ return Input(
104
+ kind=KIND,
105
+ data={
106
+ "path": normalised,
107
+ "reconstructed_ac": [],
108
+ "assumptions": [],
109
+ },
110
+ )
111
+
112
+
113
+ __all__ = ["KIND", "FileResolverError", "build_envelope"]
@@ -0,0 +1,90 @@
1
+ """Prompt resolver — wrap a raw user prompt as an :class:`Input` envelope.
2
+
3
+ The resolver is intentionally minimal. It accepts a raw string, validates
4
+ that it contains non-whitespace content, and returns
5
+ ``Input(kind="prompt", data={"raw": <text>, "reconstructed_ac": [],
6
+ "assumptions": []})``. The empty AC + assumptions lists are placeholders
7
+ that the ``refine-prompt`` skill (R2 Phase 3) fills in once the engine
8
+ runs the deterministic ``refine`` gate against the raw text.
9
+
10
+ Why split the resolver from the refiner:
11
+
12
+ - The resolver runs at command boundaries (the ``/work`` entrypoint
13
+ builds an envelope, then hands off to ``work_engine``). It must stay
14
+ side-effect-free and dependency-light so the command shell can call
15
+ it without touching the LLM-facing skill harness.
16
+ - The refiner runs inside the dispatcher loop and is allowed to halt
17
+ (medium-confidence assumptions report, low-confidence one-question
18
+ block) per :doc:`agents/contexts/implement-ticket-flow.md`. That
19
+ control-flow surface does not belong in a resolver.
20
+
21
+ Future R3 resolvers (``diff``, ``file``) follow the same pattern: thin
22
+ normalisation, no interpretation, one envelope shape per kind.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ from ..state import Input
27
+
28
+ KIND = "prompt"
29
+ """Wire value carried in :attr:`work_engine.state.Input.kind`."""
30
+
31
+ _MIN_PROMPT_LEN = 1
32
+ """Minimum non-whitespace character count for a resolvable prompt.
33
+
34
+ Set to 1 by design — the resolver is not a quality gate. It only
35
+ rejects literally empty / whitespace-only payloads (which cannot be
36
+ distinguished from missing input). Quality judgment (is the prompt
37
+ clear? is it tractable?) is the ``refine-prompt`` skill's job, surfaced
38
+ through the confidence band, not the resolver's."""
39
+
40
+
41
+ class PromptResolverError(ValueError):
42
+ """Raised when a payload cannot be resolved into a prompt envelope."""
43
+
44
+
45
+ def build_envelope(raw: str) -> Input:
46
+ """Return an :class:`Input` carrying the raw prompt + empty refinement slots.
47
+
48
+ Parameters
49
+ ----------
50
+ raw:
51
+ The user-supplied prompt text. Leading/trailing whitespace is
52
+ preserved verbatim — the refiner reads the original casing and
53
+ spacing when scoring goal clarity, so collapsing whitespace
54
+ here would lose signal.
55
+
56
+ Returns
57
+ -------
58
+ Input
59
+ Envelope of shape
60
+ ``{"kind": "prompt", "data": {"raw": <raw>,
61
+ "reconstructed_ac": [], "assumptions": []}}``. The two empty
62
+ lists are placeholders the ``refine-prompt`` skill writes into
63
+ on the rebound from the ``refine`` step.
64
+
65
+ Raises
66
+ ------
67
+ PromptResolverError
68
+ If ``raw`` is not a string, or contains no non-whitespace
69
+ characters (the only case where the envelope would carry no
70
+ actionable signal at all).
71
+ """
72
+ if not isinstance(raw, str):
73
+ raise PromptResolverError(
74
+ f"prompt must be a string; got {type(raw).__name__}",
75
+ )
76
+ if len(raw.strip()) < _MIN_PROMPT_LEN:
77
+ raise PromptResolverError(
78
+ "prompt is empty or whitespace-only — nothing to resolve",
79
+ )
80
+ return Input(
81
+ kind=KIND,
82
+ data={
83
+ "raw": raw,
84
+ "reconstructed_ac": [],
85
+ "assumptions": [],
86
+ },
87
+ )
88
+
89
+
90
+ __all__ = ["KIND", "PromptResolverError", "build_envelope"]
@@ -0,0 +1,14 @@
1
+ """Scoring helpers for prompt-driven execution.
2
+
3
+ The package owns deterministic, heuristic-based scorers that the
4
+ ``refine`` step consults when ``input.kind="prompt"``. Splitting them
5
+ from the dispatcher keeps the rubric replaceable and easy to tune in
6
+ isolation: the dispatcher only reads the resulting band, never the
7
+ per-dimension breakdown.
8
+
9
+ R2 Phase 3 ships the first scorer (:mod:`work_engine.scoring.confidence`).
10
+ Future scorers (estimate-effort, risk-band, UI-readiness) plug in here
11
+ with the same shape — pure function in, immutable result out, no LLM
12
+ calls, no side effects.
13
+ """
14
+ from __future__ import annotations
@@ -0,0 +1,300 @@
1
+ """Confidence scoring for prompt-driven execution (R2 Phase 3 Step 2).
2
+
3
+ The scorer judges whether a reconstructed prompt is good enough for the
4
+ ``work_engine`` to proceed silently, halt for confirmation, or refuse to
5
+ plan. It is heuristic-only — no LLM calls — so the same prompt produces
6
+ the same score across replays and the freeze-guard harness can pin
7
+ expectations.
8
+
9
+ Rubric (each dimension 0–2, total / 10 → band):
10
+
11
+ - ``goal_clarity`` — does the raw prompt name a single action verb +
12
+ object + observable result?
13
+ - ``scope_boundary`` — does the prompt name a file, class, module, or
14
+ domain that bounds the change?
15
+ - ``ac_evidence`` — did the refiner produce concrete, anchored
16
+ acceptance criteria?
17
+ - ``stack_data`` — does the prompt imply stack / data / migration work
18
+ *and* identify the touched surface? (penalty if implied + unspecified)
19
+ - ``reversibility`` — would a wrong reconstruction be cheaply rollback-
20
+ able?
21
+
22
+ Bands (per ``agents/roadmaps/archive/road-to-prompt-driven-execution.md``):
23
+
24
+ - ``high`` — score ≥ 0.8 → dispatcher proceeds silently
25
+ - ``medium`` — 0.5 ≤ score < 0.8 → assumptions-report halt
26
+ - ``low`` — score < 0.5 → one-question halt
27
+
28
+ The scorer is the single source of truth for both the rubric and the
29
+ band thresholds. Documentation (SKILL.md, ADR, contexts) cite this
30
+ module — they do not re-derive the values.
31
+ """
32
+ from __future__ import annotations
33
+
34
+ import re
35
+ from dataclasses import dataclass, field
36
+
37
+ # --- Public types ------------------------------------------------------
38
+
39
+ DIMENSION_NAMES: tuple[str, ...] = (
40
+ "goal_clarity",
41
+ "scope_boundary",
42
+ "ac_evidence",
43
+ "stack_data",
44
+ "reversibility",
45
+ )
46
+ """Canonical dimension order. Matches the roadmap rubric and is the
47
+ order :class:`ConfidenceScore.dimensions` is rendered in by callers."""
48
+
49
+ MAX_PER_DIMENSION = 2
50
+ """Per-dimension ceiling. Five dimensions × 2 = 10 = full score."""
51
+
52
+ BAND_HIGH_MIN = 0.8
53
+ BAND_MEDIUM_MIN = 0.5
54
+ """Band thresholds. Inclusive on the lower bound, exclusive on the upper.
55
+
56
+ A score of exactly 0.8 lands in ``high`` (per roadmap: ``high ≥ 0.8``);
57
+ exactly 0.5 lands in ``medium``. Anything below 0.5 is ``low``."""
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class ConfidenceScore:
62
+ """Immutable result of one scoring pass.
63
+
64
+ ``frozen=True`` so callers cannot accidentally mutate a band after
65
+ the dispatcher has already routed on it. The dispatcher reads
66
+ ``band`` and (for low-band halts) ``reasons``; the per-dimension
67
+ breakdown is logged in the delivery report for replay traceability.
68
+ """
69
+
70
+ band: str
71
+ score: float
72
+ dimensions: dict[str, int]
73
+ reasons: list[str] = field(default_factory=list)
74
+ ui_intent: bool = False
75
+
76
+
77
+ # --- Heuristic vocabularies -------------------------------------------
78
+
79
+ _ACTION_VERBS: frozenset[str] = frozenset({
80
+ "add", "build", "create", "implement", "introduce", "write",
81
+ "fix", "patch", "repair", "resolve",
82
+ "refactor", "rename", "extract", "inline", "split",
83
+ "remove", "delete", "drop", "purge",
84
+ "update", "upgrade", "bump", "migrate",
85
+ "optimize", "speed", "improve", "tune",
86
+ "document", "describe", "explain",
87
+ "test", "validate", "verify",
88
+ "expose", "publish", "deprecate",
89
+ "configure", "wire", "connect",
90
+ })
91
+
92
+ _DOMAIN_NOUNS: frozenset[str] = frozenset({
93
+ "auth", "authentication", "authorization", "login", "logout", "signup",
94
+ "user", "users", "account", "profile",
95
+ "dashboard", "search", "checkout", "cart", "billing", "payment",
96
+ "admin", "settings", "config",
97
+ "api", "endpoint", "webhook", "queue", "job", "worker",
98
+ "frontend", "backend", "ui", "view", "page", "form",
99
+ "database", "migration", "schema",
100
+ "report", "export", "import",
101
+ })
102
+
103
+ _STACK_DATA_KEYWORDS: frozenset[str] = frozenset({
104
+ "migration", "schema", "table", "column", "index",
105
+ "database", "db", "postgres", "mysql", "mariadb", "sqlite",
106
+ "redis", "cache", "queue",
107
+ "dependency", "package", "library", "framework", "upgrade",
108
+ "breaking change", "deprecate", "api version",
109
+ "deploy", "release",
110
+ })
111
+
112
+ _IRREVERSIBLE_KEYWORDS: frozenset[str] = frozenset({
113
+ "drop ", "delete ", "purge", "wipe", "truncate",
114
+ "send email", "charge", "refund", "billing", "payment", "money",
115
+ "production data", "live database", "broadcast",
116
+ })
117
+
118
+ _UI_KEYWORDS: frozenset[str] = frozenset({
119
+ "redesign", "color", "colour", "css", "tailwind", "layout",
120
+ "font", "spacing", "padding", "margin", "button", "icon",
121
+ "responsive", "mobile view", "look", "polish", "prettier",
122
+ "theme", "dark mode", "light mode",
123
+ })
124
+
125
+ _FILE_PATH_RE = re.compile(
126
+ r"`[^`]+`" # backtick-wrapped tokens
127
+ r"|[\w./-]+\.(?:py|php|ts|tsx|js|jsx|vue|blade\.php|sql|yml|yaml|json|md)"
128
+ r"|[A-Z][\w]+(?:\\[A-Z][\w]+)+" # PHP namespaces (Foo\Bar\Baz)
129
+ r"|[A-Z][a-zA-Z]+::[a-zA-Z]+" # PHP static call (Foo::bar)
130
+ r"|[a-z]+(?:[A-Z][a-z]+){2,}", # camelCase identifiers w/ ≥2 humps
131
+ )
132
+
133
+
134
+ # --- Public API --------------------------------------------------------
135
+
136
+ def score(
137
+ *,
138
+ raw: str,
139
+ ac: list[str] | None = None,
140
+ assumptions: list[str] | None = None,
141
+ ) -> ConfidenceScore:
142
+ """Score a reconstructed prompt and return the band + rubric breakdown.
143
+
144
+ Parameters
145
+ ----------
146
+ raw:
147
+ The original user prompt text. Pre-stripped or not; the scorer
148
+ normalises whitespace internally.
149
+ ac:
150
+ Reconstructed acceptance criteria from the ``refine-prompt``
151
+ skill. ``None`` is treated as an empty list (no AC produced).
152
+ assumptions:
153
+ Inferred assumptions surfaced by the refiner. Currently
154
+ informational — the rubric does not penalise assumption count
155
+ directly because medium-band halts are the resolution surface.
156
+
157
+ Returns
158
+ -------
159
+ ConfidenceScore
160
+ Frozen dataclass carrying the band, normalised score, the
161
+ five-dimension breakdown, human-readable reasons, and a UI-intent
162
+ flag for R3 routing.
163
+ """
164
+ text = (raw or "").strip()
165
+ text_lower = text.lower()
166
+ ac_list = list(ac or ())
167
+
168
+ g_val, g_reason = _score_goal_clarity(text, text_lower)
169
+ s_val, s_reason = _score_scope_boundary(text, text_lower)
170
+ e_val, e_reason = _score_acceptance_evidence(ac_list)
171
+ k_val, k_reason = _score_stack_data(text_lower)
172
+ r_val, r_reason = _score_reversibility(text_lower)
173
+
174
+ dimensions = {
175
+ "goal_clarity": g_val,
176
+ "scope_boundary": s_val,
177
+ "ac_evidence": e_val,
178
+ "stack_data": k_val,
179
+ "reversibility": r_val,
180
+ }
181
+ total = sum(dimensions.values())
182
+ norm = round(total / (MAX_PER_DIMENSION * len(DIMENSION_NAMES)), 4)
183
+ return ConfidenceScore(
184
+ band=_band_from_score(norm),
185
+ score=norm,
186
+ dimensions=dimensions,
187
+ reasons=[g_reason, s_reason, e_reason, k_reason, r_reason],
188
+ ui_intent=_detect_ui_intent(text_lower),
189
+ )
190
+
191
+
192
+ # --- Band mapping ------------------------------------------------------
193
+
194
+ def _band_from_score(score_value: float) -> str:
195
+ """Map a normalised score to one of ``high`` / ``medium`` / ``low``.
196
+
197
+ Inclusive on the lower bound to match the roadmap contract
198
+ (``high \u2265 0.8``); a score of exactly ``0.8`` lands in ``high``,
199
+ exactly ``0.5`` in ``medium``, anything below ``0.5`` in ``low``.
200
+ """
201
+ if score_value >= BAND_HIGH_MIN:
202
+ return "high"
203
+ if score_value >= BAND_MEDIUM_MIN:
204
+ return "medium"
205
+ return "low"
206
+
207
+
208
+ # --- Per-dimension scorers --------------------------------------------
209
+
210
+ def _score_goal_clarity(text: str, text_lower: str) -> tuple[int, str]:
211
+ """Score whether the prompt names a single, observable outcome."""
212
+ if not text:
213
+ return 0, "goal_clarity=0: empty prompt"
214
+ has_verb = any(
215
+ re.search(rf"\b{re.escape(v)}\w*\b", text_lower)
216
+ for v in _ACTION_VERBS
217
+ )
218
+ is_question = text.rstrip().endswith("?")
219
+ word_count = len(text.split())
220
+ conjunction_split = bool(re.search(r"\b(and then|and also|plus)\b", text_lower))
221
+
222
+ if has_verb and not is_question and 4 <= word_count <= 40 and not conjunction_split:
223
+ return 2, "goal_clarity=2: action verb + bounded length + single outcome"
224
+ if has_verb and not is_question:
225
+ if conjunction_split:
226
+ return 1, "goal_clarity=1: verb present but multiple outcomes joined"
227
+ return 1, "goal_clarity=1: verb present but length is borderline"
228
+ if is_question:
229
+ return 0, "goal_clarity=0: prompt is a question, no executable verb"
230
+ return 0, "goal_clarity=0: no recognisable action verb"
231
+
232
+
233
+ def _score_scope_boundary(text: str, text_lower: str) -> tuple[int, str]:
234
+ """Score whether the prompt bounds the change to a concrete surface."""
235
+ has_path = bool(_FILE_PATH_RE.search(text))
236
+ has_domain = any(
237
+ re.search(rf"\b{re.escape(n)}\b", text_lower)
238
+ for n in _DOMAIN_NOUNS
239
+ )
240
+ if has_path:
241
+ return 2, "scope_boundary=2: explicit file/class/identifier named"
242
+ if has_domain:
243
+ return 1, "scope_boundary=1: domain noun present, no concrete path"
244
+ return 0, "scope_boundary=0: no file or domain anchor"
245
+
246
+
247
+ def _score_acceptance_evidence(ac: list[str]) -> tuple[int, str]:
248
+ """Score the reconstructed AC list produced by the refiner."""
249
+ n = len(ac)
250
+ if n == 0:
251
+ return 0, "ac_evidence=0: no acceptance criteria reconstructed"
252
+ anchored_signals = ("should", "must", "given", "when", "then", "expect")
253
+ anchored = sum(
254
+ 1 for line in ac
255
+ if any(s in line.lower() for s in anchored_signals)
256
+ )
257
+ if n >= 3 and anchored >= 2:
258
+ return 2, f"ac_evidence=2: {n} criteria, {anchored} anchored"
259
+ if n >= 1:
260
+ return 1, f"ac_evidence=1: {n} criteria, {anchored} anchored"
261
+ return 0, "ac_evidence=0: empty AC list"
262
+
263
+
264
+ def _score_stack_data(text_lower: str) -> tuple[int, str]:
265
+ """Penalise stack/data work that is implied but not bounded."""
266
+ implies_stack = any(k in text_lower for k in _STACK_DATA_KEYWORDS)
267
+ if not implies_stack:
268
+ return 2, "stack_data=2: prompt is behavioural, no stack/data signal"
269
+ has_target = bool(re.search(
270
+ r"\b(table|column|index|file|migration)\s+[`\"\w]",
271
+ text_lower,
272
+ ))
273
+ if has_target:
274
+ return 2, "stack_data=2: stack/data work named with explicit target"
275
+ return 0, "stack_data=0: stack/data work implied without target"
276
+
277
+
278
+ def _score_reversibility(text_lower: str) -> tuple[int, str]:
279
+ """Score how cheaply a wrong reconstruction could be rolled back."""
280
+ if any(k in text_lower for k in _IRREVERSIBLE_KEYWORDS):
281
+ return 0, "reversibility=0: irreversible keyword detected"
282
+ config_signals = ("config", "env", "secret", ".env", "deploy")
283
+ if any(s in text_lower for s in config_signals):
284
+ return 1, "reversibility=1: config/env surface, partial rollback cost"
285
+ return 2, "reversibility=2: code-only change, cheap to revert"
286
+
287
+
288
+ def _detect_ui_intent(text_lower: str) -> bool:
289
+ """Flag prompts that read as UI work for R3 routing."""
290
+ return any(k in text_lower for k in _UI_KEYWORDS)
291
+
292
+
293
+ __all__ = [
294
+ "BAND_HIGH_MIN",
295
+ "BAND_MEDIUM_MIN",
296
+ "ConfidenceScore",
297
+ "DIMENSION_NAMES",
298
+ "MAX_PER_DIMENSION",
299
+ "score",
300
+ ]
@@ -0,0 +1,31 @@
1
+ """Stack detection for the UI directive set (R3 Phase 1).
2
+
3
+ The :mod:`work_engine.stack.detect` module sniffs the project root for
4
+ manifest signals (``composer.json``, ``package.json``, ``components.json``)
5
+ and labels the frontend stack one of:
6
+
7
+ - ``blade-livewire-flux`` — Laravel + Livewire + Flux UI components
8
+ - ``react-shadcn`` — React + Radix-based shadcn/ui primitives
9
+ - ``vue`` — Vue.js (3.x or 2.x)
10
+ - ``plain`` — none of the above; Tailwind + raw HTML/Blade fallback
11
+
12
+ The label feeds the dispatcher's UI directive set (``directives/ui/apply.py``)
13
+ to pick the right implementation skill.
14
+
15
+ Detection is **manifest-only** by design — we do not read JS/PHP source
16
+ files because:
17
+
18
+ 1. Manifests are deterministic and cheap to parse.
19
+ 2. Source-level signals (e.g. counting ``<x-flux::*`` calls) are noisy
20
+ on greenfield repos where audit will already emit ``greenfield=true``.
21
+ 3. Re-detect on manifest mtime change is sufficient — adding a stack
22
+ means editing a manifest, and we cache against that.
23
+
24
+ See ``agents/roadmaps/road-to-product-ui-track.md`` Phase 1 Step 1 for
25
+ the full heuristic table and the ``state.stack`` schema slice it feeds.
26
+ """
27
+ from __future__ import annotations
28
+
29
+ from . import detect
30
+
31
+ __all__ = ["detect"]