@hyuga/carrylint 0.1.0 → 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/README.md CHANGED
@@ -20,17 +20,16 @@ Other skill linters check your skill is **spec-valid**. carrylint checks it **ac
20
20
 
21
21
  | kind | severity | 説明 |
22
22
  |---|---|---|
23
- | `abs-path` | **error** | マシン固有の絶対パス(`C:\Users\…` / `/Users/…` / `/home/…` / `$HOME` / `%USERPROFILE%`) |
24
- | `placeholder` | **error** | 配布物に残った未解決プレースホルダ(`<FILL_ME>` / `YOUR_API_KEY` / `path/to/your…`) |
25
- | `undeclared-cli` | **error** | 外部/プロバイダCLI(`codex` `gemini` `ollama` …)を叩くのに install 手順も `requires` 宣言もない |
26
- | `home-path` | warn | `~/…` ホーム相対パス(ユーザー前提) |
23
+ | `abs-path` | **error** | 作者環境前提の絶対パス(`C:\Users\…` / `/Users/<実名>/` / `/home/<実名>/`)。`$HOME` / `~` / `%USERPROFILE%` と汎用名(`/home/user` 等)は可搬なので**対象外** |
24
+ | `placeholder` | **error** | 配布物に残った未完成マーカー(`<FILL_ME>` / `REPLACE_ME` / `CHANGEME` / `<INSERT …>`)。`YOUR_API_KEY` / `/path/to/` は「置き換えてね」の正当な文書慣習なので**対象外** |
25
+ | `undeclared-cli` | warn | 外部/プロバイダCLI(`codex` `ollama` …)を宣言なしで叩く。ホストの設定/確認(`claude mcp add` `codex --version` 等)は**除外** |
27
26
  | `provider-env` | warn | プロバイダ固有 API キー env の生参照(`OPENAI_API_KEY` …) |
28
27
  | `todo` | warn | 配布物に残った `TODO:` / `FIXME:` |
29
28
  | `model-id` | opt-in | モデルID直書き(`claude-*` / `gpt-*` / `gemini-*`)※`--model-ids` で有効化 |
30
29
 
31
- Low-noise by design: only the unambiguous breakers are **error** (fail the PR); the rest are **warn**; intentional model pinning is **off** unless you ask for it.
30
+ Low-noise by design: only unambiguous, author-only breakers are **error** (fail the PR); the rest are **warn**. Validated against a real-world audit of 230 public skills (v0.1.1): every remaining `error` was a genuine hardcoded personal path.
32
31
 
33
- > 誤検知=狼少年化が唯一の死因なので、曖昧さゼロのものだけ error にしています。
32
+ > 誤検知=狼少年化が唯一の死因。実データ230リポの監査で ERROR の誤検知をゼロに追い込みました(v0.1.1)。
34
33
 
35
34
  ## Use as a GitHub Action / CIで使う(定着の本体)
36
35
 
package/action.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: 'carrylint runtime-portability linter'
2
- description: 'Fail CI when a skill/command hardcodes machine- or model-specific assumptions: absolute paths, undeclared CLIs, unresolved placeholders. Zero-dep, model-agnostic.'
2
+ description: "Fail CI when a skill or command isn't portable: absolute paths, undeclared CLIs, placeholders. Zero-dep, model-agnostic."
3
3
  author: 'hyuga611'
4
4
  branding:
5
5
  icon: 'package'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyuga/carrylint",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Runtime-portability linter for agent skills & commands: fail CI when a SKILL.md / AGENTS.md / slash-command hardcodes machine- or model-specific assumptions (absolute paths, undeclared CLIs, unresolved placeholders). Zero-dependency, model-agnostic.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/check.mjs CHANGED
@@ -4,14 +4,17 @@
4
4
  // SKILL.md / .claude/commands / AGENTS.md / CLAUDE.md / GEMINI.md / .cursor/rules などの本文から、
5
5
  // 作った本人の環境・モデル前提が焼き込まれた「実行時に可搬でない」記述を検出する。
6
6
  //
7
- // ・abs-path : マシン固有の絶対パス (C:\… /Users/… /home/… $HOME %USERPROFILE%) [ERROR]
8
- // ・placeholder : 配布物に残った未解決プレースホルダ (<FILL_ME> YOUR_API_KEY path/to/…) [ERROR]
9
- // ・undeclared-cli : 本文が外部/プロバイダCLIを叩くのに宣言もインストール手順もない [ERROR]
10
- // ・home-path : ~/… ホーム相対パス (ユーザー前提) [WARN]
7
+ // ・abs-path : 作者環境前提の絶対パス (C:\… /Users/<実名>/ /home/<実名>/) [ERROR]
8
+ // $HOME / ~ / %USERPROFILE% と汎用名(/home/user 等)は可搬なので対象外
9
+ // ・placeholder : 配布物に残った未完成マーカー (<FILL_ME> REPLACE_ME CHANGEME <INSERT…>) [ERROR]
10
+ // YOUR_API_KEY / /path/to/ は「置き換えてね」の正当な文書慣習なので対象外
11
+ // ・undeclared-cli : 外部/プロバイダCLIを宣言なしで叩く。ホストの mcp add/--version 等は除外 [WARN]
11
12
  // ・provider-env : プロバイダ固有の API キー env の生参照 [WARN]
