flower-trellis 0.4.0-beta.4 → 0.4.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/enhancements/0.6/.agents/skills/trellis-push/SKILL.md +17 -8
- package/enhancements/0.6/.agents/skills/trellis-route/SKILL.md +8 -3
- package/enhancements/0.6/.claude/skills/trellis-push/SKILL.md +17 -8
- package/enhancements/0.6/.claude/skills/trellis-route/SKILL.md +8 -3
- package/enhancements/0.6/overrides/skills/trellis-finish-work.md +14 -0
- package/enhancements/0.6/overrides/workflow-states/in_progress-inline.md +2 -2
- package/enhancements/0.6/overrides/workflow-states/in_progress.md +2 -2
- package/enhancements/0.6/overrides/workflow-states/no_task.md +2 -2
- package/enhancements/0.6/overrides/workflow-states/planning-inline.md +1 -1
- package/enhancements/0.6/overrides/workflow-states/planning.md +1 -1
- package/enhancements/0.6/overrides/workflow.md +18 -10
- package/enhancements/0.6/scripts/push_snapshot.py +387 -0
- package/enhancements/0.6/scripts/spec_router.py +244 -33
- package/enhancements/MANIFEST.json +3 -4
- package/package.json +1 -1
- package/src/lib/copy-scripts.js +2 -0
- package/src/lib/skill-catalog.js +0 -1
- package/enhancements/common/.common/.claude/skills/sub2api-account-json-fix/SKILL.md +0 -47
- package/enhancements/common/.common/.claude/skills/sub2api-account-json-fix/env/push.env.example +0 -8
- package/enhancements/common/.common/.claude/skills/sub2api-account-json-fix/scripts/run.sh +0 -27
- package/enhancements/common/.common/.codex/skills/sub2api-account-json-fix/SKILL.md +0 -126
- package/enhancements/common/.common/.codex/skills/sub2api-account-json-fix/agents/openai.yaml +0 -4
- package/enhancements/common/.common/.codex/skills/sub2api-account-json-fix/env/push.env.example +0 -8
- package/enhancements/common/.common/.codex/skills/sub2api-account-json-fix/scripts/fix_exported_account_json.py +0 -890
- package/enhancements/common/.common/.codex/skills/sub2api-account-json-fix/scripts/run.sh +0 -26
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
"""从 `.trellis/spec/` 发现相关项目 SOP/spec 文件。
|
|
4
4
|
|
|
5
|
-
这个 helper
|
|
6
|
-
|
|
5
|
+
这个 helper 刻意保持轻量:只返回候选路径、置信度和命中原因,让 AI 在项目
|
|
6
|
+
局部知识可能影响做法的决策边界前读取匹配文件;它不把完整文档注入上下文。
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -21,40 +21,72 @@ MAX_BODY_CHARS = 8000
|
|
|
21
21
|
DEFAULT_LIMIT = 3
|
|
22
22
|
MIN_SCORE = 3
|
|
23
23
|
MIN_BODY_ONLY_HITS = 5
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
MIN_ANCHORED_BODY_HITS = 2
|
|
25
|
+
WEAK_TOKENS = {
|
|
26
26
|
"action",
|
|
27
27
|
"actions",
|
|
28
28
|
"after",
|
|
29
|
+
"and",
|
|
29
30
|
"before",
|
|
31
|
+
"change",
|
|
32
|
+
"changes",
|
|
33
|
+
"cli",
|
|
30
34
|
"command",
|
|
31
35
|
"commands",
|
|
36
|
+
"commit",
|
|
32
37
|
"context",
|
|
33
38
|
"current",
|
|
39
|
+
"data",
|
|
40
|
+
"documentation",
|
|
41
|
+
"edit",
|
|
34
42
|
"file",
|
|
35
43
|
"files",
|
|
44
|
+
"flow",
|
|
45
|
+
"flower",
|
|
46
|
+
"for",
|
|
47
|
+
"from",
|
|
48
|
+
"guide",
|
|
49
|
+
"guides",
|
|
50
|
+
"in",
|
|
51
|
+
"index",
|
|
36
52
|
"match",
|
|
37
53
|
"matched",
|
|
38
54
|
"matches",
|
|
39
55
|
"matching",
|
|
56
|
+
"md",
|
|
40
57
|
"normal",
|
|
58
|
+
"of",
|
|
59
|
+
"or",
|
|
41
60
|
"path",
|
|
42
61
|
"paths",
|
|
43
62
|
"project",
|
|
63
|
+
"py",
|
|
44
64
|
"read",
|
|
65
|
+
"readme",
|
|
45
66
|
"reason",
|
|
46
67
|
"reasons",
|
|
47
68
|
"relevant",
|
|
69
|
+
"run",
|
|
70
|
+
"simple",
|
|
71
|
+
"small",
|
|
48
72
|
"sop",
|
|
49
73
|
"spec",
|
|
50
74
|
"status",
|
|
51
75
|
"task",
|
|
52
76
|
"tasks",
|
|
77
|
+
"the",
|
|
78
|
+
"to",
|
|
79
|
+
"trellis",
|
|
80
|
+
"typo",
|
|
81
|
+
"update",
|
|
82
|
+
"with",
|
|
53
83
|
"workflow",
|
|
54
84
|
}
|
|
55
|
-
TOKEN_RE = re.compile(r"[A-Za-z0-
|
|
85
|
+
TOKEN_RE = re.compile(r"[A-Za-z0-9@]+|[\u4e00-\u9fff]+")
|
|
86
|
+
CJK_RE = re.compile(r"^[\u4e00-\u9fff]+$")
|
|
56
87
|
HEADER_RE = re.compile(r"^\s{0,3}#{1,3}\s+(.+?)\s*$", re.MULTILINE)
|
|
57
88
|
FRONTMATTER_BOUNDARY_RE = re.compile(r"^---\s*$")
|
|
89
|
+
MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+\.md(?:#[^)]+)?)\)")
|
|
58
90
|
|
|
59
91
|
|
|
60
92
|
@dataclass
|
|
@@ -66,7 +98,9 @@ class Candidate:
|
|
|
66
98
|
kind: str
|
|
67
99
|
load: str
|
|
68
100
|
priority: str
|
|
101
|
+
confidence: str
|
|
69
102
|
reasons: list[str]
|
|
103
|
+
action: str
|
|
70
104
|
|
|
71
105
|
|
|
72
106
|
def find_trellis_root(start: Path) -> Path | None:
|
|
@@ -183,8 +217,45 @@ def as_list(value: Any) -> list[str]:
|
|
|
183
217
|
return [text] if text else []
|
|
184
218
|
|
|
185
219
|
|
|
220
|
+
def add_token(tokens: list[str], seen: set[str], token: str) -> None:
|
|
221
|
+
"""按原始顺序追加去重 token。
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
tokens: 正在构造的 token 列表。
|
|
225
|
+
seen: 已追加 token 集合。
|
|
226
|
+
token: 待追加 token。
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
None。
|
|
230
|
+
"""
|
|
231
|
+
if len(token) < 2 or token in seen:
|
|
232
|
+
return
|
|
233
|
+
seen.add(token)
|
|
234
|
+
tokens.append(token)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def add_cjk_tokens(tokens: list[str], seen: set[str], text: str) -> None:
|
|
238
|
+
"""为连续中文文本追加有限 n-gram token。
|
|
239
|
+
|
|
240
|
+
中文没有空格分词。这里生成 2 到 6 字的 n-gram,用 token 集合匹配替代旧
|
|
241
|
+
版任意子串匹配,同时保留 `发版` 命中 `发版流程` 这类常见能力。
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
tokens: 正在构造的 token 列表。
|
|
245
|
+
seen: 已追加 token 集合。
|
|
246
|
+
text: 连续中文文本。
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
None。
|
|
250
|
+
"""
|
|
251
|
+
max_size = min(6, len(text))
|
|
252
|
+
for size in range(2, max_size + 1):
|
|
253
|
+
for start in range(0, len(text) - size + 1):
|
|
254
|
+
add_token(tokens, seen, text[start : start + size])
|
|
255
|
+
|
|
256
|
+
|
|
186
257
|
def normalize_tokens(text: str) -> list[str]:
|
|
187
|
-
"""
|
|
258
|
+
"""提取查询或文档 token,用于确定性的轻量匹配。
|
|
188
259
|
|
|
189
260
|
Args:
|
|
190
261
|
text: 查询或可搜索文本。
|
|
@@ -195,14 +266,129 @@ def normalize_tokens(text: str) -> list[str]:
|
|
|
195
266
|
seen: set[str] = set()
|
|
196
267
|
tokens: list[str] = []
|
|
197
268
|
for match in TOKEN_RE.finditer(text.lower()):
|
|
198
|
-
token = match.group(0)
|
|
199
|
-
if
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
269
|
+
token = match.group(0)
|
|
270
|
+
if CJK_RE.match(token):
|
|
271
|
+
add_cjk_tokens(tokens, seen, token)
|
|
272
|
+
else:
|
|
273
|
+
add_token(tokens, seen, token)
|
|
203
274
|
return tokens
|
|
204
275
|
|
|
205
276
|
|
|
277
|
+
def significant_hits(query_tokens: list[str], target_tokens: list[str]) -> list[str]:
|
|
278
|
+
"""计算非弱词 token 命中,保留查询 token 顺序。
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
query_tokens: 查询 token。
|
|
282
|
+
target_tokens: 待匹配文本 token。
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
非弱词命中列表。
|
|
286
|
+
"""
|
|
287
|
+
target_set = set(target_tokens)
|
|
288
|
+
return [
|
|
289
|
+
token
|
|
290
|
+
for token in query_tokens
|
|
291
|
+
if token not in WEAK_TOKENS and token in target_set
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def collect_index_descriptions(spec_dir: Path) -> dict[str, list[str]]:
|
|
296
|
+
"""从 `index.md` 链接行收集目标文档的路由描述。
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
spec_dir: `.trellis/spec` 目录。
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
以 spec 相对路径为 key 的描述文本列表。
|
|
303
|
+
"""
|
|
304
|
+
descriptions: dict[str, list[str]] = {}
|
|
305
|
+
spec_root = spec_dir.resolve()
|
|
306
|
+
for index_path in iter_spec_files(spec_dir):
|
|
307
|
+
if index_path.name != "index.md":
|
|
308
|
+
continue
|
|
309
|
+
|
|
310
|
+
text = read_markdown(index_path)
|
|
311
|
+
if text is None:
|
|
312
|
+
continue
|
|
313
|
+
_, body = parse_frontmatter(text)
|
|
314
|
+
|
|
315
|
+
for line in body.splitlines():
|
|
316
|
+
matches = list(MARKDOWN_LINK_RE.finditer(line))
|
|
317
|
+
if not matches:
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
clean_line = MARKDOWN_LINK_RE.sub(lambda item: item.group(1), line).strip()
|
|
321
|
+
for match in matches:
|
|
322
|
+
link_target = match.group(2).split("#", 1)[0].strip()
|
|
323
|
+
if "://" in link_target or link_target.startswith("#"):
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
target_path = (index_path.parent / link_target).resolve()
|
|
327
|
+
try:
|
|
328
|
+
target_rel_path = target_path.relative_to(spec_root).as_posix()
|
|
329
|
+
except ValueError:
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
if not target_path.is_file() or target_path == index_path.resolve():
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
description = f"{match.group(1)} {clean_line}".strip()
|
|
336
|
+
if description:
|
|
337
|
+
descriptions.setdefault(target_rel_path, []).append(description)
|
|
338
|
+
return descriptions
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def classify_confidence(
|
|
342
|
+
matched_triggers: list[str],
|
|
343
|
+
path_hits: list[str],
|
|
344
|
+
header_hits: list[str],
|
|
345
|
+
index_hits: list[str],
|
|
346
|
+
body_hits: list[str],
|
|
347
|
+
) -> str | None:
|
|
348
|
+
"""根据强锚点和弱证据判断候选置信度。
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
matched_triggers: frontmatter trigger 命中。
|
|
352
|
+
path_hits: 路径命中。
|
|
353
|
+
header_hits: 标题命中。
|
|
354
|
+
index_hits: index 描述命中。
|
|
355
|
+
body_hits: 正文样本命中。
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
`high` / `medium`;证据不足时返回 None。
|
|
359
|
+
"""
|
|
360
|
+
if matched_triggers:
|
|
361
|
+
return "high"
|
|
362
|
+
|
|
363
|
+
anchor_groups = [path_hits, header_hits, index_hits]
|
|
364
|
+
anchor_count = sum(1 for group in anchor_groups if group)
|
|
365
|
+
if len(path_hits) >= 2 or len(header_hits) >= 2 or len(index_hits) >= 2:
|
|
366
|
+
return "high"
|
|
367
|
+
if anchor_count >= 2:
|
|
368
|
+
return "high"
|
|
369
|
+
if anchor_count == 1 and len(body_hits) >= MIN_ANCHORED_BODY_HITS:
|
|
370
|
+
return "high"
|
|
371
|
+
if anchor_count == 1:
|
|
372
|
+
return "medium"
|
|
373
|
+
if len(body_hits) >= MIN_BODY_ONLY_HITS:
|
|
374
|
+
return "medium"
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def action_for_confidence(confidence: str) -> str:
|
|
379
|
+
"""按置信度返回读取建议。
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
confidence: 候选置信度。
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
面向 AI 的行动建议。
|
|
386
|
+
"""
|
|
387
|
+
if confidence == "high":
|
|
388
|
+
return "read before acting"
|
|
389
|
+
return "read if clearly relevant"
|
|
390
|
+
|
|
391
|
+
|
|
206
392
|
def read_markdown(path: Path) -> str | None:
|
|
207
393
|
"""以容错 UTF-8 方式读取 Markdown 文件。
|
|
208
394
|
|
|
@@ -239,7 +425,13 @@ def iter_spec_files(spec_dir: Path) -> list[Path]:
|
|
|
239
425
|
return sorted(result)
|
|
240
426
|
|
|
241
427
|
|
|
242
|
-
def score_file(
|
|
428
|
+
def score_file(
|
|
429
|
+
root: Path,
|
|
430
|
+
path: Path,
|
|
431
|
+
query: str,
|
|
432
|
+
query_tokens: list[str],
|
|
433
|
+
index_descriptions: dict[str, list[str]],
|
|
434
|
+
) -> Candidate | None:
|
|
243
435
|
"""按查询为一个 Markdown spec 文件打分。
|
|
244
436
|
|
|
245
437
|
Args:
|
|
@@ -247,6 +439,7 @@ def score_file(root: Path, path: Path, query: str, query_tokens: list[str]) -> C
|
|
|
247
439
|
path: Markdown 文件路径。
|
|
248
440
|
query: 原始查询文本。
|
|
249
441
|
query_tokens: 标准化后的查询 token。
|
|
442
|
+
index_descriptions: 从 index.md 收集的目标文档描述。
|
|
250
443
|
|
|
251
444
|
Returns:
|
|
252
445
|
分数达到阈值时返回候选项,否则返回 None。
|
|
@@ -258,11 +451,13 @@ def score_file(root: Path, path: Path, query: str, query_tokens: list[str]) -> C
|
|
|
258
451
|
metadata, body = parse_frontmatter(text)
|
|
259
452
|
rel_path = path.relative_to(root).as_posix()
|
|
260
453
|
spec_rel_path = path.relative_to(root / ".trellis" / "spec").as_posix()
|
|
261
|
-
|
|
454
|
+
spec_rel_tokens = normalize_tokens(spec_rel_path)
|
|
262
455
|
body_sample = body[:MAX_BODY_CHARS]
|
|
263
|
-
|
|
456
|
+
body_tokens = normalize_tokens(body_sample)
|
|
264
457
|
headers = HEADER_RE.findall(body)
|
|
265
|
-
|
|
458
|
+
header_tokens = normalize_tokens(" ".join(headers))
|
|
459
|
+
index_text = " ".join(index_descriptions.get(spec_rel_path, []))
|
|
460
|
+
index_tokens = normalize_tokens(index_text)
|
|
266
461
|
|
|
267
462
|
kind = str(metadata.get("kind") or "").strip()
|
|
268
463
|
load = str(metadata.get("load") or "").strip()
|
|
@@ -282,31 +477,35 @@ def score_file(root: Path, path: Path, query: str, query_tokens: list[str]) -> C
|
|
|
282
477
|
score += 8 * len(matched_triggers)
|
|
283
478
|
reasons.append(f"matched triggers: {', '.join(matched_triggers[:5])}")
|
|
284
479
|
|
|
285
|
-
path_hits =
|
|
480
|
+
path_hits = significant_hits(query_tokens, spec_rel_tokens)
|
|
286
481
|
if path_hits:
|
|
287
|
-
score +=
|
|
482
|
+
score += 5 * len(path_hits)
|
|
288
483
|
reasons.append(f"matched path tokens: {', '.join(path_hits[:5])}")
|
|
289
484
|
|
|
290
|
-
header_hits =
|
|
485
|
+
header_hits = significant_hits(query_tokens, header_tokens)
|
|
291
486
|
if header_hits:
|
|
292
487
|
score += 3 * len(header_hits)
|
|
293
488
|
reasons.append(f"matched headings: {', '.join(header_hits[:5])}")
|
|
294
489
|
|
|
295
|
-
|
|
296
|
-
|
|
490
|
+
index_hits = significant_hits(query_tokens, index_tokens)
|
|
491
|
+
if index_hits:
|
|
492
|
+
score += 4 * len(index_hits)
|
|
493
|
+
reasons.append(f"matched index descriptions: {', '.join(index_hits[:5])}")
|
|
494
|
+
|
|
495
|
+
body_hits = significant_hits(query_tokens, body_tokens)
|
|
297
496
|
if body_hits:
|
|
298
497
|
score += len(body_hits)
|
|
299
498
|
reasons.append(f"matched body tokens: {', '.join(body_hits[:5])}")
|
|
300
499
|
|
|
301
|
-
# 避免 `
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
500
|
+
# 避免 `to` / `flow` / `commit` 这类泛词或少量正文词把无关文件拉进上下文。
|
|
501
|
+
confidence = classify_confidence(
|
|
502
|
+
matched_triggers,
|
|
503
|
+
path_hits,
|
|
504
|
+
header_hits,
|
|
505
|
+
index_hits,
|
|
506
|
+
body_hits,
|
|
308
507
|
)
|
|
309
|
-
if
|
|
508
|
+
if confidence is None:
|
|
310
509
|
return None
|
|
311
510
|
|
|
312
511
|
if kind.lower() in {"sop", "procedure", "guide", "thinking-guide"} and score > 0:
|
|
@@ -325,7 +524,9 @@ def score_file(root: Path, path: Path, query: str, query_tokens: list[str]) -> C
|
|
|
325
524
|
kind=kind or ("thinking-guide" if "/guides/" in f"/{rel_path}" else "spec"),
|
|
326
525
|
load=load,
|
|
327
526
|
priority=priority,
|
|
527
|
+
confidence=confidence,
|
|
328
528
|
reasons=reasons,
|
|
529
|
+
action=action_for_confidence(confidence),
|
|
329
530
|
)
|
|
330
531
|
|
|
331
532
|
|
|
@@ -345,12 +546,20 @@ def find_candidates(root: Path, query: str, limit: int) -> list[Candidate]:
|
|
|
345
546
|
return []
|
|
346
547
|
|
|
347
548
|
candidates: list[Candidate] = []
|
|
348
|
-
|
|
349
|
-
|
|
549
|
+
spec_dir = root / ".trellis" / "spec"
|
|
550
|
+
index_descriptions = collect_index_descriptions(spec_dir)
|
|
551
|
+
for path in iter_spec_files(spec_dir):
|
|
552
|
+
candidate = score_file(root, path, query, query_tokens, index_descriptions)
|
|
350
553
|
if candidate:
|
|
351
554
|
candidates.append(candidate)
|
|
352
555
|
|
|
353
|
-
candidates.sort(
|
|
556
|
+
candidates.sort(
|
|
557
|
+
key=lambda item: (
|
|
558
|
+
0 if item.confidence == "high" else 1,
|
|
559
|
+
-item.score,
|
|
560
|
+
item.path,
|
|
561
|
+
)
|
|
562
|
+
)
|
|
354
563
|
return candidates[:limit]
|
|
355
564
|
|
|
356
565
|
|
|
@@ -372,13 +581,14 @@ def format_markdown(candidates: list[Candidate]) -> str:
|
|
|
372
581
|
lines.append(f"- {candidate.path}")
|
|
373
582
|
lines.append(f" kind: {candidate.kind}")
|
|
374
583
|
lines.append(f" score: {candidate.score}")
|
|
584
|
+
lines.append(f" confidence: {candidate.confidence}")
|
|
375
585
|
if candidate.load:
|
|
376
586
|
lines.append(f" load: {candidate.load}")
|
|
377
587
|
if candidate.priority:
|
|
378
588
|
lines.append(f" priority: {candidate.priority}")
|
|
379
589
|
if candidate.reasons:
|
|
380
590
|
lines.append(f" reason: {'; '.join(candidate.reasons)}")
|
|
381
|
-
lines.append(" action:
|
|
591
|
+
lines.append(f" action: {candidate.action}")
|
|
382
592
|
return "\n".join(lines)
|
|
383
593
|
|
|
384
594
|
|
|
@@ -396,10 +606,11 @@ def format_json(candidates: list[Candidate]) -> str:
|
|
|
396
606
|
"file": candidate.path,
|
|
397
607
|
"kind": candidate.kind,
|
|
398
608
|
"score": candidate.score,
|
|
609
|
+
"confidence": candidate.confidence,
|
|
399
610
|
"load": candidate.load,
|
|
400
611
|
"priority": candidate.priority,
|
|
401
612
|
"reason": candidate.reasons,
|
|
402
|
-
"action":
|
|
613
|
+
"action": candidate.action,
|
|
403
614
|
}
|
|
404
615
|
for candidate in candidates
|
|
405
616
|
]
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"syncedAt": "2026-07-
|
|
2
|
+
"syncedAt": "2026-07-02T11:06:42.782Z",
|
|
3
3
|
"syncedFrom": "vendor/skill-garden",
|
|
4
|
-
"sourceCommit": "
|
|
4
|
+
"sourceCommit": "a70185e3b372058363296e81e2138f4f239d6e3b",
|
|
5
5
|
"common": {
|
|
6
6
|
"codexSkills": [
|
|
7
7
|
"craft-rpa",
|
|
8
8
|
"craft-slides",
|
|
9
9
|
"humanize-writing",
|
|
10
10
|
"open-idea",
|
|
11
|
-
"sub2api-account-json-fix",
|
|
12
11
|
"torrent-analyze"
|
|
13
12
|
],
|
|
14
13
|
"claudeSkills": [
|
|
@@ -16,7 +15,6 @@
|
|
|
16
15
|
"craft-slides",
|
|
17
16
|
"humanize-writing",
|
|
18
17
|
"open-idea",
|
|
19
|
-
"sub2api-account-json-fix",
|
|
20
18
|
"torrent-analyze"
|
|
21
19
|
]
|
|
22
20
|
},
|
|
@@ -140,6 +138,7 @@
|
|
|
140
138
|
],
|
|
141
139
|
"scripts": [
|
|
142
140
|
"auto_loop.py",
|
|
141
|
+
"push_snapshot.py",
|
|
143
142
|
"spec_router.py"
|
|
144
143
|
]
|
|
145
144
|
}
|
package/package.json
CHANGED
package/src/lib/copy-scripts.js
CHANGED
|
@@ -24,6 +24,8 @@ export function copyScriptAssets(target, variantDir, skills = []) {
|
|
|
24
24
|
let aliases = [];
|
|
25
25
|
if (name === "auto_loop") {
|
|
26
26
|
aliases = ["auto-loop", "auto-loop-runner", "trellis-auto-loop"];
|
|
27
|
+
} else if (name === "push_snapshot") {
|
|
28
|
+
aliases = ["push-snapshot", "trellis-push", "push", "snapshot"];
|
|
27
29
|
} else if (name === "spec_router") {
|
|
28
30
|
aliases = [
|
|
29
31
|
"spec-router",
|
package/src/lib/skill-catalog.js
CHANGED
|
@@ -40,7 +40,6 @@ const SKILL_DESCRIPTION_OVERRIDES = {
|
|
|
40
40
|
"plan-version": "规划版本任务、波次和分工",
|
|
41
41
|
push: "提交、推送并同步任务进度",
|
|
42
42
|
"re-implement": "需求变更后重新实现",
|
|
43
|
-
"sub2api-account-json-fix": "批量补全并推送 sub2api 账号 JSON",
|
|
44
43
|
"sync-prd": "根据代码或需求变化同步 PRD",
|
|
45
44
|
"torrent-analyze": "解析磁链或种子 hash 并整理信息",
|
|
46
45
|
"trellis-analyze-task": "深度分析并细化任务",
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: sub2api-account-json-fix
|
|
3
|
-
description: 修正当前项目里的 sub2api 账号导出 JSON,并把这些账号直接推送到 sub2api admin 接口。用于批量推送 `sub2api-account-*.json` 到远端 sub2api,尤其适合先同步模板里的 `model_mapping`、`allow_overages`,再按指定 group_ids 创建账号。
|
|
4
|
-
argument-hint: <template-json> [target-json...]
|
|
5
|
-
disable-model-invocation: true
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
# Sub2API 账号推送
|
|
9
|
-
|
|
10
|
-
先确认模板账号,再运行项目内的推送脚本。
|
|
11
|
-
|
|
12
|
-
## 规则
|
|
13
|
-
|
|
14
|
-
- 先用模板账号补齐目标账号的 `credentials.model_mapping`。
|
|
15
|
-
- 确保目标账号包含 `extra.allow_overages`。
|
|
16
|
-
- 把 `name` 改成 `credentials.email`。
|
|
17
|
-
- 然后把代理推送到 sub2api,再逐个创建账号。
|
|
18
|
-
- 创建账号后默认立刻关闭调度。
|
|
19
|
-
- 如果账号带有 `refresh_token`,创建后默认立刻调用一次 refresh,确保远端拿到最新 access token。
|
|
20
|
-
- 如果 `.claude/skills/sub2api-account-json-fix/env/push.env` 不存在,继续检查系统环境变量。
|
|
21
|
-
- 如果地址或 token 仍未配置,或者 token 还是 `admin-xxxx` 这种示例值,先向用户索要真实值,不要直接推送。
|
|
22
|
-
|
|
23
|
-
## 技能目录配置
|
|
24
|
-
|
|
25
|
-
优先读取:
|
|
26
|
-
|
|
27
|
-
- `.claude/skills/sub2api-account-json-fix/env/push.env`
|
|
28
|
-
|
|
29
|
-
支持这些键:
|
|
30
|
-
|
|
31
|
-
- `SUB2API_ACCOUNT_JSON_FIX_PUSH_ADMIN_BASE_URL`
|
|
32
|
-
- `SUB2API_ACCOUNT_JSON_FIX_PUSH_TOKEN`
|
|
33
|
-
- `SUB2API_ACCOUNT_JSON_FIX_PUSH_GROUP_IDS`
|
|
34
|
-
- `SUB2API_ACCOUNT_JSON_FIX_PUSH_SCHEDULABLE`
|
|
35
|
-
- `SUB2API_ACCOUNT_JSON_FIX_PUSH_REFRESH_AFTER_CREATE`
|
|
36
|
-
|
|
37
|
-
## 执行方式
|
|
38
|
-
|
|
39
|
-
如果用户给了参数,按“第一个参数是模板文件,后续参数是目标文件”的规则执行;否则先向用户确认模板文件。
|
|
40
|
-
|
|
41
|
-
运行:
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
bash .claude/skills/sub2api-account-json-fix/scripts/run.sh $ARGUMENTS
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
如果用户没有显式要求写回本地文件,就不要自动加 `--write`;先预览或直接推送内存中的规范化结果即可。
|
package/enhancements/common/.common/.claude/skills/sub2api-account-json-fix/env/push.env.example
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Claude Code 项目技能的本地配置示例。
|
|
2
|
-
# 把本文件复制为同目录下的 push.env 后再填写真实值。
|
|
3
|
-
|
|
4
|
-
SUB2API_ACCOUNT_JSON_FIX_PUSH_ADMIN_BASE_URL=http://127.0.0.1:8080/api/v1/admin
|
|
5
|
-
SUB2API_ACCOUNT_JSON_FIX_PUSH_TOKEN=admin-xxxx
|
|
6
|
-
SUB2API_ACCOUNT_JSON_FIX_PUSH_GROUP_IDS=3,7
|
|
7
|
-
SUB2API_ACCOUNT_JSON_FIX_PUSH_SCHEDULABLE=false
|
|
8
|
-
SUB2API_ACCOUNT_JSON_FIX_PUSH_REFRESH_AFTER_CREATE=true
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
5
|
-
CLAUDE_SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
6
|
-
SKILL_GARDEN_ROOT="$(cd "$CLAUDE_SKILL_DIR/../../.." && pwd -P)"
|
|
7
|
-
MAIN_SCRIPT="$SKILL_GARDEN_ROOT/.codex/skills/sub2api-account-json-fix/scripts/fix_exported_account_json.py"
|
|
8
|
-
LOCAL_ENV_FILE="$CLAUDE_SKILL_DIR/env/push.env"
|
|
9
|
-
|
|
10
|
-
export SUB2API_ACCOUNT_JSON_FIX_ENV_FILE="$LOCAL_ENV_FILE"
|
|
11
|
-
|
|
12
|
-
if [[ -f "$LOCAL_ENV_FILE" ]]; then
|
|
13
|
-
set -a
|
|
14
|
-
# shellcheck disable=SC1090
|
|
15
|
-
source "$LOCAL_ENV_FILE"
|
|
16
|
-
set +a
|
|
17
|
-
fi
|
|
18
|
-
|
|
19
|
-
if [[ $# -lt 1 ]]; then
|
|
20
|
-
echo "用法: run.sh <template-json> [target-json...]" >&2
|
|
21
|
-
exit 2
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
TEMPLATE_FILE="$1"
|
|
25
|
-
shift
|
|
26
|
-
|
|
27
|
-
python "$MAIN_SCRIPT" --template "$TEMPLATE_FILE" "$@" --push
|