sillyspec 3.33.6 → 3.33.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sillyspec",
3
- "version": "3.33.6",
3
+ "version": "3.33.7",
4
4
  "description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
5
5
  "icon": "logo.jpg",
6
6
  "homepage": "https://sillyspec.ppdmq.top/",
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "license": "MIT",
26
26
  "scripts": {
27
- "test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs test/knowledge-graph.test.mjs test/explore-graph-guidance.test.mjs test/git-helper-input.test.mjs test/push-plan.test.mjs test/push-cli.test.mjs test/push-skill.test.mjs test/flow-guidance.test.mjs",
27
+ "test:core": "node --test test/preview-migration.test.mjs test/preview-progress.test.mjs test/preview-outlet.test.mjs test/preview-gc.test.mjs test/preview-gate-isolation.test.mjs test/db-engine.test.mjs test/db-concurrency.test.mjs test/db-atomic-write.test.mjs test/platform-sync-schema.test.mjs test/platform-sync-serialization.test.mjs test/change-ownership-guards.test.mjs test/watcher.test.mjs test/watcher-alerts.test.mjs test/watcher-timeline.test.mjs test/sentinel-rules.test.mjs test/handoff.test.mjs test/execution-mode-render.test.mjs test/knowledge-inbox.test.mjs test/knowledge-baseline.test.mjs test/quick-laststep-fourfields-preview.test.mjs test/doctor-align-execute-progress.test.mjs test/test-ledger.test.mjs test/test-bindings.test.mjs test/flow-draft-binding-extract.test.mjs test/gate-snapshot-lifecycle.test.mjs test/gate-snapshot-cleanup.test.mjs test/verify-trace-residual.test.mjs test/platform-creds-fallback.test.mjs test/archive-timeline-bake.test.mjs test/doc-ref-check.test.mjs test/flow-status-json.test.mjs test/flow-status-title.test.mjs test/flow-resume-title.test.mjs test/datetime-timeago.test.mjs test/resume-domain-flip.test.mjs test/fr-regress-cap-drop.test.mjs test/fr-priority-overlap.test.mjs test/archive-commit-suggest-race.test.mjs test/flow-done-fail-brief.test.mjs test/verify-probes-refresh-backup.test.mjs test/taskcard-yaml-triage-validate.test.mjs test/gate-last-reader.test.mjs test/status-multi-active-list.test.mjs test/gate-docs-only.test.mjs test/probe7-fr-prefill.test.mjs test/plan-fill-batch-config.test.mjs test/step-guide-prune.test.mjs test/gate-stage-review-hint.test.mjs test/design-ref-suggest.test.mjs test/flow-friction-batch3.test.mjs test/wave-auto-adopt.test.mjs test/test-incremental-rerun.test.mjs test/target-files-main-docs-rescue.test.mjs test/knowledge-graph.test.mjs test/explore-graph-guidance.test.mjs test/git-helper-input.test.mjs test/push-plan.test.mjs test/push-cli.test.mjs test/push-skill.test.mjs test/flow-guidance.test.mjs",
28
28
  "test": "node test/run-tests.mjs",
29
29
  "lint": "node test/check-syntax.mjs",
30
30
  "eval:dry": "node eval/run.mjs --dry-run",
@@ -994,7 +994,10 @@ export function detectAgentLogEntries(opts) {
994
994
  * specBase/local.yaml platform 段(url + token,与链路 A 同源)。
995
995
  * @returns {{url:string, token:string}|null} 未配置返回 null(合法状态,静默跳过上报)
996
996
  */
997
- function readPlatformPushConfig(specBase, env = process.env) {
997
+ export function readPlatformPushConfig(specBase, env = process.env) {
998
+ // 2026-10-28-knowledge-hits-cli-push:export 化(签名与行为零变化)——
999
+ // knowledge-hits.js 写后直报复用同一配置解析(单一源,不复制 local.yaml
1000
+ // 解析逻辑)。
998
1001
  if (env.SILLYHUB_PLATFORM_URL && env.SILLYHUB_PLATFORM_TOKEN) {
999
1002
  return { url: env.SILLYHUB_PLATFORM_URL, token: env.SILLYHUB_PLATFORM_TOKEN };
1000
1003
  }
@@ -14,16 +14,92 @@
14
14
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'fs'
15
15
  import { runtimeStatePath } from './runtime-paths.js'
16
16
  import { join } from 'path'
17
+ import { readPlatformPushConfig } from './agent-session-log.js'
17
18
 
18
19
  const HITS_FILENAME = 'knowledge-hits.jsonl'
19
20
 
21
+ // 直报超时上限(2026-10-28-knowledge-hits-cli-push):对齐 agent-log PUSH_TIMEOUT_MS
22
+ // 量级——最坏情况下进程退出被活跃 socket 拖住的时长有界。
23
+ const HITS_PUSH_TIMEOUT_MS = 10_000
24
+
20
25
  function hitsFilePath(runtimeDir) {
21
26
  return runtimeStatePath(runtimeDir, HITS_FILENAME)
22
27
  }
23
28
 
29
+ /**
30
+ * runtimeDir → specBase 反推(剥尾 `.runtime` 段;跨平台:正反斜杠与尾分隔符容忍)。
31
+ *
32
+ * 全部 appendKnowledgeHit 调用形态的 runtimeDir 最终都是 `<specBase>/.runtime`
33
+ * (join(specBase,'.runtime') 或 resolveRuntimeRoot 产物)——统一剥最后一段反推
34
+ * specBase 供平台配置读取(local.yaml 在 specBase 根)。结果保留输入的分隔符
35
+ * 形态(不做平台归一——Node fs 对 / 与 \ 双向兼容,归一反而扭曲 POSIX 形态输入)。
36
+ *
37
+ * @param {string} runtimeDir
38
+ * @returns {string} specBase(runtimeDir 末段不是 .runtime 时原样返回——容错面)
39
+ */
40
+ export function specBaseFromRuntimeDir(runtimeDir) {
41
+ const raw = String(runtimeDir)
42
+ const norm = raw.replace(/\\/g, '/').replace(/\/+$/, '')
43
+ const idx = norm.lastIndexOf('/')
44
+ const last = idx === -1 ? norm : norm.slice(idx + 1)
45
+ if (last !== '.runtime') return runtimeDir
46
+ // \→/ 等长替换、去尾 / 只动末尾——norm 前 idx 字符与原串位置一一对应,
47
+ // 直接按剥点切片即保留原分隔符形态的 specBase。
48
+ return raw.slice(0, idx)
49
+ }
50
+
51
+ /**
52
+ * 单行遥测直报平台(2026-10-28-knowledge-hits-cli-push FR-01)。
53
+ *
54
+ * POST {url}/api/changes/-/hits-sync(Bearer shpsync_ 令牌——workspace 由令牌派生),
55
+ * body {lines:[line], daemon_local_id:'cli'};接收端行级幂等(行 sha256 去重,
56
+ * 平台仓 2026-10-28-knowledge-hits-cli-direct)。best-effort 语义照
57
+ * pushAgentLogToPlatform 先例:无配置/被禁用静默跳过(返回 pushed:null);
58
+ * 网络/非 2xx/超时 warn 一行不抛(返回 pushed:false, reason)——本地行已落盘、
59
+ * daemon 周期上行与平台按钮 flush 为兜底通道。
60
+ *
61
+ * @param {object} p
62
+ * @param {string} p.specBase - local.yaml 所在 spec 基路径
63
+ * @param {object} [p.env] - env(默认 process.env;测试注入用)
64
+ * @param {string} p.line - 已落盘的遥测行(JSON 字符串)
65
+ * @returns {Promise<{pushed:boolean|null, reason?:string}>}
66
+ */
67
+ export async function pushKnowledgeHitToPlatform({ specBase, env = process.env, line }) {
68
+ if (env.SILLYSPEC_HITS_PUSH === '0') {
69
+ return { pushed: null, reason: 'disabled' }
70
+ }
71
+ const cfg = readPlatformPushConfig(specBase, env)
72
+ if (!cfg) return { pushed: null, reason: 'no-config' }
73
+ const controller = new AbortController()
74
+ const timer = setTimeout(() => controller.abort(), HITS_PUSH_TIMEOUT_MS)
75
+ try {
76
+ const res = await fetch(`${cfg.url.replace(/\/+$/, '')}/api/changes/-/hits-sync`, {
77
+ method: 'POST',
78
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${cfg.token}` },
79
+ body: JSON.stringify({ lines: [line], daemon_local_id: 'cli' }),
80
+ signal: controller.signal,
81
+ })
82
+ if (!res.ok) {
83
+ console.warn(`[knowledge-hits-push] → HTTP ${res.status}(本地行已落盘,daemon 兜底)`)
84
+ return { pushed: false, reason: `http-${res.status}` }
85
+ }
86
+ return { pushed: true }
87
+ } catch (err) {
88
+ const msg = err && err.name === 'AbortError' ? '超时' : (err && err.message ? err.message : err)
89
+ console.warn(`[knowledge-hits-push] 直报失败: ${msg}(本地行已落盘,daemon 兜底)`)
90
+ return { pushed: false, reason: 'network' }
91
+ } finally {
92
+ clearTimeout(timer)
93
+ }
94
+ }
95
+
24
96
  /**
25
97
  * 追加一条遥测记录(目录/文件缺失自动创建)。
26
98
  *
99
+ * 2026-10-28-knowledge-hits-cli-push:落盘成功后 void fire 平台直报(FR-01/02)——
100
+ * 不 await 不抛(失败 warn 一行由 pushKnowledgeHitToPlatform 内部收口),主流程
101
+ * 同步语义与返回值不变;无配置/被禁用时零行为差异。
102
+ *
27
103
  * @param {string} runtimeDir - .runtime 目录
28
104
  * @param {object} hit - 记录对象,常用字段 { type:'inject'|'classify', change, query, matchedFiles, at }
29
105
  * (at 缺省时补当前 ISO 时间;其余字段原样序列化,classify 的 qlId/targetFile 等照写)
@@ -35,7 +111,10 @@ export function appendKnowledgeHit(runtimeDir, hit) {
35
111
  record.at = new Date().toISOString()
36
112
  }
37
113
  mkdirSync(runtimeDir, { recursive: true })
38
- appendFileSync(hitsFilePath(runtimeDir), JSON.stringify(record) + '\n', 'utf8')
114
+ const line = JSON.stringify(record)
115
+ appendFileSync(hitsFilePath(runtimeDir), line + '\n', 'utf8')
116
+ // 写后直报(void fire):本地已落盘,直报成败不影响本函数语义(FR-02)。
117
+ void pushKnowledgeHitToPlatform({ specBase: specBaseFromRuntimeDir(runtimeDir), line }).catch(() => {})
39
118
  return record
40
119
  }
41
120
 
@@ -4806,6 +4806,51 @@ export function reconcileTargetFiles({ cwd, specBase = null, changeName = null,
4806
4806
  if (rescuedCount > 0) {
4807
4807
  notes.push(`对账救赎:${rescuedCount} 个声明文件经近窗提交触及面命中(isolated worktree per-task commit 形态,porcelain 恒空——R9/R11 四连拦根治)`)
4808
4808
  }
4809
+ // 主仓治理文档救赎(坑 target-files-worktree-docs-mismatch,2026-10-26-session-stage-progress
4810
+ // verify 实证):模块卡等主仓 .sillyspec/ 文档按 CLI 铁律「一律写主仓,绝不写 worktree 副本」
4811
+ // ——worktree 形态 actual(wt diff+porcelain)结构性看不见主仓侧改动,声明主仓治理路径恒落
4812
+ // ②类假红(「写 worktree=产物蒸发,写主仓=对账 missing」两规则打架)。救赎面=主仓 .sillyspec/
4813
+ // 的 status(未提交)∪ baseline..HEAD diff(已提交),仅救赎「声明而 missing」的主仓治理路径
4814
+ // (不扩 undeclared 面——与 R9/R11 窗口救赎同语义)。fail-open:主仓读取失败走既有 missing 判定。
4815
+ if (actual.form === 'worktree') {
4816
+ const govDeclared = declaredPaths.filter((p) => {
4817
+ const clean = String(p).replace(/\\/g, '/').replace(/^NEW:/, '')
4818
+ return clean.startsWith('.sillyspec/')
4819
+ })
4820
+ if (govDeclared.length > 0) {
4821
+ let mainDocsRescued = 0
4822
+ try {
4823
+ const mainFace = new Set()
4824
+ // -uall:未跟踪目录缺省折叠成目录条目(?? .sillyspec/docs/<proj>/ 整目录一行)——
4825
+ // 模块卡常为新建文件,折叠形态对不上文件级声明,逐文件展开(scope-audit 同款口径)
4826
+ const st = gitQuiet(cwd, ['status', '--porcelain', '--untracked-files=all', '--', '.sillyspec/'], { timeout: 30000, trim: false })
4827
+ for (const line of String(st || '').split('\n')) {
4828
+ if (!line || line.length < 4) continue
4829
+ const p = line.slice(3).trim().replace(/^"|"$/g, '')
4830
+ const arrow = p.indexOf(' -> ')
4831
+ const f = (arrow !== -1 ? p.slice(arrow + 4) : p).replace(/\\/g, '/')
4832
+ if (f) mainFace.add(pathKey(f))
4833
+ }
4834
+ if (actual.baseAnchor) {
4835
+ const df = gitQuiet(cwd, ['diff', '--name-only', `${actual.baseAnchor}..HEAD`, '--', '.sillyspec/'], { timeout: 30000 })
4836
+ for (const f of String(df || '').split('\n')) {
4837
+ const t = f.trim().replace(/\\/g, '/')
4838
+ if (t) mainFace.add(pathKey(t))
4839
+ }
4840
+ }
4841
+ for (const p of govDeclared) {
4842
+ const clean = String(p).replace(/\\/g, '/').replace(/^NEW:/, '')
4843
+ if (!actualKeySet.has(pathKey(p)) && mainFace.has(pathKey(clean))) {
4844
+ actualKeySet.add(pathKey(p))
4845
+ mainDocsRescued++
4846
+ }
4847
+ }
4848
+ } catch { /* 主仓治理面读取失败 → 走既有 missing 判定(fail-open 不新造阻断) */ }
4849
+ if (mainDocsRescued > 0) {
4850
+ notes.push(`主仓治理文档救赎:${mainDocsRescued} 个 .sillyspec/ 声明经主仓实际改动面命中(worktree 对账面看不见主仓侧模块卡等治理文档——「治理文档一律写主仓」铁律与 worktree actual 的结构性错配,2026-10-26 实证)`)
4851
+ }
4852
+ }
4853
+ }
4809
4854
  // ①交集(计数进 evidence):唯一路径口径(多卡声明同文件只计一次)
4810
4855
  for (const path of declaredPaths) {
4811
4856
  if (actualKeySet.has(pathKey(path))) matched.push(path)