@ikon85/agent-workflow-kit 0.24.0 → 0.25.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/.agents/skills/setup-workflow/workflow-advisories.md +28 -6
- package/.claude/hooks/_hook_utils.py +19 -0
- package/.claude/hooks/_safety_guard.py +24 -0
- package/.claude/hooks/block-bg-double-background.py +30 -0
- package/.claude/hooks/block-npm-install-in-pnpm.py +30 -0
- package/.claude/hooks/block-secrets.py +30 -0
- package/.claude/hooks/convention-drift-hint.py +27 -0
- package/.claude/hooks/grep-shim-guard.py +30 -0
- package/.claude/hooks/loc-offender-forewarn.py +56 -0
- package/.claude/hooks/migration-snapshot-reminder.py +24 -0
- package/.claude/hooks/skill-drift-hint.py +9 -45
- package/.claude/skills/setup-workflow/workflow-advisories.md +28 -6
- package/README.md +13 -0
- package/agent-workflow-kit.package.json +35 -7
- package/package.json +1 -1
- package/scripts/loc_offender_gate.py +24 -0
- package/scripts/safety-guardrails/core.py +221 -0
- package/scripts/safety-guardrails/search.py +92 -0
- package/scripts/security/audit-gate.mjs +122 -0
- package/scripts/security/ensure-gitleaks.mjs +101 -0
- package/scripts/security/gitleaks-profile.json +14 -0
- package/scripts/security/install-git-hooks.mjs +52 -0
- package/scripts/workflow-advisories/capabilities.json +33 -0
- package/scripts/workflow-advisories/core.py +100 -0
- package/src/lib/bundle.mjs +4 -0
|
@@ -183,3 +183,103 @@ def stop_check_decision(profile: dict, payload: dict, root: Path) -> Decision:
|
|
|
183
183
|
return _command_decision(
|
|
184
184
|
commands, config, root, "Changed-surface stop checks:", "Stop",
|
|
185
185
|
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def git_commit_time(root: Path, relative_path: str) -> int | None:
|
|
189
|
+
try:
|
|
190
|
+
result = subprocess.run(
|
|
191
|
+
["git", "-C", str(root), "log", "-1", "--format=%ct", "--", relative_path],
|
|
192
|
+
capture_output=True, text=True, timeout=5,
|
|
193
|
+
)
|
|
194
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
195
|
+
return None
|
|
196
|
+
value = result.stdout.strip()
|
|
197
|
+
return int(value) if result.returncode == 0 and value.isdigit() else None
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def read_source_list(path: Path) -> list[str]:
|
|
201
|
+
try:
|
|
202
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
203
|
+
except OSError:
|
|
204
|
+
return []
|
|
205
|
+
return [
|
|
206
|
+
line.strip() for line in lines
|
|
207
|
+
if line.strip() and not line.strip().startswith("#")
|
|
208
|
+
]
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def collect_stale_maps(
|
|
212
|
+
root: Path, maps: list[tuple[str, list[str]]],
|
|
213
|
+
) -> list[tuple[str, list[str]]]:
|
|
214
|
+
stale_maps: list[tuple[str, list[str]]] = []
|
|
215
|
+
for document, sources in maps:
|
|
216
|
+
document_time = git_commit_time(root, document)
|
|
217
|
+
if document_time is None:
|
|
218
|
+
continue
|
|
219
|
+
stale = [
|
|
220
|
+
source for source in sources
|
|
221
|
+
if (source_time := git_commit_time(root, source)) is not None
|
|
222
|
+
and source_time > document_time
|
|
223
|
+
]
|
|
224
|
+
if stale:
|
|
225
|
+
stale_maps.append((document, stale))
|
|
226
|
+
return stale_maps
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def collect_skill_stale(root: Path, skills_relative: str) -> list[tuple[str, list[str]]]:
|
|
230
|
+
skills_dir = root / skills_relative
|
|
231
|
+
maps = [
|
|
232
|
+
(
|
|
233
|
+
f"{skills_relative}/{sources_file.parent.name}/SKILL.md",
|
|
234
|
+
read_source_list(sources_file),
|
|
235
|
+
)
|
|
236
|
+
for sources_file in sorted(skills_dir.glob("*/SOURCES.txt"))
|
|
237
|
+
] if skills_dir.is_dir() else []
|
|
238
|
+
return [
|
|
239
|
+
(Path(document).parent.name, sources)
|
|
240
|
+
for document, sources in collect_stale_maps(root, maps)
|
|
241
|
+
]
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def convention_freshness_decision(profile: dict, root: Path) -> Decision:
|
|
245
|
+
config = profile.get("freshness", {})
|
|
246
|
+
maps = [
|
|
247
|
+
(entry.get("document", ""), entry.get("sources", []))
|
|
248
|
+
for entry in config.get("documents", [])
|
|
249
|
+
if entry.get("document")
|
|
250
|
+
]
|
|
251
|
+
stale = collect_stale_maps(root, maps)
|
|
252
|
+
if not stale:
|
|
253
|
+
return Decision(None, "SessionStart")
|
|
254
|
+
lines = ["Convention freshness advisory:"]
|
|
255
|
+
for document, sources in stale:
|
|
256
|
+
lines.append(f"- {document} is older than:")
|
|
257
|
+
lines.extend(f" - {source}" for source in sources)
|
|
258
|
+
return Decision(
|
|
259
|
+
_bounded("\n".join(lines), int(config.get("outputBudget", 1000))),
|
|
260
|
+
"SessionStart",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def migration_reminder_decision(profile: dict, payload: dict) -> Decision:
|
|
265
|
+
config = profile.get("migration", {})
|
|
266
|
+
if payload.get("tool_name") != "Bash":
|
|
267
|
+
return Decision(None, "PostToolUse")
|
|
268
|
+
command = payload.get("tool_input", {}).get("command", "")
|
|
269
|
+
if not any(
|
|
270
|
+
re.search(pattern, command)
|
|
271
|
+
for pattern in config.get("commandMatchers", [])
|
|
272
|
+
):
|
|
273
|
+
return Decision(None, "PostToolUse")
|
|
274
|
+
artifact = config.get("artifact")
|
|
275
|
+
refresh = config.get("refreshCommand", [])
|
|
276
|
+
if not artifact or not refresh:
|
|
277
|
+
return Decision(None, "PostToolUse")
|
|
278
|
+
message = (
|
|
279
|
+
f"Migration advisory: refresh {artifact} with `{' '.join(refresh)}` "
|
|
280
|
+
"before treating the migration result as complete."
|
|
281
|
+
)
|
|
282
|
+
return Decision(
|
|
283
|
+
_bounded(message, int(config.get("outputBudget", 500))),
|
|
284
|
+
"PostToolUse",
|
|
285
|
+
)
|
package/src/lib/bundle.mjs
CHANGED
|
@@ -94,11 +94,15 @@ export const HELPER_FILES = [
|
|
|
94
94
|
// Profile-driven non-blocking change-lifecycle advisories. The shell Stop
|
|
95
95
|
// entry delegates to its sibling Python adapter; decisions stay in core.py.
|
|
96
96
|
{ path: 'scripts/workflow-advisories/core.py', kind: 'script', mode: 0o644 },
|
|
97
|
+
{ path: 'scripts/workflow-advisories/capabilities.json', kind: 'doc', mode: 0o644 },
|
|
97
98
|
{ path: '.claude/hooks/recon-size-hint.py', kind: 'hook', mode: 0o755 },
|
|
98
99
|
{ path: '.claude/hooks/baseline-capture-hint.py', kind: 'hook', mode: 0o755 },
|
|
99
100
|
{ path: '.claude/hooks/pre-refactor-sweep.py', kind: 'hook', mode: 0o755 },
|
|
100
101
|
{ path: '.claude/hooks/typecheck-on-stop.py', kind: 'hook', mode: 0o755 },
|
|
101
102
|
{ path: '.claude/hooks/typecheck-on-stop.sh', kind: 'hook', mode: 0o755 },
|
|
103
|
+
{ path: '.claude/hooks/convention-drift-hint.py', kind: 'hook', mode: 0o755 },
|
|
104
|
+
{ path: '.claude/hooks/migration-snapshot-reminder.py', kind: 'hook', mode: 0o755 },
|
|
105
|
+
{ path: '.claude/hooks/loc-offender-forewarn.py', kind: 'hook', mode: 0o755 },
|
|
102
106
|
{ path: '.claude/hooks/drift-guard.py', kind: 'hook', mode: 0o755 },
|
|
103
107
|
// SessionStart skill-freshness drift-hint (audit-skills names it). For each
|
|
104
108
|
// <skill>/SOURCES.txt it flags sources newer in git than the SKILL.md. Imports
|