@torus-engineering/tas-kit 1.9.0 → 1.11.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.
- package/.claude/commands/ado-create.md +17 -17
- package/.claude/commands/ado-delete.md +11 -11
- package/.claude/commands/ado-get.md +12 -12
- package/.claude/commands/ado-status.md +12 -12
- package/.claude/commands/ado-update.md +15 -15
- package/.claude/commands/tas-adr.md +33 -33
- package/.claude/commands/tas-apitest-plan.md +173 -173
- package/.claude/commands/tas-apitest.md +143 -143
- package/.claude/commands/tas-brainstorm.md +14 -14
- package/.claude/commands/tas-bug.md +113 -113
- package/.claude/commands/tas-design.md +37 -37
- package/.claude/commands/tas-dev.md +128 -128
- package/.claude/commands/tas-e2e-mobile.md +155 -155
- package/.claude/commands/tas-e2e-web.md +163 -163
- package/.claude/commands/tas-e2e.md +102 -102
- package/.claude/commands/tas-epic.md +35 -35
- package/.claude/commands/tas-feature.md +47 -47
- package/.claude/commands/tas-fix.md +51 -51
- package/.claude/commands/tas-functest-mobile.md +144 -144
- package/.claude/commands/tas-functest-web.md +192 -192
- package/.claude/commands/tas-functest.md +76 -76
- package/.claude/commands/tas-init.md +14 -14
- package/.claude/commands/tas-plan.md +198 -200
- package/.claude/commands/tas-prd.md +37 -37
- package/.claude/commands/tas-review.md +111 -111
- package/.claude/commands/tas-sad.md +43 -43
- package/.claude/commands/tas-security.md +87 -81
- package/.claude/commands/tas-spec.md +20 -20
- package/.claude/commands/tas-status.md +13 -13
- package/.claude/commands/tas-story.md +91 -91
- package/.claude/commands/tas-verify.md +51 -51
- package/.claude/rules/common/post-review-agent.md +49 -49
- package/.claude/rules/common/project-status.md +14 -14
- package/.claude/rules/common/stack-detection.md +6 -6
- package/.claude/rules/common/token-logging.md +27 -27
- package/.claude/rules/csharp/api-testing.md +171 -171
- package/.claude/skills/ado-integration/SKILL.md +36 -36
- package/.claude/skills/tas-conventions/SKILL.md +32 -32
- package/.claude/skills/tas-implementation-complete/SKILL.md +100 -99
- package/.claude/skills/tas-tdd/SKILL.md +123 -123
- package/.claude/skills/token-logger/SKILL.md +19 -19
- package/.tas/README.md +266 -1520
- package/.tas/checklists/code-review.md +13 -13
- package/.tas/checklists/security.md +3 -3
- package/.tas/checklists/story-done.md +11 -11
- package/.tas/hooks/README.md +138 -0
- package/.tas/hooks/pre-commit +26 -0
- package/.tas/hooks/security-scan.js +599 -0
- package/.tas/project-status-example.yaml +3 -3
- package/.tas/tas-example.yaml +25 -8
- package/.tas/templates/ADR.md +16 -16
- package/.tas/templates/API-Test-Spec.md +3 -3
- package/.tas/templates/Bug.md +12 -12
- package/.tas/templates/Design-Spec.md +8 -8
- package/.tas/templates/E2E-Execution-Report.md +1 -1
- package/.tas/templates/Epic.md +1 -1
- package/.tas/templates/Feature.md +10 -10
- package/.tas/templates/Func-Test-Spec.md +3 -3
- package/.tas/templates/SAD.md +106 -106
- package/.tas/templates/Security-Report.md +3 -3
- package/.tas/templates/Story.md +9 -9
- package/.tas/tools/tas-ado-readme.md +169 -169
- package/.tas/tools/tas-ado.py +1 -1
- package/CLAUDE-Example.md +37 -58
- package/README.md +294 -42
- package/bin/cli.js +24 -7
- package/lib/install.js +161 -47
- package/package.json +1 -1
package/lib/install.js
CHANGED
|
@@ -31,16 +31,21 @@ async function removeDeletedFiles(target, deletedFiles) {
|
|
|
31
31
|
return { removed, skipped };
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
async function
|
|
34
|
+
async function ask(question, defaultValue = '') {
|
|
35
35
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
36
36
|
return new Promise((resolve) => {
|
|
37
|
-
rl.question(
|
|
37
|
+
rl.question(question, (answer) => {
|
|
38
38
|
rl.close();
|
|
39
|
-
resolve(answer
|
|
39
|
+
resolve((answer || '').trim() || defaultValue);
|
|
40
40
|
});
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
async function confirm(question) {
|
|
45
|
+
const answer = await ask(`${question} [y/N] `);
|
|
46
|
+
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
47
|
+
}
|
|
48
|
+
|
|
44
49
|
async function exists(p) {
|
|
45
50
|
return fs.access(p).then(() => true).catch(() => false);
|
|
46
51
|
}
|
|
@@ -49,10 +54,131 @@ async function copyDir(src, dest) {
|
|
|
49
54
|
await fs.cp(src, dest, { recursive: true });
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
|
|
57
|
+
// ─── Security hook wiring ────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
async function chooseSecurityHookMode({ target, yes, forced }) {
|
|
60
|
+
if (forced) return forced;
|
|
61
|
+
if (yes) return 'native';
|
|
62
|
+
|
|
63
|
+
const hasPackageJson = await exists(path.join(target, 'package.json'));
|
|
64
|
+
const hint = hasPackageJson
|
|
65
|
+
? ' Detected package.json — husky mode is available.'
|
|
66
|
+
: ' No package.json — husky mode will add one or fall back to native.';
|
|
67
|
+
|
|
68
|
+
console.log('\n Pre-commit security hook wiring:');
|
|
69
|
+
console.log(hint);
|
|
70
|
+
console.log(' [1] husky — shared via git, requires Node project');
|
|
71
|
+
console.log(' [2] native — plain .git/hooks/pre-commit, any stack');
|
|
72
|
+
console.log(' [3] skip — wire it later with "tas-kit install --security-hook=..."');
|
|
73
|
+
const answer = await ask(' Choose [1/2/3] (default: 2): ', '2');
|
|
74
|
+
if (answer === '1') return 'husky';
|
|
75
|
+
if (answer === '3') return 'none';
|
|
76
|
+
return 'native';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function installSecurityHookNative({ target }) {
|
|
80
|
+
const gitDir = path.join(target, '.git');
|
|
81
|
+
if (!(await exists(gitDir))) {
|
|
82
|
+
console.warn(' [skip] Security hook (native): .git/ not found — run `git init` first, then re-run installer with --security-hook=native');
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
87
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
88
|
+
|
|
89
|
+
const src = path.join(PACKAGE_DIR, '.tas', 'hooks', 'pre-commit');
|
|
90
|
+
const dest = path.join(hooksDir, 'pre-commit');
|
|
91
|
+
|
|
92
|
+
if (await exists(dest)) {
|
|
93
|
+
const existing = await fs.readFile(dest, 'utf8').catch(() => '');
|
|
94
|
+
if (!existing.includes('TAS Kit')) {
|
|
95
|
+
const backup = dest + '.backup';
|
|
96
|
+
await fs.copyFile(dest, backup);
|
|
97
|
+
console.log(' [ok] .git/hooks/pre-commit.backup (preserved existing hook)');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
await fs.copyFile(src, dest);
|
|
102
|
+
if (process.platform !== 'win32') {
|
|
103
|
+
await fs.chmod(dest, 0o755);
|
|
104
|
+
}
|
|
105
|
+
console.log(' [ok] .git/hooks/pre-commit (security scan wired — native)');
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function installSecurityHookHusky({ target }) {
|
|
110
|
+
const pkgPath = path.join(target, 'package.json');
|
|
111
|
+
const hasPackageJson = await exists(pkgPath);
|
|
112
|
+
|
|
113
|
+
if (!hasPackageJson) {
|
|
114
|
+
console.warn(' [warn] Security hook (husky): no package.json — falling back to native mode');
|
|
115
|
+
return await installSecurityHookNative({ target });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Read + update package.json (add prepare script + husky devDep)
|
|
119
|
+
let pkg;
|
|
120
|
+
try {
|
|
121
|
+
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
122
|
+
} catch (err) {
|
|
123
|
+
console.warn(` [warn] Security hook (husky): package.json unreadable (${err.message}) — falling back to native`);
|
|
124
|
+
return await installSecurityHookNative({ target });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
pkg.scripts = pkg.scripts || {};
|
|
128
|
+
if (!pkg.scripts.prepare) {
|
|
129
|
+
pkg.scripts.prepare = 'husky';
|
|
130
|
+
} else if (!/husky/.test(pkg.scripts.prepare)) {
|
|
131
|
+
pkg.scripts.prepare = `${pkg.scripts.prepare} && husky`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
135
|
+
if (!pkg.devDependencies.husky) {
|
|
136
|
+
pkg.devDependencies.husky = '^9.1.0';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
140
|
+
console.log(' [ok] package.json (husky devDep + prepare script)');
|
|
141
|
+
|
|
142
|
+
// Write .husky/pre-commit
|
|
143
|
+
const huskyDir = path.join(target, '.husky');
|
|
144
|
+
await fs.mkdir(huskyDir, { recursive: true });
|
|
145
|
+
|
|
146
|
+
const huskyHook = path.join(huskyDir, 'pre-commit');
|
|
147
|
+
const content = [
|
|
148
|
+
'# TAS Kit — husky pre-commit (delegates to .tas/hooks/pre-commit)',
|
|
149
|
+
'. "$(dirname -- "$0")/../.tas/hooks/pre-commit"',
|
|
150
|
+
'',
|
|
151
|
+
].join('\n');
|
|
152
|
+
await fs.writeFile(huskyHook, content);
|
|
153
|
+
if (process.platform !== 'win32') {
|
|
154
|
+
await fs.chmod(huskyHook, 0o755);
|
|
155
|
+
}
|
|
156
|
+
console.log(' [ok] .husky/pre-commit (delegates to .tas/hooks/pre-commit)');
|
|
157
|
+
console.log(' [--] Run `npm install` in the project to activate husky');
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function installSecurityHook({ target, mode }) {
|
|
162
|
+
if (mode === 'none' || !mode) {
|
|
163
|
+
console.log(' [skip] Security hook (you can enable later: tas-kit install --security-hook=native|husky)');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (mode === 'husky') {
|
|
167
|
+
await installSecurityHookHusky({ target });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (mode === 'native') {
|
|
171
|
+
await installSecurityHookNative({ target });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
console.warn(` [warn] Unknown security hook mode: "${mode}" — skipping`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ─── Update ──────────────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
export async function update({ directory, yes, securityHook }) {
|
|
53
180
|
const target = path.resolve(directory);
|
|
54
181
|
|
|
55
|
-
// Must already have .claude/ or .tas/ — otherwise suggest install
|
|
56
182
|
const claudeExists = await exists(path.join(target, '.claude'));
|
|
57
183
|
const tasExists = await exists(path.join(target, '.tas'));
|
|
58
184
|
if (!claudeExists && !tasExists) {
|
|
@@ -74,21 +200,12 @@ export async function update({ directory, yes }) {
|
|
|
74
200
|
console.log();
|
|
75
201
|
}
|
|
76
202
|
|
|
77
|
-
|
|
78
|
-
await copyDir(
|
|
79
|
-
path.join(PACKAGE_DIR, '.claude'),
|
|
80
|
-
path.join(target, '.claude')
|
|
81
|
-
);
|
|
203
|
+
await copyDir(path.join(PACKAGE_DIR, '.claude'), path.join(target, '.claude'));
|
|
82
204
|
console.log(' [ok] .claude/ (updated)');
|
|
83
205
|
|
|
84
|
-
|
|
85
|
-
await copyDir(
|
|
86
|
-
path.join(PACKAGE_DIR, '.tas'),
|
|
87
|
-
path.join(target, '.tas')
|
|
88
|
-
);
|
|
206
|
+
await copyDir(path.join(PACKAGE_DIR, '.tas'), path.join(target, '.tas'));
|
|
89
207
|
console.log(' [ok] .tas/ (updated)');
|
|
90
208
|
|
|
91
|
-
// Remove files deleted from the kit in previous versions
|
|
92
209
|
const deletedFiles = await getDeletedFiles();
|
|
93
210
|
if (deletedFiles.length > 0) {
|
|
94
211
|
const { removed } = await removeDeletedFiles(target, deletedFiles);
|
|
@@ -99,12 +216,20 @@ export async function update({ directory, yes }) {
|
|
|
99
216
|
}
|
|
100
217
|
}
|
|
101
218
|
|
|
102
|
-
// Set executable bit on tas-ado.py (Unix/macOS)
|
|
103
219
|
if (process.platform !== 'win32') {
|
|
104
220
|
const adoPy = path.join(target, '.tas', 'tools', 'tas-ado.py');
|
|
105
221
|
if (await exists(adoPy)) {
|
|
106
222
|
await fs.chmod(adoPy, 0o755);
|
|
107
223
|
}
|
|
224
|
+
const preCommit = path.join(target, '.tas', 'hooks', 'pre-commit');
|
|
225
|
+
if (await exists(preCommit)) {
|
|
226
|
+
await fs.chmod(preCommit, 0o755);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Re-wire hook only if user explicitly asked via flag
|
|
231
|
+
if (securityHook) {
|
|
232
|
+
await installSecurityHook({ target, mode: securityHook });
|
|
108
233
|
}
|
|
109
234
|
|
|
110
235
|
console.log(` [--] CLAUDE.md, tas.yaml, .env.example — not touched`);
|
|
@@ -117,15 +242,15 @@ and manually merge changes into your CLAUDE.md and tas.yaml if needed.
|
|
|
117
242
|
`);
|
|
118
243
|
}
|
|
119
244
|
|
|
120
|
-
|
|
245
|
+
// ─── Install ─────────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
export async function install({ directory, yes, securityHook }) {
|
|
121
248
|
const target = path.resolve(directory);
|
|
122
249
|
|
|
123
|
-
// Ensure target directory exists
|
|
124
250
|
await fs.mkdir(target, { recursive: true });
|
|
125
251
|
|
|
126
252
|
console.log(`\nInstalling TAS Kit into: ${target}\n`);
|
|
127
253
|
|
|
128
|
-
// Warn if .claude/ or .tas/ already exist
|
|
129
254
|
const claudeExists = await exists(path.join(target, '.claude'));
|
|
130
255
|
const tasExists = await exists(path.join(target, '.tas'));
|
|
131
256
|
|
|
@@ -141,64 +266,51 @@ export async function install({ directory, yes }) {
|
|
|
141
266
|
console.log();
|
|
142
267
|
}
|
|
143
268
|
|
|
144
|
-
|
|
145
|
-
await copyDir(
|
|
146
|
-
path.join(PACKAGE_DIR, '.claude'),
|
|
147
|
-
path.join(target, '.claude')
|
|
148
|
-
);
|
|
269
|
+
await copyDir(path.join(PACKAGE_DIR, '.claude'), path.join(target, '.claude'));
|
|
149
270
|
console.log(' [ok] .claude/');
|
|
150
271
|
|
|
151
|
-
|
|
152
|
-
await copyDir(
|
|
153
|
-
path.join(PACKAGE_DIR, '.tas'),
|
|
154
|
-
path.join(target, '.tas')
|
|
155
|
-
);
|
|
272
|
+
await copyDir(path.join(PACKAGE_DIR, '.tas'), path.join(target, '.tas'));
|
|
156
273
|
console.log(' [ok] .tas/');
|
|
157
274
|
|
|
158
|
-
// Copy CLAUDE-Example.md as CLAUDE.md (only if absent)
|
|
159
275
|
const claudeMdTarget = path.join(target, 'CLAUDE.md');
|
|
160
276
|
if (!(await exists(claudeMdTarget))) {
|
|
161
|
-
await fs.copyFile(
|
|
162
|
-
path.join(PACKAGE_DIR, 'CLAUDE-Example.md'),
|
|
163
|
-
claudeMdTarget
|
|
164
|
-
);
|
|
277
|
+
await fs.copyFile(path.join(PACKAGE_DIR, 'CLAUDE-Example.md'), claudeMdTarget);
|
|
165
278
|
console.log(' [ok] CLAUDE.md (from CLAUDE-Example.md)');
|
|
166
279
|
} else {
|
|
167
280
|
console.log(' [--] CLAUDE.md already exists, skipped');
|
|
168
281
|
}
|
|
169
282
|
|
|
170
|
-
// Copy .env.example (only if absent)
|
|
171
283
|
const envExampleTarget = path.join(target, '.env.example');
|
|
172
284
|
if (!(await exists(envExampleTarget))) {
|
|
173
|
-
await fs.copyFile(
|
|
174
|
-
path.join(PACKAGE_DIR, '.env.example'),
|
|
175
|
-
envExampleTarget
|
|
176
|
-
);
|
|
285
|
+
await fs.copyFile(path.join(PACKAGE_DIR, '.env.example'), envExampleTarget);
|
|
177
286
|
console.log(' [ok] .env.example');
|
|
178
287
|
} else {
|
|
179
288
|
console.log(' [--] .env.example already exists, skipped');
|
|
180
289
|
}
|
|
181
290
|
|
|
182
|
-
// Copy tas-example.yaml as tas.yaml (only if absent)
|
|
183
291
|
const tasYamlTarget = path.join(target, 'tas.yaml');
|
|
184
292
|
if (!(await exists(tasYamlTarget))) {
|
|
185
|
-
await fs.copyFile(
|
|
186
|
-
path.join(PACKAGE_DIR, '.tas', 'tas-example.yaml'),
|
|
187
|
-
tasYamlTarget
|
|
188
|
-
);
|
|
293
|
+
await fs.copyFile(path.join(PACKAGE_DIR, '.tas', 'tas-example.yaml'), tasYamlTarget);
|
|
189
294
|
console.log(' [ok] tas.yaml (from .tas/tas-example.yaml)');
|
|
190
295
|
} else {
|
|
191
296
|
console.log(' [--] tas.yaml already exists, skipped');
|
|
192
297
|
}
|
|
193
298
|
|
|
194
|
-
// Set executable bit on tas-ado.py (Unix/macOS)
|
|
195
299
|
if (process.platform !== 'win32') {
|
|
196
300
|
const adoPy = path.join(target, '.tas', 'tools', 'tas-ado.py');
|
|
197
301
|
if (await exists(adoPy)) {
|
|
198
302
|
await fs.chmod(adoPy, 0o755);
|
|
199
303
|
}
|
|
304
|
+
const preCommit = path.join(target, '.tas', 'hooks', 'pre-commit');
|
|
305
|
+
if (await exists(preCommit)) {
|
|
306
|
+
await fs.chmod(preCommit, 0o755);
|
|
307
|
+
}
|
|
200
308
|
}
|
|
201
309
|
|
|
310
|
+
// ─── Wire security pre-commit hook ─────────────────────────────────────────
|
|
311
|
+
const mode = await chooseSecurityHookMode({ target, yes, forced: securityHook });
|
|
312
|
+
await installSecurityHook({ target, mode });
|
|
313
|
+
|
|
202
314
|
console.log(`
|
|
203
315
|
TAS Kit installed successfully!
|
|
204
316
|
|
|
@@ -208,6 +320,8 @@ Next steps:
|
|
|
208
320
|
3. Create .env — add AZURE_DEVOPS_PAT (see .env.example)
|
|
209
321
|
4. Open Claude Code — run /tas-init to initialize your project
|
|
210
322
|
|
|
211
|
-
Docs:
|
|
323
|
+
Docs:
|
|
324
|
+
.tas/README.md — kit overview
|
|
325
|
+
.tas/hooks/README.md — pre-commit security hook details
|
|
212
326
|
`);
|
|
213
327
|
}
|
package/package.json
CHANGED