devrites 3.2.24 → 3.2.26
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/engine/hooks_agent_dispatch.go +4 -3
- package/engine/tests/hook_test.go +8 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +5 -6
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +4 -5
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +5 -6
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +4 -5
- package/pack/generated/codex/AGENTS.md +1 -1
- package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-audit/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-doubt/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-interview/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +5 -6
- package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +4 -4
- package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-adopt/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-build/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +4 -5
- package/pack/generated/codex/skills/rite-clarify/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-customize/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-define/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-doctor/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-dogfood/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-explain/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-frame/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-handoff/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-learn/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-plan/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-polish/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-pov/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-prototype/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-prove/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-quick/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-resolve/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-review/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-seal/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-ship/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-spec/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-status/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-temper/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-upgrade/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-vet/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +4 -4
- package/package.json +6 -2
- package/scripts/build-host-artifacts.sh +2 -2
- package/scripts/build-release-tarball.sh +4 -4
- package/scripts/check-generated-skill-budget.mjs +2 -1
- package/scripts/check-instruction-size-baseline.mjs +2 -1
- package/scripts/check-reference-governance.mjs +2 -1
- package/scripts/codex-generate.sh +6 -6
- package/scripts/pin.sh +70 -24
- package/scripts/run-tests.mjs +2 -1
- package/scripts/scan-pack-security.py +1 -1
- package/scripts/skill-pruning-audit.mjs +2 -1
- package/scripts/skills-inventory.mjs +2 -1
- package/scripts/validate-workflow-security.py +68 -9
- package/scripts/validate.sh +11 -9
|
@@ -20,25 +20,82 @@ import re
|
|
|
20
20
|
import sys
|
|
21
21
|
|
|
22
22
|
SHA_RE = re.compile(r"^[0-9a-f]{40}$")
|
|
23
|
-
USES_RE = re.compile(r"^\s*-?\s*uses
|
|
23
|
+
USES_RE = re.compile(r"^\s*-?\s*uses\s*:\s*([^\s#]+)")
|
|
24
24
|
DEPENDABOT_ONLY_RE = re.compile(
|
|
25
|
-
r"^\s*if
|
|
25
|
+
r"^\s*if\s*:\s*(?:\$\{\{\s*)?"
|
|
26
26
|
r"(?:github\.actor|github\.event\.pull_request\.user\.login)\s*==\s*"
|
|
27
27
|
r"['\"]dependabot\[bot\]['\"]",
|
|
28
28
|
re.MULTILINE,
|
|
29
29
|
)
|
|
30
30
|
UNQUOTED_NAME_COLON_RE = re.compile(r"^\s*(?:-\s*)?name:\s+[^'\"].*:\s+\S")
|
|
31
|
-
RUN_RE = re.compile(r"^(\s*)(?:-\s*)?run
|
|
31
|
+
RUN_RE = re.compile(r"^(\s*)(?:-\s*)?run\s*:\s*(.*)$")
|
|
32
32
|
DISPATCH_EXPRESSION_RE = re.compile(r"\$\{\{[^}]*\binputs\b", re.IGNORECASE)
|
|
33
|
+
KEY_RE = re.compile(r"^(\s*)([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def jobs_without_permissions(lines):
|
|
37
|
+
"""Return job IDs missing direct permissions, or None with a global scope."""
|
|
38
|
+
for line in lines:
|
|
39
|
+
match = KEY_RE.match(line)
|
|
40
|
+
if match and not match.group(1) and match.group(2) == "permissions":
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
jobs_line = None
|
|
44
|
+
for i, line in enumerate(lines):
|
|
45
|
+
match = KEY_RE.match(line)
|
|
46
|
+
if match and not match.group(1) and match.group(2) == "jobs":
|
|
47
|
+
jobs_line = i
|
|
48
|
+
break
|
|
49
|
+
if jobs_line is None:
|
|
50
|
+
return ["<workflow>"]
|
|
51
|
+
|
|
52
|
+
job_indent = None
|
|
53
|
+
job_starts = []
|
|
54
|
+
for i in range(jobs_line + 1, len(lines)):
|
|
55
|
+
line = lines[i]
|
|
56
|
+
if not line.strip() or line.lstrip().startswith("#"):
|
|
57
|
+
continue
|
|
58
|
+
match = KEY_RE.match(line)
|
|
59
|
+
if not match:
|
|
60
|
+
continue
|
|
61
|
+
indent = len(match.group(1))
|
|
62
|
+
if indent == 0:
|
|
63
|
+
break
|
|
64
|
+
if job_indent is None:
|
|
65
|
+
job_indent = indent
|
|
66
|
+
if indent == job_indent:
|
|
67
|
+
job_starts.append((i, match.group(2)))
|
|
68
|
+
if not job_starts:
|
|
69
|
+
return ["<workflow>"]
|
|
70
|
+
|
|
71
|
+
missing = []
|
|
72
|
+
for position, (start, job_id) in enumerate(job_starts):
|
|
73
|
+
end = job_starts[position + 1][0] if position + 1 < len(job_starts) else len(lines)
|
|
74
|
+
property_indents = []
|
|
75
|
+
properties = []
|
|
76
|
+
for line in lines[start + 1:end]:
|
|
77
|
+
match = KEY_RE.match(line)
|
|
78
|
+
if not match:
|
|
79
|
+
continue
|
|
80
|
+
indent = len(match.group(1))
|
|
81
|
+
if indent > job_indent:
|
|
82
|
+
property_indents.append(indent)
|
|
83
|
+
properties.append((indent, match.group(2)))
|
|
84
|
+
direct_indent = min(property_indents) if property_indents else None
|
|
85
|
+
if direct_indent is None or not any(
|
|
86
|
+
indent == direct_indent and key == "permissions"
|
|
87
|
+
for indent, key in properties):
|
|
88
|
+
missing.append(job_id)
|
|
89
|
+
return missing
|
|
33
90
|
|
|
34
91
|
|
|
35
92
|
def safe_dependabot_target(text):
|
|
36
|
-
if re.search(r"^\s*-?\s*uses
|
|
93
|
+
if re.search(r"^\s*-?\s*uses\s*:\s*actions/checkout@", text, re.MULTILINE):
|
|
37
94
|
return False
|
|
38
|
-
jobs =
|
|
95
|
+
jobs = re.split(r"(?m)^jobs\s*:\s*(?:#.*)?$", text, maxsplit=1)
|
|
39
96
|
if len(jobs) != 2:
|
|
40
97
|
return False
|
|
41
|
-
blocks = re.split(r"(?m)^ [A-Za-z0-9_-]
|
|
98
|
+
blocks = re.split(r"(?m)^ [A-Za-z0-9_-]+\s*:\s*(?:#.*)?$", jobs[1])[1:]
|
|
42
99
|
return bool(blocks) and all(DEPENDABOT_ONLY_RE.search(block) for block in blocks)
|
|
43
100
|
|
|
44
101
|
|
|
@@ -46,9 +103,11 @@ def scan_text(path, text):
|
|
|
46
103
|
findings = []
|
|
47
104
|
lines = text.splitlines()
|
|
48
105
|
dependabot_target_is_safe = safe_dependabot_target(text)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
106
|
+
unscoped_jobs = jobs_without_permissions(lines)
|
|
107
|
+
if unscoped_jobs:
|
|
108
|
+
findings.append("%s: jobs without explicit permissions: %s. Add a global "
|
|
109
|
+
"least-privilege block or scope every job"
|
|
110
|
+
% (path, ", ".join(unscoped_jobs)))
|
|
52
111
|
for i, line in enumerate(lines, 1):
|
|
53
112
|
if UNQUOTED_NAME_COLON_RE.match(line):
|
|
54
113
|
findings.append("%s:%d: name has an unquoted colon. Quote the complete "
|
package/scripts/validate.sh
CHANGED
|
@@ -15,9 +15,11 @@ good() { printf 'ok: %s\n' "$*"; }
|
|
|
15
15
|
|
|
16
16
|
# ---- 1. bash -n on every shell script ------------------------------------
|
|
17
17
|
section "bash syntax (bash -n)"
|
|
18
|
-
SH_LIST="$ROOT/install.sh $ROOT/uninstall.sh $ROOT/update.sh"
|
|
19
|
-
for f in "$ROOT"/scripts/*.sh "$ROOT"/tests/*.sh "$ROOT"/pack/.claude/hooks/*.sh "$ROOT"/pack/.claude/skills/*/scripts/*.sh; do
|
|
20
|
-
|
|
18
|
+
SH_LIST=("$ROOT/install.sh" "$ROOT/uninstall.sh" "$ROOT/update.sh")
|
|
19
|
+
for f in "$ROOT"/scripts/*.sh "$ROOT"/tests/*.sh "$ROOT"/pack/.claude/hooks/*.sh "$ROOT"/pack/.claude/skills/*/scripts/*.sh; do
|
|
20
|
+
[ -f "$f" ] && SH_LIST+=("$f")
|
|
21
|
+
done
|
|
22
|
+
for f in "${SH_LIST[@]}"; do
|
|
21
23
|
if bash -n "$f" 2>/tmp/dr_synerr; then good "syntax ${f#$ROOT/}"; else bad "syntax ${f#$ROOT/}: $(cat /tmp/dr_synerr)"; fi
|
|
22
24
|
done
|
|
23
25
|
|
|
@@ -67,10 +69,10 @@ done
|
|
|
67
69
|
# ---- 5. frontmatter validation ------------------------------------------
|
|
68
70
|
section "frontmatter"
|
|
69
71
|
if command -v python3 >/dev/null 2>&1; then
|
|
70
|
-
FM_FILES=
|
|
71
|
-
for d in "$SKILLS"/*/; do
|
|
72
|
-
for a in "$AGENTS"/*.md; do
|
|
73
|
-
if python3 "$ROOT/scripts/validate-frontmatter.py" $FM_FILES; then good "frontmatter parses"; else bad "frontmatter validation failed"; fi
|
|
72
|
+
FM_FILES=()
|
|
73
|
+
for d in "$SKILLS"/*/; do [ -f "${d}SKILL.md" ] && FM_FILES+=("${d}SKILL.md"); done
|
|
74
|
+
for a in "$AGENTS"/*.md; do [ -f "$a" ] && FM_FILES+=("$a"); done
|
|
75
|
+
if python3 "$ROOT/scripts/validate-frontmatter.py" "${FM_FILES[@]}"; then good "frontmatter parses"; else bad "frontmatter validation failed"; fi
|
|
74
76
|
else
|
|
75
77
|
echo "skip: python3 not found"
|
|
76
78
|
fi
|
|
@@ -335,11 +337,11 @@ fi
|
|
|
335
337
|
# Local validation skips this gate only when shellcheck is not installed.
|
|
336
338
|
section "shellcheck (-S error blocking · -S warning advisory)"
|
|
337
339
|
if command -v shellcheck >/dev/null 2>&1; then
|
|
338
|
-
for f in $SH_LIST; do
|
|
340
|
+
for f in "${SH_LIST[@]}"; do
|
|
339
341
|
if shellcheck -S error "$f"; then good "shellcheck ${f#"$ROOT"/}"; else bad "shellcheck (error) ${f#"$ROOT"/}"; fi
|
|
340
342
|
done
|
|
341
343
|
# Warnings are advisory. Print them per file without failing the build.
|
|
342
|
-
for f in $SH_LIST; do
|
|
344
|
+
for f in "${SH_LIST[@]}"; do
|
|
343
345
|
shellcheck -S warning "$f" >/dev/null 2>&1 || echo " advisory (warning-level): ${f#"$ROOT"/}"
|
|
344
346
|
done
|
|
345
347
|
else
|