@simplysm/sd-claude 13.0.78 → 13.0.80
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/rules/sd-claude-rules.md +4 -63
- package/claude/rules/sd-simplysm-usage.md +7 -0
- package/claude/sd-session-start.sh +10 -0
- package/claude/skills/sd-api-review/SKILL.md +89 -0
- package/claude/skills/sd-check/SKILL.md +55 -57
- package/claude/skills/sd-commit/SKILL.md +37 -42
- package/claude/skills/sd-debug/SKILL.md +75 -265
- package/claude/skills/sd-document/SKILL.md +63 -53
- package/claude/skills/sd-document/_common.py +94 -0
- package/claude/skills/sd-document/extract_docx.py +19 -48
- package/claude/skills/sd-document/extract_pdf.py +22 -50
- package/claude/skills/sd-document/extract_pptx.py +17 -40
- package/claude/skills/sd-document/extract_xlsx.py +19 -40
- package/claude/skills/sd-email-analyze/SKILL.md +23 -31
- package/claude/skills/sd-email-analyze/email-analyzer.py +79 -65
- package/claude/skills/sd-init/SKILL.md +133 -0
- package/claude/skills/sd-plan/SKILL.md +69 -120
- package/claude/skills/sd-readme/SKILL.md +106 -131
- package/claude/skills/sd-review/SKILL.md +38 -155
- package/claude/skills/sd-simplify/SKILL.md +59 -0
- package/package.json +3 -2
- package/README.md +0 -297
- package/claude/refs/sd-angular.md +0 -127
- package/claude/refs/sd-code-conventions.md +0 -155
- package/claude/refs/sd-directories.md +0 -7
- package/claude/refs/sd-library-issue.md +0 -7
- package/claude/refs/sd-migration.md +0 -7
- package/claude/refs/sd-orm-v12.md +0 -81
- package/claude/refs/sd-orm.md +0 -23
- package/claude/refs/sd-service.md +0 -5
- package/claude/refs/sd-simplysm-docs.md +0 -52
- package/claude/refs/sd-solid.md +0 -68
- package/claude/refs/sd-workflow.md +0 -25
- package/claude/rules/sd-refs-linker.md +0 -52
- package/claude/sd-statusline.js +0 -296
- package/claude/skills/sd-api-name-review/SKILL.md +0 -154
- package/claude/skills/sd-brainstorm/SKILL.md +0 -215
- package/claude/skills/sd-debug/condition-based-waiting-example.ts +0 -158
- package/claude/skills/sd-debug/condition-based-waiting.md +0 -114
- package/claude/skills/sd-debug/defense-in-depth.md +0 -128
- package/claude/skills/sd-debug/find-polluter.sh +0 -64
- package/claude/skills/sd-debug/root-cause-tracing.md +0 -168
- package/claude/skills/sd-discuss/SKILL.md +0 -91
- package/claude/skills/sd-explore/SKILL.md +0 -118
- package/claude/skills/sd-plan-dev/SKILL.md +0 -294
- package/claude/skills/sd-plan-dev/code-quality-reviewer-prompt.md +0 -49
- package/claude/skills/sd-plan-dev/final-review-prompt.md +0 -50
- package/claude/skills/sd-plan-dev/implementer-prompt.md +0 -60
- package/claude/skills/sd-plan-dev/spec-reviewer-prompt.md +0 -45
- package/claude/skills/sd-review/api-reviewer-prompt.md +0 -75
- package/claude/skills/sd-review/code-reviewer-prompt.md +0 -82
- package/claude/skills/sd-review/convention-checker-prompt.md +0 -61
- package/claude/skills/sd-review/refactoring-analyzer-prompt.md +0 -92
- package/claude/skills/sd-skill/SKILL.md +0 -417
- package/claude/skills/sd-skill/anthropic-best-practices.md +0 -156
- package/claude/skills/sd-skill/cso-guide.md +0 -161
- package/claude/skills/sd-skill/examples/CLAUDE_MD_TESTING.md +0 -200
- package/claude/skills/sd-skill/persuasion-principles.md +0 -220
- package/claude/skills/sd-skill/testing-skills-with-subagents.md +0 -408
- package/claude/skills/sd-skill/writing-guide.md +0 -159
- package/claude/skills/sd-tdd/SKILL.md +0 -385
- package/claude/skills/sd-tdd/testing-anti-patterns.md +0 -317
- package/claude/skills/sd-use/SKILL.md +0 -67
- package/claude/skills/sd-worktree/SKILL.md +0 -78
package/README.md
DELETED
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
# @simplysm/sd-claude
|
|
2
|
-
|
|
3
|
-
Simplysm Claude Code CLI — asset installer and Claude account profile manager.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add @simplysm/sd-claude
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
On install, the `postinstall` script automatically copies Claude Code assets (`sd-*` entries) from the package's `claude/` directory into the project's `.claude/` directory and configures the status line in `.claude/settings.json`.
|
|
12
|
-
|
|
13
|
-
## CLI
|
|
14
|
-
|
|
15
|
-
The package provides the `sd-claude` binary.
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
sd-claude <command> [options]
|
|
19
|
-
sd-claude --help
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### `install`
|
|
23
|
-
|
|
24
|
-
Installs Claude Code assets to the project's `.claude/` directory. This is also run automatically via the `postinstall` script.
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
sd-claude install
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
**Behavior:**
|
|
31
|
-
|
|
32
|
-
- Locates the project root via `INIT_CWD` environment variable or by finding `node_modules` in the path.
|
|
33
|
-
- Copies all `sd-*` entries from the package's `claude/` directory to the project's `.claude/` directory.
|
|
34
|
-
- Removes any previously installed `sd-*` entries before copying (clean install).
|
|
35
|
-
- Adds a `statusLine` entry to `.claude/settings.json` if one is not already configured:
|
|
36
|
-
```json
|
|
37
|
-
{ "type": "command", "command": "node .claude/sd-statusline.js" }
|
|
38
|
-
```
|
|
39
|
-
- Skips installation when running inside the simplysm monorepo at the same major version.
|
|
40
|
-
- Errors during installation are suppressed (logged as warnings) to avoid blocking `pnpm install`.
|
|
41
|
-
|
|
42
|
-
### `auth`
|
|
43
|
-
|
|
44
|
-
Manages Claude account profiles. Profiles are stored in `~/.sd-claude/auth/<name>/`.
|
|
45
|
-
|
|
46
|
-
Each profile stores:
|
|
47
|
-
- `auth.json` — `oauthAccount` and `userID` from `~/.claude.json`
|
|
48
|
-
- `credentials.json` — full contents of `~/.claude/.credentials.json`
|
|
49
|
-
|
|
50
|
-
#### `auth add <name>`
|
|
51
|
-
|
|
52
|
-
Saves the currently logged-in Claude account as a named profile.
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
sd-claude auth add <name>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
- `<name>`: Profile name. Must match `[a-z0-9_-]+`.
|
|
59
|
-
- Reads the current auth state from `~/.claude.json` and `~/.claude/.credentials.json`.
|
|
60
|
-
- Fails if the profile name already exists. Remove it first with `auth remove`.
|
|
61
|
-
- Requires an active Claude login. Run `/login` in Claude Code before saving.
|
|
62
|
-
|
|
63
|
-
**Example:**
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
sd-claude auth add personal
|
|
67
|
-
sd-claude auth add work
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
#### `auth use <name>`
|
|
71
|
-
|
|
72
|
-
Switches to a saved Claude account profile.
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
sd-claude auth use <name>
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
- Reads the saved `auth.json` and `credentials.json` from `~/.sd-claude/auth/<name>/`.
|
|
79
|
-
- Updates `~/.claude.json` (only `oauthAccount` and `userID` fields; other fields are preserved).
|
|
80
|
-
- Replaces `~/.claude/.credentials.json` entirely with the saved credentials.
|
|
81
|
-
- Warns if the saved token has expired (run `/login` after switching to refresh).
|
|
82
|
-
|
|
83
|
-
**Example:**
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
sd-claude auth use work
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
#### `auth list`
|
|
90
|
-
|
|
91
|
-
Displays all saved profiles with their active status, email, token expiry, and usage.
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
sd-claude auth list
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
Output format per profile:
|
|
98
|
-
|
|
99
|
-
```
|
|
100
|
-
* <name> (<email>) expires: YYYY-MM-DD │ 5h: <pct>(<remaining>) │ 7d: <pct>(<remaining>)
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
- `*` marks the currently active profile (matched by `userID`); inactive profiles show a space.
|
|
104
|
-
- Usage is fetched live from `https://api.anthropic.com/api/oauth/usage` using the profile's OAuth token (5-second timeout). Shows `?` when unavailable or when the token is expired.
|
|
105
|
-
- Usage shows the `daily` window (falling back to `five_hour`) as the `5h` column, and `seven_day` as the `7d` column.
|
|
106
|
-
- Profiles are sorted alphabetically.
|
|
107
|
-
|
|
108
|
-
**Example output:**
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
* work (work@company.com) expires: 2025-12-31 │ 5h: 42%(3h15m) │ 7d: 18%(2d4h)
|
|
112
|
-
personal (personal@gmail.com) expires: 2025-11-01 │ 5h: ? │ 7d: ?
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
#### `auth remove <name>`
|
|
116
|
-
|
|
117
|
-
Removes a saved Claude account profile.
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
sd-claude auth remove <name>
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
- Deletes `~/.sd-claude/auth/<name>/` and all its contents.
|
|
124
|
-
- Warns if the profile being removed is the currently active account (the active session in Claude Code is not affected).
|
|
125
|
-
|
|
126
|
-
**Example:**
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
sd-claude auth remove personal
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
## Programmatic API
|
|
133
|
-
|
|
134
|
-
All commands are also exported as functions for use in Node.js scripts.
|
|
135
|
-
|
|
136
|
-
```ts
|
|
137
|
-
import {
|
|
138
|
-
runInstall,
|
|
139
|
-
runAuthAdd,
|
|
140
|
-
runAuthUse,
|
|
141
|
-
runAuthList,
|
|
142
|
-
runAuthRemove,
|
|
143
|
-
validateName,
|
|
144
|
-
getProfileDir,
|
|
145
|
-
profileExists,
|
|
146
|
-
listProfiles,
|
|
147
|
-
readCurrentAuth,
|
|
148
|
-
readCurrentCredentials,
|
|
149
|
-
getCurrentUserID,
|
|
150
|
-
} from "@simplysm/sd-claude";
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### `runInstall(): void`
|
|
154
|
-
|
|
155
|
-
Runs the install command programmatically. See [`install`](#install) for behavior details.
|
|
156
|
-
|
|
157
|
-
```ts
|
|
158
|
-
import { runInstall } from "@simplysm/sd-claude";
|
|
159
|
-
|
|
160
|
-
runInstall();
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
### `runAuthAdd(name: string, homeDir?: string): void`
|
|
164
|
-
|
|
165
|
-
Saves the currently logged-in Claude account as a named profile.
|
|
166
|
-
|
|
167
|
-
- `name`: Profile name (validated against `[a-z0-9_-]+`).
|
|
168
|
-
- `homeDir`: Override for the home directory (defaults to `os.homedir()`). Primarily used in tests.
|
|
169
|
-
|
|
170
|
-
```ts
|
|
171
|
-
import { runAuthAdd } from "@simplysm/sd-claude";
|
|
172
|
-
|
|
173
|
-
runAuthAdd("work");
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
### `runAuthUse(name: string, homeDir?: string): void`
|
|
177
|
-
|
|
178
|
-
Switches to a saved Claude account profile.
|
|
179
|
-
|
|
180
|
-
- `name`: Profile name to switch to.
|
|
181
|
-
- `homeDir`: Override for the home directory.
|
|
182
|
-
|
|
183
|
-
```ts
|
|
184
|
-
import { runAuthUse } from "@simplysm/sd-claude";
|
|
185
|
-
|
|
186
|
-
runAuthUse("work");
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### `runAuthList(homeDir?: string): Promise<void>`
|
|
190
|
-
|
|
191
|
-
Prints all saved profiles to stdout with status, expiry, and live usage data.
|
|
192
|
-
|
|
193
|
-
- `homeDir`: Override for the home directory.
|
|
194
|
-
|
|
195
|
-
```ts
|
|
196
|
-
import { runAuthList } from "@simplysm/sd-claude";
|
|
197
|
-
|
|
198
|
-
await runAuthList();
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### `runAuthRemove(name: string, homeDir?: string): void`
|
|
202
|
-
|
|
203
|
-
Removes a saved Claude account profile.
|
|
204
|
-
|
|
205
|
-
- `name`: Profile name to remove.
|
|
206
|
-
- `homeDir`: Override for the home directory.
|
|
207
|
-
|
|
208
|
-
```ts
|
|
209
|
-
import { runAuthRemove } from "@simplysm/sd-claude";
|
|
210
|
-
|
|
211
|
-
runAuthRemove("personal");
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### `validateName(name: string): void`
|
|
215
|
-
|
|
216
|
-
Validates that a profile name matches `[a-z0-9_-]+`. Throws an `Error` if invalid.
|
|
217
|
-
|
|
218
|
-
```ts
|
|
219
|
-
import { validateName } from "@simplysm/sd-claude";
|
|
220
|
-
|
|
221
|
-
validateName("my-profile"); // OK
|
|
222
|
-
validateName("My Profile"); // throws Error
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### `getProfileDir(name: string, homeDir?: string): string`
|
|
226
|
-
|
|
227
|
-
Returns the absolute path to the profile directory: `<homeDir>/.sd-claude/auth/<name>`.
|
|
228
|
-
|
|
229
|
-
```ts
|
|
230
|
-
import { getProfileDir } from "@simplysm/sd-claude";
|
|
231
|
-
|
|
232
|
-
const dir = getProfileDir("work");
|
|
233
|
-
// e.g. "/home/user/.sd-claude/auth/work"
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### `profileExists(name: string, homeDir?: string): boolean`
|
|
237
|
-
|
|
238
|
-
Returns `true` if the profile directory exists.
|
|
239
|
-
|
|
240
|
-
```ts
|
|
241
|
-
import { profileExists } from "@simplysm/sd-claude";
|
|
242
|
-
|
|
243
|
-
if (profileExists("work")) {
|
|
244
|
-
console.log("Profile work exists");
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### `listProfiles(homeDir?: string): string[]`
|
|
249
|
-
|
|
250
|
-
Returns an array of all saved profile names (directory names under `~/.sd-claude/auth/`). Returns an empty array if no profiles exist.
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
import { listProfiles } from "@simplysm/sd-claude";
|
|
254
|
-
|
|
255
|
-
const profiles = listProfiles();
|
|
256
|
-
// e.g. ["personal", "work"]
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### `readCurrentAuth(homeDir?: string): { oauthAccount: Record<string, unknown>; userID: string }`
|
|
260
|
-
|
|
261
|
-
Reads `oauthAccount` and `userID` from `~/.claude.json`. Throws if not logged in.
|
|
262
|
-
|
|
263
|
-
```ts
|
|
264
|
-
import { readCurrentAuth } from "@simplysm/sd-claude";
|
|
265
|
-
|
|
266
|
-
const { oauthAccount, userID } = readCurrentAuth();
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
### `readCurrentCredentials(homeDir?: string): Record<string, unknown>`
|
|
270
|
-
|
|
271
|
-
Reads and returns the full contents of `~/.claude/.credentials.json`.
|
|
272
|
-
|
|
273
|
-
```ts
|
|
274
|
-
import { readCurrentCredentials } from "@simplysm/sd-claude";
|
|
275
|
-
|
|
276
|
-
const credentials = readCurrentCredentials();
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
### `getCurrentUserID(homeDir?: string): string | undefined`
|
|
280
|
-
|
|
281
|
-
Returns the `userID` from `~/.claude.json`, or `undefined` if the file does not exist or cannot be read.
|
|
282
|
-
|
|
283
|
-
```ts
|
|
284
|
-
import { getCurrentUserID } from "@simplysm/sd-claude";
|
|
285
|
-
|
|
286
|
-
const userID = getCurrentUserID();
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
## Profile Storage Layout
|
|
290
|
-
|
|
291
|
-
```
|
|
292
|
-
~/.sd-claude/
|
|
293
|
-
auth/
|
|
294
|
-
<name>/
|
|
295
|
-
auth.json # { oauthAccount, userID }
|
|
296
|
-
credentials.json # full ~/.claude/.credentials.json snapshot
|
|
297
|
-
```
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# Angular Guidelines (v12 only)
|
|
2
|
-
|
|
3
|
-
> v13 has no Angular (replaced by SolidJS). Rules below are v12 only.
|
|
4
|
-
|
|
5
|
-
## Core Rules
|
|
6
|
-
|
|
7
|
-
- **Signal-based**: Do not use RxJS for state. Use `$signal`, `$computed`, `$effect`
|
|
8
|
-
- **Standalone only**: All components must have `standalone: true`
|
|
9
|
-
- **OnPush + None**: `ChangeDetectionStrategy.OnPush` and `ViewEncapsulation.None` are mandatory
|
|
10
|
-
- **input()/output()**: Do not use `@Input()`, `@Output()` decorators → use `input()`, `output()`, `model()` instead
|
|
11
|
-
- **Control flow**: Use `@if`, `@for`, `@switch` (no `*ngIf`, `*ngFor`)
|
|
12
|
-
- **DI**: Use `inject()` (no constructor parameter injection)
|
|
13
|
-
- **Icons**: Use `@ng-icons/tabler-icons` (not FontAwesome)
|
|
14
|
-
- **Package imports**: Import `@simplysm/sd-angular` only from root (no subfolders)
|
|
15
|
-
|
|
16
|
-
## Signal Utilities
|
|
17
|
-
|
|
18
|
-
- `$signal<T>(value?)` — writable signal (can mark dirty with `$mark()`)
|
|
19
|
-
- `$computed(fn)` — synchronous computed. Async: `$computed([deps], asyncFn, { initialValue })`
|
|
20
|
-
- `$effect(fn)` — dependency tracking: `$effect([() => dep1()], () => { ... })`
|
|
21
|
-
- `$arr(signal)` — array operations (`.insert()`, `.remove()`, `.toggle()`, `.diffs()`)
|
|
22
|
-
- `$obj(signal)` — object operations (`.updateField()`, `.snapshot()`, `.changed()`)
|
|
23
|
-
- `$set(signal)` — Set operations (`.add()`, `.toggle()`)
|
|
24
|
-
|
|
25
|
-
## Component Pattern
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
@Component({
|
|
29
|
-
selector: "app-my-page",
|
|
30
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
31
|
-
encapsulation: ViewEncapsulation.None,
|
|
32
|
-
standalone: true,
|
|
33
|
-
imports: [SdTextfieldControl, SdButtonControl],
|
|
34
|
-
template: `
|
|
35
|
-
...
|
|
36
|
-
`,
|
|
37
|
-
})
|
|
38
|
-
export class MyPage {
|
|
39
|
-
#sdToast = inject(SdToastProvider);
|
|
40
|
-
#sdModal = inject(SdModalProvider);
|
|
41
|
-
|
|
42
|
-
busyCount = $signal(0);
|
|
43
|
-
data = $signal<IData>({});
|
|
44
|
-
|
|
45
|
-
constructor() {
|
|
46
|
-
$effect([], async () => {
|
|
47
|
-
await this.#loadData();
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Bootstrap
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
sdHmrBootstrapAsync(AppPage, {
|
|
57
|
-
providers: [
|
|
58
|
-
provideRouter([...routes], withHashLocation()),
|
|
59
|
-
provideSdAngular({
|
|
60
|
-
clientName: "my-app",
|
|
61
|
-
defaultTheme: "compact",
|
|
62
|
-
}),
|
|
63
|
-
],
|
|
64
|
-
});
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Modal
|
|
68
|
-
|
|
69
|
-
Implement `ISdModal<T>` interface:
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
export class MyModal implements ISdModal<TResult> {
|
|
73
|
-
param = input<string>();
|
|
74
|
-
close = output<TResult>();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Usage
|
|
78
|
-
await this.#sdModal.showAsync({
|
|
79
|
-
type: MyModal,
|
|
80
|
-
title: "Title",
|
|
81
|
-
inputs: { param: "value" },
|
|
82
|
-
});
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Data Sheet
|
|
86
|
-
|
|
87
|
-
CRUD tables extend `AbsSdDataSheet<TFilter, TItem, TKey>`:
|
|
88
|
-
|
|
89
|
-
- `search()` — query data
|
|
90
|
-
- `submit()` — save changes
|
|
91
|
-
- `downloadExcel()` / `uploadExcel()` — Excel integration
|
|
92
|
-
- `$mark()` — mark dirty when cell is modified (mandatory)
|
|
93
|
-
|
|
94
|
-
## Busy / Error Handling
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
this.busyCount.update((v) => v + 1);
|
|
98
|
-
try {
|
|
99
|
-
await this.#sdToast.try(async () => {
|
|
100
|
-
/* work */
|
|
101
|
-
});
|
|
102
|
-
} finally {
|
|
103
|
-
this.busyCount.update((v) => v - 1);
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Theming
|
|
108
|
-
|
|
109
|
-
- Use CSS variables: `--theme-primary-default`, `--gap-sm`, `--border-radius-default`, etc.
|
|
110
|
-
- Do not hardcode colors
|
|
111
|
-
- Themes: `"compact"` | `"mobile"` | `"kiosk"` + dark mode
|
|
112
|
-
|
|
113
|
-
## Permissions
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
perms = usePermsSignal(["base.partner"], ["use", "edit"]);
|
|
117
|
-
canEdit = $computed(() => this.perms().includes("edit"));
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Routing
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
{
|
|
124
|
-
path: "my-page",
|
|
125
|
-
loadComponent: () => import("./MyPage").then((m) => m.MyPage),
|
|
126
|
-
}
|
|
127
|
-
```
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
# Code Conventions
|
|
2
|
-
|
|
3
|
-
## Generic Type Parameters
|
|
4
|
-
|
|
5
|
-
- Always use descriptive names — single-letter `T` alone is not allowed
|
|
6
|
-
- Use `T` prefix + descriptive name: `TItem`, `TData`, `TResult`, `TKey`, `TValue`, `TAuthInfo`
|
|
7
|
-
|
|
8
|
-
## Prototype Extensions
|
|
9
|
-
|
|
10
|
-
Importing `@simplysm/core-common` adds extension methods to Array, Map, Set:
|
|
11
|
-
|
|
12
|
-
- `Array`: `single()`, `filterExists()`, `groupBy()`, `orderBy()`, etc.
|
|
13
|
-
- `Map`: `getOrCreate()`, `update()`
|
|
14
|
-
- `Set`: `adds()`, `toggle()`
|
|
15
|
-
|
|
16
|
-
Before using extension methods: Verify actual existence in `@simplysm/core-common` extensions (check README or source). Do not guess methods that don't exist.
|
|
17
|
-
|
|
18
|
-
## Function Naming Conventions
|
|
19
|
-
|
|
20
|
-
- Do not use `Async` suffix on function names — Async is the default
|
|
21
|
-
- When both sync and async versions exist, use `Sync` suffix on the sync function
|
|
22
|
-
- **Exception — extensions**: When adding an async version to an existing prototype (e.g., `Array`), follow the original naming convention. If the sync method already exists without a `Sync` suffix, use `Async` suffix for the async version.
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
// Good
|
|
26
|
-
async function readFile() { ... } // Async (default)
|
|
27
|
-
function readFileSync() { ... } // Sync version
|
|
28
|
-
|
|
29
|
-
// Bad
|
|
30
|
-
async function readFileAsync() { ... } // Async suffix prohibited
|
|
31
|
-
|
|
32
|
-
// Exception — Array extension already has mapMany()
|
|
33
|
-
Array.prototype.mapManyAsync = async function () { ... } // OK
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
## File Naming
|
|
38
|
-
|
|
39
|
-
- Auxiliary files (`types.ts`, `utils.ts`, etc.) must be prefixed with the main file name (e.g., `CrudSheet.types.ts`)
|
|
40
|
-
- File names must be self-identifying without relying on the parent directory
|
|
41
|
-
|
|
42
|
-
## JSDoc Convention
|
|
43
|
-
|
|
44
|
-
- Not enforced — omit when code is self-explanatory
|
|
45
|
-
- When written, use English
|
|
46
|
-
|
|
47
|
-
## Re-export Restriction
|
|
48
|
-
|
|
49
|
-
- Re-export (`export * from`, `export { } from`) is **only allowed in `src/index.ts`**
|
|
50
|
-
- All other files must not re-export — duplicated re-exports make code harder to find and maintain
|
|
51
|
-
|
|
52
|
-
## index.ts Export Pattern
|
|
53
|
-
|
|
54
|
-
- Use `//` comments to group exports
|
|
55
|
-
- Always `export *` (wildcard), never explicit `export type { ... } from "..."`
|
|
56
|
-
|
|
57
|
-
## `#region` / `#endregion`
|
|
58
|
-
|
|
59
|
-
- When splitting a large ts/tsx file has a bigger tradeoff than keeping it as-is, use `#region`/`#endregion` to organize sections within the file
|
|
60
|
-
- Do not use in simple export files like index.ts
|
|
61
|
-
|
|
62
|
-
## `any` vs `unknown` vs Generics
|
|
63
|
-
|
|
64
|
-
Choose based on **what you do with the value**:
|
|
65
|
-
|
|
66
|
-
| Type | When | Key rule |
|
|
67
|
-
|------|------|----------|
|
|
68
|
-
| `any` | Value is **passed through** without property access | No `.prop`, no method calls, no narrowing |
|
|
69
|
-
| `unknown` | Value's properties **will be accessed** but type is unknown | Must narrow with type guard before any access |
|
|
70
|
-
| Generic `<T>` | **Input/output type relationship** must be preserved | Caller's type flows through to return type |
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
// any — pure pass-through, no property access
|
|
74
|
-
function logAndStore(value: any): void {
|
|
75
|
-
console.log(value); // OK: no property access
|
|
76
|
-
storage.push(value); // OK: just forwarding
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// unknown — will access properties, must narrow first
|
|
80
|
-
function getName(data: unknown): string {
|
|
81
|
-
if (typeof data === "object" && data !== null && "name" in data) {
|
|
82
|
-
return String((data as { name: unknown }).name);
|
|
83
|
-
}
|
|
84
|
-
throw new Error("No name property");
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Generic — input type preserved in output
|
|
88
|
-
function wrapValue<T>(value: T): { value: T } {
|
|
89
|
-
return { value };
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**any vs Generic for pass-through:** If the function only forwards a value without accessing it AND the caller does not need type preservation in the return, use `any`. If the caller needs the same type back (input→output relationship), use a generic.
|
|
94
|
-
|
|
95
|
-
## Type Safety for Public APIs
|
|
96
|
-
|
|
97
|
-
- API changes must be detectable via **typecheck alone** — all affected usage sites must show compile errors
|
|
98
|
-
- Public component props must support **IDE intellisense** (autocomplete, type hints)
|
|
99
|
-
- **No `Record<string, any>` for structured props** — define explicit interfaces so consumers get autocomplete
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
// Bad — consumers get no autocomplete, changes are invisible
|
|
103
|
-
interface TableProps {
|
|
104
|
-
columns: Record<string, any>;
|
|
105
|
-
onRowClick: Function;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Good — changes to ColumnDef break consumers at compile time
|
|
109
|
-
interface TableProps<TRow> {
|
|
110
|
-
columns: ColumnDef<TRow>[];
|
|
111
|
-
onRowClick?: (row: TRow) => void;
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
## Forced Type Casting Prohibition
|
|
116
|
-
|
|
117
|
-
- **`as unknown as X` is prohibited** — dangerous escape hatch that silences real type errors
|
|
118
|
-
- **`as X` must be avoided** unless there is no alternative — when tempted, try these fixes first:
|
|
119
|
-
|
|
120
|
-
| Type error situation | Fix (instead of `as`) |
|
|
121
|
-
|---------------------|-----------------------|
|
|
122
|
-
| Missing properties | Add them to the interface |
|
|
123
|
-
| Type too wide | Use generics to propagate correctly |
|
|
124
|
-
| Unknown shape | Type guard: `if ("prop" in obj)`, `instanceof` |
|
|
125
|
-
| Multiple shapes | Discriminated union or overload |
|
|
126
|
-
| Wrong at source | Fix the root cause, not the usage site |
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
|
-
// Bad — casting hides the real problem
|
|
130
|
-
const admin = user as unknown as AdminUser;
|
|
131
|
-
|
|
132
|
-
// Bad — lazy cast instead of proper narrowing
|
|
133
|
-
const name = (event as any).target.name;
|
|
134
|
-
|
|
135
|
-
// Good — fix parameter type at the source
|
|
136
|
-
function getAdminDashboard(admin: AdminUser): string { ... }
|
|
137
|
-
|
|
138
|
-
// Good — narrow unknown with type guard
|
|
139
|
-
function getNameFromEvent(event: unknown): string {
|
|
140
|
-
if (typeof event === "object" && event !== null && "target" in event) {
|
|
141
|
-
const target = event.target;
|
|
142
|
-
if (typeof target === "object" && target !== null && "name" in target) {
|
|
143
|
-
return String(target.name);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
throw new Error("Invalid event structure");
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
## Boolean Prop Defaults
|
|
151
|
-
|
|
152
|
-
- Name boolean props so their default value is `false`
|
|
153
|
-
- Prefer `hide*`, `disable*` patterns where the feature is ON by default
|
|
154
|
-
- This avoids double-negation in JSX: `hideX={false}` is clearer than `showX={true}` when both mean "show X"
|
|
155
|
-
- Exception: inherent HTML attributes like `draggable` may default to `true`
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Directory Reference
|
|
2
|
-
|
|
3
|
-
- `.cache/`: Build cache (`eslint.cache`, `typecheck-{env}.tsbuildinfo`, `dts.tsbuildinfo`). Reset: delete `.cache/`
|
|
4
|
-
- `.tmp/playwright/`: Playwright MCP output directory
|
|
5
|
-
- Screenshots/snapshots must always be saved to the `.tmp/playwright/` directory
|
|
6
|
-
- When calling `browser_take_screenshot`, always prefix filename with `.tmp/playwright/` (e.g., `.tmp/playwright/screenshot.png`)
|
|
7
|
-
- `PLAYWRIGHT_OUTPUT_DIR` only applies to auto-generated filenames; explicitly specified filenames are resolved relative to cwd
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Simplysm Library Issue Reporting
|
|
2
|
-
|
|
3
|
-
Source code for `@simplysm/*` packages can be found in `node_modules/@simplysm/`. If debugging reveals the root cause is in the simplysm library itself, generate a GitHub issue-formatted text (title, reproduction steps, expected behavior, actual behavior) and display it to the user.
|
|
4
|
-
|
|
5
|
-
**Report facts only — do not suggest fixes or include code location hints. Do not auto-submit the issue — only display the text.**
|
|
6
|
-
|
|
7
|
-
The issue body must NEVER include internal analysis of library code (class names, variable names, style properties, inheritance chains, etc.). Only describe user-observable symptoms.
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# Migration Rules
|
|
2
|
-
|
|
3
|
-
When porting/migrating code from another codebase (e.g., v12 Angular → v13 SolidJS):
|
|
4
|
-
|
|
5
|
-
1. **Analyze every line**: Read the original source and all its dependencies (imports, base classes, etc.) line by line. Understand every feature, prop, and behavior. If a dependency cannot be found, ask the user.
|
|
6
|
-
2. **Ask about every difference**: Any change from the original (API, pattern, design, omission, addition) must be asked to the user. Never decide silently.
|
|
7
|
-
3. **Verify after completion**: Compare the result 1:1 with the original and report any omissions or differences to the user.
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# ORM Guidelines (v12)
|
|
2
|
-
|
|
3
|
-
## Table Definition — Decorator-based
|
|
4
|
-
|
|
5
|
-
```typescript
|
|
6
|
-
@Table({ description: "Users", database: "mydb" })
|
|
7
|
-
export class User {
|
|
8
|
-
@Column({ primaryKey: 1, autoIncrement: true, description: "ID" })
|
|
9
|
-
id!: number;
|
|
10
|
-
|
|
11
|
-
@Column({ dataType: { type: "STRING", length: 100 }, description: "Name" })
|
|
12
|
-
name!: string;
|
|
13
|
-
|
|
14
|
-
@Column({ nullable: true, description: "Email" })
|
|
15
|
-
email?: string;
|
|
16
|
-
|
|
17
|
-
@ForeignKey(["departmentId"], () => Department, "Department")
|
|
18
|
-
department?: Department;
|
|
19
|
-
|
|
20
|
-
@ForeignKeyTarget(() => Order, "user", "Orders")
|
|
21
|
-
orders?: Order[];
|
|
22
|
-
}
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
### Decorators
|
|
26
|
-
|
|
27
|
-
- `@Table({ description, database?, schema?, name?, view?, procedure? })` — define table/view/procedure
|
|
28
|
-
- `@Column({ description, name?, dataType?, nullable?, autoIncrement?, primaryKey? })` — define column
|
|
29
|
-
- `@ForeignKey(columnNames, targetTypeFwd, description)` — FK relationship
|
|
30
|
-
- `@ForeignKeyTarget(sourceTypeFwd, fkPropertyKey, description, multiplicity?)` — FK reverse relationship
|
|
31
|
-
- `@ReferenceKey(columnNames, targetTypeFwd, description)` — reference relationship
|
|
32
|
-
- `@ReferenceKeyTarget(sourceTypeFwd, refKeyPropertyKey, description, multiplicity?)` — reference reverse relationship
|
|
33
|
-
- `@Index({ name?, order?, orderBy?, unique? })` — index
|
|
34
|
-
|
|
35
|
-
### Requirements
|
|
36
|
-
|
|
37
|
-
- `tsconfig` requires `experimentalDecorators: true` and `emitDecoratorMetadata: true`
|
|
38
|
-
|
|
39
|
-
## DbContext
|
|
40
|
-
|
|
41
|
-
```typescript
|
|
42
|
-
export abstract class MyDbContext extends DbContext {
|
|
43
|
-
user = new Queryable(this, User);
|
|
44
|
-
order = new Queryable(this, Order);
|
|
45
|
-
|
|
46
|
-
get migrations(): Type<IDbMigration>[] {
|
|
47
|
-
return [Migration001, Migration002];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Query
|
|
53
|
-
|
|
54
|
-
```typescript
|
|
55
|
-
// Select
|
|
56
|
-
const users = await db.user
|
|
57
|
-
.select((item) => ({ id: item.id, name: item.name }))
|
|
58
|
-
.where((item) => [db.qh.equal(item.id, userId)])
|
|
59
|
-
.resultAsync();
|
|
60
|
-
|
|
61
|
-
// Insert
|
|
62
|
-
await db.user.insertAsync([{ name: "John Doe" }]);
|
|
63
|
-
|
|
64
|
-
// Connect with transaction
|
|
65
|
-
await db.connectAsync(async () => {
|
|
66
|
-
await db.user.insertAsync([{ name: "John Doe" }]);
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## SQL Injection Prevention
|
|
71
|
-
|
|
72
|
-
ORM uses string escaping (not parameter binding). **Always validate user input before ORM queries.**
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
const userId = Number(req.query.id);
|
|
76
|
-
if (Number.isNaN(userId)) throw new Error("Invalid ID");
|
|
77
|
-
await db.user
|
|
78
|
-
.select((item) => ({ id: item.id, name: item.name }))
|
|
79
|
-
.where((item) => [db.qh.equal(item.id, userId)])
|
|
80
|
-
.resultAsync();
|
|
81
|
-
```
|