ai-developer-skill-os 5.0.0 → 6.0.1

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 (39) hide show
  1. package/README.md +10 -10
  2. package/_template/BEHAVIOR_SPEC.md +96 -0
  3. package/docs/GOVERNANCE.md +1 -1
  4. package/fix_seeds.js +22 -0
  5. package/framework/KERNEL.md +65 -40
  6. package/framework/decision-primitives.md +47 -0
  7. package/migrate-to-v6.js +130 -0
  8. package/migrate.js +147 -0
  9. package/migrate.py +133 -0
  10. package/migrate_all.cjs +96 -0
  11. package/package.json +2 -2
  12. package/skills/qk-access-policy/SKILL.md +18 -40
  13. package/skills/qk-ai-builder/SKILL.md +20 -40
  14. package/skills/qk-api-lifecycle/SKILL.md +22 -41
  15. package/skills/qk-bug-resolution/SKILL.md +22 -40
  16. package/skills/qk-context-loader/SKILL.md +19 -39
  17. package/skills/qk-data-lifecycle/SKILL.md +19 -40
  18. package/skills/qk-db-optimizer/SKILL.md +19 -40
  19. package/skills/qk-design-to-code/SKILL.md +20 -41
  20. package/skills/qk-docs/SKILL.md +20 -40
  21. package/skills/qk-engineering-standard/SKILL.md +21 -40
  22. package/skills/qk-feature-delivery/SKILL.md +22 -40
  23. package/skills/qk-help/SKILL.md +18 -36
  24. package/skills/qk-orchestrator/SKILL.md +18 -41
  25. package/skills/qk-policy-engine/SKILL.md +18 -38
  26. package/skills/qk-production-release/SKILL.md +18 -39
  27. package/skills/qk-project-bootstrap/SKILL.md +20 -41
  28. package/skills/qk-project-health/SKILL.md +20 -40
  29. package/skills/qk-project-memory/SKILL.md +19 -40
  30. package/skills/qk-system-evolution/SKILL.md +19 -40
  31. package/skills/qk-ui-audit/SKILL.md +19 -41
  32. package/skills/qk-ui-system-builder/SKILL.md +18 -39
  33. package/skills/qk-validation-gate/SKILL.md +20 -38
  34. package/specs/contracts/behavior-contract.yaml +40 -0
  35. package/specs/expectations/qk-bug-resolution.yaml +35 -0
  36. package/specs/scenarios/fix-login-nullref.yaml +3 -0
  37. package/tests/behavior-conformance.test.js +151 -0
  38. package/_template/SKILL.md +0 -58
  39. package/tests/spec-compliance.test.js +0 -193
