@aiti-ai/aiti-git-trace 0.0.0-stage → 0.2.12
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/AGENTS.md +28 -0
- package/LICENSE +21 -0
- package/README.md +38 -2
- package/api.ts +424 -0
- package/check-client.mjs +273 -0
- package/client/src/common.ts +179 -0
- package/client/src/entry.tsx +81 -0
- package/client/src/host-shim.ts +118 -0
- package/client/src/jsx-runtime.ts +2 -0
- package/client/src/panels.tsx +1377 -0
- package/client/src/trace-app.tsx +275 -0
- package/client/src/trace-files-row.pure.ts +88 -0
- package/client/src/trace-files-row.tsx +110 -0
- package/client/src/version-mention.ts +90 -0
- package/deploy-commit.mjs +52 -0
- package/dev-pack.mjs +246 -0
- package/dist/aiti-git-trace-client.mjs +4 -0
- package/dist/entry-C_le59dp.js +1594 -0
- package/dist/panels-BpC4XLni.js +1522 -0
- package/ext-icons-deploy.mjs +24 -0
- package/icon.svg +1 -0
- package/icons/git-branch.svg +1 -0
- package/icons/git-commit-horizontal.svg +1 -0
- package/index.ts +705 -0
- package/package.json +33 -4
- package/reconcile-deps.mjs +41 -0
- package/reload-instances.mjs +60 -0
- package/scripts/aiti-trace.mjs +117 -0
- package/skills/index.json +1 -0
- package/skills/rollback-session/SKILL.md +34 -0
- package/skills/trace-ops/SKILL.md +31 -0
- package/skills/watch-path/SKILL.md +34 -0
- package/test/fixtures/ops-sample.jsonl +3 -0
- package/tools/deploy.mjs +103 -0
- package/tools/trace-compact.mjs +58 -0
- package/trace-core.test.mjs +1120 -0
- package/trace-core.ts +1346 -0
- package/trace-files-row.test.mjs +66 -0
- package/tsconfig.check.json +21 -0
- package/version-mention.test.mjs +76 -0
- package/vite.config.ts +43 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trace-files-row 纯函数矩阵测试(方案 docs/aiti-git-trace-message-files-ext-plan.md §3.3)。
|
|
3
|
+
*
|
|
4
|
+
* 覆盖:增量口径、entryId 聚合、跨 store/op 去重、相对→绝对路径、writtenFiles 差集、
|
|
5
|
+
* 空态(无命中/全无增量/空 id 集合)。组件层(取数/渲染)在 trace-files-row.tsx,不在此覆盖。
|
|
6
|
+
*/
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import { rowHasDelta, toAbsPath, aggregateTraceTurnFiles, diffAgainstWritten } from "./client/src/trace-files-row.pure.ts";
|
|
10
|
+
|
|
11
|
+
const store = (over = {}) => ({
|
|
12
|
+
root: "/repo",
|
|
13
|
+
before: "aaa",
|
|
14
|
+
files: { M: 1, A: 0, D: 0 },
|
|
15
|
+
paths: ["a.ts"],
|
|
16
|
+
...over,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("rowHasDelta:纯基线 op(before=null)且无 leaks → false(与徽标/时间线口径一致)", () => {
|
|
20
|
+
assert.equal(rowHasDelta({ stores: [{ root: "/r", before: null, files: { M: 1 } }], leaks: [] }), false);
|
|
21
|
+
assert.equal(rowHasDelta({ stores: [store()], leaks: [] }), true);
|
|
22
|
+
assert.equal(rowHasDelta({ stores: [{ root: "/r", before: null }], leaks: [{}] }), true);
|
|
23
|
+
assert.equal(rowHasDelta({ stores: [store({ files: { M: 0, A: 0, D: 0 } })], leaks: [] }), false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("toAbsPath:相对拼 root,绝对原样,Windows 盘符原样", () => {
|
|
27
|
+
assert.equal(toAbsPath("/repo", "src/a.ts"), "/repo/src/a.ts");
|
|
28
|
+
assert.equal(toAbsPath("/repo/", "src/a.ts"), "/repo/src/a.ts");
|
|
29
|
+
assert.equal(toAbsPath("/repo", "/abs/a.ts"), "/abs/a.ts");
|
|
30
|
+
assert.equal(toAbsPath(undefined, "C:\\repo\\a.ts"), "C:\\repo\\a.ts");
|
|
31
|
+
assert.equal(toAbsPath("/repo", ""), "");
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("aggregateTraceTurnFiles:按 entryId 聚合本轮,跨 op/store 去重保首见序", () => {
|
|
35
|
+
const rows = [
|
|
36
|
+
{ entryId: "e1", stores: [store({ paths: ["a.ts", "b.ts"] })] },
|
|
37
|
+
{ entryId: "e2", stores: [store({ paths: ["b.ts", "c.ts"] }), store({ root: "/repo2", paths: ["d.ts"] })] },
|
|
38
|
+
{ entryId: "e9", stores: [store({ paths: ["x.ts"] })] }, // 不在本轮
|
|
39
|
+
];
|
|
40
|
+
const info = aggregateTraceTurnFiles(rows, ["e1", "e2"]);
|
|
41
|
+
assert.deepEqual(info.absPaths, ["/repo/a.ts", "/repo/b.ts", "/repo/c.ts", "/repo2/d.ts"]);
|
|
42
|
+
assert.equal(info.ops, 2);
|
|
43
|
+
assert.equal(info.truncated, false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("aggregateTraceTurnFiles:无命中 / 全无增量 / 空 id 集合 → null", () => {
|
|
47
|
+
assert.equal(aggregateTraceTurnFiles([{ entryId: "e1", stores: [store()] }], []), null);
|
|
48
|
+
assert.equal(aggregateTraceTurnFiles([{ entryId: "e2", stores: [store()] }], ["e1"]), null);
|
|
49
|
+
assert.equal(aggregateTraceTurnFiles([{ entryId: "e1", stores: [{ root: "/r", before: null, paths: ["a.ts"] }] }], ["e1"]), null);
|
|
50
|
+
assert.equal(aggregateTraceTurnFiles(null, ["e1"]), null);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("aggregateTraceTurnFiles:pathsTruncated 任一 store 命中即置位", () => {
|
|
54
|
+
const info = aggregateTraceTurnFiles([{ entryId: "e1", stores: [store({ pathsTruncated: true, paths: ["a.ts"] })] }], ["e1"]);
|
|
55
|
+
assert.equal(info.truncated, true);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("diffAgainstWritten:归一分隔符后求差集,保 trace 首见序", () => {
|
|
59
|
+
const extra = diffAgainstWritten(
|
|
60
|
+
["/repo/a.ts", "/repo/b.ts", "/repo/sub/c.ts"],
|
|
61
|
+
["/repo/b.ts", "/repo/sub/c.ts".replace(/\//g, "\\")],
|
|
62
|
+
);
|
|
63
|
+
assert.deepEqual(extra, ["/repo/a.ts"]);
|
|
64
|
+
assert.deepEqual(diffAgainstWritten(["/repo/a.ts"], []), ["/repo/a.ts"]);
|
|
65
|
+
assert.deepEqual(diffAgainstWritten([], ["/repo/a.ts"]), []);
|
|
66
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2020",
|
|
4
|
+
"module": "esnext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"jsxImportSource": "react",
|
|
8
|
+
"strict": false,
|
|
9
|
+
"noImplicitAny": false,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"allowJs": false,
|
|
13
|
+
"baseUrl": ".",
|
|
14
|
+
"paths": {
|
|
15
|
+
"react": ["client/src/host-shim.ts"],
|
|
16
|
+
"react/jsx-runtime": ["client/src/jsx-runtime.ts"],
|
|
17
|
+
"react/jsx-dev-runtime": ["client/src/jsx-runtime.ts"]
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"include": ["client/src/**/*.ts", "client/src/**/*.tsx"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* version-mention 纯函数单测(docs/aiti-git-trace-timeline-file-actions-plan.md §3 / E4)。
|
|
3
|
+
*
|
|
4
|
+
* 覆盖:@ token 纯路径、边路仓 show/diff 命令、无 target 回退、base==target 去重、
|
|
5
|
+
* 行号映射(hunk 头 / +/−/ctx 行、hunk 前内容)。
|
|
6
|
+
*/
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import test from "node:test";
|
|
9
|
+
import { buildVersionMention, resolveDiffLineInfo } from "./client/src/version-mention.ts";
|
|
10
|
+
|
|
11
|
+
test("buildVersionMention:有 base/target + 行号 → @ token + 边路仓 show+diff 命令", () => {
|
|
12
|
+
const s = buildVersionMention({ path: "src/a.ts", root: "/repo", base: "aaa111", target: "bbb222", line: 120, lineKind: "new" });
|
|
13
|
+
assert.ok(s.startsWith("@src/a.ts "), "at token 纯路径(带尾空格)");
|
|
14
|
+
assert.ok(s.includes("after=bbb222"), "短 hash 摘要");
|
|
15
|
+
assert.ok(s.includes("base=aaa111"), "base 短 hash");
|
|
16
|
+
assert.ok(s.includes("行120(新文件)"), "行号+新文件标注");
|
|
17
|
+
assert.ok(s.includes("git --git-dir=/repo/.aiti-trace --work-tree=/repo show bbb222:src/a.ts"), "完整 hash show 命令");
|
|
18
|
+
assert.ok(s.includes("diff aaa111..bbb222 -- src/a.ts"), "区间 diff 命令");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("buildVersionMention:无 base → 只 show 命令,无 diff", () => {
|
|
22
|
+
const s = buildVersionMention({ path: "a.ts", root: "/r", target: "ccc333" });
|
|
23
|
+
assert.ok(s.includes("after=ccc333"));
|
|
24
|
+
assert.ok(s.includes("show ccc333:a.ts"));
|
|
25
|
+
assert.ok(!s.includes("diff "), "无 base 不出 diff 命令");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("buildVersionMention:无 target(loose 工作区)→ 回退工作区版本附文", () => {
|
|
29
|
+
const s = buildVersionMention({ path: "a.ts", root: "/r" });
|
|
30
|
+
assert.ok(s.startsWith("@a.ts "));
|
|
31
|
+
assert.ok(s.includes("无边路 commit"), "回退提示");
|
|
32
|
+
assert.ok(!s.includes("git --git-dir"));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("buildVersionMention:base==target → 不重复 base 段", () => {
|
|
36
|
+
const s = buildVersionMention({ path: "a.ts", root: "/r", base: "xxx", target: "xxx" });
|
|
37
|
+
assert.ok(!s.includes("base="), "base==target 去重");
|
|
38
|
+
assert.ok(s.includes("after=xxx"));
|
|
39
|
+
assert.ok(!s.includes("diff "), "base==target 不出 diff");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("buildVersionMention:root 尾斜杠归一", () => {
|
|
43
|
+
const s = buildVersionMention({ path: "a.ts", root: "/r/", target: "ddd" });
|
|
44
|
+
assert.ok(s.includes("--git-dir=/r/.aiti-trace"), "尾斜杠被去");
|
|
45
|
+
assert.ok(s.includes("--work-tree=/r "), "work-tree 无重复斜杠");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("resolveDiffLineInfo:hunk 头 + +/−/ctx 行号映射", () => {
|
|
49
|
+
const lines = ["diff --git a b", "index 123..456", "@@ -10,3 +20,3 @@", " ctx", "+add", "-del"];
|
|
50
|
+
// hunk 前内容 → other
|
|
51
|
+
assert.equal(resolveDiffLineInfo(lines, 0).lineKind, "other");
|
|
52
|
+
assert.equal(resolveDiffLineInfo(lines, 0).line, null);
|
|
53
|
+
// hunk 头
|
|
54
|
+
assert.deepEqual(resolveDiffLineInfo(lines, 2), { line: null, lineKind: "hunk" });
|
|
55
|
+
// ctx 行 → new line 20(上下文 old/new 同步推进,但返回 new)
|
|
56
|
+
assert.equal(resolveDiffLineInfo(lines, 3).line, 20);
|
|
57
|
+
assert.equal(resolveDiffLineInfo(lines, 3).lineKind, "context");
|
|
58
|
+
// +add 行 → new line 21
|
|
59
|
+
assert.equal(resolveDiffLineInfo(lines, 4).line, 21);
|
|
60
|
+
assert.equal(resolveDiffLineInfo(lines, 4).lineKind, "new");
|
|
61
|
+
// -del 行 → old line 11(ctx 推进 old 10→11)
|
|
62
|
+
assert.equal(resolveDiffLineInfo(lines, 5).line, 11);
|
|
63
|
+
assert.equal(resolveDiffLineInfo(lines, 5).lineKind, "old");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("resolveDiffLineInfo:多 hunk 跳转后行号正确重置", () => {
|
|
67
|
+
const lines = [
|
|
68
|
+
"@@ -10,2 +20,2 @@",
|
|
69
|
+
" ctx1", // ctx: new=20,old=10 → 推进 new=21,old=11
|
|
70
|
+
"+add1", // +: new=21 → 推进 new=22
|
|
71
|
+
"@@ -50,1 +60,1 @@",
|
|
72
|
+
"+add2", // +: new=60
|
|
73
|
+
];
|
|
74
|
+
assert.equal(resolveDiffLineInfo(lines, 4).line, 60);
|
|
75
|
+
assert.equal(resolveDiffLineInfo(lines, 4).lineKind, "new");
|
|
76
|
+
});
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* aiti-git-trace client 构建配置(Vite lib mode)——JSX 模板实例。
|
|
3
|
+
*
|
|
4
|
+
* client/src/entry.tsx(JSX + TS)→ 单个 ESM bundle:client/aiti-git-trace-client.mjs
|
|
5
|
+
* (与 index.ts 的 client 字段一致;产物覆盖原手写版,源码在 git 历史可回溯)。
|
|
6
|
+
*
|
|
7
|
+
* 模板与原理:pi-web-ext/templates/ext-jsx-client/ + docs/aiti-ext-jsx-vite-plan.md
|
|
8
|
+
* 三个必踩的坑(模板已修好,勿回退):jsx automatic / 正则 alias / external []。
|
|
9
|
+
*/
|
|
10
|
+
import { defineConfig } from "vite";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
esbuild: {
|
|
15
|
+
jsx: "automatic",
|
|
16
|
+
jsxImportSource: "react",
|
|
17
|
+
},
|
|
18
|
+
resolve: {
|
|
19
|
+
alias: [
|
|
20
|
+
// 正则精确匹配(字符串 alias 是前缀匹配,find 'react' 会吞掉 'react/jsx-runtime')
|
|
21
|
+
{ find: /^react\/jsx-dev-runtime$/, replacement: fileURLToPath(new URL("client/src/jsx-runtime.ts", import.meta.url)) },
|
|
22
|
+
{ find: /^react\/jsx-runtime$/, replacement: fileURLToPath(new URL("client/src/jsx-runtime.ts", import.meta.url)) },
|
|
23
|
+
{ find: /^react$/, replacement: fileURLToPath(new URL("client/src/host-shim.ts", import.meta.url)) },
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
build: {
|
|
27
|
+
lib: {
|
|
28
|
+
entry: fileURLToPath(new URL("client/src/entry.tsx", import.meta.url)),
|
|
29
|
+
formats: ["es"],
|
|
30
|
+
fileName: () => "aiti-git-trace-client.mjs",
|
|
31
|
+
},
|
|
32
|
+
outDir: "dist",
|
|
33
|
+
emptyOutDir: true,
|
|
34
|
+
target: "es2020",
|
|
35
|
+
// 薄壳拆分(2026-09-13):panels chunk 由浏览器按需直接拉取(不再经宿主批量包二次
|
|
36
|
+
// minify),必须自身压缩;entry 顺带压缩(批量包再压一次无罩)。
|
|
37
|
+
minify: true,
|
|
38
|
+
sourcemap: false,
|
|
39
|
+
rollupOptions: {
|
|
40
|
+
external: [], // npm 包全部内联进单个 .mjs
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|