@fantasticit/sumi 0.7.3 → 0.7.5

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 (64) hide show
  1. package/dist/adapters.js +1 -1
  2. package/dist/adapters.js.map +1 -1
  3. package/dist/agent-hooks.d.ts +15 -0
  4. package/dist/agent-hooks.js +93 -5
  5. package/dist/agent-hooks.js.map +1 -1
  6. package/node_modules/@sumi/create/package.json +1 -1
  7. package/node_modules/@sumi/kernel/dist/config.d.ts +5 -0
  8. package/node_modules/@sumi/kernel/dist/config.js +16 -0
  9. package/node_modules/@sumi/kernel/dist/config.js.map +1 -1
  10. package/node_modules/@sumi/kernel/dist/engine.js +1 -0
  11. package/node_modules/@sumi/kernel/dist/engine.js.map +1 -1
  12. package/node_modules/@sumi/kernel/dist/registry.d.ts +2 -0
  13. package/node_modules/@sumi/kernel/dist/registry.js +15 -1
  14. package/node_modules/@sumi/kernel/dist/registry.js.map +1 -1
  15. package/node_modules/@sumi/kernel/package.json +2 -2
  16. package/node_modules/@sumi/plugin-sdk/README.md +23 -16
  17. package/node_modules/@sumi/plugin-sdk/dist/protocol.d.ts +9 -0
  18. package/node_modules/@sumi/plugin-sdk/dist/protocol.js +17 -0
  19. package/node_modules/@sumi/plugin-sdk/dist/protocol.js.map +1 -1
  20. package/node_modules/@sumi/plugin-sdk/package.json +1 -1
  21. package/node_modules/@sumi/plugins-core/README.md +14 -12
  22. package/node_modules/@sumi/plugins-core/dist/plugins/exec.js +7 -2
  23. package/node_modules/@sumi/plugins-core/dist/plugins/exec.js.map +1 -1
  24. package/node_modules/@sumi/plugins-core/dist/plugins/test-affected.js +12 -3
  25. package/node_modules/@sumi/plugins-core/dist/plugins/test-affected.js.map +1 -1
  26. package/node_modules/@sumi/plugins-core/dist/test-scope.d.ts +1 -1
  27. package/node_modules/@sumi/plugins-core/dist/test-scope.js +12 -6
  28. package/node_modules/@sumi/plugins-core/dist/test-scope.js.map +1 -1
  29. package/node_modules/@sumi/plugins-core/dist/vitest-runner.js +129 -5
  30. package/node_modules/@sumi/plugins-core/dist/vitest-runner.js.map +1 -1
  31. package/node_modules/@sumi/plugins-core/package.json +4 -4
  32. package/node_modules/@sumi/plugins-governance/README.md +11 -10
  33. package/node_modules/@sumi/plugins-governance/dist/checkout.d.ts +12 -2
  34. package/node_modules/@sumi/plugins-governance/dist/checkout.js +40 -3
  35. package/node_modules/@sumi/plugins-governance/dist/checkout.js.map +1 -1
  36. package/node_modules/@sumi/plugins-governance/dist/commands/gate.js +6 -2
  37. package/node_modules/@sumi/plugins-governance/dist/commands/gate.js.map +1 -1
  38. package/node_modules/@sumi/plugins-governance/dist/commands/pr-report.js +1 -1
  39. package/node_modules/@sumi/plugins-governance/dist/commands/pr-report.js.map +1 -1
  40. package/node_modules/@sumi/plugins-governance/dist/gate-cache.d.ts +25 -0
  41. package/node_modules/@sumi/plugins-governance/dist/gate-cache.js +83 -0
  42. package/node_modules/@sumi/plugins-governance/dist/gate-cache.js.map +1 -0
  43. package/node_modules/@sumi/plugins-governance/dist/pr-report-status.d.ts +7 -1
  44. package/node_modules/@sumi/plugins-governance/dist/pr-report-status.js +27 -6
  45. package/node_modules/@sumi/plugins-governance/dist/pr-report-status.js.map +1 -1
  46. package/node_modules/@sumi/plugins-governance/dist/protected.d.ts +1 -0
  47. package/node_modules/@sumi/plugins-governance/instructions/push.md +1 -1
  48. package/node_modules/@sumi/plugins-governance/package.json +2 -2
  49. package/node_modules/@sumi/plugins-openspec/package.json +2 -2
  50. package/node_modules/@sumi/plugins-openspec/sumi-plugins.yaml +2 -0
  51. package/node_modules/@sumi/plugins-precedent/README.md +2 -0
  52. package/node_modules/@sumi/plugins-precedent/dist/after-edit-context.d.ts +26 -0
  53. package/node_modules/@sumi/plugins-precedent/dist/after-edit-context.js +192 -0
  54. package/node_modules/@sumi/plugins-precedent/dist/after-edit-context.js.map +1 -0
  55. package/node_modules/@sumi/plugins-precedent/dist/commands/after-edit-context.d.ts +1 -0
  56. package/node_modules/@sumi/plugins-precedent/dist/commands/after-edit-context.js +13 -0
  57. package/node_modules/@sumi/plugins-precedent/dist/commands/after-edit-context.js.map +1 -0
  58. package/node_modules/@sumi/plugins-precedent/package.json +3 -3
  59. package/node_modules/@sumi/plugins-precedent/sumi-plugins.yaml +5 -0
  60. package/node_modules/@sumi/plugins-review/package.json +3 -3
  61. package/package.json +10 -10
  62. package/node_modules/@sumi/kernel/test/fixtures/fake-plugin.mjs +0 -44
  63. package/node_modules/@sumi/kernel/test/fixtures/py-plugin.py +0 -3
  64. package/node_modules/@sumi/plugin-sdk/test/isolate-tmpdir.ts +0 -27
