@deftai/directive-content 0.55.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/.agents/skills/deft/SKILL.md +6 -0
- package/.agents/skills/deft-directive-article-review/SKILL.md +11 -0
- package/.agents/skills/deft-directive-build/SKILL.md +10 -0
- package/.agents/skills/deft-directive-cost/SKILL.md +12 -0
- package/.agents/skills/deft-directive-debug/SKILL.md +13 -0
- package/.agents/skills/deft-directive-gh-arch/SKILL.md +11 -0
- package/.agents/skills/deft-directive-gh-slice/SKILL.md +10 -0
- package/.agents/skills/deft-directive-glossary/SKILL.md +10 -0
- package/.agents/skills/deft-directive-interview/SKILL.md +9 -0
- package/.agents/skills/deft-directive-pre-pr/SKILL.md +9 -0
- package/.agents/skills/deft-directive-refinement/SKILL.md +10 -0
- package/.agents/skills/deft-directive-release/SKILL.md +12 -0
- package/.agents/skills/deft-directive-review-cycle/SKILL.md +10 -0
- package/.agents/skills/deft-directive-setup/SKILL.md +10 -0
- package/.agents/skills/deft-directive-swarm/SKILL.md +10 -0
- package/.agents/skills/deft-directive-sync/SKILL.md +9 -0
- package/.agents/skills/deft-directive-triage/SKILL.md +9 -0
- package/.agents/skills/deft-directive-write-skill/SKILL.md +9 -0
- package/LICENSE.md +22 -0
- package/QUICK-START.md +167 -0
- package/UPGRADING.md +517 -0
- package/coding/build-output.md +28 -0
- package/coding/coding.md +235 -0
- package/coding/debugging.md +110 -0
- package/coding/holzmann.md +96 -0
- package/coding/hygiene.md +127 -0
- package/coding/security.md +158 -0
- package/coding/testing.md +162 -0
- package/coding/toolchain.md +44 -0
- package/commands.md +300 -0
- package/context/context.md +57 -0
- package/context/deterministic-split.md +67 -0
- package/context/examples.md +26 -0
- package/context/fractal-summaries.md +69 -0
- package/context/long-horizon.md +46 -0
- package/context/spec-deltas.md +177 -0
- package/context/tool-design.md +34 -0
- package/context/working-memory.md +62 -0
- package/contracts/boundary-maps.md +65 -0
- package/contracts/deterministic-questions.md +59 -0
- package/contracts/hierarchy.md +77 -0
- package/conventions/content-manifest.json +399 -0
- package/conventions/machine-generated-banner.md +130 -0
- package/conventions/references.md +120 -0
- package/conventions/rule-ownership.json +382 -0
- package/conventions/task-caching.md +43 -0
- package/conventions/vbrief-filenames.md +70 -0
- package/deployments/README.md +37 -0
- package/deployments/agentuity/README.md +138 -0
- package/deployments/agentuity/via-cli.md +380 -0
- package/deployments/agentuity/via-cloud.md +425 -0
- package/deployments/agentuity/via-github-actions.md +664 -0
- package/deployments/agentuity/via-gravity-network.md +606 -0
- package/deployments/agentuity/via-vpc.md +607 -0
- package/deployments/aws/README.md +38 -0
- package/deployments/aws/via-app-runner.md +612 -0
- package/deployments/aws/via-ecs-fargate.md +561 -0
- package/deployments/aws/via-elastic-beanstalk.md +628 -0
- package/deployments/aws/via-lambda.md +649 -0
- package/deployments/azure/README.md +37 -0
- package/deployments/azure/via-aks.md +390 -0
- package/deployments/azure/via-app-service.md +564 -0
- package/deployments/azure/via-container-apps.md +599 -0
- package/deployments/azure/via-functions.md +552 -0
- package/deployments/cloud-gov/README.md +63 -0
- package/deployments/cloud-gov/agents/compliance-docs.md +154 -0
- package/deployments/cloud-gov/agents.md +39 -0
- package/deployments/cloud-gov/cicd.md +64 -0
- package/deployments/cloud-gov/deployment.md +150 -0
- package/deployments/cloud-gov/logging.md +43 -0
- package/deployments/cloud-gov/manifest.md +121 -0
- package/deployments/cloud-gov/overview.md +58 -0
- package/deployments/cloud-gov/security.md +46 -0
- package/deployments/cloud-gov/services.md +72 -0
- package/deployments/cloud-gov/upstream/README.md +18 -0
- package/deployments/cloudflare/README.md +33 -0
- package/deployments/cloudflare/via-dashboard.md +83 -0
- package/deployments/cloudflare/via-git.md +90 -0
- package/deployments/cloudflare/via-github-actions.md +185 -0
- package/deployments/cloudflare/via-terraform.md +157 -0
- package/deployments/cloudflare/via-wrangler.md +165 -0
- package/deployments/fly-io/README.md +37 -0
- package/deployments/fly-io/via-dockerfile.md +648 -0
- package/deployments/fly-io/via-flyctl.md +653 -0
- package/deployments/fly-io/via-github-actions.md +695 -0
- package/deployments/fly-io/via-multi-region.md +598 -0
- package/deployments/google/README.md +34 -0
- package/deployments/google/via-app-engine.md +42 -0
- package/deployments/google/via-cloud-functions.md +23 -0
- package/deployments/google/via-cloud-run.md +330 -0
- package/deployments/google/via-gke.md +23 -0
- package/deployments/netlify/README.md +99 -0
- package/deployments/netlify/via-cli.md +17 -0
- package/deployments/netlify/via-functions.md +19 -0
- package/deployments/netlify/via-git.md +25 -0
- package/deployments/vercel/README.md +90 -0
- package/deployments/vercel/via-api.md +16 -0
- package/deployments/vercel/via-cli.md +17 -0
- package/deployments/vercel/via-git.md +24 -0
- package/docs/BROWNFIELD.md +179 -0
- package/docs/getting-started.md +137 -0
- package/docs/good-agents-md.md +137 -0
- package/events/README.md +89 -0
- package/events/event-record.schema.json +26 -0
- package/events/registry.json +166 -0
- package/events/registry.schema.json +71 -0
- package/glossary.md +145 -0
- package/incidents/README.md +81 -0
- package/incidents/_template.md +38 -0
- package/interfaces/cli.md +104 -0
- package/interfaces/rest.md +212 -0
- package/interfaces/tui.md +242 -0
- package/interfaces/web.md +123 -0
- package/languages/6502-DASM.md +132 -0
- package/languages/c.md +235 -0
- package/languages/commands.md +23 -0
- package/languages/cpp.md +132 -0
- package/languages/csharp.md +259 -0
- package/languages/dart.md +183 -0
- package/languages/delphi.md +218 -0
- package/languages/elixir.md +208 -0
- package/languages/go.md +78 -0
- package/languages/java.md +278 -0
- package/languages/javascript.md +163 -0
- package/languages/julia.md +175 -0
- package/languages/kotlin.md +193 -0
- package/languages/markdown.md +168 -0
- package/languages/mermaid.md +146 -0
- package/languages/officejs.md +392 -0
- package/languages/python.md +209 -0
- package/languages/r.md +163 -0
- package/languages/rust.md +216 -0
- package/languages/sql.md +216 -0
- package/languages/swift.md +153 -0
- package/languages/typescript.md +132 -0
- package/languages/vba.md +279 -0
- package/languages/vhdl.md +180 -0
- package/languages/visual-basic.md +151 -0
- package/languages/zig.md +196 -0
- package/meta/SOUL.md +27 -0
- package/meta/code-field.md +44 -0
- package/meta/morals.md +40 -0
- package/meta/philosophy.md +39 -0
- package/meta/project.md +49 -0
- package/meta/ralph.md +223 -0
- package/meta/security.md +80 -0
- package/meta/versioning.md +326 -0
- package/package.json +22 -0
- package/packs/lessons/lessons-pack-0.1.json +553 -0
- package/packs/patterns/patterns-pack-0.1.json +57 -0
- package/packs/rules/rules-pack-0.1.json +4767 -0
- package/packs/skills/skills-pack-0.1.json +262 -0
- package/packs/strategies/strategies-pack-0.1.json +167 -0
- package/packs/swarm-spec/swarm-spec-pack-0.1.json +17 -0
- package/patterns/executor-layer-credentials.md +227 -0
- package/patterns/llm-app.md +156 -0
- package/patterns/multi-agent.md +278 -0
- package/patterns/prompt-assembly-layer-ordering.md +154 -0
- package/patterns/role-as-overlay.md +179 -0
- package/platforms/2600.md +137 -0
- package/platforms/unity.md +329 -0
- package/references/composer-skill-porting.md +152 -0
- package/references/cost-models.md +163 -0
- package/references/ip-risk.md +246 -0
- package/references/plain-english-ux.md +275 -0
- package/resilience/context-pruning.md +67 -0
- package/resilience/continue-here.md +62 -0
- package/scm/changelog.md +276 -0
- package/scm/git.md +139 -0
- package/scm/github.md +265 -0
- package/secrets/.gitkeep +0 -0
- package/skills/deft-build/SKILL.md +20 -0
- package/skills/deft-directive-article-review/SKILL.md +156 -0
- package/skills/deft-directive-build/SKILL.md +302 -0
- package/skills/deft-directive-cost/SKILL.md +201 -0
- package/skills/deft-directive-debug/SKILL.md +140 -0
- package/skills/deft-directive-decompose/SKILL.md +96 -0
- package/skills/deft-directive-gh-arch/SKILL.md +160 -0
- package/skills/deft-directive-gh-slice/SKILL.md +199 -0
- package/skills/deft-directive-glossary/SKILL.md +118 -0
- package/skills/deft-directive-interview/SKILL.md +528 -0
- package/skills/deft-directive-pre-pr/SKILL.md +131 -0
- package/skills/deft-directive-probe/SKILL.md +127 -0
- package/skills/deft-directive-refinement/SKILL.md +403 -0
- package/skills/deft-directive-release/SKILL.md +266 -0
- package/skills/deft-directive-review-cycle/SKILL.md +401 -0
- package/skills/deft-directive-setup/SKILL.md +717 -0
- package/skills/deft-directive-swarm/SKILL.md +989 -0
- package/skills/deft-directive-sync/SKILL.md +288 -0
- package/skills/deft-directive-triage/SKILL.md +137 -0
- package/skills/deft-directive-write-skill/SKILL.md +169 -0
- package/skills/deft-interview/SKILL.md +16 -0
- package/skills/deft-pre-pr/SKILL.md +16 -0
- package/skills/deft-review-cycle/SKILL.md +16 -0
- package/skills/deft-roadmap-refresh/SKILL.md +16 -0
- package/skills/deft-setup/SKILL.md +20 -0
- package/skills/deft-swarm/SKILL.md +16 -0
- package/skills/deft-sync/SKILL.md +16 -0
- package/strategies/README.md +83 -0
- package/strategies/artifact-guards.md +85 -0
- package/strategies/bdd.md +115 -0
- package/strategies/brownfield.md +7 -0
- package/strategies/discuss.md +129 -0
- package/strategies/emit-hints.md +69 -0
- package/strategies/enterprise.md +193 -0
- package/strategies/interview.md +551 -0
- package/strategies/map.md +179 -0
- package/strategies/probe.md +151 -0
- package/strategies/rapid.md +155 -0
- package/strategies/research.md +155 -0
- package/strategies/roadmap.md +9 -0
- package/strategies/speckit.md +437 -0
- package/strategies/v0-20-contract.md +134 -0
- package/strategies/yolo.md +169 -0
- package/swarm/swarm.md +300 -0
- package/templates/COST-ESTIMATE.md +114 -0
- package/templates/PULL_REQUEST_TEMPLATE.md +35 -0
- package/templates/agent-prompt-preamble.md +409 -0
- package/templates/agents-entry.md +211 -0
- package/templates/agents-entry.placeholders.md +75 -0
- package/templates/embed.go +20 -0
- package/templates/embed_test.go +36 -0
- package/templates/make-spec-example.md +9 -0
- package/templates/make-spec.md +246 -0
- package/templates/project.md.template +52 -0
- package/templates/specification.md +1 -0
- package/templates/swarm-greptile-poller-prompt.md +556 -0
- package/templates/user.md.template +31 -0
- package/tools/RWLDL.md +80 -0
- package/tools/greptile.md +141 -0
- package/tools/installer.md +23 -0
- package/tools/taskfile-migration.md +32 -0
- package/tools/taskfile.md +185 -0
- package/tools/telemetry.md +285 -0
- package/vbrief/schemas/cache-meta.schema.json +137 -0
- package/vbrief/schemas/candidates.schema.json +130 -0
- package/vbrief/schemas/codebase-map.schema.json +213 -0
- package/vbrief/schemas/lessons-pack.schema.json +134 -0
- package/vbrief/schemas/patterns-pack.schema.json +84 -0
- package/vbrief/schemas/rules-pack.schema.json +105 -0
- package/vbrief/schemas/skills-pack.schema.json +94 -0
- package/vbrief/schemas/slices.schema.json +87 -0
- package/vbrief/schemas/strategies-pack.schema.json +89 -0
- package/vbrief/schemas/swarm-spec-pack.schema.json +84 -0
- package/vbrief/schemas/vbrief-core.schema.json +1022 -0
- package/vbrief/vbrief.md +684 -0
- package/verification/integration.md +76 -0
- package/verification/plan-checking.md +85 -0
- package/verification/uat.md +60 -0
- package/verification/verification.md +117 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# [Project Name] Project Guidelines
|
|
2
|
+
|
|
3
|
+
Only specify items here that **override or extend** the deft defaults.
|
|
4
|
+
See `deft/main.md`, `deft/coding/coding.md`, and language-specific files for base standards.
|
|
5
|
+
|
|
6
|
+
**Tech Type**: [CLI / Web / API / Library / TUI]
|
|
7
|
+
**Specification**: [specification.md](./specification.md)
|
|
8
|
+
|
|
9
|
+
## 📏 Overrides
|
|
10
|
+
|
|
11
|
+
<!-- Only list deviations from deft defaults. Examples: -->
|
|
12
|
+
<!-- - Coverage: ≥95% (overrides default 85%) -->
|
|
13
|
+
<!-- - Additional task targets unique to this project -->
|
|
14
|
+
|
|
15
|
+
## 🔐 Secrets
|
|
16
|
+
|
|
17
|
+
<!-- List project-specific secrets needed -->
|
|
18
|
+
```bash
|
|
19
|
+
cp secrets/[example].example secrets/[example] # [Description]
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 🎯 Principles
|
|
23
|
+
|
|
24
|
+
<!-- Non-negotiable rules unique to THIS project (3-5 items). -->
|
|
25
|
+
- ! [Principle 1 — describe]
|
|
26
|
+
- ! [Principle 2 — describe]
|
|
27
|
+
- ⊗ [Anti-principle — what to avoid]
|
|
28
|
+
|
|
29
|
+
## 📦 Tech Stack
|
|
30
|
+
|
|
31
|
+
**Language**: [Primary language with version]
|
|
32
|
+
**Framework**: [Main framework if applicable]
|
|
33
|
+
**Database**: [Database if applicable]
|
|
34
|
+
**Deployment**: [Platform/method]
|
|
35
|
+
|
|
36
|
+
## 📄 Documentation
|
|
37
|
+
|
|
38
|
+
- **Architecture**: [architecture.md](./docs/architecture.md)
|
|
39
|
+
- **API**: [api.md](./docs/api.md)
|
|
40
|
+
- **Deployment**: [deployment.md](./docs/deployment.md)
|
|
41
|
+
|
|
42
|
+
## Branching
|
|
43
|
+
|
|
44
|
+
<!-- Uncomment the line below to allow direct commits to master (trunk-based workflow). -->
|
|
45
|
+
<!-- Allow direct commits to master: true -->
|
|
46
|
+
|
|
47
|
+
## 🚧 Constraints
|
|
48
|
+
|
|
49
|
+
<!-- Optional: timeline, compliance, compatibility requirements -->
|
|
50
|
+
- **Timeline**: [deadline if any]
|
|
51
|
+
- **Compatibility**: [supported versions, platforms]
|
|
52
|
+
- **Compliance**: [regulatory requirements if any]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
see ../SPECIFICATION.md
|
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
templates/swarm-greptile-poller-prompt.md
|
|
3
|
+
|
|
4
|
+
Canonical Greptile review-cycle / poller sub-agent prompt body.
|
|
5
|
+
|
|
6
|
+
Used by parent monitor agents when delegating post-PR work via the platform
|
|
7
|
+
dispatch primitive per the parent's Phase 3 runtime capability detection
|
|
8
|
+
(`spawn_subagent` for "grok-build" / Grok Build TUI, `start_agent` for
|
|
9
|
+
Warp-orchestrated, or equivalent in other environments). See
|
|
10
|
+
`skills/deft-directive-swarm/SKILL.md` Phase 3 + #1342 slices 1-3 and the
|
|
11
|
+
review-cycle skill's updated Approach 1.
|
|
12
|
+
|
|
13
|
+
The parent reads this file and applies Python `str.format(...)` to substitute
|
|
14
|
+
five placeholders, then passes the formatted prompt via the chosen dispatch
|
|
15
|
+
primitive (spawn_subagent, start_agent, etc.):
|
|
16
|
+
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
prompt = Path("templates/swarm-greptile-poller-prompt.md").read_text(encoding="utf-8").format(
|
|
19
|
+
pr_number=N,
|
|
20
|
+
repo="owner/repo",
|
|
21
|
+
poll_interval_seconds=90,
|
|
22
|
+
poll_cap_minutes=30,
|
|
23
|
+
parent_agent_id="<parent-id>",
|
|
24
|
+
)
|
|
25
|
+
# Dispatch via the detected primitive (example for start_agent shown;
|
|
26
|
+
# use spawn_subagent + the launch adapter for grok-build per #1342)
|
|
27
|
+
start_agent(name=f"greptile-poller-{{N}}", prompt=prompt, execution_mode="local")
|
|
28
|
+
|
|
29
|
+
This file is the proven prompt body. Hand-authored variants have repeatedly
|
|
30
|
+
missed two specific parsing bugs (markdown-link `Last reviewed commit:`,
|
|
31
|
+
raw `\b(P0|P1)\b` substring scan with negation false-positive); the body
|
|
32
|
+
below encodes the fixes inline. See #727 (canonical encoding) and
|
|
33
|
+
`skills/deft-directive-swarm/SKILL.md` Phase 6 Sub-Agent Role Separation
|
|
34
|
+
(#1342 platform adapter unification: the platform adapter dispatches via
|
|
35
|
+
`spawn_subagent` on grok-build and `start_agent` on Warp-orchestrated, so
|
|
36
|
+
this template is platform-agnostic by construction) for the rules that
|
|
37
|
+
mandate using this template instead of hand-authoring.
|
|
38
|
+
|
|
39
|
+
NOTE on `.format()` escaping: every literal curly brace in this file is
|
|
40
|
+
doubled (`{{` / `}}`) so it survives the `str.format(...)` pass. The five
|
|
41
|
+
placeholders below are the ONLY single-braced tokens.
|
|
42
|
+
-->
|
|
43
|
+
|
|
44
|
+
TASK: You are a review-cycle agent for PR #{pr_number} in {repo}. Embody `skills/deft-directive-review-cycle/SKILL.md` end-to-end as a single coherent role -- you handle BOTH polling Greptile for review state AND fixing any P0/P1 findings. Do NOT split into separate "poll" and "fix" agents. Do NOT exit until the exit condition is met OR you hit a terminal error / timeout.
|
|
45
|
+
|
|
46
|
+
DO NOT STOP until ONE of the six terminal exit conditions below fires.
|
|
47
|
+
|
|
48
|
+
## Role posture
|
|
49
|
+
|
|
50
|
+
- Single role: review-cycle agent. Read `skills/deft-directive-review-cycle/SKILL.md` and follow Phase 2 (Review/Fix Loop) end-to-end.
|
|
51
|
+
- Parent agent ID for status messages: `{parent_agent_id}`. Send status updates via `send_message_to_agent` at start, on each terminal exit condition, and on any blocker.
|
|
52
|
+
- Execution: local. Working directory: the worktree the parent gave you (or your `--cwd` if running under `oz agent run --cwd`).
|
|
53
|
+
|
|
54
|
+
## Bounded poll loop
|
|
55
|
+
|
|
56
|
+
- Poll interval: `{poll_interval_seconds}` seconds between checks (recommended default 90s -- Greptile reviews land in 3-7 min, so faster polling adds noise without information).
|
|
57
|
+
- Total budget: `{poll_cap_minutes}` minutes (recommended default 30 min).
|
|
58
|
+
- Use a Python script with `time.sleep(...)` driven by an internal timer -- do NOT use shell `while true; sleep`-style loops, and do NOT yield between polls (yielding ends the agent's turn with no self-wake; #195 lesson).
|
|
59
|
+
- **Heartbeat write per iteration (#1365):** every poll iteration MUST also atomically write a heartbeat record to `.deft-scratch/subagent-status/<agent-id>.json` per the contract in `docs/subagent-heartbeat.md`. The record carries `agent_id`, `parent_id` (= `{parent_agent_id}`), `last_heartbeat_at` (ISO-8601 UTC with `Z`), `last_message`, `phase = "polling"` (or `"fixing"` when addressing P0/P1 findings), and `terminal_state = null`. The terminal exit conditions ((1) CLEAN / (2) NEW P0/P1 FINDINGS escalation / (3) ERRORED / (4) TIMEOUT / (5) STALL) MUST also write ONE final heartbeat with `phase = "terminal"` and `terminal_state` set to the canonical exit name BEFORE sending the parent message and exiting. The 90s poll cadence naturally satisfies the 2-3 min cadence floor in `docs/subagent-heartbeat.md`; the per-iteration heartbeat is what lets `scripts/subagent_monitor.py` detect a stalled poller within the threshold instead of waiting on the `{poll_cap_minutes}`-minute cap.
|
|
60
|
+
|
|
61
|
+
## Per-poll fetch
|
|
62
|
+
|
|
63
|
+
Each iteration MUST run BOTH:
|
|
64
|
+
|
|
65
|
+
1. `gh pr view {pr_number} --repo {repo} --comments` -- captures the rolling Greptile summary comment AND any "Comments Outside Diff" section (the MCP `get_review_comments` tool does NOT return Outside-Diff comments). Use `do_not_summarize_output: true` semantics -- summarizers silently drop the Outside-Diff section. If the output is too large to process, extract just the relevant portion via PowerShell `Select-String "Outside Diff" -Context 50` or `grep -A 50 "Outside Diff"`.
|
|
66
|
+
2. `gh pr checks {pr_number} --repo {repo}` -- captures the GitHub CheckRun statuses (`Greptile Review`, `CI / Python`, `CI / Go`, etc.).
|
|
67
|
+
|
|
68
|
+
## Greptile state detection
|
|
69
|
+
|
|
70
|
+
Parse the Greptile rolling-summary comment body returned by step 1.
|
|
71
|
+
|
|
72
|
+
### `Last reviewed commit:` (markdown-link form)
|
|
73
|
+
|
|
74
|
+
Greptile emits the line as a markdown link, NOT an inline SHA:
|
|
75
|
+
|
|
76
|
+
Last reviewed commit: [<commit subject>](https://github.com/<owner>/<repo>/commit/<sha>)
|
|
77
|
+
|
|
78
|
+
The SHA-extraction regex MUST handle the markdown-link form. The link-text group MUST be NON-GREEDY (`.*?`), NOT a `[^\]]*` negated-bracket class: Greptile commit subjects routinely contain escaped brackets in the link text (e.g. `add \[Unreleased\] entry`), and a `[^\]]*` class stops at the FIRST `]` -- which is the escaped `\]` inside the subject, not the real `](` link boundary -- so the regex fails to match and `last_reviewed_sha` falls to `None` (false STALL / TIMEOUT on a clean review; #1326). The non-greedy `.*?` skips intermediate `]` characters and binds to the real `]( ... /commit/<sha>` boundary. Recommended:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
import re
|
|
82
|
+
m = re.search(
|
|
83
|
+
r"Last reviewed commit:\s*\[.*?\]\(https?://github\.com/[^/]+/[^/]+/commit/(?P<sha>[0-9a-f]{{7,40}})",
|
|
84
|
+
body,
|
|
85
|
+
)
|
|
86
|
+
last_reviewed_sha = m.group("sha") if m else None
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
A regex that requires the SHA inline after `Last reviewed commit:` will NEVER match Greptile's actual output -- the poller will fall through every iteration and run to its `{poll_cap_minutes}`-minute cap (Agent D, post-#721 swarm; #727 comment 2 Bug 1).
|
|
90
|
+
|
|
91
|
+
### P0/P1 findings detection (TRIPLE-TIER -- #910)
|
|
92
|
+
|
|
93
|
+
Greptile renders findings in at least THREE distinct surface forms across review passes on the same PR (recurrence record: v0.25.1 swarm session, 2026-05-04 -- #907 first review, #908 first review, #908 retrigger). A single-tier detector is structurally insufficient. The detector MUST evaluate ALL THREE tiers below and combine them via the final `has_blocking` formula. The clean-summary phrasing `No P0 or P1 issues found` contains the literal tokens `P0` and `P1`, so a raw `\b(P0|P1)\b` substring scan produces a FALSE POSITIVE on every clean review -- the negation-guard rules embedded in Tier 2 / Tier 3 below are non-negotiable.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import re
|
|
97
|
+
|
|
98
|
+
# --- Code-fence strip BEFORE detection (detector self-reference, #1004) ----
|
|
99
|
+
# On PRs that touch the detector ITSELF (this template or its test file),
|
|
100
|
+
# Greptile quotes the detector's own tokens (`<img alt="P0"`, `Not safe to
|
|
101
|
+
# merge`, `Three P1 findings`, ...) verbatim inside code-fence regions when
|
|
102
|
+
# it describes the diff. Those quoted tokens are NOT real findings, but a
|
|
103
|
+
# raw scan over the full body counts them and trips a FALSE POSITIVE
|
|
104
|
+
# (recurrence record: PR #996 first review, 2026-05-08 -- detector reported
|
|
105
|
+
# P0=3/P1=3/tier3=True against an actual 1 P1 + 2 P2 because Greptile quoted
|
|
106
|
+
# agent2's own fixtures). Strip triple-backtick fenced blocks AND <code> /
|
|
107
|
+
# <pre> HTML regions FIRST so the Tier 1/2/3 scans below only see prose.
|
|
108
|
+
# Residual case (documented in `## Implementation Notes`): a REAL finding
|
|
109
|
+
# Greptile happens to render inside a fence is dropped -- rare and far less
|
|
110
|
+
# costly than the recurring self-reference false-positive.
|
|
111
|
+
_CODE_FENCE_RE = re.compile(r"`{{3}}.*?`{{3}}", re.DOTALL)
|
|
112
|
+
_HTML_CODE_RE = re.compile(r"<(code|pre)\b[^>]*>.*?</\1>", re.DOTALL | re.IGNORECASE)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def strip_code_fences(text):
|
|
116
|
+
text = _CODE_FENCE_RE.sub(" ", text)
|
|
117
|
+
text = _HTML_CODE_RE.sub(" ", text)
|
|
118
|
+
return text
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# All Tier 1/2/3 detection below runs against the fence-stripped body.
|
|
122
|
+
body = strip_code_fences(body)
|
|
123
|
+
|
|
124
|
+
# --- Tier 1: HTML badge count ---------------------------------------------
|
|
125
|
+
# Greptile renders per-finding severity badges as `<img alt="P0" ...>` /
|
|
126
|
+
# `<img alt="P1" ...>`. These markers appear ONLY on actual findings, never
|
|
127
|
+
# in clean-summary prose. Tier 1 is robust by construction but only fires
|
|
128
|
+
# when Greptile chose the badge-rendering surface for THIS review pass.
|
|
129
|
+
tier1_p0 = body.count('<img alt="P0"')
|
|
130
|
+
tier1_p1 = body.count('<img alt="P1"')
|
|
131
|
+
|
|
132
|
+
# --- Tier 2: markdown-bullet bold scan with negation-context guards -------
|
|
133
|
+
# Greptile sometimes renders findings as markdown bullets, e.g.
|
|
134
|
+
# - **P1 -- wrong exception type for state validation in populate()**
|
|
135
|
+
# * **P0: state.json schema mismatch**
|
|
136
|
+
# The bold-headed bullet is the structural signal; the leading list marker
|
|
137
|
+
# is optional. We scan line-by-line so the negation-context window is the
|
|
138
|
+
# physical line, not the whole document (a `No P1 findings` line elsewhere
|
|
139
|
+
# in the body MUST NOT cancel a real `**P1 -- ...**` bullet).
|
|
140
|
+
_TIER2_RE = re.compile(r"^[\s\-\*]*\*\*P([01])\b[^*]*\*\*", re.MULTILINE)
|
|
141
|
+
_TIER2_NEGATIONS = ("No ", "Zero ", "0 ", "no ")
|
|
142
|
+
|
|
143
|
+
def _line_for(body: str, pos: int) -> str:
|
|
144
|
+
line_start = body.rfind("\n", 0, pos) + 1
|
|
145
|
+
line_end = body.find("\n", pos)
|
|
146
|
+
return body[line_start : line_end if line_end != -1 else len(body)]
|
|
147
|
+
|
|
148
|
+
tier2_p0 = 0
|
|
149
|
+
tier2_p1 = 0
|
|
150
|
+
for m in _TIER2_RE.finditer(body):
|
|
151
|
+
line = _line_for(body, m.start())
|
|
152
|
+
if any(neg in line for neg in _TIER2_NEGATIONS):
|
|
153
|
+
continue # negation context (e.g. `No **P1** findings`) -- skip
|
|
154
|
+
if m.group(1) == "0":
|
|
155
|
+
tier2_p0 += 1
|
|
156
|
+
else:
|
|
157
|
+
tier2_p1 += 1
|
|
158
|
+
|
|
159
|
+
# --- Tier 2.5: SLizard `### P[01] ·` heading form (#1035) ----------------
|
|
160
|
+
# SLizard renders findings as level-3 markdown headings prefixed with the
|
|
161
|
+
# severity tag and a separator glyph -- e.g. `### P1 ` followed by middot,
|
|
162
|
+
# bullet, hyphenation point, or ASCII hyphen, then the finding title:
|
|
163
|
+
# ### P1 · Inaccurate description claim about ROADMAP.md
|
|
164
|
+
# ### P0 • data-loss risk in cache eviction
|
|
165
|
+
# The Tier 2 markdown-bullet bold regex requires `**P[01] ... **` wrapping,
|
|
166
|
+
# so SLizard's heading form passes through invisible. Tier 2.5 closes that
|
|
167
|
+
# gap WITHOUT renumbering Tiers 1/2/3 (the existing detector citations in
|
|
168
|
+
# meta/lessons.md and the swarm-skill anti-patterns key on the 1/2/3 names).
|
|
169
|
+
# Recurrence record: PR #1034 (2026-05-11) live SLizard P1 missed by the
|
|
170
|
+
# triple-tier detector -- #1035 (this fix).
|
|
171
|
+
# Negation-context guard MUST apply the same line-scoped tokens as Tier 2.
|
|
172
|
+
_TIER25_RE = re.compile(
|
|
173
|
+
r"^#{{1,6}}\s+P([01])\s*[\u00b7\u2027\u2022\-]\s", re.MULTILINE
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
tier25_p0 = 0
|
|
177
|
+
tier25_p1 = 0
|
|
178
|
+
for m in _TIER25_RE.finditer(body):
|
|
179
|
+
line = _line_for(body, m.start())
|
|
180
|
+
if any(neg in line for neg in _TIER2_NEGATIONS):
|
|
181
|
+
continue # negation context -- skip
|
|
182
|
+
if m.group(1) == "0":
|
|
183
|
+
tier25_p0 += 1
|
|
184
|
+
else:
|
|
185
|
+
tier25_p1 += 1
|
|
186
|
+
|
|
187
|
+
# --- Tier 3: inline-prose sentinels ---------------------------------------
|
|
188
|
+
# Greptile sometimes inlines the verdict as plain prose, e.g.
|
|
189
|
+
# Three P1 findings (two from prior review, one new): wrong exception ...
|
|
190
|
+
# Not safe to merge until the mocked-import test defect is resolved.
|
|
191
|
+
# P1 -- wrong exception type for state validation in populate()
|
|
192
|
+
# Negation-context guard applies to the count-prose sentinel (`No P0 findings`,
|
|
193
|
+
# `Zero P1 findings` MUST NOT trigger). The `Not safe to merge` substring is
|
|
194
|
+
# Greptile's explicit human-readable verdict and is treated as a hard block.
|
|
195
|
+
_TIER3_COUNT_RE = re.compile(
|
|
196
|
+
r"\b(?:One|Two|Three|Four|Five|Six|Seven|Eight|Nine|Ten|\d+)\s+P[01]\s+findings?\b",
|
|
197
|
+
re.IGNORECASE,
|
|
198
|
+
)
|
|
199
|
+
_TIER3_LINE_RE = re.compile(r"^\s*P[01]\s+--\s", re.MULTILINE)
|
|
200
|
+
_TIER3_NEGATIONS = ("No ", "Zero ", "no ", "NO ")
|
|
201
|
+
|
|
202
|
+
def _has_tier3_sentinel(body: str) -> bool:
|
|
203
|
+
if "Not safe to merge" in body:
|
|
204
|
+
return True
|
|
205
|
+
for m in _TIER3_COUNT_RE.finditer(body):
|
|
206
|
+
line = _line_for(body, m.start())
|
|
207
|
+
if any(neg in line for neg in _TIER3_NEGATIONS):
|
|
208
|
+
continue
|
|
209
|
+
# Reject a leading `0 ` count to avoid `0 P1 findings` false-positive.
|
|
210
|
+
if re.match(r"\s*0\b", m.group(0)):
|
|
211
|
+
continue
|
|
212
|
+
return True
|
|
213
|
+
for m in _TIER3_LINE_RE.finditer(body):
|
|
214
|
+
line = _line_for(body, m.start())
|
|
215
|
+
if any(neg in line for neg in _TIER3_NEGATIONS):
|
|
216
|
+
continue
|
|
217
|
+
return True
|
|
218
|
+
return False
|
|
219
|
+
|
|
220
|
+
tier3_sentinel = _has_tier3_sentinel(body)
|
|
221
|
+
|
|
222
|
+
# --- Combined verdict -----------------------------------------------------
|
|
223
|
+
# Use max() per severity so a finding visible in MULTIPLE tiers is not
|
|
224
|
+
# double-counted; sum P0+P1 across the union; OR with the Tier 3 sentinel
|
|
225
|
+
# (which is severity-agnostic by construction). Tier 2.5 (#1035) joins the
|
|
226
|
+
# per-severity max() so SLizard `### P[01] · ...` heading-form findings
|
|
227
|
+
# count toward has_blocking regardless of whether Tier 1 or Tier 2 fired.
|
|
228
|
+
has_blocking = (
|
|
229
|
+
(
|
|
230
|
+
max(tier1_p0, tier2_p0, tier25_p0)
|
|
231
|
+
+ max(tier1_p1, tier2_p1, tier25_p1)
|
|
232
|
+
)
|
|
233
|
+
> 0
|
|
234
|
+
or tier3_sentinel
|
|
235
|
+
)
|
|
236
|
+
p0_count = max(tier1_p0, tier2_p0, tier25_p0)
|
|
237
|
+
p1_count = max(tier1_p1, tier2_p1, tier25_p1)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Optional structured-section fallback:** Greptile occasionally emits `### P0 findings (N)` / `### P1 findings (N)` headings. This surface is rare relative to the three tiers above and is provided as a diagnostic-only readout, NOT as a fourth tier in the `has_blocking` formula:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
import re
|
|
244
|
+
def severity_count(body, sev):
|
|
245
|
+
m = re.search(rf"###\s+{{sev}}\s+findings\s+\((\d+)\)", body)
|
|
246
|
+
return int(m.group(1)) if m else 0
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Anti-patterns:**
|
|
250
|
+
|
|
251
|
+
- ⊗ A badge-only detector (Tier 1 alone). The recurrence record is three false-negatives in a single swarm session because Greptile rendered findings as markdown bullets / inline prose with zero badges.
|
|
252
|
+
- ⊗ A `\b(P0|P1)\b` substring scan WITHOUT negation-context guards. The clean-summary phrase `No P0 or P1 issues found` triggers it on every clean review. The Tier 2 / Tier 3 implementations above embed the guards; do not strip them.
|
|
253
|
+
- ⊗ Treating `Not safe to merge` as a Tier 3 maybe-signal. Greptile uses that exact phrase as its explicit human-readable verdict; it is a hard block.
|
|
254
|
+
|
|
255
|
+
### Confidence parse
|
|
256
|
+
|
|
257
|
+
Greptile's summary contains a line like `Confidence Score: 5/5` (or `4/5`, etc.). The line is sometimes inline prose and sometimes rendered as a markdown heading (`## Confidence Score: 3/5` -- the rolling-summary header form Greptile uses on PR rolling comments). The inline regex `re.search` does match inline AND most heading forms because it is unanchored, but defence-in-depth requires an explicit heading-form fallback for the case where the heading line carries trailing markup or whitespace that the inline regex declines (#1035 surfacing event: PR #1034 rolling-summary header `## Confidence Score: 3/5` paired with verdict prose `Safe to merge once corrected` produced a missed parse and the poller fell through to TIMEOUT). Parse via the inline-or-line form first; on miss, attempt the strictly-anchored heading-form regex:
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import re
|
|
261
|
+
m = re.search(r"Confidence Score:\s*(\d+)\s*/\s*5", body)
|
|
262
|
+
if m is None:
|
|
263
|
+
# Heading-form fallback (#1035): anchored ^...$ multiline so a stray `0`
|
|
264
|
+
# outside the `/5` slash form cannot trip the gate. `0/5` is a valid
|
|
265
|
+
# score and MUST parse; the slash form requirement is the structural
|
|
266
|
+
# leading-`0` rejection guard.
|
|
267
|
+
m = re.search(
|
|
268
|
+
r"^#{{1,6}}\s*Confidence Score:\s*(\d+)\s*/\s*5\s*$",
|
|
269
|
+
body,
|
|
270
|
+
re.MULTILINE,
|
|
271
|
+
)
|
|
272
|
+
confidence = int(m.group(1)) if m else None
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
The clean threshold is `confidence > 3`, i.e. 4/5 or 5/5. Lower scores indicate Greptile is uncertain -- do NOT exit clean.
|
|
276
|
+
|
|
277
|
+
### Informal-clean missing canonical fields (#1543)
|
|
278
|
+
|
|
279
|
+
Greptile sometimes posts a separate informal clean reply (`current diff is clean`, `prior issues are now resolved`, `no new issues`, `looks solid`) without the canonical rolling-summary fields `Last reviewed commit:` and `Confidence Score: X/5`. This is NOT "review still writing" and MUST NOT fall through to silent polling or generic `(5) STALL`.
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
import re
|
|
283
|
+
|
|
284
|
+
_INFORMAL_CLEAN_SIGNAL_RE = re.compile(
|
|
285
|
+
r"(?:"
|
|
286
|
+
r"diff is clean|"
|
|
287
|
+
r"(?:prior |previously flagged )?issues? (?:are )?now resolved|"
|
|
288
|
+
r"all prior issues resolved|"
|
|
289
|
+
r"no new issues(?: to flag)?|"
|
|
290
|
+
r"looks solid|"
|
|
291
|
+
r"good to proceed"
|
|
292
|
+
r")",
|
|
293
|
+
re.IGNORECASE,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
def is_informal_clean_missing_canonical_fields(
|
|
297
|
+
body: str,
|
|
298
|
+
last_reviewed_sha,
|
|
299
|
+
confidence,
|
|
300
|
+
has_blocking: bool,
|
|
301
|
+
) -> bool:
|
|
302
|
+
if last_reviewed_sha is not None or confidence is not None:
|
|
303
|
+
return False
|
|
304
|
+
if has_blocking:
|
|
305
|
+
return False
|
|
306
|
+
if body.strip().startswith("Greptile encountered an error while reviewing this PR"):
|
|
307
|
+
return False
|
|
308
|
+
return _INFORMAL_CLEAN_SIGNAL_RE.search(body) is not None
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
When `is_informal_clean_missing_canonical_fields(...)` is True on the current poll, exit immediately via `### (6) INFORMAL-CLEAN` below. Do NOT increment `stall_streak` toward `(5) STALL` -- the recovery path is retrigger / canonical evidence / documented override, not more polling.
|
|
312
|
+
|
|
313
|
+
## CLEAN gate evaluation, `clean_gate_holdout`, and per-poll instrumentation (#1039)
|
|
314
|
+
|
|
315
|
+
The (1) CLEAN terminal exit is an AND of the six conditions enumerated under `### (1) CLEAN` below. A parse failure in ANY of them (regex doesn't match Greptile's actual rendering / parse silently returns None / a `CI / *` check is `pending` rather than `completed` / the Greptile Review check-run is non-terminal) keeps both `has_blocking = False` AND `is_clean = False` simultaneously, dropping the poller into the fall-through path that polls until the `{poll_cap_minutes}`-minute cap (PR #1038 recurrence, 2026-05-11; #1039). The gate evaluator below names the FIRST failing condition (in (1)/(2)/(3)/(4)/(5)/(6) order) as `clean_gate_holdout` so the per-poll log AND the (4) TIMEOUT / (5) STALL exit messages surface WHICH of the six conditions held the gate -- the operator MUST NEVER have to ask "which condition failed?" (Tier 3 per-condition fail-loud, #1039).
|
|
316
|
+
|
|
317
|
+
Holdout names map to the six conditions verbatim: condition (1) -> `sha_match`, (2) -> `has_blocking`, (3) -> `confidence`, (4) -> `ci_failures`, (5) -> `errored`, (6) -> `terminal_check_run` (#1259). The function MUST evaluate in this exact order so the holdout names the first failure, not a downstream cascade:
|
|
318
|
+
|
|
319
|
+
```python
|
|
320
|
+
def evaluate_clean_gate(
|
|
321
|
+
last_reviewed_sha,
|
|
322
|
+
head_sha,
|
|
323
|
+
has_blocking,
|
|
324
|
+
confidence,
|
|
325
|
+
ci_failures,
|
|
326
|
+
errored,
|
|
327
|
+
terminal_check_run,
|
|
328
|
+
):
|
|
329
|
+
"""Return (is_clean, clean_gate_holdout) per the (6)-condition AND gate.
|
|
330
|
+
|
|
331
|
+
clean_gate_holdout names the FIRST failing condition (in 1/2/3/4/5/6
|
|
332
|
+
order) or None when all six pass. The order is the operative
|
|
333
|
+
contract -- callers MUST NOT reorder the checks or the holdout will
|
|
334
|
+
name a downstream cascade rather than the root cause (#1039).
|
|
335
|
+
|
|
336
|
+
Condition (6) `terminal_check_run` (#1259) fails the gate CLOSED unless
|
|
337
|
+
the `Greptile Review` check-run on the current HEAD is terminal --
|
|
338
|
+
`status == "completed"` AND `conclusion` in `{{success, neutral}}`. A
|
|
339
|
+
non-terminal Greptile conclusion (`queued` / `in_progress` /
|
|
340
|
+
`cancelled` / `timed_out` / `stale` / `action_required` / `failure`)
|
|
341
|
+
is NOT clean even when the rolling summary already parses clean (SHA
|
|
342
|
+
matches HEAD, confidence > 3, no P0/P1). This is the INCOMPLETE_BUT_RATED
|
|
343
|
+
scenario from `skills/deft-directive-review-cycle/SKILL.md` Step 6:
|
|
344
|
+
without (6), all five legacy conditions pass and the poller exits CLEAN
|
|
345
|
+
prematurely. `ci_failures` (condition 4) is scoped to `CI / *` checks
|
|
346
|
+
ONLY -- it does NOT cover the Greptile Review check-run -- so the
|
|
347
|
+
terminal Greptile conclusion is a DISTINCT condition that `ci_failures`
|
|
348
|
+
cannot stand in for. Mirrors the SKILL.md Step 6 fail-closed all-of's
|
|
349
|
+
terminal-check-run field so the swarm-dispatched poller path enforces
|
|
350
|
+
the same gate as the one-shot review-cycle entry.
|
|
351
|
+
"""
|
|
352
|
+
if last_reviewed_sha is None or last_reviewed_sha != head_sha:
|
|
353
|
+
return False, "sha_match"
|
|
354
|
+
if has_blocking:
|
|
355
|
+
return False, "has_blocking"
|
|
356
|
+
if confidence is None or confidence <= 3:
|
|
357
|
+
return False, "confidence"
|
|
358
|
+
if ci_failures > 0:
|
|
359
|
+
return False, "ci_failures"
|
|
360
|
+
if errored:
|
|
361
|
+
return False, "errored"
|
|
362
|
+
if not terminal_check_run:
|
|
363
|
+
return False, "terminal_check_run"
|
|
364
|
+
return True, None
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Each poll iteration MUST emit a Tier 1 diagnostic log line (#1039 AC-1) with the fields below in this exact order so a future operator can grep the poller's transcript for `is_clean=False` and see WHICH of the six conditions was the holdout. The fields appear verbatim in this order -- a future edit MUST NOT reorder, rename, or drop them; the `tests/content/test_swarm_poller_template.py` sync tests pin the field set:
|
|
368
|
+
|
|
369
|
+
Derive `greptile_terminal` from the `Greptile Review` check-run on the current HEAD (NOT from the `CI / *` checks `ci_failure_count` already covers). The check-run is terminal-clean only when `status == "completed"` AND `conclusion` in `{{success, neutral}}`; any other status (`queued` / `in_progress`) or conclusion (`cancelled` / `timed_out` / `stale` / `action_required` / `failure` / `None`) is non-terminal and MUST fail the gate closed (#1259):
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
# From `gh api repos/{repo}/commits/<head_sha>/check-runs` (or the parsed
|
|
373
|
+
# `gh pr checks {pr_number}` output), find the `Greptile Review` check-run:
|
|
374
|
+
greptile_terminal = (
|
|
375
|
+
greptile_run is not None
|
|
376
|
+
and greptile_run.get("status") == "completed"
|
|
377
|
+
and greptile_run.get("conclusion") in ("success", "neutral")
|
|
378
|
+
)
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
```python
|
|
382
|
+
is_clean, clean_gate_holdout = evaluate_clean_gate(
|
|
383
|
+
last_reviewed_sha=last_reviewed_sha,
|
|
384
|
+
head_sha=head_sha,
|
|
385
|
+
has_blocking=has_blocking,
|
|
386
|
+
confidence=confidence,
|
|
387
|
+
ci_failures=ci_failure_count,
|
|
388
|
+
errored=errored,
|
|
389
|
+
terminal_check_run=greptile_terminal,
|
|
390
|
+
)
|
|
391
|
+
print(
|
|
392
|
+
f"[poll {{i}}/{{cap}}] last_reviewed_sha={{last_reviewed_sha}} "
|
|
393
|
+
f"head={{head_sha}} sha_match={{last_reviewed_sha == head_sha}} "
|
|
394
|
+
f"confidence={{confidence}} has_blocking={{has_blocking}} "
|
|
395
|
+
f"p0={{p0_count}} p1={{p1_count}} errored={{errored}} "
|
|
396
|
+
f"ci_failures={{ci_failure_count}} is_clean={{is_clean}} "
|
|
397
|
+
f"clean_gate_holdout={{clean_gate_holdout}}"
|
|
398
|
+
)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Also track a `stall_streak` counter across polls: increment when `has_blocking is False and is_clean is False` (the wedged signature -- no blocking signals detected, no CLEAN exit reachable); reset to 0 on any poll where either `has_blocking` becomes True (drops into (2) NEW P0/P1 FINDINGS) or `is_clean` becomes True (drops into (1) CLEAN). `stall_streak >= 3` is the (5) STALL trip condition below.
|
|
402
|
+
|
|
403
|
+
## Terminal exit conditions
|
|
404
|
+
|
|
405
|
+
When ANY of the six conditions below fires, send the corresponding message to `{parent_agent_id}` and exit. Each message body MUST end with the exact line `-- no more polling, exiting now` so the parent can detect the exit unambiguously.
|
|
406
|
+
|
|
407
|
+
### (1) CLEAN
|
|
408
|
+
|
|
409
|
+
ALL of:
|
|
410
|
+
- `last_reviewed_sha` parsed and matches the current PR HEAD SHA (compare via `gh pr view {pr_number} --repo {repo} --json headRefOid --jq .headRefOid`).
|
|
411
|
+
- `has_blocking` is False (no P0 / P1 findings).
|
|
412
|
+
- `confidence > 3` (i.e. 4/5 or 5/5 -- a `confidence == 3` parse is NOT clean; the gate names `clean_gate_holdout="confidence"` and you stay in the loop, you do NOT send the CLEAN message).
|
|
413
|
+
- `gh pr checks {pr_number}` shows no `failure` status on `CI / *` checks.
|
|
414
|
+
- The Greptile rolling-summary comment body does NOT equal `Greptile encountered an error while reviewing this PR` (errored sentinel; #526).
|
|
415
|
+
- `terminal_check_run` is True: the `Greptile Review` check-run on the current HEAD is terminal -- `status == "completed"` AND `conclusion` in `{{success, neutral}}` (#1259). A non-terminal Greptile conclusion (`queued` / `in_progress` / `cancelled` / `timed_out` / `stale` / `action_required` / `failure`) is NOT clean even when the rolling summary already parses clean (the INCOMPLETE_BUT_RATED scenario -- rolling summary posted, SHA matches, confidence > 3, no P0/P1, but the check-run has not terminally landed). This is DISTINCT from the `CI / *` `failure` bullet above: `ci_failures` is scoped to `CI / *` checks only and does NOT cover the Greptile Review check-run, so a non-terminal Greptile conclusion would otherwise slip through. The gate names `clean_gate_holdout="terminal_check_run"` and you stay in the loop.
|
|
416
|
+
|
|
417
|
+
Send to parent:
|
|
418
|
+
|
|
419
|
+
Subject: PR #{pr_number} CLEAN -- ready for merge
|
|
420
|
+
Body:
|
|
421
|
+
Greptile review on HEAD <sha> is clean.
|
|
422
|
+
Confidence: <N>/5
|
|
423
|
+
Findings: P0=0, P1=0
|
|
424
|
+
CI: <list of CheckRun statuses>
|
|
425
|
+
Last reviewed commit: <sha>
|
|
426
|
+
-- no more polling, exiting now
|
|
427
|
+
|
|
428
|
+
**Swarm-orchestrated terminal contract (#1364):** when this poller is dispatched as part of a swarm cohort (parent monitor is running `skills/deft-directive-swarm/SKILL.md` Phase 6), this exact subject line -- `PR #{pr_number} CLEAN -- ready for merge` -- with `confidence > 3` recorded on the **current HEAD** is the ONLY acceptable "review complete" signal the swarm monitor accepts toward the Phase 5 -> 6 merge-gate transition. The five other terminal exits below ((2) NEW P0/P1 FINDINGS escalation, (3) ERRORED, (4) TIMEOUT, (5) STALL, (6) INFORMAL-CLEAN) are NOT "review complete" signals for swarm purposes: each one MUST force either fresh poller re-dispatch on the same PR or explicit user escalation BEFORE the monitor surfaces the Phase 5 -> 6 gate. The monitor enforces this structurally via `task swarm:verify-review-clean` (#1364); see `skills/deft-directive-swarm/SKILL.md` Phase 5 Exit Condition for the cohort verifier mandate. A poller that has terminated lifecycle-clean (i.e. the sub-agent process exited normally) but with `clean_gate_holdout != None` HAS NOT "reported review-clean" for swarm-cycle purposes -- the verifier picks the gap up and the monitor re-dispatches.
|
|
429
|
+
|
|
430
|
+
### (2) NEW P0/P1 FINDINGS
|
|
431
|
+
|
|
432
|
+
`last_reviewed_sha` matches HEAD AND `has_blocking` is True. Do NOT exit on P2 -- those are non-blocking style suggestions per `skills/deft-directive-review-cycle/SKILL.md`.
|
|
433
|
+
|
|
434
|
+
Address the findings per Phase 2 Step 2-3 of the review-cycle skill: read every finding, plan a single coherent batch, run `task check`, commit with message `fix: address Greptile review findings (batch)`, push. After the push, RESET the poll counter (the new commit triggers a fresh Greptile review pass) and continue polling. Do NOT exit -- this is the loop body of the review-cycle skill.
|
|
435
|
+
|
|
436
|
+
If the same review surfaces 3 consecutive review cycles (push -> review -> still P0/P1 -> push -> review -> still P0/P1 -> push -> review -> still P0/P1), escalate to parent:
|
|
437
|
+
|
|
438
|
+
Subject: PR #{pr_number} escalation -- 3 review cycles still surfacing P0/P1
|
|
439
|
+
Body:
|
|
440
|
+
Three consecutive review cycles after push still surfaced P0/P1 findings.
|
|
441
|
+
Latest findings: <summary>
|
|
442
|
+
Latest HEAD: <sha>
|
|
443
|
+
-- no more polling, exiting now
|
|
444
|
+
|
|
445
|
+
### (3) ERRORED
|
|
446
|
+
|
|
447
|
+
The Greptile rolling-summary comment body equals `Greptile encountered an error while reviewing this PR` (#526) on the current HEAD.
|
|
448
|
+
|
|
449
|
+
Retry ONCE: post `@greptileai review` as a PR comment via `gh pr comment {pr_number} --repo {repo} --body "@greptileai review"` and continue polling for an additional 10 minutes. If the retry also errors, exit:
|
|
450
|
+
|
|
451
|
+
Subject: PR #{pr_number} Greptile errored -- escalation required
|
|
452
|
+
Body:
|
|
453
|
+
Greptile errored on HEAD <sha>; retry via @greptileai also errored.
|
|
454
|
+
Parent should escalate to user with the three-way choice per
|
|
455
|
+
skills/deft-directive-swarm/SKILL.md Phase 6 Step 1:
|
|
456
|
+
(a) wait longer (~15-20 min)
|
|
457
|
+
(b) push an empty `chore: retrigger greptile` commit
|
|
458
|
+
(c) merge with documented override (rationale in merge commit body)
|
|
459
|
+
-- no more polling, exiting now
|
|
460
|
+
|
|
461
|
+
### (4) TIMEOUT
|
|
462
|
+
|
|
463
|
+
`{poll_cap_minutes}` minutes elapsed without reaching CLEAN, NEW P0/P1 FINDINGS escalation, ERRORED, or (5) STALL.
|
|
464
|
+
|
|
465
|
+
Send:
|
|
466
|
+
|
|
467
|
+
Subject: PR #{pr_number} poll cap exceeded -- parent should escalate
|
|
468
|
+
Body:
|
|
469
|
+
{poll_cap_minutes}-minute poll cap exceeded.
|
|
470
|
+
Latest state:
|
|
471
|
+
last_reviewed_sha: <sha or "unparsed">
|
|
472
|
+
head_sha: <sha>
|
|
473
|
+
confidence: <N or "unparsed">
|
|
474
|
+
P0 count: <N>
|
|
475
|
+
P1 count: <N>
|
|
476
|
+
Greptile errored: <true|false>
|
|
477
|
+
CI: <statuses>
|
|
478
|
+
clean_gate_holdout: <which-of-the-five-conditions-failed>
|
|
479
|
+
-- no more polling, exiting now
|
|
480
|
+
|
|
481
|
+
### (5) STALL
|
|
482
|
+
|
|
483
|
+
`has_blocking` is False (no blocking signals detected) AND `is_clean` is False (CLEAN gate not satisfied) for N consecutive polls (default N=3, ~4.5 min at the recommended 90s interval). This is the bounded fail-loud exit (#1039 AC-2) that surfaces a parse-gap or detector-coverage gap immediately, instead of letting the poller burn its `{poll_cap_minutes}`-minute cap polling stale state (PR #1038, 2026-05-11; the recurrence record). The exit message MUST surface `clean_gate_holdout` so the operator sees WHICH of the six CLEAN-gate conditions blocked progress (#1039 AC-3, extended to the (6) terminal_check_run condition per #1259).
|
|
484
|
+
|
|
485
|
+
Increment the `stall_streak` counter introduced under `## CLEAN gate evaluation, clean_gate_holdout, and per-poll instrumentation (#1039)` above; reset on any poll where `has_blocking` or `is_clean` flips True. When `stall_streak >= 3`, send:
|
|
486
|
+
|
|
487
|
+
Subject: PR #{pr_number} poll loop wedged -- terminal-condition detection failure
|
|
488
|
+
Body:
|
|
489
|
+
Detector cannot reach CLEAN or NEW P0/P1 FINDINGS but no blocking signals
|
|
490
|
+
are visible. Likely terminal-condition detection gap on this PR's review surface.
|
|
491
|
+
Latest state:
|
|
492
|
+
last_reviewed_sha: <sha or "unparsed">
|
|
493
|
+
head_sha: <sha>
|
|
494
|
+
confidence: <N or "unparsed">
|
|
495
|
+
has_blocking: <True|False>
|
|
496
|
+
ci_failures: <N>
|
|
497
|
+
errored: <true|false>
|
|
498
|
+
clean_gate_holdout: <which-of-the-five-conditions-failed>
|
|
499
|
+
Parent should diagnose via Tier 1 instrumentation log.
|
|
500
|
+
-- no more polling, exiting now
|
|
501
|
+
|
|
502
|
+
### (6) INFORMAL-CLEAN
|
|
503
|
+
|
|
504
|
+
`is_informal_clean_missing_canonical_fields(...)` is True on the current poll: Greptile posted informal clean prose but omitted BOTH canonical fields `Last reviewed commit:` and `Confidence Score: X/5`. This is the **`informal-clean missing-canonical-fields`** blocked recovery state (#1543). Do NOT keep polling.
|
|
505
|
+
|
|
506
|
+
Send:
|
|
507
|
+
|
|
508
|
+
Subject: PR #{pr_number} informal-clean missing canonical fields -- recovery required
|
|
509
|
+
Body:
|
|
510
|
+
Greptile informal-clean missing-canonical-fields state (#1543).
|
|
511
|
+
Latest Greptile comment says the diff is clean / prior issues resolved,
|
|
512
|
+
but lacks canonical `Last reviewed commit:` and `Confidence Score: X/5`.
|
|
513
|
+
Recovery options:
|
|
514
|
+
(1) comment `@greptileai review` to retrigger a canonical rolling summary
|
|
515
|
+
(2) wait for Greptile to edit its primary summary with both canonical fields
|
|
516
|
+
(3) document an explicit operator override per swarm SKILL Phase 6 Step 1
|
|
517
|
+
Latest state:
|
|
518
|
+
last_reviewed_sha: unparsed
|
|
519
|
+
head_sha: <sha>
|
|
520
|
+
confidence: unparsed
|
|
521
|
+
has_blocking: False
|
|
522
|
+
-- no more polling, exiting now
|
|
523
|
+
|
|
524
|
+
**Swarm-orchestrated terminal contract (#1543):** like `(3) ERRORED`, `(4) TIMEOUT`, and `(5) STALL`, this exit is NOT a "review complete" signal for `task swarm:verify-review-clean`. The monitor MUST re-dispatch or escalate; do NOT surface the Phase 5 -> 6 merge gate until canonical evidence or a documented override resolves the state.
|
|
525
|
+
|
|
526
|
+
## Constraints (non-negotiable)
|
|
527
|
+
|
|
528
|
+
- ⊗ Do NOT chain destructive commands (`rm`, `Remove-Item`, `del`, `git clean`, `git reset --hard`) with non-destructive ones in a single shell call. Each in its OWN call. Chaining poisons Warp's `is_risky` classification on the whole pipeline and forces user approval on every otherwise-safe operation.
|
|
529
|
+
- ⊗ Do NOT clean up the commit-message temp file in the same shell call as the `git commit -F <tmp>` invocation. Leave it orphaned -- worktree teardown reclaims it.
|
|
530
|
+
- ⊗ Do NOT poll in the parent's own turn. You are the poller; the parent yields to wait for your messages.
|
|
531
|
+
- ⊗ Do NOT split your role into separate "poll" and "fix" agents. You are a review-cycle agent embodying `skills/deft-directive-review-cycle/SKILL.md` end-to-end.
|
|
532
|
+
- ⊗ Do NOT use `git reset --hard` or `git push --force` (or `--force-with-lease`) on this branch. The monitor owns rebase cascade per Phase 6 Step 1 of `skills/deft-directive-swarm/SKILL.md`.
|
|
533
|
+
- ! Set `$env:GIT_EDITOR = "true"` (Windows PowerShell) or `GIT_EDITOR=true` (Unix) BEFORE any git command that could open an editor (rebase, commit --amend) to prevent terminal lockup.
|
|
534
|
+
- ! Use Python scripts (single `run_shell_command` call) for the poll loop, NEVER shell `Start-Sleep` + repeated tool calls. The Python script handles `time.sleep({poll_interval_seconds})` between polls and exits when a terminal condition fires.
|
|
535
|
+
- ! Always pass `do_not_summarize_output: true` semantics when fetching `gh pr view --comments` -- summarizers silently drop the Outside-Diff section.
|
|
536
|
+
- ! Send a status message to `{parent_agent_id}` at start (acknowledging the task) and at every terminal exit (CLEAN / NEW P0/P1 FINDINGS escalation / ERRORED / TIMEOUT / STALL). Do NOT silently complete.
|
|
537
|
+
|
|
538
|
+
## Implementation Notes
|
|
539
|
+
|
|
540
|
+
Dogfood lessons captured during the #727 self-review cycle. The template body above already prescribes the correct behaviour; these notes record the specific micro-bugs prior poller scripts hit so future implementations can avoid them.
|
|
541
|
+
|
|
542
|
+
- **Do NOT window-slice the Greptile body before searching for `Confidence Score:` or `Last reviewed commit:`.** Greptile places the confidence header near the TOP of its summary, while the `Last reviewed commit:` anchor is near the BOTTOM (typically ~5KB lower in real PRs). A naive optimization like `body[idx-200:idx+4000]` around the SHA anchor will silently miss the confidence score. Always run `re.search(...)` against the FULL `gh pr view --comments` output. (Captured during the #727 dogfood self-review where this exact micro-optimization caused the prior agent's poll script to miss the confidence parse; the template's prescribed full-body search is correct.)
|
|
543
|
+
- **`Last reviewed commit:` regex is markdown-link aware AND non-greedy.** The recommended pattern is `r"Last reviewed commit:\s*\[.*?\]\(https?://github\.com/[^/]+/[^/]+/commit/(?P<sha>[0-9a-f]{{7,40}})"`. The naive inline-SHA form (`r"Last reviewed commit:\s*([0-9a-f]{{7,40}})"`) does NOT match Greptile's actual output -- Greptile emits `Last reviewed commit: [<subject>](<url>/commit/<sha>)` -- and is the bug Agent D's poll script hit (see #727 followup comments). The link-text group MUST be the non-greedy `.*?`, NOT a `[^\]]*` negated-bracket class: a commit subject containing escaped brackets (e.g. `add \[Unreleased\] entry`) makes `[^\]]*` stop at the first escaped `\]` rather than the real `](` boundary, so the regex fails to match and `last_reviewed_sha` falls to `None` -- a false STALL / TIMEOUT on an otherwise-clean review (#1326).
|
|
544
|
+
- **P0/P1 detection uses the triple-tier detector at `### P0/P1 findings detection` above (#910), extended with Tier 2.5 SLizard heading form (#1035).** The detector body in this template is the authoritative implementation -- combine Tier 1 (HTML badge count via `body.count('<img alt="P0"')` / `body.count('<img alt="P1"')`), Tier 2 (markdown-bullet bold scan with line-scoped negation guards), Tier 2.5 (SLizard `### P[01]` heading-form regex `^#{{1,6}}\s+P([01])\s*[\u00b7\u2027\u2022\-]\s` with the SAME line-scoped negation-context guard as Tier 2), and Tier 3 (inline-prose sentinels: `Not safe to merge` substring + count-prose regex + line-anchored `^P[01] -- ` regex) via `has_blocking = (max(tier1_p0, tier2_p0, tier25_p0) + max(tier1_p1, tier2_p1, tier25_p1)) > 0 or tier3_sentinel`. Tier 2.5 is numbered 2.5 (not renumbered as a new Tier 4) so existing detector citations in `meta/lessons.md` and the swarm-skill anti-patterns -- which key on the 1/2/3 names -- stay stable. The single-tier badge-only approach is INSUFFICIENT and was the recurrence cause of three false-negatives in the v0.25.1 swarm session (#907 first review, #908 first review, #908 retrigger); the triple-tier-without-Tier-2.5 detector missed SLizard's `### P1 · ...` heading form on PR #1034 (2026-05-11, #1035). A `\b(P0|P1)\b` raw substring scan false-positives on the clean-summary phrase `No P0 or P1 issues found` and is forbidden -- the Tier 2 / Tier 2.5 / Tier 3 implementations above embed the negation-context guards (`No `, `Zero `, `0 `, lowercase `no `) and MUST be used verbatim.
|
|
545
|
+
- **The detector strips code-fence regions before counting, but a residual self-reference false-positive remains (#1004).** On PRs that modify the detector itself (`templates/swarm-greptile-poller-prompt.md` or `tests/content/test_swarm_poller_template.py`), Greptile quotes the detector's own tokens (`<img alt="P0"`, `Not safe to merge`, `Three P1 findings`, ...) verbatim when describing the diff. The `strip_code_fences(body)` pre-process at the top of the `### P0/P1 findings detection` block removes triple-backtick fenced blocks and `<code>` / `<pre>` HTML regions before Tier 1/2/3 detection, so tokens Greptile quotes inside fences no longer count (option 1 from #1004). The RESIDUAL case: if Greptile quotes a detector token in UNFENCED prose (e.g. an inline summary sentence that names `Not safe to merge` while describing the change), or conversely renders a REAL finding inside a fence, the strip cannot disambiguate. When you are reviewing a PR that touches the detector and the poller reports `has_blocking=True`, manually confirm the flagged tokens are genuine findings and not Greptile quoting the detector's own source before escalating. Recurrence record: PR #996 first review (2026-05-08) reported `P0=3/P1=3/tier3=True` against an actual `1 P1 + 2 P2`; a careful poller logged the discrepancy and continued, but a less-careful poller would have looped on findings that do not exist (#1004, #910 follow-up).
|
|
546
|
+
- **CLEAN-gate detector failures fail LOUD via (5) STALL, not silent via 30-min TIMEOUT (#1039).** The pre-#1039 poller could not distinguish a parse-gap (regex doesn't match Greptile's actual rendering -> `last_reviewed_sha = None` -> condition (1) False) from "Greptile is still working" (no review posted yet -> same outcome), so the wedged signature kept both `has_blocking = False` AND `is_clean = False` and the poller burned its `{poll_cap_minutes}`-minute cap. The (5) STALL terminal exit above bounds the wedged-signature exit at ~4.5 min (N=3 consecutive polls at the recommended 90s interval) and the `clean_gate_holdout` field in BOTH (4) TIMEOUT and (5) STALL exit messages names the FIRST of the six conditions that blocked the gate -- the operator MUST NEVER have to ask "which condition failed?" Recurrence record: PR #1038 (2026-05-11) poller agent `5794b0e7-...` wedged 30 min on a textbook clean review; maintainer intervened out-of-band; #1039. The INCOMPLETE_BUT_RATED variant -- rolling summary parses clean but the Greptile check-run is non-terminal -- is held by condition (6) `terminal_check_run` and bounded by the same (5) STALL exit (#1259).
|
|
547
|
+
|
|
548
|
+
## Cross-references
|
|
549
|
+
|
|
550
|
+
- `skills/deft-directive-review-cycle/SKILL.md` -- the canonical review-cycle skill you embody end-to-end.
|
|
551
|
+
- `skills/deft-directive-swarm/SKILL.md` Phase 6 Sub-Agent Role Separation -- the rules that mandate using THIS template (#727).
|
|
552
|
+
- `skills/deft-directive-swarm/SKILL.md` Phase 6 Step 1 -- Greptile errored-state retry / escalation procedure (#526).
|
|
553
|
+
- `meta/lessons.md` `## Orchestrator Role Separation + Canonical Poller Template (2026-04)` -- short cross-reference; the rule body lives in the skills above (per `main.md` Rule Authority [AXIOM]).
|
|
554
|
+
- #727 -- this template's acceptance issue and the full anti-pattern record (rm-chaining, parsing-bug recurrence, role-conflation in implementation-agent prompts).
|
|
555
|
+
- #1039 -- (5) STALL terminal exit + Tier 1 instrumentation + Tier 3 per-condition fail-loud (`clean_gate_holdout`); the third recurrence in this template's detector-gap chain after #910 (triple-tier) and #1035 (Tier 2.5 + confidence-heading).
|
|
556
|
+
- #1364 -- cohort-level CLEAN verification gate (`task swarm:verify-review-clean`, `scripts/swarm_verify_review_clean.py`). The (1) CLEAN section's swarm-orchestrated terminal contract block declares that only the exact `PR #{pr_number} CLEAN -- ready for merge` subject with `confidence > 3` on current HEAD is an acceptable "review complete" signal for the swarm monitor's Phase 5 -> 6 transition; the cohort verifier picks up any other terminal exit ((2) NEW P0/P1 FINDINGS escalation, (3) ERRORED, (4) TIMEOUT, (5) STALL) and holds the merge gate until fresh poller re-dispatch or explicit user escalation resolves it. Recurrence record: #1166 swarm execution where multiple pollers exited with `clean_gate_holdout=confidence` (confidence == 3) and the monitor still raised the Phase 5 -> 6 gate because the trigger keyed on "all pollers have reported back" rather than "every PR in the cohort is objectively CLEAN".
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# User Preferences
|
|
2
|
+
|
|
3
|
+
Legend (from RFC2119): !=MUST, ~=SHOULD, ≉=SHOULD NOT, ⊗=MUST NOT, ?=MAY.
|
|
4
|
+
|
|
5
|
+
**Rule Precedence**: This file has HIGHEST precedence - overrides all other deft rules.
|
|
6
|
+
Only add items here that **override or extend** the defaults in `deft/main.md` and related files.
|
|
7
|
+
|
|
8
|
+
## 👤 Identity
|
|
9
|
+
|
|
10
|
+
Address the user as: **[Your Name]**
|
|
11
|
+
|
|
12
|
+
## 📏 Overrides
|
|
13
|
+
|
|
14
|
+
<!-- Only specify if different from deft defaults (e.g., coverage ≥85%, hyphens in filenames) -->
|
|
15
|
+
|
|
16
|
+
**Primary Languages**:
|
|
17
|
+
- [Language 1 - e.g., Python, Go, TypeScript]
|
|
18
|
+
- [Language 2]
|
|
19
|
+
|
|
20
|
+
**Code Style** (preferences beyond language defaults):
|
|
21
|
+
- [e.g., Prefer functional over OOP]
|
|
22
|
+
- [e.g., Explicit over implicit]
|
|
23
|
+
|
|
24
|
+
## 🎯 Custom Rules
|
|
25
|
+
|
|
26
|
+
<!-- Rules unique to you that aren't covered by deft core -->
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
**Note**: You can edit this file anytime to update your preferences.
|
|
31
|
+
**See**: [deft/main.md](./deft/main.md) for framework defaults.
|