@vegastack/skills 0.4.0 → 0.6.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 (64) hide show
  1. package/README.md +4 -2
  2. package/dist/index.js +38 -45
  3. package/package.json +1 -1
  4. package/skill/architect/SKILL.md +68 -0
  5. package/skill/architect/agents/openai.yaml +4 -0
  6. package/skill/architect/assets/adr-template.md +21 -0
  7. package/skill/architect/assets/arch-template.md +20 -0
  8. package/skill/architect/references/advisory.md +102 -0
  9. package/skill/architect/references/ai-agents.md +95 -0
  10. package/skill/architect/references/data.md +90 -0
  11. package/skill/architect/references/infra.md +128 -0
  12. package/skill/architect/references/mobile.md +78 -0
  13. package/skill/architect/references/pinned-facts.md +108 -0
  14. package/skill/architect/references/principles.md +91 -0
  15. package/skill/architect/references/project-profile.md +37 -0
  16. package/skill/architect/references/security.md +97 -0
  17. package/skill/architect/references/stack.md +38 -0
  18. package/skill/architect/references/web.md +152 -0
  19. package/skill/architect/refresh/REFRESH.md +29 -0
  20. package/skill/architect/refresh/sources.json +244 -0
  21. package/skill/skill-maintainer/references/release-ops.md +11 -15
  22. package/skill/skill-maintainer/refresh/REFRESH.md +3 -3
  23. package/skill-integrity.json +20 -44
  24. package/skill/arch-guardian/SKILL.md +0 -84
  25. package/skill/arch-guardian/agents/openai.yaml +0 -4
  26. package/skill/arch-guardian/assets/adr-template.md +0 -25
  27. package/skill/arch-guardian/assets/answers-example.json +0 -10
  28. package/skill/arch-guardian/assets/architecture-profile.json +0 -13
  29. package/skill/arch-guardian/assets/architecture-profile.schema.json +0 -31
  30. package/skill/arch-guardian/assets/deployment-review-template.md +0 -24
  31. package/skill/arch-guardian/assets/service-design-template.md +0 -33
  32. package/skill/arch-guardian/assets/threat-model-template.md +0 -34
  33. package/skill/arch-guardian/references/advisory-report.md +0 -65
  34. package/skill/arch-guardian/references/architecture/agent-product.md +0 -22
  35. package/skill/arch-guardian/references/architecture/ai-cost.md +0 -24
  36. package/skill/arch-guardian/references/architecture/ai-data-boundaries.md +0 -21
  37. package/skill/arch-guardian/references/architecture/ai-evals.md +0 -28
  38. package/skill/arch-guardian/references/architecture/connectors-sandbox.md +0 -39
  39. package/skill/arch-guardian/references/architecture/data-memory.md +0 -25
  40. package/skill/arch-guardian/references/architecture/delivery-operations.md +0 -34
  41. package/skill/arch-guardian/references/architecture/durable-execution.md +0 -45
  42. package/skill/arch-guardian/references/architecture/flutter.md +0 -26
  43. package/skill/arch-guardian/references/architecture/foundation.md +0 -31
  44. package/skill/arch-guardian/references/architecture/hosting-reliability.md +0 -37
  45. package/skill/arch-guardian/references/architecture/identity-tenancy.md +0 -37
  46. package/skill/arch-guardian/references/architecture/model-lifecycle.md +0 -20
  47. package/skill/arch-guardian/references/architecture/models-observability.md +0 -23
  48. package/skill/arch-guardian/references/architecture/realtime-channels.md +0 -16
  49. package/skill/arch-guardian/references/architecture/security-privacy.md +0 -27
  50. package/skill/arch-guardian/references/architecture/topology-monorepo.md +0 -47
  51. package/skill/arch-guardian/references/architecture/web.md +0 -29
  52. package/skill/arch-guardian/references/foundation-compatibility.json +0 -44
  53. package/skill/arch-guardian/references/golden-patterns.md +0 -43
  54. package/skill/arch-guardian/references/profile-governance.md +0 -40
  55. package/skill/arch-guardian/references/rule-model.json +0 -36
  56. package/skill/arch-guardian/references/workflows.md +0 -48
  57. package/skill/arch-guardian/refresh/REFRESH.md +0 -47
  58. package/skill/arch-guardian/refresh/sources.json +0 -1171
  59. package/skill/arch-guardian/scripts/lib.mjs +0 -48
  60. package/skill/arch-guardian/scripts/profile-tool.mjs +0 -217
  61. package/skill/arch-guardian/scripts/refresh-evidence.mjs +0 -366
  62. package/skill/arch-guardian/scripts/schema-validate.mjs +0 -63
  63. package/skill/arch-guardian/scripts/validate-profile.mjs +0 -65
  64. package/skill/arch-guardian/scripts/verify-corpus.mjs +0 -136