@@ -10,6 +10,7 @@ export interface GovernanceConfig {
10
10
  tier?: string;
11
11
  prepare?: string[];
12
12
  defaultBranch?: string;
13
+ cache?: boolean;
13
14
  };
14
15
  }
15
16
  export declare function parseConfig(text: string | null): GovernanceConfig | null;
@@ -4,7 +4,7 @@ description: 推送前使用:按推送门禁的档位在工作区运行检查
4
4
 
5
5
  # 推送前(推送门禁)
6
6
 
7
- 推送时 pre-push hook 运行 `sumi gate`:先判断这次推送是否需要人工确认(control 变更、删除或强制更新 ref 等),需要而未确认时立即拒绝、不运行检查;确认后重新推送,才在被推送提交的独立检出中安装依赖,按项目配置 `gate.tier` 的档位重跑 check。门禁不派发评审、不做人工确认,所以评审要在推送前于工作区完成,人工确认由用户完成。
7
+ 推送时 pre-push hook 运行 `sumi gate`:先判断这次推送是否需要人工确认(control 变更、删除或强制更新 ref 等),需要而未确认时立即拒绝、不运行检查;确认后重新推送,在被推送提交的独立检出中运行检查,或复用本机同 head、base、档位的推送门禁成功缓存。`SUMI_GATE_NO_CACHE=1 git push` 可强制现场重跑;项目可配置 `gate.cache: false` 永久关闭复用。PR 报告始终现场运行,不复用门禁缓存。门禁不派发评审、不做人工确认,所以评审要在推送前于工作区完成,人工确认由用户完成。
8
8
 
9
9
  1. 读 `control/config.yaml` 的 `gate.tier`(未配置时为最低档位)。
10
10
  2. 运行 `sumi run check --tier <该档位>`。分支还没有上游时,工作区与门禁都以与远端默认分支的合并基础为基准,评审范围一致;分支已推送过时,门禁只看新增提交,工作区评审的范围(如配置基准下相对主干的全部改动)覆盖它且内容一致时,门禁直接复用,不必按门禁的基准再评审一遍。
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sumi/plugins-governance",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "治理插件",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,7 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "dependencies": {
16
- "@sumi/plugin-sdk": "0.7.3",
16
+ "@sumi/plugin-sdk": "0.7.5",
17
17
  "yaml": "2.9.1",
