@1aboveio/skills 1.0.14 → 1.0.15

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 (86) hide show
  1. package/.codex-plugin/plugin.json +1 -1
  2. package/.codex-plugin/resources.json +147 -147
  3. package/package.json +4 -4
  4. package/skills/app-dev/airflow/SKILL.md +1 -1
  5. package/skills/app-dev/android-development/SKILL.md +3 -2
  6. package/skills/app-dev/android-development/references/diagnosis.md +5 -0
  7. package/skills/app-dev/android-development/references/feature-work.md +15 -0
  8. package/skills/app-dev/android-development/references/init.md +26 -4
  9. package/skills/app-dev/android-development/scripts/init.mjs +105 -8
  10. package/skills/app-dev/app-debug/SKILL.md +1 -1
  11. package/skills/app-dev/better-auth/SKILL.md +1 -1
  12. package/skills/app-dev/data-catalog/SKILL.md +53 -16
  13. package/skills/app-dev/data-catalog/scripts/persist-conventions-core.mjs +231 -21
  14. package/skills/app-dev/data-catalog/scripts/persist-conventions.mjs +12 -1
  15. package/skills/app-dev/data-quality/SKILL.md +1 -1
  16. package/skills/app-dev/ios-foundation/SKILL.md +1 -1
  17. package/skills/app-dev/monorepo/SKILL.md +1 -1
  18. package/skills/app-dev/nextjs-fullstack/SKILL.md +1 -1
  19. package/skills/app-dev/prisma-setup/SKILL.md +1 -1
  20. package/skills/app-dev/pyspark/SKILL.md +1 -1
  21. package/skills/app-dev/python-backend/SKILL.md +1 -1
  22. package/skills/app-dev/shadcn/SKILL.md +1 -1
  23. package/skills/app-dev/zod-v4/SKILL.md +1 -1
  24. package/skills/cicd/cloud-build/SKILL.md +1 -1
  25. package/skills/cicd/cloud-debug/SKILL.md +1 -1
  26. package/skills/cicd/cloud-deploy/SKILL.md +1 -1
  27. package/skills/cicd/docker/SKILL.md +1 -1
  28. package/skills/cicd/github-actions/SKILL.md +1 -1
  29. package/skills/cicd/github-flow/SKILL.md +10 -1
  30. package/skills/cicd/github-flow/scripts/persist-conventions-core.mjs +231 -21
  31. package/skills/cicd/github-flow/scripts/persist-conventions.mjs +18 -1
  32. package/skills/cicd/google-cloud/SKILL.md +1 -1
  33. package/skills/cicd/mergify/SKILL.md +1 -1
  34. package/skills/cicd/podman/SKILL.md +1 -1
  35. package/skills/content/article-review/SKILL.md +1 -1
  36. package/skills/content/article-to-image/SKILL.md +1 -1
  37. package/skills/content/flowchart-svg/SKILL.md +1 -1
  38. package/skills/content/image-gen/SKILL.md +1 -1
  39. package/skills/content/mermaid/SKILL.md +1 -1
  40. package/skills/content/wechat-article-writer/SKILL.md +1 -1
  41. package/skills/content/wechat-publisher/SKILL.md +1 -1
  42. package/skills/content/wechat-search/SKILL.md +1 -1
  43. package/skills/content/xai-search/SKILL.md +1 -1
  44. package/skills/core/distill-writing/SKILL.md +7 -1
  45. package/skills/core/doc-to-md/SKILL.md +1 -1
  46. package/skills/core/editorial-review/SKILL.md +1 -1
  47. package/skills/core/house-style/SKILL.md +1 -1
  48. package/skills/core/house-style/references/persistence.md +8 -3
  49. package/skills/core/house-style/scripts/init-guidance.mjs +1 -1
  50. package/skills/core/plain-language/SKILL.md +1 -1
  51. package/skills/core/polish-cn/SKILL.md +1 -1
  52. package/skills/core/reg-parser/SKILL.md +1 -1
  53. package/skills/core/terminology/SKILL.md +1 -1
  54. package/skills/core/terminology/references/init.md +11 -0
  55. package/skills/core/write-doc/SKILL.md +1 -1
  56. package/skills/core/write-report/SKILL.md +1 -1
  57. package/skills/engineering/better-review/SKILL.md +1 -1
  58. package/skills/engineering/e2e-test/SKILL.md +1 -1
  59. package/skills/engineering/engineering/SKILL.md +19 -4
  60. package/skills/engineering/engineering/references/init-scope.md +5 -2
  61. package/skills/engineering/engineering/references/scope-management.md +4 -2
  62. package/skills/engineering/engineering/scripts/init.mjs +34 -9
  63. package/skills/engineering/engineering/scripts/persist-conventions-core.mjs +231 -21
  64. package/skills/engineering/engineering/scripts/persist-conventions.mjs +48 -21
  65. package/skills/engineering/engineering-runtime/SKILL.md +1 -1
  66. package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
  67. package/skills/engineering/ensure-coverage/SKILL.md +1 -1
  68. package/skills/engineering/golden-path/SKILL.md +1 -1
  69. package/skills/engineering/harness-runtime/SKILL.md +1 -1
  70. package/skills/engineering/resolve-issues/SKILL.md +1 -1
  71. package/skills/engineering/resolve-issues/references/review.md +27 -4
  72. package/skills/engineering/resolve-release/SKILL.md +1 -1
  73. package/skills/engineering/rush-issues/SKILL.md +1 -1
  74. package/skills/engineering/rush-release/SKILL.md +1 -1
  75. package/skills/engineering/test-quality/SKILL.md +3 -1
  76. package/skills/engineering/test-quality/scripts/persist-conventions-core.mjs +231 -21
  77. package/skills/engineering/test-quality/scripts/persist-conventions.mjs +13 -1
  78. package/skills/risk-compliance/compliance-audit/SKILL.md +1 -1
  79. package/skills/risk-compliance/contract-compare/SKILL.md +1 -1
  80. package/skills/risk-compliance/contract-review/SKILL.md +1 -1
  81. package/skills/risk-compliance/doc-authenticity/SKILL.md +1 -1
  82. package/skills/risk-compliance/fraud-detection/SKILL.md +1 -1
  83. package/skills/risk-compliance/payment-analysis/SKILL.md +1 -1
  84. package/skills/risk-compliance/portfolio-payment-analysis/SKILL.md +1 -1
  85. package/skills/risk-compliance/similar-domain/SKILL.md +1 -1
  86. package/skills/risk-compliance/tld-detection/SKILL.md +1 -1
