@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.
- package/README.md +78 -16
- package/bin/cli.mjs +8 -2
- package/docs/CONFIGURATION.md +25 -0
- package/docs/SEMANTIC.md +3 -3
- package/lib/api-standalone.js +78 -12
- package/lib/api.js +282 -57
- package/lib/candidates.js +454 -454
- package/lib/client.js +598 -248
- package/lib/config.js +33 -2
- package/lib/embedding.js +116 -15
- package/lib/index.js +32 -0
- package/lib/inject.js +34 -3
- package/lib/lang.js +124 -0
- package/lib/pipeline-activities.js +178 -0
- package/lib/pipeline-attribution.js +165 -0
- package/lib/pipeline-command.js +279 -0
- package/lib/pipeline-confirm.js +142 -0
- package/lib/pipeline-exec.js +174 -0
- package/lib/pipeline-nudge.js +1281 -0
- package/lib/pipeline-observations.js +181 -0
- package/lib/pipeline-observe.js +133 -0
- package/lib/pipeline-runner.js +346 -0
- package/lib/pipeline-runs.js +442 -0
- package/lib/pipeline.js +128 -7
- package/lib/semantic.js +66 -10
- package/lib/settings.js +132 -1
- package/lib/store.js +77 -3
- package/lib/summarize.js +2 -3
- package/package.json +3 -3
- package/scripts/changelog-prep.mjs +62 -7
- package/scripts/check-sync.js +8 -6
- package/scripts/release-docs.mjs +204 -0
- package/scripts/release-notes.mjs +162 -0
- package/src/api-standalone.js +78 -12
- package/src/api.js +282 -57
- package/src/candidates.js +454 -454
- package/src/config.js +33 -2
- package/src/embedding.js +116 -15
- package/src/index.js +32 -0
- package/src/inject.js +34 -3
- package/src/lang.js +124 -0
- package/src/pipeline-activities.js +178 -0
- package/src/pipeline-attribution.js +165 -0
- package/src/pipeline-command.js +279 -0
- package/src/pipeline-confirm.js +142 -0
- package/src/pipeline-exec.js +174 -0
- package/src/pipeline-nudge.js +1281 -0
- package/src/pipeline-observations.js +181 -0
- package/src/pipeline-observe.js +133 -0
- package/src/pipeline-runner.js +346 -0
- package/src/pipeline-runs.js +442 -0
- package/src/pipeline.js +128 -7
- package/src/semantic.js +66 -10
- package/src/settings.js +132 -1
- package/src/store.js +77 -3
- package/src/summarize.js +2 -3
- package/test/api-hardening.test.js +173 -0
- package/test/api.test.js +98 -1
- package/test/changelog-prep.test.js +74 -8
- package/test/cli-enums.test.js +37 -0
- package/test/client.test.js +152 -3
- package/test/docs-config-keys.test.js +65 -0
- package/test/helpers/write-admission-samples.js +6 -2
- package/test/lib-smoke.test.js +20 -0
- package/test/persona-api.test.js +172 -0
- package/test/persona-flag.test.js +36 -0
- package/test/persona-inject.test.js +145 -0
- package/test/persona-settings.test.js +145 -0
- package/test/pipeline-activities.test.js +155 -0
- package/test/pipeline-attribution.test.js +175 -0
- package/test/pipeline-command.test.js +243 -0
- package/test/pipeline-confirm.test.js +148 -0
- package/test/pipeline-exec.test.js +172 -0
- package/test/pipeline-nudge.test.js +1036 -0
- package/test/pipeline-observations.test.js +157 -0
- package/test/pipeline-observe.test.js +127 -0
- package/test/pipeline-r1r2.test.js +149 -0
- package/test/pipeline-runner.test.js +442 -0
- package/test/pipeline-runs.test.js +767 -0
- package/test/pipeline.test.js +137 -6
- package/test/reindex-backfill.test.js +83 -4
- package/test/release-docs.test.js +130 -0
- package/test/release-notes.test.js +240 -0
- package/test/release-workflow.test.js +16 -0
- package/test/route-policy.test.js +113 -0
- package/test/security-doc.test.js +139 -0
- package/test/sensitive-scan.test.js +44 -0
- package/test/workflow-guards.test.js +156 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { openRun, OBSERVED, UNATTRIBUTED, AMBIGUOUS } from "../src/pipeline-exec.js";
|
|
4
|
+
import { validatePipeline } from "../src/pipeline.js";
|
|
5
|
+
|
|
6
|
+
// 这组测试守的是 M.O. 的**执行窗口**:有人显式说「按 X 走一遍」,窗口才开;窗口内的
|
|
7
|
+
// 工具调用才可能被记账;关窗产出一条 run。
|
|
8
|
+
//
|
|
9
|
+
// 最要紧的三条性质(都是「不许编造」的具体形态):
|
|
10
|
+
// ① 窗口外发生的事一个字都不记——漏记可接受,编造不可
|
|
11
|
+
// ② 归属认不出来 → 进 unattributed,**不写进 run.steps**(不是「这步跳过了」)
|
|
12
|
+
// ③ 没观察到的步骤 → 进 unobserved,既不写 skipped 也不写 used(缺席 ≠ 跳过)
|
|
13
|
+
|
|
14
|
+
function pipelineOf(steps) {
|
|
15
|
+
return validatePipeline({
|
|
16
|
+
id: "demo",
|
|
17
|
+
name: "演示",
|
|
18
|
+
project: "someone/repo",
|
|
19
|
+
version: 0,
|
|
20
|
+
status: "active",
|
|
21
|
+
done: "走完",
|
|
22
|
+
trigger: [{ kind: "phrase", value: "走一遍" }],
|
|
23
|
+
steps: steps.map((s) => ({
|
|
24
|
+
id: s.id,
|
|
25
|
+
phase: "准备",
|
|
26
|
+
title: `第 ${s.id} 步`,
|
|
27
|
+
when: "需要时",
|
|
28
|
+
do: "做事",
|
|
29
|
+
who: "main",
|
|
30
|
+
tool: s.tool ?? "",
|
|
31
|
+
done: "做完",
|
|
32
|
+
risk: "R0",
|
|
33
|
+
needs: s.needs ?? [],
|
|
34
|
+
why: "因为"
|
|
35
|
+
}))
|
|
36
|
+
}, "demo.json");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const clock = (() => {
|
|
40
|
+
let n = 0;
|
|
41
|
+
return () => new Date(Date.UTC(2026, 9, 16, 12, 0, n++));
|
|
42
|
+
})();
|
|
43
|
+
|
|
44
|
+
test("开窗需要一条工作流:没有 steps 直接拒,不静默建一个空窗口", () => {
|
|
45
|
+
assert.throws(() => openRun({ pipeline: null }), /需要一条工作流/);
|
|
46
|
+
assert.throws(() => openRun({ pipeline: { id: "x" } }), /需要一条工作流/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("显式点名归属:人说了算,不再走匹配", () => {
|
|
50
|
+
const p = pipelineOf([{ id: "A1", tool: "npm test" }]);
|
|
51
|
+
const run = openRun({ pipeline: p, by: "human", triggeredBy: "发版", now: clock });
|
|
52
|
+
const r = run.observe({ tool: "bash", command: "随便什么命令" }, { stepId: "A1" });
|
|
53
|
+
assert.equal(r.result, OBSERVED);
|
|
54
|
+
assert.equal(r.resolved, "explicit", "人给的归属不该被自动匹配覆盖");
|
|
55
|
+
|
|
56
|
+
const closed = run.close();
|
|
57
|
+
assert.equal(closed.run.steps.length, 1);
|
|
58
|
+
assert.equal(closed.run.steps[0].id, "A1");
|
|
59
|
+
assert.equal(closed.run.by, "human");
|
|
60
|
+
assert.equal(closed.run.triggeredBy, "发版");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("自动归属只认唯一 exact:weak 与多候选走歧义桶,绝不自选一条", () => {
|
|
64
|
+
const p = pipelineOf([
|
|
65
|
+
{ id: "P6", tool: "npm test" },
|
|
66
|
+
{ id: "P5", tool: "npm run badge:sync" }
|
|
67
|
+
]);
|
|
68
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
69
|
+
|
|
70
|
+
// 唯一 exact → 自动记账
|
|
71
|
+
assert.equal(run.observe({ command: "npm test" }).result, OBSERVED);
|
|
72
|
+
|
|
73
|
+
// weak(词前缀但不相等)→ 歧义,不记账
|
|
74
|
+
const weak = run.observe({ command: "npm run badge:sync --all" });
|
|
75
|
+
assert.equal(weak.result, AMBIGUOUS, "弱匹配必须请人确认,不能自动写账");
|
|
76
|
+
|
|
77
|
+
// 认不出来 → unattributed
|
|
78
|
+
assert.equal(run.observe({ command: "docker ps" }).result, UNATTRIBUTED);
|
|
79
|
+
|
|
80
|
+
const closed = run.close();
|
|
81
|
+
assert.deepEqual(closed.run.steps.map((s) => s.id), ["P6"], "只有被确认的那步进账");
|
|
82
|
+
assert.equal(closed.ambiguous.length, 1);
|
|
83
|
+
assert.equal(closed.unattributed.length, 1);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("同一步被观察到多次只计一次,次数进 note(tallyRuns 按 run 计数,重复会让 uses 虚高)", () => {
|
|
87
|
+
const p = pipelineOf([{ id: "P6", tool: "npm test" }]);
|
|
88
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
89
|
+
run.observe({ command: "npm test" });
|
|
90
|
+
run.observe({ command: "npm test" });
|
|
91
|
+
run.observe({ command: "npm test" });
|
|
92
|
+
const closed = run.close();
|
|
93
|
+
assert.equal(closed.run.steps.length, 1, "三次观察仍是一步");
|
|
94
|
+
assert.match(closed.run.steps[0].note, /3 次/);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("任一环节失败就永久标记 refuted:后来的成功不许抹掉跑砸过一次这件事", () => {
|
|
98
|
+
const p = pipelineOf([{ id: "P6", tool: "npm test" }]);
|
|
99
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
100
|
+
run.observe({ command: "npm test", ok: false }); // 先失败
|
|
101
|
+
run.observe({ command: "npm test", ok: true }); // 再成功
|
|
102
|
+
const closed = run.close();
|
|
103
|
+
assert.equal(closed.run.steps[0].refuted, true, "失败过就是失败过,不该被后一次成功洗掉");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("没被观察到的步骤进 unobserved:既不写 skipped 也不写 used——缺席不等于跳过", () => {
|
|
107
|
+
const p = pipelineOf([
|
|
108
|
+
{ id: "A1", tool: "npm test" },
|
|
109
|
+
{ id: "A2", tool: "git diff" },
|
|
110
|
+
{ id: "A3", tool: "" } // 空 tool:结构性地无法被归属
|
|
111
|
+
]);
|
|
112
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
113
|
+
run.observe({ command: "npm test" });
|
|
114
|
+
const closed = run.close();
|
|
115
|
+
|
|
116
|
+
assert.deepEqual(closed.run.steps.map((s) => s.id), ["A1"]);
|
|
117
|
+
assert.deepEqual(closed.unobserved, ["A2", "A3"]);
|
|
118
|
+
// 关键:没观察到的步骤不许出现在 steps 里被标成 skipped——那是编造「这步被跳过了」。
|
|
119
|
+
assert.equal(closed.run.steps.some((s) => s.skipped === true), false);
|
|
120
|
+
assert.equal(closed.run.steps.some((s) => s.detoured === true), false);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("一步都没走成:如实返回 no-steps,不伪造一条空 run 去凑契约", () => {
|
|
124
|
+
const p = pipelineOf([{ id: "A1", tool: "npm test" }]);
|
|
125
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
126
|
+
run.observe({ command: "完全无关的命令" });
|
|
127
|
+
const closed = run.close();
|
|
128
|
+
|
|
129
|
+
assert.equal(closed.run, null, "账本契约要求至少一步,所以这里不能有 run");
|
|
130
|
+
assert.equal(closed.reason, "no-steps");
|
|
131
|
+
assert.equal(closed.unattributed.length, 1, "但这次观察要留着,别丢");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("窗口关掉之后不再接受观察:不悄悄记进一个已经结完的账", () => {
|
|
135
|
+
const p = pipelineOf([{ id: "A1", tool: "npm test" }]);
|
|
136
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
137
|
+
run.observe({ command: "npm test" });
|
|
138
|
+
const first = run.close();
|
|
139
|
+
const after = run.observe({ command: "npm test" });
|
|
140
|
+
|
|
141
|
+
assert.equal(after.accepted, false);
|
|
142
|
+
assert.equal(after.result, "run-closed");
|
|
143
|
+
assert.equal(run.close(), first, "重复 close 返回同一份,不产生第二份账");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("state() 只报状态不结账:窗口开着时也能回答「走到哪了」", () => {
|
|
147
|
+
const p = pipelineOf([{ id: "A1", tool: "npm test" }, { id: "A2", tool: "git diff" }]);
|
|
148
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
149
|
+
run.observe({ command: "npm test" });
|
|
150
|
+
run.observe({ command: "乱七八糟" });
|
|
151
|
+
const s = run.state();
|
|
152
|
+
|
|
153
|
+
assert.equal(s.open, true);
|
|
154
|
+
assert.equal(s.observations, 2);
|
|
155
|
+
assert.deepEqual(s.recorded, ["A1"]);
|
|
156
|
+
assert.equal(s.unattributed, 1);
|
|
157
|
+
assert.equal(s.ambiguous, 0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("产出的 run 能过账本的 normalizer:窗口与账本两个契约对得上", async () => {
|
|
161
|
+
const { normalizeRun } = await import("../src/pipeline-runs.js");
|
|
162
|
+
const p = pipelineOf([{ id: "A1", tool: "npm test" }]);
|
|
163
|
+
const run = openRun({ pipeline: p, now: clock });
|
|
164
|
+
run.observe({ command: "npm test", ok: false });
|
|
165
|
+
const closed = run.close();
|
|
166
|
+
|
|
167
|
+
// 不传 expectedStepIds:这里验的是形状能被账本接受
|
|
168
|
+
const normalized = normalizeRun(closed.run);
|
|
169
|
+
assert.equal(normalized.steps[0].id, "A1");
|
|
170
|
+
assert.equal(normalized.steps[0].refuted, true);
|
|
171
|
+
assert.equal(normalized.by, "main");
|
|
172
|
+
});
|