@@ -1,63 +0,0 @@
1
- function pointer(root, reference) {
2
- if (!reference.startsWith('#/')) throw new Error(`Only local JSON Schema references are supported: ${reference}`)
3
- return reference.slice(2).split('/').reduce((value, key) => value?.[key.replaceAll('~1', '/').replaceAll('~0', '~')], root)
4
- }
5
-
6
- const same = (left, right) => JSON.stringify(left) === JSON.stringify(right)
7
- const typeOf = value => Array.isArray(value) ? 'array' : value === null ? 'null' : typeof value
8
- const joinPath = (base, key) => `${base}/${String(key).replaceAll('~', '~0').replaceAll('/', '~1')}`
9
- const validDate = value => {
10
- if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false
11
- const [year, month, day] = value.split('-').map(Number)
12
- const parsed = new Date(Date.UTC(year, month - 1, day))
13
- return parsed.getUTCFullYear() === year && parsed.getUTCMonth() === month - 1 && parsed.getUTCDate() === day
14
- }
15
-
16
- export function validateJsonSchema(schema, value) {
17
- const errors = []
18
- function validate(node, data, path = '$', quiet = false) {
19
- const local = []
20
- const add = message => local.push(`${path}: ${message}`)
21
- if (node.$ref) return validate(pointer(schema, node.$ref), data, path, quiet)
22
- if (node.const !== undefined && !same(data, node.const)) add(`must equal ${JSON.stringify(node.const)}`)
23
- if (node.enum && !node.enum.some(item => same(item, data))) add(`must be one of ${node.enum.map(item => JSON.stringify(item)).join(', ')}`)
24
- if (node.type) {
25
- const allowed = Array.isArray(node.type) ? node.type : [node.type]
26
- if (!allowed.includes(typeOf(data)) || (typeOf(data) === 'number' && !Number.isFinite(data))) add(`must be ${allowed.join(' or ')}`)
27
- }
28
- if (typeof data === 'string') {
29
- if (node.minLength !== undefined && data.length < node.minLength) add(`must have length >= ${node.minLength}`)
30
- if (node.pattern && !new RegExp(node.pattern).test(data)) add(`must match ${node.pattern}`)
31
- if (node.format === 'date' && !validDate(data)) add('must be a real ISO calendar date (YYYY-MM-DD)')
32
- }
33
- if (typeof data === 'number' && node.minimum !== undefined && data < node.minimum) add(`must be >= ${node.minimum}`)
34
- if (Array.isArray(data)) {
35
- if (node.minItems !== undefined && data.length < node.minItems) add(`must contain at least ${node.minItems} item(s)`)
36
- if (node.uniqueItems && new Set(data.map(item => JSON.stringify(item))).size !== data.length) add('must contain unique items')
37
- if (node.items) data.forEach((item, index) => local.push(...validate(node.items, item, joinPath(path, index), true)))
38
- }
39
- if (data && typeof data === 'object' && !Array.isArray(data)) {
40
- const keys = Object.keys(data)
41
- if (node.minProperties !== undefined && keys.length < node.minProperties) add(`must contain at least ${node.minProperties} properties`)
42
- for (const required of node.required ?? []) if (!(required in data)) local.push(`${joinPath(path, required)}: is required`)
43
- for (const [key, child] of Object.entries(node.properties ?? {})) if (key in data) local.push(...validate(child, data[key], joinPath(path, key), true))
44
- if (node.propertyNames) for (const key of keys) local.push(...validate(node.propertyNames, key, `${path} property ${JSON.stringify(key)}`, true))
45
- const known = new Set(Object.keys(node.properties ?? {}))
46
- for (const key of keys.filter(key => !known.has(key))) {
47
- if (node.additionalProperties === false) local.push(`${joinPath(path, key)}: additional property is not allowed`)
48
- else if (node.additionalProperties && typeof node.additionalProperties === 'object') local.push(...validate(node.additionalProperties, data[key], joinPath(path, key), true))
49
- }
50
- }
51
- for (const child of node.allOf ?? []) local.push(...validate(child, data, path, true))
52
- if (node.oneOf) {
53
- const matches = node.oneOf.map(child => validate(child, data, path, true)).filter(result => result.length === 0).length
54
- if (matches !== 1) add(`must match exactly one schema branch (matched ${matches})`)
55
- }
56
- if (node.not && validate(node.not, data, path, true).length === 0) add('must not match forbidden schema')
57
- if (node.if && validate(node.if, data, path, true).length === 0 && node.then) local.push(...validate(node.then, data, path, true))
58
- if (!quiet) errors.push(...local)
59
- return local
60
- }
61
- validate(schema, value)
62
- return errors
63
- }
@@ -1,65 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile } from 'node:fs/promises'
3
- import { dirname, join, resolve } from 'node:path'
4
- import { fileURLToPath, pathToFileURL } from 'node:url'
5
- import { listFiles, readJsonYaml, resolveProfile } from './lib.mjs'
6
- import { validateJsonSchema } from './schema-validate.mjs'
7
-
8
- const skillRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
9
- const architectureRoot = join(skillRoot, 'references', 'architecture')
10
- const schemaPath = join(skillRoot, 'assets', 'architecture-profile.schema.json')
11
-
12
- export async function canonicalRuleIds() {
13
- const ids = new Set()
14
- const duplicates = new Set()
15
- for (const path of await listFiles(architectureRoot, file => file.endsWith('.md'))) {
16
- const body = await readFile(path, 'utf8')
17
- for (const match of body.matchAll(/\*\*([A-Z]+-[0-9]{3}) —/g)) {
18
- if (ids.has(match[1])) duplicates.add(match[1])
19
- ids.add(match[1])
20
- }
21
- }
22
- if (duplicates.size) throw new Error(`Duplicate canonical rule IDs: ${[...duplicates].join(', ')}`)
23
- return ids
24
- }
25
-
26
- // Collect every REQUIRED-* placeholder still present so a draft cannot be mistaken for
27
- // confirmed facts.
28
- function unconfirmedValues(value, path = '') {
29
- if (typeof value === 'string') return value.startsWith('REQUIRED-') ? [path || '(root)'] : []
30
- if (Array.isArray(value)) return value.flatMap((item, index) => unconfirmedValues(item, `${path}[${index}]`))
31
- if (value && typeof value === 'object') return Object.entries(value).flatMap(([key, item]) => unconfirmedValues(item, path ? `${path}.${key}` : key))
32
- return []
33
- }
34
-
35
- export async function validateProfile(path) {
36
- let profile
37
- try { profile = await readJsonYaml(path) } catch (error) {
38
- return { profile: null, errors: [error.message] }
39
- }
40
- if (profile.schemaVersion === 2 || profile.schemaVersion === 3) {
41
- return { profile, errors: [`schemaVersion ${profile.schemaVersion} is obsolete; run profile-tool.mjs migrate <profile> for a deterministic read-only v4 draft, then confirm project facts (v2 needs a fresh v4 profile from answers)`] }
42
- }
43
- const errors = []
44
- const schema = JSON.parse(await readFile(schemaPath, 'utf8'))
45
- for (const message of validateJsonSchema(schema, profile)) errors.push(message)
46
- for (const location of unconfirmedValues(profile)) errors.push(`${location}: replace the REQUIRED placeholder with a confirmed fact`)
47
- return { profile, errors }
48
- }
49
-
50
- if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
51
- const argument = process.argv.slice(2).find(value => !value.startsWith('-'))
52
- let path = argument ? resolve(argument) : null
53
- if (!path) {
54
- const discovered = await resolveProfile(process.cwd())
55
- if (!discovered) { console.error('error: no .vegastack/architecture.json (or legacy .yaml) profile found; pass a path explicitly'); process.exit(2) }
56
- if (discovered.legacy) console.error(`deprecation: ${discovered.relative} uses the legacy .yaml name for a JSON document; rename to .vegastack/architecture.json`)
57
- path = discovered.path
58
- }
59
- const json = process.argv.includes('--json')
60
- const result = await validateProfile(path)
61
- if (json) console.log(JSON.stringify({ path, valid: result.errors.length === 0, errors: result.errors }, null, 2))
62
- else if (result.errors.length) for (const error of result.errors) console.error(`invalid profile: ${error}`)
63
- else console.log(`validate-profile: valid ${path}`)
64
- if (result.errors.length) process.exitCode = 1
65
- }
@@ -1,136 +0,0 @@
1
- #!/usr/bin/env node
2
- import { readFile } from 'node:fs/promises'
3
- import { basename, dirname, join, relative, resolve } from 'node:path'
4
- import { fileURLToPath } from 'node:url'
5
- import { listFiles, pathExists, readJsonYaml } from './lib.mjs'
6
- import { canonicalRuleIds } from './validate-profile.mjs'
7
-
8
- let mermaid = null
9
- try {
10
- const { JSDOM } = await import('jsdom')
11
- const dom = new JSDOM('<!doctype html><html><body></body></html>')
12
- globalThis.window = dom.window
13
- globalThis.document = dom.window.document
14
- mermaid = (await import('mermaid')).default
15
- mermaid.initialize({ startOnLoad: false, securityLevel: 'strict' })
16
- } catch {
17
- // Installed copies have no development dependencies and use structural checks.
18
- }
19
-
20
- const skillRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
21
- const references = join(skillRoot, 'references')
22
- const architectureRoot = join(references, 'architecture')
23
- const skillPath = join(skillRoot, 'SKILL.md')
24
- const skillBody = await readFile(skillPath, 'utf8')
25
- const chapters = await listFiles(architectureRoot, path => path.endsWith('.md'))
26
- const registry = await readJsonYaml(join(skillRoot, 'refresh', 'sources.json'))
27
- const sourceIds = new Set(registry.sources.map(source => source.id))
28
- const ruleIds = await canonicalRuleIds()
29
- const ruleModel = await readJsonYaml(join(references, 'rule-model.json'))
30
- const errors = []
31
- let diagrams = 0
32
- let normativeLines = 0
33
-
34
- const expectedReferences = new Set(['agent-product.md', 'ai-cost.md', 'ai-data-boundaries.md', 'ai-evals.md', 'connectors-sandbox.md', 'data-memory.md', 'delivery-operations.md', 'durable-execution.md', 'flutter.md', 'foundation.md', 'hosting-reliability.md', 'identity-tenancy.md', 'model-lifecycle.md', 'models-observability.md', 'realtime-channels.md', 'security-privacy.md', 'topology-monorepo.md', 'web.md'])
35
- if (chapters.length !== expectedReferences.size) errors.push(`expected ${expectedReferences.size} normative references, found ${chapters.length}`)
36
- for (const path of chapters) if (!expectedReferences.delete(basename(path))) errors.push(`unexpected architecture reference ${basename(path)}`)
37
- for (const missing of expectedReferences) errors.push(`missing architecture reference ${missing}`)
38
-
39
- const skillLines = skillBody.split('\n').length
40
- if (skillLines > 120) errors.push(`SKILL.md exceeds 120 lines (${skillLines})`)
41
- const routed = new Set([...skillBody.matchAll(/\(references\/architecture\/([^)]+\.md)\)/g)].map(match => match[1]))
42
-
43
- const citedSources = new Set()
44
- for (const path of chapters) {
45
- const body = await readFile(path, 'utf8')
46
- normativeLines += body.split('\n').length
47
- if (!routed.has(basename(path))) errors.push(`${path}: not routed directly from SKILL.md`)
48
- for (const match of body.matchAll(/\[([A-Z0-9]+(?:-[A-Z0-9]+)*)\]|<!-- source: ([A-Z0-9]+(?:-[A-Z0-9]+)*) -->/g)) {
49
- const id = match[1] ?? match[2]
50
- citedSources.add(id)
51
- if (!sourceIds.has(id)) errors.push(`${path}: unknown source ${id}`)
52
- }
53
- for (const [index, line] of body.split('\n').entries()) {
54
- const definitions = [...line.matchAll(/\*\*([A-Z]+-[0-9]{3}) —/g)]
55
- if (definitions.length && !/\bMUST(?:\s+NOT)?\b/.test(line)) errors.push(`${path}: rule ${definitions[0][1]} must define a MUST or MUST NOT invariant in its canonical line`)
56
- if (/\bMUST(?:\s+NOT)?\b/.test(line) && definitions.length !== 1) errors.push(`${path}:${index + 1}: every MUST/MUST NOT must occur in exactly one canonical rule definition`)
57
- }
58
- for (const block of body.matchAll(/```mermaid\n([\s\S]*?)```/g)) {
59
- diagrams += 1
60
- const text = block[1].trim()
61
- if (!/^(flowchart|sequenceDiagram|stateDiagram-v2)\b/.test(text)) errors.push(`${path}: unsupported Mermaid declaration`)
62
- if ((text.match(/\[/g) ?? []).length !== (text.match(/\]/g) ?? []).length) errors.push(`${path}: unbalanced Mermaid brackets`)
63
- if (mermaid) try { await mermaid.parse(text) } catch (error) { errors.push(`${path}: Mermaid parser: ${error.message}`) }
64
- }
65
- }
66
- if (normativeLines > 800) errors.push(`normative references exceed 800 lines (${normativeLines})`)
67
- if (diagrams < 4) errors.push(`expected at least four decision-useful Mermaid diagrams, found ${diagrams}`)
68
-
69
- // criticalSources in foundation-compatibility must mirror every critical:true registry entry —
70
- // asserted here so the mirror can never drift silently.
71
- const compatibility = await readJsonYaml(join(references, 'foundation-compatibility.json'))
72
- const declaredCritical = new Set(compatibility.sourceDrift?.criticalSources ?? [])
73
- const actualCritical = new Set(registry.sources.filter(source => source.critical).map(source => source.id))
74
- for (const id of actualCritical) if (!declaredCritical.has(id)) errors.push(`criticalSources mirror missing critical registry source ${id}`)
75
- for (const id of declaredCritical) if (!actualCritical.has(id)) errors.push(`criticalSources mirror lists non-critical or unknown source ${id}`)
76
-
77
- for (const source of registry.sources) {
78
- if (!citedSources.has(source.id)) errors.push(`orphan source registry entry ${source.id}`)
79
- if (!source.urls?.primary) errors.push(`source ${source.id} has no primary URL`)
80
- for (const target of source.affected ?? []) {
81
- const [kind, value] = String(target).split(':', 2)
82
- if (!['rule', 'ref', 'profile'].includes(kind)) errors.push(`source ${source.id} has invalid affected mapping ${target}`)
83
- if (kind === 'rule' && !ruleIds.has(value)) errors.push(`source ${source.id} maps unknown rule ${value}`)
84
- if (kind === 'ref' && !await pathExists(join(architectureRoot, `${value}.md`))) errors.push(`source ${source.id} maps unknown reference ${value}`)
85
- }
86
- }
87
-
88
- const modeledRules = new Set()
89
- for (const group of ruleModel.groups ?? []) {
90
- for (const required of ['activation', 'verification', 'rationale']) if (!group[required]) errors.push(`rule model group missing ${required}`)
91
- for (const rule of group.rules ?? []) {
92
- if (modeledRules.has(rule)) errors.push(`rule model duplicates ${rule}`)
93
- modeledRules.add(rule)
94
- if (!ruleIds.has(rule)) errors.push(`rule model names unknown rule ${rule}`)
95
- }
96
- }
97
- for (const rule of ruleIds) if (!modeledRules.has(rule)) errors.push(`canonical rule ${rule} has no activation/verification/rationale model`)
98
- for (const override of ruleModel.overrides ?? []) if (!modeledRules.has(override.rule)) errors.push(`rule model override names unknown rule ${override.rule}`)
99
-
100
- const bannedNames = ['golden-architecture.md', 'evidence-manifest.json', 'coverage-matrix.md', 'verification-ledger.md', '23-evidence-comparisons.md', '24-roadmap.md', 'compile-guide.mjs', 'architecture-check.mjs', 'control-catalog.json']
101
- const allFiles = await listFiles(skillRoot)
102
- for (const path of allFiles) if (bannedNames.includes(basename(path))) errors.push(`forbidden runtime artifact remains: ${relative(skillRoot, path)}`)
103
- const runtimeText = (await Promise.all((await listFiles(skillRoot, path => /\.(?:md|json|yaml|yml|mjs)$/.test(path) && !path.includes('/tests/'))).map(path => readFile(path, 'utf8')))).join('\n')
104
- const historicalNames = ['C' + 'RM', 'S' + 'IM', 'Fl' + 'ue']
105
- if (new RegExp(`\\b(?:${historicalNames.join('|')})\\b`).test(runtimeText)) errors.push('historical comparison names remain in runtime skill')
106
-
107
- const githubSlug = heading => heading.toLowerCase().trim().replace(/<[^>]+>/g, '').replace(/[^\p{L}\p{N}\s-]/gu, '').replace(/\s+/g, '-')
108
- const headingsFor = body => new Set([...body.matchAll(/^#{1,6}\s+(.+)$/gm)].map(match => githubSlug(match[1])))
109
- for (const path of await listFiles(skillRoot, path => path.endsWith('.md'))) {
110
- const body = await readFile(path, 'utf8')
111
- for (const match of body.matchAll(/\[[^\]]+\]\(([^)]+)\)/g)) {
112
- const target = match[1]
113
- if (/^(https?:|mailto:)/.test(target)) continue
114
- const [filePart, anchor] = target.split('#')
115
- const targetPath = filePart ? resolve(dirname(path), decodeURIComponent(filePart)) : path
116
- if (!await pathExists(targetPath)) { errors.push(`${path}: broken link ${target}`); continue }
117
- if (anchor && !headingsFor(await readFile(targetPath, 'utf8')).has(anchor)) errors.push(`${path}: broken anchor ${target}`)
118
- }
119
- const lines = body.split('\n')
120
- for (let index = 0; index < lines.length; index += 1) {
121
- if (!lines[index].startsWith('|')) continue
122
- const expected = (lines[index].match(/\|/g) ?? []).length
123
- let cursor = index + 1
124
- while (cursor < lines.length && lines[cursor].startsWith('|')) {
125
- const actual = (lines[cursor].match(/\|/g) ?? []).length
126
- if (actual !== expected) errors.push(`${path}:${cursor + 1}: malformed Markdown table`)
127
- cursor += 1
128
- }
129
- index = cursor - 1
130
- }
131
- }
132
-
133
- if (errors.length) {
134
- for (const error of errors) console.error(error)
135
- process.exitCode = 1
136
- } else console.log(`verify-corpus: ${chapters.length} normative references, ${normativeLines} lines, ${ruleIds.size} rules, ${diagrams} Mermaid diagrams, ${sourceIds.size} cited sources, Mermaid mode=${mermaid ? 'formal' : 'structural-fallback'}`)