18
18
  "picomatch": "4.0.7"
19
19
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sumi/plugins-openspec",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "OpenSpec 插件",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,7 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "dependencies": {
16
- "@sumi/plugin-sdk": "0.7.3"
16
+ "@sumi/plugin-sdk": "0.7.5"
17
17
  },
18
18
  "files": [
19
19
  "dist",
@@ -1,4 +1,6 @@
1
1
  # OpenSpec 插件:change 标签、规格校验与 change 流程的阶段控制。
2
+ pathHints:
3
+ testNeutral: ['openspec/**']
2
4
  plugins:
3
5
  - id: openspec/planner
4
6
  version: 0.1.0
@@ -4,6 +4,8 @@
4
4
 
5
5
  依赖 `@sumi/plugins-governance`(人工确认与编辑窗口)。
6
6
 
7
+ 启用后,编辑后 hook 默认仅提示与本次文件路径有关的规则、判例、评审条目和机械门;`global: true` 的全局条目不会反复推送。项目可在 `sumi.config.yaml` 设 `agentContext: { afterEdit: false }` 关闭此提示。会话去重状态位于 `.sumi/after-edit-context/`,提示送达后才记录,写入时清理超过七天的旧状态;没有会话 ID 时不持久去重。需要完整内容时按提示运行 `node ./control/bin/sumi.cjs context <路径...>`。
8
+
7
9
  ## 插件
8
10
 
9
11
  | id | kind | 作用 | 触发条件 / 档位 | `with` 配置项 |
@@ -0,0 +1,26 @@
1
+ import { type AfterEditResult, type ContextItem } from '@sumi/plugin-sdk';
2
+ interface ContextData {
3
+ plugins: {
4
+ id: string;
5
+ description?: string;
6
+ global?: boolean;
7
+ }[];
8
+ items: ContextItem[];
9
+ rules: {
10
+ file: string;
11
+ line: number;
12
+ text: string;
13
+ }[];
14
+ generalRules: {
15
+ file: string;
16
+ line: number;
17
+ text: string;
18
+ }[];
19
+ }
20
+ /** 只接受编辑工具的明确路径,不猜测任意 shell 命令可能写入了什么。 */
21
+ export declare function editedPaths(root: string, input: string): string[];
22
+ export declare function queryContext(root: string, paths: string[], timeout?: number): ContextData | null;
23
+ export declare function acknowledgeContext(root: string, input: string): void;
24
+ /** 插件层的提示策略;查询函数可替换以测试失败、超时和异常。 */
25
+ export declare function afterEditContext(root: string, input: string, query?: (paths: string[]) => ContextData | null): AfterEditResult | null;
26
+ export {};
@@ -0,0 +1,192 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
3
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
4
+ import { configFile, PROJECT_SUMI, spawnCommandSync } from '@sumi/plugin-sdk';
5
+ import { parse } from 'yaml';
6
+ const LIMIT = 2500;
7
+ const QUERY_TIMEOUT = 8000;
8
+ function inside(root, path) {
9
+ const canonical = (value) => {
10
+ let parent = value;
11
+ for (;;) {
12
+ try {
13
+ return resolve(realpathSync(parent), relative(parent, value));
14
+ }
15
+ catch {
16
+ const next = dirname(parent);
17
+ if (next === parent)
18
+ throw new Error('无法解析编辑路径');
19
+ parent = next;
20
+ }
21
+ }
22
+ };
23
+ const rel = relative(realpathSync(root), canonical(resolve(root, path))).replaceAll('\\', '/');
24
+ return rel && rel !== '..' && !rel.startsWith('../') && !isAbsolute(rel) ? rel : null;
25
+ }
26
+ /** 只接受编辑工具的明确路径,不猜测任意 shell 命令可能写入了什么。 */
27
+ export function editedPaths(root, input) {
28
+ try {
29
+ const payload = JSON.parse(input);
30
+ const tool = payload.tool_input;
31
+ if (!tool || typeof tool !== 'object')
32
+ return [];
33
+ const base = typeof payload.cwd === 'string' ? resolve(root, payload.cwd) : root;
34
+ const candidates = [tool.file_path, tool.notebook_path];
35
+ if (payload.tool_name === 'apply_patch') {
36
+ const patch = typeof tool.command === 'string' ? tool.command : tool.patch;
37
+ if (typeof patch === 'string') {
38
+ for (const match of patch.matchAll(/^\*\*\* (?:Add|Update|Delete|Move) File: (.+)$/gm))
39
+ candidates.push(match[1]);
40
+ for (const match of patch.matchAll(/^\*\*\* Move to: (.+)$/gm))
41
+ candidates.push(match[1]);
42
+ }
43
+ }
44
+ return [
45
+ ...new Set(candidates
46
+ .filter((v) => typeof v === 'string')
47
+ .map((v) => inside(root, resolve(base, v)))
48
+ .filter((v) => !!v)),
49
+ ];
50
+ }
51
+ catch {
52
+ return [];
53
+ }
54
+ }
55
+ function sessionId(input) {
56
+ try {
57
+ const value = JSON.parse(input).session_id;
58
+ return typeof value === 'string' && value ? value : null;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ function enabled(root) {
65
+ const file = configFile(root);
66
+ if (!file)
67
+ return false;
68
+ const data = parse(readFileSync(join(root, file), 'utf8'));
69
+ return data?.agentContext?.afterEdit !== false;
70
+ }
71
+ export function queryContext(root, paths, timeout = QUERY_TIMEOUT) {
72
+ const launcher = process.env.SUMI_BIN || join(root, 'control/bin/sumi.cjs');
73
+ const result = spawnCommandSync(process.execPath, [launcher, 'context', ...paths, '--json'], {
74
+ cwd: root,
75
+ encoding: 'utf8',
76
+ timeout,
77
+ maxBuffer: 1024 * 1024,
78
+ windowsHide: true,
79
+ });
80
+ if (result.status !== 0 || result.error)
81
+ return null;
82
+ const value = JSON.parse(result.stdout);
83
+ return Array.isArray(value.plugins) && Array.isArray(value.items) && Array.isArray(value.rules) && Array.isArray(value.generalRules)
84
+ ? value
85
+ : null;
86
+ }
87
+ function stateFile(root, session) {
88
+ const key = createHash('sha256').update(session).digest('hex');
89
+ return join(root, '.sumi/after-edit-context', `${key}.json`);
90
+ }
91
+ function previous(root, session) {
92
+ if (!session)
93
+ return new Set();
94
+ try {
95
+ const value = JSON.parse(readFileSync(stateFile(root, session), 'utf8'));
96
+ return new Set(Array.isArray(value) ? value.filter((v) => typeof v === 'string') : []);
97
+ }
98
+ catch {
99
+ return new Set();
100
+ }
101
+ }
102
+ function save(root, session, keys) {
103
+ try {
104
+ const file = stateFile(root, session);
105
+ const dir = join(root, '.sumi/after-edit-context');
106
+ mkdirSync(dir, { recursive: true });
107
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
108
+ for (const name of readdirSync(dir)) {
109
+ if (!name.endsWith('.json'))
110
+ continue;
111
+ const stale = join(dir, name);
112
+ try {
113
+ if (statSync(stale).mtimeMs < cutoff)
114
+ unlinkSync(stale);
115
+ }
116
+ catch {
117
+ /* 去重状态清理是尽力而为 */
118
+ }
119
+ }
120
+ const temporary = `${file}.${randomUUID()}.tmp`;
121
+ writeFileSync(temporary, JSON.stringify([...new Set([...previous(root, session), ...keys])]));
122
+ renameSync(temporary, file);
123
+ }
124
+ catch {
125
+ // 会话去重只是体验优化;状态不可写时仍放行编辑。
126
+ }
127
+ }
128
+ export function acknowledgeContext(root, input) {
129
+ try {
130
+ const value = JSON.parse(input);
131
+ const session = sessionId(value.originalInput ?? '');
132
+ if (session && Array.isArray(value.keys) && value.keys.every((key) => typeof key === 'string'))
133
+ save(root, session, value.keys);
134
+ }
135
+ catch {
136
+ /* 送达回执不可影响编辑操作 */
137
+ }
138
+ }
139
+ /** 插件层的提示策略;查询函数可替换以测试失败、超时和异常。 */
140
+ export function afterEditContext(root, input, query = (paths) => queryContext(root, paths)) {
141
+ try {
142
+ if (!enabled(root))
143
+ return null;
144
+ const paths = editedPaths(root, input);
145
+ if (!paths.length)
146
+ return null;
147
+ const context = query(paths);
148
+ if (!context)
149
+ return null;
150
+ const session = sessionId(input);
151
+ const seen = previous(root, session);
152
+ const itemRow = (i) => ({
153
+ key: `item:${i.ref}`,
154
+ text: `- [${i.ref}] ${i.text}${i.source ? `(来源:${i.source})` : ''}`,
155
+ });
156
+ const ruleRow = (r) => ({ key: `rule:${r.file}:${r.line}`, text: `- ${r.file}:${r.line} ${r.text}` });
157
+ const pluginRow = (p) => ({
158
+ key: `plugin:${p.id}`,
159
+ text: `- 机械门 ${p.id}${p.description ? `:${p.description}` : ''}`,
160
+ });
161
+ const rows = [
162
+ ...context.items.filter((i) => !i.global).map(itemRow),
163
+ ...context.rules.map(ruleRow),
164
+ ...context.plugins.filter((p) => !p.global).map(pluginRow),
165
+ ]
166
+ .map((row) => ({ ...row, text: row.text.length > 220 ? `${row.text.slice(0, 219)}…` : row.text }))
167
+ .filter((row) => !seen.has(row.key));
168
+ if (!rows.length)
169
+ return null;
170
+ const footer = `\n原文:${PROJECT_SUMI} context ${paths.map((p) => JSON.stringify(p)).join(' ')}`;
171
+ let body = '本次编辑命中的项目约束(请核对原文):';
172
+ let count = 0;
173
+ const deliveryKeys = [];
174
+ for (const row of rows) {
175
+ if (body.length + row.text.length + footer.length + 20 > LIMIT)
176
+ break;
177
+ body += `\n${row.text}`;
178
+ deliveryKeys.push(row.key);
179
+ count += 1;
180
+ }
181
+ if (!count)
182
+ return { additionalContext: `${body}\n内容过长,已截断。${footer}`.slice(0, LIMIT) };
183
+ if (count < rows.length)
184
+ body += '\n其余条目已截断;';
185
+ body += footer;
186
+ return session ? { additionalContext: body.slice(0, LIMIT), deliveryKeys } : { additionalContext: body.slice(0, LIMIT) };
187
+ }
188
+ catch {
189
+ return null;
190
+ }
191
+ }
192
+ //# sourceMappingURL=after-edit-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"after-edit-context.js","sourceRoot":"","sources":["../src/after-edit-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { acknowledgeContext, afterEditContext } from '../after-edit-context.js';
2
+ let input = '';
3
+ for await (const chunk of process.stdin)
4
+ input += chunk;
5
+ const root = process.env.SUMI_ROOT ?? process.cwd();
6
+ if (process.env.SUMI_AFTER_EDIT_DELIVERED === '1')
7
+ acknowledgeContext(root, input);
8
+ else {
9
+ const result = afterEditContext(root, input);
10
+ if (result)
11
+ process.stdout.write(JSON.stringify(result));
12
+ }
13
+ //# sourceMappingURL=after-edit-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"after-edit-context.js","sourceRoot":"","sources":["../../src/commands/after-edit-context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAEhF,IAAI,KAAK,GAAG,EAAE,CAAC;AACf,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;IAAE,KAAK,IAAI,KAAK,CAAC;AACxD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;AACpD,IAAI,OAAO,CAAC,GAAG,CAAC,yBAAyB,KAAK,GAAG;IAAE,kBAAkB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;KAC9E,CAAC;IACJ,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7C,IAAI,MAAM;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3D,CAAC"}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sumi/plugins-precedent",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "判例插件",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,10 +13,10 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "dependencies": {
16
- "@sumi/plugin-sdk": "0.7.3",
16
+ "@sumi/plugin-sdk": "0.7.5",
17
17
  "yaml": "2.9.1",
18
18
  "picomatch": "4.0.7",
19
- "@sumi/plugins-governance": "0.7.3"
19
+ "@sumi/plugins-governance": "0.7.5"
20
20
  },
21
21
  "files": [
22
22
  "dist",
@@ -1,4 +1,6 @@
1
1
  # 判例插件:判例 = 一句约束 + 执行者 + 来源。规划器把命中的判例接线成执行义务,自检保证登记了就可执行。
2
+ pathHints:
3
+ testNeutral: ['precedents/**']
2
4
  plugins:
3
5
  - id: prec/planner
4
6
  version: 0.1.0
@@ -7,8 +9,11 @@ plugins:
7
9
  command: [node, ./dist/plugins/planner.js]
8
10
  timeout: 60
9
11
  context: context-precedents
12
+ hooks:
13
+ agent: { afterEdit: context-after-edit }
10
14
  commands:
11
15
  - { name: context-precedents, argv: [node, ./dist/commands/context.js], description: 列出给定路径上生效的判例 }
16
+ - { name: context-after-edit, argv: [node, ./dist/commands/after-edit-context.js], description: 编辑后提示新命中的约束 }
12
17
  - id: prec/checker
13
18
  version: 0.1.0
14
19
  description: 运行判例的检查器
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sumi/plugins-review",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "评审插件",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,10 +13,10 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "dependencies": {
16
- "@sumi/plugin-sdk": "0.7.3",
16
+ "@sumi/plugin-sdk": "0.7.5",
17
17
  "yaml": "2.9.1",
18
18
  "picomatch": "4.0.7",
19
- "@sumi/plugins-governance": "0.7.3"
19
+ "@sumi/plugins-governance": "0.7.5"
20
20
  },
21
21
  "files": [
22
22
  "dist",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fantasticit/sumi",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "sumi 命令行",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,14 +13,14 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "dependencies": {
16
- "@sumi/create": "0.7.3",
17
- "@sumi/kernel": "0.7.3",
18
- "@sumi/plugin-sdk": "0.7.3",
19
- "@sumi/plugins-core": "0.7.3",
20
- "@sumi/plugins-governance": "0.7.3",
21
- "@sumi/plugins-openspec": "0.7.3",
22
- "@sumi/plugins-precedent": "0.7.3",
23
- "@sumi/plugins-review": "0.7.3",
16
+ "@sumi/create": "0.7.5",
17
+ "@sumi/kernel": "0.7.5",
18
+ "@sumi/plugin-sdk": "0.7.5",
19
+ "@sumi/plugins-core": "0.7.5",
20
+ "@sumi/plugins-governance": "0.7.5",
21
+ "@sumi/plugins-openspec": "0.7.5",
22
+ "@sumi/plugins-precedent": "0.7.5",
23
+ "@sumi/plugins-review": "0.7.5",
24
24
  "tar": "7.5.22",
25
25
  "yaml": "2.9.1"
26
26
  },
@@ -42,5 +42,5 @@
42
42
  "tar",
43
43
  "yaml"
44
44
  ],
45
- "sumiBuild": "sha256:dca999bdcea7d6bc0eec4bd4c67e34b822a5717e8f7358d4ceafdf21b4c64569"
45
+ "sumiBuild": "sha256:1ca7ee9572526f5e6bf9dfaedc8544a0c34dbc8b16e6b28676f12cbe80678328"
46
46
  }
