@1aboveio/skills 0.17.0 → 0.19.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.
Files changed (51) hide show
  1. package/README.md +4 -4
  2. package/package.json +1 -1
  3. package/runtime/skills/distribution/generated/recipes.json +52 -32
  4. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +1 -1
  5. package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +23 -1
  6. package/skills/cicd-pipeline/mergify/references/watch-contract.md +2 -2
  7. package/skills/cicd-pipeline/mergify/scripts/watch-pr-delivery-core.mjs +48 -4
  8. package/skills/data-science/airflow/SKILL.md +198 -0
  9. package/skills/data-science/pyspark/assets/templates/etl.py +1 -0
  10. package/skills/data-science/pyspark/references/etl-contract.md +3 -0
  11. package/skills/engineering/engineering-runtime/coherence/workflow.json +65 -15
  12. package/skills/engineering/engineering-runtime/scripts/workflow-coherence.mjs +1 -1
  13. package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +23 -1
  14. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  15. package/skills/engineering/resolve-issues/generated/workflow-repair-policy.json +55 -11
  16. package/skills/engineering/resolve-issues/scripts/run-state.mjs +4 -4
  17. package/skills/engineering/resolve-release/SKILL.md +2 -1
  18. package/skills/engineering/resolve-release/agents/openai.yaml +9 -0
  19. package/skills/engineering/resolve-release/references/related-skills.md +1 -0
  20. package/skills/engineering/rush-issues/LICENSE +3 -0
  21. package/skills/engineering/rush-issues/SKILL.md +179 -0
  22. package/skills/engineering/rush-issues/agents/openai.yaml +9 -0
  23. package/skills/engineering/rush-issues/evals/evals.json +65 -0
  24. package/skills/engineering/rush-issues/references/canary.md +45 -0
  25. package/skills/engineering/rush-issues/references/cicd.md +37 -0
  26. package/skills/engineering/rush-issues/references/combine.md +51 -0
  27. package/skills/engineering/rush-issues/references/expire.md +55 -0
  28. package/skills/engineering/rush-issues/references/exploration.md +53 -0
  29. package/skills/engineering/rush-issues/references/implementation.md +66 -0
  30. package/skills/engineering/rush-issues/references/preflight.md +31 -0
  31. package/skills/engineering/rush-issues/references/profiling.md +78 -0
  32. package/skills/engineering/rush-issues/references/review.md +48 -0
  33. package/skills/engineering/rush-issues/references/shared-modules.md +66 -0
  34. package/skills/engineering/rush-issues/references/task-plan.md +110 -0
  35. package/skills/engineering/rush-issues/scripts/discover-models.mjs +9 -0
  36. package/skills/engineering/rush-issues/scripts/model-catalog.mjs +9 -0
  37. package/skills/engineering/rush-issues/scripts/preflight-models.mjs +466 -0
  38. package/skills/engineering/rush-release/LICENSE +3 -0
  39. package/skills/engineering/rush-release/SKILL.md +99 -0
  40. package/skills/engineering/rush-release/agents/openai.yaml +8 -0
  41. package/skills/engineering/rush-release/evals/evals.json +44 -0
  42. package/skills/engineering/rush-release/references/candidate.md +30 -0
  43. package/skills/engineering/rush-release/references/cut.md +66 -0
  44. package/skills/engineering/rush-release/references/preflight.md +47 -0
  45. package/skills/engineering/rush-release/references/publish.md +100 -0
  46. package/skills/engineering/rush-release/scripts/apply.mjs +185 -0
  47. package/skills/engineering/rush-release/scripts/green-head.mjs +231 -0
  48. package/skills/engineering/rush-release/scripts/plan.mjs +264 -0
  49. package/skills/fullstack/zod-v4/SKILL.md +1 -1
  50. package/skills/data-science/airflow-dag-develop/SKILL.md +0 -111
  51. /package/skills/data-science/{airflow-dag-develop → airflow}/LICENSE +0 -0
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ // Changelog + SemVer plan from baseline tag..SHA.
3
+ //
4
+ // Usage:
5
+ // node plan.mjs --sha <sha> [--trunk main] [--json] [--strict]
6
+ //
7
+ // Exit: 0 ok · 1 git/inspect failed · 2 usage
8
+ import { existsSync, readFileSync } from 'node:fs'
9
+ import { join } from 'node:path'
10
+ import { spawnSync } from 'node:child_process'
11
+ import { isMainModule } from '../../engineering-runtime/scripts/main-module.mjs'
12
+
13
+ const SEMVER_RE = /^v?(\d+)\.(\d+)\.(\d+)$/
14
+ const TITLE_RE = /^\s*([a-zA-Z]+)(?:\(([^)]*)\))?(!)?:\s*(.+)$/
15
+ const SECTION_BY_TYPE = {
16
+ feat: 'Added',
17
+ fix: 'Fixed',
18
+ perf: 'Changed',
19
+ refactor: 'Changed',
20
+ revert: 'Removed',
21
+ security: 'Security',
22
+ }
23
+ const INTERNAL_TYPES = new Set(['build', 'chore', 'ci', 'docs', 'style', 'test'])
24
+ const SECTION_ORDER = ['Breaking', 'Added', 'Changed', 'Removed', 'Fixed', 'Security']
25
+
26
+ export function parseSemver(input) {
27
+ const match = SEMVER_RE.exec(String(input || '').trim())
28
+ if (!match) return null
29
+ return { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) }
30
+ }
31
+
32
+ export function formatVersion(version) {
33
+ return `${version.major}.${version.minor}.${version.patch}`
34
+ }
35
+
36
+ export function compareVersions(left, right) {
37
+ for (const key of ['major', 'minor', 'patch']) {
38
+ if (left[key] !== right[key]) return left[key] < right[key] ? -1 : 1
39
+ }
40
+ return 0
41
+ }
42
+
43
+ export function latestReleaseTag(tags) {
44
+ const parsed = tags
45
+ .map((tag) => ({ tag, version: parseSemver(tag) }))
46
+ .filter((item) => item.version)
47
+ if (parsed.length === 0) return null
48
+ parsed.sort((left, right) => compareVersions(left.version, right.version))
49
+ return parsed[parsed.length - 1]
50
+ }
51
+
52
+ export function classifySubject(subject, body = '') {
53
+ const title = String(subject || '').trim()
54
+ const match = TITLE_RE.exec(title)
55
+ const type = match ? match[1].toLowerCase() : 'other'
56
+ const breaking = Boolean(match && match[3]) || /(^|\n)BREAKING[ -]CHANGE:/.test(body)
57
+ const internal = INTERNAL_TYPES.has(type)
58
+ const section = breaking ? 'Breaking' : internal ? null : (SECTION_BY_TYPE[type] || 'Changed')
59
+ return {
60
+ title,
61
+ type,
62
+ breaking,
63
+ internal,
64
+ section,
65
+ scope: match ? match[2] || null : null,
66
+ summary: match ? match[4] : title,
67
+ }
68
+ }
69
+
70
+ export function bumpFor(changes, current, { strict = false } = {}) {
71
+ const zeroVer = !strict && current.major === 0
72
+ if (changes.some((change) => change.breaking)) {
73
+ return { bump: zeroVer ? 'minor' : 'major', reason: 'breaking change in the release content' }
74
+ }
75
+ if (changes.some((change) => change.type === 'feat')) {
76
+ return { bump: 'minor', reason: 'new feature in the release content' }
77
+ }
78
+ return { bump: 'patch', reason: 'fixes and internal changes only' }
79
+ }
80
+
81
+ export function nextVersion(current, bump) {
82
+ if (bump === 'major') return { major: current.major + 1, minor: 0, patch: 0 }
83
+ if (bump === 'minor') return { major: current.major, minor: current.minor + 1, patch: 0 }
84
+ return { major: current.major, minor: current.minor, patch: current.patch + 1 }
85
+ }
86
+
87
+ export function renderChangelog({ version, date, changes }) {
88
+ const groups = new Map(SECTION_ORDER.map((name) => [name, []]))
89
+ for (const change of changes) {
90
+ if (!change.section) continue
91
+ const list = groups.get(change.section)
92
+ if (list) list.push(change)
93
+ }
94
+ const lines = [`## [${version}] - ${date}`]
95
+ for (const name of SECTION_ORDER) {
96
+ const items = groups.get(name)
97
+ if (!items.length) continue
98
+ lines.push('', `### ${name}`)
99
+ for (const item of items) lines.push(`- ${item.summary}`)
100
+ }
101
+ if (lines.length === 1) {
102
+ lines.push('', '- See the commits included in this tag.')
103
+ }
104
+ return `${lines.join('\n')}\n`
105
+ }
106
+
107
+ export function discoverVersionFiles(root) {
108
+ const files = []
109
+ for (const name of ['package.json', 'package-lock.json', 'pyproject.toml', 'VERSION']) {
110
+ if (existsSync(join(root, name))) files.push(name)
111
+ }
112
+ return files
113
+ }
114
+
115
+ export function currentVersionFromFiles(root, files = discoverVersionFiles(root)) {
116
+ if (files.includes('package.json')) {
117
+ const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))
118
+ const parsed = parseSemver(pkg.version)
119
+ if (parsed) return formatVersion(parsed)
120
+ }
121
+ if (files.includes('pyproject.toml')) {
122
+ const text = readFileSync(join(root, 'pyproject.toml'), 'utf8')
123
+ const match = text.match(/^version\s*=\s*"([^"]+)"/m)
124
+ const parsed = match && parseSemver(match[1])
125
+ if (parsed) return formatVersion(parsed)
126
+ }
127
+ if (files.includes('VERSION')) {
128
+ const parsed = parseSemver(readFileSync(join(root, 'VERSION'), 'utf8'))
129
+ if (parsed) return formatVersion(parsed)
130
+ }
131
+ return null
132
+ }
133
+
134
+ function git(args, cwd) {
135
+ const result = spawnSync('git', args, { encoding: 'utf8', cwd })
136
+ if (result.status !== 0) {
137
+ const err = new Error(result.stderr.trim() || `git ${args.join(' ')} failed`)
138
+ err.exitCode = 1
139
+ throw err
140
+ }
141
+ return result.stdout
142
+ }
143
+
144
+ function versionFilesAt(sha, cwd) {
145
+ const tree = new Set(git(['ls-tree', '-r', '--name-only', sha], cwd)
146
+ .split('\n')
147
+ .map((line) => line.trim())
148
+ .filter(Boolean))
149
+ return ['package.json', 'package-lock.json', 'pyproject.toml', 'VERSION']
150
+ .filter((name) => tree.has(name))
151
+ }
152
+
153
+ function versionFromGitFiles(sha, files, cwd) {
154
+ const contents = (path) => git(['show', `${sha}:${path}`], cwd)
155
+ if (files.includes('package.json')) {
156
+ const parsed = parseSemver(JSON.parse(contents('package.json')).version)
157
+ if (parsed) return formatVersion(parsed)
158
+ }
159
+ if (files.includes('pyproject.toml')) {
160
+ const match = contents('pyproject.toml').match(/^version\s*=\s*"([^"]+)"/m)
161
+ const parsed = match && parseSemver(match[1])
162
+ if (parsed) return formatVersion(parsed)
163
+ }
164
+ if (files.includes('VERSION')) {
165
+ const parsed = parseSemver(contents('VERSION'))
166
+ if (parsed) return formatVersion(parsed)
167
+ }
168
+ return null
169
+ }
170
+
171
+ function parseLog(stdout) {
172
+ return stdout
173
+ .split('\u001e')
174
+ .map((chunk) => chunk.trim())
175
+ .filter(Boolean)
176
+ .map((chunk) => {
177
+ const [subject, ...rest] = chunk.split('\n')
178
+ return classifySubject(subject, rest.join('\n'))
179
+ })
180
+ }
181
+
182
+ export function planRelease({ sha, trunk = 'main', strict = false, cwd = process.cwd(), now = new Date() } = {}) {
183
+ if (!sha) {
184
+ const err = new Error('usage: plan.mjs --sha <sha> [--trunk main] [--json] [--strict]')
185
+ err.exitCode = 2
186
+ throw err
187
+ }
188
+ git(['rev-parse', '--verify', sha], cwd)
189
+ const tags = git(['tag', '--merged', sha, '--list', 'v*'], cwd)
190
+ .split('\n')
191
+ .map((line) => line.trim())
192
+ .filter((tag) => parseSemver(tag))
193
+ const baseline = latestReleaseTag(tags)
194
+ const range = baseline ? `${baseline.tag}..${sha}` : sha
195
+ const logArgs = baseline
196
+ ? ['log', range, '--first-parent', '--format=%s%n%b%x1e']
197
+ : ['log', sha, '--first-parent', '-n', '50', '--format=%s%n%b%x1e']
198
+ const changes = parseLog(git(logArgs, cwd))
199
+ const files = versionFilesAt(sha, cwd)
200
+ const fileVersion = versionFromGitFiles(sha, files, cwd)
201
+ const current = parseSemver(fileVersion) || baseline?.version || { major: 0, minor: 0, patch: 0 }
202
+ const { bump, reason } = bumpFor(changes, current, { strict })
203
+ const next = nextVersion(current, bump)
204
+ const version = formatVersion(next)
205
+ const date = now.toISOString().slice(0, 10)
206
+ return {
207
+ trunk,
208
+ sha,
209
+ baselineTag: baseline?.tag || null,
210
+ currentVersion: formatVersion(current),
211
+ bump,
212
+ reason,
213
+ nextVersion: version,
214
+ tag: `v${version}`,
215
+ versionFiles: files,
216
+ changes,
217
+ date,
218
+ changelogMarkdown: renderChangelog({ version, date, changes }),
219
+ }
220
+ }
221
+
222
+ function parseArgs(argv) {
223
+ const opts = { json: false, strict: false, trunk: 'main', sha: null }
224
+ for (let i = 0; i < argv.length; i += 1) {
225
+ const arg = argv[i]
226
+ if (arg === '--json') opts.json = true
227
+ else if (arg === '--strict') opts.strict = true
228
+ else if (arg === '--sha') opts.sha = argv[++i]
229
+ else if (arg === '--trunk') opts.trunk = argv[++i]
230
+ else if (arg === '--help' || arg === '-h') opts.help = true
231
+ else {
232
+ const err = new Error(`unknown argument: ${arg}`)
233
+ err.exitCode = 2
234
+ throw err
235
+ }
236
+ }
237
+ if (opts.help) return opts
238
+ if (!opts.sha || !opts.trunk) {
239
+ const err = new Error('usage: plan.mjs --sha <sha> [--trunk main] [--json] [--strict]')
240
+ err.exitCode = 2
241
+ throw err
242
+ }
243
+ return opts
244
+ }
245
+
246
+ export function main(argv = process.argv.slice(2)) {
247
+ const opts = parseArgs(argv)
248
+ if (opts.help) {
249
+ process.stdout.write('plan.mjs --sha <sha> [--trunk main] [--json] [--strict]\n')
250
+ return 0
251
+ }
252
+ const result = planRelease(opts)
253
+ process.stdout.write(opts.json ? `${JSON.stringify(result, null, 2)}\n` : `${result.tag}\n${result.changelogMarkdown}`)
254
+ return 0
255
+ }
256
+
257
+ if (isMainModule(import.meta.url)) {
258
+ try {
259
+ process.exitCode = main()
260
+ } catch (error) {
261
+ process.stderr.write(`${error.message}\n`)
262
+ process.exitCode = error.exitCode || 1
263
+ }
264
+ }
@@ -22,7 +22,7 @@ Use these skills by name when the Zod change is part of a larger stack-specific
22
22
  - `web-design-guidelines` for UI review after validation-driven form changes
