ts-reviewer 1.0.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/README.md +202 -0
- package/dist/cli.js +57 -0
- package/dist/cli.js.map +1 -0
- package/dist/hash.js +11 -0
- package/dist/hash.js.map +1 -0
- package/dist/incoming.js +34 -0
- package/dist/incoming.js.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/io.js +39 -0
- package/dist/io.js.map +1 -0
- package/dist/meta.js +27 -0
- package/dist/meta.js.map +1 -0
- package/dist/paths.js +57 -0
- package/dist/paths.js.map +1 -0
- package/dist/prompt.js +64 -0
- package/dist/prompt.js.map +1 -0
- package/dist/updatePolicy.js +18 -0
- package/dist/updatePolicy.js.map +1 -0
- package/package.json +39 -0
- package/ts-reviewer/SKILL.md +351 -0
- package/ts-reviewer/references/async-patterns.md +64 -0
- package/ts-reviewer/references/code-quality.md +74 -0
- package/ts-reviewer/references/fix-workflow.md +358 -0
- package/ts-reviewer/references/modernization.md +94 -0
- package/ts-reviewer/references/security.md +75 -0
- package/ts-reviewer/references/tsconfig.md +59 -0
- package/ts-reviewer/references/type-safety.md +101 -0
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Pure TypeScript Reviewer
|
|
2
|
+
|
|
3
|
+
An AI skill for deep code review and auto-fix of pure TypeScript codebases. Finds bugs, type safety holes, security vulnerabilities, async anti-patterns, outdated practices, and code smells — then fixes them with regression tests and verification.
|
|
4
|
+
|
|
5
|
+
Built for **TypeScript 5.9+** without any framework-specific checks (no React, Vue, Angular, etc.).
|
|
6
|
+
|
|
7
|
+
## What It Does
|
|
8
|
+
|
|
9
|
+
Three modes, one skill:
|
|
10
|
+
|
|
11
|
+
| Mode | What happens |
|
|
12
|
+
|---|---|
|
|
13
|
+
| **scan** | Analyzes the codebase and writes a prioritized report to `code-smells.md` |
|
|
14
|
+
| **fix** | Reads the report and applies fixes file-by-file with tsc/lint/test verification |
|
|
15
|
+
| **auto** | Runs scan, asks you to confirm, fixes everything, deletes the report if clean |
|
|
16
|
+
|
|
17
|
+
The review covers six domains, each with its own detailed checklist:
|
|
18
|
+
|
|
19
|
+
| Domain | Examples |
|
|
20
|
+
|---|---|
|
|
21
|
+
| **Type Safety** | `any` abuse, unsafe casts, non-null assertions, missing exhaustive checks |
|
|
22
|
+
| **Security** | Injection, prototype pollution, ReDoS, path traversal, hardcoded secrets |
|
|
23
|
+
| **Async Patterns** | Floating promises, race conditions, missing error propagation, `forEach(async...)` |
|
|
24
|
+
| **Modernization** | `enum` → `as const`, missing `satisfies`, `using` keyword, `import type` |
|
|
25
|
+
| **Code Quality** | Dead code, complexity, duplication, hacky patterns, error handling |
|
|
26
|
+
| **Config** | tsconfig.json strict flags, module resolution, deprecated options |
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
### Install with npx
|
|
31
|
+
|
|
32
|
+
From the root of the project where you want to install the skill:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx ts-reviewer
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The installer prints a short summary before installation:
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
TypeScript Code Reviewer
|
|
42
|
+
Checks: type safety, async patterns, security, tsconfig, modernization, code quality
|
|
43
|
+
Target TypeScript: 5.9+
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then it asks which AI agents to install for. Use Up/Down arrows to move, Space to toggle, and Enter to confirm.
|
|
47
|
+
|
|
48
|
+
Supported targets:
|
|
49
|
+
|
|
50
|
+
| AI agent | Install path |
|
|
51
|
+
|---|---|
|
|
52
|
+
| Claude Code | `.claude/skills/ts-reviewer/` |
|
|
53
|
+
| Codex | `.codex/skills/ts-reviewer/` |
|
|
54
|
+
| Antigravity | `.agent/skills/ts-reviewer/` |
|
|
55
|
+
|
|
56
|
+
In non-interactive terminals, the installer selects all supported targets.
|
|
57
|
+
|
|
58
|
+
### Manual Install
|
|
59
|
+
|
|
60
|
+
You can still copy the `ts-reviewer/` folder directly into the skill directory for your AI agent.
|
|
61
|
+
|
|
62
|
+
## Usage
|
|
63
|
+
|
|
64
|
+
### Scan — find issues
|
|
65
|
+
|
|
66
|
+
Just ask Claude to review your code:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
Review my TypeScript code
|
|
70
|
+
```
|
|
71
|
+
```
|
|
72
|
+
Find issues in this project
|
|
73
|
+
```
|
|
74
|
+
```
|
|
75
|
+
Audit the codebase for security and type safety problems
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Claude will analyze the project and write a report to `code-smells.md` in the project root (or `.claude/code-smells.md` if that directory exists).
|
|
79
|
+
|
|
80
|
+
### Fix — apply fixes from the report
|
|
81
|
+
|
|
82
|
+
After reviewing the scan report, ask Claude to fix the issues:
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
Fix the issues from the report
|
|
86
|
+
```
|
|
87
|
+
```
|
|
88
|
+
Apply fixes from code-smells.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The fix workflow:
|
|
92
|
+
1. Parses the report as a work plan
|
|
93
|
+
2. Runs existing tests to capture a baseline (knows what was already failing)
|
|
94
|
+
3. Fixes issues file-by-file, writes regression tests, runs `tsc` after each file
|
|
95
|
+
4. Runs linter, fixes lint errors
|
|
96
|
+
5. Runs full test suite, compares with baseline, fixes any regressions it caused
|
|
97
|
+
6. Repeats verification up to 5 iterations
|
|
98
|
+
7. Updates the report: if all fixed → deletes `code-smells.md`; if some remain → keeps it as an audit trail with BEFORE/AFTER diffs for every fix
|
|
99
|
+
|
|
100
|
+
**Important:** fix never commits or stages anything. You review the changes and decide what to keep.
|
|
101
|
+
|
|
102
|
+
### Auto — scan + fix in one pass
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Review and fix my TypeScript code
|
|
106
|
+
```
|
|
107
|
+
```
|
|
108
|
+
Auto-fix code smells
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Runs scan, shows you the summary, asks if you want to proceed with fixes, then runs the full fix cycle. If everything is clean afterward, the report is deleted.
|
|
112
|
+
|
|
113
|
+
## Scope Modes
|
|
114
|
+
|
|
115
|
+
By default the entire codebase is reviewed. You can narrow the scope:
|
|
116
|
+
|
|
117
|
+
| What you say | What gets reviewed |
|
|
118
|
+
|---|---|
|
|
119
|
+
| *"review my code"* | Full codebase |
|
|
120
|
+
| *"review my changes"*, *"check uncommitted"* | Staged + unstaged + untracked `.ts` files |
|
|
121
|
+
| *"review my PR"*, *"diff against main"* | All changes on current branch vs base |
|
|
122
|
+
| *"review last commit"*, *"check last 3 commits"* | Last N commits |
|
|
123
|
+
|
|
124
|
+
### Diff-aware severity boost
|
|
125
|
+
|
|
126
|
+
In scoped modes, issues on **new/modified lines** get their severity boosted by one level (Low→Medium, Medium→High, etc.). A Medium code smell in a three-year-old file is tech debt; the same smell in code you wrote today should be fixed before merging.
|
|
127
|
+
|
|
128
|
+
Issues on unchanged lines are listed separately as pre-existing tech debt — informational, not blocking.
|
|
129
|
+
|
|
130
|
+
## Severity Scale
|
|
131
|
+
|
|
132
|
+
| Level | Meaning |
|
|
133
|
+
|---|---|
|
|
134
|
+
| **Highest** | Active bugs, security vulnerabilities, data loss risks |
|
|
135
|
+
| **High** | Bugs waiting to happen, will break under edge cases |
|
|
136
|
+
| **Medium** | Tech debt — clean up when you're already editing that file |
|
|
137
|
+
| **Low** | Style and conventions — improve when convenient |
|
|
138
|
+
|
|
139
|
+
## Project Structure
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
src/ # npm/npx installer source
|
|
143
|
+
├── cli.ts # CLI entrypoint and provider prompt
|
|
144
|
+
├── prompt.ts # raw-mode keyboard multi-select
|
|
145
|
+
└── paths.ts # target directories and skill asset loading
|
|
146
|
+
|
|
147
|
+
ts-reviewer/
|
|
148
|
+
├── SKILL.md # Main skill file — mode routing, workflow orchestration
|
|
149
|
+
└── references/
|
|
150
|
+
├── type-safety.md # Checklist: any, casts, !, exhaustiveness, generics
|
|
151
|
+
├── security.md # Checklist: injection, pollution, ReDoS, traversal
|
|
152
|
+
├── async-patterns.md # Checklist: floating promises, races, cancellation
|
|
153
|
+
├── modernization.md # Checklist: TS 5.9+ idioms, satisfies, using, as const
|
|
154
|
+
├── code-quality.md # Checklist: complexity, dead code, naming, duplication
|
|
155
|
+
├── tsconfig.md # Checklist: strict flags, module resolution, deprecated
|
|
156
|
+
└── fix-workflow.md # Complete fix protocol: tests, verification, rollback
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**SKILL.md** (349 lines) is the orchestrator — it routes between scan/fix/auto modes, defines scope detection, severity scale, and report format. It stays under the 500-line recommended limit for Claude skills.
|
|
160
|
+
|
|
161
|
+
**Reference files** contain the detailed checklists and protocols. Each analysis agent reads only the reference file relevant to its domain, keeping context focused. The fix workflow is in its own reference file because it's a complex multi-step protocol.
|
|
162
|
+
|
|
163
|
+
## How It Works Under the Hood
|
|
164
|
+
|
|
165
|
+
### Scan mode
|
|
166
|
+
|
|
167
|
+
1. **Discovery** — maps the project, reads tsconfig.json, detects linter and test runner
|
|
168
|
+
2. **Diagnostics** — runs `tsc --noEmit`, linter, and LSP diagnostics (if available)
|
|
169
|
+
3. **Analysis** — six specialized passes (sub-agents in Claude Code, sequential in Claude.ai), each with its own checklist
|
|
170
|
+
4. **Report** — deduplicates, applies severity boost (scoped modes), consolidates recurring patterns, writes `code-smells.md`
|
|
171
|
+
|
|
172
|
+
### Fix mode
|
|
173
|
+
|
|
174
|
+
1. Parses `code-smells.md` as the work plan
|
|
175
|
+
2. Captures test baseline (runs tests before changes)
|
|
176
|
+
3. Applies fixes bottom-to-top within each file (so line numbers don't shift)
|
|
177
|
+
4. Writes regression tests for each testable fix
|
|
178
|
+
5. Runs `tsc --noEmit` after each file
|
|
179
|
+
6. Full verification loop: tsc + linter + test suite (max 5 iterations)
|
|
180
|
+
7. Compares test results with baseline — only fixes regressions it caused
|
|
181
|
+
8. Updates or deletes the report
|
|
182
|
+
|
|
183
|
+
## Tips
|
|
184
|
+
|
|
185
|
+
- **Add `code-smells.md` to `.gitignore`** — it's a review artifact, not part of your source code.
|
|
186
|
+
|
|
187
|
+
- **Commit before running fix** — so you can `git diff` to review changes and `git checkout -- .` to revert if needed.
|
|
188
|
+
|
|
189
|
+
- **Edit the report before fix** — since fix uses `code-smells.md` as its work plan, you can delete issues you don't want fixed, change severities, or add notes before running fix.
|
|
190
|
+
|
|
191
|
+
- **Scoped review for PRs** — `"review my branch against main"` is the most practical mode for day-to-day use. Full codebase audits are better suited for periodic health checks.
|
|
192
|
+
|
|
193
|
+
## Requirements
|
|
194
|
+
|
|
195
|
+
- TypeScript 5.9+ project
|
|
196
|
+
- Git repository (for scoped modes and safe revert during fix)
|
|
197
|
+
- Node.js with `npx` available (for tsc, linter)
|
|
198
|
+
- Claude Code (recommended) or any Claude interface with skill support
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_1 = require("./index");
|
|
5
|
+
const prompt_1 = require("./prompt");
|
|
6
|
+
const paths_1 = require("./paths");
|
|
7
|
+
const SKILL_NAME = "ts-reviewer";
|
|
8
|
+
const TYPESCRIPT_VERSION = "5.9+";
|
|
9
|
+
function printHelp() {
|
|
10
|
+
process.stdout.write([
|
|
11
|
+
"ts-reviewer: install TypeScript review skill\n",
|
|
12
|
+
"\n",
|
|
13
|
+
"Usage:\n",
|
|
14
|
+
" npx ts-reviewer\n",
|
|
15
|
+
"\n",
|
|
16
|
+
"Options:\n",
|
|
17
|
+
" -h, --help show help\n",
|
|
18
|
+
].join(""));
|
|
19
|
+
}
|
|
20
|
+
async function main() {
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
if (args.includes("-h") || args.includes("--help")) {
|
|
23
|
+
printHelp();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const cwd = process.cwd();
|
|
27
|
+
const skillName = SKILL_NAME;
|
|
28
|
+
process.stdout.write([
|
|
29
|
+
"TypeScript Code Reviewer\n",
|
|
30
|
+
`Checks: type safety, async patterns, security, tsconfig, modernization, code quality\n`,
|
|
31
|
+
`Target TypeScript: ${TYPESCRIPT_VERSION}\n`,
|
|
32
|
+
"\n",
|
|
33
|
+
].join(""));
|
|
34
|
+
const selected = await (0, prompt_1.multiSelect)("Install for which AI agents? (Space = toggle, Enter = confirm)", paths_1.AI_PROVIDERS);
|
|
35
|
+
if (selected.length === 0) {
|
|
36
|
+
process.stdout.write("No agents selected. Exiting.\n");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
for (const provider of selected) {
|
|
40
|
+
const targetDir = (0, paths_1.skillTargetDir)(cwd, provider, skillName);
|
|
41
|
+
const providerLabel = paths_1.AI_PROVIDERS.find((p) => p.value === provider).label;
|
|
42
|
+
process.stdout.write(`\n[${providerLabel}]\n`);
|
|
43
|
+
const result = await (0, index_1.scaffoldTsReviewerSkill)({ cwd, skillName, targetDir });
|
|
44
|
+
for (const e of result.entries) {
|
|
45
|
+
const suffix = e.detail ? ` (${e.detail})` : "";
|
|
46
|
+
process.stdout.write(`${e.action}\t${e.relativePath}${suffix}\n`);
|
|
47
|
+
}
|
|
48
|
+
if (result.hadConflicts)
|
|
49
|
+
process.exitCode = 2;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
main().catch((err) => {
|
|
53
|
+
process.stderr.write(err?.stack ? String(err.stack) : String(err));
|
|
54
|
+
process.stderr.write("\n");
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AAEA,mCAAkD;AAClD,qCAAuC;AACvC,mCAAwE;AAExE,MAAM,UAAU,GAAG,aAAa,CAAC;AACjC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAElC,SAAS,SAAS;IAChB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,gDAAgD;QAChD,IAAI;QACJ,UAAU;QACV,qBAAqB;QACrB,IAAI;QACJ,YAAY;QACZ,+BAA+B;KAChC,CAAC,IAAI,CAAC,EAAE,CAAC,CACX,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnD,SAAS,EAAE,CAAC;QACZ,OAAO;IACT,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,SAAS,GAAG,UAAU,CAAC;IAE7B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB;QACE,4BAA4B;QAC5B,wFAAwF;QACxF,sBAAsB,kBAAkB,IAAI;QAC5C,IAAI;KACL,CAAC,IAAI,CAAC,EAAE,CAAC,CACX,CAAC;IAEF,MAAM,QAAQ,GAAG,MAAM,IAAA,oBAAW,EAChC,gEAAgE,EAChE,oBAAY,CACb,CAAC;IAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACvD,OAAO;IACT,CAAC;IAED,KAAK,MAAM,QAAQ,IAAI,QAAwB,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,IAAA,sBAAc,EAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;QAC3D,MAAM,aAAa,GAAG,oBAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAE,CAAC,KAAK,CAAC;QAE5E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,aAAa,KAAK,CAAC,CAAC;QAE/C,MAAM,MAAM,GAAG,MAAM,IAAA,+BAAuB,EAAC,EAAE,GAAG,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC;QAE5E,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,YAAY,GAAG,MAAM,IAAI,CAAC,CAAC;QACpE,CAAC;QAED,IAAI,MAAM,CAAC,YAAY;YAAE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAE,GAAa,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAE,GAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IACzF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sha256Hex = sha256Hex;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
function sha256Hex(input) {
|
|
9
|
+
return node_crypto_1.default.createHash("sha256").update(input, "utf8").digest("hex");
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=hash.js.map
|
package/dist/hash.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":";;;;;AAEA,8BAEC;AAJD,8DAAiC;AAEjC,SAAgB,SAAS,CAAC,KAAa;IACrC,OAAO,qBAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACzE,CAAC"}
|
package/dist/incoming.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findAvailableIncomingPath = findAvailableIncomingPath;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
async function exists(p) {
|
|
10
|
+
try {
|
|
11
|
+
await promises_1.default.stat(p);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
if (e && e.code === "ENOENT")
|
|
16
|
+
return false;
|
|
17
|
+
throw e;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function findAvailableIncomingPath(dstPath) {
|
|
21
|
+
const dir = node_path_1.default.dirname(dstPath);
|
|
22
|
+
const base = node_path_1.default.basename(dstPath);
|
|
23
|
+
const first = node_path_1.default.join(dir, `${base}.incoming`);
|
|
24
|
+
if (!(await exists(first)))
|
|
25
|
+
return first;
|
|
26
|
+
for (let i = 2; i < 1000; i++) {
|
|
27
|
+
const p = node_path_1.default.join(dir, `${base}.incoming${i}`);
|
|
28
|
+
if (!(await exists(p)))
|
|
29
|
+
return p;
|
|
30
|
+
}
|
|
31
|
+
// should never happen
|
|
32
|
+
return node_path_1.default.join(dir, `${base}.incoming-${Date.now()}`);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=incoming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incoming.js","sourceRoot":"","sources":["../src/incoming.ts"],"names":[],"mappings":";;;;;AAaA,8DAcC;AA3BD,gEAAkC;AAClC,0DAA6B;AAE7B,KAAK,UAAU,MAAM,CAAC,CAAS;IAC7B,IAAI,CAAC;QACH,MAAM,kBAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QAC3C,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,yBAAyB,CAAC,OAAe;IAC7D,MAAM,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,mBAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAEpC,MAAM,KAAK,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,WAAW,CAAC,CAAC;IACjD,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IAEzC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,YAAY,CAAC,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,CAAC,CAAC;IACnC,CAAC;IAED,sBAAsB;IACtB,OAAO,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,aAAa,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAC1D,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.scaffoldTsReviewerSkill = scaffoldTsReviewerSkill;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const paths_1 = require("./paths");
|
|
9
|
+
const meta_1 = require("./meta");
|
|
10
|
+
const hash_1 = require("./hash");
|
|
11
|
+
const io_1 = require("./io");
|
|
12
|
+
const updatePolicy_1 = require("./updatePolicy");
|
|
13
|
+
const incoming_1 = require("./incoming");
|
|
14
|
+
async function scaffoldTsReviewerSkill(opts) {
|
|
15
|
+
const skillName = opts.skillName;
|
|
16
|
+
const targetDir = opts.targetDir;
|
|
17
|
+
await (0, io_1.ensureDir)(targetDir);
|
|
18
|
+
const metaPath = node_path_1.default.join(targetDir, ".scaffold-meta.json");
|
|
19
|
+
const meta = (await (0, meta_1.readMeta)(metaPath)) ?? { version: 1, files: {} };
|
|
20
|
+
const entries = [];
|
|
21
|
+
let hadConflicts = false;
|
|
22
|
+
for (const t of (0, paths_1.listSkillAssetTemplates)(skillName)) {
|
|
23
|
+
const relativePath = node_path_1.default.relative(opts.cwd, node_path_1.default.join(targetDir, t.fileName)).replace(/\\/g, "/");
|
|
24
|
+
const dstPath = node_path_1.default.join(targetDir, t.fileName);
|
|
25
|
+
const desired = t.content;
|
|
26
|
+
const desiredHash = (0, hash_1.sha256Hex)(desired);
|
|
27
|
+
const existing = await (0, io_1.readFileIfExists)(dstPath);
|
|
28
|
+
const existingHash = existing === null ? null : (0, hash_1.sha256Hex)(existing);
|
|
29
|
+
const lastWrittenHash = meta.files[t.fileName]?.lastWrittenHash ?? null;
|
|
30
|
+
const decision = (0, updatePolicy_1.decideUpdate)({ existingHash, lastWrittenHash });
|
|
31
|
+
if (decision.kind === "write") {
|
|
32
|
+
await (0, io_1.atomicWriteFile)(dstPath, desired);
|
|
33
|
+
meta.files[t.fileName] = { lastWrittenHash: desiredHash };
|
|
34
|
+
entries.push({ action: "WROTE", relativePath });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (decision.kind === "overwrite") {
|
|
38
|
+
await (0, io_1.atomicWriteFile)(dstPath, desired);
|
|
39
|
+
meta.files[t.fileName] = { lastWrittenHash: desiredHash };
|
|
40
|
+
entries.push({ action: "UPDATED", relativePath });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (decision.kind === "skip") {
|
|
44
|
+
entries.push({ action: "SKIPPED", relativePath });
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// conflict
|
|
48
|
+
hadConflicts = true;
|
|
49
|
+
const incomingPath = await (0, incoming_1.findAvailableIncomingPath)(dstPath);
|
|
50
|
+
await (0, io_1.atomicWriteFile)(incomingPath, desired);
|
|
51
|
+
entries.push({
|
|
52
|
+
action: "CONFLICT",
|
|
53
|
+
relativePath,
|
|
54
|
+
detail: node_path_1.default.basename(incomingPath),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
await (0, meta_1.writeMeta)(metaPath, meta);
|
|
58
|
+
return { entries, hadConflicts };
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/io.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureDir = ensureDir;
|
|
7
|
+
exports.readFileIfExists = readFileIfExists;
|
|
8
|
+
exports.atomicWriteFile = atomicWriteFile;
|
|
9
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
async function ensureDir(dirPath) {
|
|
12
|
+
await promises_1.default.mkdir(dirPath, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
async function readFileIfExists(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
return await promises_1.default.readFile(filePath, "utf8");
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
if (e && (e.code === "ENOENT" || e.code === "ENOTDIR"))
|
|
20
|
+
return null;
|
|
21
|
+
throw e;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function atomicWriteFile(filePath, content) {
|
|
25
|
+
const dir = node_path_1.default.dirname(filePath);
|
|
26
|
+
await ensureDir(dir);
|
|
27
|
+
const tmp = node_path_1.default.join(dir, `.${node_path_1.default.basename(filePath)}.tmp-${process.pid}-${Date.now()}`);
|
|
28
|
+
await promises_1.default.writeFile(tmp, content, "utf8");
|
|
29
|
+
try {
|
|
30
|
+
// Windows cannot atomically replace existing; remove first.
|
|
31
|
+
await promises_1.default.unlink(filePath);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
if (!(e && e.code === "ENOENT"))
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
await promises_1.default.rename(tmp, filePath);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=io.js.map
|
package/dist/io.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io.js","sourceRoot":"","sources":["../src/io.ts"],"names":[],"mappings":";;;;;AAGA,8BAEC;AAED,4CAOC;AAED,0CAeC;AA/BD,gEAAkC;AAClC,0DAA6B;AAEtB,KAAK,UAAU,SAAS,CAAC,OAAe;IAC7C,MAAM,kBAAE,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC/C,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACrD,IAAI,CAAC;QACH,OAAO,MAAM,kBAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QACpE,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,eAAe,CAAC,QAAgB,EAAE,OAAe;IACrE,MAAM,GAAG,GAAG,mBAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IAErB,MAAM,GAAG,GAAG,mBAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,mBAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC3F,MAAM,kBAAE,CAAC,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAEzC,IAAI,CAAC;QACH,4DAA4D;QAC5D,MAAM,kBAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC;YAAE,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,kBAAE,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC"}
|
package/dist/meta.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readMeta = readMeta;
|
|
7
|
+
exports.writeMeta = writeMeta;
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
async function readMeta(metaPath) {
|
|
10
|
+
try {
|
|
11
|
+
const raw = await promises_1.default.readFile(metaPath, "utf8");
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (!parsed || parsed.version !== 1 || typeof parsed.files !== "object")
|
|
14
|
+
return null;
|
|
15
|
+
return parsed;
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
if (e && e.code === "ENOENT")
|
|
19
|
+
return null;
|
|
20
|
+
throw e;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function writeMeta(metaPath, meta) {
|
|
24
|
+
const body = JSON.stringify(meta, null, 2) + "\n";
|
|
25
|
+
await promises_1.default.writeFile(metaPath, body, "utf8");
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=meta.js.map
|
package/dist/meta.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta.js","sourceRoot":"","sources":["../src/meta.ts"],"names":[],"mappings":";;;;;AAOA,4BAUC;AAED,8BAGC;AAtBD,gEAAkC;AAO3B,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QACrF,OAAO,MAAsB,CAAC;IAChC,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC1C,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAkB;IAClE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IAClD,MAAM,kBAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;AAC7C,CAAC"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AI_PROVIDERS = void 0;
|
|
7
|
+
exports.skillTargetDir = skillTargetDir;
|
|
8
|
+
exports.skillAssetsDir = skillAssetsDir;
|
|
9
|
+
exports.listSkillAssetTemplates = listSkillAssetTemplates;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
exports.AI_PROVIDERS = [
|
|
13
|
+
{ label: "Claude Code", value: "claude-code" },
|
|
14
|
+
{ label: "Codex", value: "codex" },
|
|
15
|
+
{ label: "Antigravity", value: "antigravity" },
|
|
16
|
+
];
|
|
17
|
+
const PROVIDER_BASE = {
|
|
18
|
+
"claude-code": ".claude",
|
|
19
|
+
"codex": ".codex",
|
|
20
|
+
"antigravity": ".agent",
|
|
21
|
+
};
|
|
22
|
+
function skillTargetDir(projectRoot, provider, skillName) {
|
|
23
|
+
return node_path_1.default.join(projectRoot, PROVIDER_BASE[provider], "skills", skillName);
|
|
24
|
+
}
|
|
25
|
+
function packageRootDir() {
|
|
26
|
+
// dist/* at runtime; src/* in dev. In both cases, package root is one level up.
|
|
27
|
+
return node_path_1.default.resolve(__dirname, "..");
|
|
28
|
+
}
|
|
29
|
+
function skillAssetsDir(skillName) {
|
|
30
|
+
return node_path_1.default.join(packageRootDir(), skillName);
|
|
31
|
+
}
|
|
32
|
+
function readAsset(skillName, fileName) {
|
|
33
|
+
const p = node_path_1.default.join(skillAssetsDir(skillName), fileName);
|
|
34
|
+
return node_fs_1.default.readFileSync(p, "utf8");
|
|
35
|
+
}
|
|
36
|
+
function walkAssetFiles(rootDir, currentDir = rootDir) {
|
|
37
|
+
const entries = node_fs_1.default.readdirSync(currentDir, { withFileTypes: true });
|
|
38
|
+
const files = [];
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const absolutePath = node_path_1.default.join(currentDir, entry.name);
|
|
41
|
+
if (entry.isDirectory()) {
|
|
42
|
+
files.push(...walkAssetFiles(rootDir, absolutePath));
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (entry.isFile()) {
|
|
46
|
+
files.push(node_path_1.default.relative(rootDir, absolutePath).replace(/\\/g, "/"));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return files.sort();
|
|
50
|
+
}
|
|
51
|
+
function listSkillAssetTemplates(skillName) {
|
|
52
|
+
return walkAssetFiles(skillAssetsDir(skillName)).map((fileName) => ({
|
|
53
|
+
fileName,
|
|
54
|
+
content: readAsset(skillName, fileName),
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":";;;;;;AAiBA,wCAEC;AASD,wCAEC;AA0BD,0DAKC;AA7DD,sDAAyB;AACzB,0DAA6B;AAIhB,QAAA,YAAY,GAA2C;IAClE,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE;IAC9C,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE;IAClC,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,aAAa,EAAE;CAC/C,CAAC;AAEF,MAAM,aAAa,GAA+B;IAChD,aAAa,EAAE,SAAS;IACxB,OAAO,EAAE,QAAQ;IACjB,aAAa,EAAE,QAAQ;CACxB,CAAC;AAEF,SAAgB,cAAc,CAAC,WAAmB,EAAE,QAAoB,EAAE,SAAiB;IACzF,OAAO,mBAAI,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC9E,CAAC;AAID,SAAS,cAAc;IACrB,gFAAgF;IAChF,OAAO,mBAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC;AAED,SAAgB,cAAc,CAAC,SAAiB;IAC9C,OAAO,mBAAI,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,SAAS,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,SAAS,CAAC,SAAiB,EAAE,QAAgB;IACpD,MAAM,CAAC,GAAG,mBAAI,CAAC,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,CAAC;IACzD,OAAO,iBAAE,CAAC,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,cAAc,CAAC,OAAe,EAAE,UAAU,GAAG,OAAO;IAC3D,MAAM,OAAO,GAAG,iBAAE,CAAC,WAAW,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACpE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,YAAY,GAAG,mBAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,KAAK,CAAC,IAAI,CAAC,GAAG,cAAc,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;YACrD,SAAS;QACX,CAAC;QAED,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACnB,KAAK,CAAC,IAAI,CAAC,mBAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC;AACtB,CAAC;AAED,SAAgB,uBAAuB,CAAC,SAAiB;IACvD,OAAO,cAAc,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClE,QAAQ;QACR,OAAO,EAAE,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC;KACxC,CAAC,CAAC,CAAC;AACN,CAAC"}
|
package/dist/prompt.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.multiSelect = multiSelect;
|
|
7
|
+
const node_readline_1 = __importDefault(require("node:readline"));
|
|
8
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
9
|
+
function render(message, choices, cursor, selected, isFirst) {
|
|
10
|
+
if (!isFirst) {
|
|
11
|
+
node_process_1.default.stdout.write(`\x1b[${choices.length + 1}A`);
|
|
12
|
+
}
|
|
13
|
+
node_process_1.default.stdout.write(`\x1b[2K${message}\n`);
|
|
14
|
+
for (let i = 0; i < choices.length; i++) {
|
|
15
|
+
const mark = selected.has(i) ? "[x]" : "[ ]";
|
|
16
|
+
const arrow = i === cursor ? ">" : " ";
|
|
17
|
+
node_process_1.default.stdout.write(`\x1b[2K${arrow} ${mark} ${choices[i].label}\n`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function multiSelect(message, choices) {
|
|
21
|
+
if (!node_process_1.default.stdin.isTTY) {
|
|
22
|
+
return choices.map((c) => c.value);
|
|
23
|
+
}
|
|
24
|
+
const selected = new Set();
|
|
25
|
+
let cursor = 0;
|
|
26
|
+
node_readline_1.default.emitKeypressEvents(node_process_1.default.stdin);
|
|
27
|
+
node_process_1.default.stdin.setRawMode(true);
|
|
28
|
+
render(message, choices, cursor, selected, true);
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const onKey = (_str, key) => {
|
|
31
|
+
if (!key)
|
|
32
|
+
return;
|
|
33
|
+
if (key.name === "up") {
|
|
34
|
+
cursor = (cursor - 1 + choices.length) % choices.length;
|
|
35
|
+
render(message, choices, cursor, selected, false);
|
|
36
|
+
}
|
|
37
|
+
else if (key.name === "down") {
|
|
38
|
+
cursor = (cursor + 1) % choices.length;
|
|
39
|
+
render(message, choices, cursor, selected, false);
|
|
40
|
+
}
|
|
41
|
+
else if (key.name === "space") {
|
|
42
|
+
if (selected.has(cursor))
|
|
43
|
+
selected.delete(cursor);
|
|
44
|
+
else
|
|
45
|
+
selected.add(cursor);
|
|
46
|
+
render(message, choices, cursor, selected, false);
|
|
47
|
+
}
|
|
48
|
+
else if (key.name === "return") {
|
|
49
|
+
node_process_1.default.stdin.removeListener("keypress", onKey);
|
|
50
|
+
node_process_1.default.stdin.setRawMode(false);
|
|
51
|
+
node_process_1.default.stdin.pause();
|
|
52
|
+
resolve(choices.filter((_, i) => selected.has(i)).map((c) => c.value));
|
|
53
|
+
}
|
|
54
|
+
else if (key.ctrl && key.name === "c") {
|
|
55
|
+
node_process_1.default.stdin.removeListener("keypress", onKey);
|
|
56
|
+
node_process_1.default.stdin.setRawMode(false);
|
|
57
|
+
node_process_1.default.stdin.pause();
|
|
58
|
+
node_process_1.default.exit(0);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
node_process_1.default.stdin.on("keypress", onKey);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decideUpdate = decideUpdate;
|
|
4
|
+
// Inputs are hashes (sha256 hex) or null if missing.
|
|
5
|
+
function decideUpdate(opts) {
|
|
6
|
+
const { existingHash, lastWrittenHash } = opts;
|
|
7
|
+
// missing file
|
|
8
|
+
if (existingHash === null)
|
|
9
|
+
return { kind: "write" };
|
|
10
|
+
// file exists but we don't know provenance => never overwrite
|
|
11
|
+
if (lastWrittenHash === null)
|
|
12
|
+
return { kind: "conflict" };
|
|
13
|
+
// safe overwrite only if user hasn't modified since last scaffold
|
|
14
|
+
if (existingHash === lastWrittenHash)
|
|
15
|
+
return { kind: "overwrite" };
|
|
16
|
+
return { kind: "conflict" };
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=updatePolicy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updatePolicy.js","sourceRoot":"","sources":["../src/updatePolicy.ts"],"names":[],"mappings":";;AAOA,oCAgBC;AAjBD,qDAAqD;AACrD,SAAgB,YAAY,CAAC,IAG5B;IACC,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC;IAE/C,eAAe;IACf,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAEpD,8DAA8D;IAC9D,IAAI,eAAe,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAE1D,kEAAkE;IAClE,IAAI,YAAY,KAAK,eAAe;QAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAEnE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;AAC9B,CAAC"}
|