@@ -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 persist(repoRoot, section, { write = false } = {}) {
73
- const block = sectionBlock(section)
74
- const plan = [
75
- [CONTRIBUTING_FILE, (text) => renderConventions(text, section.id, block)],
76
- ['AGENTS.md', renderAgents],
77
- ['CLAUDE.md', renderClaude],
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
- return plan.map(([name, render]) => {
80
- const file = path.join(repoRoot, name)
81
- if (fs.lstatSync(file, { throwIfNoEntry: false })?.isSymbolicLink()) return { file: name, action: 'unchanged' }
82
- const current = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null
83
- const next = render(current)
84
- const action = current == null ? 'create' : next === current ? 'unchanged' : 'update'
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
- if (write) fs.symlinkSync('AGENTS.md', file)
87
- return { file: name, action: 'link' }
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
- if (write && action !== 'unchanged') fs.writeFileSync(file, next)
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
- const results = persist(repoRoot, section, { write })
111
- const done = { create: 'created', link: 'linked to AGENTS.md', update: 'updated', unchanged: 'unchanged' }
112
- for (const r of results) log(` ${r.file}: ${write || r.action === 'unchanged' ? done[r.action] : `would ${r.action}`}`)
113
- const changed = results.filter((r) => r.action !== 'unchanged').length
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
- return 0
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,8 @@
1
1
  // persist-conventions — write the `## CI conventions` and `## Delivery` sections
2
2
  // of a repo's CONTRIBUTING.md (../SKILL.md, Persist to CONTRIBUTING.md). Run only
3
3
  // when the user asks; previews unless --write. Without --flow, writes CI conventions only.
4
+ // Either run also adds the skill's task-entry loader once to AGENTS.md (and to a
5
+ // regular CLAUDE.md that does not reach it).
4
6
  //
5
7
  // node persist-conventions.mjs [--repo-root <dir>] [--write]
6
8
  // [--flow direct|pull-request|merge-queue] [--done merged|committed] [--merge-method squash|merge|rebase]
@@ -10,11 +12,26 @@ import { fileURLToPath } from 'node:url'
10
12
  import { renderDelivery, validateDelivery } from './flow-core.mjs'
11
13
  import { runCli } from './persist-conventions-core.mjs'
12
14
 
15
+ // One loader for both sections: whichever run happens first adds it, the other finds it.
16
+ const LOADER = {
17
+ id: 'delivery',
18
+ skill: 'github-flow',
19
+ title: 'Delivery task entry',
20
+ body: [
21
+ 'After a commit, or when asked to push, open a pull request, or merge until the',
22
+ "change is done, read and apply the installed `github-flow` skill. The user's request",
23
+ "and the repository's declared delivery policy decide how far to go; loading it",
24
+ 'authorizes no push or merge, and `resolve-issues` and `rush-issues` deliver their own',
25
+ 'pull requests.',
26
+ ].join('\n'),
27
+ }
28
+
13
29
  const CI = {
14
30
  id: 'ci',
15
31
  owner: 'github-flow',
16
32
  title: 'CI conventions',
17
33
  body: fs.readFileSync(fileURLToPath(new URL('../references/ci-conventions.md', import.meta.url)), 'utf8'),
34
+ loader: LOADER,
18
35
  }
19
36
 
20
37
  const DELIVERY_FLAGS = { '--flow': 'flow', '--done': 'done', '--merge-method': 'mergeMethod' }
@@ -42,7 +59,7 @@ if (Object.keys(delivery).length) {
42
59
  }
43
60
  let code = deliveryOnly ? 0 : runCli(rest, CI)
44
61
  if (code === 0 && delivery.flow) {
45
- code = runCli(rest, { id: 'delivery', owner: 'github-flow', title: 'Delivery', body: renderDelivery(delivery) })
62
+ code = runCli(rest, { id: 'delivery', owner: 'github-flow', title: 'Delivery', body: renderDelivery(delivery), loader: LOADER })
46
63
  } else if (code === 0) {
47
64
  console.log('persist-conventions: Delivery: skipped; pass --flow to write it')
48
65
  }
@@ -5,7 +5,7 @@ description: >
5
5
  project management. Use when configuring GCP resources, managing service
6
6
  accounts, setting up IAP access, configuring VPC networks, managing secrets,
7
7
  or debugging cloud infrastructure.
8
- release-version: 0.46.0
8
+ release-version: 0.47.0
9
9
  ---
10
10
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
11
11
  # Google Cloud Platform (GCP) Skill
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: mergify
3
3
  description: "Mergify merge queue and stacked PRs (`mergify stack`): config, rules, queue status, delivery. Use whenever a merge queue or stack is involved, including plain asks ('watch #123 till it merges', 'where is #412 in the queue', 'push this stack'): `gh pr view`/`gh pr checks` cannot see a dequeue; use `mergify queue show` and the bundled watcher. Also: a hook saying 'use mergify stack push', a missing Change-Id; authoring or debugging `.mergify.yml`, queue rules, merge protections, batch size, the ruleset making merges queue-only; enqueue policy (`@mergifyio queue` vs `auto_merge_conditions`); a push dequeuing a queued PR; bypass actors; freezing a branch. Failures: a PR stuck, queued but never merging, or merged unqueued; CI green on GitHub but unmet in Mergify; a batch going red or bisecting; a queue check missing rather than failing. Outranks the vendor mergify-* skills. NOT for pushing a PR in a repo without stack hooks (github-flow), workflow YAML (github-actions), or GCP deploy pipelines (cloud-deploy)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Mergify Merge Queue
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: podman
3
3
  description: "Debug, design, or fix Podman: rootless/rootful containers, pods, podman-compose, the socket and systemd services, Docker-compatible API (DOCKER_HOST/CONTAINER_HOST), networking, volumes, SELinux labels, Docker migration, and CI/SSH provisioning of rootless sockets (user lingering, XDG_RUNTIME_DIR). Covers FailedToOpenSocket and systemctl --user DBUS/XDG errors."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Podman Operations
@@ -6,7 +6,7 @@ description: >
6
6
  Use when a user shares an article, pastes article text, sends a URL, or asks to
7
7
  review, rate, critique, evaluate, 评价, 点评, 分析, or 看看 any article, post, or 文章.
8
8
  Default template is 公众号-支付行业.
9
- release-version: 0.46.0
9
+ release-version: 0.47.0
10
10
  ---
11
11
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
12
12
  # 文章评审引擎
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: article-to-image
3
3
  description: "Generate article cover images: WeChat banner/square (公众号封面), Xiaohongshu (小红书封面), LinkedIn banner, general wide. Brainstorms 4 visual directions from the article, then renders each format with correct aspect ratios and title-safe zones. Use when given an article and asked for covers, banners, or 出图."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Article-to-Image
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: flowchart-svg
3
3
  description: Create polished flowcharts directly as SVG and export PNG copies. Use when asked to draw, design, render, or deliver process diagrams, architecture diagrams, consulting workflows, decision flows, swimlanes, or other flowcharts as image files, especially when final outputs must include .svg and .png versions.
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Flowchart SVG
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: image-gen
3
3
  description: Generate an image from a text prompt with OpenAI gpt-image-2 or Google Nano Banana (Gemini), saved to an image file. Use whenever the user asks to generate, create, make, draw, paint, render, or design an image, picture, illustration, photo, artwork, logo, icon, avatar, banner, hero image, thumbnail, poster, wallpaper, sticker, or concept art — including phrasings like "an image of X", "a picture of X", "show me what X would look like", or a request for a visual for a slide, blog post, README, or social post. Also triggers on Chinese phrasings such as 生成图片, 画一张图, 做一张海报, 设计一个 logo. NOT for charts or data visualizations (dataviz), flowcharts or architecture diagrams (flowchart-svg), or editing an existing image file.
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Image Generation
@@ -2,7 +2,7 @@
2
2
  name: mermaid
3
3
  description: Create and edit Mermaid diagrams for architecture, API flows, database schemas, workflows, and data models.
4
4
  user-invocable: true
5
- release-version: 0.46.0
5
+ release-version: 0.47.0
6
6
  ---
7
7
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
8
8
  # Mermaid Diagram Creation
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: wechat-article-writer
3
3
  description: "Write WeChat 公众号 articles in Chinese content styles — 踩坑型 (pitfall-story), 实操型 (how-to), 对比型 (comparison), 观察型 (industry insight) — in the account's house style from the nearest WRITING.md. Use for drafting from scratch, rewriting a draft, adapting source material into a publishable article, or picking the right style. NOT for business or technical documents (write-doc)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # WeChat Article Writer (公众号写作)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: wechat-publisher
3
3
  description: "Publish articles to a WeChat Official Account (公众号) via API: push Markdown to the draft box (or auto-publish with --publish), convert Markdown to WeChat HTML, upload images to the media library. Requires AppID + AppSecret + whitelisted server IP on a 认证订阅号/服务号. Consumes wechat-article-writer's Markdown."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # WeChat Publisher (公众号发布)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: wechat-search
3
3
  description: "Search WeChat 公众号 articles via Sogou Weixin, or read/summarize an article from a mp.weixin.qq.com URL (读/分析/总结) — suggest proactively when such a URL appears. Use for Chinese WeChat content, industry reports, competitor or trending topics. Requires Chrome Browser Relay. NOT general web search (web_search)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # WeChat Article Search & Reader (搜狗微信搜索)
@@ -9,7 +9,7 @@ metadata:
9
9
  type: secret
10
10
  required: true
11
11
  description: API key for direct xAI or the explicitly configured Responses gateway
12
- release-version: 0.46.0
12
+ release-version: 0.47.0
13
13
  ---
14
14
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
15
15
  # xAI Search (X/Twitter + Web)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: distill-writing
3
3
  description: "Learn a house writing style from approved sample documents, or save an explicitly stated project document preference, into WRITING.md and writing/templates/. Use for sample-based style learning, house-style setup or refresh, and requests such as 'remember this for future PRDs' or 'make this our report default', including handoffs from write-doc, write-report or plain-language. Preserve manual edits and separate document types. NOT for drafting a document (write-doc) or analytical report (write-report) or saving chat-only preferences (plain-language)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Distill a writing style
@@ -81,6 +81,12 @@ and the patterns supported by the approved documents.
81
81
  guide, and `TERMINOLOGY.md` by the [terminology setup](../terminology/references/init.md). Existing:
82
82
  follow [updating](references/updating.md). Keep at most three short annotated excerpts; sanitize
83
83
  names and numbers in them.
84
+ After explicit project style setup, make the result discoverable through the scoped agent
85
+ entrypoints using [the document loader](../house-style/references/persistence.md#make-the-saved-policy-discoverable).
86
+ That step installs only the missing loader within its preservation and scope rules; it does
87
+ not save extra preferences or rerun initialization. Future drafting uses `write-doc` and
88
+ analytical reports use `write-report`, not this style-learning workflow. A guide link alone
89
+ is not a skill-routing rule. Report unreachable entrypoints or unavailable writer skills.
84
90
  6. **Test transfer.** Pick a topic none of the references cover. Write its first one or two sections
85
91
  with `write-doc` (documents) or `write-report` (analytical reports) using the new files. Compare them with the references: voice, terms, structure,
86
92
  presentation. Fix each rule that did not carry over or that copied a reference fact. Do not save
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: doc-to-md
3
3
  description: "Convert documents (PDF, DOCX, PPTX, XLSX, images) to Markdown so agents can read them. Use as the preprocessing step whenever binary files need to enter agent context — contract review, compliance audit, document analysis, evidence review."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # doc-to-md
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: editorial-review
3
3
  description: "Review a document or generated report for what it says (does the reader get the answer, is the argument sound, is anything unsupported, obvious or repeated), then fix the content. Use to review, tighten or clarify a doc or report, and as the review step of write-doc, write-report and domain report skills. NOT for Chinese wording (polish-cn) or code review (better-review)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Editorial review
@@ -3,7 +3,7 @@ name: house-style
3
3
  description: "Shared reference the writing skills read by path: WRITING.md lookup and precedence, template choice, gap markers, template shape, the bundled templates, and the default English and Chinese style guides. Never select or invoke it as a task."
4
4
  disable-model-invocation: true
5
5
  user-invocable: false
6
- release-version: 0.46.0
6
+ release-version: 0.47.0
7
7
  ---
8
8
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
9
9
  # House style
@@ -73,11 +73,13 @@ do not save a rule that would weaken them. Explain the conflict instead.
73
73
 
74
74
  ## Make the saved policy discoverable
75
75
 
76
- At the guide's in-scope directory, ensure `AGENTS.md` and every existing `CLAUDE.md` entrypoint
77
- can reach this instruction, adapted to the correct relative guide path:
76
+ At the guide's in-scope directory (or the glossary's directory for explicit terminology-only
77
+ setup without `WRITING.md`), ensure `AGENTS.md` and every existing `CLAUDE.md` entrypoint
78
+ can reach this instruction, adapted to the correct relative guidance paths:
78
79
 
79
80
  ```markdown
80
- For document drafting and editing, use `write-doc`; for analytical reports, use `write-report`.
81
+ Before document drafting and editing, read and apply `write-doc`; for analytical reports,
82
+ read and apply `write-report`. After context loss, reload the applicable skill rules.
81
83
  Follow their shared nearest-WRITING.md lookup.
82
84
  Read the applicable guide and its selected project template before writing. Save a preference
83
85
  only when explicitly asked; keep reply preferences separate from document rules.
@@ -88,6 +90,9 @@ An entrypoint already loading a configured local agent file needs no duplicate i
88
90
  Preserve other content and valid links, including distinct in-scope symlink targets. Do not
89
91
  claim persistence setup is complete while an entrypoint is unresolved. Reply-preference setup
90
92
  uses `plain-language`'s own loader instead; mixed saves may need both loaders without duplicates.
93
+ Terminology-only setup requires reading the applicable `TERMINOLOGY.md` but does not create
94
+ a missing writing guide or invoke writer initialization. A pre-existing equivalent writer
95
+ loader stays unchanged; this setup adds missing routing, not a policy refresh.
91
96
 
92
97
  ## Report the result
93
98
 
@@ -5,7 +5,7 @@ import os from 'node:os';
5
5
 
6
6
  const WRITING = '<!-- writing-md: 1 -->\n# Writing guide\n\nProject document preferences are not yet saved; bundled defaults apply.\n\n## Terms\n\nTerms: see [TERMINOLOGY.md](TERMINOLOGY.md).\n';
7
7
  const TERMS = '# Terminology\n\n## Terms\n\n| Concept | English | 简体 | 繁體 | Avoid | Meaning | Status | Source |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n';
8
- const LOADER = '## Document guidance\n\nFor document drafting and editing, use `write-doc`; for analytical reports, use `write-report`. Read the applicable `WRITING.md` and `TERMINOLOGY.md`. Save future preferences only when explicitly requested; automatic skill selection does not initialize guidance.\n';
8
+ const LOADER = '## Document guidance\n\nFor document drafting and editing, use `write-doc`; for analytical reports, use `write-report`. Read the applicable `WRITING.md` and `TERMINOLOGY.md`. Save future preferences only when explicitly requested; automatic skill selection does not initialize guidance.\nAfter context loss, read and apply the applicable writer skill again before continuing.\n';
9
9
  const inside = (root, p) => p === root || (!path.relative(root, p).startsWith('..' + path.sep) && path.relative(root, p) !== '..' && !path.isAbsolute(path.relative(root, p)));
10
10
  const home = fs.realpathSync(os.homedir());
11
11
  const result = { scope: null, start: null, projectBoundary: null, inherited: null, missingInputs: [], files: [], errors: [] };
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: plain-language
3
3
  description: "Session mode: every reply and status line in plain language — lead with the point, everyday words, full sentences, no filler — in English or Chinese, until turned off. Enable by default when the project has an applicable WRITING.md, even without a slash command. Use for /plain-language, 'talk plainly', '说人话', and explicit requests to remember project reply preferences; /plain-language init persists the default and loading instructions. Save reply rules only under Agent replies; route document preferences to distill-writing. Ordinary mode toggles remain session-only. Turn off with '/plain-language off' or 'normal mode'. NOT for writing a document (write-doc) or analytical report (write-report) or reviewing one (editorial-review)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Plain language
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: polish-cn
3
3
  description: "Polish the Chinese wording of a finished document or report, with a script proving every number, date, identifier, link and protected term is unchanged; prefers a Chinese model and records which ran. Use for '中文润色' or as a report skill's last step. NOT for cutting or restructuring content (editorial-review) or translating (write-doc or write-report)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Polish Chinese
@@ -2,7 +2,7 @@
2
2
  name: reg-parser
3
3
  version: 1.1.0
4
4
  description: "Parse regulatory/scheme documents into structured outputs: convert a regulation (AML, VARS, PCI-DSS, KYC, GDPR) into an audit-ready compliance checklist, or trace contract clauses to a mandate source. Suggest proactively when a regulation PDF accompanies a contract review or compliance task. NOT general summarization (doc-to-md), writing a gap-analysis report (write-report), or writing a policy from the checklist (write-doc)."
5
- release-version: 0.46.0
5
+ release-version: 0.47.0
6
6
  ---
7
7
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
8
8
  # reg-parser
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: terminology
3
3
  description: "Build and keep a project's TERMINOLOGY.md: the preferred term per concept in English, 简体 and 繁體, and the terms to avoid. Use to set it up from the project's documents, when the user settles or corrects a term, or when another writing skill needs the format. NOT for writing documents (write-doc) or analytical reports (write-report) or the code domain model (domain-modeling)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Terminology
@@ -30,3 +30,14 @@ the house style is set up, or over a file `write-doc` seeded, to widen it to the
30
30
  `proposed`, and add `WRITING.md` to Source. Replace the table with one line under the heading:
31
31
  `Terms: see [TERMINOLOGY.md](TERMINOLOGY.md).` When another skill started this setup, ask before
32
32
  editing WRITING.md.
33
+ 9. **Make explicit project setup discoverable.** Within the authorized project/client scope,
34
+ ensure local agent entrypoints tell document writers to read the applicable
35
+ `TERMINOLOGY.md` before drafting or editing. Use the
36
+ [document loader](../../house-style/references/persistence.md#make-the-saved-policy-discoverable)
37
+ with its scope, preservation and entrypoint checks: drafting routes to `write-doc`,
38
+ analytical reports to `write-report`, not to a new terminology-extraction run. Reuse an
39
+ equivalent existing loader; a glossary link alone does not establish skill routing.
40
+ Do not create `WRITING.md` or run writer initialization just to add the loader. Report
41
+ missing writer skills or partial entrypoint reachability without installing anything.
42
+ This step belongs to explicit project terminology setup, not an ordinary term correction
43
+ or automatically selected writing task, and does not expand existing write scope.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: write-doc
3
3
  description: "Write or edit documents that define or propose something: BRD, PRD, technical spec, integration guide, proposal, policy or risk procedure; English, Chinese or bilingual. Use automatically for drafting or revising these documents. Markdown by default; export when requested. NOT for analytical reports or writing up findings (write-report), ordinary chat replies (plain-language), learning style or saving preferences alone (distill-writing), performing an audit (compliance-audit), or WeChat articles (wechat-article-writer)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Write a document
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: write-report
3
3
  description: "Write or edit analytical reports from data, research, analysis or findings: payment, fraud, operational, performance, research, gap-analysis, audit, external assessment, management and Visa VARS reports. Use whenever asked to report results, write up findings, or turn analysis into a report, even without naming this skill. English, Chinese or bilingual; Markdown by default, export when requested. NOT for defining requirements, policies or proposals (write-doc), performing the underlying domain analysis or audit, chat-only answers (plain-language), or learning house style (distill-writing)."
4
- release-version: 0.46.0
4
+ release-version: 0.47.0
5
5
  ---
6
6
  **Updates:** Once/session run `node <skill-base-dir>/release-check.mjs`.
7
7
  # Write an analytical report