@modusensus/dsh-mneme 0.9.0 → 0.9.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.
Files changed (88) hide show
  1. package/README.md +78 -16
  2. package/bin/cli.mjs +8 -2
  3. package/docs/CONFIGURATION.md +25 -0
  4. package/docs/SEMANTIC.md +3 -3
  5. package/lib/api-standalone.js +78 -12
  6. package/lib/api.js +282 -57
  7. package/lib/candidates.js +454 -454
  8. package/lib/client.js +598 -248
  9. package/lib/config.js +33 -2
  10. package/lib/embedding.js +116 -15
  11. package/lib/index.js +32 -0
  12. package/lib/inject.js +34 -3
  13. package/lib/lang.js +124 -0
  14. package/lib/pipeline-activities.js +178 -0
  15. package/lib/pipeline-attribution.js +165 -0
  16. package/lib/pipeline-command.js +279 -0
  17. package/lib/pipeline-confirm.js +142 -0
  18. package/lib/pipeline-exec.js +174 -0
  19. package/lib/pipeline-nudge.js +1281 -0
  20. package/lib/pipeline-observations.js +181 -0
  21. package/lib/pipeline-observe.js +133 -0
  22. package/lib/pipeline-runner.js +346 -0
  23. package/lib/pipeline-runs.js +442 -0
  24. package/lib/pipeline.js +128 -7
  25. package/lib/semantic.js +66 -10
  26. package/lib/settings.js +132 -1
  27. package/lib/store.js +77 -3
  28. package/lib/summarize.js +2 -3
  29. package/package.json +3 -3
  30. package/scripts/changelog-prep.mjs +62 -7
  31. package/scripts/check-sync.js +8 -6
  32. package/scripts/release-docs.mjs +204 -0
  33. package/scripts/release-notes.mjs +162 -0
  34. package/src/api-standalone.js +78 -12
  35. package/src/api.js +282 -57
  36. package/src/candidates.js +454 -454
  37. package/src/config.js +33 -2
  38. package/src/embedding.js +116 -15
  39. package/src/index.js +32 -0
  40. package/src/inject.js +34 -3
  41. package/src/lang.js +124 -0
  42. package/src/pipeline-activities.js +178 -0
  43. package/src/pipeline-attribution.js +165 -0
  44. package/src/pipeline-command.js +279 -0
  45. package/src/pipeline-confirm.js +142 -0
  46. package/src/pipeline-exec.js +174 -0
  47. package/src/pipeline-nudge.js +1281 -0
  48. package/src/pipeline-observations.js +181 -0
  49. package/src/pipeline-observe.js +133 -0
  50. package/src/pipeline-runner.js +346 -0
  51. package/src/pipeline-runs.js +442 -0
  52. package/src/pipeline.js +128 -7
  53. package/src/semantic.js +66 -10
  54. package/src/settings.js +132 -1
  55. package/src/store.js +77 -3
  56. package/src/summarize.js +2 -3
  57. package/test/api-hardening.test.js +173 -0
  58. package/test/api.test.js +98 -1
  59. package/test/changelog-prep.test.js +74 -8
  60. package/test/cli-enums.test.js +37 -0
  61. package/test/client.test.js +152 -3
  62. package/test/docs-config-keys.test.js +65 -0
  63. package/test/helpers/write-admission-samples.js +6 -2
  64. package/test/lib-smoke.test.js +20 -0
  65. package/test/persona-api.test.js +172 -0
  66. package/test/persona-flag.test.js +36 -0
  67. package/test/persona-inject.test.js +145 -0
  68. package/test/persona-settings.test.js +145 -0
  69. package/test/pipeline-activities.test.js +155 -0
  70. package/test/pipeline-attribution.test.js +175 -0
  71. package/test/pipeline-command.test.js +243 -0
  72. package/test/pipeline-confirm.test.js +148 -0
  73. package/test/pipeline-exec.test.js +172 -0
  74. package/test/pipeline-nudge.test.js +1036 -0
  75. package/test/pipeline-observations.test.js +157 -0
  76. package/test/pipeline-observe.test.js +127 -0
  77. package/test/pipeline-r1r2.test.js +149 -0
  78. package/test/pipeline-runner.test.js +442 -0
  79. package/test/pipeline-runs.test.js +767 -0
  80. package/test/pipeline.test.js +137 -6
  81. package/test/reindex-backfill.test.js +83 -4
  82. package/test/release-docs.test.js +130 -0
  83. package/test/release-notes.test.js +240 -0
  84. package/test/release-workflow.test.js +16 -0
  85. package/test/route-policy.test.js +113 -0
  86. package/test/security-doc.test.js +139 -0
  87. package/test/sensitive-scan.test.js +44 -0
  88. package/test/workflow-guards.test.js +156 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * M.O. 的**执行窗口**:一次 run 从「有人决定按这条工作流走一遍」开始记账,到这一遍结束为止。