@@ -0,0 +1,3 @@
1
+ scenario_id: fix-login-nullref
2
+ description: "Một API production bị lỗi NullReferenceException sau khi merge."
3
+ expected_behavior: qk-bug-resolution
@@ -0,0 +1,151 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import YAML from 'js-yaml';
6
+
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ const rootDir = path.resolve(__dirname, '..');
9
+ const skillsDir = path.join(rootDir, 'skills');
10
+
11
+ function getActiveSkillDirs() {
12
+ if (!fs.existsSync(skillsDir)) return [];
13
+ return fs.readdirSync(skillsDir)
14
+ .filter(name => !name.startsWith('_') && fs.statSync(path.join(skillsDir, name)).isDirectory())
15
+ .map(name => path.join(skillsDir, name));
16
+ }
17
+
18
+ function parseBSF(skillPath) {
19
+ const content = fs.readFileSync(skillPath, 'utf8');
20
+
21
+ // Lấy frontmatter (v6)
22
+ const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/);
23
+ if (!fmMatch) return null;
24
+ const frontmatter = YAML.load(fmMatch[1]);
25
+ if (frontmatter.version !== '6.0.0') return { type: 'v5', frontmatter };
26
+
27
+ const bsf = { type: 'v6', frontmatter };
28
+
29
+ // Parse YAML blocks
30
+ const yamlBlocks = [...content.matchAll(/```yaml\n([\s\S]*?)\n```/g)];
31
+
32
+ try {
33
+ let parsedBlocks = {};
34
+ yamlBlocks.forEach(block => {
35
+ const parsed = YAML.load(block[1]);
36
+ if (parsed) Object.assign(parsedBlocks, parsed);
37
+ });
38
+
39
+ bsf.constraints = {
40
+ must: parsedBlocks.must || [],
41
+ must_not: parsedBlocks.must_not || []
42
+ };
43
+ bsf.policies = {
44
+ prefer: parsedBlocks.prefer || []
45
+ };
46
+ bsf.escalation = {
47
+ stop: parsedBlocks.stop || [],
48
+ ask: parsedBlocks.ask || []
49
+ };
50
+
51
+ if (!content.includes('## Scope')) {
52
+ bsf.error = 'Missing Scope section';
53
+ }
54
+ } catch (e) {
55
+ bsf.error = 'Invalid YAML inside markdown blocks';
56
+ }
57
+
58
+ return bsf;
59
+ }
60
+
61
+ describe('Behavior Validation Framework', () => {
62
+
63
+ describe('Level 1: Specification Valid', () => {
64
+ it('Every v6 skill must have valid Schema (Metadata, Scope, Constraints)', () => {
65
+ const dirs = getActiveSkillDirs();
66
+ const v6Skills = [];
67
+
68
+ dirs.forEach(dir => {
69
+ const skillPath = path.join(dir, 'SKILL.md');
70
+ if (!fs.existsSync(skillPath)) return;
71
+ const bsf = parseBSF(skillPath);
72
+
73
+ if (bsf && bsf.type === 'v6') {
74
+ v6Skills.push(dir);
75
+ expect(bsf.error, `Parser error in ${path.basename(dir)}: ${bsf.error}`).toBeUndefined();
76
+ expect(bsf.frontmatter.category, `Missing category metadata in ${path.basename(dir)}`).toBeDefined();
77
+ expect(bsf.constraints, `Missing Constraints in ${path.basename(dir)}`).toBeDefined();
78
+ }
79
+ });
80
+
81
+ expect(v6Skills.length).toBeGreaterThan(0);
82
+ });
83
+ });
84
+
85
+ describe('Level 2: Contract Consistent', () => {
86
+ it('Constraints MUST NOT conflict with each other', () => {
87
+ const dirs = getActiveSkillDirs();
88
+
89
+ dirs.forEach(dir => {
90
+ const skillPath = path.join(dir, 'SKILL.md');
91
+ if (!fs.existsSync(skillPath)) return;
92
+ const bsf = parseBSF(skillPath);
93
+
94
+ if (bsf && bsf.type === 'v6' && !bsf.error) {
95
+ const must = bsf.constraints.must || [];
96
+ const must_not = bsf.constraints.must_not || [];
97
+
98
+ must.forEach(rule => {
99
+ const conflict = must_not.some(c => c.toLowerCase() === rule.toLowerCase());
100
+ expect(conflict, `Conflict detected in ${path.basename(dir)}: '${rule}' is in both must and must_not`).toBe(false);
101
+ });
102
+ }
103
+ });
104
+ });
105
+ });
106
+
107
+ describe('Level 3: Behavior Conformance', () => {
108
+ const scenariosDir = path.join(rootDir, 'specs', 'scenarios');
109
+ const expectationsDir = path.join(rootDir, 'specs', 'expectations');
110
+
111
+ it('All scenarios must map to a valid behavior expectation', () => {
112
+ if (!fs.existsSync(scenariosDir)) return;
113
+
114
+ const scenarioFiles = fs.readdirSync(scenariosDir).filter(f => f.endsWith('.yaml'));
115
+ if (scenarioFiles.length === 0) return;
116
+
117
+ scenarioFiles.forEach(file => {
118
+ const content = fs.readFileSync(path.join(scenariosDir, file), 'utf8');
119
+ const scenario = YAML.load(content);
120
+
121
+ expect(scenario.scenario_id, `Missing scenario_id in ${file}`).toBeDefined();
122
+ expect(scenario.expected_behavior, `Missing expected_behavior in ${file}`).toBeDefined();
123
+
124
+ const expectationPath = path.join(expectationsDir, `${scenario.expected_behavior}.yaml`);
125
+ expect(fs.existsSync(expectationPath), `Missing expectation file for behavior: ${scenario.expected_behavior}`).toBe(true);
126
+ });
127
+ });
128
+
129
+ it('All expectations must define behavior properties (must, prefer, must_not)', () => {
130
+ if (!fs.existsSync(expectationsDir)) return;
131
+
132
+ const expectationFiles = fs.readdirSync(expectationsDir).filter(f => f.endsWith('.yaml'));
133
+ if (expectationFiles.length === 0) return;
134
+
135
+ expectationFiles.forEach(file => {
136
+ const content = fs.readFileSync(path.join(expectationsDir, file), 'utf8');
137
+ const expectation = YAML.load(content);
138
+
139
+ expect(expectation.behavior, `Missing behavior ID in ${file}`).toBeDefined();
140
+ expect(Array.isArray(expectation.must), `${file} should define 'must' properties`).toBe(true);
141
+ expect(Array.isArray(expectation.must_not), `${file} should define 'must_not' properties`).toBe(true);
142
+ });
143
+ });
144
+ });
145
+
146
+ describe('Level 4: Regression Stable (Placeholder)', () => {
147
+ it('Golden Snapshot testing (Reserved for LLM runner)', () => {
148
+ expect(true).toBe(true);
149
+ });
150
+ });
151
+ });
@@ -1,58 +0,0 @@
1
- ---
2
- name: qk-template
3
- version: 5.0.0
4
- updated: 2026-07-03
5
- description: Bản mẫu siêu gọn để tạo skill theo chuẩn Agent OS v5.0.
6
- category: template
7
- tags: [template, boilerplate]
8
- platforms: [claude-code, cursor, windsurf, gemini-cli]
9
- ---
10
-
11
- # 🛠️ [Tên Kỹ Năng]
12
-
13
- > **Inheritance:** Kỹ năng này tuân thủ Kiến trúc v5.0 của `framework/KERNEL.md`.
14
- > Các xử lý tư duy nội bộ sẽ dựa vào các thông số dưới đây và trả về Decision Summary.
15
-
16
- ---
17
-
18
- ## 🎯 Mission (Scope)
19
- - ✅ Làm gì.
20
- - ❌ Không được làm gì.
21
-
22
- ---
23
-
24
- ## ⚙️ Capabilities (Cognitive Pipeline)
25
- ```yaml
26
- Pipeline:
27
- - inference
28
- - planning
29
- - execution
30
- - bias-review
31
- - ship-check
32
- ```
33
-
34
- ---
35
-
36
- ## 🎛️ Dials (Hành vi)
37
- ```yaml
38
- Dials:
39
- - id: strictness # (Import từ dial-library/strictness.md)
40
- - id: complexity-budget # (Import từ dial-library/complexity-budget.md)
41
- ```
42
-
43
- ---
44
-
45
- ## 🛡️ Biases (Sửa lỗi mặc định của AI)
46
- ```yaml
47
- Biases:
48
- - id: cosmetic-refactor # (Import từ bias-library/cosmetic-refactor.md)
49
- - id: enterprise-crud # (Import từ bias-library/enterprise-crud.md)
50
- ```
51
-
52
- ---
53
-
54
- ## 🛫 Ship Criteria (Điều kiện xuất xưởng)
55
- ```yaml
56
- Rules:
57
- - id: minimal-diff # (Import từ rule-library/minimal-diff.md)
58
- ```
@@ -1,193 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { fileURLToPath } from 'url';
5
- import YAML from 'js-yaml';
6
-
7
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
- const rootDir = path.resolve(__dirname, '..');
9
- const skillsDir = path.join(rootDir, 'skills');
10
-
11
- function parseFrontmatter(skillPath) {
12
- const content = fs.readFileSync(skillPath, 'utf8');
13
- const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
14
- if (!match) return null;
15
- try {
16
- return YAML.load(match[1]);
17
- } catch {
18
- return null;
19
- }
20
- }
21
-
22
- function getActiveSkillDirs() {
23
- if (!fs.existsSync(skillsDir)) return [];
24
- return fs.readdirSync(skillsDir)
25
- .filter(name => !name.startsWith('_') && fs.statSync(path.join(skillsDir, name)).isDirectory())
26
- .map(name => path.join(skillsDir, name));
27
- }
28
-
29
- describe('SKILL.md Spec Compliance', () => {
30
- const REQUIRED_FIELDS = [
31
- 'name', 'version', 'updated', 'description', 'behavior', 'intent',
32
- 'priority', 'tags', 'platforms', 'trigger', 'inputs', 'outputs',
33
- 'allowed_tools', 'pipeline'
34
- ];
35
-
36
- const VALID_BEHAVIORS = ['static-analysis', 'development', 'validation', 'maintenance'];
37
- const VALID_INTENTS = ['review-code', 'fix-bug', 'implement-feature', 'validate', 'maintain'];
38
- const VALID_PRIORITIES = ['low', 'medium', 'high', 'critical'];
39
- const VALID_PIPELINE_STEPS = new Set([
40
- 'analyze', 'plan', 'design', 'implement', 'review', 'validate', 'complete', 'delegate', 'evaluate', 'engineering-standard'
41
- ]);
42
-
43
- it('every active SKILL.md should have frontmatter', () => {
44
- const dirs = getActiveSkillDirs();
45
- dirs.forEach(dir => {
46
- const skillPath = path.join(dir, 'SKILL.md');
47
- expect(fs.existsSync(skillPath)).toBe(true);
48
- const fm = parseFrontmatter(skillPath);
49
- expect(fm).not.toBeNull();
50
- });
51
- });
52
-
53
- it('every SKILL.md frontmatter should contain all required fields', () => {
54
- const dirs = getActiveSkillDirs();
55
- dirs.forEach(dir => {
56
- const skillPath = path.join(dir, 'SKILL.md');
57
- const fm = parseFrontmatter(skillPath);
58
- REQUIRED_FIELDS.forEach(field => {
59
- expect(fm[field], `Missing ${field} in ${path.basename(dir)}/SKILL.md`).toBeDefined();
60
- });
61
- });
62
- });
63
-
64
- it('behavior field should be one of the allowed values', () => {
65
- const dirs = getActiveSkillDirs();
66
- dirs.forEach(dir => {
67
- const skillPath = path.join(dir, 'SKILL.md');
68
- const fm = parseFrontmatter(skillPath);
69
- expect(VALID_BEHAVIORS).toContain(fm.behavior);
70
- });
71
- });
72
-
73
- it('intent field should be one of the allowed values', () => {
74
- const dirs = getActiveSkillDirs();
75
- dirs.forEach(dir => {
76
- const skillPath = path.join(dir, 'SKILL.md');
77
- const fm = parseFrontmatter(skillPath);
78
- expect(VALID_INTENTS).toContain(fm.intent);
79
- });
80
- });
81
-
82
- it('priority field should be one of the allowed values', () => {
83
- const dirs = getActiveSkillDirs();
84
- dirs.forEach(dir => {
85
- const skillPath = path.join(dir, 'SKILL.md');
86
- const fm = parseFrontmatter(skillPath);
87
- expect(VALID_PRIORITIES).toContain(fm.priority);
88
- });
89
- });
90
-
91
- it('pipeline steps should use only allowed verbs', () => {
92
- const dirs = getActiveSkillDirs();
93
- dirs.forEach(dir => {
94
- const skillPath = path.join(dir, 'SKILL.md');
95
- const fm = parseFrontmatter(skillPath);
96
- const steps = Array.isArray(fm.pipeline) ? fm.pipeline : [fm.pipeline];
97
- steps.forEach(step => {
98
- expect(VALID_PIPELINE_STEPS.has(step), `Invalid pipeline step "${step}" in ${path.basename(dir)}`).toBe(true);
99
- });
100
- });
101
- });
102
-
103
- it('name field should match directory name and use qk- prefix', () => {
104
- const dirs = getActiveSkillDirs();
105
- dirs.forEach(dir => {
106
- const skillName = path.basename(dir);
107
- const skillPath = path.join(dir, 'SKILL.md');
108
- const fm = parseFrontmatter(skillPath);
109
- expect(fm.name).toBe(skillName);
110
- expect(fm.name.startsWith('qk-')).toBe(true);
111
- });
112
- });
113
-
114
- it('version should be semver string', () => {
115
- const dirs = getActiveSkillDirs();
116
- dirs.forEach(dir => {
117
- const skillPath = path.join(dir, 'SKILL.md');
118
- const fm = parseFrontmatter(skillPath);
119
- expect(fm.version).toMatch(/^\d+\.\d+\.\d+$/);
120
- });
121
- });
122
-
123
- it('updated should be YYYY-MM-DD', () => {
124
- const dirs = getActiveSkillDirs();
125
- dirs.forEach(dir => {
126
- const skillPath = path.join(dir, 'SKILL.md');
127
- const fm = parseFrontmatter(skillPath);
128
- const val = typeof fm.updated === 'string' ? fm.updated : new Date(fm.updated).toISOString().split('T')[0];
129
- expect(val).toMatch(/^\d{4}-\d{2}-\d{2}$/);
130
- });
131
- });
132
-
133
- it('platforms should be an array of valid IDE strings', () => {
134
- const valid = ['claude-code', 'cursor', 'windsurf', 'gemini-cli'];
135
- const dirs = getActiveSkillDirs();
136
- dirs.forEach(dir => {
137
- const skillPath = path.join(dir, 'SKILL.md');
138
- const fm = parseFrontmatter(skillPath);
139
- expect(Array.isArray(fm.platforms)).toBe(true);
140
- fm.platforms.forEach(p => expect(valid).toContain(p));
141
- });
142
- });
143
-
144
- it('tags should be an array of strings', () => {
145
- const dirs = getActiveSkillDirs();
146
- dirs.forEach(dir => {
147
- const skillPath = path.join(dir, 'SKILL.md');
148
- const fm = parseFrontmatter(skillPath);
149
- expect(Array.isArray(fm.tags)).toBe(true);
150
- fm.tags.forEach(t => expect(typeof t).toBe('string'));
151
- });
152
- });
153
-
154
- it('trigger should be a non-empty string', () => {
155
- const dirs = getActiveSkillDirs();
156
- dirs.forEach(dir => {
157
- const skillPath = path.join(dir, 'SKILL.md');
158
- const fm = parseFrontmatter(skillPath);
159
- expect(typeof fm.trigger).toBe('string');
160
- expect(fm.trigger.length).toBeGreaterThan(0);
161
- });
162
- });
163
-
164
- it('allowed_tools should be an array of strings', () => {
165
- const dirs = getActiveSkillDirs();
166
- dirs.forEach(dir => {
167
- const skillPath = path.join(dir, 'SKILL.md');
168
- const fm = parseFrontmatter(skillPath);
169
- expect(Array.isArray(fm.allowed_tools)).toBe(true);
170
- fm.allowed_tools.forEach(t => expect(typeof t).toBe('string'));
171
- });
172
- });
173
-
174
- it('body should contain required sections', () => {
175
- const dirs = getActiveSkillDirs();
176
- dirs.forEach(dir => {
177
- const skillPath = path.join(dir, 'SKILL.md');
178
- const content = fs.readFileSync(skillPath, 'utf8');
179
- expect(content).toContain('Goal');
180
- expect(content).toContain('Chain of Thought');
181
- expect(content).toContain('Constraints');
182
- expect(content).toContain('Output Format');
183
- });
184
- });
185
-
186
- it('no skill should reference archived version of itself', () => {
187
- const registryPath = path.join(rootDir, 'skills.json');
188
- const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
189
- registry.skills.forEach(skill => {
190
- expect(skill.path).not.toContain('_archive_old_skills');
191
- });
192
- });
193
- });