@torus-engineering/tas-kit 1.10.0 → 1.12.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.
- package/.tas/README.md +70 -70
- package/{.claude → .tas/_platform/claude-code}/settings.json +0 -12
- package/{.claude → .tas/_platform}/hooks/code-quality.js +1 -1
- package/{.claude → .tas/_platform}/hooks/session-end.js +20 -25
- package/.tas/commands/ado-create.md +28 -0
- package/.tas/commands/ado-delete.md +22 -0
- package/.tas/commands/ado-get.md +20 -0
- package/.tas/commands/ado-status.md +18 -0
- package/.tas/commands/ado-update.md +27 -0
- package/.tas/commands/tas-adr.md +33 -0
- package/.tas/commands/tas-apitest-plan.md +173 -0
- package/.tas/commands/tas-apitest.md +143 -0
- package/.tas/commands/tas-brainstorm.md +19 -0
- package/.tas/commands/tas-bug.md +113 -0
- package/.tas/commands/tas-design.md +37 -0
- package/.tas/commands/tas-dev.md +125 -0
- package/{.claude → .tas}/commands/tas-e2e-mobile.md +155 -155
- package/{.claude → .tas}/commands/tas-e2e-web.md +163 -163
- package/.tas/commands/tas-e2e.md +102 -0
- package/.tas/commands/tas-epic.md +35 -0
- package/.tas/commands/tas-feature.md +47 -0
- package/.tas/commands/tas-fix.md +51 -0
- package/.tas/commands/tas-functest-mobile.md +144 -0
- package/{.claude → .tas}/commands/tas-functest-web.md +192 -192
- package/.tas/commands/tas-functest.md +76 -0
- package/.tas/commands/tas-init.md +17 -0
- package/.tas/commands/tas-plan.md +198 -0
- package/.tas/commands/tas-prd.md +37 -0
- package/.tas/commands/tas-review.md +113 -0
- package/.tas/commands/tas-sad.md +43 -0
- package/.tas/commands/tas-security.md +87 -0
- package/.tas/commands/tas-spec.md +50 -0
- package/.tas/commands/tas-status.md +16 -0
- package/.tas/commands/tas-story.md +91 -0
- package/.tas/platforms.json +5 -0
- package/.tas/project-status-example.yaml +17 -17
- package/.tas/rules/ado-integration.md +65 -0
- package/{.claude/skills/api-design/SKILL.md → .tas/rules/common/api-design.md} +517 -530
- package/{.claude → .tas}/rules/common/code-review.md +30 -6
- package/.tas/rules/common/post-implementation-review.md +51 -0
- package/{.claude → .tas}/rules/common/project-status.md +80 -80
- package/.tas/rules/common/stack-detection.md +29 -0
- package/.tas/rules/common/story-done.md +30 -0
- package/.tas/rules/common/tdd.md +89 -0
- package/{.claude → .tas}/rules/common/testing.md +3 -8
- package/.tas/rules/common/token-logging.md +36 -0
- package/{.claude → .tas}/rules/csharp/api-testing.md +20 -20
- package/{.claude → .tas}/rules/csharp/coding-style.md +0 -2
- package/{.claude → .tas}/rules/csharp/security.md +10 -0
- package/{.claude → .tas}/rules/python/coding-style.md +0 -2
- package/{.claude → .tas}/rules/typescript/coding-style.md +0 -2
- package/.tas/rules/typescript/patterns.md +142 -0
- package/.tas/rules/typescript/security.md +88 -0
- package/{.claude → .tas}/rules/typescript/testing.md +0 -4
- package/{.claude → .tas}/rules/web/coding-style.md +0 -2
- package/.tas/tas-example.yaml +10 -11
- package/.tas/templates/ADR.md +47 -47
- package/.tas/templates/AGENTS.md +37 -0
- package/.tas/templates/API-Test-Spec.md +3 -3
- package/.tas/templates/Bug.md +67 -67
- package/.tas/templates/Design-Spec.md +36 -36
- package/.tas/templates/E2E-Execution-Report.md +1 -1
- package/.tas/templates/Epic.md +46 -46
- 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 +27 -27
- package/.tas/templates/Story.md +9 -9
- package/.tas/tools/tas-ado-readme.md +68 -68
- package/.tas/tools/tas-ado.py +621 -621
- package/README.md +78 -78
- package/bin/cli.js +91 -73
- package/lib/adapters/antigravity.js +137 -0
- package/lib/adapters/claude-code.js +35 -0
- package/lib/adapters/codex.js +163 -0
- package/lib/adapters/cursor.js +80 -0
- package/lib/adapters/index.js +20 -0
- package/lib/adapters/utils.js +81 -0
- package/lib/deleted-files.json +99 -0
- package/lib/install.js +403 -327
- package/package.json +4 -3
- package/.claude/agents/code-reviewer.md +0 -41
- package/.claude/agents/e2e-runner.md +0 -61
- package/.claude/agents/planner.md +0 -82
- package/.claude/agents/tdd-guide.md +0 -84
- package/.claude/commands/ado-create.md +0 -27
- package/.claude/commands/ado-delete.md +0 -21
- package/.claude/commands/ado-get.md +0 -20
- package/.claude/commands/ado-status.md +0 -18
- package/.claude/commands/ado-update.md +0 -26
- package/.claude/commands/tas-adr.md +0 -33
- package/.claude/commands/tas-apitest-plan.md +0 -173
- package/.claude/commands/tas-apitest.md +0 -143
- package/.claude/commands/tas-brainstorm.md +0 -19
- package/.claude/commands/tas-bug.md +0 -113
- package/.claude/commands/tas-design.md +0 -37
- package/.claude/commands/tas-dev.md +0 -128
- package/.claude/commands/tas-e2e.md +0 -102
- package/.claude/commands/tas-epic.md +0 -35
- package/.claude/commands/tas-feature.md +0 -47
- package/.claude/commands/tas-fix.md +0 -51
- package/.claude/commands/tas-functest-mobile.md +0 -144
- package/.claude/commands/tas-functest.md +0 -76
- package/.claude/commands/tas-init.md +0 -17
- package/.claude/commands/tas-plan.md +0 -200
- package/.claude/commands/tas-prd.md +0 -37
- package/.claude/commands/tas-review.md +0 -111
- package/.claude/commands/tas-sad.md +0 -43
- package/.claude/commands/tas-security.md +0 -87
- package/.claude/commands/tas-spec.md +0 -50
- package/.claude/commands/tas-status.md +0 -16
- package/.claude/commands/tas-story.md +0 -91
- package/.claude/commands/tas-verify.md +0 -51
- package/.claude/rules/common/post-review-agent.md +0 -49
- package/.claude/rules/common/stack-detection.md +0 -29
- package/.claude/rules/common/token-logging.md +0 -27
- package/.claude/rules/typescript/patterns.md +0 -62
- package/.claude/rules/typescript/security.md +0 -28
- package/.claude/settings.local.json +0 -38
- package/.claude/skills/ado-integration/SKILL.md +0 -75
- package/.claude/skills/ai-regression-testing/SKILL.md +0 -364
- package/.claude/skills/architecture-decision-records/SKILL.md +0 -184
- package/.claude/skills/benchmark/SKILL.md +0 -98
- package/.claude/skills/browser-qa/SKILL.md +0 -92
- package/.claude/skills/canary-watch/SKILL.md +0 -104
- package/.claude/skills/js-backend-patterns/SKILL.md +0 -603
- package/.claude/skills/tas-conventions/SKILL.md +0 -65
- package/.claude/skills/tas-implementation-complete/SKILL.md +0 -99
- package/.claude/skills/tas-tdd/SKILL.md +0 -123
- package/.claude/skills/token-logger/SKILL.md +0 -19
- package/.tas/checklists/code-review.md +0 -29
- package/.tas/checklists/security.md +0 -21
- package/.tas/checklists/story-done.md +0 -23
- package/CLAUDE-Example.md +0 -61
- /package/{.claude → .tas}/agents/architect.md +0 -0
- /package/{.claude → .tas}/agents/aws-reviewer.md +0 -0
- /package/{.claude → .tas}/agents/build-resolver.md +0 -0
- /package/{.claude → .tas}/agents/code-explorer.md +0 -0
- /package/{.claude → .tas}/agents/csharp-reviewer.md +0 -0
- /package/{.claude → .tas}/agents/database-reviewer.md +0 -0
- /package/{.claude → .tas}/agents/doc-updater.md +0 -0
- /package/{.claude → .tas}/agents/python-reviewer.md +0 -0
- /package/{.claude → .tas}/agents/security-reviewer.md +0 -0
- /package/{.claude → .tas}/agents/typescript-reviewer.md +0 -0
- /package/{.claude → .tas}/rules/.gitkeep +0 -0
- /package/{.claude → .tas}/rules/common/hooks.md +0 -0
- /package/{.claude → .tas}/rules/common/patterns.md +0 -0
- /package/{.claude → .tas}/rules/common/security.md +0 -0
- /package/{.claude → .tas}/rules/csharp/hooks.md +0 -0
- /package/{.claude → .tas}/rules/csharp/patterns.md +0 -0
- /package/{.claude → .tas}/rules/csharp/testing.md +0 -0
- /package/{.claude → .tas}/rules/python/hooks.md +0 -0
- /package/{.claude → .tas}/rules/python/patterns.md +0 -0
- /package/{.claude → .tas}/rules/python/security.md +0 -0
- /package/{.claude → .tas}/rules/python/testing.md +0 -0
- /package/{.claude → .tas}/rules/typescript/hooks.md +0 -0
- /package/{.claude → .tas}/rules/web/design-quality.md +0 -0
- /package/{.claude → .tas}/rules/web/hooks.md +0 -0
- /package/{.claude → .tas}/rules/web/patterns.md +0 -0
- /package/{.claude → .tas}/rules/web/performance.md +0 -0
- /package/{.claude → .tas}/rules/web/security.md +0 -0
- /package/{.claude → .tas}/rules/web/testing.md +0 -0
package/lib/install.js
CHANGED
|
@@ -1,327 +1,403 @@
|
|
|
1
|
-
import fs from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import readline from 'node:readline';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.log('
|
|
72
|
-
console.log('
|
|
73
|
-
const answer = await ask(' Choose [1/2/3] (default:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
await
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
await fs.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (!
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
await
|
|
204
|
-
console.log(' [ok] .
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
console.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
console.log(`\
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
import { PLATFORMS, getPlatform } from './adapters/index.js';
|
|
7
|
+
|
|
8
|
+
const PACKAGE_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const TAS_SRC = path.join(PACKAGE_DIR, '.tas');
|
|
10
|
+
const PLATFORMS_FILE = '.tas/platforms.json';
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
|
|
13
|
+
async function getDeletedFiles() {
|
|
14
|
+
try {
|
|
15
|
+
const manifest = require('./deleted-files.json');
|
|
16
|
+
return Object.values(manifest).flat();
|
|
17
|
+
} catch {
|
|
18
|
+
return [];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function removeDeletedFiles(target, deletedFiles) {
|
|
23
|
+
const removed = [];
|
|
24
|
+
const skipped = [];
|
|
25
|
+
for (const relPath of deletedFiles) {
|
|
26
|
+
const fullPath = path.join(target, relPath);
|
|
27
|
+
try {
|
|
28
|
+
await fs.rm(fullPath, { force: false });
|
|
29
|
+
removed.push(relPath);
|
|
30
|
+
} catch {
|
|
31
|
+
skipped.push(relPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { removed, skipped };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function ask(question, defaultValue = '') {
|
|
38
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
rl.question(question, (answer) => {
|
|
41
|
+
rl.close();
|
|
42
|
+
resolve((answer || '').trim() || defaultValue);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function confirm(question) {
|
|
48
|
+
const answer = await ask(`${question} [y/N] `);
|
|
49
|
+
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function exists(p) {
|
|
53
|
+
return fs.access(p).then(() => true).catch(() => false);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function copyDir(src, dest) {
|
|
57
|
+
await fs.cp(src, dest, { recursive: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Platform selection ───────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
async function choosePlatforms({ yes, forced }) {
|
|
63
|
+
if (forced && forced.length > 0) return forced;
|
|
64
|
+
if (yes) return ['claude-code'];
|
|
65
|
+
|
|
66
|
+
console.log('\n Agentic Coding Platform(s) to install:');
|
|
67
|
+
PLATFORMS.forEach((p, i) => {
|
|
68
|
+
const def = p.id === 'claude-code' ? ' (default)' : '';
|
|
69
|
+
console.log(` [${i + 1}] ${p.label}${def}`);
|
|
70
|
+
});
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log(' Enter numbers comma-separated for multiple platforms.');
|
|
73
|
+
const answer = await ask(' Choose [1/2/3/4] (default: 1): ', '1');
|
|
74
|
+
|
|
75
|
+
const selected = [];
|
|
76
|
+
for (const part of answer.split(',')) {
|
|
77
|
+
const idx = parseInt(part.trim(), 10) - 1;
|
|
78
|
+
if (idx >= 0 && idx < PLATFORMS.length) {
|
|
79
|
+
const id = PLATFORMS[idx].id;
|
|
80
|
+
if (!selected.includes(id)) selected.push(id);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return selected.length > 0 ? selected : ['claude-code'];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function savePlatforms(target, platforms) {
|
|
87
|
+
const tasDir = path.join(target, '.tas');
|
|
88
|
+
await fs.mkdir(tasDir, { recursive: true });
|
|
89
|
+
await fs.writeFile(
|
|
90
|
+
path.join(target, PLATFORMS_FILE),
|
|
91
|
+
JSON.stringify({ platforms }, null, 2) + '\n'
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function loadPlatforms(target) {
|
|
96
|
+
try {
|
|
97
|
+
const data = JSON.parse(await fs.readFile(path.join(target, PLATFORMS_FILE), 'utf8'));
|
|
98
|
+
return Array.isArray(data.platforms) ? data.platforms : ['claude-code'];
|
|
99
|
+
} catch {
|
|
100
|
+
// fallback: detect from existing directories
|
|
101
|
+
const detected = [];
|
|
102
|
+
if (await exists(path.join(target, '.claude', 'commands'))) detected.push('claude-code');
|
|
103
|
+
if (await exists(path.join(target, '.cursor', 'agents'))) detected.push('cursor');
|
|
104
|
+
if (await exists(path.join(target, '.codex', 'skills'))) detected.push('codex');
|
|
105
|
+
if (await exists(path.join(target, '.agents', 'skills'))) detected.push('antigravity');
|
|
106
|
+
return detected.length > 0 ? detected : ['claude-code'];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function runPlatformAdapters(platforms, { tasDir, target }) {
|
|
111
|
+
for (const id of platforms) {
|
|
112
|
+
const platform = getPlatform(id);
|
|
113
|
+
if (!platform) {
|
|
114
|
+
console.warn(` [warn] Unknown platform "${id}" — skipping`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
console.log(`\n Platform: ${platform.label}`);
|
|
118
|
+
await platform.install({ tasDir, target });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─── Security hook wiring ────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
async function chooseSecurityHookMode({ target, yes, forced }) {
|
|
125
|
+
if (forced) return forced;
|
|
126
|
+
if (yes) return 'native';
|
|
127
|
+
|
|
128
|
+
const hasPackageJson = await exists(path.join(target, 'package.json'));
|
|
129
|
+
const hint = hasPackageJson
|
|
130
|
+
? ' Detected package.json — husky mode is available.'
|
|
131
|
+
: ' No package.json — husky mode will add one or fall back to native.';
|
|
132
|
+
|
|
133
|
+
console.log('\n Pre-commit security hook wiring:');
|
|
134
|
+
console.log(hint);
|
|
135
|
+
console.log(' [1] husky — shared via git, requires Node project');
|
|
136
|
+
console.log(' [2] native — plain .git/hooks/pre-commit, any stack');
|
|
137
|
+
console.log(' [3] skip — wire it later with "tas-kit install --security-hook=..."');
|
|
138
|
+
const answer = await ask(' Choose [1/2/3] (default: 2): ', '2');
|
|
139
|
+
if (answer === '1') return 'husky';
|
|
140
|
+
if (answer === '3') return 'none';
|
|
141
|
+
return 'native';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function installSecurityHookNative({ target }) {
|
|
145
|
+
const gitDir = path.join(target, '.git');
|
|
146
|
+
if (!(await exists(gitDir))) {
|
|
147
|
+
console.warn(' [skip] Security hook (native): .git/ not found — run `git init` first, then re-run installer with --security-hook=native');
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const hooksDir = path.join(gitDir, 'hooks');
|
|
152
|
+
await fs.mkdir(hooksDir, { recursive: true });
|
|
153
|
+
|
|
154
|
+
const src = path.join(target, '.tas', 'hooks', 'pre-commit');
|
|
155
|
+
const dest = path.join(hooksDir, 'pre-commit');
|
|
156
|
+
|
|
157
|
+
if (await exists(dest)) {
|
|
158
|
+
const existing = await fs.readFile(dest, 'utf8').catch(() => '');
|
|
159
|
+
if (!existing.includes('TAS Kit')) {
|
|
160
|
+
const backup = dest + '.backup';
|
|
161
|
+
await fs.copyFile(dest, backup);
|
|
162
|
+
console.log(' [ok] .git/hooks/pre-commit.backup (preserved existing hook)');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await fs.copyFile(src, dest);
|
|
167
|
+
if (process.platform !== 'win32') {
|
|
168
|
+
await fs.chmod(dest, 0o755);
|
|
169
|
+
}
|
|
170
|
+
console.log(' [ok] .git/hooks/pre-commit (security scan wired — native)');
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function installSecurityHookHusky({ target }) {
|
|
175
|
+
const pkgPath = path.join(target, 'package.json');
|
|
176
|
+
const hasPackageJson = await exists(pkgPath);
|
|
177
|
+
|
|
178
|
+
if (!hasPackageJson) {
|
|
179
|
+
console.warn(' [warn] Security hook (husky): no package.json — falling back to native mode');
|
|
180
|
+
return await installSecurityHookNative({ target });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
let pkg;
|
|
184
|
+
try {
|
|
185
|
+
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
186
|
+
} catch (err) {
|
|
187
|
+
console.warn(` [warn] Security hook (husky): package.json unreadable (${err.message}) — falling back to native`);
|
|
188
|
+
return await installSecurityHookNative({ target });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pkg.scripts = pkg.scripts || {};
|
|
192
|
+
if (!pkg.scripts.prepare) {
|
|
193
|
+
pkg.scripts.prepare = 'husky';
|
|
194
|
+
} else if (!/husky/.test(pkg.scripts.prepare)) {
|
|
195
|
+
pkg.scripts.prepare = `${pkg.scripts.prepare} && husky`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
pkg.devDependencies = pkg.devDependencies || {};
|
|
199
|
+
if (!pkg.devDependencies.husky) {
|
|
200
|
+
pkg.devDependencies.husky = '^9.1.0';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
204
|
+
console.log(' [ok] package.json (husky devDep + prepare script)');
|
|
205
|
+
|
|
206
|
+
const huskyDir = path.join(target, '.husky');
|
|
207
|
+
await fs.mkdir(huskyDir, { recursive: true });
|
|
208
|
+
|
|
209
|
+
const huskyHook = path.join(huskyDir, 'pre-commit');
|
|
210
|
+
const content = [
|
|
211
|
+
'# TAS Kit — husky pre-commit (delegates to .tas/hooks/pre-commit)',
|
|
212
|
+
'. "$(dirname -- "$0")/../.tas/hooks/pre-commit"',
|
|
213
|
+
'',
|
|
214
|
+
].join('\n');
|
|
215
|
+
await fs.writeFile(huskyHook, content);
|
|
216
|
+
if (process.platform !== 'win32') {
|
|
217
|
+
await fs.chmod(huskyHook, 0o755);
|
|
218
|
+
}
|
|
219
|
+
console.log(' [ok] .husky/pre-commit (delegates to .tas/hooks/pre-commit)');
|
|
220
|
+
console.log(' [--] Run `npm install` in the project to activate husky');
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function installSecurityHook({ target, mode }) {
|
|
225
|
+
if (mode === 'none' || !mode) {
|
|
226
|
+
console.log(' [skip] Security hook (you can enable later: tas-kit install --security-hook=native|husky)');
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (mode === 'husky') {
|
|
230
|
+
await installSecurityHookHusky({ target });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (mode === 'native') {
|
|
234
|
+
await installSecurityHookNative({ target });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
console.warn(` [warn] Unknown security hook mode: "${mode}" — skipping`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ─── Update ──────────────────────────────────────────────────────────────────
|
|
241
|
+
|
|
242
|
+
export async function update({ directory, yes, securityHook, platforms: forcedPlatforms }) {
|
|
243
|
+
const target = path.resolve(directory);
|
|
244
|
+
|
|
245
|
+
const tasExists = await exists(path.join(target, '.tas'));
|
|
246
|
+
if (!tasExists) {
|
|
247
|
+
console.error(` ERROR: No TAS Kit found in: ${target}`);
|
|
248
|
+
console.error(` Run "install" first to set up TAS Kit.`);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log(`\nUpdating TAS Kit in: ${target}\n`);
|
|
253
|
+
|
|
254
|
+
const platforms = forcedPlatforms && forcedPlatforms.length > 0
|
|
255
|
+
? forcedPlatforms
|
|
256
|
+
: await loadPlatforms(target);
|
|
257
|
+
|
|
258
|
+
const platformLabels = platforms.map(id => getPlatform(id)?.label || id).join(', ');
|
|
259
|
+
|
|
260
|
+
if (!yes) {
|
|
261
|
+
console.warn(` This will overwrite .tas/ and platform directories (${platformLabels}) with the latest kit files.`);
|
|
262
|
+
console.warn(` Your CLAUDE.md, tas.yaml, and .env.example will NOT be touched.\n`);
|
|
263
|
+
const ok = await confirm('Continue?');
|
|
264
|
+
if (!ok) {
|
|
265
|
+
console.log('Update cancelled.');
|
|
266
|
+
process.exit(0);
|
|
267
|
+
}
|
|
268
|
+
console.log();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const tasDest = path.join(target, '.tas');
|
|
272
|
+
if (path.resolve(TAS_SRC) !== path.resolve(tasDest)) {
|
|
273
|
+
await copyDir(TAS_SRC, tasDest);
|
|
274
|
+
}
|
|
275
|
+
console.log(' [ok] .tas/ (updated)');
|
|
276
|
+
|
|
277
|
+
const deletedFiles = await getDeletedFiles();
|
|
278
|
+
if (deletedFiles.length > 0) {
|
|
279
|
+
const { removed } = await removeDeletedFiles(target, deletedFiles);
|
|
280
|
+
for (const f of removed) {
|
|
281
|
+
console.log(` [rm] ${f} (removed in this version)`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
await runPlatformAdapters(platforms, { tasDir: path.join(target, '.tas'), target });
|
|
286
|
+
|
|
287
|
+
await savePlatforms(target, platforms);
|
|
288
|
+
|
|
289
|
+
if (process.platform !== 'win32') {
|
|
290
|
+
await chmodExecutables(target);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (securityHook) {
|
|
294
|
+
await installSecurityHook({ target, mode: securityHook });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
console.log(` [--] CLAUDE.md, tas.yaml, .env.example — not touched`);
|
|
298
|
+
console.log(`
|
|
299
|
+
TAS Kit updated successfully!
|
|
300
|
+
|
|
301
|
+
If this version added new settings or templates, check the changelog
|
|
302
|
+
and manually merge changes into your CLAUDE.md and tas.yaml if needed.
|
|
303
|
+
`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ─── Install ─────────────────────────────────────────────────────────────────
|
|
307
|
+
|
|
308
|
+
export async function install({ directory, yes, securityHook, platforms: forcedPlatforms }) {
|
|
309
|
+
const target = path.resolve(directory);
|
|
310
|
+
|
|
311
|
+
await fs.mkdir(target, { recursive: true });
|
|
312
|
+
|
|
313
|
+
console.log(`\nInstalling TAS Kit into: ${target}\n`);
|
|
314
|
+
|
|
315
|
+
const tasExists = await exists(path.join(target, '.tas'));
|
|
316
|
+
if (tasExists && !yes) {
|
|
317
|
+
console.warn(` WARNING: .tas/ already exists in target directory.`);
|
|
318
|
+
console.warn(` Existing files with the same names will be overwritten.\n`);
|
|
319
|
+
const ok = await confirm('Continue?');
|
|
320
|
+
if (!ok) {
|
|
321
|
+
console.log('Installation cancelled.');
|
|
322
|
+
process.exit(0);
|
|
323
|
+
}
|
|
324
|
+
console.log();
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Copy .tas/ (skip if installing into the package itself)
|
|
328
|
+
const tasDest = path.join(target, '.tas');
|
|
329
|
+
if (path.resolve(TAS_SRC) !== path.resolve(tasDest)) {
|
|
330
|
+
await copyDir(TAS_SRC, tasDest);
|
|
331
|
+
console.log(' [ok] .tas/');
|
|
332
|
+
} else {
|
|
333
|
+
console.log(' [--] .tas/ (source = destination, skipped)');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Platform selection
|
|
337
|
+
const platforms = await choosePlatforms({ yes, forced: forcedPlatforms });
|
|
338
|
+
await runPlatformAdapters(platforms, { tasDir: path.join(target, '.tas'), target });
|
|
339
|
+
await savePlatforms(target, platforms);
|
|
340
|
+
|
|
341
|
+
// Project config files (first-time only)
|
|
342
|
+
const claudeMdTarget = path.join(target, 'CLAUDE.md');
|
|
343
|
+
if (!(await exists(claudeMdTarget))) {
|
|
344
|
+
await fs.copyFile(path.join(PACKAGE_DIR, '.tas', 'templates', 'AGENTS.md'), claudeMdTarget);
|
|
345
|
+
console.log(' [ok] CLAUDE.md (from .tas/templates/AGENTS.md)');
|
|
346
|
+
} else {
|
|
347
|
+
console.log(' [--] CLAUDE.md already exists, skipped');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const envExampleTarget = path.join(target, '.env.example');
|
|
351
|
+
if (!(await exists(envExampleTarget))) {
|
|
352
|
+
await fs.copyFile(path.join(PACKAGE_DIR, '.env.example'), envExampleTarget);
|
|
353
|
+
console.log(' [ok] .env.example');
|
|
354
|
+
} else {
|
|
355
|
+
console.log(' [--] .env.example already exists, skipped');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const tasYamlTarget = path.join(target, 'tas.yaml');
|
|
359
|
+
if (!(await exists(tasYamlTarget))) {
|
|
360
|
+
await fs.copyFile(path.join(PACKAGE_DIR, '.tas', 'tas-example.yaml'), tasYamlTarget);
|
|
361
|
+
console.log(' [ok] tas.yaml (from .tas/tas-example.yaml)');
|
|
362
|
+
} else {
|
|
363
|
+
console.log(' [--] tas.yaml already exists, skipped');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (process.platform !== 'win32') {
|
|
367
|
+
await chmodExecutables(target);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const mode = await chooseSecurityHookMode({ target, yes, forced: securityHook });
|
|
371
|
+
await installSecurityHook({ target, mode });
|
|
372
|
+
|
|
373
|
+
const platformLabels = platforms.map(id => getPlatform(id)?.label || id).join(', ');
|
|
374
|
+
console.log(`
|
|
375
|
+
TAS Kit installed successfully! (Platforms: ${platformLabels})
|
|
376
|
+
|
|
377
|
+
Next steps:
|
|
378
|
+
1. Edit CLAUDE.md — add your project's tech stack and conventions
|
|
379
|
+
2. Edit tas.yaml — set project name, team and ADO config
|
|
380
|
+
3. Create .env — add AZURE_DEVOPS_PAT (see .env.example)
|
|
381
|
+
4. Open your IDE — run /tas-init to initialize your project
|
|
382
|
+
|
|
383
|
+
Docs:
|
|
384
|
+
.tas/README.md — kit overview
|
|
385
|
+
.tas/hooks/README.md — pre-commit security hook details
|
|
386
|
+
`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
390
|
+
|
|
391
|
+
async function chmodExecutables(target) {
|
|
392
|
+
const adoPy = path.join(target, '.tas', 'tools', 'tas-ado.py');
|
|
393
|
+
if (await exists(adoPy)) await fs.chmod(adoPy, 0o755);
|
|
394
|
+
|
|
395
|
+
const preCommit = path.join(target, '.tas', 'hooks', 'pre-commit');
|
|
396
|
+
if (await exists(preCommit)) await fs.chmod(preCommit, 0o755);
|
|
397
|
+
|
|
398
|
+
const hooksDir = path.join(target, '.tas', '_platform', 'hooks');
|
|
399
|
+
if (await exists(hooksDir)) {
|
|
400
|
+
const files = await fs.readdir(hooksDir);
|
|
401
|
+
for (const f of files) await fs.chmod(path.join(hooksDir, f), 0o755);
|
|
402
|
+
}
|
|
403
|
+
}
|