在准备环境前提交次全部更改。
This commit is contained in:
@@ -1,128 +0,0 @@
|
||||
# audit_flagger.ps1 — 判断 git 工作区是否存在高风险改动
|
||||
# 兼容 Windows PowerShell 5.1(避免 try{} 内嵌套脚本块的解析器 bug)
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
function Get-TaipeiNow {
|
||||
$tz = [TimeZoneInfo]::FindSystemTimeZoneById("Taipei Standard Time")
|
||||
if ($tz) {
|
||||
return [TimeZoneInfo]::ConvertTime([DateTimeOffset]::Now, $tz)
|
||||
}
|
||||
return [DateTimeOffset]::Now
|
||||
}
|
||||
|
||||
function Sha1Hex([string]$s) {
|
||||
$sha1 = [System.Security.Cryptography.SHA1]::Create()
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($s)
|
||||
$hash = $sha1.ComputeHash($bytes)
|
||||
return ([BitConverter]::ToString($hash) -replace "-", "").ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Get-ChangedFiles {
|
||||
$status = git status --porcelain 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { return @() }
|
||||
$result = @()
|
||||
foreach ($line in $status) {
|
||||
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
||||
$pathPart = $line.Substring([Math]::Min(3, $line.Length - 1)).Trim()
|
||||
if ($pathPart -match " -> ") { $pathPart = ($pathPart -split " -> ")[-1] }
|
||||
if ([string]::IsNullOrWhiteSpace($pathPart)) { continue }
|
||||
$result += $pathPart.Replace("\", "/").Trim()
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Test-NoiseFile([string]$f) {
|
||||
if ($f -match "^docs/audit/") { return $true }
|
||||
if ($f -match "^\.kiro/\.audit_state\.json$") { return $true }
|
||||
if ($f -match "^\.kiro/\.last_prompt_id\.json$") { return $true }
|
||||
if ($f -match "^\.kiro/scripts/") { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
function Write-AuditState([string]$json) {
|
||||
$null = New-Item -ItemType Directory -Force -Path ".kiro" 2>$null
|
||||
Set-Content -Path ".kiro/.audit_state.json" -Value $json -Encoding UTF8
|
||||
}
|
||||
|
||||
# --- 主逻辑 ---
|
||||
|
||||
# 非 git 仓库直接退出
|
||||
$null = git rev-parse --is-inside-work-tree 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { exit 0 }
|
||||
|
||||
$allFiles = Get-ChangedFiles
|
||||
# 过滤噪声
|
||||
$files = @()
|
||||
foreach ($f in $allFiles) {
|
||||
if (-not (Test-NoiseFile $f)) { $files += $f }
|
||||
}
|
||||
$files = $files | Sort-Object -Unique
|
||||
|
||||
$now = Get-TaipeiNow
|
||||
|
||||
if ($files.Count -eq 0) {
|
||||
$json = '{"audit_required":false,"db_docs_required":false,"reasons":[],"changed_files":[],"change_fingerprint":"","marked_at":"' + $now.ToString("o") + '","last_reminded_at":null}'
|
||||
Write-AuditState $json
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 高风险路径("regex|label" 格式,避免 @{} 哈希表在 PS 5.1 的解析问题)
|
||||
$riskRules = @(
|
||||
"^apps/etl/pipelines/feiqiu/(api|cli|config|database|loaders|models|orchestration|scd|tasks|utils|quality)/|etl",
|
||||
"^apps/backend/app/|backend",
|
||||
"^packages/shared/|shared",
|
||||
"^db/|db"
|
||||
)
|
||||
|
||||
$reasons = @()
|
||||
$auditRequired = $false
|
||||
$dbDocsRequired = $false
|
||||
|
||||
foreach ($f in $files) {
|
||||
foreach ($rule in $riskRules) {
|
||||
$idx = $rule.LastIndexOf("|")
|
||||
$pat = $rule.Substring(0, $idx)
|
||||
$lbl = $rule.Substring($idx + 1)
|
||||
if ($f -match $pat) {
|
||||
$auditRequired = $true
|
||||
$tag = "dir:" + $lbl
|
||||
if ($reasons -notcontains $tag) { $reasons += $tag }
|
||||
}
|
||||
}
|
||||
if ($f -notmatch "/") {
|
||||
$auditRequired = $true
|
||||
if ($reasons -notcontains "root-file") { $reasons += "root-file" }
|
||||
}
|
||||
if ($f -match "^db/" -or $f -match "/migrations/" -or $f -match "\.sql$" -or $f -match "\.prisma$") {
|
||||
$dbDocsRequired = $true
|
||||
if ($reasons -notcontains "db-schema-change") { $reasons += "db-schema-change" }
|
||||
}
|
||||
}
|
||||
|
||||
$fp = Sha1Hex(($files -join "`n"))
|
||||
|
||||
# 读取已有状态以保留 last_reminded_at
|
||||
$lastReminded = $null
|
||||
if (Test-Path ".kiro/.audit_state.json") {
|
||||
$raw = Get-Content ".kiro/.audit_state.json" -Raw 2>$null
|
||||
if ($raw) {
|
||||
$existing = $raw | ConvertFrom-Json 2>$null
|
||||
if ($existing -and $existing.change_fingerprint -eq $fp) {
|
||||
$lastReminded = $existing.last_reminded_at
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$stateObj = [ordered]@{
|
||||
audit_required = [bool]$auditRequired
|
||||
db_docs_required = [bool]$dbDocsRequired
|
||||
reasons = $reasons
|
||||
changed_files = $files | Select-Object -First 50
|
||||
change_fingerprint = $fp
|
||||
marked_at = $now.ToString("o")
|
||||
last_reminded_at = $lastReminded
|
||||
}
|
||||
|
||||
Write-AuditState ($stateObj | ConvertTo-Json -Depth 6)
|
||||
exit 0
|
||||
165
.kiro/scripts/audit_flagger.py
Normal file
165
.kiro/scripts/audit_flagger.py
Normal file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""audit_flagger — 判断 git 工作区是否存在高风险改动,写入 .kiro/.audit_state.json
|
||||
|
||||
替代原 PowerShell 版本,避免 Windows PowerShell 5.1 解析器 bug。
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
TZ_TAIPEI = timezone(timedelta(hours=8))
|
||||
|
||||
RISK_RULES = [
|
||||
(re.compile(r"^apps/etl/pipelines/feiqiu/(api|cli|config|database|loaders|models|orchestration|scd|tasks|utils|quality)/"), "etl"),
|
||||
(re.compile(r"^apps/backend/app/"), "backend"),
|
||||
(re.compile(r"^apps/admin-web/src/"), "admin-web"),
|
||||
(re.compile(r"^apps/miniprogram/(miniapp|miniprogram)/"), "miniprogram"),
|
||||
(re.compile(r"^packages/shared/"), "shared"),
|
||||
(re.compile(r"^db/"), "db"),
|
||||
]
|
||||
|
||||
NOISE_PATTERNS = [
|
||||
re.compile(r"^docs/audit/"),
|
||||
re.compile(r"^\.kiro/"), # .kiro 配置变更不触发业务审计
|
||||
re.compile(r"^tmp/"),
|
||||
re.compile(r"^\.hypothesis/"),
|
||||
]
|
||||
|
||||
DB_PATTERNS = [
|
||||
re.compile(r"^db/"),
|
||||
re.compile(r"/migrations/"),
|
||||
re.compile(r"\.sql$"),
|
||||
re.compile(r"\.prisma$"),
|
||||
]
|
||||
|
||||
STATE_PATH = os.path.join(".kiro", ".audit_state.json")
|
||||
|
||||
|
||||
def now_taipei():
|
||||
return datetime.now(TZ_TAIPEI).isoformat()
|
||||
|
||||
|
||||
def sha1hex(s: str) -> str:
|
||||
return hashlib.sha1(s.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def get_changed_files() -> list[str]:
|
||||
"""从 git status --porcelain 提取变更文件路径"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "status", "--porcelain"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
if result.returncode != 0:
|
||||
return []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
files = []
|
||||
for line in result.stdout.splitlines():
|
||||
if len(line) < 4:
|
||||
continue
|
||||
path = line[3:].strip()
|
||||
if " -> " in path:
|
||||
path = path.split(" -> ")[-1]
|
||||
path = path.strip().strip('"').replace("\\", "/")
|
||||
if path:
|
||||
files.append(path)
|
||||
return files
|
||||
|
||||
|
||||
def is_noise(f: str) -> bool:
|
||||
return any(p.search(f) for p in NOISE_PATTERNS)
|
||||
|
||||
|
||||
def write_state(state: dict):
|
||||
os.makedirs(".kiro", exist_ok=True)
|
||||
with open(STATE_PATH, "w", encoding="utf-8") as fh:
|
||||
json.dump(state, fh, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def main():
|
||||
# 非 git 仓库直接退出
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["git", "rev-parse", "--is-inside-work-tree"],
|
||||
capture_output=True, text=True, timeout=5
|
||||
)
|
||||
if r.returncode != 0:
|
||||
return
|
||||
except Exception:
|
||||
return
|
||||
|
||||
all_files = get_changed_files()
|
||||
files = sorted(set(f for f in all_files if not is_noise(f)))
|
||||
now = now_taipei()
|
||||
|
||||
if not files:
|
||||
write_state({
|
||||
"audit_required": False,
|
||||
"db_docs_required": False,
|
||||
"reasons": [],
|
||||
"changed_files": [],
|
||||
"change_fingerprint": "",
|
||||
"marked_at": now,
|
||||
"last_reminded_at": None,
|
||||
})
|
||||
return
|
||||
|
||||
reasons = []
|
||||
audit_required = False
|
||||
db_docs_required = False
|
||||
|
||||
for f in files:
|
||||
for pattern, label in RISK_RULES:
|
||||
if pattern.search(f):
|
||||
audit_required = True
|
||||
tag = f"dir:{label}"
|
||||
if tag not in reasons:
|
||||
reasons.append(tag)
|
||||
# 根目录散文件
|
||||
if "/" not in f:
|
||||
audit_required = True
|
||||
if "root-file" not in reasons:
|
||||
reasons.append("root-file")
|
||||
# DB 文档触发
|
||||
if any(p.search(f) for p in DB_PATTERNS):
|
||||
db_docs_required = True
|
||||
if "db-schema-change" not in reasons:
|
||||
reasons.append("db-schema-change")
|
||||
|
||||
fp = sha1hex("\n".join(files))
|
||||
|
||||
# 保留已有状态的 last_reminded_at
|
||||
last_reminded = None
|
||||
if os.path.isfile(STATE_PATH):
|
||||
try:
|
||||
with open(STATE_PATH, "r", encoding="utf-8") as fh:
|
||||
existing = json.load(fh)
|
||||
if existing.get("change_fingerprint") == fp:
|
||||
last_reminded = existing.get("last_reminded_at")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
write_state({
|
||||
"audit_required": audit_required,
|
||||
"db_docs_required": db_docs_required,
|
||||
"reasons": reasons,
|
||||
"changed_files": files[:50],
|
||||
"change_fingerprint": fp,
|
||||
"marked_at": now,
|
||||
"last_reminded_at": last_reminded,
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
# 绝不阻塞 prompt 提交
|
||||
pass
|
||||
@@ -1,72 +0,0 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-TaipeiNow {
|
||||
try {
|
||||
$tz = [TimeZoneInfo]::FindSystemTimeZoneById("Taipei Standard Time")
|
||||
return [TimeZoneInfo]::ConvertTime([DateTimeOffset]::Now, $tz)
|
||||
} catch {
|
||||
return [DateTimeOffset]::Now
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$statePath = ".kiro/.audit_state.json"
|
||||
if (-not (Test-Path $statePath)) { exit 0 }
|
||||
|
||||
$state = $null
|
||||
try { $state = Get-Content $statePath -Raw | ConvertFrom-Json } catch { exit 0 }
|
||||
if (-not $state) { exit 0 }
|
||||
|
||||
# If no pending audit, do nothing
|
||||
if (-not $state.audit_required) { exit 0 }
|
||||
|
||||
# If working tree is clean (ignoring logs/state), stop reminding
|
||||
$null = git rev-parse --is-inside-work-tree 2>$null
|
||||
if ($LASTEXITCODE -ne 0) { exit 0 }
|
||||
$status = git status --porcelain
|
||||
$files = @()
|
||||
foreach ($line in $status) {
|
||||
if ([string]::IsNullOrWhiteSpace($line)) { continue }
|
||||
$pathPart = $line.Substring([Math]::Min(3, $line.Length-1)).Trim()
|
||||
if ($pathPart -match " -> ") { $pathPart = ($pathPart -split " -> ")[-1] }
|
||||
$p = $pathPart.Replace("\", "/").Trim()
|
||||
if ($p -and $p -notmatch "^docs/audit/" -and $p -notmatch "^\.kiro/") { $files += $p }
|
||||
}
|
||||
if (($files | Sort-Object -Unique).Count -eq 0) {
|
||||
$state.audit_required = $false
|
||||
$state.reasons = @()
|
||||
$state.changed_files = @()
|
||||
$state.last_reminded_at = $null
|
||||
($state | ConvertTo-Json -Depth 6) | Set-Content -Path $statePath -Encoding UTF8
|
||||
exit 0
|
||||
}
|
||||
|
||||
$now = Get-TaipeiNow
|
||||
$minInterval = [TimeSpan]::FromMinutes(15)
|
||||
|
||||
$last = $null
|
||||
if ($state.last_reminded_at) {
|
||||
try { $last = [DateTimeOffset]::Parse($state.last_reminded_at) } catch { $last = $null }
|
||||
}
|
||||
|
||||
$shouldRemind = $true
|
||||
if ($last) {
|
||||
$elapsed = $now - $last
|
||||
if ($elapsed -lt $minInterval) { $shouldRemind = $false }
|
||||
}
|
||||
|
||||
if (-not $shouldRemind) { exit 0 }
|
||||
|
||||
# Update last_reminded_at (persist even if user ignores)
|
||||
$state.last_reminded_at = $now.ToString("o")
|
||||
($state | ConvertTo-Json -Depth 6) | Set-Content -Path $statePath -Encoding UTF8
|
||||
|
||||
$reasons = @()
|
||||
if ($state.reasons) { $reasons = @($state.reasons) }
|
||||
$reasonText = if ($reasons.Count -gt 0) { ($reasons -join ", ") } else { "high-risk paths changed" }
|
||||
|
||||
[Console]::Error.WriteLine("[AUDIT REMINDER] Pending audit detected ($reasonText). Run /audit (Manual: Run /audit hook) to sync docs & write audit artifacts. (rate limit: 15min)")
|
||||
exit 1
|
||||
} catch {
|
||||
exit 0
|
||||
}
|
||||
107
.kiro/scripts/audit_reminder.py
Normal file
107
.kiro/scripts/audit_reminder.py
Normal file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
"""audit_reminder — Agent 结束时检查是否有待审计改动,15 分钟限频提醒。
|
||||
|
||||
替代原 PowerShell 版本,避免 Windows PowerShell 5.1 解析器 bug。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
TZ_TAIPEI = timezone(timedelta(hours=8))
|
||||
STATE_PATH = os.path.join(".kiro", ".audit_state.json")
|
||||
MIN_INTERVAL = timedelta(minutes=15)
|
||||
|
||||
|
||||
def now_taipei():
|
||||
return datetime.now(TZ_TAIPEI)
|
||||
|
||||
|
||||
def load_state():
|
||||
if not os.path.isfile(STATE_PATH):
|
||||
return None
|
||||
try:
|
||||
with open(STATE_PATH, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def save_state(state):
|
||||
os.makedirs(".kiro", exist_ok=True)
|
||||
with open(STATE_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def get_real_changes():
|
||||
"""获取排除噪声后的变更文件"""
|
||||
try:
|
||||
r = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True, timeout=10)
|
||||
if r.returncode != 0:
|
||||
return []
|
||||
except Exception:
|
||||
return []
|
||||
files = []
|
||||
for line in r.stdout.splitlines():
|
||||
if len(line) < 4:
|
||||
continue
|
||||
path = line[3:].strip().strip('"').replace("\\", "/")
|
||||
if " -> " in path:
|
||||
path = path.split(" -> ")[-1]
|
||||
# 排除审计产物、.kiro 配置、临时文件
|
||||
if path and not path.startswith("docs/audit/") and not path.startswith(".kiro/") and not path.startswith("tmp/") and not path.startswith(".hypothesis/"):
|
||||
files.append(path)
|
||||
return sorted(set(files))
|
||||
|
||||
|
||||
def main():
|
||||
state = load_state()
|
||||
if not state:
|
||||
sys.exit(0)
|
||||
|
||||
if not state.get("audit_required"):
|
||||
sys.exit(0)
|
||||
|
||||
# 工作树干净时清除审计状态
|
||||
real_files = get_real_changes()
|
||||
if not real_files:
|
||||
state["audit_required"] = False
|
||||
state["reasons"] = []
|
||||
state["changed_files"] = []
|
||||
state["last_reminded_at"] = None
|
||||
save_state(state)
|
||||
sys.exit(0)
|
||||
|
||||
now = now_taipei()
|
||||
|
||||
# 15 分钟限频
|
||||
last_str = state.get("last_reminded_at")
|
||||
if last_str:
|
||||
try:
|
||||
last = datetime.fromisoformat(last_str)
|
||||
if (now - last) < MIN_INTERVAL:
|
||||
sys.exit(0)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 更新提醒时间
|
||||
state["last_reminded_at"] = now.isoformat()
|
||||
save_state(state)
|
||||
|
||||
reasons = state.get("reasons", [])
|
||||
reason_text = ", ".join(reasons) if reasons else "high-risk paths changed"
|
||||
sys.stderr.write(
|
||||
f"[AUDIT REMINDER] Pending audit detected ({reason_text}). "
|
||||
f"Run /audit (Manual: Run /audit hook) to sync docs & write audit artifacts. "
|
||||
f"(rate limit: 15min)\n"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
sys.exit(0)
|
||||
@@ -1,61 +0,0 @@
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-TaipeiNow {
|
||||
try {
|
||||
$tz = [TimeZoneInfo]::FindSystemTimeZoneById("Taipei Standard Time")
|
||||
return [TimeZoneInfo]::ConvertTime([DateTimeOffset]::Now, $tz)
|
||||
} catch {
|
||||
return [DateTimeOffset]::Now
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$now = Get-TaipeiNow
|
||||
$promptId = "P{0}" -f $now.ToString("yyyyMMdd-HHmmss")
|
||||
$promptRaw = $env:USER_PROMPT
|
||||
if ($null -eq $promptRaw) { $promptRaw = "" }
|
||||
|
||||
# 截断过长的 prompt(避免意外记录展开的 #context)
|
||||
if ($promptRaw.Length -gt 20000) {
|
||||
$promptRaw = $promptRaw.Substring(0, 5000) + "`n[TRUNCATED: prompt too long; possible expanded #context]"
|
||||
}
|
||||
|
||||
$summary = ($promptRaw -replace "\s+", " ").Trim()
|
||||
if ($summary.Length -gt 120) { $summary = $summary.Substring(0, 120) + "…" }
|
||||
if ([string]::IsNullOrWhiteSpace($summary)) { $summary = "(empty prompt)" }
|
||||
|
||||
# CHANGE [2026-02-15] intent: 简化为每次直接写独立文件到 prompt_logs/,不再维护 prompt_log.md 中间文件
|
||||
$logDir = Join-Path "docs" "audit" "prompt_logs"
|
||||
$null = New-Item -ItemType Directory -Force -Path $logDir 2>$null
|
||||
|
||||
$filename = "prompt_log_{0}.md" -f $now.ToString("yyyyMMdd_HHmmss")
|
||||
$targetLog = Join-Path $logDir $filename
|
||||
|
||||
$timestamp = $now.ToString("yyyy-MM-dd HH:mm:ss zzz")
|
||||
$entry = @"
|
||||
- [$promptId] $timestamp
|
||||
- summary: $summary
|
||||
- prompt:
|
||||
``````text
|
||||
$promptRaw
|
||||
``````
|
||||
|
||||
"@
|
||||
|
||||
Set-Content -Path $targetLog -Value $entry -Encoding UTF8
|
||||
|
||||
# 保存 last prompt id 供下游 /audit 溯源
|
||||
$stateDir = ".kiro"
|
||||
$null = New-Item -ItemType Directory -Force -Path $stateDir 2>$null
|
||||
$lastPrompt = @{
|
||||
prompt_id = $promptId
|
||||
at = $now.ToString("o")
|
||||
} | ConvertTo-Json -Depth 4
|
||||
|
||||
Set-Content -Path (Join-Path $stateDir ".last_prompt_id.json") -Value $lastPrompt -Encoding UTF8
|
||||
|
||||
exit 0
|
||||
} catch {
|
||||
# 不阻塞 prompt 提交
|
||||
exit 0
|
||||
}
|
||||
60
.kiro/scripts/prompt_audit_log.py
Normal file
60
.kiro/scripts/prompt_audit_log.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""prompt_audit_log — 每次提交 prompt 时生成独立日志文件。
|
||||
|
||||
替代原 PowerShell 版本,避免 Windows PowerShell 5.1 解析器 bug。
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone, timedelta
|
||||
|
||||
TZ_TAIPEI = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def main():
|
||||
now = datetime.now(TZ_TAIPEI)
|
||||
prompt_id = f"P{now.strftime('%Y%m%d-%H%M%S')}"
|
||||
prompt_raw = os.environ.get("USER_PROMPT", "")
|
||||
|
||||
# 截断过长的 prompt(避免展开的 #context 占用过多空间)
|
||||
if len(prompt_raw) > 20000:
|
||||
prompt_raw = prompt_raw[:5000] + "\n[TRUNCATED: prompt too long; possible expanded #context]"
|
||||
|
||||
summary = " ".join(prompt_raw.split()).strip()
|
||||
if len(summary) > 120:
|
||||
summary = summary[:120] + "…"
|
||||
if not summary:
|
||||
summary = "(empty prompt)"
|
||||
|
||||
# 写独立日志文件
|
||||
log_dir = os.path.join("docs", "audit", "prompt_logs")
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
filename = f"prompt_log_{now.strftime('%Y%m%d_%H%M%S')}.md"
|
||||
target = os.path.join(log_dir, filename)
|
||||
|
||||
timestamp = now.strftime("%Y-%m-%d %H:%M:%S %z")
|
||||
entry = f"""- [{prompt_id}] {timestamp}
|
||||
- summary: {summary}
|
||||
- prompt:
|
||||
```text
|
||||
{prompt_raw}
|
||||
```
|
||||
"""
|
||||
with open(target, "w", encoding="utf-8") as f:
|
||||
f.write(entry)
|
||||
|
||||
# 保存 last prompt id 供 /audit 溯源
|
||||
os.makedirs(".kiro", exist_ok=True)
|
||||
last_prompt = {"prompt_id": prompt_id, "at": now.isoformat()}
|
||||
with open(os.path.join(".kiro", ".last_prompt_id.json"), "w", encoding="utf-8") as f:
|
||||
json.dump(last_prompt, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
# 不阻塞 prompt 提交
|
||||
pass
|
||||
Reference in New Issue
Block a user