thachvd-kit 1.0.37 → 1.0.39

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 (33) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +303 -11
  3. package/THIRD_PARTY_NOTICES.md +49 -49
  4. package/bin/cli.js +1791 -1785
  5. package/bin/config.js +164 -0
  6. package/bin/entry.js +11 -1
  7. package/bin/native-skills.js +183 -149
  8. package/bin/spec-doctor.js +252 -0
  9. package/bin/spec-link.js +97 -0
  10. package/bin/spec-recipe.js +74 -0
  11. package/bin/spec-site.js +639 -0
  12. package/bin/spec-state.js +415 -0
  13. package/bin/spec.js +901 -0
  14. package/bin/upgrade.js +303 -303
  15. package/package.json +3 -3
  16. package/skills/finishing-a-development-branch/SKILL.md +240 -240
  17. package/skills/requesting-code-review/code-reviewer.md +198 -198
  18. package/skills/subagent-driven-development/SKILL.md +574 -574
  19. package/skills/subagent-driven-development/implementer-prompt.md +154 -154
  20. package/skills/subagent-driven-development/re-review-prompt.md +115 -115
  21. package/skills/subagent-driven-development/scripts/review-package +53 -53
  22. package/skills/subagent-driven-development/scripts/review-package.js +52 -52
  23. package/skills/subagent-driven-development/scripts/sdd-workspace +82 -82
  24. package/skills/subagent-driven-development/scripts/sdd-workspace-lib.js +62 -62
  25. package/skills/subagent-driven-development/scripts/sdd-workspace.js +15 -15
  26. package/skills/subagent-driven-development/scripts/task-brief +43 -43
  27. package/skills/subagent-driven-development/scripts/task-brief.js +46 -46
  28. package/skills/subagent-driven-development/task-reviewer-prompt.md +207 -207
  29. package/skills/system-discovery/SKILL.md +140 -0
  30. package/skills/system-reverse-engineer/SKILL.md +208 -0
  31. package/skills/system-spec-review/SKILL.md +177 -0
  32. package/skills/upstream.json +30 -30
  33. package/skills/using-git-worktrees/SKILL.md +175 -175
