@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.
- package/.agent-src/commands/agent-handoff.md +4 -1
- package/.agent-src/commands/agent-status.md +3 -0
- package/.agent-src/commands/agents-audit.md +4 -0
- package/.agent-src/commands/agents-cleanup.md +6 -1
- package/.agent-src/commands/agents-prepare.md +3 -0
- package/.agent-src/commands/analyze-reference-repo.md +4 -0
- package/.agent-src/commands/bug-fix.md +7 -3
- package/.agent-src/commands/bug-investigate.md +4 -0
- package/.agent-src/commands/chat-history-checkpoint.md +126 -0
- package/.agent-src/commands/chat-history-clear.md +6 -1
- package/.agent-src/commands/chat-history-resume.md +7 -2
- package/.agent-src/commands/chat-history.md +7 -2
- package/.agent-src/commands/check-current-md.md +137 -0
- package/.agent-src/commands/commit-in-chunks.md +118 -0
- package/.agent-src/commands/commit.md +4 -0
- package/.agent-src/commands/compress.md +37 -2
- package/.agent-src/commands/context-create.md +4 -0
- package/.agent-src/commands/context-refactor.md +4 -0
- package/.agent-src/commands/copilot-agents-init.md +3 -0
- package/.agent-src/commands/copilot-agents-optimize.md +3 -0
- package/.agent-src/commands/create-pr-description.md +4 -0
- package/.agent-src/commands/create-pr.md +4 -0
- package/.agent-src/commands/do-and-judge.md +4 -1
- package/.agent-src/commands/do-in-steps.md +3 -0
- package/.agent-src/commands/e2e-heal.md +4 -0
- package/.agent-src/commands/e2e-plan.md +4 -0
- package/.agent-src/commands/estimate-ticket.md +4 -1
- package/.agent-src/commands/feature-dev.md +4 -0
- package/.agent-src/commands/feature-explore.md +4 -0
- package/.agent-src/commands/feature-plan.md +4 -0
- package/.agent-src/commands/feature-refactor.md +4 -0
- package/.agent-src/commands/feature-roadmap.md +6 -0
- package/.agent-src/commands/fix-ci.md +4 -0
- package/.agent-src/commands/fix-portability.md +5 -2
- package/.agent-src/commands/fix-pr-bot-comments.md +4 -0
- package/.agent-src/commands/fix-pr-comments.md +4 -0
- package/.agent-src/commands/fix-pr-developer-comments.md +4 -0
- package/.agent-src/commands/fix-references.md +3 -0
- package/.agent-src/commands/fix-seeder.md +4 -0
- package/.agent-src/commands/implement-ticket.md +39 -13
- package/.agent-src/commands/jira-ticket.md +4 -0
- package/.agent-src/commands/judge.md +3 -0
- package/.agent-src/commands/memory-add.md +5 -3
- package/.agent-src/commands/memory-full.md +5 -2
- package/.agent-src/commands/memory-promote.md +7 -6
- package/.agent-src/commands/mode.md +3 -0
- package/.agent-src/commands/module-create.md +4 -0
- package/.agent-src/commands/module-explore.md +4 -0
- package/.agent-src/commands/onboard.md +33 -0
- package/.agent-src/commands/optimize-agents.md +4 -0
- package/.agent-src/commands/optimize-augmentignore.md +12 -0
- package/.agent-src/commands/optimize-rtk-filters.md +3 -0
- package/.agent-src/commands/optimize-skills.md +4 -0
- package/.agent-src/commands/override-create.md +4 -0
- package/.agent-src/commands/override-manage.md +4 -0
- package/.agent-src/commands/package-reset.md +3 -0
- package/.agent-src/commands/package-test.md +3 -0
- package/.agent-src/commands/prepare-for-review.md +4 -0
- package/.agent-src/commands/project-analyze.md +4 -0
- package/.agent-src/commands/project-health.md +4 -0
- package/.agent-src/commands/propose-memory.md +6 -8
- package/.agent-src/commands/quality-fix.md +4 -0
- package/.agent-src/commands/refine-ticket.md +12 -7
- package/.agent-src/commands/review-changes.md +39 -8
- package/.agent-src/commands/review-routing.md +4 -0
- package/.agent-src/commands/roadmap-create.md +18 -0
- package/.agent-src/commands/roadmap-execute.md +14 -1
- package/.agent-src/commands/rule-compliance-audit.md +4 -0
- package/.agent-src/commands/set-cost-profile.md +11 -0
- package/.agent-src/commands/sync-agent-settings.md +12 -0
- package/.agent-src/commands/sync-gitignore.md +3 -0
- package/.agent-src/commands/tests-create.md +4 -0
- package/.agent-src/commands/tests-execute.md +6 -3
- package/.agent-src/commands/threat-model.md +4 -0
- package/.agent-src/commands/update-form-request-messages.md +4 -0
- package/.agent-src/commands/upstream-contribute.md +4 -0
- package/.agent-src/commands/work.md +161 -0
- package/.agent-src/guidelines/agent-infra/engineering-memory-data-format.md +2 -6
- package/.agent-src/guidelines/agent-infra/layered-settings.md +0 -1
- package/.agent-src/guidelines/agent-infra/memory-access.md +0 -7
- package/.agent-src/guidelines/agent-infra/role-contracts.md +2 -4
- package/.agent-src/guidelines/agent-infra/self-improvement-pipeline.md +0 -1
- package/.agent-src/guidelines/php/patterns/strategy.md +180 -2
- package/.agent-src/personas/README.md +0 -1
- package/.agent-src/rules/artifact-drafting-protocol.md +7 -2
- package/.agent-src/rules/artifact-engagement-recording.md +133 -0
- package/.agent-src/rules/ask-when-uncertain.md +18 -13
- package/.agent-src/rules/augment-portability.md +64 -37
- package/.agent-src/rules/autonomous-execution.md +158 -0
- package/.agent-src/rules/chat-history-cadence.md +109 -0
- package/.agent-src/rules/chat-history-ownership.md +123 -0
- package/.agent-src/rules/chat-history-visibility.md +96 -0
- package/.agent-src/rules/cli-output-handling.md +27 -4
- package/.agent-src/rules/command-suggestion.md +134 -0
- package/.agent-src/rules/commit-policy.md +109 -0
- package/.agent-src/rules/direct-answers.md +114 -0
- package/.agent-src/rules/docs-sync.md +36 -0
- package/.agent-src/rules/downstream-changes.md +10 -9
- package/.agent-src/rules/improve-before-implement.md +9 -6
- package/.agent-src/rules/language-and-tone.md +85 -6
- package/.agent-src/rules/non-destructive-by-default.md +117 -0
- package/.agent-src/rules/package-ci-checks.md +4 -0
- package/.agent-src/rules/preservation-guard.md +20 -0
- package/.agent-src/rules/roadmap-progress-sync.md +159 -27
- package/.agent-src/rules/role-mode-adherence.md +1 -1
- package/.agent-src/rules/scope-control.md +42 -1
- package/.agent-src/rules/size-enforcement.md +2 -3
- package/.agent-src/rules/skill-quality.md +3 -8
- package/.agent-src/rules/ui-audit-before-build.md +106 -0
- package/.agent-src/rules/user-interaction.md +107 -51
- package/.agent-src/scripts/update_roadmap_progress.py +73 -9
- package/.agent-src/skills/blade-ui/SKILL.md +47 -3
- package/.agent-src/skills/command-routing/SKILL.md +32 -0
- package/.agent-src/skills/command-writing/SKILL.md +52 -2
- package/.agent-src/skills/description-assist/SKILL.md +21 -0
- package/.agent-src/skills/estimate-ticket/SKILL.md +0 -1
- package/.agent-src/skills/existing-ui-audit/SKILL.md +202 -0
- package/.agent-src/skills/fe-design/SKILL.md +78 -61
- package/.agent-src/skills/file-editor/SKILL.md +9 -0
- package/.agent-src/skills/finishing-a-development-branch/SKILL.md +4 -0
- package/.agent-src/skills/flux/SKILL.md +31 -4
- package/.agent-src/skills/guideline-writing/SKILL.md +24 -2
- package/.agent-src/skills/learning-to-rule-or-skill/SKILL.md +51 -9
- package/.agent-src/skills/livewire/SKILL.md +49 -4
- package/.agent-src/skills/md-language-check/SKILL.md +103 -0
- package/.agent-src/skills/php-coder/SKILL.md +24 -0
- package/.agent-src/skills/react-shadcn-ui/SKILL.md +121 -0
- package/.agent-src/skills/refine-prompt/SKILL.md +220 -0
- package/.agent-src/skills/refine-ticket/SKILL.md +32 -28
- package/.agent-src/skills/roadmap-management/SKILL.md +24 -11
- package/.agent-src/skills/rule-writing/SKILL.md +23 -1
- package/.agent-src/skills/skill-writing/SKILL.md +3 -5
- package/.agent-src/skills/upstream-contribute/SKILL.md +3 -3
- package/.agent-src/skills/using-git-worktrees/SKILL.md +3 -1
- package/.agent-src/templates/AGENTS.md +24 -6
- package/.agent-src/templates/agent-settings.md +149 -0
- package/.agent-src/templates/roadmaps.md +11 -4
- package/.agent-src/templates/scripts/implement_ticket/__init__.py +63 -26
- package/.agent-src/templates/scripts/implement_ticket/__main__.py +8 -2
- package/.agent-src/templates/scripts/memory_lookup.py +1 -1
- package/.agent-src/templates/scripts/telemetry/__init__.py +42 -0
- package/.agent-src/templates/scripts/telemetry/aggregator.py +154 -0
- package/.agent-src/templates/scripts/telemetry/boundary.py +171 -0
- package/.agent-src/templates/scripts/telemetry/engagement.py +238 -0
- package/.agent-src/templates/scripts/telemetry/report_renderer.py +170 -0
- package/.agent-src/templates/scripts/telemetry/settings.py +112 -0
- package/.agent-src/templates/scripts/telemetry_record.py +166 -0
- package/.agent-src/templates/scripts/telemetry_report.py +161 -0
- package/.agent-src/templates/scripts/telemetry_status.py +142 -0
- package/.agent-src/templates/scripts/work_engine/__init__.py +58 -0
- package/.agent-src/templates/scripts/work_engine/__main__.py +9 -0
- package/.agent-src/templates/scripts/work_engine/cli.py +195 -0
- package/.agent-src/templates/scripts/work_engine/cli_args.py +116 -0
- package/.agent-src/templates/scripts/{implement_ticket → work_engine}/delivery_state.py +10 -3
- package/.agent-src/templates/scripts/work_engine/directives/__init__.py +33 -0
- package/.agent-src/templates/scripts/work_engine/directives/backend/__init__.py +98 -0
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/analyze.py +1 -1
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/implement.py +3 -3
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/memory.py +2 -2
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/plan.py +2 -2
- package/.agent-src/templates/scripts/work_engine/directives/backend/refine.py +396 -0
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/report.py +37 -5
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/test.py +2 -2
- package/.agent-src/templates/scripts/{implement_ticket/steps → work_engine/directives/backend}/verify.py +2 -2
- package/.agent-src/templates/scripts/work_engine/directives/mixed/__init__.py +116 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/contract.py +254 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/stitch.py +229 -0
- package/.agent-src/templates/scripts/work_engine/directives/mixed/ui.py +231 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/__init__.py +113 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/_passthrough.py +44 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/apply.py +241 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/audit.py +414 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/design.py +335 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/polish.py +510 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui/review.py +468 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/__init__.py +119 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/_skipped.py +37 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/apply.py +165 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/refine.py +66 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/report.py +62 -0
- package/.agent-src/templates/scripts/work_engine/directives/ui_trivial/test.py +115 -0
- package/.agent-src/templates/scripts/work_engine/dispatcher.py +331 -0
- package/.agent-src/templates/scripts/work_engine/emitters.py +43 -0
- package/.agent-src/templates/scripts/work_engine/errors.py +19 -0
- package/.agent-src/templates/scripts/work_engine/hook_bootstrap.py +76 -0
- package/.agent-src/templates/scripts/work_engine/hooks/__init__.py +54 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/__init__.py +32 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/_chat_history_base.py +103 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_append.py +44 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_halt_append.py +42 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_heartbeat.py +50 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/chat_history_turn_check.py +49 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/directive_set_guard.py +53 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/halt_surface_audit.py +50 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/state_shape_validation.py +52 -0
- package/.agent-src/templates/scripts/work_engine/hooks/builtin/trace.py +84 -0
- package/.agent-src/templates/scripts/work_engine/hooks/context.py +66 -0
- package/.agent-src/templates/scripts/work_engine/hooks/events.py +44 -0
- package/.agent-src/templates/scripts/work_engine/hooks/exceptions.py +79 -0
- package/.agent-src/templates/scripts/work_engine/hooks/registry.py +60 -0
- package/.agent-src/templates/scripts/work_engine/hooks/runner.py +73 -0
- package/.agent-src/templates/scripts/work_engine/hooks/settings.py +141 -0
- package/.agent-src/templates/scripts/work_engine/input_builders.py +163 -0
- package/.agent-src/templates/scripts/work_engine/intent/__init__.py +47 -0
- package/.agent-src/templates/scripts/work_engine/intent/classify.py +280 -0
- package/.agent-src/templates/scripts/work_engine/migration/__init__.py +8 -0
- package/.agent-src/templates/scripts/work_engine/migration/v0_to_v1.py +231 -0
- package/.agent-src/templates/scripts/{implement_ticket → work_engine}/persona_policy.py +1 -1
- package/.agent-src/templates/scripts/work_engine/resolvers/__init__.py +22 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/diff.py +106 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/file.py +113 -0
- package/.agent-src/templates/scripts/work_engine/resolvers/prompt.py +90 -0
- package/.agent-src/templates/scripts/work_engine/scoring/__init__.py +14 -0
- package/.agent-src/templates/scripts/work_engine/scoring/confidence.py +300 -0
- package/.agent-src/templates/scripts/work_engine/stack/__init__.py +31 -0
- package/.agent-src/templates/scripts/work_engine/stack/detect.py +187 -0
- package/.agent-src/templates/scripts/work_engine/state.py +641 -0
- package/.agent-src/templates/scripts/work_engine/state_io.py +202 -0
- package/.claude-plugin/marketplace.json +105 -2
- package/AGENTS.md +38 -8
- package/CHANGELOG.md +609 -0
- package/README.md +136 -14
- package/config/agent-settings.template.yml +45 -0
- package/config/gitignore-block.txt +4 -0
- package/docs/MIGRATION.md +122 -0
- package/docs/architecture.md +111 -35
- package/docs/contracts/STABILITY.md +95 -0
- package/docs/contracts/adr-chat-history-split.md +132 -0
- package/docs/contracts/adr-command-suggestion.md +146 -0
- package/docs/contracts/adr-implement-ticket-runtime.md +122 -0
- package/docs/contracts/adr-product-ui-track.md +384 -0
- package/docs/contracts/adr-prompt-driven-execution.md +187 -0
- package/docs/contracts/agent-memory-contract.md +149 -0
- package/docs/contracts/artifact-engagement-flow.md +262 -0
- package/docs/contracts/command-clusters.md +126 -0
- package/docs/contracts/command-suggestion-flow.md +148 -0
- package/docs/contracts/implement-ticket-flow.md +628 -0
- package/docs/contracts/linear-ai-rules-inclusion.md +143 -0
- package/docs/contracts/linear-ai-three-layers.md +131 -0
- package/docs/contracts/rule-interactions.md +107 -0
- package/docs/contracts/rule-interactions.yml +142 -0
- package/docs/contracts/ui-stack-extension.md +236 -0
- package/docs/contracts/ui-track-flow.md +338 -0
- package/docs/development.md +1 -1
- package/docs/getting-started.md +3 -3
- package/docs/installation.md +124 -2
- package/docs/migrations/commands-1.15.0.md +112 -0
- package/docs/showcase.md +204 -0
- package/docs/ui-track-mental-model.md +121 -0
- package/package.json +1 -1
- package/scripts/agent-config +199 -0
- package/scripts/audit_cloud_compatibility.py +288 -0
- package/scripts/build_cloud_bundle.py +458 -0
- package/scripts/build_linear_digest.py +263 -0
- package/scripts/chat_history.py +796 -7
- package/scripts/check_compression.py +139 -0
- package/scripts/check_iron_law_prominence.py +143 -0
- package/scripts/check_md_language.py +159 -0
- package/scripts/check_portability.py +38 -0
- package/scripts/check_public_links.py +185 -0
- package/scripts/check_references.py +1 -0
- package/scripts/check_reply_consistency.py +140 -0
- package/scripts/command_suggester/__init__.py +51 -0
- package/scripts/command_suggester/cooldown.py +132 -0
- package/scripts/command_suggester/loader.py +70 -0
- package/scripts/command_suggester/match.py +180 -0
- package/scripts/command_suggester/rank.py +120 -0
- package/scripts/command_suggester/render.py +86 -0
- package/scripts/command_suggester/sanitize.py +113 -0
- package/scripts/command_suggester/settings.py +125 -0
- package/scripts/command_suggester/types.py +78 -0
- package/scripts/hooks/augment-chat-history.sh +56 -0
- package/scripts/install-hooks.sh +67 -0
- package/scripts/install.py +150 -33
- package/scripts/lint_marketplace.py +27 -0
- package/scripts/lint_no_new_atomic_commands.py +179 -0
- package/scripts/lint_rule_interactions.py +149 -0
- package/scripts/memory_lookup.py +1 -1
- package/scripts/migrate_command_suggestions.py +151 -0
- package/scripts/release.py +297 -64
- package/scripts/schemas/command.schema.json +41 -0
- package/scripts/skill_linter.py +81 -0
- package/scripts/sync_agent_settings.py +42 -12
- package/scripts/update_counts.py +10 -0
- package/templates/consumer-settings/augment-cli-hooks.json +54 -0
- package/templates/consumer-settings/claude-settings.json +55 -1
- package/.agent-src/rules/chat-history.md +0 -171
- package/.agent-src/templates/scripts/implement_ticket/cli.py +0 -171
- package/.agent-src/templates/scripts/implement_ticket/dispatcher.py +0 -134
- package/.agent-src/templates/scripts/implement_ticket/steps/__init__.py +0 -49
- package/.agent-src/templates/scripts/implement_ticket/steps/refine.py +0 -140
|
@@ -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:`docs/contracts/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"]
|