file-brief 2.0.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 +21 -0
- package/README.md +611 -0
- package/cordis.patch.yml +9 -0
- package/install.ps1 +59 -0
- package/install.sh +58 -0
- package/package.json +40 -0
- package/skills/file-brief/SKILL.md +82 -0
- package/skills/file-brief/agents/openai.yaml +7 -0
- package/skills/file-brief/scripts/file_catalog.py +2590 -0
- package/skills/file-brief/scripts/inspect_r_data.R +229 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
#!/usr/bin/env Rscript
|
|
2
|
+
#
|
|
3
|
+
# =============================================================================
|
|
4
|
+
# 代码介绍
|
|
5
|
+
# =============================================================================
|
|
6
|
+
# 输入:
|
|
7
|
+
# 1. 命令行第一个参数:一个 .rds、.rda 或 .RData 文件的绝对路径。
|
|
8
|
+
# 2. 文件内容必须能够由 base R 的 readRDS() 或 load() 读取。
|
|
9
|
+
#
|
|
10
|
+
# 输出:
|
|
11
|
+
# 标准输出为 UTF-8 JSON。顶层包含 status、format、objects 和 warnings。
|
|
12
|
+
# objects 只描述对象名称、class、typeof、维度、列名、缺失量、近似唯一值数量、
|
|
13
|
+
# 列表成员名、函数参数名等结构信息;不会输出数据行、单元格值、因子水平或文本内容。
|
|
14
|
+
# 失败时仍输出 status="error" 的 JSON,并以非零状态退出。
|
|
15
|
+
#
|
|
16
|
+
# 作用:
|
|
17
|
+
# 为 file-brief 技能提供稳定的 R 数据结构探查能力,使 Agent 不必在每个
|
|
18
|
+
# 分析任务中重复编写 readRDS()/load()/str() 等一次性检查代码。
|
|
19
|
+
#
|
|
20
|
+
# 设计逻辑:
|
|
21
|
+
# - 根据扩展名选择 readRDS() 或隔离环境中的 load()。
|
|
22
|
+
# - 递归描述对象,但限制最大深度、成员数量和统计样本量,避免巨大对象产生巨大输出。
|
|
23
|
+
# - 数据框按列输出结构统计;数组输出维度;列表输出成员结构;其他对象输出通用元数据。
|
|
24
|
+
# - 所有 JSON 由 jsonlite 生成,保证 Python 调用方可稳定解析。
|
|
25
|
+
#
|
|
26
|
+
# 主要函数:
|
|
27
|
+
# scalar_text() 将 class/typeof 等结构标签压缩为单个字符串。
|
|
28
|
+
# approximate_unique() 在最多 10,000 个元素上计算近似唯一值数量。
|
|
29
|
+
# summarize_column() 描述数据框的一列,不泄露实际值。
|
|
30
|
+
# summarize_object() 递归描述任意 R 对象。
|
|
31
|
+
# inspect_r_file() 读取文件并构造最终结构结果。
|
|
32
|
+
#
|
|
33
|
+
# 调用方式:
|
|
34
|
+
# Rscript --vanilla inspect_r_data.R "/path/to/data.rds"
|
|
35
|
+
# =============================================================================
|
|
36
|
+
|
|
37
|
+
suppressWarnings(suppressMessages({
|
|
38
|
+
if (!requireNamespace("jsonlite", quietly = TRUE)) {
|
|
39
|
+
stop("The jsonlite package is required.")
|
|
40
|
+
}
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
MAX_DEPTH <- 3L
|
|
44
|
+
MAX_CHILDREN <- 100L
|
|
45
|
+
MAX_UNIQUE_SAMPLE <- 10000L
|
|
46
|
+
|
|
47
|
+
scalar_text <- function(value) {
|
|
48
|
+
if (length(value) == 0L) {
|
|
49
|
+
return("")
|
|
50
|
+
}
|
|
51
|
+
paste(as.character(value), collapse = ", ")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
approximate_unique <- function(value) {
|
|
55
|
+
if (length(value) == 0L) {
|
|
56
|
+
return(0L)
|
|
57
|
+
}
|
|
58
|
+
sampled <- head(value, MAX_UNIQUE_SAMPLE)
|
|
59
|
+
sampled <- sampled[!is.na(sampled)]
|
|
60
|
+
length(unique(sampled))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
summarize_column <- function(value) {
|
|
64
|
+
list(
|
|
65
|
+
class = scalar_text(class(value)),
|
|
66
|
+
typeof = typeof(value),
|
|
67
|
+
length = length(value),
|
|
68
|
+
missing = sum(is.na(value)),
|
|
69
|
+
approximate_unique = approximate_unique(value),
|
|
70
|
+
unique_sample_limit = min(length(value), MAX_UNIQUE_SAMPLE)
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
summarize_object <- function(value, depth = 0L) {
|
|
75
|
+
base <- list(
|
|
76
|
+
class = scalar_text(class(value)),
|
|
77
|
+
typeof = typeof(value),
|
|
78
|
+
length = length(value),
|
|
79
|
+
object_size_bytes = as.numeric(utils::object.size(value))
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
dims <- dim(value)
|
|
83
|
+
if (!is.null(dims)) {
|
|
84
|
+
base$dimensions <- as.integer(dims)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (is.data.frame(value)) {
|
|
88
|
+
column_names <- names(value)
|
|
89
|
+
selected <- head(seq_along(value), MAX_CHILDREN)
|
|
90
|
+
columns <- lapply(selected, function(index) summarize_column(value[[index]]))
|
|
91
|
+
names(columns) <- column_names[selected]
|
|
92
|
+
base$column_count <- ncol(value)
|
|
93
|
+
base$row_count <- nrow(value)
|
|
94
|
+
base$columns <- columns
|
|
95
|
+
base$truncated_columns <- ncol(value) > MAX_CHILDREN
|
|
96
|
+
return(base)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (is.matrix(value) || is.array(value)) {
|
|
100
|
+
base$missing <- sum(is.na(value))
|
|
101
|
+
return(base)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (is.function(value)) {
|
|
105
|
+
base$parameters <- names(formals(value))
|
|
106
|
+
return(base)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (isS4(value)) {
|
|
110
|
+
slot_names <- methods::slotNames(value)
|
|
111
|
+
base$slot_names <- head(slot_names, MAX_CHILDREN)
|
|
112
|
+
if (depth < MAX_DEPTH) {
|
|
113
|
+
chosen <- head(slot_names, MAX_CHILDREN)
|
|
114
|
+
slots <- lapply(chosen, function(slot_name) {
|
|
115
|
+
summarize_object(methods::slot(value, slot_name), depth + 1L)
|
|
116
|
+
})
|
|
117
|
+
names(slots) <- chosen
|
|
118
|
+
base$slots <- slots
|
|
119
|
+
}
|
|
120
|
+
base$truncated_slots <- length(slot_names) > MAX_CHILDREN
|
|
121
|
+
return(base)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (is.list(value)) {
|
|
125
|
+
item_names <- names(value)
|
|
126
|
+
if (is.null(item_names)) {
|
|
127
|
+
item_names <- paste0("[[", seq_along(value), "]]")
|
|
128
|
+
} else {
|
|
129
|
+
empty <- !nzchar(item_names)
|
|
130
|
+
item_names[empty] <- paste0("[[", which(empty), "]]")
|
|
131
|
+
}
|
|
132
|
+
chosen_indices <- head(seq_along(value), MAX_CHILDREN)
|
|
133
|
+
base$member_names <- item_names[chosen_indices]
|
|
134
|
+
if (depth < MAX_DEPTH) {
|
|
135
|
+
children <- lapply(chosen_indices, function(index) {
|
|
136
|
+
summarize_object(value[[index]], depth + 1L)
|
|
137
|
+
})
|
|
138
|
+
names(children) <- item_names[chosen_indices]
|
|
139
|
+
base$members <- children
|
|
140
|
+
}
|
|
141
|
+
base$truncated_members <- length(value) > MAX_CHILDREN
|
|
142
|
+
return(base)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (is.atomic(value)) {
|
|
146
|
+
base$missing <- sum(is.na(value))
|
|
147
|
+
base$approximate_unique <- approximate_unique(value)
|
|
148
|
+
base$unique_sample_limit <- min(length(value), MAX_UNIQUE_SAMPLE)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
base
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
inspect_r_file <- function(path) {
|
|
155
|
+
extension <- tolower(tools::file_ext(path))
|
|
156
|
+
warnings <- character()
|
|
157
|
+
|
|
158
|
+
if (extension == "rds") {
|
|
159
|
+
value <- readRDS(path)
|
|
160
|
+
objects <- list(value = summarize_object(value))
|
|
161
|
+
return(list(
|
|
162
|
+
status = "ok",
|
|
163
|
+
format = "RDS",
|
|
164
|
+
objects = objects,
|
|
165
|
+
warnings = warnings
|
|
166
|
+
))
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (extension %in% c("rda", "rdata")) {
|
|
170
|
+
environment <- new.env(parent = emptyenv())
|
|
171
|
+
object_names <- load(path, envir = environment)
|
|
172
|
+
selected_names <- head(object_names, MAX_CHILDREN)
|
|
173
|
+
objects <- lapply(selected_names, function(object_name) {
|
|
174
|
+
summarize_object(get(object_name, envir = environment, inherits = FALSE))
|
|
175
|
+
})
|
|
176
|
+
names(objects) <- selected_names
|
|
177
|
+
if (length(object_names) > MAX_CHILDREN) {
|
|
178
|
+
warnings <- c(warnings, sprintf(
|
|
179
|
+
"Only the first %d of %d objects were described.",
|
|
180
|
+
MAX_CHILDREN,
|
|
181
|
+
length(object_names)
|
|
182
|
+
))
|
|
183
|
+
}
|
|
184
|
+
return(list(
|
|
185
|
+
status = "ok",
|
|
186
|
+
format = "RData",
|
|
187
|
+
object_count = length(object_names),
|
|
188
|
+
objects = objects,
|
|
189
|
+
warnings = warnings
|
|
190
|
+
))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
stop(sprintf("Unsupported R data extension: %s", extension))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
args <- commandArgs(trailingOnly = TRUE)
|
|
197
|
+
if (length(args) != 1L) {
|
|
198
|
+
cat(jsonlite::toJSON(
|
|
199
|
+
list(status = "error", message = "Expected exactly one input path."),
|
|
200
|
+
auto_unbox = TRUE,
|
|
201
|
+
null = "null"
|
|
202
|
+
))
|
|
203
|
+
quit(status = 2L)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
input_path <- normalizePath(args[[1L]], winslash = "\\", mustWork = FALSE)
|
|
207
|
+
|
|
208
|
+
tryCatch(
|
|
209
|
+
{
|
|
210
|
+
if (!file.exists(input_path)) {
|
|
211
|
+
stop(sprintf("Input file does not exist: %s", input_path))
|
|
212
|
+
}
|
|
213
|
+
result <- inspect_r_file(input_path)
|
|
214
|
+
cat(jsonlite::toJSON(
|
|
215
|
+
result,
|
|
216
|
+
auto_unbox = TRUE,
|
|
217
|
+
null = "null",
|
|
218
|
+
digits = NA
|
|
219
|
+
))
|
|
220
|
+
},
|
|
221
|
+
error = function(error) {
|
|
222
|
+
cat(jsonlite::toJSON(
|
|
223
|
+
list(status = "error", message = conditionMessage(error)),
|
|
224
|
+
auto_unbox = TRUE,
|
|
225
|
+
null = "null"
|
|
226
|
+
))
|
|
227
|
+
quit(status = 2L)
|
|
228
|
+
}
|
|
229
|
+
)
|