feat(shared): 新增14個共用spec、models/todo工具與樣板產生器,收斂跨repo重複規範
依 todo.md 執行的規範治理專案:新增 spec-preflight 等 14 個共用規範(含 conventional-commit/pull-request/git-push/issue-read/todo-list/ask-user/ subagent/no-scratch-files/skill-invocation/script-path/action-scaffold/ node-src-layout/plugin-cli/model),擴充 spec-git-safety 與 spec-gitea(token 優先序、機密遮蔽、Wiki 頁名轉義規則);新增可執行 skill `models`(模型能力 查詢與標籤)與 `todo`(依指定模型產生/附加 todo.md);新增 plugin.meta.json 單一事實來源與 gen-plugin-files.mjs 樣板產生器,統一四個 repo 的 manifest/ README/AGENTS.md 並移除寫死的本機使用者路徑;新增 shared/scripts/lib 的 log/機密遮蔽三語言參考實作。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env node
|
||||
// ==============================================================================
|
||||
// 用途:時間戳與 log 格式的參考實作(Node/mjs 版)。對應 /jsc-shared:spec-time-log
|
||||
// 規範:更新時間一律 Asia/Taipei、固定 yyyy/MM/dd HH:mm:ss;輸出訊息一律
|
||||
// [時間][階段][等級]: 訊息、一行一則、走 stderr。本檔案不會被其他 repo
|
||||
// 跨 repo 直接 import,純供其他 repo 的腳本對照複製或標註對應關係
|
||||
// (等價邏輯抽自 persona/scripts/persona-lib.mjs 的 nowDisplay(),只取
|
||||
// 時間與 log 兩個函式,不耦合該檔其他情緒/記憶邏輯)。
|
||||
// 更新時間:2026/08/11 00:00:00
|
||||
// 相依:Node.js 標準內建功能,無外部套件。
|
||||
// ==============================================================================
|
||||
|
||||
const TAIPEI_OFFSET_MS = 8 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* 回傳 Asia/Taipei 時區的 yyyy/MM/dd HH:mm:ss 時間字串。
|
||||
* @param {Date} date 基準時間,預設為呼叫當下。
|
||||
* @returns {string}
|
||||
*/
|
||||
export function nowTaipei(date = new Date()) {
|
||||
const shifted = new Date(Math.floor(date.getTime() / 1000) * 1000 + TAIPEI_OFFSET_MS);
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
return `${shifted.getUTCFullYear()}/${pad(shifted.getUTCMonth() + 1)}/${pad(shifted.getUTCDate())} ${pad(shifted.getUTCHours())}:${pad(shifted.getUTCMinutes())}:${pad(shifted.getUTCSeconds())}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 輸出統一格式訊息([時間][階段][等級]: 訊息,一行一則),一律走 stderr。
|
||||
* @param {string} level INF/WRN/ERR/TRC/DBG。
|
||||
* @param {string} message 訊息內容。
|
||||
* @param {string} [stage] 階段名稱,選填;空字串則不輸出 [階段] 區塊。
|
||||
*/
|
||||
export function log(level, message, stage = "") {
|
||||
const stamp = nowTaipei();
|
||||
if (stage) {
|
||||
console.error(`[${stamp}][${stage}][${level}]: ${message}`);
|
||||
} else {
|
||||
console.error(`[${stamp}][${level}]: ${message}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
# ==============================================================================
|
||||
# 用途:時間戳與 log 格式的參考實作(Python 版)。對應 /jsc-shared:spec-time-log
|
||||
# 規範:更新時間一律 Asia/Taipei、固定 yyyy/MM/dd HH:mm:ss(以
|
||||
# zoneinfo.ZoneInfo 取得,不硬編 +8 offset,正確處理 DST/歷史時區變更);
|
||||
# 輸出訊息一律 [時間][階段][等級]: 訊息、一行一則、走 stderr。本檔案不會
|
||||
# 被其他 repo 跨 repo 直接 import,純供其他 repo 的腳本對照複製或標註
|
||||
# 對應關係(原始寫法見 doc/scripts/worklog/wiki_api.py 的 now_str()/log())。
|
||||
# 更新時間:2026/08/11 00:00:00
|
||||
# 相依:Python 3 標準庫(zoneinfo、datetime)。
|
||||
# ==============================================================================
|
||||
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
TAIPEI = ZoneInfo("Asia/Taipei")
|
||||
|
||||
|
||||
def now_taipei():
|
||||
"""取得台灣時區的 yyyy/MM/dd HH:mm:ss 時間字串。"""
|
||||
return datetime.now(TAIPEI).strftime("%Y/%m/%d %H:%M:%S")
|
||||
|
||||
|
||||
def log(level, message, stage=""):
|
||||
"""輸出統一格式訊息([時間][階段][等級]: 訊息,一行一則),一律走 stderr 不污染 stdout。"""
|
||||
if stage:
|
||||
print(f"[{now_taipei()}][{stage}][{level}]: {message}", file=sys.stderr)
|
||||
else:
|
||||
print(f"[{now_taipei()}][{level}]: {message}", file=sys.stderr)
|
||||
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env bash
|
||||
# ==============================================================================
|
||||
# 用途:時間戳與 log 格式的參考實作(bash 版)。對應 /jsc-shared:spec-time-log
|
||||
# 規範:更新時間一律 Asia/Taipei、固定 yyyy/MM/dd HH:mm:ss;輸出訊息一律
|
||||
# [時間][階段][等級]: 訊息、一行一則、走 stderr。本檔案不會被其他 repo
|
||||
# 跨 repo 直接 source/呼叫,純供其他 repo 的腳本對照複製或標註對應關係
|
||||
# (原始寫法見 doc/scripts/worklog/worklog.sh 第23-33行附近)。
|
||||
# 更新時間:2026/08/11 00:00:00
|
||||
# 相依:bash、coreutils date(需支援 TZ 環境變數)。
|
||||
# ==============================================================================
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# now_taipei:回傳 Asia/Taipei 時區的 yyyy/MM/dd HH:mm:ss 時間字串
|
||||
# ------------------------------------------------------------------------------
|
||||
now_taipei() {
|
||||
TZ='Asia/Taipei' date +'%Y/%m/%d %H:%M:%S'
|
||||
}
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# log:輸出統一格式訊息([時間][階段][等級]: 訊息,一行一則),一律走 stderr
|
||||
# $1 = level(INF/WRN/ERR/TRC/DBG)
|
||||
# $2 = message
|
||||
# $3 = stage(選填;空則不輸出 [階段] 區塊)
|
||||
# ------------------------------------------------------------------------------
|
||||
log() {
|
||||
local level="$1" message="$2" stage="${3:-}" stamp
|
||||
stamp="$(now_taipei)"
|
||||
if [ -n "$stage" ]; then
|
||||
printf '[%s][%s][%s]: %s\n' "$stamp" "$stage" "$level" "$message" >&2
|
||||
else
|
||||
printf '[%s][%s]: %s\n' "$stamp" "$level" "$message" >&2
|
||||
fi
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"_meta": {
|
||||
"用途": "機密遮蔽規則(9 條)的唯一權威資料來源,供 Python/JavaScript/Bash 三語言各自載入套用。任何實作異動一律先改本檔,再同步對應語言的實作。",
|
||||
"來源": "抽自 doc/scripts/worklog/transcript.py 的 REDACT_PATTERNS,並比對 persona/scripts/persona-lib.mjs 的 redactSecrets() 確認語意一致。",
|
||||
"語法規則": "pattern 一律使用三語言可直接使用的正則語法:不使用 Python 專屬語法特性(例如 (?i) inline flag、\\d 的 Unicode 語意),大小寫不分的規則改用顯式字元類別(如 [Tt][Oo][Kk][Ee][Nn])展開;數字一律用 [0-9] 而非 \\d。\\b/\\s/\\S 為 Python re、JavaScript RegExp 皆直接支援的語法,Bash 端以 GNU grep -E/sed -E(glibc regex,支援 \\b、\\s 等 GNU 擴充)套用。",
|
||||
"反向參照規則": "replacement 內的反向參照統一用 $1 風格(JavaScript String.replace/Bash sed 皆可直接使用)。Python 使用本檔時,套用前需先把 replacement 內的 $1、$2…轉成 \\1、\\2… 再交給 re.sub();transcript.py 的 REDACT_PATTERNS 保留原有 \\1 寫法不受本檔影響。",
|
||||
"更新時間": "2026/08/11 00:00:00"
|
||||
},
|
||||
"rules": [
|
||||
{
|
||||
"pattern": "[A-Za-z0-9_-]*:[A-Za-z0-9_-]{16,}@",
|
||||
"replacement": "***@",
|
||||
"purpose": "URL 內嵌憑證(user:token@ 形式)"
|
||||
},
|
||||
{
|
||||
"pattern": "\\b[0-9a-f]{40}\\b",
|
||||
"replacement": "***",
|
||||
"purpose": "Gitea 40 字元 hex token"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bgh[pousr]_[A-Za-z0-9_]{16,}\\b",
|
||||
"replacement": "***",
|
||||
"purpose": "GitHub token(ghp_/gho_/ghu_/ghs_/ghr_ 開頭)"
|
||||
},
|
||||
{
|
||||
"pattern": "\\bsk-[A-Za-z0-9_-]{16,}\\b",
|
||||
"replacement": "***",
|
||||
"purpose": "API key(sk- 開頭)"
|
||||
},
|
||||
{
|
||||
"pattern": "\\b([Tt][Oo][Kk][Ee][Nn]|[Pp][Aa][Ss][Ss][Ww][Oo][Rr][Dd]|[Pp][Aa][Ss][Ss][Ww][Dd]|[Pp][Ww][Dd]|[Ss][Ee][Cc][Rr][Ee][Tt]|[Aa][Pp][Ii][_-]?[Kk][Ee][Yy])\\b\\s*[:=]\\s*\\S+",
|
||||
"replacement": "$1=***",
|
||||
"purpose": "key=value 形式機密(token/password/passwd/pwd/secret/api_key,不分大小寫)"
|
||||
},
|
||||
{
|
||||
"pattern": "[Aa][Uu][Tt][Hh][Oo][Rr][Ii][Zz][Aa][Tt][Ii][Oo][Nn]:\\s*([Tt][Oo][Kk][Ee][Nn]|[Bb][Ee][Aa][Rr][Ee][Rr])\\s+\\S+",
|
||||
"replacement": "Authorization: $1 ***",
|
||||
"purpose": "HTTP Authorization 標頭(token/bearer,不分大小寫)"
|
||||
},
|
||||
{
|
||||
"pattern": "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}",
|
||||
"replacement": "***",
|
||||
"purpose": "Email 位址"
|
||||
},
|
||||
{
|
||||
"pattern": "\\b09[0-9]{2}[-\\s]?[0-9]{3}[-\\s]?[0-9]{3}\\b",
|
||||
"replacement": "***",
|
||||
"purpose": "台灣手機號碼"
|
||||
},
|
||||
{
|
||||
"pattern": "\\b[A-Z][12][0-9]{8}\\b",
|
||||
"replacement": "***",
|
||||
"purpose": "台灣身分證字號"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user