agentic-awesome-skills 14.3.1 → 14.4.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/README.md CHANGED
@@ -1,9 +1,9 @@
1
- <!-- registry-sync: version=14.3.1; skills=1948; stars=43042; updated_at=2026-07-13T08:55:06+00:00 -->
1
+ <!-- registry-sync: version=14.4.0; skills=1958; stars=43124; updated_at=2026-07-14T07:01:47+00:00 -->
2
2
  [![Agentic Awesome Skills social preview](apps/web-app/public/social-card.png)](https://github.com/sickn33/agentic-awesome-skills)
3
3
 
4
- # 🌌 Agentic Awesome Skills: 1,948+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Autohand Code, Copilot & More
4
+ # 🌌 Agentic Awesome Skills: 1,958+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Autohand Code, Copilot & More
5
5
 
6
- > **Installable GitHub library of 1,948+ agentic skills for Claude Code, Cursor, Codex CLI, Autohand Code, Gemini CLI, Antigravity, and other AI coding assistants.**
6
+ > **Installable GitHub library of 1,958+ agentic skills for Claude Code, Cursor, Codex CLI, Autohand Code, Gemini CLI, Antigravity, and other AI coding assistants.**
7
7
 
8
8
  Agentic Awesome Skills is an installable GitHub library and npm installer for reusable `SKILL.md` playbooks. It is designed for Claude Code, Cursor, Codex CLI, Autohand Code, Gemini CLI, Antigravity, Kiro, OpenCode, GitHub Copilot, and other AI coding assistants that benefit from structured operating instructions. Instead of collecting one-off prompt snippets, this repository gives you a searchable, installable catalog of skills, bundles, workflows, plugin-safe distributions, and practical docs that help agents perform recurring tasks with better context, stronger constraints, and clearer outputs.
9
9
 
@@ -13,7 +13,7 @@ You can use this repo to install a broad multi-tool skill library, start from fo
13
13
 
14
14
  The canonical project page is the GitHub repository at <https://github.com/sickn33/agentic-awesome-skills>; the hosted catalog is a companion discovery surface for search, plugins, skill detail pages, and exact-set composition.
15
15
 
16
- **Start here:** [Install in 1 minute](#installation) · [Build an exact set](https://sickn33.github.io/agentic-awesome-skills/workbench) · [Recommended plugins](#recommended-specialized-plugins) · [Compare plugin packs](https://sickn33.github.io/agentic-awesome-skills/plugins) · [Choose your tool](#choose-your-tool) · [📚 Browse 1,948+ Skills](#browse-1948-skills) · [Bundles & workflows](#bundles--workflows) · [Support the project](#support-the-project)
16
+ **Start here:** [Install in 1 minute](#installation) · [Build an exact set](https://sickn33.github.io/agentic-awesome-skills/workbench) · [Recommended plugins](#recommended-specialized-plugins) · [Compare plugin packs](https://sickn33.github.io/agentic-awesome-skills/plugins) · [Choose your tool](#choose-your-tool) · [📚 Browse 1,958+ Skills](#browse-1958-skills) · [Bundles & workflows](#bundles--workflows) · [Support the project](#support-the-project)
17
17
 
18
18
  [![GitHub stars](https://img.shields.io/badge/⭐%2043%2C000%2B%20Stars-gold?style=for-the-badge)](https://github.com/sickn33/agentic-awesome-skills/stargazers)
19
19
  [![Follow @AASkills_ on X](https://img.shields.io/badge/Follow-%40AASkills__-black?style=for-the-badge&logo=x)](https://x.com/AASkills_)
@@ -30,13 +30,13 @@ The canonical project page is the GitHub repository at <https://github.com/sickn
30
30
  [![OpenCode](https://img.shields.io/badge/OpenCode-CLI-gray?style=for-the-badge)](https://github.com/opencode-ai/opencode)
31
31
  [![Antigravity](https://img.shields.io/badge/Antigravity-AI%20IDE-red?style=for-the-badge)](https://github.com/sickn33/agentic-awesome-skills)
32
32
 
33
- **Current release: V14.3.1.** Trusted by 43k+ GitHub stargazers, this repository combines official and community skill collections with bundles, workflows, installation paths, and docs that help you go from first install to daily use quickly.
33
+ **Current release: V14.4.0.** Trusted by 43k+ GitHub stargazers, this repository combines official and community skill collections with bundles, workflows, installation paths, and docs that help you go from first install to daily use quickly.
34
34
 
35
35
  ## Why This Repo
36
36
 
37
37
  - **Installable, not just inspirational**: use `npx agentic-awesome-skills` to put skills where your tool expects them.
38
38
  - **Built for major agent workflows**: Claude Code, Cursor, Codex CLI, Autohand Code, Gemini CLI, Antigravity, Kiro, OpenCode, Copilot, and more.
39
- - **Broad coverage with real utility**: 1,948+ skills across development, testing, security, infrastructure, product, and marketing.
39
+ - **Broad coverage with real utility**: 1,958+ skills across development, testing, security, infrastructure, product, and marketing.
40
40
  - **Inspect before installing**: the hosted [Skill Workbench](https://sickn33.github.io/agentic-awesome-skills/workbench) filters recorded risk, provenance, host compatibility, and setup evidence, then generates release-pinned preview and install commands for exact IDs.
41
41
  - **Focused by default**: specialized plugins help you start with the web, security, data, docs, DevOps, QA, OSS, or agent/MCP workflows you actually need.
42
42
  - **Useful whether you want breadth or curation**: install the full catalog, choose a specialized plugin, start with bundles, or compare alternatives before installing.
@@ -49,7 +49,7 @@ The canonical project page is the GitHub repository at <https://github.com/sickn
49
49
  - [Choose Your Tool](#choose-your-tool)
50
50
  - [Quick FAQ](#quick-faq)
51
51
  - [Bundles & Workflows](#bundles--workflows)
52
- - [Browse 1,948+ Skills](#browse-1948-skills)
52
+ - [Browse 1,958+ Skills](#browse-1958-skills)
53
53
  - [Troubleshooting](#troubleshooting)
54
54
  - [Stable Skills Manifest v1](#stable-skills-manifest-v1)
55
55
  - [Support the Project](#support-the-project)
@@ -171,7 +171,7 @@ Use the table above for install targets. Use specialized plugins when you are ch
171
171
 
172
172
  ### What is Agentic Awesome Skills?
173
173
 
174
- **Agentic Awesome Skills** (Release 14.3.1) is a large, installable skill library for AI coding assistants. It packages 1,948+ reusable `SKILL.md` playbooks, specialized plugins, bundles, workflows, generated catalogs, and a CLI installer so Claude Code, Codex CLI, Autohand Code, Cursor, Gemini CLI, Antigravity, and similar tools can reuse proven operating instructions instead of one-off prompts.
174
+ **Agentic Awesome Skills** (Release 14.4.0) is a large, installable skill library for AI coding assistants. It packages 1,958+ reusable `SKILL.md` playbooks, specialized plugins, bundles, workflows, generated catalogs, and a CLI installer so Claude Code, Codex CLI, Autohand Code, Cursor, Gemini CLI, Antigravity, and similar tools can reuse proven operating instructions instead of one-off prompts.
175
175
 
176
176
  ### How do I install it?
177
177
 
@@ -246,7 +246,7 @@ Remove `--dry-run` only after reviewing the install, update, and removal plan. U
246
246
 
247
247
  The hosted [Skill Workbench](https://sickn33.github.io/agentic-awesome-skills/workbench) builds the same pinned command from catalog evidence and exposes conflicts before you copy it.
248
248
 
249
- ## Browse 1,948+ Skills
249
+ ## Browse 1,958+ Skills
250
250
 
251
251
  Use the root repo as a landing page, then jump into the deeper surface that matches your intent.
252
252
 
@@ -386,6 +386,11 @@ Key source families include:
386
386
 
387
387
  ### Community Contributors
388
388
 
389
+ - **[0xsarwagya/ontoly](https://github.com/0xsarwagya/ontoly)**: Source for the `ontoly-software-graph` skill - deterministic TypeScript software graphs, MCP-backed architecture review, request tracing, impact analysis, and dependency analysis (MIT).
390
+ - [amElnagdy/guard-skills](https://github.com/amElnagdy/guard-skills) — Code Quality & Testing Guard Skills (by amElnagdy)
391
+
392
+ - [cloudflare/security-audit-skill](https://github.com/cloudflare/security-audit-skill) — Cloudflare Web Security Audit Skill (by Cloudflare)
393
+
389
394
  - **[supernovae-st/nika-agents](https://github.com/supernovae-st/nika-agents)**: Official upstream source for the `nika` skill and its deterministic, budget-aware AI workflow runner (MIT skill content; AGPL-3.0 engine).
390
395
  - **[atdy/maoxuan-product-agent](https://github.com/atdy/maoxuan-product-agent)**: Source for the `product-decision-agent` skill - Chinese-first product judgment across prioritization, growth, operations, data, delivery, and cross-functional collaboration, with 36 tested scenarios (MIT).
391
396
  - **[hafiz-actyte/idea-autopsy](https://github.com/hafiz-actyte/idea-autopsy)**: Source for the `idea-autopsy` skill - business-idea validation that hunts the one sentence that kills an idea before you build: kill-list check, five hard filters, free-AI one-prompt test, and live ad-market verification (MIT).
@@ -419,6 +424,7 @@ Key source families include:
419
424
  - **[mskadu/opencode-agent-skills](https://github.com/mskadu/opencode-agent-skills)**: Source for opencode behavior, permission, skill-suggestion, and smart Git automation skills.
420
425
  - **[adelaidasofia/ai-brain-starter](https://github.com/adelaidasofia/ai-brain-starter)**: Source for the `ingest-youtube` skill - YouTube transcript ingestion into markdown vaults with yt-dlp metadata, VTT cleanup, and capture-seed stubs (MIT).
421
426
  - **[JDDavenport/context-kit](https://github.com/JDDavenport/context-kit)**: Source reference for the `context-kit` skill - local-first Personal Context Artifact setup, installer review, and private context hygiene for Claude Code and adjacent agent workflows.
427
+ - **[JunsW/feature-track](https://github.com/JunsW/feature-track)**: Source for the `feature-tracking` skill - lightweight repository-native feature memory for current status, source-of-truth documents, decisions, risks, and cross-session handoff (MIT).
422
428
  - **[JularDepick/user-thoughts.SKILL](https://github.com/JularDepick/user-thoughts.SKILL)**: Source for the `user-thoughts` skill - persistent project idea repository workflows for capturing decisions, tech stack notes, UI/UX rationale, and MDBASE-backed project memory (MIT).
423
429
  - **[TheaDust/lore](https://github.com/TheaDust/lore)**: Source for the `lore` skill - Markdown-only, zero-dependency long-term project memory for AI coding agents, with monorepo scopes, two-section platform mirrors, and stdlib Python helpers (MIT).
424
430
  - **[ZeroPointRepo/youtube-skills](https://github.com/ZeroPointRepo/youtube-skills)**: Source for the `youtube-full` skill - TranscriptAPI-backed YouTube transcripts, search, channel browsing, playlists, and cloud-safe video research workflows (MIT).
@@ -555,6 +561,7 @@ Key source families include:
555
561
  - **[sergebulaev/linkedin-skills](https://github.com/sergebulaev/linkedin-skills)**: Source for the `linkedin-post-writer` skill - LinkedIn post drafting from 16 tested hook formulas mapped to engagement goals, with 2026 formatting rules and an AI-tell scrub pass, from a 10-skill LinkedIn bundle for Claude Code and Codex (MIT).
556
562
  - **[Sharrmavishal/operating-kit](https://github.com/Sharrmavishal/operating-kit)**: Source for the `pre-ship-gate` skill - a pre-deploy gate that walks the silent failure modes (migrations, feature flags, stale build cache, release pointer, staged rollout, missing env) and verifies the live revision instead of trusting deploy output (MIT).
557
563
  - **[Ducksss/codex-profiles](https://github.com/Ducksss/codex-profiles)**: Source for the `codex-profiles` skill - Codex CLI/Desktop profile isolation around separate `CODEX_HOME` directories, diagnostics, and account-context boundaries without copying auth tokens (MIT).
564
+ - **[kimtth/agent-pptify-kit](https://github.com/kimtth/agent-pptify-kit)**: Source for the `pptx-deck-creation` skill - editable, production-ready PowerPoint deck creation with narrative planning, explicit layouts, asset guidance, and quality checks (MIT).
558
565
 
559
566
  </details>
560
567
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentic-awesome-skills",
3
- "version": "14.3.1",
4
- "description": "1,948+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.",
3
+ "version": "14.4.0",
4
+ "description": "1,958+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.",
5
5
  "license": "MIT",
6
6
  "scripts": {
7
7
  "validate": "node tools/scripts/run-python.js tools/scripts/validate_skills.py",
@@ -45,6 +45,7 @@
45
45
  "security:docs": "node tools/scripts/tests/docs_security_content.test.js",
46
46
  "security:scan": "node tools/scripts/run-python.js tools/scripts/security_scanner.py",
47
47
  "security:scan:strict": "node tools/scripts/run-python.js tools/scripts/security_scanner.py --strict",
48
+ "lint:workflows": "node tools/scripts/run_actionlint.js",
48
49
  "score:skills": "node tools/scripts/run-python.js tools/scripts/score_skills.py",
49
50
  "score:skills:json": "node tools/scripts/run-python.js tools/scripts/score_skills.py --json",
50
51
  "score:skills:report": "node tools/scripts/run-python.js tools/scripts/score_skills.py --output data/scores.json",
@@ -52,6 +53,8 @@
52
53
  "drift:update": "node tools/scripts/run-python.js tools/scripts/detect_drift.py --update-baseline",
53
54
  "registry:report": "node tools/scripts/run-python.js tools/scripts/generate_registry_report.py",
54
55
  "pr:preflight": "node tools/scripts/pr_preflight.cjs",
56
+ "pr:evidence": "node tools/scripts/run-python.js tools/scripts/changed_skill_evidence.py",
57
+ "pr:decision": "node tools/scripts/pr_decision_manifest.cjs",
55
58
  "merge:batch": "node tools/scripts/merge_batch.cjs",
56
59
  "release:preflight": "node tools/scripts/release_workflow.js preflight",
57
60
  "release:prepare": "node tools/scripts/release_workflow.js prepare",
@@ -0,0 +1,83 @@
1
+ function splitNulFields(buffer, options = {}) {
2
+ if (!Buffer.isBuffer(buffer)) {
3
+ throw new Error("Raw Git diff must be provided as a Buffer.");
4
+ }
5
+ if (buffer.length === 0 && options.allowEmpty) {
6
+ return [];
7
+ }
8
+ if (buffer.length === 0 || buffer[buffer.length - 1] !== 0) {
9
+ throw new Error("Raw Git diff is empty or missing its final NUL terminator.");
10
+ }
11
+
12
+ const fields = [];
13
+ let start = 0;
14
+ for (let index = 0; index < buffer.length; index += 1) {
15
+ if (buffer[index] === 0) {
16
+ fields.push(buffer.subarray(start, index));
17
+ start = index + 1;
18
+ }
19
+ }
20
+ return fields;
21
+ }
22
+
23
+ function decodeGitPath(buffer) {
24
+ let value;
25
+ try {
26
+ value = new TextDecoder("utf-8", { fatal: true }).decode(buffer);
27
+ } catch {
28
+ throw new Error("Raw Git diff contains a path that is not canonical UTF-8.");
29
+ }
30
+ if (Buffer.from(value, "utf8").compare(buffer) !== 0) {
31
+ throw new Error("Raw Git diff path did not round-trip to canonical UTF-8 bytes.");
32
+ }
33
+ return value;
34
+ }
35
+
36
+ function parseRawDiff(buffer, options = {}) {
37
+ const fields = splitNulFields(buffer, options);
38
+ if (!fields.length) return [];
39
+ const records = [];
40
+
41
+ for (let index = 0; index < fields.length;) {
42
+ const header = fields[index].toString("ascii");
43
+ if (!fields[index].equals(Buffer.from(header, "ascii"))) {
44
+ throw new Error("Raw Git diff contains a non-ASCII record header.");
45
+ }
46
+ index += 1;
47
+
48
+ const match = header.match(
49
+ /^:(?<oldMode>[0-7]{6}) (?<newMode>[0-7]{6}) (?<oldOid>[0-9a-f]{40}|[0-9a-f]{64}) (?<newOid>[0-9a-f]{40}|[0-9a-f]{64}) (?<status>[ACDMRTUXB])(?<score>[0-9]{0,3})$/u,
50
+ );
51
+ if (!match?.groups || match.groups.oldOid.length !== match.groups.newOid.length) {
52
+ throw new Error(`Malformed raw Git diff header: ${header}`);
53
+ }
54
+
55
+ if (index >= fields.length) throw new Error("Raw Git diff record is missing its path.");
56
+ const firstPath = decodeGitPath(fields[index]);
57
+ index += 1;
58
+ const isRenameOrCopy = match.groups.status === "R" || match.groups.status === "C";
59
+ let secondPath = null;
60
+ if (isRenameOrCopy) {
61
+ if (index >= fields.length) {
62
+ throw new Error("Raw Git rename/copy record is missing its destination path.");
63
+ }
64
+ secondPath = decodeGitPath(fields[index]);
65
+ index += 1;
66
+ }
67
+
68
+ const status = match.groups.status;
69
+ records.push({
70
+ status,
71
+ old_path: status === "A" ? null : firstPath,
72
+ new_path: status === "D" ? null : isRenameOrCopy ? secondPath : firstPath,
73
+ old_mode: match.groups.oldMode,
74
+ new_mode: match.groups.newMode,
75
+ old_oid: match.groups.oldOid,
76
+ new_oid: match.groups.newOid,
77
+ similarity: match.groups.score ? Number(match.groups.score) : null,
78
+ });
79
+ }
80
+ return records;
81
+ }
82
+
83
+ module.exports = { decodeGitPath, parseRawDiff, splitNulFields };
@@ -0,0 +1,135 @@
1
+ const { classifyChangeRecords, classifyPathPolicy } = require("./workflow-contract");
2
+
3
+ const DECISION_SCHEMA_VERSION = 1;
4
+ const VALID_SEMANTIC_REVIEW_STATES = new Set(["available", "unavailable", "unknown"]);
5
+
6
+ function normalizePath(value) {
7
+ return String(value || "").replace(/^\.\//, "");
8
+ }
9
+
10
+ function isSensitivePath(filePath) {
11
+ const normalized = normalizePath(filePath);
12
+ return !classifyPathPolicy(normalized).approvalSafe;
13
+ }
14
+
15
+ function stableUnique(values) {
16
+ return [...new Set(values.filter(Boolean))].sort();
17
+ }
18
+
19
+ function changeType(change) {
20
+ return String(change?.change_type || change?.status || "").toLowerCase();
21
+ }
22
+
23
+ function buildDecisionManifest({ preflight, evidence, semanticReviewState = "unknown" }) {
24
+ if (!preflight || !Array.isArray(preflight.changedFiles)) {
25
+ throw new Error("preflight.changedFiles is required");
26
+ }
27
+ if (!evidence || !Array.isArray(evidence.changes) || typeof evidence.blocking !== "boolean") {
28
+ throw new Error("changed-skill evidence is incomplete");
29
+ }
30
+ if (!VALID_SEMANTIC_REVIEW_STATES.has(semanticReviewState)) {
31
+ throw new Error(`Invalid semantic review state: ${semanticReviewState}`);
32
+ }
33
+
34
+ const changedFiles = stableUnique(preflight.changedFiles.map(normalizePath));
35
+ const sensitivePaths = changedFiles.filter(isSensitivePath);
36
+ const changeRecords = Array.isArray(preflight.changeRecords) ? preflight.changeRecords : [];
37
+ const recordChangedFiles = stableUnique(changeRecords.flatMap((record) => [record?.old_path, record?.new_path])
38
+ .filter(Boolean)
39
+ .map(normalizePath));
40
+ const recordsConsistent = JSON.stringify(recordChangedFiles) === JSON.stringify(changedFiles);
41
+ const recordPolicy = changeRecords.length > 0
42
+ ? classifyChangeRecords(changeRecords, { requireBlobSizes: false })
43
+ : {
44
+ approvalSafe: changedFiles.length === 0,
45
+ reasons: changedFiles.length === 0 ? [] : ["missing_change_records"],
46
+ paths: [],
47
+ };
48
+ if (!recordsConsistent) {
49
+ recordPolicy.approvalSafe = false;
50
+ recordPolicy.reasons = stableUnique([...recordPolicy.reasons, "changed_files_records_mismatch"]);
51
+ }
52
+ const changedSkills = stableUnique(
53
+ evidence.changes.map((change) => change.skill_id || change.new_skill_id || change.old_skill_id),
54
+ );
55
+ const reasons = [];
56
+ let route = "eligible_for_later_automation";
57
+
58
+ if ((preflight.directDerivedChanges || []).length > 0) {
59
+ route = "block";
60
+ reasons.push("direct_derived_changes");
61
+ }
62
+ if (evidence.blocking) {
63
+ route = "block";
64
+ reasons.push("changed_skill_regression");
65
+ reasons.push(...(evidence.reasons || []).map((reason) => `evidence:${reason}`));
66
+ }
67
+
68
+ if (route !== "block") {
69
+ if (sensitivePaths.length > 0) {
70
+ route = "human_review";
71
+ reasons.push("sensitive_paths");
72
+ }
73
+ if (!recordPolicy.approvalSafe) {
74
+ route = "human_review";
75
+ reasons.push("unsafe_change_records");
76
+ reasons.push(...recordPolicy.reasons.map((reason) => `record:${reason}`));
77
+ }
78
+ if (evidence.changes.length > 0) {
79
+ route = "human_review";
80
+ reasons.push("canonical_skill_content_changed");
81
+ }
82
+ if (evidence.changes.some((change) => ["added", "renamed", "copied"].includes(changeType(change)))) {
83
+ route = "human_review";
84
+ reasons.push("new_or_relocated_skill");
85
+ }
86
+ if (semanticReviewState !== "available" && evidence.changes.length > 0) {
87
+ route = "human_review";
88
+ reasons.push(
89
+ semanticReviewState === "unavailable"
90
+ ? "semantic_review_unavailable"
91
+ : "semantic_review_unknown",
92
+ );
93
+ }
94
+ }
95
+
96
+ if (route === "eligible_for_later_automation") {
97
+ reasons.push("deterministic_low_risk_candidate");
98
+ }
99
+
100
+ return {
101
+ schema_version: DECISION_SCHEMA_VERSION,
102
+ mode: "shadow",
103
+ untrusted_advisory: true,
104
+ route,
105
+ reason_codes: stableUnique(reasons),
106
+ base_ref: evidence.base_ref || preflight.baseRef || null,
107
+ head_ref: evidence.head_ref || preflight.headRef || null,
108
+ semantic_review: { state: semanticReviewState },
109
+ change: {
110
+ primary_category: preflight.primaryCategory || "none",
111
+ categories: stableUnique(preflight.categories || []),
112
+ changed_files_count: changedFiles.length,
113
+ changed_files: changedFiles,
114
+ change_records: changeRecords,
115
+ record_policy: {
116
+ approval_safe: recordPolicy.approvalSafe,
117
+ reasons: stableUnique(recordPolicy.reasons),
118
+ },
119
+ sensitive_paths: sensitivePaths,
120
+ direct_derived_changes: stableUnique(preflight.directDerivedChanges || []),
121
+ changed_skills: changedSkills,
122
+ },
123
+ deterministic_gate: {
124
+ blocking: Boolean(evidence.blocking || (preflight.directDerivedChanges || []).length > 0),
125
+ evidence_reasons: stableUnique(evidence.reasons || []),
126
+ },
127
+ };
128
+ }
129
+
130
+ module.exports = {
131
+ DECISION_SCHEMA_VERSION,
132
+ buildDecisionManifest,
133
+ isSensitivePath,
134
+ normalizePath,
135
+ };
@@ -17,10 +17,306 @@ const REFERENCES_FILES = new Set([
17
17
  "package-lock.json",
18
18
  ]);
19
19
 
20
+ const REGULAR_FILE_MODE = "100644";
21
+ const ABSENT_FILE_MODE = "000000";
22
+ const MAX_APPROVAL_BLOB_BYTES = 1024 * 1024;
23
+ const MAX_APPROVAL_CHANGE_RECORDS = 500;
24
+ const MAX_APPROVAL_TOTAL_BLOB_BYTES = 8 * 1024 * 1024;
25
+ const SKILL_PATH_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
26
+ const CANONICAL_SKILL_PATH = new RegExp(`^skills\\/(?:${SKILL_PATH_SEGMENT}\\/)+SKILL\\.md$`, "u");
27
+ const SKILL_SUPPORT_PATH = new RegExp(
28
+ `^skills\\/(?:${SKILL_PATH_SEGMENT}\\/)+(?:assets|references|resources)\\/(?:[^/]+\\/)*[^/]+$`,
29
+ "u",
30
+ );
31
+ const NARROW_DOC_PATH = /^(?:README\.md|CONTRIBUTING\.md|docs\/(?:[^/]+\/)*[^/]+\.md)$/;
32
+ const LOW_RISK_EXTENSIONS = new Set([
33
+ ".csv",
34
+ ".gif",
35
+ ".jpeg",
36
+ ".jpg",
37
+ ".json",
38
+ ".md",
39
+ ".pdf",
40
+ ".png",
41
+ ".txt",
42
+ ".webp",
43
+ ".yaml",
44
+ ".yml",
45
+ ]);
46
+ const CHANGE_STATUS_SIDES = Object.freeze({
47
+ A: { old: false, new: true },
48
+ C: { old: true, new: true },
49
+ D: { old: true, new: false },
50
+ M: { old: true, new: true },
51
+ R: { old: true, new: true },
52
+ });
53
+
20
54
  function normalizeRepoPath(filePath) {
21
55
  return String(filePath || "").replace(/\\/g, "/").replace(/^\.\//, "");
22
56
  }
23
57
 
58
+ function validateRawRepoPath(filePath) {
59
+ const value = typeof filePath === "string" ? filePath : "";
60
+ const reasons = [];
61
+
62
+ if (!value) {
63
+ reasons.push("missing_path");
64
+ }
65
+ if (/[\u0000-\u001f\u007f]/u.test(value)) {
66
+ reasons.push("control_character_path");
67
+ }
68
+ if (value.includes("\\")) {
69
+ reasons.push("backslash_path");
70
+ }
71
+ if (value.startsWith("/") || /^[A-Za-z]:/u.test(value)) {
72
+ reasons.push("absolute_path");
73
+ }
74
+ if (value.startsWith("./") || value.includes("//")) {
75
+ reasons.push("noncanonical_path");
76
+ }
77
+
78
+ const segments = value.split("/");
79
+ if (segments.some((segment) => (
80
+ !segment ||
81
+ segment === "." ||
82
+ segment === ".." ||
83
+ segment.trim() !== segment ||
84
+ segment.endsWith(".") ||
85
+ segment.includes(":") ||
86
+ Buffer.byteLength(segment, "utf8") > 255
87
+ ))) {
88
+ reasons.push("noncanonical_path");
89
+ }
90
+ if (Buffer.byteLength(value, "utf8") > 4096) {
91
+ reasons.push("noncanonical_path");
92
+ }
93
+ if (value && path.posix.normalize(value) !== value) {
94
+ reasons.push("noncanonical_path");
95
+ }
96
+ if (value && value.normalize("NFC") !== value) {
97
+ reasons.push("noncanonical_unicode_path");
98
+ }
99
+
100
+ return {
101
+ safe: reasons.length === 0,
102
+ reasons: [...new Set(reasons)],
103
+ };
104
+ }
105
+
106
+ function classifyPathPolicy(filePath) {
107
+ const pathValidation = validateRawRepoPath(filePath);
108
+ if (!pathValidation.safe) {
109
+ return {
110
+ safe: false,
111
+ sensitive: true,
112
+ approvalSafe: false,
113
+ kind: "invalid",
114
+ reasons: pathValidation.reasons,
115
+ };
116
+ }
117
+
118
+ let kind = "unknown";
119
+ if (CANONICAL_SKILL_PATH.test(filePath)) {
120
+ kind = "canonical_skill";
121
+ } else if (SKILL_SUPPORT_PATH.test(filePath)) {
122
+ kind = "skill_support";
123
+ } else if (NARROW_DOC_PATH.test(filePath)) {
124
+ kind = "documentation";
125
+ }
126
+
127
+ const extension = path.posix.extname(filePath).toLowerCase();
128
+ const recognizedExtension = LOW_RISK_EXTENSIONS.has(extension);
129
+ const reasons = [];
130
+ if (kind === "unknown") {
131
+ reasons.push("unapproved_path");
132
+ }
133
+ if (!recognizedExtension) {
134
+ reasons.push("unknown_extension");
135
+ }
136
+
137
+ const approvalSafe = reasons.length === 0;
138
+ return {
139
+ safe: approvalSafe,
140
+ sensitive: !approvalSafe,
141
+ approvalSafe,
142
+ kind,
143
+ reasons,
144
+ };
145
+ }
146
+
147
+ function isFullObjectId(value, options = {}) {
148
+ const allowZero = Boolean(options.allowZero);
149
+ if (typeof value !== "string" || !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/u.test(value)) {
150
+ return false;
151
+ }
152
+ return allowZero || !/^0+$/u.test(value);
153
+ }
154
+
155
+ function readBlobSize(record, side, blobSizes) {
156
+ const inlineSize = record[`${side}_size`];
157
+ if (Object.prototype.hasOwnProperty.call(record, `${side}_size`)) {
158
+ return Number.isSafeInteger(inlineSize) && inlineSize >= 0 ? inlineSize : undefined;
159
+ }
160
+
161
+ const oid = record[`${side}_oid`];
162
+ if (blobSizes instanceof Map) {
163
+ return blobSizes.get(oid);
164
+ }
165
+ if (blobSizes && typeof blobSizes === "object") {
166
+ return blobSizes[oid];
167
+ }
168
+ return undefined;
169
+ }
170
+
171
+ function classifyChangeRecords(records, options = {}) {
172
+ const requireBlobSizes = options.requireBlobSizes !== false;
173
+ const maxBlobBytes = Number.isSafeInteger(options.maxBlobBytes) && options.maxBlobBytes >= 0
174
+ ? options.maxBlobBytes
175
+ : MAX_APPROVAL_BLOB_BYTES;
176
+ const maxChangeRecords = Number.isSafeInteger(options.maxChangeRecords) && options.maxChangeRecords >= 0
177
+ ? options.maxChangeRecords
178
+ : MAX_APPROVAL_CHANGE_RECORDS;
179
+ const maxTotalBlobBytes = Number.isSafeInteger(options.maxTotalBlobBytes) && options.maxTotalBlobBytes >= 0
180
+ ? options.maxTotalBlobBytes
181
+ : MAX_APPROVAL_TOTAL_BLOB_BYTES;
182
+ const reasons = [];
183
+ const paths = [];
184
+ const canonicalSkillChanges = [];
185
+ const skillContentChanges = [];
186
+ let sensitive = false;
187
+ let totalBlobBytes = 0;
188
+
189
+ if (!Array.isArray(records) || records.length === 0) {
190
+ return {
191
+ safe: false,
192
+ sensitive: true,
193
+ approvalSafe: false,
194
+ reasons: ["missing_change_records"],
195
+ paths,
196
+ requiresHumanReview: false,
197
+ canonicalSkillChanges,
198
+ skillContentChanges,
199
+ };
200
+ }
201
+ if (records.length > maxChangeRecords) {
202
+ reasons.push("too_many_change_records");
203
+ sensitive = true;
204
+ }
205
+
206
+ records.forEach((record, index) => {
207
+ const prefix = `record_${index}`;
208
+ if (!record || typeof record !== "object") {
209
+ reasons.push(`${prefix}:malformed_record`);
210
+ sensitive = true;
211
+ return;
212
+ }
213
+
214
+ const status = String(record.status || "");
215
+ const expectedSides = CHANGE_STATUS_SIDES[status];
216
+ if (!expectedSides) {
217
+ reasons.push(`${prefix}:unknown_status`);
218
+ sensitive = true;
219
+ return;
220
+ }
221
+
222
+ for (const side of ["old", "new"]) {
223
+ const expected = expectedSides[side];
224
+ const filePath = record[`${side}_path`];
225
+ const mode = String(record[`${side}_mode`] || "");
226
+ const oid = String(record[`${side}_oid`] || "");
227
+
228
+ if (!expected) {
229
+ if (filePath !== null) {
230
+ reasons.push(`${prefix}:${side}_invalid_absent_path`);
231
+ }
232
+ if (mode !== ABSENT_FILE_MODE || !isFullObjectId(oid, { allowZero: true }) || !/^0+$/u.test(oid)) {
233
+ reasons.push(`${prefix}:${side}_invalid_absent_side`);
234
+ }
235
+ continue;
236
+ }
237
+
238
+ if (typeof filePath !== "string" || !filePath) {
239
+ reasons.push(`${prefix}:${side}_missing_path`);
240
+ sensitive = true;
241
+ continue;
242
+ }
243
+
244
+ const pathPolicy = classifyPathPolicy(filePath);
245
+ paths.push({ record: index, side, path: filePath, ...pathPolicy });
246
+ if (!pathPolicy.approvalSafe) {
247
+ sensitive = true;
248
+ for (const reason of pathPolicy.reasons) {
249
+ reasons.push(`${prefix}:${side}_${reason}`);
250
+ }
251
+ }
252
+ if (pathPolicy.kind === "canonical_skill") {
253
+ canonicalSkillChanges.push(filePath);
254
+ }
255
+ if (["canonical_skill", "skill_support"].includes(pathPolicy.kind)) {
256
+ skillContentChanges.push(filePath);
257
+ }
258
+
259
+ if (mode !== REGULAR_FILE_MODE) {
260
+ sensitive = true;
261
+ if (mode === "100755") {
262
+ reasons.push(`${prefix}:${side}_executable_mode`);
263
+ } else if (mode === "120000") {
264
+ reasons.push(`${prefix}:${side}_symlink_mode`);
265
+ } else if (mode === "160000") {
266
+ reasons.push(`${prefix}:${side}_gitlink_mode`);
267
+ } else {
268
+ reasons.push(`${prefix}:${side}_unknown_mode`);
269
+ }
270
+ }
271
+
272
+ if (!isFullObjectId(oid)) {
273
+ sensitive = true;
274
+ reasons.push(`${prefix}:${side}_invalid_object_id`);
275
+ } else {
276
+ const blobSize = readBlobSize(record, side, options.blobSizes);
277
+ if ((!Number.isSafeInteger(blobSize) || blobSize < 0) && requireBlobSizes) {
278
+ sensitive = true;
279
+ reasons.push(`${prefix}:${side}_missing_blob_size`);
280
+ } else if (Number.isSafeInteger(blobSize) && blobSize > maxBlobBytes) {
281
+ sensitive = true;
282
+ reasons.push(`${prefix}:${side}_oversized_blob`);
283
+ } else if (Number.isSafeInteger(blobSize)) {
284
+ totalBlobBytes += blobSize;
285
+ }
286
+ }
287
+ }
288
+
289
+ if (status === "M" && record.old_path !== record.new_path) {
290
+ reasons.push(`${prefix}:path_changed_without_rename`);
291
+ sensitive = true;
292
+ }
293
+ if ((status === "R" || status === "C") && record.old_path === record.new_path) {
294
+ reasons.push(`${prefix}:rename_copy_path_unchanged`);
295
+ sensitive = true;
296
+ }
297
+ });
298
+
299
+ if (totalBlobBytes > maxTotalBlobBytes) {
300
+ reasons.push("oversized_total_diff");
301
+ sensitive = true;
302
+ }
303
+
304
+ const uniqueReasons = [...new Set(reasons)];
305
+ const uniqueCanonicalSkillChanges = [...new Set(canonicalSkillChanges)].sort();
306
+ const uniqueSkillContentChanges = [...new Set(skillContentChanges)].sort();
307
+ const approvalSafe = uniqueReasons.length === 0 && !sensitive;
308
+ return {
309
+ safe: approvalSafe,
310
+ sensitive: !approvalSafe,
311
+ approvalSafe,
312
+ reasons: uniqueReasons,
313
+ paths,
314
+ requiresHumanReview: uniqueSkillContentChanges.length > 0,
315
+ canonicalSkillChanges: uniqueCanonicalSkillChanges,
316
+ skillContentChanges: uniqueSkillContentChanges,
317
+ };
318
+ }
319
+
24
320
  function matchesContractEntry(filePath, entry) {
25
321
  const normalizedPath = normalizeRepoPath(filePath);
26
322
  const normalizedEntry = normalizeRepoPath(entry);
@@ -182,7 +478,9 @@ function hasIssueLink(body) {
182
478
  }
183
479
 
184
480
  module.exports = {
481
+ classifyChangeRecords,
185
482
  classifyChangedFiles,
483
+ classifyPathPolicy,
186
484
  extractChangelogSection,
187
485
  getDirectDerivedChanges,
188
486
  getManagedFiles,
@@ -191,7 +489,11 @@ module.exports = {
191
489
  isDerivedFile,
192
490
  isMixedFile,
193
491
  loadWorkflowContract,
492
+ maxApprovalBlobBytes: MAX_APPROVAL_BLOB_BYTES,
493
+ maxApprovalChangeRecords: MAX_APPROVAL_CHANGE_RECORDS,
494
+ maxApprovalTotalBlobBytes: MAX_APPROVAL_TOTAL_BLOB_BYTES,
194
495
  normalizeRepoPath,
195
496
  matchesContractEntry,
196
497
  requiresReferencesValidation,
498
+ validateRawRepoPath,
197
499
  };