@1aboveio/skills 1.0.14 → 1.0.16
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/.codex-plugin/plugin.json +1 -1
- package/.codex-plugin/resources.json +164 -160
- package/package.json +4 -4
- package/skills/app-dev/airflow/SKILL.md +1 -1
- package/skills/app-dev/android-development/SKILL.md +30 -2
- package/skills/app-dev/android-development/references/diagnosis.md +55 -0
- package/skills/app-dev/android-development/references/feature-work.md +15 -0
- package/skills/app-dev/android-development/references/init.md +57 -6
- package/skills/app-dev/android-development/references/setup.md +8 -2
- package/skills/app-dev/android-development/references/ui-journeys.md +58 -0
- package/skills/app-dev/android-development/scripts/device-run.sh +93 -2
- package/skills/app-dev/android-development/scripts/failure_capture.py +331 -0
- package/skills/app-dev/android-development/scripts/init.mjs +234 -17
- package/skills/app-dev/android-development/scripts/maestro_failure.py +401 -0
- package/skills/app-dev/android-development/scripts/test_failure_capture.py +378 -0
- package/skills/app-dev/android-development/scripts/test_maestro_failure.py +462 -0
- package/skills/app-dev/app-debug/SKILL.md +1 -1
- package/skills/app-dev/better-auth/SKILL.md +1 -1
- package/skills/app-dev/data-catalog/SKILL.md +53 -16
- package/skills/app-dev/data-catalog/scripts/persist-conventions-core.mjs +231 -21
- package/skills/app-dev/data-catalog/scripts/persist-conventions.mjs +12 -1
- package/skills/app-dev/data-quality/SKILL.md +1 -1
- package/skills/app-dev/ios-foundation/SKILL.md +1 -1
- package/skills/app-dev/monorepo/SKILL.md +1 -1
- package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
- package/skills/app-dev/prisma-setup/SKILL.md +1 -1
- package/skills/app-dev/pyspark/SKILL.md +1 -1
- package/skills/app-dev/python-backend/SKILL.md +1 -1
- package/skills/app-dev/shadcn/SKILL.md +1 -1
- package/skills/app-dev/zod-v4/SKILL.md +1 -1
- package/skills/cicd/cloud-build/SKILL.md +1 -1
- package/skills/cicd/cloud-debug/SKILL.md +1 -1
- package/skills/cicd/cloud-deploy/SKILL.md +1 -1
- package/skills/cicd/docker/SKILL.md +1 -1
- package/skills/cicd/github-actions/SKILL.md +1 -1
- package/skills/cicd/github-flow/SKILL.md +10 -1
- package/skills/cicd/github-flow/scripts/persist-conventions-core.mjs +231 -21
- package/skills/cicd/github-flow/scripts/persist-conventions.mjs +18 -1
- package/skills/cicd/google-cloud/SKILL.md +1 -1
- package/skills/cicd/mergify/SKILL.md +1 -1
- package/skills/cicd/podman/SKILL.md +1 -1
- package/skills/content/article-review/SKILL.md +1 -1
- package/skills/content/article-to-image/SKILL.md +1 -1
- package/skills/content/flowchart-svg/SKILL.md +1 -1
- package/skills/content/image-gen/SKILL.md +1 -1
- package/skills/content/mermaid/SKILL.md +1 -1
- package/skills/content/wechat-article-writer/SKILL.md +1 -1
- package/skills/content/wechat-publisher/SKILL.md +1 -1
- package/skills/content/wechat-search/SKILL.md +1 -1
- package/skills/content/xai-search/SKILL.md +1 -1
- package/skills/core/distill-writing/SKILL.md +7 -1
- package/skills/core/doc-to-md/SKILL.md +1 -1
- package/skills/core/editorial-review/SKILL.md +1 -1
- package/skills/core/house-style/SKILL.md +1 -1
- package/skills/core/house-style/references/persistence.md +8 -3
- package/skills/core/house-style/scripts/init-guidance.mjs +1 -1
- package/skills/core/plain-language/SKILL.md +1 -1
- package/skills/core/polish-cn/SKILL.md +1 -1
- package/skills/core/reg-parser/SKILL.md +1 -1
- package/skills/core/terminology/SKILL.md +1 -1
- package/skills/core/terminology/references/init.md +11 -0
- package/skills/core/write-doc/SKILL.md +1 -1
- package/skills/core/write-report/SKILL.md +1 -1
- package/skills/engineering/better-review/SKILL.md +1 -1
- package/skills/engineering/e2e-test/SKILL.md +1 -1
- package/skills/engineering/engineering/SKILL.md +19 -4
- package/skills/engineering/engineering/references/init-scope.md +5 -2
- package/skills/engineering/engineering/references/scope-management.md +4 -2
- package/skills/engineering/engineering/scripts/init.mjs +34 -9
- package/skills/engineering/engineering/scripts/persist-conventions-core.mjs +231 -21
- package/skills/engineering/engineering/scripts/persist-conventions.mjs +48 -21
- package/skills/engineering/engineering-runtime/SKILL.md +1 -1
- package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
- package/skills/engineering/ensure-coverage/SKILL.md +1 -1
- package/skills/engineering/golden-path/SKILL.md +1 -1
- package/skills/engineering/harness-runtime/SKILL.md +4 -3
- package/skills/engineering/harness-runtime/agent-catalog.mjs +928 -0
- package/skills/engineering/harness-runtime/contracts.md +2 -0
- package/skills/engineering/harness-runtime/references/model-catalog.md +7 -1
- package/skills/engineering/resolve-issues/SKILL.md +9 -6
- package/skills/engineering/resolve-issues/references/delivery-progress.md +2 -0
- package/skills/engineering/resolve-issues/references/expire.md +6 -3
- package/skills/engineering/resolve-issues/references/implementation.md +6 -1
- package/skills/engineering/resolve-issues/references/preflight.md +26 -17
- package/skills/engineering/resolve-issues/references/review.md +39 -4
- package/skills/engineering/resolve-issues/references/shared-modules.md +88 -20
- package/skills/engineering/resolve-issues/references/task-plan.md +35 -1
- package/skills/engineering/resolve-issues/scripts/preflight-models.mjs +382 -592
- package/skills/engineering/resolve-release/SKILL.md +1 -1
- package/skills/engineering/rush-issues/SKILL.md +4 -4
- package/skills/engineering/rush-release/SKILL.md +1 -1
- package/skills/engineering/test-quality/SKILL.md +3 -1
- package/skills/engineering/test-quality/scripts/persist-conventions-core.mjs +231 -21
- package/skills/engineering/test-quality/scripts/persist-conventions.mjs +13 -1
- package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
- package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
- package/skills/risk-compliance/contract-review/SKILL.md +1 -1
- package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
- package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
- package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
- package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
- package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
- package/skills/engineering/resolve-issues/scripts/discover-models.mjs +0 -9
- package/skills/engineering/resolve-issues/scripts/model-catalog.mjs +0 -9
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
// A section sits between `conventions:<id>` markers, so a re-run replaces only
|
|
14
14
|
// that block and keeps everything else, including other skills' sections.
|
|
15
15
|
// Nothing is written without --write: the user triggers the write, not an agent.
|
|
16
|
+
//
|
|
17
|
+
// The guide pointer and a skill's task-entry loader are independent. A link to
|
|
18
|
+
// CONTRIBUTING.md says where the policy lives; it does not load the skill that applies
|
|
19
|
+
// it. So a repo whose AGENTS.md (or regular CLAUDE.md) already names the guide still
|
|
20
|
+
// gets the loader, and a repo that already loads the skill keeps its own rule.
|
|
21
|
+
// engineering's init.mjs reuses `withTaskEntries` for its own two loaders.
|
|
16
22
|
|
|
17
23
|
import fs from 'node:fs'
|
|
18
24
|
import path from 'node:path'
|
|
@@ -20,6 +26,8 @@ import path from 'node:path'
|
|
|
20
26
|
export const CONTRIBUTING_FILE = 'CONTRIBUTING.md'
|
|
21
27
|
const MENTIONS_CONTRIBUTING = /(?:^|[^\w-])CONTRIBUTING\.md/
|
|
22
28
|
|
|
29
|
+
const TASK_ENTRY = 'task-entry'
|
|
30
|
+
|
|
23
31
|
export const AGENTS_SECTION = [
|
|
24
32
|
'## Contributing',
|
|
25
33
|
'',
|
|
@@ -69,26 +77,218 @@ export function renderClaude(current) {
|
|
|
69
77
|
return `${current.replace(/\s*$/, '')}\n\n${AGENTS_SECTION}`
|
|
70
78
|
}
|
|
71
79
|
|
|
72
|
-
export function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
export function taskEntryMarkers(id) {
|
|
81
|
+
return [`<!-- conventions:${id}-${TASK_ENTRY}:start -->`, `<!-- conventions:${id}-${TASK_ENTRY}:end -->`]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Pure: the managed loader block. A short rule in an entrypoint; the conventions it
|
|
85
|
+
// points at stay in CONTRIBUTING.md.
|
|
86
|
+
export function taskEntryBlock({ id, title, body }) {
|
|
87
|
+
const [start, end] = taskEntryMarkers(id)
|
|
88
|
+
const recovery = body.includes('After context loss') ? '' : '\nAfter context loss, reload this skill\'s rules before continuing.'
|
|
89
|
+
return `${start}\n## ${title}\n\n${body.trim()}${recovery}\n${end}\n`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
93
|
+
function withoutExamples(text) {
|
|
94
|
+
let fence = null
|
|
95
|
+
let comment = false
|
|
96
|
+
const active = text.split('\n').map(line => {
|
|
97
|
+
if (fence) {
|
|
98
|
+
const match = /^ {0,3}(`{3,}|~{3,})/.exec(line)
|
|
99
|
+
if (match && match[1][0] === fence[0] && match[1].length >= fence.length
|
|
100
|
+
&& line.slice(match[0].length).trim() === '') fence = null
|
|
101
|
+
return ''
|
|
102
|
+
}
|
|
103
|
+
let masked = ''
|
|
104
|
+
let cursor = 0
|
|
105
|
+
while (cursor < line.length) {
|
|
106
|
+
const marker = line.indexOf(comment ? '-->' : '<!--', cursor)
|
|
107
|
+
const end = marker < 0 ? line.length : marker
|
|
108
|
+
masked += comment ? ' '.repeat(end - cursor) : line.slice(cursor, end)
|
|
109
|
+
if (marker < 0) break
|
|
110
|
+
const length = comment ? 3 : 4
|
|
111
|
+
masked += ' '.repeat(length)
|
|
112
|
+
cursor = marker + length
|
|
113
|
+
comment = !comment
|
|
114
|
+
}
|
|
115
|
+
const match = /^ {0,3}(`{3,}|~{3,})/.exec(masked)
|
|
116
|
+
if (match) { fence = match[1]; return '' }
|
|
117
|
+
return /^(?: {4}|\t|\s*>)/.test(line) ? '' : line
|
|
118
|
+
}).join('\n')
|
|
119
|
+
if (fence) throw new Error('unclosed Markdown code fence; repair guidance before setup')
|
|
120
|
+
if (comment) {
|
|
121
|
+
throw new Error('unclosed HTML comment; repair guidance before setup')
|
|
122
|
+
}
|
|
123
|
+
return active
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Keep code-form skill names, but not inline examples containing whole instructions.
|
|
127
|
+
const visible = (text) => withoutExamples(text).replace(/<!--[\s\S]*?-->/g, '')
|
|
128
|
+
.replace(/(`+)([^`]*?)\1/g, (span, _ticks, content) => /\s/.test(content) ? '' : span)
|
|
129
|
+
|
|
130
|
+
function hasHeading(text, title) {
|
|
131
|
+
return new RegExp(`^#{1,6}\\s+${escape(title)}\\s*$`, 'm').test(visible(text))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// A hand-written equivalent ("read and apply the `test-quality` skill") already loads
|
|
135
|
+
// the skill, so the managed block would repeat it. A guide link never matches.
|
|
136
|
+
function loadsSkill(text, skill, contexts = []) {
|
|
137
|
+
const name = escape(skill)
|
|
138
|
+
const instruction = new RegExp(`\\b(?:read|load|apply|use|follow)\\s+(?:and\\s+(?:apply|read)\\s+)?(?:(?:the|installed)\\s+){0,2}(?:\`${name}\`|${name}\\s+skills?\\b)`, 'i')
|
|
139
|
+
return visible(text).split(/\n\s*\n/).some(paragraph =>
|
|
140
|
+
instruction.test(paragraph)
|
|
141
|
+
&& (!contexts.length || contexts.every(word => new RegExp(`\\b${escape(word)}\\b`, 'i').test(paragraph))))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Pure: `{ text, conflicts }` — each loader's block added once, where it is missing.
|
|
145
|
+
// An equivalent existing rule is preserved; malformed markers and an unmanaged section
|
|
146
|
+
// under the same heading are reported instead of written over.
|
|
147
|
+
export function withTaskEntries(current, loaders = []) {
|
|
148
|
+
let text = current
|
|
149
|
+
const conflicts = []
|
|
150
|
+
for (const loader of loaders) {
|
|
151
|
+
const body = visible(text)
|
|
152
|
+
const denied = new RegExp(`\\b(?:do not|don't|never|must not)\\s+(?:(?:automatically|ever)\\s+)?(?:read|load|apply|use|follow)\\b[^.\\n]{0,120}\\b${escape(loader.skill)}\\b`, 'i')
|
|
153
|
+
if (denied.test(body)) {
|
|
154
|
+
conflicts.push(`an existing rule forbids loading "${loader.skill}"; reconcile it before setup`)
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
const restricted = new RegExp(`\\b${escape(loader.skill)}\\b[^.\\n]{0,100}\\bonly\\s+(?:when|if)\\s+(?:explicitly|the user)`, 'i')
|
|
158
|
+
if (restricted.test(body)) {
|
|
159
|
+
conflicts.push(`an existing explicit-only rule restricts "${loader.skill}"; reconcile it before setup`)
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
if (loader.contexts?.length && visible(text).split(/\n\s*\n/).some(paragraph =>
|
|
163
|
+
loadsSkill(paragraph, loader.skill) && /\b(?:for|during)\b[^.\n]{0,80}\bonly\s*[,;:]/i.test(paragraph))) {
|
|
164
|
+
conflicts.push(`an existing task-only rule restricts "${loader.skill}"; reconcile it before setup`)
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
const [start, end] = taskEntryMarkers(loader.id)
|
|
168
|
+
const active = withoutExamples(text)
|
|
169
|
+
const starts = active.split(start).length - 1
|
|
170
|
+
const ends = active.split(end).length - 1
|
|
171
|
+
if (starts || ends) {
|
|
172
|
+
// Already written: a re-run leaves the block as it is rather than refreshing it.
|
|
173
|
+
if (starts !== 1 || ends !== 1 || active.indexOf(start) > active.indexOf(end)) {
|
|
174
|
+
conflicts.push(`malformed "${loader.title}" markers; repair them by hand`)
|
|
175
|
+
} else if (!loadsSkill(active.slice(active.indexOf(start), active.indexOf(end)), loader.skill, loader.contexts)) {
|
|
176
|
+
conflicts.push(`the preserved "${loader.title}" block has no applicable skill loader; reconcile it before setup`)
|
|
177
|
+
}
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
if (loadsSkill(text, loader.skill, loader.contexts)) continue
|
|
181
|
+
if (hasHeading(text, loader.title)) {
|
|
182
|
+
conflicts.push(`an existing "${loader.title}" section needs manual reconciliation; no competing block added`)
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
text = `${text.replace(/\s*$/, '')}\n\n${taskEntryBlock(loader)}`
|
|
186
|
+
}
|
|
187
|
+
return { text, conflicts }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function indexEntries(section) {
|
|
191
|
+
const loaders = section.loader ? [section.loader] : []
|
|
192
|
+
return [
|
|
193
|
+
{ name: 'AGENTS.md', render: renderAgents, loaders },
|
|
194
|
+
// A regular CLAUDE.md that already points at AGENTS.md inherits that file's loaders.
|
|
195
|
+
{ name: 'CLAUDE.md', render: renderClaude, loaders, reachesAgents: true },
|
|
78
196
|
]
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function reachesSelectedAgents(text, repoRoot, file) {
|
|
200
|
+
const body = visible(text)
|
|
201
|
+
const expected = path.join(repoRoot, 'AGENTS.md')
|
|
202
|
+
const canonical = name => fs.existsSync(name) ? fs.realpathSync(name) : path.resolve(name)
|
|
203
|
+
const affirmative = body.split('\n').filter(line => !/\b(?:do not|don't|never|must not)\b/i.test(line)).join('\n')
|
|
204
|
+
const references = [...affirmative.matchAll(/\[[^\]]*\]\((?:<([^>]+)>|([^\s)]+))\)/g)]
|
|
205
|
+
.map(match => match[1] ?? match[2])
|
|
206
|
+
for (const match of affirmative.matchAll(/\b(?:read|load|follow|see|consult)\s+`?((?:[^\s`]+\/)?AGENTS\.md)`?/gi)) {
|
|
207
|
+
references.push(match[1])
|
|
208
|
+
}
|
|
209
|
+
return references.some(ref => {
|
|
210
|
+
if (/^[a-z][a-z\d+.-]*:|^\/\//i.test(ref)) return false
|
|
211
|
+
try {
|
|
212
|
+
const target = path.resolve(path.dirname(file), decodeURIComponent(ref.split(/[?#]/)[0]))
|
|
213
|
+
return canonical(target) === canonical(expected)
|
|
214
|
+
} catch { return false }
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function apply(repoRoot, entries, write) {
|
|
219
|
+
repoRoot = fs.realpathSync(repoRoot)
|
|
220
|
+
const readText = file => new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(fs.readFileSync(file))
|
|
221
|
+
const plannedContent = new Map()
|
|
222
|
+
const plan = entries.map((entry) => {
|
|
223
|
+
const file = path.join(repoRoot, entry.name)
|
|
224
|
+
if (fs.lstatSync(file, { throwIfNoEntry: false })?.isSymbolicLink()) {
|
|
225
|
+
let conflicts = []
|
|
226
|
+
try {
|
|
227
|
+
const target = fs.realpathSync(file)
|
|
228
|
+
const relative = path.relative(repoRoot, target)
|
|
229
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
230
|
+
conflicts = ['linked entrypoint is outside the selected project; verify its task loader separately']
|
|
231
|
+
} else {
|
|
232
|
+
const current = plannedContent.get(target) ?? readText(file)
|
|
233
|
+
const checked = withTaskEntries(current, entry.loaders)
|
|
234
|
+
conflicts = checked.conflicts
|
|
235
|
+
if (checked.text !== current && !(entry.reachesAgents && reachesSelectedAgents(current, repoRoot, target))) {
|
|
236
|
+
conflicts.push('linked entrypoint lacks the task loader; explicit reconciliation is required')
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
} catch { conflicts = ['linked entrypoint could not be verified; setup is partial'] }
|
|
240
|
+
return { file: entry.name, action: 'unchanged', conflicts }
|
|
241
|
+
}
|
|
242
|
+
const current = fs.existsSync(file) ? readText(file) : null
|
|
243
|
+
const base = entry.render(current)
|
|
244
|
+
let next = base
|
|
245
|
+
let conflicts = []
|
|
246
|
+
if (base !== LINK_TO_AGENTS) {
|
|
247
|
+
const applied = withTaskEntries(base, entry.loaders)
|
|
248
|
+
const inherits = entry.reachesAgents && reachesSelectedAgents(current ?? '', repoRoot, file)
|
|
249
|
+
next = inherits ? base : applied.text
|
|
250
|
+
conflicts = applied.conflicts
|
|
251
|
+
}
|
|
85
252
|
if (next === LINK_TO_AGENTS) {
|
|
86
|
-
|
|
87
|
-
|
|
253
|
+
return { file: entry.name, action: 'link', conflicts, next }
|
|
254
|
+
}
|
|
255
|
+
const action = current == null ? 'create' : next === current ? 'unchanged' : 'update'
|
|
256
|
+
if (current !== null && action !== 'unchanged' && fs.statSync(file).nlink > 1) {
|
|
257
|
+
conflicts.push('hard-linked guidance cannot be changed without exclusive ownership')
|
|
88
258
|
}
|
|
89
|
-
|
|
90
|
-
return { file: name, action }
|
|
259
|
+
plannedContent.set(file, next)
|
|
260
|
+
return { file: entry.name, action, conflicts, next }
|
|
91
261
|
})
|
|
262
|
+
const blocked = plan.some(entry => entry.conflicts.length)
|
|
263
|
+
if (write && !blocked) {
|
|
264
|
+
for (const entry of plan.filter(entry => entry.action !== 'unchanged')) {
|
|
265
|
+
const file = path.join(repoRoot, entry.file)
|
|
266
|
+
fs.accessSync(entry.action === 'create' || entry.action === 'link' ? repoRoot : file, fs.constants.W_OK)
|
|
267
|
+
}
|
|
268
|
+
for (const entry of plan) {
|
|
269
|
+
const file = path.join(repoRoot, entry.file)
|
|
270
|
+
if (entry.next === LINK_TO_AGENTS) fs.symlinkSync('AGENTS.md', file)
|
|
271
|
+
else if (entry.action !== 'unchanged') fs.writeFileSync(file, entry.next)
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return plan.map(({ next: _next, ...entry }) => ({
|
|
275
|
+
...entry, action: blocked && entry.action !== 'unchanged' ? 'blocked' : entry.action,
|
|
276
|
+
}))
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function persist(repoRoot, section, { write = false } = {}) {
|
|
280
|
+
const block = sectionBlock(section)
|
|
281
|
+
const entries = [
|
|
282
|
+
{ name: CONTRIBUTING_FILE, render: (text) => renderConventions(text, section.id, block), loaders: [] },
|
|
283
|
+
...indexEntries(section),
|
|
284
|
+
]
|
|
285
|
+
return apply(repoRoot, entries, write)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// The index half alone: for a section that is already present, only the guide pointer
|
|
289
|
+
// and the task-entry loader are due.
|
|
290
|
+
export function persistIndex(repoRoot, section, { write = false } = {}) {
|
|
291
|
+
return apply(repoRoot, indexEntries(section), write)
|
|
92
292
|
}
|
|
93
293
|
|
|
94
294
|
// `section` = { id, owner, title, body }. Exit 0 on success, 2 on a usage error.
|
|
@@ -107,12 +307,22 @@ export function runCli(argv, section, { cwd = process.cwd(), log = console.log }
|
|
|
107
307
|
log(`persist-conventions: not a directory: ${repoRoot}`)
|
|
108
308
|
return 2
|
|
109
309
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const
|
|
310
|
+
let results
|
|
311
|
+
try { results = persist(repoRoot, section, { write }) }
|
|
312
|
+
catch (error) { log(`persist-conventions: setup refused: ${error.message}`); return 2 }
|
|
313
|
+
const done = { create: 'created', link: 'linked to AGENTS.md', update: 'updated', unchanged: 'unchanged', blocked: 'blocked' }
|
|
314
|
+
const conflicts = []
|
|
315
|
+
for (const r of results) {
|
|
316
|
+
for (const conflict of r.conflicts ?? []) conflicts.push(`${r.file}: ${conflict}`)
|
|
317
|
+
log(` ${r.file}: ${write || r.action === 'unchanged' ? done[r.action] : `would ${r.action}`}`)
|
|
318
|
+
}
|
|
319
|
+
const changed = results.filter((r) => !['unchanged', 'blocked'].includes(r.action)).length
|
|
114
320
|
log(write
|
|
115
321
|
? `persist-conventions: ${section.title}: ${changed} file(s) written, ${results.length - changed} unchanged`
|
|
116
322
|
: `persist-conventions: ${section.title}: preview only, ${changed} file(s) would change; re-run with --write to write`)
|
|
117
|
-
|
|
323
|
+
if (conflicts.length) {
|
|
324
|
+
for (const conflict of conflicts) log(` conflict: ${conflict}`)
|
|
325
|
+
log(`persist-conventions: ${section.title}: ${conflicts.length} conflict(s); no files written; reconcile before setup`)
|
|
326
|
+
}
|
|
327
|
+
return conflicts.length ? 2 : 0
|
|
118
328
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// persist-conventions — the data catalog conventions as the
|
|
2
2
|
// `## Data catalog conventions` section of a repo's CONTRIBUTING.md (../SKILL.md,
|
|
3
|
-
// First use in a repo). Previews unless --write.
|
|
3
|
+
// First use in a repo). Previews unless --write. --write also adds the skill's
|
|
4
|
+
// task-entry loader to AGENTS.md (and to a regular CLAUDE.md that does not reach it).
|
|
4
5
|
//
|
|
5
6
|
// node persist-conventions.mjs [--repo-root <dir>] [--write]
|
|
6
7
|
import fs from 'node:fs'
|
|
@@ -12,6 +13,16 @@ const SECTION = {
|
|
|
12
13
|
owner: 'data-catalog',
|
|
13
14
|
title: 'Data catalog conventions',
|
|
14
15
|
body: fs.readFileSync(fileURLToPath(new URL('../references/data-catalog-conventions.md', import.meta.url)), 'utf8'),
|
|
16
|
+
loader: {
|
|
17
|
+
id: 'data-catalog',
|
|
18
|
+
skill: 'data-catalog',
|
|
19
|
+
title: 'Data catalog task entry',
|
|
20
|
+
body: [
|
|
21
|
+
"When adding, renaming, removing, or changing the meaning of a table's output",
|
|
22
|
+
'column, or reviewing a table script, read and apply the installed `data-catalog`',
|
|
23
|
+
"skill. The repository's catalog rules stay in its contributor guide.",
|
|
24
|
+
].join('\n'),
|
|
25
|
+
},
|
|
15
26
|
}
|
|
16
27
|
|
|
17
28
|
process.exitCode = runCli(process.argv.slice(2), SECTION)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: data-quality
|
|
3
3
|
description: "Check a published warehouse table's data against its data-catalog definitions with read-only SELECTs: columns never filled or stuck at one value, counts below zero, ratios outside 0–1, declared values that never appear and undeclared values that do. Then classify each finding as a wrong definition, wrong code, or wrong data. Use when asked whether a table's data is right, to profile or sanity-check a dwd/ads table, to find dead or constant columns, to check a column's values against its documented list, after a pipeline change ships, or when a data-catalog definition and the data might disagree. NOT for validating a Spark change before it ships (pyspark canary and reconciliation), writing definitions (data-catalog), or monitoring dashboards."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Data quality
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ios-foundation
|
|
3
3
|
description: "Set up and prove an Expo / React Native native iOS workspace from an assigned or fresh isolated workspace to a manual Login smoke on an owned iOS Simulator: isolated workspace and private caches, Xcode/runtime/CocoaPods preflight, frozen install with `pod install --deployment`, platform-scoped prebuild, an ad-hoc-signed Debug simulator build, a private simulator by exact UDID, Metro on a free port, a disposable PostgreSQL backend with owned cleanup, and the evidence record (setup proof only; feature work reports the smoke result). Use for 'set up iOS', 'build the iOS app', 'run the app on the iOS simulator', 'add iOS to the React Native app', 'prove the iOS build', or when a pod install, xcodebuild, simulator boot, Metro connection or Keychain session fails. NOT for Android (android-development), signing, TestFlight or App Store release, or general workflow YAML (github-actions)."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# iOS foundation
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: monorepo
|
|
3
3
|
description: Turborepo architecture for shared packages, pnpm workspaces, DAL boundaries, and pipelines.
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Turborepo Monorepo Architecture
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nextjs-fullstack
|
|
3
3
|
description: "Build or debug Next.js App Router features with React 19 — pages/layouts/route handlers, Server/Client Components, Server Actions, forms, middleware, DAL, strict TypeScript, Zod — plus our conventions: nuqs URL state, SWR cache, Skeleton/Progress + Sonner feedback, drag-and-drop upload, the portal app shell, list pages as filter/sort/paginate tables. shadcn component mechanics: see the shadcn skill."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Next.js 16 Fullstack Development
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: prisma-setup
|
|
3
3
|
description: Use when adding or debugging Prisma with PostgreSQL, Next.js, or Turborepo, including schema.prisma, prisma.config.ts, client generation, migrations, seeds, relations, adapters, and DAL/database-layer setup. Triggers on phrases like "Prisma schema", "prisma migrate", "schema.prisma", "seed the database", "PrismaClient error", "relation issue", or "database layer".
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Prisma v7 Setup Guide
|
|
@@ -12,7 +12,7 @@ description: >
|
|
|
12
12
|
Connect, spark-submit, Hudi commits, backfills, validate-only canaries,
|
|
13
13
|
cluster reconciliation, or Spark event-log profiling — including warehouse
|
|
14
14
|
job timeouts, empty Hudi writes, or EMR/Dataproc performance regressions.
|
|
15
|
-
release-version: 0.
|
|
15
|
+
release-version: 0.48.0
|
|
16
16
|
---
|
|
17
17
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
18
18
|
# PySpark
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: python-backend
|
|
3
3
|
description: "Python backend architecture for FastAPI, Tortoise ORM, Pydantic Settings, loguru, and uv. Also: uv.lock installs in CI/Docker via a proxy or mirror (`uv sync --frozen` bypasses it), slow uv installs on runners, relocking."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Python Backend Conventions
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: shadcn
|
|
3
3
|
description: "Add or debug shadcn/ui in a React/Next.js project: components.json, shadcn init/add, presets, registries, Tailwind styling, base/radix/aria composition, component fixes, and list-index pages — where the default is the DiceUI data table with filter/sort/pagination in the URL via nuqs, not a bare shadcn Table."
|
|
4
4
|
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
|
|
5
|
-
release-version: 0.
|
|
5
|
+
release-version: 0.48.0
|
|
6
6
|
---
|
|
7
7
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
8
8
|
# shadcn/ui
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: zod-v4
|
|
3
3
|
description: Zod v4 schema conventions and v3 migration for errors, validators, objects, enums, records, and async parsing.
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Zod v3 to v4 Migration
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cloud-build
|
|
3
3
|
description: "Build or debug Cloud Build pipelines for Cloud Run: cloudbuild.yaml, triggers, Artifact Registry, builds, migrations, deploys, secrets, VPC, notifications. Use cloud-deploy for staged promotion. NOT for GitHub Actions PR checks (github-actions)."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Cloud Build Direct Deploy to Cloud Run
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: cloud-debug
|
|
3
3
|
description: "Debug Cloud Build or Deploy failures: builds, deployments, health checks, and pipeline errors. Inputs: service, build ID, region. NOT for GitHub Actions runs (github-actions) or runtime errors in a running service (app-debug)."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Cloud Build & Cloud Deploy Debug Skill
|
|
@@ -5,7 +5,7 @@ description: >
|
|
|
5
5
|
CI/CD pipelines with staged promotion, verification gates, or GitFlow branching.
|
|
6
6
|
Also use when debugging Cloud Deploy pipeline failures. NOT for simple single-container
|
|
7
7
|
deploys — use cloud-build instead.
|
|
8
|
-
release-version: 0.
|
|
8
|
+
release-version: 0.48.0
|
|
9
9
|
---
|
|
10
10
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
11
11
|
# Cloud Deploy + Skaffold + Kustomize for Cloud Run
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docker
|
|
3
3
|
description: Docker/Podman container architecture and build patterns. Read when designing or reviewing containerization, Dockerfile structure, multi-stage builds, PyPI mirror support, or docker-compose setup for Python/uv projects, and when a Dockerfile's dependency install misses the layer cache on every build (build args such as a commit SHA or version declared too early) or an image build is slow in CI.
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# Docker Build Patterns
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: github-actions
|
|
3
3
|
description: "Author, review, audit, profile, optimize, or debug GitHub Actions workflows (.github/workflows/*.yml): run-history audits, job and step timing, resource bottlenecks, before/after comparisons, conditional CI by changed path, the one required gate check, PR and merge-queue lanes, sharding, workers, caching, self-hosted runners, timeouts, concurrency, permissions and action pinning, post-deploy verification, npm publishing. Use for 'set up CI', 'CI is slow', 'profile CI', 'optimize our pipeline', 'audit CI for redundant or wasted jobs', 'why is this check stuck expected or never started', 'harden this workflow', or 'browser not found on the runner'. NOT for pushing, opening, or merging a PR, or writing CI conventions into CONTRIBUTING.md (github-flow), queue rules or a check unmet inside Mergify (mergify), Playwright config and specs (e2e-test), cloudbuild.yaml or Cloud Run deploys (cloud-build), or which gates a repo must prove (ensure-coverage)."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# GitHub Actions
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: github-flow
|
|
3
3
|
description: "Deliver a change after the commit: push, open a PR, wait for checks, and merge to main through the repo's flow (direct push, pull request, or merge queue). Use whenever you commit or finish a change, when CONTRIBUTING.md has a Delivery section, and for asks like 'commit this', 'push it', 'open a PR', 'ship it', 'land it', 'get it merged'. Also: declare a repo's delivery flow, or write the CI conventions and Delivery section into CONTRIBUTING.md on request. NOT for writing workflow YAML (github-actions), Mergify config or a stuck queued PR (mergify), cutting a release (resolve-release), or PRs that resolve-issues or rush-issues deliver."
|
|
4
|
-
release-version: 0.
|
|
4
|
+
release-version: 0.48.0
|
|
5
5
|
---
|
|
6
6
|
**Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
|
|
7
7
|
# GitHub Flow
|
|
@@ -114,6 +114,15 @@ node "$SKILL_DIR/scripts/persist-conventions.mjs" --flow <flow> ... --write
|
|
|
114
114
|
- Adds a pointer to `AGENTS.md` unless it names `CONTRIBUTING.md`; creates missing files.
|
|
115
115
|
- Creates a missing `CLAUDE.md` as a symlink to `AGENTS.md`. Adds the pointer to a regular
|
|
116
116
|
`CLAUDE.md` that names neither file. Leaves a symlinked `CLAUDE.md` alone.
|
|
117
|
+
- Also adds one `## Delivery task entry` rule to `AGENTS.md`, between
|
|
118
|
+
`conventions:delivery-task-entry` markers, whichever section it writes: after a commit, or when
|
|
119
|
+
asked to push, open a PR, or merge, read and apply this skill. The rule is scoped to that task
|
|
120
|
+
and grants nothing: the user's request and the repo's declared policy still decide how far to go,
|
|
121
|
+
and `resolve-issues`/`rush-issues` deliver their own PRs. It is checked separately from the guide
|
|
122
|
+
pointer, so an `AGENTS.md` or regular `CLAUDE.md` that already links `CONTRIBUTING.md` still gets
|
|
123
|
+
it. An entrypoint that already loads the skill, a `CLAUDE.md` that points at `AGENTS.md`, or an
|
|
124
|
+
unmanaged section with that heading is preserved and reported rather than duplicated; a re-run
|
|
125
|
+
changes nothing.
|
|
117
126
|
- Report each file's result line. Committing the files is a change of its own: deliver it with this skill.
|
|
118
127
|
|
|
119
128
|
## Related skills
|