23
23
  - `vercel-react-best-practices` for React/Next.js performance-sensitive form patterns
24
24
  - `app-debug` for runtime failures caused by validation or payload mismatches
25
- - `airflow-dag-develop` only when Zod-related config or payload contracts affect DAG tooling
25
+ - `airflow` only when Zod-related config or payload contracts affect DAG tooling
26
26
 
27
27
  ---
28
28
 
@@ -1,111 +0,0 @@
1
- ---
2
- name: airflow-dag-develop
3
- description: Airflow DAG architecture and conventions. Read when designing, creating, or debugging DAGs — covers CLI validation, environment-based scheduling, and pytest integrity tests.
4
- user-invocable: true
5
- ---
6
-
7
- # Airflow DAG Development Patterns
8
-
9
- ## 1. Quick DAG Validation with Airflow CLI
10
-
11
- Use these commands for fast feedback during development:
12
-
13
- ```bash
14
- # List import errors (fastest check - catches syntax and import issues)
15
- airflow dags list-import-errors
16
-
17
- # Full lint check (comprehensive - catches best practice violations)
18
- airflow dags lint
19
- ```
20
-
21
- **When to use:**
22
- - `list-import-errors`: During active development for quick syntax/import validation
23
- - `lint`: Before commits to catch style and best practice issues
24
-
25
- ## 2. Environment-Based Schedule Override
26
-
27
- Disable schedules in local development to prevent accidental runs:
28
-
29
- ```python
30
- import os
31
- from datetime import datetime
32
- from airflow import DAG
33
-
34
- def get_schedule(production_schedule: str) -> str | None:
35
- """Return None for local dev, actual schedule for deployed environments.
36
-
37
- Set AIRFLOW_ENV=local in .env for local development.
38
- Cloud Run deployments set AIRFLOW_ENV=dev|staging|prod.
39
- """
40
- env = os.getenv("AIRFLOW_ENV", "local")
41
- if env == "local":
42
- return None
43
- return production_schedule
44
- ```
45
-
46
- **Usage in DAG:**
47
- ```python
48
- with DAG(
49
- dag_id="my_dag",
50
- schedule=get_schedule("0 4 * * *"), # Daily 4 AM in prod, None locally
51
- start_date=datetime(2024, 1, 1),
52
- catchup=False,
53
- ) as dag:
54
- ...
55
- ```
56
-
57
- **Environment setup:**
58
- - **Local:** Add `AIRFLOW_ENV=local` to `.env`
59
- - **Cloud Run:** Set via cloudbuild.yaml substitutions (`_AIRFLOW_ENV: dev|staging|prod`)
60
-
61
- ## 3. Pytest DAG Integrity Tests
62
-
63
- Standard pytest patterns for validating DAG quality:
64
-
65
- ```python
66
- # tests/test_dag_integrity.py
67
- import pytest
68
- from airflow.models import DagBag
69
-
70
- @pytest.fixture(scope="session")
71
- def dagbag():
72
- return DagBag(dag_folder="dags/", include_examples=False)
73
-
74
- def test_no_import_errors(dagbag):
75
- """Verify all DAGs load without import errors."""
76
- assert not dagbag.import_errors, f"DAG import errors: {dagbag.import_errors}"
77
-
78
- def test_dags_have_tags(dagbag):
79
- """Verify all DAGs have at least one tag for organization."""
80
- for dag_id, dag in dagbag.dags.items():
81
- assert dag.tags, f"DAG {dag_id} has no tags"
82
-
83
- def test_dags_have_owner(dagbag):
84
- """Verify all DAGs have an owner in default_args."""
85
- for dag_id, dag in dagbag.dags.items():
86
- assert dag.default_args.get("owner"), f"DAG {dag_id} missing owner"
87
-
88
- def test_no_cycles(dagbag):
89
- """Verify DAGs don't have circular dependencies."""
90
- for dag_id, dag in dagbag.dags.items():
91
- # DagBag already validates this, but explicit test for clarity
92
- assert dag.topo_sort(), f"DAG {dag_id} has cycles"
93
- ```
94
-
95
- **Run tests:**
96
- ```bash
97
- # Run all DAG tests
98
- pytest tests/test_dag_integrity.py -v
99
-
100
- # Run specific test
101
- pytest tests/test_dag_integrity.py::test_no_import_errors -v
102
- ```
103
-
104
- ## Quick Reference
105
-
106
- | Task | Command |
107
- |------|---------|
108
- | Check import errors | `airflow dags list-import-errors` |
109
- | Lint DAGs | `airflow dags lint` |
110
- | Run integrity tests | `pytest tests/test_dag_integrity.py -v` |
111
- | Trigger manual run | `airflow dags trigger <dag_id>` |