diff-easy 0.1.0
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/LICENSE +22 -0
- package/README.md +142 -0
- package/dist/cli/cmux.d.ts +9 -0
- package/dist/cli/cmux.js +274 -0
- package/dist/cli/comment.d.ts +2 -0
- package/dist/cli/comment.js +91 -0
- package/dist/cli/github.d.ts +84 -0
- package/dist/cli/github.js +364 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +397 -0
- package/dist/cli/turns.d.ts +14 -0
- package/dist/cli/turns.js +333 -0
- package/dist/cli/utils.d.ts +27 -0
- package/dist/cli/utils.js +204 -0
- package/dist/client/assets/arc-CHYjdFTk.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-weHBYDgM.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-DRa2msuJ.js +36 -0
- package/dist/client/assets/array-BifhSqXX.js +1 -0
- package/dist/client/assets/blockDiagram-GPEHLZMM-BTdKsXy5.js +132 -0
- package/dist/client/assets/c4Diagram-AAUBKEIU-CwTvCkOg.js +10 -0
- package/dist/client/assets/channel-Cv40nQW1.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-BFsjrTtc.js +1 -0
- package/dist/client/assets/chunk-3OPIFGDE-V_xVw1dq.js +62 -0
- package/dist/client/assets/chunk-4BX2VUAB-DoLO6LLy.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-CHgBwbdB.js +1 -0
- package/dist/client/assets/chunk-5ZQYHXKU-DMixAgQG.js +2 -0
- package/dist/client/assets/chunk-727SXJPM-C-3FZTmm.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-DpQZhXt-.js +231 -0
- package/dist/client/assets/chunk-BSJP7CBP-BnO9E_G4.js +1 -0
- package/dist/client/assets/chunk-CSCIHK7Q-BxItphyO.js +124 -0
- package/dist/client/assets/chunk-FMBD7UC4-4hEcfvfT.js +15 -0
- package/dist/client/assets/chunk-KSCS5N6A-DnArkpOI.js +10 -0
- package/dist/client/assets/chunk-L5ZTLDWV-DQoYJ1BW.js +1 -0
- package/dist/client/assets/chunk-LZXEDZCA-CdCevBX-.js +2 -0
- package/dist/client/assets/chunk-ND2GUHAM-Dt89U_nU.js +1 -0
- package/dist/client/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/dist/client/assets/chunk-NZK2D7GU-C2Ev_f_w.js +1 -0
- package/dist/client/assets/chunk-O5CBEL6O-BeZ3MFnJ.js +70 -0
- package/dist/client/assets/chunk-QZHKN3VN-BmDOSWHp.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-gLuInTk4.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-DPbDc8_T.js +32 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-DOpVKUrb.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DOpVKUrb.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-CCPyalms.js +1 -0
- package/dist/client/assets/cytoscape.esm-FqbQrHcz.js +321 -0
- package/dist/client/assets/dagre-BM42HDAG-6jrYMjX3.js +4 -0
- package/dist/client/assets/dagre-Bx709z4p.js +1 -0
- package/dist/client/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-dCQ_KoXl.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-B3Xj4-XE.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-DaGgF2yL.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-CF_c4tPT.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-BgBHbJkT.js +24 -0
- package/dist/client/assets/dist-7wVkzZne.js +1 -0
- package/dist/client/assets/erDiagram-TEJ5UH35-BZgX1grR.js +85 -0
- package/dist/client/assets/eventmodeling-FCH6USID-BaI6RNKr.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-BFNUa12V.js +162 -0
- package/dist/client/assets/ganttDiagram-6RSMTGT7-C8t6BThI.js +292 -0
- package/dist/client/assets/gitGraph-WXDBUCRP-BlicfDkM.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-BmnIXuXP.js +106 -0
- package/dist/client/assets/graphlib-B8gBHxth.js +1 -0
- package/dist/client/assets/index-BfVtbKTZ.css +2 -0
- package/dist/client/assets/index-p8zzfjdu.js +87 -0
- package/dist/client/assets/info-J43DQDTF-DHLG38N3.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-oQ-lq-45.js +2 -0
- package/dist/client/assets/init-D6jRqBbL.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DrODlBLD.js +70 -0
- package/dist/client/assets/journeyDiagram-JHISSGLW-Bxg5wCQz.js +139 -0
- package/dist/client/assets/kanban-definition-UN3LZRKU-DAdvUDIB.js +89 -0
- package/dist/client/assets/katex-Vhh-h91d.js +257 -0
- package/dist/client/assets/line-D8kNd46u.js +1 -0
- package/dist/client/assets/linear-jwGZSrg4.js +1 -0
- package/dist/client/assets/mermaid-parser.core-PVPfUfyU.js +4 -0
- package/dist/client/assets/mermaid.core-DwdP6CFg.js +9 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-DeOoL56Q.js +96 -0
- package/dist/client/assets/ordinal-hYBb2elL.js +1 -0
- package/dist/client/assets/packet-YPE3B663-BBEaxdJP.js +1 -0
- package/dist/client/assets/path-BWPyau1x.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-fvTRFm-2.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-oep1px2r.js +30 -0
- package/dist/client/assets/prism-bash-D6zCJ74D.js +1 -0
- package/dist/client/assets/prism-clojure-_z-zoNcE.js +1 -0
- package/dist/client/assets/prism-csharp-XPn2bw0d.js +1 -0
- package/dist/client/assets/prism-dart-CDv16Fbu.js +1 -0
- package/dist/client/assets/prism-elixir-DhHhV5Rj.js +1 -0
- package/dist/client/assets/prism-haskell-5hq1Nh9c.js +1 -0
- package/dist/client/assets/prism-hcl-B6Z0Ouoo.js +1 -0
- package/dist/client/assets/prism-java-6wtusvIl.js +1 -0
- package/dist/client/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
- package/dist/client/assets/prism-nix-C88dTX93.js +1 -0
- package/dist/client/assets/prism-perl-Lsbi34sH.js +1 -0
- package/dist/client/assets/prism-php-Co4fM40K.js +1 -0
- package/dist/client/assets/prism-protobuf-yzSHlhwy.js +1 -0
- package/dist/client/assets/prism-ruby-DFCASkuD.js +1 -0
- package/dist/client/assets/prism-scala-VkM3wCOL.js +1 -0
- package/dist/client/assets/prism-solidity-DTom4k5p.js +1 -0
- package/dist/client/assets/prism-sql-CYhVDMEE.js +1 -0
- package/dist/client/assets/prism-vim-BgpkjCK5.js +1 -0
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-D9l66CHb.js +7 -0
- package/dist/client/assets/radar-GUYGQ44K-DAU5EPVd.js +1 -0
- package/dist/client/assets/requirementDiagram-4Y6WPE33-helpQlwb.js +84 -0
- package/dist/client/assets/rough.esm-CSKSodPl.js +1 -0
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-C9sNuuvA.js +40 -0
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-BP0bhCwQ.js +162 -0
- package/dist/client/assets/src-CDAPGf2l.js +1 -0
- package/dist/client/assets/stateDiagram-AJRCARHV-CsFizpla.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bvn8tanl.js +1 -0
- package/dist/client/assets/timeline-definition-PNZ67QCA-BnkmHs1F.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-DD9HZO5W.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-BBKuQ1lo.js +1 -0
- package/dist/client/assets/vennDiagram-CIIHVFJN-BPFTHflF.js +34 -0
- package/dist/client/assets/wardley-L42UT6IY-y88gDkwb.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-BlcA7IVh.js +78 -0
- package/dist/client/assets/xychartDiagram-2RQKCTM6-DX5vUCRH.js +7 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/index.html +14 -0
- package/dist/client/logo.png +0 -0
- package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
- package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
- package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/manifest.json +1 -0
- package/dist/client/site-data/og-image.png +0 -0
- package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
- package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
- package/dist/server/file-watcher.d.ts +33 -0
- package/dist/server/file-watcher.js +340 -0
- package/dist/server/generated-file-check.d.ts +17 -0
- package/dist/server/generated-file-check.js +280 -0
- package/dist/server/git-diff.d.ts +94 -0
- package/dist/server/git-diff.js +909 -0
- package/dist/server/grouping/cache.d.ts +3 -0
- package/dist/server/grouping/cache.js +22 -0
- package/dist/server/grouping/claudeCli.d.ts +10 -0
- package/dist/server/grouping/claudeCli.js +95 -0
- package/dist/server/grouping/digest.d.ts +15 -0
- package/dist/server/grouping/digest.js +38 -0
- package/dist/server/grouping/reconcile.d.ts +6 -0
- package/dist/server/grouping/reconcile.js +34 -0
- package/dist/server/grouping/types.d.ts +9 -0
- package/dist/server/grouping/types.js +1 -0
- package/dist/server/server.d.ts +37 -0
- package/dist/server/server.js +1283 -0
- package/dist/shared/grouping.d.ts +4 -0
- package/dist/shared/grouping.js +170 -0
- package/dist/types/diff.d.ts +263 -0
- package/dist/types/diff.js +1 -0
- package/dist/types/watch.d.ts +59 -0
- package/dist/types/watch.js +8 -0
- package/dist/utils/commentFormatting.d.ts +6 -0
- package/dist/utils/commentFormatting.js +90 -0
- package/dist/utils/commentImports.d.ts +11 -0
- package/dist/utils/commentImports.js +386 -0
- package/dist/utils/createId.d.ts +1 -0
- package/dist/utils/createId.js +5 -0
- package/dist/utils/diffMode.d.ts +3 -0
- package/dist/utils/diffMode.js +13 -0
- package/dist/utils/diffSelection.d.ts +13 -0
- package/dist/utils/diffSelection.js +39 -0
- package/dist/utils/editorOptions.d.ts +60 -0
- package/dist/utils/editorOptions.js +171 -0
- package/dist/utils/fileUtils.d.ts +12 -0
- package/dist/utils/fileUtils.js +21 -0
- package/dist/utils/hubConfig.d.ts +32 -0
- package/dist/utils/hubConfig.js +51 -0
- package/dist/utils/suggestionUtils.d.ts +19 -0
- package/dist/utils/suggestionUtils.js +31 -0
- package/dist/utils/turnLog.d.ts +47 -0
- package/dist/utils/turnLog.js +133 -0
- package/package.json +105 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { execFileSync } from 'child_process';
|
|
2
|
+
import { randomUUID } from 'crypto';
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'fs';
|
|
4
|
+
import { homedir, tmpdir } from 'os';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
import { deleteHubInfo, deleteServerInfo, readHubInfo, readServerInfo, readTurnLog, writeTurnLog, } from '../utils/turnLog.js';
|
|
9
|
+
import { readStdin } from './utils.js';
|
|
10
|
+
/** Git ref that anchors all live snapshot commits so their objects survive GC. */
|
|
11
|
+
const SNAPSHOT_REF = 'refs/difit/snapshots';
|
|
12
|
+
const PROMPT_PREVIEW_MAX = 120;
|
|
13
|
+
/** Per-session retention: keep at most this many most-recent turns. */
|
|
14
|
+
const MAX_TURNS_PER_SESSION = 50;
|
|
15
|
+
function gitEnv(extra = {}) {
|
|
16
|
+
return {
|
|
17
|
+
...process.env,
|
|
18
|
+
GIT_AUTHOR_NAME: 'diff-easy',
|
|
19
|
+
GIT_AUTHOR_EMAIL: 'diff-easy@local',
|
|
20
|
+
GIT_COMMITTER_NAME: 'diff-easy',
|
|
21
|
+
GIT_COMMITTER_EMAIL: 'diff-easy@local',
|
|
22
|
+
...extra,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function resolveRepoRoot(cwd) {
|
|
26
|
+
try {
|
|
27
|
+
return execFileSync('git', ['-C', cwd, 'rev-parse', '--show-toplevel'], {
|
|
28
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
29
|
+
})
|
|
30
|
+
.toString()
|
|
31
|
+
.trim();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Capture the entire current working tree (tracked + untracked, honoring
|
|
39
|
+
* .gitignore) as a standalone git commit, without touching the user's real
|
|
40
|
+
* index or working tree. The commit is kept alive by reanchorSnapshots().
|
|
41
|
+
*/
|
|
42
|
+
function snapshotWorkingTree(repoRoot) {
|
|
43
|
+
const tmpIndex = join(tmpdir(), `difit-index-${process.pid}-${randomUUID()}`);
|
|
44
|
+
// Seed the temp index from the repo's real index so `git add -A` reuses the
|
|
45
|
+
// stat cache and only re-hashes actually-changed files. Without this, a fresh
|
|
46
|
+
// (empty) index forces git to lstat + hash the ENTIRE working tree every turn
|
|
47
|
+
// — seconds on a large repo, twice per turn, on the agent's critical path.
|
|
48
|
+
try {
|
|
49
|
+
const gitDir = execFileSync('git', ['rev-parse', '--absolute-git-dir'], {
|
|
50
|
+
cwd: repoRoot,
|
|
51
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
52
|
+
})
|
|
53
|
+
.toString()
|
|
54
|
+
.trim();
|
|
55
|
+
const realIndex = join(gitDir, 'index');
|
|
56
|
+
if (gitDir && existsSync(realIndex)) {
|
|
57
|
+
copyFileSync(realIndex, tmpIndex);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// fall back to an empty temp index (slower but correct)
|
|
62
|
+
}
|
|
63
|
+
const env = gitEnv({ GIT_INDEX_FILE: tmpIndex });
|
|
64
|
+
try {
|
|
65
|
+
execFileSync('git', ['add', '-A'], { cwd: repoRoot, env, stdio: 'ignore' });
|
|
66
|
+
const tree = execFileSync('git', ['write-tree'], { cwd: repoRoot, env }).toString().trim();
|
|
67
|
+
const commit = execFileSync('git', ['commit-tree', tree, '-m', 'diff-easy turn snapshot'], {
|
|
68
|
+
cwd: repoRoot,
|
|
69
|
+
env,
|
|
70
|
+
})
|
|
71
|
+
.toString()
|
|
72
|
+
.trim();
|
|
73
|
+
return { commit, tree };
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
try {
|
|
77
|
+
unlinkSync(tmpIndex);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// best-effort cleanup
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Keep at most MAX_TURNS_PER_SESSION most-recent turns per session. */
|
|
85
|
+
function pruneLog(log) {
|
|
86
|
+
const bySession = new Map();
|
|
87
|
+
for (const turn of log) {
|
|
88
|
+
const arr = bySession.get(turn.sessionId) ?? [];
|
|
89
|
+
arr.push(turn);
|
|
90
|
+
bySession.set(turn.sessionId, arr);
|
|
91
|
+
}
|
|
92
|
+
if ([...bySession.values()].every((arr) => arr.length <= MAX_TURNS_PER_SESSION)) {
|
|
93
|
+
return log;
|
|
94
|
+
}
|
|
95
|
+
const keep = new Set();
|
|
96
|
+
for (const arr of bySession.values()) {
|
|
97
|
+
// log is chronological (appended in order), so the tail is most-recent.
|
|
98
|
+
for (const turn of arr.slice(-MAX_TURNS_PER_SESSION))
|
|
99
|
+
keep.add(turn.id);
|
|
100
|
+
}
|
|
101
|
+
return log.filter((turn) => keep.has(turn.id));
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Point SNAPSHOT_REF at a single anchor commit whose parents are every snapshot
|
|
105
|
+
* commit still referenced by the (pruned) log. Snapshot commits dropped by
|
|
106
|
+
* pruning are no longer reachable from any ref, so a routine `git gc` reclaims
|
|
107
|
+
* them — bounding `.git` growth.
|
|
108
|
+
*/
|
|
109
|
+
function reanchorSnapshots(repoRoot, log, tree) {
|
|
110
|
+
const commits = new Set();
|
|
111
|
+
for (const turn of log) {
|
|
112
|
+
if (turn.commitBefore)
|
|
113
|
+
commits.add(turn.commitBefore);
|
|
114
|
+
if (turn.commitAfter)
|
|
115
|
+
commits.add(turn.commitAfter);
|
|
116
|
+
}
|
|
117
|
+
const env = gitEnv();
|
|
118
|
+
if (commits.size === 0) {
|
|
119
|
+
try {
|
|
120
|
+
execFileSync('git', ['update-ref', '-d', SNAPSHOT_REF], { cwd: repoRoot, stdio: 'ignore' });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// ref may not exist yet
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const parentArgs = [];
|
|
128
|
+
for (const commit of commits)
|
|
129
|
+
parentArgs.push('-p', commit);
|
|
130
|
+
const anchor = execFileSync('git', ['commit-tree', tree, ...parentArgs, '-m', 'diff-easy snapshot anchor'], { cwd: repoRoot, env })
|
|
131
|
+
.toString()
|
|
132
|
+
.trim();
|
|
133
|
+
execFileSync('git', ['update-ref', SNAPSHOT_REF, anchor], { cwd: repoRoot, env });
|
|
134
|
+
}
|
|
135
|
+
function previewOf(prompt) {
|
|
136
|
+
if (!prompt)
|
|
137
|
+
return null;
|
|
138
|
+
const oneLine = prompt.replace(/\s+/gu, ' ').trim();
|
|
139
|
+
if (oneLine.length <= PROMPT_PREVIEW_MAX)
|
|
140
|
+
return oneLine;
|
|
141
|
+
return `${oneLine.slice(0, PROMPT_PREVIEW_MAX - 1)}…`;
|
|
142
|
+
}
|
|
143
|
+
/** True if a process with this pid currently exists (EPERM still means alive). */
|
|
144
|
+
function isPidAlive(pid) {
|
|
145
|
+
try {
|
|
146
|
+
process.kill(pid, 0);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return err.code === 'EPERM';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function pingTurnComplete(port) {
|
|
154
|
+
try {
|
|
155
|
+
const controller = new AbortController();
|
|
156
|
+
const timer = setTimeout(() => controller.abort(), 300);
|
|
157
|
+
await fetch(`http://127.0.0.1:${port}/events/turn-complete`, {
|
|
158
|
+
method: 'POST',
|
|
159
|
+
signal: controller.signal,
|
|
160
|
+
}).catch(() => undefined);
|
|
161
|
+
clearTimeout(timer);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// server not running / unreachable — fine, capture is decoupled from viewing
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Best-effort live-refresh ping. Capped so it never slows the agent's turn.
|
|
169
|
+
*
|
|
170
|
+
* A viewer advertises itself in one of two ways: a per-repo `server.json` (a
|
|
171
|
+
* standalone `difit` opened on this repo) or the global `hub.json` (the
|
|
172
|
+
* always-on hub, which serves many repos and never writes a per-repo file).
|
|
173
|
+
* Ping every live advertisement so the timeline refreshes regardless of which
|
|
174
|
+
* one the user has open — without this, hub users only saw new turns after a
|
|
175
|
+
* manual refresh.
|
|
176
|
+
*/
|
|
177
|
+
export async function notifyServer(repoRoot) {
|
|
178
|
+
const targets = [];
|
|
179
|
+
const repoInfo = readServerInfo(repoRoot);
|
|
180
|
+
if (repoInfo) {
|
|
181
|
+
targets.push({ info: repoInfo, clearStale: () => deleteServerInfo(repoRoot) });
|
|
182
|
+
}
|
|
183
|
+
const hubInfo = readHubInfo();
|
|
184
|
+
if (hubInfo) {
|
|
185
|
+
targets.push({ info: hubInfo, clearStale: () => deleteHubInfo() });
|
|
186
|
+
}
|
|
187
|
+
const pingedPorts = new Set();
|
|
188
|
+
for (const { info, clearStale } of targets) {
|
|
189
|
+
// A stale advertisement (the viewer exited without cleaning up) would make
|
|
190
|
+
// us POST to whatever process now holds that port. If the recorded pid is
|
|
191
|
+
// dead, skip the ping and clear the stale file.
|
|
192
|
+
if (typeof info.pid === 'number' && !isPidAlive(info.pid)) {
|
|
193
|
+
clearStale();
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (pingedPorts.has(info.port)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
pingedPorts.add(info.port);
|
|
200
|
+
await pingTurnComplete(info.port);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function runSnapshot() {
|
|
204
|
+
// Skip when this Claude session was spawned internally by diff-easy itself
|
|
205
|
+
// (e.g. the file-grouping provider's `claude -p` call). Otherwise diff-easy
|
|
206
|
+
// would record its own tool invocations as spurious agent turns — which also
|
|
207
|
+
// fires the turn-complete SSE and hijacks the live view away from the diff
|
|
208
|
+
// the user is grouping. See src/server/grouping/claudeCli.ts.
|
|
209
|
+
if (process.env.DIFIT_INTERNAL_CLAUDE === '1')
|
|
210
|
+
return;
|
|
211
|
+
// Never throw: a failing hook must not block the agent.
|
|
212
|
+
try {
|
|
213
|
+
const input = await readStdin().catch(() => '');
|
|
214
|
+
let payload = {};
|
|
215
|
+
try {
|
|
216
|
+
payload = JSON.parse(input || '{}');
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
payload = {};
|
|
220
|
+
}
|
|
221
|
+
const cwd = payload.cwd || process.cwd();
|
|
222
|
+
const repoRoot = resolveRepoRoot(cwd);
|
|
223
|
+
if (!repoRoot)
|
|
224
|
+
return; // no-op outside a git repo
|
|
225
|
+
const { commit, tree } = snapshotWorkingTree(repoRoot);
|
|
226
|
+
const sessionId = payload.session_id || 'unknown';
|
|
227
|
+
const event = payload.hook_event_name || '';
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
const log = readTurnLog(repoRoot);
|
|
230
|
+
if (event === 'UserPromptSubmit') {
|
|
231
|
+
const prompt = typeof payload.prompt === 'string' ? payload.prompt : null;
|
|
232
|
+
const record = {
|
|
233
|
+
id: randomUUID(),
|
|
234
|
+
sessionId,
|
|
235
|
+
prompt,
|
|
236
|
+
promptPreview: previewOf(prompt),
|
|
237
|
+
commitBefore: commit,
|
|
238
|
+
commitAfter: null,
|
|
239
|
+
startedAt: now,
|
|
240
|
+
endedAt: null,
|
|
241
|
+
status: 'open',
|
|
242
|
+
};
|
|
243
|
+
log.push(record);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
// Stop / SubagentStop / anything else closes the current open turn.
|
|
247
|
+
const openTurn = [...log]
|
|
248
|
+
.reverse()
|
|
249
|
+
.find((t) => t.sessionId === sessionId && t.status === 'open');
|
|
250
|
+
if (openTurn) {
|
|
251
|
+
openTurn.commitAfter = commit;
|
|
252
|
+
openTurn.endedAt = now;
|
|
253
|
+
openTurn.status = 'closed';
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
const lastForSession = [...log].reverse().find((t) => t.sessionId === sessionId);
|
|
257
|
+
log.push({
|
|
258
|
+
id: randomUUID(),
|
|
259
|
+
sessionId,
|
|
260
|
+
prompt: null,
|
|
261
|
+
promptPreview: null,
|
|
262
|
+
commitBefore: lastForSession?.commitAfter ?? commit,
|
|
263
|
+
commitAfter: commit,
|
|
264
|
+
startedAt: now,
|
|
265
|
+
endedAt: now,
|
|
266
|
+
status: 'closed',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const pruned = pruneLog(log);
|
|
271
|
+
writeTurnLog(repoRoot, pruned);
|
|
272
|
+
reanchorSnapshots(repoRoot, pruned, tree);
|
|
273
|
+
await notifyServer(repoRoot);
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// swallow — hook must always exit 0
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
export function createSnapshotCommand() {
|
|
280
|
+
return new Command('snapshot')
|
|
281
|
+
.description('Internal: capture a working-tree snapshot for a Claude Code turn (hook entrypoint)')
|
|
282
|
+
.action(async () => {
|
|
283
|
+
await runSnapshot();
|
|
284
|
+
process.exit(0);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const HOOK_EVENTS = ['UserPromptSubmit', 'Stop'];
|
|
288
|
+
function snapshotCommandString() {
|
|
289
|
+
// Absolute, install-independent invocation of this very CLI.
|
|
290
|
+
const cliPath = fileURLToPath(import.meta.url).replace(/turns\.js$/u, 'index.js');
|
|
291
|
+
return `"${process.execPath}" "${cliPath}" snapshot`;
|
|
292
|
+
}
|
|
293
|
+
function readSettings(path) {
|
|
294
|
+
try {
|
|
295
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return {};
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
export function createInitCommand() {
|
|
302
|
+
return new Command('init')
|
|
303
|
+
.description('Install the Claude Code hooks that capture per-turn diffs')
|
|
304
|
+
.option('--project', 'install into ./.claude/settings.json instead of ~/.claude/settings.json')
|
|
305
|
+
.action((options) => {
|
|
306
|
+
const settingsPath = options.project
|
|
307
|
+
? join(process.cwd(), '.claude', 'settings.json')
|
|
308
|
+
: join(homedir(), '.claude', 'settings.json');
|
|
309
|
+
const settings = readSettings(settingsPath);
|
|
310
|
+
settings.hooks ??= {};
|
|
311
|
+
const command = snapshotCommandString();
|
|
312
|
+
let added = 0;
|
|
313
|
+
let already = 0;
|
|
314
|
+
for (const event of HOOK_EVENTS) {
|
|
315
|
+
const entries = (settings.hooks[event] ??= []);
|
|
316
|
+
const exists = entries.some((entry) => entry.hooks?.some((h) => h.command?.includes('snapshot') && h.command?.includes('index.js')));
|
|
317
|
+
if (exists) {
|
|
318
|
+
already += 1;
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
entries.push({ hooks: [{ type: 'command', command }] });
|
|
322
|
+
added += 1;
|
|
323
|
+
}
|
|
324
|
+
const dir = dirname(settingsPath);
|
|
325
|
+
if (!existsSync(dir))
|
|
326
|
+
mkdirSync(dir, { recursive: true });
|
|
327
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
328
|
+
console.log(`diff-easy: hooks written to ${settingsPath}`);
|
|
329
|
+
console.log(` added ${added} hook(s), ${already} already present`);
|
|
330
|
+
console.log(' events: UserPromptSubmit, Stop');
|
|
331
|
+
console.log('\nStart (or restart) Claude Code in your repo, then run `diff-easy` to review per-turn diffs.');
|
|
332
|
+
});
|
|
333
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Stats } from 'node:fs';
|
|
2
|
+
import type { SimpleGit } from 'simple-git';
|
|
3
|
+
import type { CommentImport } from '../types/diff.js';
|
|
4
|
+
type StdinStat = Pick<Stats, 'isFIFO' | 'isFile' | 'isSocket'>;
|
|
5
|
+
type StdinSource = 'pipe' | 'file' | 'socket' | 'tty';
|
|
6
|
+
export declare function detectStdinSource(stdinStat?: StdinStat): StdinSource;
|
|
7
|
+
interface ShouldReadStdinOptions {
|
|
8
|
+
commitish: string;
|
|
9
|
+
hasPositionalArgs: boolean;
|
|
10
|
+
hasPrOption: boolean;
|
|
11
|
+
stdinSource?: StdinSource;
|
|
12
|
+
}
|
|
13
|
+
export declare function shouldReadStdin(options: ShouldReadStdinOptions): boolean;
|
|
14
|
+
export declare function getGitRoot(): string;
|
|
15
|
+
export declare function validateCommitish(commitish: string): boolean;
|
|
16
|
+
export declare function shortHash(hash: string): string;
|
|
17
|
+
export declare function createCommitRangeString(baseHash: string, targetHash: string): string;
|
|
18
|
+
export declare function parseCommentOptions(commentValues: string[]): CommentImport[];
|
|
19
|
+
export declare function validateDiffArguments(targetCommitish: string, baseCommitish?: string): {
|
|
20
|
+
valid: boolean;
|
|
21
|
+
error?: string;
|
|
22
|
+
};
|
|
23
|
+
export declare function findUntrackedFiles(git: SimpleGit): Promise<string[]>;
|
|
24
|
+
export declare function markFilesIntentToAdd(git: SimpleGit, files: string[]): Promise<void>;
|
|
25
|
+
export declare function promptUser(message: string): Promise<boolean>;
|
|
26
|
+
export declare function readStdin(): Promise<string>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { fstatSync } from 'node:fs';
|
|
3
|
+
import { createInterface } from 'readline/promises';
|
|
4
|
+
import { parseCommentImportValue } from '../utils/commentImports.js';
|
|
5
|
+
export function detectStdinSource(stdinStat = fstatSync(0)) {
|
|
6
|
+
if (stdinStat.isFIFO()) {
|
|
7
|
+
return 'pipe';
|
|
8
|
+
}
|
|
9
|
+
if (stdinStat.isFile()) {
|
|
10
|
+
return 'file';
|
|
11
|
+
}
|
|
12
|
+
if (stdinStat.isSocket()) {
|
|
13
|
+
return 'socket';
|
|
14
|
+
}
|
|
15
|
+
return 'tty';
|
|
16
|
+
}
|
|
17
|
+
export function shouldReadStdin(options) {
|
|
18
|
+
if (options.commitish === '-') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (options.hasPositionalArgs || options.hasPrOption) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
const stdinSource = options.stdinSource ?? detectStdinSource();
|
|
25
|
+
return stdinSource === 'pipe' || stdinSource === 'file' || stdinSource === 'socket';
|
|
26
|
+
}
|
|
27
|
+
export function getGitRoot() {
|
|
28
|
+
try {
|
|
29
|
+
const result = execSync('git rev-parse --show-toplevel', {
|
|
30
|
+
encoding: 'utf8',
|
|
31
|
+
stdio: 'pipe',
|
|
32
|
+
});
|
|
33
|
+
return result.trim();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error('Not a git repository (or any of the parent directories)');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function validateCommitish(commitish) {
|
|
40
|
+
if (!commitish || typeof commitish !== 'string') {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
const trimmed = commitish.trim();
|
|
44
|
+
if (trimmed.length === 0) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (trimmed === '.' || trimmed === 'working' || trimmed === 'staged') {
|
|
48
|
+
return true; // Allow special keywords for working directory and staging area diff
|
|
49
|
+
}
|
|
50
|
+
const baseCommitish = stripRevisionSuffix(trimmed);
|
|
51
|
+
if (baseCommitish.length === 0) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return isValidCommitishBase(baseCommitish);
|
|
55
|
+
}
|
|
56
|
+
function isValidCommitishBase(baseCommitish) {
|
|
57
|
+
const validBasePatterns = [
|
|
58
|
+
/^[a-f0-9]{4,40}$/i, // SHA hashes
|
|
59
|
+
/^HEAD$/, // HEAD
|
|
60
|
+
/^@$/, // @ is Git alias for HEAD
|
|
61
|
+
];
|
|
62
|
+
if (validBasePatterns.some((pattern) => pattern.test(baseCommitish))) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// For branch, tag, and remote refs, use git's ref naming rules.
|
|
66
|
+
return isValidBranchName(baseCommitish);
|
|
67
|
+
}
|
|
68
|
+
function stripRevisionSuffix(commitish) {
|
|
69
|
+
let suffixStart = commitish.length;
|
|
70
|
+
while (suffixStart > 0) {
|
|
71
|
+
const current = commitish[suffixStart - 1];
|
|
72
|
+
if (current === '^') {
|
|
73
|
+
suffixStart--;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (current === '~') {
|
|
77
|
+
suffixStart--;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!isAsciiDigit(current)) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
let digitStart = suffixStart - 1;
|
|
84
|
+
while (digitStart > 0 && isAsciiDigit(commitish[digitStart - 1])) {
|
|
85
|
+
digitStart--;
|
|
86
|
+
}
|
|
87
|
+
const operator = commitish[digitStart - 1];
|
|
88
|
+
if (operator !== '^' && operator !== '~') {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
suffixStart = digitStart - 1;
|
|
92
|
+
}
|
|
93
|
+
return commitish.slice(0, suffixStart);
|
|
94
|
+
}
|
|
95
|
+
function isAsciiDigit(char) {
|
|
96
|
+
return char >= '0' && char <= '9';
|
|
97
|
+
}
|
|
98
|
+
function isValidBranchName(name) {
|
|
99
|
+
// Git branch name rules
|
|
100
|
+
if (name.startsWith('-'))
|
|
101
|
+
return false; // Cannot start with dash
|
|
102
|
+
if (name.endsWith('.'))
|
|
103
|
+
return false; // Cannot end with dot
|
|
104
|
+
// @ is a valid Git alias for HEAD, so we should allow it
|
|
105
|
+
if (name.includes('..'))
|
|
106
|
+
return false; // No consecutive dots
|
|
107
|
+
if (name.includes('@{'))
|
|
108
|
+
return false; // No @{ sequence
|
|
109
|
+
if (name.includes('//'))
|
|
110
|
+
return false; // No consecutive slashes
|
|
111
|
+
if (name.startsWith('/') || name.endsWith('/'))
|
|
112
|
+
return false; // No leading/trailing slashes
|
|
113
|
+
if (name.endsWith('.lock'))
|
|
114
|
+
return false; // Cannot end with .lock
|
|
115
|
+
// Check for forbidden characters
|
|
116
|
+
const forbiddenChars = /[~^:?*[\\\x00-\x20\x7F]/;
|
|
117
|
+
if (forbiddenChars.test(name))
|
|
118
|
+
return false;
|
|
119
|
+
// Check path components
|
|
120
|
+
const components = name.split('/');
|
|
121
|
+
for (const component of components) {
|
|
122
|
+
if (component === '')
|
|
123
|
+
return false; // Empty component
|
|
124
|
+
if (component.startsWith('.'))
|
|
125
|
+
return false; // Component cannot start with dot
|
|
126
|
+
if (component.endsWith('.lock'))
|
|
127
|
+
return false; // Component cannot end with .lock
|
|
128
|
+
}
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
export function shortHash(hash) {
|
|
132
|
+
return hash.substring(0, 7);
|
|
133
|
+
}
|
|
134
|
+
export function createCommitRangeString(baseHash, targetHash) {
|
|
135
|
+
return `${baseHash}...${targetHash}`;
|
|
136
|
+
}
|
|
137
|
+
export function parseCommentOptions(commentValues) {
|
|
138
|
+
return commentValues.flatMap((value) => parseCommentImportValue(value));
|
|
139
|
+
}
|
|
140
|
+
export function validateDiffArguments(targetCommitish, baseCommitish) {
|
|
141
|
+
// Validate target commitish format
|
|
142
|
+
if (!validateCommitish(targetCommitish)) {
|
|
143
|
+
return { valid: false, error: 'Invalid target commit-ish format' };
|
|
144
|
+
}
|
|
145
|
+
// Validate base commitish format if provided
|
|
146
|
+
if (baseCommitish !== undefined && !validateCommitish(baseCommitish)) {
|
|
147
|
+
return { valid: false, error: 'Invalid base commit-ish format' };
|
|
148
|
+
}
|
|
149
|
+
// Special arguments are only allowed in target, not base (except staged with working)
|
|
150
|
+
const specialArgs = ['working', 'staged', '.'];
|
|
151
|
+
if (baseCommitish && specialArgs.includes(baseCommitish)) {
|
|
152
|
+
// Allow 'staged' as base only when target is 'working'
|
|
153
|
+
if (baseCommitish === 'staged' && targetCommitish === 'working') {
|
|
154
|
+
// This is valid: working vs staged
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
return {
|
|
158
|
+
valid: false,
|
|
159
|
+
error: `Special arguments (working, staged, .) are only allowed as target, not base. Got base: ${baseCommitish}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Cannot compare same values
|
|
164
|
+
if (targetCommitish === baseCommitish) {
|
|
165
|
+
return {
|
|
166
|
+
valid: false,
|
|
167
|
+
error: `Cannot compare ${targetCommitish} with itself`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// "working" shows unstaged changes and can only be compared with staging area
|
|
171
|
+
if (targetCommitish === 'working' && baseCommitish && baseCommitish !== 'staged') {
|
|
172
|
+
return {
|
|
173
|
+
valid: false,
|
|
174
|
+
error: '"working" shows unstaged changes and cannot be compared with another commit. Use "." instead to compare all uncommitted changes with a specific commit.',
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
return { valid: true };
|
|
178
|
+
}
|
|
179
|
+
export async function findUntrackedFiles(git) {
|
|
180
|
+
const status = await git.status();
|
|
181
|
+
return status.not_added;
|
|
182
|
+
}
|
|
183
|
+
// Add files with --intent-to-add to make them visible in `git diff` without staging content
|
|
184
|
+
export async function markFilesIntentToAdd(git, files) {
|
|
185
|
+
await git.add(['--intent-to-add', ...files]);
|
|
186
|
+
}
|
|
187
|
+
export async function promptUser(message) {
|
|
188
|
+
const rl = createInterface({
|
|
189
|
+
input: process.stdin,
|
|
190
|
+
output: process.stdout,
|
|
191
|
+
});
|
|
192
|
+
const answer = await rl.question(message);
|
|
193
|
+
rl.close();
|
|
194
|
+
// Empty string (Enter) or 'y', 'yes' return true
|
|
195
|
+
const trimmed = answer.trim().toLowerCase();
|
|
196
|
+
return trimmed === '' || ['y', 'yes'].includes(trimmed);
|
|
197
|
+
}
|
|
198
|
+
export async function readStdin() {
|
|
199
|
+
const chunks = [];
|
|
200
|
+
for await (const chunk of process.stdin) {
|
|
201
|
+
chunks.push(chunk);
|
|
202
|
+
}
|
|
203
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
204
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-7wVkzZne.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./chunk-NNHCCRGN-DlpIbxXb.js";import{x as e}from"./mermaid-parser.core-PVPfUfyU.js";export{e as createArchitectureServices};
|