12
13
  // ・todo : 配布物に残った TODO:/FIXME:/XXX: [WARN]
13
14
  // ・model-id : モデルID直書き (claude-* gpt-* gemini-*) ← 既定OFF・--model-ids で有効 [opt-in]
14
15
  //
16
+ // v0.1.1: 実データ160+70リポの監査に基づき誤検知を除去($HOME/~・汎用名・API例・ホストCLI)。
17
+ //
15
18
  // Agent Skills はオープン標準として "形式" は 20+ エージェントで可搬になった。carrylint は
16
19
  // "中身が実際に動くか" を見る側。実行時に LLM も API キーも使わない純静的解析(依存ゼロ)。
17
20
  //
@@ -65,29 +68,38 @@ export const DEFAULT_RULES = {
65
68
  ],
66
69
  };
67
70
 
68
- // 配布物に残っていたら「未完成」を示す、曖昧さの小さいプレースホルダ(ERROR)。
69
- // 汎用の <name> 等は意図的なテンプレなので拾わない(skills-lint と同じ思想)。
71
+ // 配布物に残っていたら「作者が埋め忘れた未完成」を示す、曖昧さゼロのマーカーのみ(ERROR)。
72
+ // v0.1.1: YOUR_API_KEY / /path/to/ / <your-x> は「実行時に置き換えてね」という正当な
73
+ // ドキュメント慣習(実データ監査で 26/26 が誤検知)だったので ERROR から除外した。
70
74
  const PLACEHOLDER_ERROR = [
71
75
  /<fill[_\- ]?me>?/i,
72
76
  /\bfill[_\-]me\b/i,
73
77
  /\breplace[_\-]?me\b/i,
74
78
  /\bchange[_\-]?me\b/i,
75
- /\byour[_\-](?:api[_\-]?key|token|secret|password)\b/i,
76
- /<your-[^>]{1,40}>/i,
77
79
  /<insert[ _\-][^>]{1,40}>/i,
78
- /\bpath\/to\/your\b/i,
79
- /(?:^|[\s`"'(])\/path\/to\//,
80
- /\bpath\/to\/\.{3}/,
81
80
  ];
82
81
 
83
82
  // ---------------- パターン(純粋・textスキャン) ----------------
84
83
 
85
- const DRIVE_ABS = /(?<![\w.])[A-Za-z]:\\[^\s`"')<>|]+/g; // C:\Users\atlan\…
86
- const UNIX_HOME_ABS = /(?<![\w.\-\/])\/(?:Users|home)\/[^\s`"')<>|]+/g; // /Users/atlan/… /home/foo/…
87
- const ENV_HOME = /(?:\$\{?HOME\b\}?|%USERPROFILE%)/gi; // $HOME ${HOME} %USERPROFILE%
88
- const TILDE_HOME = /(?<![\w.])~\/[^\s`"')<>|]*/g; // ~/foo …(WARN)
84
+ const DRIVE_ABS = /(?<![\w.])[A-Za-z]:\\[^\s`"')<>|]+/g; // C:\Users\atlan\…(Windows絶対=非可搬)
85
+ // /Users/<name>/ /home/<name>/ <name> を捕捉(汎用名/glob は後段で除外)。
86
+ const UNIX_USER_ABS = /(?<![\w.\-\/])\/(Users|home)\/([^\s/`"')<>|]+)([^\s`"')<>|]*)/g;
89
87
  const TODO_MARK = /(?:\b(?:TODO|FIXME|HACK|XXX)\b\s*[::]|<!--\s*(?:TODO|FIXME)\b)/;
90
88
 
89
+ // v0.1.1: $HOME / ${HOME} / %USERPROFILE% / ~ は各ユーザーで解決=可搬。エラーにしない
90
+ // (実データ監査で「これらは可搬な書き方」だと確認)。実在の個人パスは /Users/<実名>/ のみ。
91
+ const GENERIC_USER = new Set([
92
+ 'user', 'users', 'you', 'me', 'example', 'name', 'username', 'ubuntu', 'root',
93
+ 'admin', 'foo', 'bar', 'home', 'someone', 'yourname', 'test', 'ec2-user',
94
+ ]);
95
+ // ホスト/セットアップ系サブコマンドは「その場で使う道具」でなく「環境設定・確認」なので依存扱いしない
96
+ // (実データ監査の undeclared-cli 誤検知は大半が `claude mcp add` / `codex mcp add` 等だった)。
97
+ const META_SUBCMD = new Set([
98
+ 'mcp', 'login', 'logout', 'config', 'configure', 'auth', 'add', 'install',
99
+ 'update', 'upgrade', 'init', 'setup', 'doctor', 'version', 'help',
100
+ '--version', '-v', '--help', '-h',
101
+ ]);
102
+
91
103
  function push(findings, ln, kind, severity, msg) {
92
104
  findings.push({ ln, kind, severity, msg });
93
105
  }
@@ -156,7 +168,10 @@ function cliInvocations(line, inFence, rules) {
156
168
  .replace(/^[!$#>]\s*/, '')
157
169
  .replace(/^sudo\s+/, '')
158
170
  .replace(/^(?:[A-Za-z_][\w]*=[^\s]*\s+)+/, '');
159
- if (new RegExp('^' + w + '\\b\\s+\\S', 'i').test(rest)) hits.add(w);
171
+ const mm = new RegExp('^' + w + '\\b\\s+(\\S+)', 'i').exec(rest);
172
+ if (!mm) continue; // 裸の言及(引数なし)は呼び出しでない
173
+ if (META_SUBCMD.has(mm[1].toLowerCase())) continue; // mcp / --version / login 等は設定・確認=依存でない
174
+ hits.add(w);
160
175
  }
161
176
  }
162
177
  return hits;
@@ -197,16 +212,16 @@ export function scan(text, opts = {}) {
197
212
  }
198
213
  if (ignored.has(ln)) return;
199
214
 
200
- // 1) マシン固有の絶対パス(ERROR
201
- for (const re of [DRIVE_ABS, UNIX_HOME_ABS, ENV_HOME]) {
202
- re.lastIndex = 0;
203
- for (const m of line.matchAll(re)) {
204
- push(findings, ln, 'abs-path', 'error', `マシン固有の絶対パス \`${m[0].trim()}\` — 他人の環境で解決しません(相対パスや {baseDir} に)`);
205
- }
215
+ // 1) マシン固有の絶対パス(ERROR)。$HOME/~ は可搬なので対象外・汎用ユーザー名の例示も除外(v0.1.1)。
216
+ DRIVE_ABS.lastIndex = 0;
217
+ for (const m of line.matchAll(DRIVE_ABS)) {
218
+ push(findings, ln, 'abs-path', 'error', `マシン固有の絶対パス \`${m[0].trim()}\` — 他人の環境で解決しません(相対パスや {baseDir} に)`);
206
219
  }
207
- // ~/ ホーム相対(WARN)
208
- for (const m of line.matchAll(TILDE_HOME)) {
209
- push(findings, ln, 'home-path', 'warn', `ホーム相対パス \`${m[0].trim()}\` — ユーザーのホーム前提です`);
220
+ UNIX_USER_ABS.lastIndex = 0;
221
+ for (const m of line.matchAll(UNIX_USER_ABS)) {
222
+ const user = (m[2] || '').toLowerCase();
223
+ if (GENERIC_USER.has(user) || user.includes('*') || user.startsWith('$')) continue; // /home/user, /home/*/ 等の例示は除外
224
+ push(findings, ln, 'abs-path', 'error', `作者環境前提の絶対パス \`${m[0].trim()}\` — 他人の環境に \`${m[1]}/${m[2]}\` は存在しません(相対パスや {baseDir} に)`);
210
225
  }
211
226
 
212
227
  // 2) 未解決プレースホルダ(ERROR)
@@ -218,10 +233,11 @@ export function scan(text, opts = {}) {
218
233
  }
219
234
  }
220
235
 
221
- // 3) provider CLI 呼び出し(宣言なし→ERROR)
236
+ // 3) 外部/プロバイダCLIの呼び出し(宣言なし)。ホスト自身の設定/確認(mcp add 等)は除外済み。
237
+ // 概念的に曖昧なので v0.1.1 で ERROR→WARN に降格(PRは落とさず注意喚起のみ)。
222
238
  for (const cli of cliInvocations(line, inFence, rules)) {
223
239
  if (allow.has(cli) || declared.has(cli)) continue;
224
- push(findings, ln, 'undeclared-cli', 'error', `\`${cli}\` を呼んでいますが、インストール手順も requires 宣言もありません(他環境で黙って失敗します)`);
240
+ push(findings, ln, 'undeclared-cli', 'warn', `\`${cli}\` を呼んでいますが、インストール手順も宣言もありません 他環境では未導入かもしれません`);
225
241
  }
226
242
 
227
243
  // 4) プロバイダ固有 env の生参照(WARN)