3
+ *
4
+ * ## 为什么是「窗口」而不是「自动观察一切」
5
+ *
6
+ * 这是本模块存在的核心理由,也是接线前必须先想清楚的一件事:
7
+ *
8
+ * 宿主钩子(`tools/pre-execute` 等)能看到「这次调了什么工具」,但它**看不到「正在走哪条
9
+ * 工作流」**。工作流的 `trigger` 字段是人话(`{kind:"phrase", value:"发版"}`),今天没有任何
10
+ * 代码消费它;想靠它自动判定「用户说这句话就意味着开始走某条流程」,只能猜——而猜错的代价
11
+ * 是把一条**从没跑过的流程**记成跑过。那正是本仓反复防的「伪造正面证据」。
12
+ *
13
+ * 所以窗口的边界由**显式动作**划:有人(或按人指令的 agent)说明「现在按 X 走一遍」,窗口才开;
14
+ * 窗口内的工具调用才可能被记账;窗口一关就 flush 成一条 run。**窗口之外发生的一切一个字都不记**
15
+ * ——漏记账是可接受的,编造账不是。
16
+ *
17
+ * ## 一次观察有三种结局,必须分开
18
+ *
19
+ * - **归属成功**:写进这次 run 的 `steps`
20
+ * - **归属不了**:进 `unattributed`。它在图上是「没走这条路的痕迹」,即绕开的候选——
21
+ * 但它**还没被确认**是这条流程的一步,所以不进 `steps`
22
+ * - **归属有歧义**:进 `ambiguous`,**不猜**,留给人消歧
23
+ *
24
+ * 三者分开报的纪律与账本那边一致:**「认不出来」与「没做」必须长得不一样**,
25
+ * 否则一份全是 unattributed 的账会被读成「这次什么也没做」。
26
+ *
27
+ * ## 唯一没被记下来的东西:失败
28
+ *
29
+ * 账本契约要求一条 run 至少有一个步骤(`run.steps 至少要有一条`),所以「开了窗口、
30
+ * 一步都没走成」目前**落不成账**。这是已知缺口,本模块如实返回 `reason:"no-steps"`,
31
+ * 不伪造一条空 run 去凑。要真正表示失败,得先改账本契约(让零步 run 合法)。
32
+ */
33
+
34
+ import { attributionCandidates } from "./pipeline-attribution.js";
35
+
36
+ /** 一次观察被拒的三种原因;调用方据此决定要不要请人消歧。 */
37
+ export const OBSERVED = "observed";
38
+ export const UNATTRIBUTED = "unattributed";
39
+ export const AMBIGUOUS = "ambiguous";
40
+
41
+ function toIso(value) {
42
+ if (value instanceof Date && !Number.isNaN(value.getTime())) return value.toISOString();
43
+ const ms = typeof value === "number" ? value : Date.parse(String(value));
44
+ if (!Number.isFinite(ms)) throw new Error("时间必须能解析成 ISO");
45
+ return new Date(ms).toISOString();
46
+ }
47
+
48
+ /**
49
+ * 开一个记录窗口。
50
+ *
51
+ * `pipeline` 是这次要走的**那一条**工作流(由调用方点名,不由本模块猜)。
52
+ * `by` / `triggeredBy` 落进 run 的描述字段:账本里「谁做的」「为什么这次会跑」要有出处。
53
+ */
54
+ export function openRun({ pipeline, by = "main", triggeredBy = "", now = () => new Date() } = {}) {
55
+ if (!pipeline || !Array.isArray(pipeline.steps)) {
56
+ throw new Error("openRun 需要一条工作流(含 steps)");
57
+ }
58
+
59
+ const startedAt = toIso(now());
60
+ /** stepId → { at, refuted, repeats };同一步在一个窗口里只计一次,重复的进 repeats。 */
61
+ const steps = new Map();
62
+ const unattributed = [];
63
+ const ambiguous = [];
64
+ let observations = 0;
65
+ let closed = null;
66
+
67
+ /** 记下一步。同一步重复出现时不重复计数(`tallyRuns` 按 run 计数,重复会让 uses 虚高)。 */
68
+ function record(stepId, at, refuted) {
69
+ const existing = steps.get(stepId);
70
+ if (existing) {
71
+ existing.repeats += 1;
72
+ // 任何一次失败都算被推翻过——「跑砸过一次」不该被后来的成功抹掉。
73
+ if (refuted) existing.refuted = true;
74
+ return existing;
75
+ }
76
+ const entry = { id: stepId, at, refuted, repeats: 0 };
77
+ steps.set(stepId, entry);
78
+ return entry;
79
+ }
80
+
81
+ return {
82
+ /**
83
+ * 观察一次工具动作。
84
+ *
85
+ * `stepId` 是**人确认过的归属**:给了就直接记,不再走匹配(人说了算)。
86
+ * 不给则按归属规则自动判——只有 `exact` 且唯一时才自动记账;
87
+ * 多候选或只有 weak 一律进 `ambiguous`,**绝不自选一条**。
88
+ */
89
+ observe(observation, { stepId = null, at = null } = {}) {
90
+ if (closed) return { accepted: false, result: "run-closed" };
91
+ observations += 1;
92
+ const when = toIso(at ?? now());
93
+ const failed = observation && observation.ok === false;
94
+
95
+ if (stepId) {
96
+ record(stepId, when, failed);
97
+ return { accepted: true, result: OBSERVED, stepId, resolved: "explicit" };
98
+ }
99
+
100
+ const candidates = attributionCandidates(observation, pipeline);
101
+ if (candidates.length === 0) {
102
+ unattributed.push({ observation, at: when });
103
+ return { accepted: false, result: UNATTRIBUTED };
104
+ }
105
+
106
+ const exact = candidates.filter((c) => c.confidence === "exact");
107
+ if (candidates.length === 1 && exact.length === 1) {
108
+ record(exact[0].stepId, when, failed);
109
+ return { accepted: true, result: OBSERVED, stepId: exact[0].stepId, resolved: "exact" };
110
+ }
111
+
112
+ // 弱匹配、多候选都走这里:图省事挑一条就是替人做决定,而挑错的代价是染色错误的一步。
113
+ ambiguous.push({ observation, at: when, candidates });
114
+ return { accepted: false, result: AMBIGUOUS, candidates };
115
+ },
116
+
117
+ /** 只看状态,不结账。给「这一遍走到哪了」这种实时查询用。 */
118
+ state() {
119
+ return {
120
+ open: closed === null,
121
+ pipelineId: pipeline.id,
122
+ startedAt,
123
+ observations,
124
+ recorded: [...steps.values()].map((s) => s.id),
125
+ unattributed: unattributed.length,
126
+ ambiguous: ambiguous.length
127
+ };
128
+ },
129
+
130
+ /**
131
+ * 结账:产出一条**还没落盘**的 run(落盘由调用方做 `appendRun`,本模块不碰文件系统)。
132
+ *
133
+ * `steps` 里只有**真被观察到**的步骤。没观察到的步骤既不写 `skipped` 也不写 `used`——
134
+ * 缺席不等于跳过(可能是归属没认出来),把它写成跳过就是编造。它们进 `unobserved`,
135
+ * 让人自己看。
136
+ */
137
+ close() {
138
+ if (closed) return closed;
139
+ const runSteps = [...steps.values()].map((s) => ({
140
+ id: s.id,
141
+ at: s.at,
142
+ skipped: false,
143
+ detoured: false,
144
+ refuted: s.refuted === true,
145
+ note: s.repeats > 0 ? `这一步在窗口内被观察到 ${s.repeats + 1} 次,只计一次` : ""
146
+ }));
147
+ const observedIds = new Set(runSteps.map((s) => s.id));
148
+ const unobserved = pipeline.steps.map((s) => s.id).filter((id) => !observedIds.has(id));
149
+
150
+ const base = {
151
+ startedAt,
152
+ endedAt: toIso(now()),
153
+ pipelineId: pipeline.id,
154
+ observations,
155
+ unattributed,
156
+ ambiguous,
157
+ unobserved
158
+ };
159
+
160
+ if (runSteps.length === 0) {
161
+ // 契约要求至少一步,所以这一步落不成账。如实说,不伪造。
162
+ closed = { ...base, run: null, reason: "no-steps" };
163
+ return closed;
164
+ }
165
+
166
+ closed = {
167
+ ...base,
168
+ run: { at: startedAt, by, triggeredBy, steps: runSteps },
169
+ reason: null
170
+ };
171
+ return closed;
172
+ }
173
+ };
174
+ }