devrites 1.19.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/.claude-plugin/marketplace.json +24 -0
- package/.claude-plugin/plugin.json +43 -0
- package/CHANGELOG.md +391 -0
- package/LICENSE +56 -0
- package/NOTICE.md +18 -0
- package/README.md +582 -0
- package/SECURITY.md +193 -0
- package/bin/devrites.mjs +100 -0
- package/docs/architecture.md +272 -0
- package/docs/cli-mcp.md +57 -0
- package/docs/command-map.md +143 -0
- package/docs/flow.md +360 -0
- package/docs/release.md +29 -0
- package/docs/skills.md +214 -0
- package/docs/usage.md +325 -0
- package/install.sh +359 -0
- package/mcp/devrites-mcp.mjs +103 -0
- package/pack/.claude/agents/devrites-code-reviewer.md +50 -0
- package/pack/.claude/agents/devrites-doubt-reviewer.md +55 -0
- package/pack/.claude/agents/devrites-frontend-reviewer.md +52 -0
- package/pack/.claude/agents/devrites-performance-reviewer.md +47 -0
- package/pack/.claude/agents/devrites-plan-reviewer.md +79 -0
- package/pack/.claude/agents/devrites-security-auditor.md +53 -0
- package/pack/.claude/agents/devrites-simplifier-reviewer.md +75 -0
- package/pack/.claude/agents/devrites-slice-wright.md +181 -0
- package/pack/.claude/agents/devrites-spec-reviewer.md +72 -0
- package/pack/.claude/agents/devrites-strategy-reviewer.md +62 -0
- package/pack/.claude/agents/devrites-test-analyst.md +47 -0
- package/pack/.claude/hooks/devrites-a1-guard.sh +81 -0
- package/pack/.claude/hooks/devrites-allow.sh +44 -0
- package/pack/.claude/hooks/devrites-cursor.sh +28 -0
- package/pack/.claude/hooks/devrites-orient.sh +53 -0
- package/pack/.claude/hooks/devrites-redwatch.sh +39 -0
- package/pack/.claude/hooks/devrites-refresh-indexes.sh +127 -0
- package/pack/.claude/hooks/devrites-reviewer-readonly.sh +28 -0
- package/pack/.claude/hooks/devrites-statusline.sh +18 -0
- package/pack/.claude/hooks/devrites-stop-gate.sh +45 -0
- package/pack/.claude/hooks/devrites-wright-scope.sh +35 -0
- package/pack/.claude/hooks/hooks.json +52 -0
- package/pack/.claude/rules/README.md +48 -0
- package/pack/.claude/rules/afk-hitl.md +245 -0
- package/pack/.claude/rules/agents.md +98 -0
- package/pack/.claude/rules/anti-patterns.md +48 -0
- package/pack/.claude/rules/code-review.md +38 -0
- package/pack/.claude/rules/coding-style.md +55 -0
- package/pack/.claude/rules/context-hygiene.md +97 -0
- package/pack/.claude/rules/core.md +119 -0
- package/pack/.claude/rules/development-workflow.md +40 -0
- package/pack/.claude/rules/documentation.md +27 -0
- package/pack/.claude/rules/error-handling.md +33 -0
- package/pack/.claude/rules/git-workflow.md +35 -0
- package/pack/.claude/rules/hooks.md +38 -0
- package/pack/.claude/rules/patterns.md +45 -0
- package/pack/.claude/rules/performance.md +27 -0
- package/pack/.claude/rules/prose-style.md +101 -0
- package/pack/.claude/rules/security.md +63 -0
- package/pack/.claude/rules/testing.md +88 -0
- package/pack/.claude/rules/tooling.md +72 -0
- package/pack/.claude/settings.json +53 -0
- package/pack/.claude/skills/devrites-api-interface/SKILL.md +45 -0
- package/pack/.claude/skills/devrites-audit/SKILL.md +73 -0
- package/pack/.claude/skills/devrites-browser-proof/SKILL.md +38 -0
- package/pack/.claude/skills/devrites-debug-recovery/SKILL.md +50 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/build-the-loop.md +47 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/cleanup-and-classify.md +17 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/hypotheses.md +17 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/instrumentation.md +21 -0
- package/pack/.claude/skills/devrites-debug-recovery/reference/regression-test.md +31 -0
- package/pack/.claude/skills/devrites-doubt/SKILL.md +75 -0
- package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +96 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/craft.md +59 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/design-references.md +116 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/fullstack.md +45 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/quality-standards.md +215 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/reuse-first.md +59 -0
- package/pack/.claude/skills/devrites-frontend-craft/reference/shape.md +60 -0
- package/pack/.claude/skills/devrites-interview/SKILL.md +81 -0
- package/pack/.claude/skills/devrites-lib/SKILL.md +76 -0
- package/pack/.claude/skills/devrites-lib/scripts/analyze.sh +78 -0
- package/pack/.claude/skills/devrites-lib/scripts/check-acceptance.sh +75 -0
- package/pack/.claude/skills/devrites-lib/scripts/close-out.sh +47 -0
- package/pack/.claude/skills/devrites-lib/scripts/conventions.py +273 -0
- package/pack/.claude/skills/devrites-lib/scripts/coverage.sh +51 -0
- package/pack/.claude/skills/devrites-lib/scripts/devrites.sh +69 -0
- package/pack/.claude/skills/devrites-lib/scripts/doctor.sh +92 -0
- package/pack/.claude/skills/devrites-lib/scripts/evidence-fresh.sh +63 -0
- package/pack/.claude/skills/devrites-lib/scripts/footprint.sh +45 -0
- package/pack/.claude/skills/devrites-lib/scripts/learnings.sh +74 -0
- package/pack/.claude/skills/devrites-lib/scripts/mutation-gate.sh +52 -0
- package/pack/.claude/skills/devrites-lib/scripts/package-existence.sh +68 -0
- package/pack/.claude/skills/devrites-lib/scripts/preamble.sh +76 -0
- package/pack/.claude/skills/devrites-lib/scripts/progress.sh +103 -0
- package/pack/.claude/skills/devrites-lib/scripts/readiness.sh +62 -0
- package/pack/.claude/skills/devrites-lib/scripts/reconcile.sh +123 -0
- package/pack/.claude/skills/devrites-lib/scripts/resolve.sh +279 -0
- package/pack/.claude/skills/devrites-lib/scripts/stuck.sh +67 -0
- package/pack/.claude/skills/devrites-lib/scripts/test-integrity.sh +87 -0
- package/pack/.claude/skills/devrites-lib/scripts/tick-afk.sh +52 -0
- package/pack/.claude/skills/devrites-prose-craft/SKILL.md +105 -0
- package/pack/.claude/skills/devrites-prose-craft/reference/banned-phrases.md +95 -0
- package/pack/.claude/skills/devrites-prose-craft/reference/examples.md +88 -0
- package/pack/.claude/skills/devrites-prose-craft/reference/structures.md +134 -0
- package/pack/.claude/skills/devrites-refresh-indexes/SKILL.md +54 -0
- package/pack/.claude/skills/devrites-source-driven/SKILL.md +36 -0
- package/pack/.claude/skills/devrites-ux-shape/SKILL.md +121 -0
- package/pack/.claude/skills/devrites-ux-shape/reference/brief-template.md +93 -0
- package/pack/.claude/skills/devrites-ux-shape/reference/visual-direction-probe.md +48 -0
- package/pack/.claude/skills/rite/SKILL.md +135 -0
- package/pack/.claude/skills/rite/reference/menu.md +32 -0
- package/pack/.claude/skills/rite-adopt/SKILL.md +83 -0
- package/pack/.claude/skills/rite-adopt/reference/adoption.md +58 -0
- package/pack/.claude/skills/rite-adopt/reference/anti-patterns.md +19 -0
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +96 -0
- package/pack/.claude/skills/rite-autocomplete/reference/decision-policy.md +35 -0
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +54 -0
- package/pack/.claude/skills/rite-autocomplete/reference/stop-conditions.md +59 -0
- package/pack/.claude/skills/rite-build/SKILL.md +261 -0
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +145 -0
- package/pack/.claude/skills/rite-build/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-build/reference/checkpoint-protocol.md +149 -0
- package/pack/.claude/skills/rite-build/reference/evidence-standard.md +32 -0
- package/pack/.claude/skills/rite-build/reference/frontend-trigger.md +39 -0
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +38 -0
- package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +43 -0
- package/pack/.claude/skills/rite-build/reference/tdd.md +26 -0
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +115 -0
- package/pack/.claude/skills/rite-define/SKILL.md +157 -0
- package/pack/.claude/skills/rite-define/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-define/reference/gates.md +152 -0
- package/pack/.claude/skills/rite-define/reference/plan-template.md +65 -0
- package/pack/.claude/skills/rite-doctor/SKILL.md +50 -0
- package/pack/.claude/skills/rite-frame/SKILL.md +116 -0
- package/pack/.claude/skills/rite-frame/reference/failure-modes.md +68 -0
- package/pack/.claude/skills/rite-handoff/SKILL.md +95 -0
- package/pack/.claude/skills/rite-handoff/reference/handoff-template.md +34 -0
- package/pack/.claude/skills/rite-learn/SKILL.md +82 -0
- package/pack/.claude/skills/rite-plan/SKILL.md +82 -0
- package/pack/.claude/skills/rite-plan/reference/anti-patterns.md +24 -0
- package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +33 -0
- package/pack/.claude/skills/rite-plan/reference/replan-and-repair.md +42 -0
- package/pack/.claude/skills/rite-plan/reference/slicing.md +52 -0
- package/pack/.claude/skills/rite-plan/reference/task-breakdown.md +34 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +90 -0
- package/pack/.claude/skills/rite-polish/reference/anti-ai-slop.md +177 -0
- package/pack/.claude/skills/rite-polish/reference/anti-patterns.md +27 -0
- package/pack/.claude/skills/rite-polish/reference/backend-polish.md +80 -0
- package/pack/.claude/skills/rite-polish/reference/browser-polish-evidence.md +31 -0
- package/pack/.claude/skills/rite-polish/reference/code.md +85 -0
- package/pack/.claude/skills/rite-polish/reference/design-system-discovery.md +35 -0
- package/pack/.claude/skills/rite-polish/reference/harden-checklist.md +109 -0
- package/pack/.claude/skills/rite-polish/reference/ui.md +136 -0
- package/pack/.claude/skills/rite-pressure-test/SKILL.md +43 -0
- package/pack/.claude/skills/rite-prototype/SKILL.md +87 -0
- package/pack/.claude/skills/rite-prove/SKILL.md +120 -0
- package/pack/.claude/skills/rite-prove/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-prove/reference/browser-proof.md +26 -0
- package/pack/.claude/skills/rite-prove/reference/failure-triage.md +25 -0
- package/pack/.claude/skills/rite-prove/reference/proof-ladder.md +26 -0
- package/pack/.claude/skills/rite-prove/reference/test-command-discovery.md +30 -0
- package/pack/.claude/skills/rite-quick/SKILL.md +81 -0
- package/pack/.claude/skills/rite-resolve/SKILL.md +113 -0
- package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +114 -0
- package/pack/.claude/skills/rite-review/SKILL.md +170 -0
- package/pack/.claude/skills/rite-review/reference/anti-patterns.md +32 -0
- package/pack/.claude/skills/rite-review/reference/cognitive-load.md +90 -0
- package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +26 -0
- package/pack/.claude/skills/rite-review/reference/five-axis-review.md +46 -0
- package/pack/.claude/skills/rite-review/reference/nielsen-heuristics.md +130 -0
- package/pack/.claude/skills/rite-review/reference/parallel-dispatch.md +62 -0
- package/pack/.claude/skills/rite-review/reference/performance-review.md +28 -0
- package/pack/.claude/skills/rite-review/reference/security-review.md +32 -0
- package/pack/.claude/skills/rite-seal/SKILL.md +183 -0
- package/pack/.claude/skills/rite-seal/reference/anti-patterns.md +27 -0
- package/pack/.claude/skills/rite-seal/reference/conventions-ledger.md +63 -0
- package/pack/.claude/skills/rite-seal/reference/final-evidence.md +72 -0
- package/pack/.claude/skills/rite-seal/reference/go-no-go.md +37 -0
- package/pack/.claude/skills/rite-seal/reference/parallel-dispatch.md +69 -0
- package/pack/.claude/skills/rite-seal/reference/risk-and-rollback.md +30 -0
- package/pack/.claude/skills/rite-seal/reference/seal-template.md +36 -0
- package/pack/.claude/skills/rite-ship/SKILL.md +120 -0
- package/pack/.claude/skills/rite-ship/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-ship/reference/close-out.md +31 -0
- package/pack/.claude/skills/rite-ship/reference/design-memory.md +120 -0
- package/pack/.claude/skills/rite-ship/reference/git-ship.md +42 -0
- package/pack/.claude/skills/rite-ship/reference/ship-template.md +33 -0
- package/pack/.claude/skills/rite-spec/SKILL.md +126 -0
- package/pack/.claude/skills/rite-spec/reference/acceptance-criteria.md +31 -0
- package/pack/.claude/skills/rite-spec/reference/anti-patterns.md +25 -0
- package/pack/.claude/skills/rite-spec/reference/interview-patterns.md +56 -0
- package/pack/.claude/skills/rite-spec/reference/investigation.md +64 -0
- package/pack/.claude/skills/rite-spec/reference/question-protocol.md +61 -0
- package/pack/.claude/skills/rite-spec/reference/references-intake.md +57 -0
- package/pack/.claude/skills/rite-spec/reference/spec-checklists.md +73 -0
- package/pack/.claude/skills/rite-spec/reference/spec-template.md +124 -0
- package/pack/.claude/skills/rite-spec/reference/state-workspace.md +159 -0
- package/pack/.claude/skills/rite-status/SKILL.md +101 -0
- package/pack/.claude/skills/rite-temper/SKILL.md +119 -0
- package/pack/.claude/skills/rite-temper/reference/anti-patterns.md +29 -0
- package/pack/.claude/skills/rite-temper/reference/review-dimensions.md +65 -0
- package/pack/.claude/skills/rite-temper/reference/scope-modes.md +53 -0
- package/pack/.claude/skills/rite-temper/reference/significance.md +46 -0
- package/pack/.claude/skills/rite-temper/reference/strategy-template.md +90 -0
- package/pack/.claude/skills/rite-vet/SKILL.md +155 -0
- package/pack/.claude/skills/rite-vet/reference/anti-patterns.md +29 -0
- package/pack/.claude/skills/rite-vet/reference/artifacts.md +135 -0
- package/pack/.claude/skills/rite-vet/reference/cross-model.md +41 -0
- package/pack/.claude/skills/rite-vet/reference/depth.md +53 -0
- package/pack/.claude/skills/rite-vet/reference/eng-lenses.md +48 -0
- package/pack/.claude/skills/rite-vet/reference/review-axes.md +167 -0
- package/pack/.claude/skills/rite-zoom-out/SKILL.md +75 -0
- package/package.json +68 -0
- package/scripts/build-release-tarball.sh +74 -0
- package/scripts/check-cross-refs.py +121 -0
- package/scripts/check-no-global-writes.sh +44 -0
- package/scripts/check-rule-uniqueness.sh +73 -0
- package/scripts/devrites-detect.sh +175 -0
- package/scripts/eval-runner.py +273 -0
- package/scripts/grade-feature.sh +104 -0
- package/scripts/install-lib.sh +83 -0
- package/scripts/pin.sh +166 -0
- package/scripts/render-eval-summary.py +48 -0
- package/scripts/run-evals.sh +149 -0
- package/scripts/run-outcome-evals.sh +49 -0
- package/scripts/scan-pack-security.py +209 -0
- package/scripts/scan-supply-chain-iocs.py +127 -0
- package/scripts/supply-chain-iocs.json +11 -0
- package/scripts/sync-version.sh +56 -0
- package/scripts/validate-frontmatter.py +149 -0
- package/scripts/validate-workflow-security.py +86 -0
- package/scripts/validate.sh +234 -0
- package/uninstall.sh +137 -0
- package/update.sh +196 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# DevRites progress footer — render the active feature's position deterministically.
|
|
3
|
+
# Run LAST (output step) by every workspace-operating rite-* skill via the standard
|
|
4
|
+
# resolution snippet, the mirror of preamble.sh (which runs first). Read-only; never
|
|
5
|
+
# mutates workspace files.
|
|
6
|
+
#
|
|
7
|
+
# Prints three lines of "chrome" the skill's own fact lines sit under:
|
|
8
|
+
# ── rite-<phase> ───────────────────────────
|
|
9
|
+
# Slice <built>/<total> ██████░░░░ <last-built name> ✓ (or "✅ ALL BUILT" at N/N)
|
|
10
|
+
# Flow spec ✓ define ✓ build ◉ prove ○ … ship ○
|
|
11
|
+
#
|
|
12
|
+
# The slice meter answers "how many steps left" (built/total); the ✅ ALL BUILT marker
|
|
13
|
+
# + the `build ✓` ribbon glyph answer "is the build phase complete". The skill prints the
|
|
14
|
+
# what-was-done / next-step / hygiene lines beneath; this script owns only the visuals so
|
|
15
|
+
# every rite-* footer looks the same with zero model drift.
|
|
16
|
+
#
|
|
17
|
+
# Usage: progress.sh [slug]
|
|
18
|
+
# slug — optional override; defaults to .devrites/ACTIVE
|
|
19
|
+
#
|
|
20
|
+
# Paths resolve relative to CWD (the project root), like preamble.sh. Prints nothing and
|
|
21
|
+
# exits 0 when there is no active workspace, so pre-spec callers stay silent.
|
|
22
|
+
|
|
23
|
+
set -u
|
|
24
|
+
slug="${1:-$(cat .devrites/ACTIVE 2>/dev/null)}"
|
|
25
|
+
d=".devrites/work/$slug"
|
|
26
|
+
s="$d/state.md"
|
|
27
|
+
|
|
28
|
+
[ -n "$slug" ] && [ -f "$s" ] || exit 0
|
|
29
|
+
|
|
30
|
+
# --- phase ----------------------------------------------------------------------------
|
|
31
|
+
# First whitespace token after "Phase:" (ignores any trailing " # comment").
|
|
32
|
+
phase="$(awk -F': *' '/^- Phase:/{print $2; exit}' "$s" | awk '{print $1}')"
|
|
33
|
+
[ -n "$phase" ] || phase="spec"
|
|
34
|
+
|
|
35
|
+
# --- slice tally ----------------------------------------------------------------------
|
|
36
|
+
# total / built counts + the name of the last built slice, parsed from "## Slice progress".
|
|
37
|
+
read -r total built lastbuilt <<EOF
|
|
38
|
+
$(awk '
|
|
39
|
+
/^## Slice progress/ { inp=1; next }
|
|
40
|
+
inp && /^## / { inp=0 }
|
|
41
|
+
inp && /^- \[/ {
|
|
42
|
+
total++
|
|
43
|
+
name=$0
|
|
44
|
+
sub(/^- \[[^]]*\][[:space:]]*/, "", name) # drop "- [x] "
|
|
45
|
+
sub(/^Slice[[:space:]]*[0-9]+:[[:space:]]*/, "", name) # drop "Slice N: "
|
|
46
|
+
sub(/[[:space:]]+(built|pending)[[:space:]]*$/, "", name) # drop trailing "built|pending"
|
|
47
|
+
sub(/[[:space:]]+[^[:alnum:][:space:]]+[[:space:]]*$/, "", name) # drop the " — " separator
|
|
48
|
+
if ($0 ~ /\[[xX]\]/) { built++; last=name }
|
|
49
|
+
}
|
|
50
|
+
END { printf "%d %d %s", total+0, built+0, last }
|
|
51
|
+
' "$s")
|
|
52
|
+
EOF
|
|
53
|
+
total="${total:-0}"; built="${built:-0}"
|
|
54
|
+
|
|
55
|
+
# --- header rule ----------------------------------------------------------------------
|
|
56
|
+
title="rite-${phase}"
|
|
57
|
+
header="── ${title} "
|
|
58
|
+
while [ "${#header}" -lt 44 ]; do header="${header}─"; done
|
|
59
|
+
printf '%s\n' "$header"
|
|
60
|
+
|
|
61
|
+
# --- slice meter ----------------------------------------------------------------------
|
|
62
|
+
# Bar is 10 cells; filled = round(built/total*10). Skipped entirely before any slices exist.
|
|
63
|
+
if [ "$total" -gt 0 ]; then
|
|
64
|
+
filled=$(( (built * 10 + total / 2) / total ))
|
|
65
|
+
[ "$filled" -gt 10 ] && filled=10
|
|
66
|
+
bar=""; i=0
|
|
67
|
+
while [ "$i" -lt "$filled" ]; do bar="${bar}█"; i=$((i+1)); done
|
|
68
|
+
while [ "$i" -lt 10 ]; do bar="${bar}░"; i=$((i+1)); done
|
|
69
|
+
if [ "$built" -ge "$total" ]; then
|
|
70
|
+
printf 'Slices %d/%d %s ✅ ALL BUILT\n' "$built" "$total" "$bar"
|
|
71
|
+
else
|
|
72
|
+
tail=""
|
|
73
|
+
[ -n "$lastbuilt" ] && tail=" ${lastbuilt} ✓"
|
|
74
|
+
printf 'Slice %d/%d %s%s\n' "$built" "$total" "$bar" "$tail"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# --- flow ribbon ----------------------------------------------------------------------
|
|
79
|
+
# Lifecycle spine; optional temper/vet appear only once their artifact exists.
|
|
80
|
+
order=(spec)
|
|
81
|
+
[ -f "$d/strategy.md" ] && order+=(temper)
|
|
82
|
+
order+=(define)
|
|
83
|
+
[ -f "$d/eng-review.md" ] && order+=(vet)
|
|
84
|
+
order+=(build prove polish review seal ship)
|
|
85
|
+
|
|
86
|
+
# Current index in the spine. `plan` is replan → sits at build; `done` is past ship.
|
|
87
|
+
cur="$phase"; [ "$cur" = "plan" ] && cur="build"
|
|
88
|
+
idx=-1
|
|
89
|
+
if [ "$phase" = "done" ]; then
|
|
90
|
+
idx=${#order[@]}
|
|
91
|
+
else
|
|
92
|
+
for n in "${!order[@]}"; do [ "${order[$n]}" = "$cur" ] && { idx=$n; break; }; done
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
ribbon="Flow "
|
|
96
|
+
for n in "${!order[@]}"; do
|
|
97
|
+
if [ "$n" -lt "$idx" ]; then g="✓"
|
|
98
|
+
elif [ "$n" -eq "$idx" ]; then g="◉"
|
|
99
|
+
else g="○"
|
|
100
|
+
fi
|
|
101
|
+
ribbon="${ribbon} ${order[$n]} ${g}"
|
|
102
|
+
done
|
|
103
|
+
printf '%s\n' "$ribbon"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Build-readiness gate for DevRites — turns /rite-build's step-0 prose checks
|
|
3
|
+
# ("if Status is awaiting_human → STOP", "if no Plan approved → STOP") into a
|
|
4
|
+
# deterministic exit code, the same way tick-afk.sh enforces the AFK cap. A gate
|
|
5
|
+
# the model *is* stopped by beats a gate the model is *asked* to honor.
|
|
6
|
+
# Read-only; never mutates the workspace.
|
|
7
|
+
#
|
|
8
|
+
# Usage: readiness.sh [slug]
|
|
9
|
+
# slug — optional; defaults to .devrites/ACTIVE
|
|
10
|
+
#
|
|
11
|
+
# Checks .devrites/work/<slug>/state.md, in order:
|
|
12
|
+
# - workspace + state.md exist
|
|
13
|
+
# - Status != awaiting_human (a HITL gate is open)
|
|
14
|
+
# - Status != blocked
|
|
15
|
+
# - "Plan approved:" is set (/rite-define writes it when the human confirms)
|
|
16
|
+
#
|
|
17
|
+
# Exit codes:
|
|
18
|
+
# 0 ready to build
|
|
19
|
+
# 2 plan not approved → /rite-define (then confirm the plan)
|
|
20
|
+
# 3 awaiting human → /rite-resolve <qid> "<answer>"
|
|
21
|
+
# 4 blocked → /rite-plan repair | unblock
|
|
22
|
+
# 5 no workspace / state.md → /rite-spec <feature>
|
|
23
|
+
|
|
24
|
+
set -euo pipefail
|
|
25
|
+
|
|
26
|
+
slug="${1:-$(cat .devrites/ACTIVE 2>/dev/null || true)}"
|
|
27
|
+
d=".devrites/work/$slug"
|
|
28
|
+
s="$d/state.md"
|
|
29
|
+
if [ -z "$slug" ] || [ ! -f "$s" ]; then
|
|
30
|
+
printf 'readiness: no active workspace/state.md (slug=%s) — run /rite-spec <feature>\n' "${slug:-<unset>}" >&2
|
|
31
|
+
exit 5
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# Read a state.md field. Tolerates the markdown-bullet rendering ("- Key: val")
|
|
35
|
+
# and a trailing " | none" / "# comment".
|
|
36
|
+
field() {
|
|
37
|
+
awk -v k="$1" 'match($0, "^[[:space:]]*-?[[:space:]]*" k ":[[:space:]]*") {
|
|
38
|
+
rest = substr($0, RLENGTH + 1)
|
|
39
|
+
sub(/[[:space:]]*(#|\|).*$/, "", rest)
|
|
40
|
+
sub(/[[:space:]]+$/, "", rest)
|
|
41
|
+
print rest; exit
|
|
42
|
+
}' "$s"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
status="$(field Status)"
|
|
46
|
+
case "$status" in
|
|
47
|
+
awaiting_human)
|
|
48
|
+
printf 'readiness: STOP — Status: awaiting_human. Resume with /rite-resolve <qid> "<answer>".\n' >&2
|
|
49
|
+
exit 3 ;;
|
|
50
|
+
blocked)
|
|
51
|
+
printf 'readiness: STOP — Status: blocked. Repair with /rite-plan repair (or unblock).\n' >&2
|
|
52
|
+
exit 4 ;;
|
|
53
|
+
esac
|
|
54
|
+
|
|
55
|
+
approved="$(field 'Plan approved')"
|
|
56
|
+
if [ -z "$approved" ] || [ "$approved" = "none" ]; then
|
|
57
|
+
printf 'readiness: STOP — plan not approved (state.md has no "Plan approved"). Run /rite-define.\n' >&2
|
|
58
|
+
exit 2
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
printf 'readiness: OK — plan approved %s, status %s. Ready to build.\n' "$approved" "${status:-running}"
|
|
62
|
+
exit 0
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Reconcile gate for /rite-build — proves the orchestrator never edited source
|
|
3
|
+
# itself. The slice-wright (a dispatched subagent) is the ONLY writer of code +
|
|
4
|
+
# tests; the orchestrator writes bookkeeping under .devrites/ and nothing else.
|
|
5
|
+
# This turns that rule (A1: "the orchestrator never edits source") into an exit
|
|
6
|
+
# code instead of a prose promise the model can rationalize past — same doctrine
|
|
7
|
+
# as readiness.sh / tick-afk.sh. Read-only w.r.t. the user's git index.
|
|
8
|
+
#
|
|
9
|
+
# Two phases, both called by /rite-build:
|
|
10
|
+
# snapshot — step 3, right BEFORE dispatching the wright. Captures the working
|
|
11
|
+
# tree (tracked + untracked, .gitignore honored) as a git tree
|
|
12
|
+
# object, without touching the user's real index/staging. The base
|
|
13
|
+
# file doubles as the "mid-build window" marker: while it exists the
|
|
14
|
+
# optional pre-block hook (devrites-a1-guard.sh) is armed; a clean
|
|
15
|
+
# check removes it (a violation keeps it hot until re-dispatch).
|
|
16
|
+
# check — step 6 (record), AFTER the wright returns and the orchestrator has
|
|
17
|
+
# written the wright's reported `Files changed` paths (one per line)
|
|
18
|
+
# to .devrites/work/<slug>/.reconcile-claimed. Diffs the tree against
|
|
19
|
+
# the snapshot; every changed path that is NOT under .devrites/ and
|
|
20
|
+
# NOT in the claimed manifest was edited outside the wright -> STOP.
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# reconcile.sh snapshot [slug]
|
|
24
|
+
# reconcile.sh check [slug]
|
|
25
|
+
# slug — optional; defaults to .devrites/ACTIVE
|
|
26
|
+
#
|
|
27
|
+
# Exit codes:
|
|
28
|
+
# 0 ok — clean (check), snapshot written, or gate skipped (non-git repo /
|
|
29
|
+
# inline fallback) with a printed notice
|
|
30
|
+
# 5 VIOLATION — source changed outside the wright's claimed set: STOP. A1
|
|
31
|
+
# breach; re-dispatch the wright (continue it once) or revert the edits
|
|
32
|
+
# 6 setup error — bad args, missing snapshot, or missing claimed manifest
|
|
33
|
+
|
|
34
|
+
set -euo pipefail
|
|
35
|
+
|
|
36
|
+
mode="${1:-}"
|
|
37
|
+
slug="${2:-$(cat .devrites/ACTIVE 2>/dev/null || true)}"
|
|
38
|
+
|
|
39
|
+
# Non-git project: nothing to diff against. Skip rather than block the build.
|
|
40
|
+
root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
|
|
41
|
+
if [ -z "$root" ]; then
|
|
42
|
+
printf 'reconcile: not a git repo — gate skipped, verify the diff by hand.\n' >&2
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
d=".devrites/work/$slug"
|
|
47
|
+
base="$d/.reconcile-base"
|
|
48
|
+
claimed="$d/.reconcile-claimed"
|
|
49
|
+
inline="$d/.reconcile-inline"
|
|
50
|
+
|
|
51
|
+
if [ -z "$slug" ] || [ ! -d "$d" ]; then
|
|
52
|
+
printf 'reconcile: no active workspace (slug=%s) — nothing to reconcile.\n' "${slug:-<unset>}" >&2
|
|
53
|
+
exit 6
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
# Capture the whole working tree (tracked + untracked, .gitignore respected) as a
|
|
57
|
+
# tree object via a throwaway index, so the user's real index/staging is never
|
|
58
|
+
# touched. Prints the tree sha on stdout.
|
|
59
|
+
worktree_tree() {
|
|
60
|
+
local idx="${TMPDIR:-/tmp}/devrites-reconcile-$$.idx"
|
|
61
|
+
rm -f "$idx"
|
|
62
|
+
GIT_INDEX_FILE="$idx" git -C "$root" add -A >/dev/null 2>&1
|
|
63
|
+
local tree; tree="$(GIT_INDEX_FILE="$idx" git -C "$root" write-tree)"
|
|
64
|
+
rm -f "$idx"
|
|
65
|
+
printf '%s\n' "$tree"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# Close the mid-build window: drop the base (disarms the a1-guard hook) and the
|
|
69
|
+
# per-slice manifest/sentinel. Called only when the slice is accepted clean or
|
|
70
|
+
# was the inline fallback — a violation deliberately leaves the window hot.
|
|
71
|
+
close_window() { rm -f "$base" "$claimed" "$inline"; }
|
|
72
|
+
|
|
73
|
+
case "$mode" in
|
|
74
|
+
snapshot)
|
|
75
|
+
rm -f "$inline" "$claimed" # clear any stale sentinel / prior-slice manifest
|
|
76
|
+
worktree_tree > "$base" # base present == mid-build window open
|
|
77
|
+
printf 'reconcile: snapshot captured for %s.\n' "$slug"
|
|
78
|
+
exit 0 ;;
|
|
79
|
+
check)
|
|
80
|
+
# Inline fallback: the orchestrator legitimately wrote the code itself
|
|
81
|
+
# (no wright was dispatched), so the gate does not apply.
|
|
82
|
+
if [ -f "$inline" ]; then
|
|
83
|
+
printf 'reconcile: inline-fallback run (no wright dispatched) — gate skipped.\n' >&2
|
|
84
|
+
close_window
|
|
85
|
+
exit 0
|
|
86
|
+
fi
|
|
87
|
+
if [ ! -f "$base" ]; then
|
|
88
|
+
printf 'reconcile: no snapshot (.reconcile-base) — run "reconcile.sh snapshot" before dispatch.\n' >&2
|
|
89
|
+
exit 6
|
|
90
|
+
fi
|
|
91
|
+
if [ ! -f "$claimed" ]; then
|
|
92
|
+
printf 'reconcile: no claimed manifest (.reconcile-claimed) — write the wright Files-changed paths first.\n' >&2
|
|
93
|
+
exit 6
|
|
94
|
+
fi
|
|
95
|
+
base_tree="$(cat "$base")"
|
|
96
|
+
now_tree="$(worktree_tree)"
|
|
97
|
+
# Every path changed since the snapshot, minus bookkeeping (.devrites/ is the
|
|
98
|
+
# orchestrator's own legit write target). Anything left that the wright did
|
|
99
|
+
# not claim was edited by someone other than the wright.
|
|
100
|
+
viol=0
|
|
101
|
+
violist=""
|
|
102
|
+
while IFS= read -r f; do
|
|
103
|
+
[ -z "$f" ] && continue
|
|
104
|
+
case "$f" in .devrites/*) continue ;; esac
|
|
105
|
+
if ! grep -qxF "$f" "$claimed"; then
|
|
106
|
+
viol=$(( viol + 1 ))
|
|
107
|
+
violist="${violist} - ${f}
|
|
108
|
+
"
|
|
109
|
+
fi
|
|
110
|
+
done < <(git -C "$root" diff --name-only "$base_tree" "$now_tree")
|
|
111
|
+
if [ "$viol" -gt 0 ]; then
|
|
112
|
+
printf 'reconcile: STOP — %d source file(s) changed OUTSIDE the wright (A1 breach):\n' "$viol" >&2
|
|
113
|
+
printf '%s' "$violist" >&2
|
|
114
|
+
printf 'The orchestrator must NOT edit source. Re-dispatch the wright (continue it once) or revert these.\n' >&2
|
|
115
|
+
exit 5
|
|
116
|
+
fi
|
|
117
|
+
close_window
|
|
118
|
+
printf "reconcile: OK — every source change is the wright's; orchestrator touched only bookkeeping.\n"
|
|
119
|
+
exit 0 ;;
|
|
120
|
+
*)
|
|
121
|
+
printf 'reconcile: usage: reconcile.sh <snapshot|check> [slug]\n' >&2
|
|
122
|
+
exit 6 ;;
|
|
123
|
+
esac
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Resolve an open question in the active DevRites workspace.
|
|
3
|
+
# Used by /rite-resolve. Keeps questions.md and state.md consistent.
|
|
4
|
+
#
|
|
5
|
+
# Usage:
|
|
6
|
+
# resolve.sh <qid> "<answer>"
|
|
7
|
+
# resolve.sh --drop <qid> ["<reason>"]
|
|
8
|
+
# resolve.sh --batch <path-to-yaml>
|
|
9
|
+
# resolve.sh next-qid <questions.md path>
|
|
10
|
+
#
|
|
11
|
+
# Exit codes:
|
|
12
|
+
# 0 resolved
|
|
13
|
+
# 2 no active workspace
|
|
14
|
+
# 3 qid not found
|
|
15
|
+
# 4 qid not open (already answered/dropped)
|
|
16
|
+
# 5 bad arguments
|
|
17
|
+
# 6 qid collision (next-qid: computed id already has a header)
|
|
18
|
+
|
|
19
|
+
set -u
|
|
20
|
+
|
|
21
|
+
die() { printf '%s\n' "$*" >&2; exit "${2:-1}"; }
|
|
22
|
+
|
|
23
|
+
# next-qid: print the next sequential qid for TODAY. Counts existing
|
|
24
|
+
# `## q-YYYY-MM-DD-` headers for today's date, prints q-YYYY-MM-DD-NNN with the
|
|
25
|
+
# next zero-padded id, and refuses (exit 6) if that header already exists.
|
|
26
|
+
# Operates on an explicit questions.md path — no active workspace required.
|
|
27
|
+
if [ "${1:-}" = "next-qid" ]; then
|
|
28
|
+
qpath="${2:-}"
|
|
29
|
+
[ -n "$qpath" ] || die "Usage: resolve.sh next-qid <questions.md path>" 5
|
|
30
|
+
today="$(date +%F)"
|
|
31
|
+
# A fresh questions.md may not exist yet — treat absent as zero headers.
|
|
32
|
+
if [ -f "$qpath" ]; then
|
|
33
|
+
used="$(grep -c "^## q-${today}-" "$qpath" 2>/dev/null || true)"
|
|
34
|
+
else
|
|
35
|
+
used=0
|
|
36
|
+
fi
|
|
37
|
+
next=$(( used + 1 ))
|
|
38
|
+
qid="$(printf 'q-%s-%03d' "$today" "$next")"
|
|
39
|
+
# Defend against gaps/manual edits: never hand out an id that already exists.
|
|
40
|
+
if [ -f "$qpath" ] && grep -q "^## ${qid}\([[:space:]]\|$\)" "$qpath"; then
|
|
41
|
+
die "qid already present: $qid (questions.md edited out of sequence)" 6
|
|
42
|
+
fi
|
|
43
|
+
printf '%s\n' "$qid"
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
slug="$(cat .devrites/ACTIVE 2>/dev/null || true)"
|
|
48
|
+
[ -n "$slug" ] || die "No active workspace. Run /rite-spec <feature> first." 2
|
|
49
|
+
work=".devrites/work/$slug"
|
|
50
|
+
qfile="$work/questions.md"
|
|
51
|
+
sfile="$work/state.md"
|
|
52
|
+
[ -f "$qfile" ] || die "questions.md missing at $qfile" 2
|
|
53
|
+
[ -f "$sfile" ] || die "state.md missing at $sfile" 2
|
|
54
|
+
|
|
55
|
+
mode=""
|
|
56
|
+
qid=""
|
|
57
|
+
payload=""
|
|
58
|
+
case "${1:-}" in
|
|
59
|
+
--drop)
|
|
60
|
+
mode="drop"
|
|
61
|
+
qid="${2:-}"
|
|
62
|
+
payload="${3:-dropped}"
|
|
63
|
+
;;
|
|
64
|
+
--batch)
|
|
65
|
+
mode="batch"
|
|
66
|
+
payload="${2:-}"
|
|
67
|
+
[ -f "$payload" ] || die "Batch file not found: $payload" 5
|
|
68
|
+
;;
|
|
69
|
+
"")
|
|
70
|
+
die "Usage: resolve.sh <qid> \"<answer>\" | --drop <qid> [\"<reason>\"] | --batch <file>" 5
|
|
71
|
+
;;
|
|
72
|
+
*)
|
|
73
|
+
mode="answer"
|
|
74
|
+
qid="$1"
|
|
75
|
+
payload="${2:-}"
|
|
76
|
+
[ -n "$payload" ] || die "Answer text required for $qid" 5
|
|
77
|
+
;;
|
|
78
|
+
esac
|
|
79
|
+
|
|
80
|
+
now() { date -u +"%Y-%m-%dT%H:%M:%SZ"; }
|
|
81
|
+
|
|
82
|
+
# Apply a single resolution to questions.md. Awk rewrites the file atomically.
|
|
83
|
+
apply_one() {
|
|
84
|
+
local _qid="$1" _status="$2" _answer="$3"
|
|
85
|
+
local _now; _now="$(now)"
|
|
86
|
+
|
|
87
|
+
awk -v qid="$_qid" -v st="$_status" -v ans="$_answer" -v ts="$_now" '
|
|
88
|
+
BEGIN { in_q=0; touched=0; found=0; status_seen=0 }
|
|
89
|
+
function flush_if_target() {
|
|
90
|
+
# called when leaving the target block
|
|
91
|
+
if (in_q && !status_seen) {
|
|
92
|
+
# malformed entry without status — append the closing fields
|
|
93
|
+
print "status: " st
|
|
94
|
+
print "answered_at: " ts
|
|
95
|
+
print "answer: " ans
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/^## q-/ {
|
|
99
|
+
flush_if_target()
|
|
100
|
+
in_q = ($0 ~ ("^## " qid "([[:space:]]|$)"))
|
|
101
|
+
status_seen = 0
|
|
102
|
+
if (in_q) { found = 1 }
|
|
103
|
+
print
|
|
104
|
+
next
|
|
105
|
+
}
|
|
106
|
+
in_q && /^status:/ {
|
|
107
|
+
status_seen = 1
|
|
108
|
+
cur = $0; sub(/^status:[[:space:]]*/, "", cur)
|
|
109
|
+
if (cur != "open") {
|
|
110
|
+
# signal non-open with sentinel to stderr-equivalent: write file unchanged
|
|
111
|
+
# we cannot abort from awk cleanly; mark and continue
|
|
112
|
+
print "status: " cur
|
|
113
|
+
in_q = 0
|
|
114
|
+
bad_state = 1
|
|
115
|
+
next
|
|
116
|
+
}
|
|
117
|
+
print "status: " st
|
|
118
|
+
next
|
|
119
|
+
}
|
|
120
|
+
in_q && /^answered_at:/ {
|
|
121
|
+
print "answered_at: " ts
|
|
122
|
+
next
|
|
123
|
+
}
|
|
124
|
+
in_q && /^answer:/ {
|
|
125
|
+
print "answer: " ans
|
|
126
|
+
next
|
|
127
|
+
}
|
|
128
|
+
{ print }
|
|
129
|
+
END {
|
|
130
|
+
flush_if_target()
|
|
131
|
+
if (!found) exit 10
|
|
132
|
+
if (bad_state) exit 11
|
|
133
|
+
}
|
|
134
|
+
' "$qfile" > "$qfile.tmp"
|
|
135
|
+
rc=$?
|
|
136
|
+
|
|
137
|
+
if [ "$rc" -eq 10 ]; then
|
|
138
|
+
rm -f "$qfile.tmp"
|
|
139
|
+
die "qid not found: $_qid" 3
|
|
140
|
+
fi
|
|
141
|
+
if [ "$rc" -eq 11 ]; then
|
|
142
|
+
rm -f "$qfile.tmp"
|
|
143
|
+
die "qid not open (already answered/dropped): $_qid" 4
|
|
144
|
+
fi
|
|
145
|
+
|
|
146
|
+
# If the awk output is missing answered_at / answer (entry without those keys),
|
|
147
|
+
# append them at the end of the block by re-running with insertion mode.
|
|
148
|
+
mv "$qfile.tmp" "$qfile"
|
|
149
|
+
|
|
150
|
+
# Ensure answered_at + answer keys exist for the target qid (insert after `status:` if missing)
|
|
151
|
+
awk -v qid="$_qid" -v ts="$_now" -v ans="$_answer" '
|
|
152
|
+
BEGIN { in_q=0; saw_at=0; saw_ans=0; saw_status_line=0 }
|
|
153
|
+
/^## q-/ {
|
|
154
|
+
if (in_q) {
|
|
155
|
+
if (!saw_at) print "answered_at: " ts
|
|
156
|
+
if (!saw_ans) print "answer: " ans
|
|
157
|
+
}
|
|
158
|
+
in_q = ($0 ~ ("^## " qid "([[:space:]]|$)"))
|
|
159
|
+
saw_at = 0; saw_ans = 0; saw_status_line = 0
|
|
160
|
+
print; next
|
|
161
|
+
}
|
|
162
|
+
in_q && /^answered_at:/ { saw_at = 1 }
|
|
163
|
+
in_q && /^answer:/ { saw_ans = 1 }
|
|
164
|
+
in_q && /^status:/ { saw_status_line = 1 }
|
|
165
|
+
{ print }
|
|
166
|
+
END {
|
|
167
|
+
if (in_q) {
|
|
168
|
+
if (!saw_at) print "answered_at: " ts
|
|
169
|
+
if (!saw_ans) print "answer: " ans
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
' "$qfile" > "$qfile.tmp" && mv "$qfile.tmp" "$qfile"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# Update state.md: if the Awaiting human block references the qid, remove the block,
|
|
176
|
+
# flip Status: awaiting_human → running, clear Next step, and append a Log entry.
|
|
177
|
+
# Two-pass: scan to detect a match, then rewrite the file with the side-effects applied.
|
|
178
|
+
clear_state_if_matches() {
|
|
179
|
+
local _qid="$1"
|
|
180
|
+
local _now; _now="$(now)"
|
|
181
|
+
|
|
182
|
+
# Pass 1: does the Awaiting block reference this qid?
|
|
183
|
+
local matched
|
|
184
|
+
matched="$(awk -v qid="$_qid" '
|
|
185
|
+
BEGIN { in_aw=0; m=0 }
|
|
186
|
+
/^## Awaiting human/ { in_aw=1; next }
|
|
187
|
+
in_aw && /^##[[:space:]]/ { in_aw=0 }
|
|
188
|
+
in_aw {
|
|
189
|
+
if ($0 ~ ("qid:[[:space:]]*" qid "([[:space:]]|$)")) m=1
|
|
190
|
+
}
|
|
191
|
+
END { print m }
|
|
192
|
+
' "$sfile")"
|
|
193
|
+
|
|
194
|
+
if [ "$matched" != "1" ]; then
|
|
195
|
+
return 0
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# Pass 2: rewrite. Drop the Awaiting human block (header + body until the next `## `),
|
|
199
|
+
# flip Status, clear Next step, append a Log entry under ## Log.
|
|
200
|
+
awk -v qid="$_qid" -v ts="$_now" '
|
|
201
|
+
BEGIN { in_aw=0; in_log=0; log_appended=0 }
|
|
202
|
+
/^## Awaiting human/ { in_aw=1; next }
|
|
203
|
+
in_aw && /^##[[:space:]]/ { in_aw=0 } # next ## header ends the block
|
|
204
|
+
in_aw { next } # swallow lines inside the awaiting block
|
|
205
|
+
|
|
206
|
+
/^- Status:/ {
|
|
207
|
+
print "- Status: running"
|
|
208
|
+
next
|
|
209
|
+
}
|
|
210
|
+
/^- Next step:/ {
|
|
211
|
+
print "- Next step: (resume — `/rite-build` to continue the workflow)"
|
|
212
|
+
next
|
|
213
|
+
}
|
|
214
|
+
/^## Log/ {
|
|
215
|
+
print
|
|
216
|
+
in_log = 1
|
|
217
|
+
next
|
|
218
|
+
}
|
|
219
|
+
in_log && /^##[[:space:]]/ {
|
|
220
|
+
# leaving the log section — append our entry before this header if we have not yet
|
|
221
|
+
if (!log_appended) {
|
|
222
|
+
printf "- %s build: resolved %s\n", ts, qid
|
|
223
|
+
log_appended = 1
|
|
224
|
+
}
|
|
225
|
+
in_log = 0
|
|
226
|
+
print
|
|
227
|
+
next
|
|
228
|
+
}
|
|
229
|
+
{ print }
|
|
230
|
+
|
|
231
|
+
END {
|
|
232
|
+
if (in_log && !log_appended) {
|
|
233
|
+
printf "- %s build: resolved %s\n", ts, qid
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
' "$sfile" > "$sfile.tmp" && mv "$sfile.tmp" "$sfile"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case "$mode" in
|
|
240
|
+
answer)
|
|
241
|
+
apply_one "$qid" "answered" "$payload"
|
|
242
|
+
clear_state_if_matches "$qid"
|
|
243
|
+
printf 'Resolved: %s\n' "$qid"
|
|
244
|
+
printf 'Status: answered\n'
|
|
245
|
+
printf 'Workspace: questions.md + state.md updated.\n'
|
|
246
|
+
;;
|
|
247
|
+
drop)
|
|
248
|
+
apply_one "$qid" "dropped" "$payload"
|
|
249
|
+
clear_state_if_matches "$qid"
|
|
250
|
+
printf 'Dropped: %s\n' "$qid"
|
|
251
|
+
printf 'Reason: %s\n' "$payload"
|
|
252
|
+
printf 'Workspace: questions.md + state.md updated.\n'
|
|
253
|
+
;;
|
|
254
|
+
batch)
|
|
255
|
+
# Batch format: each line is `qid: <answer>` or `--drop qid: <reason>`. See answer-protocol.md.
|
|
256
|
+
while IFS= read -r line; do
|
|
257
|
+
case "$line" in
|
|
258
|
+
''|\#*) continue ;;
|
|
259
|
+
--drop*)
|
|
260
|
+
rest="${line#--drop }"
|
|
261
|
+
bid="${rest%%:*}"
|
|
262
|
+
reason="${rest#*:}"
|
|
263
|
+
reason="${reason# }"
|
|
264
|
+
apply_one "$bid" "dropped" "$reason"
|
|
265
|
+
clear_state_if_matches "$bid"
|
|
266
|
+
printf 'Dropped: %s — %s\n' "$bid" "$reason"
|
|
267
|
+
;;
|
|
268
|
+
*)
|
|
269
|
+
bid="${line%%:*}"
|
|
270
|
+
ans="${line#*:}"
|
|
271
|
+
ans="${ans# }"
|
|
272
|
+
apply_one "$bid" "answered" "$ans"
|
|
273
|
+
clear_state_if_matches "$bid"
|
|
274
|
+
printf 'Resolved: %s\n' "$bid"
|
|
275
|
+
;;
|
|
276
|
+
esac
|
|
277
|
+
done < "$payload"
|
|
278
|
+
;;
|
|
279
|
+
esac
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# stuck.sh — loop detector for the unattended build. The slice-wright or orchestrator
|
|
3
|
+
# appends one record per tool action; `check` flags the semantic-repeat patterns that
|
|
4
|
+
# mean the agent is stuck burning tokens (the #1 silent AFK failure mode): the same
|
|
5
|
+
# action repeating, an action<->error ping-pong, or one action dominating the window.
|
|
6
|
+
# A detected loop pauses the build even under AFK (rules/afk-hitl.md). Deterministic;
|
|
7
|
+
# matching is by (tool,target) hash, timestamps ignored.
|
|
8
|
+
#
|
|
9
|
+
# stuck.sh log <slug> <tool> [target] # append one action record (caller)
|
|
10
|
+
# stuck.sh check <slug> [window] # non-zero if the recent window looks stuck
|
|
11
|
+
#
|
|
12
|
+
# The log lives at .devrites/work/<slug>/action.log as "<epoch> <tool> <sha1>" lines.
|
|
13
|
+
#
|
|
14
|
+
# Exit codes:
|
|
15
|
+
# 0 ok — not stuck (or logging, or no workspace: never fail the caller on log)
|
|
16
|
+
# 2 bad args
|
|
17
|
+
# 3 STUCK — the recent window is a loop: stop and escalate to a blocking question
|
|
18
|
+
set -u
|
|
19
|
+
|
|
20
|
+
cmd="${1:-}"; slug="${2:-}"
|
|
21
|
+
[ -n "$cmd" ] && [ -n "$slug" ] || { echo "usage: stuck.sh log|check <slug> [...]" >&2; exit 2; }
|
|
22
|
+
|
|
23
|
+
dir=".devrites/work/$slug"
|
|
24
|
+
log="$dir/action.log"
|
|
25
|
+
|
|
26
|
+
hash_of() {
|
|
27
|
+
if command -v sha1sum >/dev/null 2>&1; then
|
|
28
|
+
printf '%s' "$1" | sha1sum | awk '{print $1}'
|
|
29
|
+
else
|
|
30
|
+
printf '%s' "$1" | shasum | awk '{print $1}'
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
case "$cmd" in
|
|
35
|
+
log)
|
|
36
|
+
tool="${3:-}"; target="${4:-}"
|
|
37
|
+
[ -n "$tool" ] || { echo "usage: stuck.sh log <slug> <tool> [target]" >&2; exit 2; }
|
|
38
|
+
[ -d "$dir" ] || exit 0
|
|
39
|
+
printf '%s %s %s\n' "$(date +%s)" "$tool" "$(hash_of "$tool|$target")" >> "$log"
|
|
40
|
+
;;
|
|
41
|
+
check)
|
|
42
|
+
win="${3:-4}"
|
|
43
|
+
[ -f "$log" ] || { echo "stuck: no actions logged — not stuck."; exit 0; }
|
|
44
|
+
awk -v win="$win" '
|
|
45
|
+
{ h[NR]=$3 }
|
|
46
|
+
END {
|
|
47
|
+
if (NR < win) { print "stuck: only " NR " action(s) — not stuck."; exit 0 }
|
|
48
|
+
same=1
|
|
49
|
+
for (i=NR-win+1; i<=NR; i++) if (h[i]!=h[NR]) { same=0; break }
|
|
50
|
+
if (same) { print "stuck: last " win " actions identical — STUCK."; exit 3 }
|
|
51
|
+
if (NR>=6) {
|
|
52
|
+
pp=1
|
|
53
|
+
for (i=NR-5; i<=NR; i++) { e=(((NR-i)%2)==0)?h[NR]:h[NR-1]; if (h[i]!=e) { pp=0; break } }
|
|
54
|
+
if (pp && h[NR]!=h[NR-1]) { print "stuck: A<->B ping-pong over last 6 — STUCK."; exit 3 }
|
|
55
|
+
}
|
|
56
|
+
if (NR>=8) {
|
|
57
|
+
for (i=NR-7;i<=NR;i++) c[h[i]]++
|
|
58
|
+
top=0; for (k in c) if (c[k]>top) top=c[k]
|
|
59
|
+
if (top>=6) { print "stuck: one action " top "/8 of recent window — STUCK."; exit 3 }
|
|
60
|
+
}
|
|
61
|
+
print "stuck: recent window varied — not stuck."
|
|
62
|
+
}
|
|
63
|
+
' "$log"
|
|
64
|
+
;;
|
|
65
|
+
*)
|
|
66
|
+
echo "usage: stuck.sh log|check <slug> [...]" >&2; exit 2 ;;
|
|
67
|
+
esac
|