@wirecat/cli-template 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/dependabot.yml +23 -0
- package/.github/workflows/changelog-guard.yml +12 -0
- package/.github/workflows/ci.yml +88 -0
- package/.github/workflows/main.yml +17 -0
- package/.github/workflows/monthly.yml +28 -0
- package/.github/workflows/pr.yml +19 -0
- package/.github/workflows/release.yml +191 -0
- package/.github/workflows/standards.yml +51 -0
- package/.gitleaks.toml +5 -0
- package/.markdownlint-cli2.jsonc +7 -0
- package/.vale.ini +5 -0
- package/AGENTS.md +27 -0
- package/CHANGELOG.md +31 -0
- package/CLAUDE.md +17 -0
- package/LICENSE +204 -0
- package/README.md +52 -2
- package/SECURITY.md +15 -0
- package/bin/release +139 -0
- package/biome.json +6 -0
- package/config/wirecat/LICENSE +204 -0
- package/config/wirecat/biome.base.json +157 -0
- package/config/wirecat/check.mjs +41 -0
- package/config/wirecat/cspell.json +80 -0
- package/config/wirecat/lefthook.yml +26 -0
- package/config/wirecat/manifest.json +37 -0
- package/config/wirecat/markdownlint.json +10 -0
- package/config/wirecat/no-deep-wirecat-import.grit +15 -0
- package/config/wirecat/no-exec-interpolation.grit +17 -0
- package/config/wirecat/no-new-function.grit +8 -0
- package/config/wirecat/no-shell-true.grit +8 -0
- package/config/wirecat/no-sqlite-driver.grit +15 -0
- package/config/wirecat/no-unbounded-spread.grit +10 -0
- package/config/wirecat/release-lock-info.mjs +71 -0
- package/config/wirecat/release-lock.sh +34 -0
- package/config/wirecat/rumdl.toml +9 -0
- package/contracts/help.snap +16 -0
- package/contracts/mcp-tools.json +922 -0
- package/contracts/no-leak.json +3 -0
- package/contracts/plan.json +50 -0
- package/cspell.json +10 -0
- package/dist/bin/app.d.ts +3 -0
- package/dist/bin/app.d.ts.map +1 -0
- package/dist/bin/app.js +14 -0
- package/dist/bin/app.js.map +1 -0
- package/dist/commands/doctor.d.ts +23 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +44 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +36 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +4 -0
- package/dist/config.js.map +1 -0
- package/dist/identity.d.ts +5 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +12 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +62 -0
- package/dist/mcp.js.map +1 -0
- package/dist/program.d.ts +6 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +37 -0
- package/dist/program.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/docs/commands.md +227 -0
- package/docs/configuration.md +18 -0
- package/docs/dev/DESIGN.md +13 -0
- package/docs/dev/TESTING.md +16 -0
- package/docs/dev/design-questions.md +24 -0
- package/docs/dev/reference-clis.md +15 -0
- package/docs/dev/skills/design-interview/SKILL.md +34 -0
- package/docs/dev/skills/release/SKILL.md +24 -0
- package/docs/dev/test-matrix.md +52 -0
- package/docs/index.md +7 -0
- package/docs/installation.md +17 -0
- package/docs/meta.json +18 -0
- package/docs/security.md +8 -0
- package/docs/troubleshooting.md +14 -0
- package/docs/usage.md +31 -0
- package/gitignore +44 -0
- package/kit/README.md +127 -0
- package/kit/dist/interview.js +170 -0
- package/kit/dist/main.js +70 -0
- package/kit/dist/manifest.js +111 -0
- package/kit/dist/new.js +256 -0
- package/kit/dist/rename.js +36 -0
- package/kit/dist/source.js +98 -0
- package/kit/dist/upgrade.js +230 -0
- package/kit/tsconfig.json +17 -0
- package/kit/tsconfig.test.json +5 -0
- package/lefthook.yml +2 -0
- package/lint/baseline/.gitkeep +0 -0
- package/lint/overrides.json +1 -0
- package/package.json +106 -4
- package/pnpm-workspace.yaml +6 -0
- package/scripts/audit.sh +22 -0
- package/scripts/command-program.ts +1 -0
- package/scripts/commands.ts +39 -0
- package/scripts/smoke.ts +56 -0
- package/scripts/test-matrix-untested.ts +12 -0
- package/skills/app-cli/SKILL.md +21 -0
- package/src/bin/app.ts +13 -0
- package/src/commands/doctor.ts +45 -0
- package/src/commands/mcp.ts +38 -0
- package/src/config.ts +4 -0
- package/src/identity.ts +15 -0
- package/src/index.ts +2 -0
- package/src/mcp.test.ts +84 -0
- package/src/mcp.ts +82 -0
- package/src/program.test.ts +66 -0
- package/src/program.ts +53 -0
- package/src/roots.test.ts +80 -0
- package/src/testing/argv-log.ts +1 -0
- package/src/testing/sandbox.ts +25 -0
- package/src/version.ts +1 -0
- package/template.json +40 -0
- package/tsconfig.json +9 -0
- package/tsconfig.scripts.json +1 -0
- package/tsconfig.test.json +1 -0
- package/vitest.config.ts +19 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
{
|
|
2
|
+
"vcs": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"clientKind": "git",
|
|
5
|
+
"useIgnoreFile": true
|
|
6
|
+
},
|
|
7
|
+
"formatter": {
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"indentStyle": "space",
|
|
10
|
+
"indentWidth": 2,
|
|
11
|
+
"lineWidth": 120
|
|
12
|
+
},
|
|
13
|
+
"javascript": {
|
|
14
|
+
"formatter": {
|
|
15
|
+
"quoteStyle": "double",
|
|
16
|
+
"semicolons": "asNeeded",
|
|
17
|
+
"trailingCommas": "all",
|
|
18
|
+
"arrowParentheses": "always"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"linter": {
|
|
22
|
+
"enabled": true,
|
|
23
|
+
"rules": {
|
|
24
|
+
"preset": "recommended",
|
|
25
|
+
"style": {
|
|
26
|
+
"useConst": "error",
|
|
27
|
+
"noNonNullAssertion": "error",
|
|
28
|
+
"noExcessiveLinesPerFile": {
|
|
29
|
+
"level": "error",
|
|
30
|
+
"options": {
|
|
31
|
+
"maxLines": 1000
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"correctness": {
|
|
36
|
+
"noUnusedImports": "error",
|
|
37
|
+
"noUnusedVariables": "error"
|
|
38
|
+
},
|
|
39
|
+
"suspicious": {
|
|
40
|
+
"noExplicitAny": "error",
|
|
41
|
+
"noConsole": {
|
|
42
|
+
"level": "error",
|
|
43
|
+
"options": {
|
|
44
|
+
"allow": ["error"]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"noImportCycles": "error"
|
|
48
|
+
},
|
|
49
|
+
"security": {
|
|
50
|
+
"noSecrets": {
|
|
51
|
+
"level": "error",
|
|
52
|
+
"options": {
|
|
53
|
+
"entropyThreshold": 50
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"overrides": [
|
|
60
|
+
{
|
|
61
|
+
"includes": ["scripts/**"],
|
|
62
|
+
"linter": {
|
|
63
|
+
"rules": {
|
|
64
|
+
"suspicious": {
|
|
65
|
+
"noConsole": "off"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"includes": [
|
|
72
|
+
"**/*.test.*",
|
|
73
|
+
"**/*.spec.*",
|
|
74
|
+
"**/test/**",
|
|
75
|
+
"**/tests/**",
|
|
76
|
+
"**/__tests__/**",
|
|
77
|
+
"**/fixtures/**",
|
|
78
|
+
"**/generated/**"
|
|
79
|
+
],
|
|
80
|
+
"linter": {
|
|
81
|
+
"rules": {
|
|
82
|
+
"style": {
|
|
83
|
+
"noExcessiveLinesPerFile": "off"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"includes": [
|
|
90
|
+
"**/*.test.*",
|
|
91
|
+
"**/*.spec.*",
|
|
92
|
+
"**/test/**",
|
|
93
|
+
"**/tests/**",
|
|
94
|
+
"**/__tests__/**",
|
|
95
|
+
"**/fixtures/**",
|
|
96
|
+
"**/generated/**"
|
|
97
|
+
],
|
|
98
|
+
"linter": {
|
|
99
|
+
"rules": {
|
|
100
|
+
"security": {
|
|
101
|
+
"noSecrets": "off"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
"plugins": [
|
|
108
|
+
"./config/wirecat/no-new-function.grit",
|
|
109
|
+
"./config/wirecat/no-shell-true.grit",
|
|
110
|
+
"./config/wirecat/no-exec-interpolation.grit",
|
|
111
|
+
{
|
|
112
|
+
"path": "./config/wirecat/no-deep-wirecat-import.grit",
|
|
113
|
+
"includes": [
|
|
114
|
+
"**",
|
|
115
|
+
"!**/*.test.*",
|
|
116
|
+
"!**/*.spec.*",
|
|
117
|
+
"!**/test/**",
|
|
118
|
+
"!**/tests/**",
|
|
119
|
+
"!**/__tests__/**",
|
|
120
|
+
"!**/fixtures/**"
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"path": "./config/wirecat/no-sqlite-driver.grit",
|
|
125
|
+
"includes": [
|
|
126
|
+
"**",
|
|
127
|
+
"!**/*.test.*",
|
|
128
|
+
"!**/*.spec.*",
|
|
129
|
+
"!**/test/**",
|
|
130
|
+
"!**/tests/**",
|
|
131
|
+
"!**/__tests__/**",
|
|
132
|
+
"!**/fixtures/**",
|
|
133
|
+
"!**/testing/**",
|
|
134
|
+
"!**/scripts/**",
|
|
135
|
+
"!**/bench/**",
|
|
136
|
+
"!**/performance/**",
|
|
137
|
+
"!**/contracts/**"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"path": "./config/wirecat/no-unbounded-spread.grit",
|
|
142
|
+
"includes": [
|
|
143
|
+
"**",
|
|
144
|
+
"!**/*.test.*",
|
|
145
|
+
"!**/*.spec.*",
|
|
146
|
+
"!**/test/**",
|
|
147
|
+
"!**/tests/**",
|
|
148
|
+
"!**/__tests__/**",
|
|
149
|
+
"!**/fixtures/**",
|
|
150
|
+
"!**/testing/**",
|
|
151
|
+
"!**/scripts/**",
|
|
152
|
+
"!**/bench/**",
|
|
153
|
+
"!**/performance/**"
|
|
154
|
+
]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import { readFileSync } from "node:fs"
|
|
3
|
+
import { dirname, resolve } from "node:path"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
|
|
6
|
+
const directory = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const root = resolve(directory, "../..")
|
|
8
|
+
const manifest = JSON.parse(readFileSync(resolve(directory, "manifest.json"), "utf8"))
|
|
9
|
+
const errors = []
|
|
10
|
+
|
|
11
|
+
for (const [name, expected] of Object.entries(manifest.files)) {
|
|
12
|
+
const actual = createHash("sha256")
|
|
13
|
+
.update(readFileSync(resolve(directory, name)))
|
|
14
|
+
.digest("hex")
|
|
15
|
+
if (actual !== expected) errors.push(`Managed standard changed: ${name}`)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const packageJson = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"))
|
|
19
|
+
for (const [name, expected] of Object.entries(manifest.devDependencies)) {
|
|
20
|
+
if (packageJson.devDependencies?.[name] !== expected) errors.push(`Expected ${name}@${expected}`)
|
|
21
|
+
}
|
|
22
|
+
if (packageJson.packageManager !== manifest.packageManager) errors.push("Package manager differs from shared standard")
|
|
23
|
+
|
|
24
|
+
const biome = JSON.parse(readFileSync(resolve(root, "biome.json"), "utf8"))
|
|
25
|
+
if (!biome.extends?.includes("./config/wirecat/biome.base.json"))
|
|
26
|
+
errors.push("Biome does not extend the shared standard")
|
|
27
|
+
const spell = JSON.parse(readFileSync(resolve(root, "cspell.json"), "utf8"))
|
|
28
|
+
if (!spell.import?.includes("./config/wirecat/cspell.json")) errors.push("CSpell does not import the shared standard")
|
|
29
|
+
const markdown = JSON.parse(readFileSync(resolve(root, ".markdownlint-cli2.jsonc"), "utf8"))
|
|
30
|
+
if (markdown.config?.extends !== "./config/wirecat/markdownlint.json")
|
|
31
|
+
errors.push("Markdown does not extend the shared standard")
|
|
32
|
+
const hooks = readFileSync(resolve(root, "lefthook.yml"), "utf8")
|
|
33
|
+
if (!/^\s*-\s*config\/wirecat\/lefthook\.yml\s*$/m.test(hooks))
|
|
34
|
+
errors.push("Git hooks do not extend the shared standard")
|
|
35
|
+
|
|
36
|
+
if (errors.length) {
|
|
37
|
+
process.stderr.write(`${errors.join("\n")}\nUpdate standards centrally or put project overrides in root configs.\n`)
|
|
38
|
+
process.exitCode = 1
|
|
39
|
+
} else {
|
|
40
|
+
process.stdout.write(`Shared standards match ${manifest.revision}.\n`)
|
|
41
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.2",
|
|
3
|
+
"language": "en,en-GB,ru,es",
|
|
4
|
+
"ignoreRegExpList": [
|
|
5
|
+
"/^\\s*(```|~~~)[\\s\\S]*?^\\s*\\1/gm",
|
|
6
|
+
"/`[^`\\n]*`/g",
|
|
7
|
+
"/<!--[\\s\\S]*?-->/g",
|
|
8
|
+
"/\\]\\([^)]*\\)/g",
|
|
9
|
+
"/<[^>]+>/g",
|
|
10
|
+
"/^---[\\s\\S]*?^---/gm"
|
|
11
|
+
],
|
|
12
|
+
"words": [
|
|
13
|
+
"armv",
|
|
14
|
+
"brazecli",
|
|
15
|
+
"bunx",
|
|
16
|
+
"claude",
|
|
17
|
+
"codegen",
|
|
18
|
+
"CodeWiki",
|
|
19
|
+
"DeepWiki",
|
|
20
|
+
"Excalidraw",
|
|
21
|
+
"frontmatter",
|
|
22
|
+
"Fumadocs",
|
|
23
|
+
"gitleaks",
|
|
24
|
+
"Hermes",
|
|
25
|
+
"JSONL",
|
|
26
|
+
"keyring",
|
|
27
|
+
"kwallet",
|
|
28
|
+
"leemour",
|
|
29
|
+
"libc",
|
|
30
|
+
"libsecret",
|
|
31
|
+
"Lucene",
|
|
32
|
+
"LUKS",
|
|
33
|
+
"Mermaid",
|
|
34
|
+
"mtcute",
|
|
35
|
+
"npmjs",
|
|
36
|
+
"ONNX",
|
|
37
|
+
"pioh",
|
|
38
|
+
"Repomix",
|
|
39
|
+
"riscv",
|
|
40
|
+
"Shiki",
|
|
41
|
+
"subcommand",
|
|
42
|
+
"tailnet",
|
|
43
|
+
"tailscale",
|
|
44
|
+
"tgcli",
|
|
45
|
+
"unpushed",
|
|
46
|
+
"userbot",
|
|
47
|
+
"volta",
|
|
48
|
+
"webhooks",
|
|
49
|
+
"WireCat",
|
|
50
|
+
"wirecat",
|
|
51
|
+
"автодополнение",
|
|
52
|
+
"бана",
|
|
53
|
+
"вебхук",
|
|
54
|
+
"вебхуки",
|
|
55
|
+
"капчу",
|
|
56
|
+
"контрибьюторы",
|
|
57
|
+
"листателя",
|
|
58
|
+
"Минцифры",
|
|
59
|
+
"опкод",
|
|
60
|
+
"отсканируете",
|
|
61
|
+
"переголосовать",
|
|
62
|
+
"пулл",
|
|
63
|
+
"реквесты",
|
|
64
|
+
"рендерер",
|
|
65
|
+
"спиннера",
|
|
66
|
+
"схлопнет",
|
|
67
|
+
"схлопывает",
|
|
68
|
+
"токенам",
|
|
69
|
+
"токене",
|
|
70
|
+
"токеном",
|
|
71
|
+
"токену",
|
|
72
|
+
"хэша",
|
|
73
|
+
"хэшем"
|
|
74
|
+
],
|
|
75
|
+
"import": [
|
|
76
|
+
"@cspell/dict-en-gb/cspell-ext.json",
|
|
77
|
+
"@cspell/dict-ru_ru/cspell-ext.json",
|
|
78
|
+
"@cspell/dict-es-es/cspell-ext.json"
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Git hooks. Installed by the `prepare` npm script, which pnpm runs on every install — necessary
|
|
2
|
+
# because pnpm blocks dependencies' build scripts by default, so lefthook's own postinstall never
|
|
3
|
+
# fires and the hooks would otherwise silently never land.
|
|
4
|
+
#
|
|
5
|
+
# Install by hand: pnpm exec lefthook install
|
|
6
|
+
# Skip for one commit: LEFTHOOK=0 git commit …
|
|
7
|
+
|
|
8
|
+
pre-commit:
|
|
9
|
+
parallel: true
|
|
10
|
+
commands:
|
|
11
|
+
biome:
|
|
12
|
+
glob: "*.{ts,tsx,js,mjs,cjs,json,jsonc}"
|
|
13
|
+
# Staged files only. The whole-repo run is the CI gate; running it per-commit is how people
|
|
14
|
+
# end up reaching for --no-verify, which is worse than no hook.
|
|
15
|
+
run: node node_modules/@biomejs/biome/bin/biome check --no-errors-on-unmatched --files-ignore-unknown=true {staged_files}
|
|
16
|
+
|
|
17
|
+
gitleaks:
|
|
18
|
+
# Staged content only, and it warns rather than blocks when gitleaks is not installed: a
|
|
19
|
+
# fresh clone must still be able to commit. CI is the gate that cannot be skipped.
|
|
20
|
+
run: |
|
|
21
|
+
if command -v gitleaks >/dev/null 2>&1; then
|
|
22
|
+
gitleaks git --staged --no-banner --redact .
|
|
23
|
+
else
|
|
24
|
+
echo "gitleaks is not installed — staged changes were NOT scanned for secrets."
|
|
25
|
+
echo " install: https://github.com/gitleaks/gitleaks#installing (CI scans regardless)"
|
|
26
|
+
fi
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"source": "https://github.com/WireCatLabs/community",
|
|
3
|
+
"revision": "2b0f124f6375939bba10c28a7f091923323e9a13",
|
|
4
|
+
"files": {
|
|
5
|
+
"biome.base.json": "63760337a2e68522ad75e0ba163635785a01ec34dbd3cf338a09a4145932cf2a",
|
|
6
|
+
"cspell.json": "8241122ed4d461976a838099b265ef8f2689a066608257b6fdc250d9b49b58aa",
|
|
7
|
+
"lefthook.yml": "2f65a2e3103006d43f6d6f0a07ab333c132286157763222a59a5a22676acd391",
|
|
8
|
+
"markdownlint.json": "5ed7d6980054c539f3f3c0e47a090f1a725b15be2c486a769a836bc6cd48ad2d",
|
|
9
|
+
"no-deep-wirecat-import.grit": "b9cb69b9ae125604c951cd924941ab089af32f9fa91f072e34531ca861ed125d",
|
|
10
|
+
"no-exec-interpolation.grit": "b9461f887afd0f7d555814bc45c5cd47537808edc10a17b06049f3ca0fff5957",
|
|
11
|
+
"no-new-function.grit": "f6423e186cca90b1ab87d40185c6d920e99d83366af6dc11455963a0797fb627",
|
|
12
|
+
"no-shell-true.grit": "d3943cfa1d7ebbf7be0fa22c2aaf2b7d7aa9ff462f2eb14a7798632764e3875c",
|
|
13
|
+
"no-unbounded-spread.grit": "f4178e5b74212fef58fec8916b6783d9f4f192ac618aa4ced041c4167bfbf81b",
|
|
14
|
+
"release-lock-info.mjs": "4abab1f01ceee7d4c709222e923c9a6fc634bcf7b8b64d745a9ac495993310e0",
|
|
15
|
+
"release-lock.sh": "bef364092c4ab595f3b9b4130e23570c8306223228cdfa3e9384230f3b7f4a49",
|
|
16
|
+
"rumdl.toml": "048b929176f13234324f5825d1e811397f1491b9cb6684a1953e268e2c2ed863",
|
|
17
|
+
"check.mjs": "34b266411eaab7707030915f645308e86784b1574b21fa44ca974861a90c9ce1",
|
|
18
|
+
"no-sqlite-driver.grit": "19e1f510a88ddd9df44d6db43c1c9b603c3e5bbec03a3f65305b72eac7f09389"
|
|
19
|
+
},
|
|
20
|
+
"packageManager": "pnpm@11.20.0",
|
|
21
|
+
"node": ">=22",
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@biomejs/biome": "2.5.15",
|
|
24
|
+
"@cspell/dict-en-gb": "5.0.34",
|
|
25
|
+
"@cspell/dict-es-es": "3.0.9",
|
|
26
|
+
"@cspell/dict-ru_ru": "2.3.3",
|
|
27
|
+
"cspell": "10.3.6",
|
|
28
|
+
"lefthook": "2.1.16",
|
|
29
|
+
"markdownlint-cli2": "0.23.3"
|
|
30
|
+
},
|
|
31
|
+
"fileRevisions": {
|
|
32
|
+
"release-lock.sh": "49e630a99d758e3e113749bef2049b258c7262e3",
|
|
33
|
+
"release-lock-info.mjs": "49e630a99d758e3e113749bef2049b258c7262e3",
|
|
34
|
+
"biome.base.json": "06c612c609d870f0ba00010612e0413f65340408",
|
|
35
|
+
"no-sqlite-driver.grit": "06c612c609d870f0ba00010612e0413f65340408"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`import $_ from $source`,
|
|
5
|
+
`import $source`,
|
|
6
|
+
`export { $_ } from $source`,
|
|
7
|
+
`export * from $source`,
|
|
8
|
+
`export * as $_ from $source`,
|
|
9
|
+
`export type { $_ } from $source`,
|
|
10
|
+
`import($source)`,
|
|
11
|
+
`require($source)`
|
|
12
|
+
} where {
|
|
13
|
+
$source <: r"^[\"'`]@wirecat/[^/\"'`]+/(?:src|dist)(?:/.*)?[\"'`]$",
|
|
14
|
+
register_diagnostic(span=$source, message="Import WireCat packages through their public exports, never src/ or dist/: those paths are not part of the package contract and break on the next release.", severity="error")
|
|
15
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`exec($args)`,
|
|
5
|
+
`execSync($args)`,
|
|
6
|
+
`$_.execSync($args)`,
|
|
7
|
+
`child_process.exec($args)`,
|
|
8
|
+
`childProcess.exec($args)`,
|
|
9
|
+
`cp.exec($args)`
|
|
10
|
+
} where {
|
|
11
|
+
$args <: [$command, ...],
|
|
12
|
+
$command <: or {
|
|
13
|
+
r"^`[\s\S]*\$\{[\s\S]*`$",
|
|
14
|
+
`$left + $right`
|
|
15
|
+
},
|
|
16
|
+
register_diagnostic(span=$command, message="A command string built from values runs through a shell. Use execFile or spawn with an argument array.", severity="error")
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
`shell: true` as $option where {
|
|
4
|
+
$option <: within `$callee($args)` where {
|
|
5
|
+
$callee <: r"^(?:.*\.)?(?:spawn|spawnSync|exec|execSync|execFile|execFileSync|fork)$"
|
|
6
|
+
},
|
|
7
|
+
register_diagnostic(span=$option, message="shell: true hands the arguments to a shell, so a space, quote or ; in user data runs as a command. Call the program directly with an argument array.", severity="error")
|
|
8
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`import $_ from $source`,
|
|
5
|
+
`import $source`,
|
|
6
|
+
`export { $_ } from $source`,
|
|
7
|
+
`export * from $source`,
|
|
8
|
+
`export * as $_ from $source`,
|
|
9
|
+
`export type { $_ } from $source`,
|
|
10
|
+
`import($source)`,
|
|
11
|
+
`require($source)`
|
|
12
|
+
} where {
|
|
13
|
+
$source <: r"^[\"'`](?:node:sqlite|bun:sqlite|better-sqlite3|drizzle-orm)(?:/.*)?[\"'`]$",
|
|
14
|
+
register_diagnostic(span=$source, message="The shared store belongs to cli-core (WC-STORE-5): a CLI opens no SQLite driver and writes no SQL against it. Ask cli-core for a store method.", severity="error")
|
|
15
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
language js
|
|
2
|
+
|
|
3
|
+
or {
|
|
4
|
+
`$target.push($args)`,
|
|
5
|
+
`Math.max($args)`,
|
|
6
|
+
`Math.min($args)`
|
|
7
|
+
} as $call where {
|
|
8
|
+
$args <: some `...$items`,
|
|
9
|
+
register_diagnostic(span=$call, message="Spreading a list into call arguments throws past about 125,000 items. Loop or reduce instead.", severity="warn")
|
|
10
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process"
|
|
2
|
+
import { closeSync, openSync, readSync, realpathSync } from "node:fs"
|
|
3
|
+
import { fileURLToPath } from "node:url"
|
|
4
|
+
|
|
5
|
+
const safe = (value) => JSON.stringify(String(value))
|
|
6
|
+
|
|
7
|
+
export function releaseLockInfo(path, { run = execFileSync, realpath = realpathSync, label = readLabel } = {}) {
|
|
8
|
+
const lines = [`another release holds ${safe(path)}`]
|
|
9
|
+
let rows
|
|
10
|
+
for (const columns of ["PID,COMMAND,PATH,TYPE,MODE,HOLDERS", "PID,COMMAND,PATH,TYPE,MODE"]) {
|
|
11
|
+
try {
|
|
12
|
+
const result = JSON.parse(
|
|
13
|
+
run("lslocks", ["--json", "--output", columns], {
|
|
14
|
+
encoding: "utf8",
|
|
15
|
+
timeout: 2000,
|
|
16
|
+
maxBuffer: 1024 * 1024,
|
|
17
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18
|
+
}),
|
|
19
|
+
)
|
|
20
|
+
rows = result.locks
|
|
21
|
+
break
|
|
22
|
+
} catch {
|
|
23
|
+
// Older util-linux lacks HOLDERS; diagnostics also work without lslocks.
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const holders = new Set()
|
|
27
|
+
if (Array.isArray(rows)) {
|
|
28
|
+
for (const row of rows) {
|
|
29
|
+
try {
|
|
30
|
+
if (row.type !== "FLOCK" || row.mode?.endsWith("*")) continue
|
|
31
|
+
if (realpath(row.path) !== realpath(path)) continue
|
|
32
|
+
const before = holders.size
|
|
33
|
+
if (Array.isArray(row.holders)) {
|
|
34
|
+
for (const holder of row.holders) {
|
|
35
|
+
const match = /^(\d+),([^,]+),\d+$/.exec(holder)
|
|
36
|
+
if (match && Number(match[1]) > 0) holders.add(`pid ${match[1]} (${safe(match[2])})`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (holders.size === before && Number.isInteger(row.pid) && row.pid > 0)
|
|
40
|
+
holders.add(`pid ${row.pid} (${safe(row.command)})`)
|
|
41
|
+
} catch {
|
|
42
|
+
// A holder may exit while the snapshot is read, or hide its path.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
lines.push(
|
|
47
|
+
holders.size
|
|
48
|
+
? `Observed lock holders (may change): ${[...holders].join(", ")}`
|
|
49
|
+
: "Observed lock holders: unavailable",
|
|
50
|
+
)
|
|
51
|
+
try {
|
|
52
|
+
const recorded = label(`${path}.who`)
|
|
53
|
+
if (recorded) lines.push(`Last recorded release (may be stale): ${safe(recorded)}`)
|
|
54
|
+
} catch {
|
|
55
|
+
// The label is advisory; only flock decides whether a release may proceed.
|
|
56
|
+
}
|
|
57
|
+
lines.push("Wait for the lock holders to finish, then retry. Do not remove the lock file.")
|
|
58
|
+
return `${lines.join("\n")}\n`
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readLabel(path) {
|
|
62
|
+
const fd = openSync(path, "r")
|
|
63
|
+
try {
|
|
64
|
+
const bytes = Buffer.alloc(512)
|
|
65
|
+
return bytes.subarray(0, readSync(fd, bytes)).toString("utf8").trim()
|
|
66
|
+
} finally {
|
|
67
|
+
closeSync(fd)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) process.stderr.write(releaseLockInfo(process.argv[2]))
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Source from the repository root; FD9 must stay in the release shell and its children.
|
|
2
|
+
wirecat_release_lock() {
|
|
3
|
+
_wirecat_release_wait=${1-0}
|
|
4
|
+
case "$_wirecat_release_wait" in
|
|
5
|
+
''|*[!0-9]*) echo "release wait must be a nonnegative integer" >&2; return 2 ;;
|
|
6
|
+
esac
|
|
7
|
+
command -v flock >/dev/null 2>&1 || {
|
|
8
|
+
echo "release locking requires flock" >&2
|
|
9
|
+
return 1
|
|
10
|
+
}
|
|
11
|
+
# Keyed by package name, not folder: one package releases from several checkouts and worktrees.
|
|
12
|
+
_wirecat_release_key=$(node -e '
|
|
13
|
+
const { name } = JSON.parse(require("node:fs").readFileSync("package.json", "utf8"))
|
|
14
|
+
if (typeof name !== "string" || !name) throw Error("package.json has no name")
|
|
15
|
+
process.stdout.write(name.replace(/^@/, "").replace(/[^A-Za-z0-9._-]+/g, "-"))
|
|
16
|
+
' 2>/dev/null) && [ -n "$_wirecat_release_key" ] || {
|
|
17
|
+
echo "release locking needs a package name in ./package.json" >&2
|
|
18
|
+
return 1
|
|
19
|
+
}
|
|
20
|
+
_wirecat_release_lock="${XDG_RUNTIME_DIR:-/tmp}/leemour-release-$_wirecat_release_key.lock"
|
|
21
|
+
exec 9>>"$_wirecat_release_lock"
|
|
22
|
+
if flock -E 75 -w "$_wirecat_release_wait" 9; then
|
|
23
|
+
printf '%s pid %s since %s\n' "$(basename "$PWD")" "$$" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$_wirecat_release_lock.who"
|
|
24
|
+
else
|
|
25
|
+
_wirecat_release_status=$?
|
|
26
|
+
if [ "$_wirecat_release_status" -ne 75 ]; then
|
|
27
|
+
printf 'could not acquire release lock %s (flock exit %s)\n' "$_wirecat_release_lock" "$_wirecat_release_status" >&2
|
|
28
|
+
return 1
|
|
29
|
+
fi
|
|
30
|
+
node ./config/wirecat/release-lock-info.mjs "$_wirecat_release_lock" ||
|
|
31
|
+
printf 'another release holds %s; holder details unavailable\n' "$_wirecat_release_lock" >&2
|
|
32
|
+
return 1
|
|
33
|
+
fi
|
|
34
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# MD013 (line length): prose is wrapped by hand at 100–120 and tables cannot wrap.
|
|
2
|
+
# MD041 (first line a heading): READMEs open with the logo; docs-check --pages checks the pages.
|
|
3
|
+
# MD057 (relative links): docs-check follows every link and anchor already.
|
|
4
|
+
[global]
|
|
5
|
+
disable = ["MD013", "MD041", "MD057"]
|
|
6
|
+
exclude = ["docs/commands.md", "node_modules", "dist", "docs_ai"]
|
|
7
|
+
|
|
8
|
+
[MD033]
|
|
9
|
+
allowed-elements = ["p", "img", "a", "br", "details", "summary", "sup", "sub", "kbd"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
app — a new WireCat CLI, started from cli-template
|
|
2
|
+
|
|
3
|
+
Usage: app <profile> <command> [options]
|
|
4
|
+
Account commands start with your profile, a name you choose; none is ever assumed.
|
|
5
|
+
|
|
6
|
+
Works with:
|
|
7
|
+
tg Telegram in the same local store: search, review and tasks offline
|
|
8
|
+
max MAX in the same local store: search, review and tasks offline
|
|
9
|
+
wa WhatsApp in the same local store: search, review and tasks offline
|
|
10
|
+
zm Zoom meetings: transcripts and summaries, searchable with your messages
|
|
11
|
+
drive Google Drive and Docs as local text, searchable with your messages
|
|
12
|
+
memo context <person> one person across messengers, mail, notes and meetings
|
|
13
|
+
|
|
14
|
+
Next: app commands every command, by section
|
|
15
|
+
app help options output, offline, timeouts, dry run
|
|
16
|
+
For agents: app skill show rules and the discovery loop
|