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.
Files changed (69) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +1 -1
  3. package/engine/hooks_agent_dispatch.go +4 -3
  4. package/engine/tests/hook_test.go +8 -0
  5. package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +5 -6
  6. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +4 -5
  7. package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +5 -6
  8. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +4 -5
  9. package/pack/generated/codex/AGENTS.md +1 -1
  10. package/pack/generated/codex/skills/devrites-api-interface/SKILL.md +4 -4
  11. package/pack/generated/codex/skills/devrites-audit/SKILL.md +4 -4
  12. package/pack/generated/codex/skills/devrites-browser-proof/SKILL.md +4 -4
  13. package/pack/generated/codex/skills/devrites-debug-recovery/SKILL.md +4 -4
  14. package/pack/generated/codex/skills/devrites-doubt/SKILL.md +4 -4
  15. package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +4 -4
  16. package/pack/generated/codex/skills/devrites-interview/SKILL.md +4 -4
  17. package/pack/generated/codex/skills/devrites-lib/SKILL.md +4 -4
  18. package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +5 -6
  19. package/pack/generated/codex/skills/devrites-prose-craft/SKILL.md +4 -4
  20. package/pack/generated/codex/skills/devrites-refresh-indexes/SKILL.md +4 -4
  21. package/pack/generated/codex/skills/devrites-source-driven/SKILL.md +4 -4
  22. package/pack/generated/codex/skills/devrites-ux-shape/SKILL.md +4 -4
  23. package/pack/generated/codex/skills/rite/SKILL.md +4 -4
  24. package/pack/generated/codex/skills/rite-adopt/SKILL.md +4 -4
  25. package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +4 -4
  26. package/pack/generated/codex/skills/rite-build/SKILL.md +4 -4
  27. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +4 -5
  28. package/pack/generated/codex/skills/rite-clarify/SKILL.md +4 -4
  29. package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
  30. package/pack/generated/codex/skills/rite-customize/SKILL.md +4 -4
  31. package/pack/generated/codex/skills/rite-define/SKILL.md +4 -4
  32. package/pack/generated/codex/skills/rite-doctor/SKILL.md +4 -4
  33. package/pack/generated/codex/skills/rite-dogfood/SKILL.md +4 -4
  34. package/pack/generated/codex/skills/rite-explain/SKILL.md +4 -4
  35. package/pack/generated/codex/skills/rite-frame/SKILL.md +4 -4
  36. package/pack/generated/codex/skills/rite-handoff/SKILL.md +4 -4
  37. package/pack/generated/codex/skills/rite-learn/SKILL.md +4 -4
  38. package/pack/generated/codex/skills/rite-plan/SKILL.md +4 -4
  39. package/pack/generated/codex/skills/rite-polish/SKILL.md +4 -4
  40. package/pack/generated/codex/skills/rite-pov/SKILL.md +4 -4
  41. package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +4 -4
  42. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +4 -4
  43. package/pack/generated/codex/skills/rite-prototype/SKILL.md +4 -4
  44. package/pack/generated/codex/skills/rite-prove/SKILL.md +4 -4
  45. package/pack/generated/codex/skills/rite-quick/SKILL.md +4 -4
  46. package/pack/generated/codex/skills/rite-resolve/SKILL.md +4 -4
  47. package/pack/generated/codex/skills/rite-review/SKILL.md +4 -4
  48. package/pack/generated/codex/skills/rite-seal/SKILL.md +4 -4
  49. package/pack/generated/codex/skills/rite-ship/SKILL.md +4 -4
  50. package/pack/generated/codex/skills/rite-spec/SKILL.md +4 -4
  51. package/pack/generated/codex/skills/rite-status/SKILL.md +4 -4
  52. package/pack/generated/codex/skills/rite-temper/SKILL.md +4 -4
  53. package/pack/generated/codex/skills/rite-upgrade/SKILL.md +4 -4
  54. package/pack/generated/codex/skills/rite-vet/SKILL.md +4 -4
  55. package/pack/generated/codex/skills/rite-zoom-out/SKILL.md +4 -4
  56. package/package.json +6 -2
  57. package/scripts/build-host-artifacts.sh +2 -2
  58. package/scripts/build-release-tarball.sh +4 -4
  59. package/scripts/check-generated-skill-budget.mjs +2 -1
  60. package/scripts/check-instruction-size-baseline.mjs +2 -1
  61. package/scripts/check-reference-governance.mjs +2 -1
  62. package/scripts/codex-generate.sh +6 -6
  63. package/scripts/pin.sh +70 -24
  64. package/scripts/run-tests.mjs +2 -1
  65. package/scripts/scan-pack-security.py +1 -1
  66. package/scripts/skill-pruning-audit.mjs +2 -1
  67. package/scripts/skills-inventory.mjs +2 -1
  68. package/scripts/validate-workflow-security.py +68 -9
  69. 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:\s*([^\s#]+)")
23
+ USES_RE = re.compile(r"^\s*-?\s*uses\s*:\s*([^\s#]+)")
24
24
  DEPENDABOT_ONLY_RE = re.compile(
25
- r"^\s*if:\s*(?:\$\{\{\s*)?"
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:\s*(.*)$")
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:\s*actions/checkout@", text, re.MULTILINE):
93
+ if re.search(r"^\s*-?\s*uses\s*:\s*actions/checkout@", text, re.MULTILINE):
37
94
  return False
38
- jobs = text.split("\njobs:", 1)
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_-]+:\s*(?:#.*)?$", jobs[1])[1:]
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
- if not re.search(r"^\s*permissions:", text, re.MULTILINE):
50
- findings.append("%s: no permissions block. The default GITHUB_TOKEN is broad; "
51
- "add an explicit least-privilege block" % path)
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 "
@@ -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 [ -f "$f" ] && SH_LIST="$SH_LIST $f"; done
20
- for f in $SH_LIST; do
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 FM_FILES="$FM_FILES ${d}SKILL.md"; done
72
- for a in "$AGENTS"/*.md; do FM_FILES="$FM_FILES $a"; done
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