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 +16 -9
- package/package.json +5 -2
- package/tools/lib/git-raw-diff.js +83 -0
- package/tools/lib/pr-decision.js +135 -0
- package/tools/lib/workflow-contract.js +302 -0
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
<!-- registry-sync: version=14.
|
|
1
|
+
<!-- registry-sync: version=14.4.0; skills=1958; stars=43124; updated_at=2026-07-14T07:01:47+00:00 -->
|
|
2
2
|
[](https://github.com/sickn33/agentic-awesome-skills)
|
|
3
3
|
|
|
4
|
-
# 🌌 Agentic Awesome Skills: 1,
|
|
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,
|
|
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,
|
|
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
|
[](https://github.com/sickn33/agentic-awesome-skills/stargazers)
|
|
19
19
|
[](https://x.com/AASkills_)
|
|
@@ -30,13 +30,13 @@ The canonical project page is the GitHub repository at <https://github.com/sickn
|
|
|
30
30
|
[](https://github.com/opencode-ai/opencode)
|
|
31
31
|
[](https://github.com/sickn33/agentic-awesome-skills)
|
|
32
32
|
|
|
33
|
-
**Current release: V14.
|
|
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,
|
|
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,
|
|
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.
|
|
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,
|
|
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.
|
|
4
|
-
"description": "1,
|
|
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
|
};
|