package/bin/config.js ADDED
@@ -0,0 +1,164 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const CONFIG_RELATIVE_PATH = path.join('.thachvd', 'config.json');
5
+ const SUPPORTED_SPEC_LANGUAGES = new Set(['en', 'vi']);
6
+ const SUPPORTED_SPEC_PROFILES = new Set(['auto', 'backend', 'frontend', 'fullstack', 'mobile', 'infra']);
7
+
8
+ function resolveHome(env = process.env) {
9
+ return env.USERPROFILE || env.HOME || null;
10
+ }
11
+
12
+ function configPath(homeDir) {
13
+ return path.join(homeDir, CONFIG_RELATIVE_PATH);
14
+ }
15
+
16
+ function readConfig(homeDir) {
17
+ if (!homeDir) return {};
18
+ const filePath = configPath(homeDir);
19
+ if (!fs.existsSync(filePath)) return {};
20
+ try {
21
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
22
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
23
+ } catch (error) {
24
+ throw new Error(`Cannot parse ${filePath}: ${error.message}`);
25
+ }
26
+ }
27
+
28
+ function writeConfig(homeDir, config) {
29
+ if (!homeDir) throw new Error('Cannot determine home directory from USERPROFILE or HOME.');
30
+ const filePath = configPath(homeDir);
31
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
32
+ fs.writeFileSync(filePath, JSON.stringify(config, null, 2) + '\n', 'utf8');
33
+ return filePath;
34
+ }
35
+
36
+ function normalizeLanguage(value) {
37
+ const language = String(value || '').trim().toLowerCase();
38
+ if (!SUPPORTED_SPEC_LANGUAGES.has(language)) {
39
+ throw new Error(`Unsupported spec language: ${value}. Supported values: en, vi`);
40
+ }
41
+ return language;
42
+ }
43
+
44
+ function normalizeProfile(value) {
45
+ const profile = String(value || '').trim().toLowerCase();
46
+ if (!SUPPORTED_SPEC_PROFILES.has(profile)) {
47
+ throw new Error(`Unsupported spec profile: ${value}. Supported values: auto, backend, frontend, fullstack, mobile, infra`);
48
+ }
49
+ return profile;
50
+ }
51
+
52
+ function getDefaultSpecProfile(env = process.env) {
53
+ const homeDir = resolveHome(env);
54
+ if (!homeDir) return null;
55
+ const config = readConfig(homeDir);
56
+ return config.spec_profile ? normalizeProfile(config.spec_profile) : null;
57
+ }
58
+
59
+ function getDefaultSpecLanguage(env = process.env) {
60
+ const homeDir = resolveHome(env);
61
+ if (!homeDir) return null;
62
+ const config = readConfig(homeDir);
63
+ return config.spec_language ? normalizeLanguage(config.spec_language) : null;
64
+ }
65
+
66
+ function printHelp() {
67
+ console.log(`thachvd-kit config
68
+
69
+ Usage:
70
+ thachvd-kit config show
71
+ thachvd-kit config set spec-language en|vi
72
+ thachvd-kit config unset spec-language
73
+ thachvd-kit config set spec-profile auto|backend|frontend|fullstack|mobile|infra
74
+ thachvd-kit config unset spec-profile
75
+
76
+ The user-level config is stored at ~/.thachvd/config.json (or the platform-equivalent home directory).
77
+ Project-level --language and --profile always override their user defaults.
78
+ `);
79
+ }
80
+
81
+ function runConfigCommand(args, options = {}) {
82
+ const env = options.env || process.env;
83
+ const homeDir = options.homeDir || resolveHome(env);
84
+ const command = args[0] || 'show';
85
+
86
+ try {
87
+ if (command === '--help' || command === '-h' || command === 'help') {
88
+ printHelp();
89
+ return 0;
90
+ }
91
+
92
+ if (command === 'show') {
93
+ const config = readConfig(homeDir);
94
+ console.log(`Config: ${homeDir ? configPath(homeDir) : '(home unavailable)'}`);
95
+ console.log(`spec-language: ${config.spec_language || '(default: en)'}`);
96
+ console.log(`spec-profile: ${config.spec_profile || '(default: auto)'}`);
97
+ return 0;
98
+ }
99
+
100
+ if (command === 'set') {
101
+ if (!args[1] || !args[2]) {
102
+ throw new Error('Usage: thachvd-kit config set spec-language en|vi OR spec-profile auto|backend|frontend|fullstack|mobile|infra');
103
+ }
104
+ const config = readConfig(homeDir);
105
+ if (args[1] === 'spec-language') {
106
+ const language = normalizeLanguage(args[2]);
107
+ config.spec_language = language;
108
+ const filePath = writeConfig(homeDir, config);
109
+ console.log(`Updated ${filePath}`);
110
+ console.log(`spec-language: ${language}`);
111
+ return 0;
112
+ }
113
+ if (args[1] === 'spec-profile') {
114
+ const profile = normalizeProfile(args[2]);
115
+ config.spec_profile = profile;
116
+ const filePath = writeConfig(homeDir, config);
117
+ console.log(`Updated ${filePath}`);
118
+ console.log(`spec-profile: ${profile}`);
119
+ return 0;
120
+ }
121
+ throw new Error(`Unknown config key: ${args[1]}`);
122
+ }
123
+
124
+ if (command === 'unset') {
125
+ const config = readConfig(homeDir);
126
+ if (args[1] === 'spec-language') {
127
+ delete config.spec_language;
128
+ const filePath = writeConfig(homeDir, config);
129
+ console.log(`Updated ${filePath}`);
130
+ console.log('spec-language: (default: en)');
131
+ return 0;
132
+ }
133
+ if (args[1] === 'spec-profile') {
134
+ delete config.spec_profile;
135
+ const filePath = writeConfig(homeDir, config);
136
+ console.log(`Updated ${filePath}`);
137
+ console.log('spec-profile: (default: auto)');
138
+ return 0;
139
+ }
140
+ throw new Error('Usage: thachvd-kit config unset spec-language|spec-profile');
141
+ }
142
+
143
+ throw new Error(`Unknown config command: ${command}`);
144
+ } catch (error) {
145
+ console.error(`config ${command} failed: ${error.message}`);
146
+ process.exitCode = 1;
147
+ return 1;
148
+ }
149
+ }
150
+
151
+ module.exports = {
152
+ CONFIG_RELATIVE_PATH,
153
+ SUPPORTED_SPEC_LANGUAGES,
154
+ SUPPORTED_SPEC_PROFILES,
155
+ resolveHome,
156
+ configPath,
157
+ readConfig,
158
+ writeConfig,
159
+ normalizeLanguage,
160
+ normalizeProfile,
161
+ getDefaultSpecLanguage,
162
+ getDefaultSpecProfile,
163
+ runConfigCommand
164
+ };
package/bin/entry.js CHANGED
@@ -11,7 +11,7 @@ function isInitInvocation(args) {
11
11
  if (args.includes('--help') || args.includes('-h') || args.includes('--version') || args.includes('-v')) {
12
12
  return false;
13
13
  }
14
- // Note: 'upgrade' and 'global' are handled and returned above before this
14
+ // Note: 'upgrade', 'global', 'config', and 'spec' are handled and returned above before this
15
15
  // is ever called, so they never reach here.
16
16
  const first = args[0];
17
17
  return !first || first === 'init' || first.startsWith('-');
@@ -74,6 +74,16 @@ if (command === 'global') {
74
74
  return;
75
75
  }
76
76
 
77
+ if (command === 'config') {
78
+ require('./config').runConfigCommand(args.slice(1));
79
+ return;
80
+ }
81
+
82
+ if (command === 'spec') {
83
+ require('./spec').runSpecCommand(args.slice(1));
84
+ return;
85
+ }
86
+
77
87
  const shouldUpgradeGeneratedPolicy = isInitInvocation(args);
78
88
  const beforeSnapshot = shouldUpgradeGeneratedPolicy ? snapshotPolicyFiles(process.cwd()) : null;
79
89
  const force = args.includes('--yes') || args.includes('-y');
@@ -1,149 +1,183 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- const NATIVE_SKILLS = [
5
- 'using-git-worktrees',
6
- 'subagent-driven-development',
7
- 'finishing-a-development-branch'
8
- ];
9
-
10
- const NATIVE_SUPPORT_FILES = [
11
- path.join('requesting-code-review', 'code-reviewer.md')
12
- ];
13
-
14
- const NATIVE_SKILL_SURFACES = [
15
- { id: 'agents', relativeDir: path.join('.agents', 'skills') },
16
- { id: 'claude', relativeDir: path.join('.claude', 'skills') }
17
- ];
18
-
19
- const nativeSkillsSourceDir = path.resolve(__dirname, '..', 'skills');
20
-
21
- function sourcePath(relativePath) {
22
- return path.join(nativeSkillsSourceDir, relativePath);
23
- }
24
-
25
- function skillSourceDir(name) {
26
- return sourcePath(name);
27
- }
28
-
29
- function collectFiles(directory, relative = '') {
30
- const files = [];
31
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
32
- const entryRelative = path.join(relative, entry.name);
33
- const entryPath = path.join(directory, entry.name);
34
- if (entry.isDirectory()) files.push(...collectFiles(entryPath, entryRelative));
35
- else if (entry.isFile()) files.push(entryRelative);
36
- }
37
- return files;
38
- }
39
-
40
- function ownedFiles() {
41
- return [
42
- ...NATIVE_SKILLS.flatMap(name => collectFiles(skillSourceDir(name), name)),
43
- ...NATIVE_SUPPORT_FILES
44
- ];
45
- }
46
-
47
- function ownedSkillFiles(name) {
48
- return collectFiles(skillSourceDir(name), name);
49
- }
50
-
51
- function targetPath(targetDir, surface, relativePath) {
52
- return path.join(targetDir, surface.relativeDir, relativePath);
53
- }
54
-
55
- function checkNativeSkills(targetDir) {
56
- const surfaces = {};
57
- const skills = {};
58
-
59
- for (const surface of NATIVE_SKILL_SURFACES) {
60
- const missingFiles = ownedFiles().filter(relativePath => !fs.existsSync(targetPath(targetDir, surface, relativePath)));
61
- const installed = [];
62
- const missing = [];
63
- for (const name of NATIVE_SKILLS) {
64
- (ownedSkillFiles(name).every(relativePath => fs.existsSync(targetPath(targetDir, surface, relativePath)))
65
- ? installed
66
- : missing).push(name);
67
- }
68
- const supportInstalled = NATIVE_SUPPORT_FILES.filter(relativePath => !missingFiles.includes(relativePath));
69
- const supportMissing = NATIVE_SUPPORT_FILES.filter(relativePath => missingFiles.includes(relativePath));
70
- surfaces[surface.id] = {
71
- installed,
72
- missing,
73
- total: NATIVE_SKILLS.length,
74
- missingFiles,
75
- support: {
76
- installed: supportInstalled,
77
- missing: supportMissing,
78
- total: NATIVE_SUPPORT_FILES.length
79
- }
80
- };
81
- }
82
-
83
- for (const name of NATIVE_SKILLS) {
84
- skills[name] = {};
85
- for (const surface of NATIVE_SKILL_SURFACES) {
86
- skills[name][surface.id] = ownedSkillFiles(name).every(relativePath => fs.existsSync(targetPath(targetDir, surface, relativePath)));
87
- }
88
- }
89
-
90
- const installed = NATIVE_SKILLS.filter(name => NATIVE_SKILL_SURFACES.every(surface => skills[name][surface.id]));
91
- const missing = NATIVE_SKILLS.filter(name => !installed.includes(name));
92
- const missingFiles = Object.fromEntries(NATIVE_SKILL_SURFACES.map(surface => [surface.id, surfaces[surface.id].missingFiles]));
93
- return {
94
- installed,
95
- missing,
96
- total: NATIVE_SKILLS.length,
97
- allInstalled: missingFiles.agents.length === 0 && missingFiles.claude.length === 0,
98
- surfaces,
99
- skills,
100
- missingFiles
101
- };
102
- }
103
-
104
- function copyFile(source, destination) {
105
- fs.mkdirSync(path.dirname(destination), { recursive: true });
106
- fs.copyFileSync(source, destination);
107
- }
108
-
109
- function installNativeSkills(targetDir, options = {}) {
110
- const dryRun = options.dryRun === true;
111
- const operations = [];
112
- for (const surface of NATIVE_SKILL_SURFACES) {
113
- for (const relativePath of ownedFiles()) {
114
- const source = sourcePath(relativePath);
115
- const destination = targetPath(targetDir, surface, relativePath);
116
- operations.push({ surface: surface.id, relativePath, source, destination });
117
- if (!dryRun) copyFile(source, destination);
118
- }
119
- }
120
- return {
121
- ok: true,
122
- dryRun,
123
- operations,
124
- results: NATIVE_SKILLS.map(name => ({ name, ok: true, dryRun })),
125
- message: dryRun
126
- ? `Dry run: would copy ${NATIVE_SKILLS.length} native skills to .agents/skills and .claude/skills`
127
- : `installed ${NATIVE_SKILLS.length} thachvd-kit native skills to .agents/skills and .claude/skills`
128
- };
129
- }
130
-
131
- function updateNativeSkills(targetDir, options = {}) {
132
- const result = installNativeSkills(targetDir, options);
133
- return {
134
- ...result,
135
- message: result.dryRun
136
- ? `Dry run: would refresh ${NATIVE_SKILLS.length} native skills from bundled copies`
137
- : `updated ${NATIVE_SKILLS.length} thachvd-kit native skills from bundled copies`
138
- };
139
- }
140
-
141
- module.exports = {
142
- NATIVE_SKILLS,
143
- NATIVE_SUPPORT_FILES,
144
- NATIVE_SKILL_SURFACES,
145
- nativeSkillsSourceDir,
146
- checkNativeSkills,
147
- installNativeSkills,
148
- updateNativeSkills
149
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const NATIVE_SKILLS = [
5
+ 'using-git-worktrees',
6
+ 'subagent-driven-development',
7
+ 'finishing-a-development-branch',
8
+ 'system-discovery',
9
+ 'system-reverse-engineer',
10
+ 'system-spec-review'
11
+ ];
12
+
13
+ const SYSTEM_SPEC_SKILLS = [
14
+ 'system-discovery',
15
+ 'system-reverse-engineer',
16
+ 'system-spec-review'
17
+ ];
18
+
19
+ const NATIVE_SUPPORT_FILES = [
20
+ path.join('requesting-code-review', 'code-reviewer.md')
21
+ ];
22
+
23
+ const NATIVE_SKILL_SURFACES = [
24
+ { id: 'agents', relativeDir: path.join('.agents', 'skills') },
25
+ { id: 'claude', relativeDir: path.join('.claude', 'skills') }
26
+ ];
27
+
28
+ const nativeSkillsSourceDir = path.resolve(__dirname, '..', 'skills');
29
+
30
+ function sourcePath(relativePath) {
31
+ return path.join(nativeSkillsSourceDir, relativePath);
32
+ }
33
+
34
+ function skillSourceDir(name) {
35
+ return sourcePath(name);
36
+ }
37
+
38
+ function collectFiles(directory, relative = '') {
39
+ const files = [];
40
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
41
+ const entryRelative = path.join(relative, entry.name);
42
+ const entryPath = path.join(directory, entry.name);
43
+ if (entry.isDirectory()) files.push(...collectFiles(entryPath, entryRelative));
44
+ else if (entry.isFile()) files.push(entryRelative);
45
+ }
46
+ return files;
47
+ }
48
+
49
+ function ownedFiles() {
50
+ return [
51
+ ...NATIVE_SKILLS.flatMap(name => collectFiles(skillSourceDir(name), name)),
52
+ ...NATIVE_SUPPORT_FILES
53
+ ];
54
+ }
55
+
56
+ function ownedSkillFiles(name) {
57
+ return collectFiles(skillSourceDir(name), name);
58
+ }
59
+
60
+ function targetPath(targetDir, surface, relativePath) {
61
+ return path.join(targetDir, surface.relativeDir, relativePath);
62
+ }
63
+
64
+ function checkNativeSkills(targetDir) {
65
+ const surfaces = {};
66
+ const skills = {};
67
+
68
+ for (const surface of NATIVE_SKILL_SURFACES) {
69
+ const missingFiles = ownedFiles().filter(relativePath => !fs.existsSync(targetPath(targetDir, surface, relativePath)));
70
+ const installed = [];
71
+ const missing = [];
72
+ for (const name of NATIVE_SKILLS) {
73
+ (ownedSkillFiles(name).every(relativePath => fs.existsSync(targetPath(targetDir, surface, relativePath)))
74
+ ? installed
75
+ : missing).push(name);
76
+ }
77
+ const supportInstalled = NATIVE_SUPPORT_FILES.filter(relativePath => !missingFiles.includes(relativePath));
78
+ const supportMissing = NATIVE_SUPPORT_FILES.filter(relativePath => missingFiles.includes(relativePath));
79
+ surfaces[surface.id] = {
80
+ installed,
81
+ missing,
82
+ total: NATIVE_SKILLS.length,
83
+ missingFiles,
84
+ support: {
85
+ installed: supportInstalled,
86
+ missing: supportMissing,
87
+ total: NATIVE_SUPPORT_FILES.length
88
+ }
89
+ };
90
+ }
91
+
92
+ for (const name of NATIVE_SKILLS) {
93
+ skills[name] = {};
94
+ for (const surface of NATIVE_SKILL_SURFACES) {
95
+ skills[name][surface.id] = ownedSkillFiles(name).every(relativePath => fs.existsSync(targetPath(targetDir, surface, relativePath)));
96
+ }
97
+ }
98
+
99
+ const installed = NATIVE_SKILLS.filter(name => NATIVE_SKILL_SURFACES.every(surface => skills[name][surface.id]));
100
+ const missing = NATIVE_SKILLS.filter(name => !installed.includes(name));
101
+ const missingFiles = Object.fromEntries(NATIVE_SKILL_SURFACES.map(surface => [surface.id, surfaces[surface.id].missingFiles]));
102
+ return {
103
+ installed,
104
+ missing,
105
+ total: NATIVE_SKILLS.length,
106
+ allInstalled: missingFiles.agents.length === 0 && missingFiles.claude.length === 0,
107
+ surfaces,
108
+ skills,
109
+ missingFiles
110
+ };
111
+ }
112
+
113
+ function copyFile(source, destination) {
114
+ fs.mkdirSync(path.dirname(destination), { recursive: true });
115
+ fs.copyFileSync(source, destination);
116
+ }
117
+
118
+ function installNativeSkills(targetDir, options = {}) {
119
+ const dryRun = options.dryRun === true;
120
+ const operations = [];
121
+ for (const surface of NATIVE_SKILL_SURFACES) {
122
+ for (const relativePath of ownedFiles()) {
123
+ const source = sourcePath(relativePath);
124
+ const destination = targetPath(targetDir, surface, relativePath);
125
+ operations.push({ surface: surface.id, relativePath, source, destination });
126
+ if (!dryRun) copyFile(source, destination);
127
+ }
128
+ }
129
+ return {
130
+ ok: true,
131
+ dryRun,
132
+ operations,
133
+ results: NATIVE_SKILLS.map(name => ({ name, ok: true, dryRun })),
134
+ message: dryRun
135
+ ? `Dry run: would copy ${NATIVE_SKILLS.length} native skills to .agents/skills and .claude/skills`
136
+ : `installed ${NATIVE_SKILLS.length} thachvd-kit native skills to .agents/skills and .claude/skills`
137
+ };
138
+ }
139
+
140
+ function installSystemSpecSkills(targetDir, options = {}) {
141
+ const dryRun = options.dryRun === true;
142
+ const operations = [];
143
+ for (const surface of NATIVE_SKILL_SURFACES) {
144
+ for (const name of SYSTEM_SPEC_SKILLS) {
145
+ for (const relativePath of ownedSkillFiles(name)) {
146
+ const source = sourcePath(relativePath);
147
+ const destination = targetPath(targetDir, surface, relativePath);
148
+ operations.push({ surface: surface.id, relativePath, source, destination });
149
+ if (!dryRun) copyFile(source, destination);
150
+ }
151
+ }
152
+ }
153
+ return {
154
+ ok: true,
155
+ dryRun,
156
+ operations,
157
+ message: dryRun
158
+ ? `Dry run: would copy ${SYSTEM_SPEC_SKILLS.length} system-spec skills to workspace agent surfaces`
159
+ : `installed ${SYSTEM_SPEC_SKILLS.length} system-spec skills to workspace agent surfaces`
160
+ };
161
+ }
162
+
163
+ function updateNativeSkills(targetDir, options = {}) {
164
+ const result = installNativeSkills(targetDir, options);
165
+ return {
166
+ ...result,
167
+ message: result.dryRun
168
+ ? `Dry run: would refresh ${NATIVE_SKILLS.length} native skills from bundled copies`
169
+ : `updated ${NATIVE_SKILLS.length} thachvd-kit native skills from bundled copies`
170
+ };
171
+ }
172
+
173
+ module.exports = {
174
+ NATIVE_SKILLS,
175
+ SYSTEM_SPEC_SKILLS,
176
+ NATIVE_SUPPORT_FILES,
177
+ NATIVE_SKILL_SURFACES,
178
+ nativeSkillsSourceDir,
179
+ checkNativeSkills,
180
+ installNativeSkills,
181
+ installSystemSpecSkills,
182
+ updateNativeSkills
183
+ };