@@ -1,44 +0,0 @@
1
- // 测试用插件:行为由步骤配置 with 决定。
2
- import { spawn } from 'node:child_process';
3
- import { writeFileSync } from 'node:fs';
4
-
5
- let input = '';
6
- for await (const chunk of process.stdin) input += chunk;
7
- const ctx = JSON.parse(input);
8
- const c = ctx.config;
9
- if (c.touch) writeFileSync(`${ctx.repoRoot}/${c.touch}`, String(Date.now()));
10
- if (c.assertNoGitEnv) {
11
- // hook 中 git 导出的变量指向当前仓库,插件看到它们就可能把操作写进错误的仓库。
12
- const leaked = ['GIT_DIR', 'GIT_INDEX_FILE', 'GIT_WORK_TREE'].filter((v) => process.env[v] !== undefined);
13
- if (leaked.length) {
14
- c.status = 'fail';
15
- c.summary = `泄漏:${leaked.join(',')}`;
16
- }
17
- }
18
- if (c.orphan) {
19
- const child = spawn(process.execPath, ['-e', 'setTimeout(() => {}, 30000)'], { stdio: 'ignore' });
20
- writeFileSync(`${ctx.evidenceDir}/orphan.pid`, String(child.pid));
21
- child.unref();
22
- }
23
- if (c.sleep) await new Promise((r) => setTimeout(r, c.sleep * 1000));
24
- if (c.raw !== undefined) {
25
- process.stdout.write(c.raw);
26
- process.exit(c.exit ?? 0);
27
- }
28
- const status = c.status ?? 'pass';
29
- const result = {
30
- status,
31
- summary: c.summary ?? `${ctx.stepId} ${status}`,
32
- findings: c.findings ?? [],
33
- checked: ctx.files,
34
- evidence: [],
35
- next: c.next,
36
- metrics: c.metrics,
37
- plan: c.plan,
38
- report: c.report,
39
- obligations: ctx.obligations.map((o) => ({ id: o.id, outcome: (c.receipts ?? {})[o.id] ?? 'passed' })),
40
- };
41
- if (c.echoFiles) result.summary = ctx.files.join(',');
42
- process.stdout.write(JSON.stringify(result));
43
- process.exitCode = c.exit ?? (status === 'pass' ? 0 : status === 'fail' ? 1 : 2);
44
- if (c.signal) process.kill(process.pid, c.signal);
@@ -1,3 +0,0 @@
1
- import json, sys
2
- ctx = json.load(sys.stdin)
3
- print(json.dumps({"status": "pass", "summary": "python " + ctx["stepId"], "findings": [], "checked": ctx["files"], "evidence": []}), end="")
@@ -1,27 +0,0 @@
1
- /**
2
- * 各包 vitest 配置共用的 setupFiles:每个测试文件运行前把系统临时目录(TMPDIR,Windows 为 TEMP/TMP)指向
3
- * 一个专用目录,测试文件结束后整体删除。
4
- *
5
- * 测试创建的仓库、门禁检出(失败时会保留)、自检与命令的临时文件都经 os.tmpdir() 落在这里;
6
- * 测试启动的子进程(sumi、git hook、门禁)继承该环境,同样写入这里。直接用 vitest CLI、`pnpm test`,
7
- * 或由 core/test-affected(vitest Node API)与 core/coverage 经根配置的 projects 运行时都会加载本文件。
8
- */
9
- import { mkdtempSync, realpathSync, rmSync } from 'node:fs';
10
- import { tmpdir } from 'node:os';
11
- import { join } from 'node:path';
12
- import { afterAll } from 'vitest';
13
-
14
- const NAMES = ['TMPDIR', 'TMP', 'TEMP'] as const;
15
- const saved = Object.fromEntries(NAMES.map((name) => [name, process.env[name]]));
16
- // 取真实路径:macOS 的 /var 是 /private/var 的符号链接,git 与子进程报告的路径是后者
17
- const dir = realpathSync(mkdtempSync(join(tmpdir(), 'sumi-test-')));
18
- for (const name of NAMES) process.env[name] = dir;
19
-
20
- // 本文件的 afterAll 先于测试文件注册,按 vitest 默认的 stack 顺序最后运行,测试文件自己的清理先完成
21
- afterAll(() => {
22
- for (const name of NAMES) {
23
- if (saved[name] === undefined) delete process.env[name];
24
- else process.env[name] = saved[name];
25
- }
26
- rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 200 });
27
- });