autosnippet 1.5.7 → 1.5.8

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 CHANGED
@@ -114,7 +114,7 @@ asd install:full --lancedb # 仅安装 LanceDB(向量检索更快)
114
114
  | **Recipe** | `Knowledge/recipes/` 下的 Markdown 知识(配方):含代码块 + 使用说明,供 AI 检索、Guard、搜索 |
115
115
  | **Snippet** | Xcode 代码片段,通过 trigger(默认 `@`)补全,可与 Recipe 关联 |
116
116
  | **Candidate(候选)** | 待审核入库的项;来自 `as:create`、MCP 提交、`asd ais` 扫描等,经 Dashboard 审核后保存为 Recipe/Snippet |
117
- | **Knowledge** | 项目知识库目录,包含 `recipes/`、`.autosnippet/`(索引、candidates、guard 配置等);Snippet 配置在 root spec 的 list |
117
+ | **Knowledge** | 项目知识库目录,包含 `recipes/`、`.autosnippet/`(索引、candidates、guard 配置等);Snippet 配置在 root spec 的 list 中。其下各路径与 Git 的关系见 [Knowledge 目录与 Git](#knowledge-目录与-git)。 |
118
118
  | **Dashboard** | Web 管理后台(`asd ui` 启动),含 Recipes、Candidates、Guard、Snippets 等页面 |
119
119
  | **watch** | 文件监听进程(`asd ui` 或 `asd watch` 启动),保存时触发 `as:create`、`as:guard`、`as:search` |
120
120
  | **Guard** | 按 Recipe 知识库对代码做 AI 审查;`// as:guard` 触发 |
@@ -125,7 +125,23 @@ asd install:full --lancedb # 仅安装 LanceDB(向量检索更快)
125
125
  | **项目根** | 含 `AutoSnippetRoot.boxspec.json` 的目录 |
126
126
  | **Target** | SPM 模块/编译单元;`asd ais <Target>` 扫描该 Target 下的源码提取候选 |
127
127
 
128
- **详细介绍**:启动 `asd ui` 后访问 Dashboard → **使用说明** 页;或参阅 [使用文档](docs/使用文档.md)(含 Skills 一览、AI 配置、闭环详解等)。
128
+ **详细介绍**:启动 `asd ui` 后访问 Dashboard → **使用说明** 页;或参阅 [使用文档](docs/使用文档.md)(含 Skills 一览、AI 配置、闭环详解等)。
129
+
130
+ ## Knowledge 目录与 Git
131
+
132
+ Knowledge 下各路径与版本控制的关系建议如下(可按项目需要调整):
133
+
134
+ | 路径 | 说明 | 建议 |
135
+ |------|------|------|
136
+ | **Knowledge/recipes/** | Recipe 的 Markdown 文件 | **Git 子仓库**:单独建远程仓库并 `git submodule add <url> Knowledge/recipes`,用于权限拦截(仅能 push 子仓库的人可保存/上传 Recipe)。详见 [权限设置说明](docs/权限设置说明.md) 中「只把 Knowledge/recipes 作为子仓库」。 |
137
+ | **Knowledge/.autosnippet/** | Guard 规则、违反记录、candidates、recipe-stats、context 配置等 | **跟随主仓库 Git**:规则与配置建议提交到主仓库,便于团队共享。 |
138
+ | **Knowledge/.autosnippet/context/index/** | 语义向量索引(embed 生成) | **不跟随 Git**:体积大、机器相关,建议加入 `.gitignore`(如 `Knowledge/.autosnippet/context/index/` 或其下 `lancedb/`、`vector_index.json`)。 |
139
+ | **Knowledge/.autosnippet/candidates/**(若存在) | 候选数据等 | 视需要:若仅本地缓存可不提交;若团队共享可跟随主仓库或单独子仓库。 |
140
+ | **Knowledge/AutoSnippet.spmmap.json**(若存在) | SPM 依赖映射 | **跟随主仓库 Git**:便于依赖关系图一致。 |
141
+
142
+ - **跟随主仓库 Git**:由主项目 `git add/commit/push` 管理,所有人按主仓库权限读写。
143
+ - **Git 子仓库**:`Knowledge/recipes` 为单独仓库(submodule),Recipe 上传(git push)由 Git 服务端权限拦截。配合 `.env` 中 `ASD_RECIPES_WRITE_DIR=Knowledge/recipes` 是为了保证管理员(有 push 权限者)能够正确提交 Recipe:探针目录与 Recipe 写入目录一致,保存后可正常推送。
144
+ - **不跟随 Git**:在 `.gitignore` 中忽略,不提交、不推送。
129
145
 
130
146
  ---
131
147
 
package/bin/asd CHANGED
@@ -13,6 +13,7 @@ DIR="$(cd "$(dirname "$SCRIPT")" && pwd)"
13
13
  # 将调用时的当前目录传给 Node,供查找项目根(dev:link 等场景下 process.cwd() 可能不是用户所在目录)
14
14
  ASD_CWD="$(pwd)"
15
15
  export ASD_CWD
16
+ # 优先 Swift 入口(完整性校验,仅 macOS),否则回退 node
16
17
  if [ -x "$DIR/asd-verify" ]; then
17
18
  exec "$DIR/asd-verify" "$@"
18
19
  else
package/bin/asnip.js CHANGED
@@ -22,6 +22,21 @@
22
22
 
23
23
  const fs = require('fs');
24
24
  const path = require('path');
25
+
26
+ // 入口校验:包内存在 checksums.json 且未经过 asd-verify(无 ASD_VERIFIED)时,可拒跑或警告,避免绕过完整性校验直接运行 node bin/asnip.js
27
+ const pkgRoot = path.join(__dirname, '..');
28
+ const checksumsPath = path.join(pkgRoot, 'checksums.json');
29
+ if (fs.existsSync(checksumsPath) && process.env.ASD_VERIFIED !== '1') {
30
+ const msg = 'asd: 未经过完整性校验入口(请使用 asd 命令,勿直接运行 node bin/asnip.js)。开发/调试可设 ASD_SKIP_ENTRY_CHECK=1 跳过。';
31
+ if (process.env.ASD_STRICT_ENTRY === '1') {
32
+ console.error(msg);
33
+ process.exit(1);
34
+ }
35
+ if (process.env.ASD_SKIP_ENTRY_CHECK !== '1') {
36
+ console.warn('⚠️ ' + msg);
37
+ }
38
+ }
39
+
25
40
  // 读取输入命令
26
41
  const inquirer = require('inquirer');
27
42
  // 命令行工具
@@ -1023,6 +1038,17 @@ commander
1023
1038
  console.log(`${fail} .env: 不存在,请从 .env.example 复制并填写 API Key`);
1024
1039
  }
1025
1040
 
1041
+ // 2.5 写权限探针(可选)
1042
+ try {
1043
+ const writeGuard = require('../lib/writeGuard');
1044
+ const probeDir = writeGuard.getProbeDir(projectRoot);
1045
+ if (probeDir) {
1046
+ const probePath = path.join(projectRoot, probeDir);
1047
+ const exists = fs.existsSync(probePath) && fs.statSync(probePath).isDirectory();
1048
+ console.log(`${exists ? ok : fail} 写权限探针: 已配置 (${probeDir})${exists ? '' : ',目录不存在'}`);
1049
+ }
1050
+ } catch (_) {}
1051
+
1026
1052
  // 3. 语义索引(JsonAdapter: context/index/vector_index.json,LanceDB: context/index/lancedb/,manifest 由 embed 写入)
1027
1053
  const paths = require('../lib/infra/paths');
1028
1054
  const indexPath = paths.getContextIndexPath(projectRoot);
package/checksums.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "bin/asnip.js": "39b8cb4e8c8db37230d95dc234178b2c921d129f5c6ba4a671541de2781d35cd",
2
+ "bin/asnip.js": "9c8fdca74a0522836c48a61a85db65b065dc8f72e026696040d280d6714edeb9",
3
3
  "bin/ui.js": "e1b52cb75b6d404edcb9298f6e3dcae0bf7e2bd95c7df2c38ff020c8111bf96f",
4
4
  "lib/writeGuard.js": "f6574f09668d854b871415e96570359859b0ecd79d46d609af90b2d2833bd817"
5
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "autosnippet",
3
- "version": "1.5.7",
3
+ "version": "1.5.8",
4
4
  "description": "A iOS module management tool.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,6 +1,8 @@
1
- /**
2
- * AutoSnippet 原生入口:完整性校验 + spawn Node 执行 bin/asnip.js
3
- * checksums.json 不存在则跳过校验(开发模式);存在则校验关键文件 SHA-256,不通过则 exit(1)。
1
+ #!/usr/bin/env swift
2
+ /*
3
+ * AutoSnippet 完整性校验入口(Swift,仅 macOS)
4
+ * 读 checksums.json,校验关键文件 SHA-256,通过则设置 ASD_VERIFIED=1 并 spawn node bin/asnip.js。
5
+ * 构建:node scripts/build-asd-entry.js,产物 bin/asd-verify。
4
6
  */
5
7
 
6
8
  import Foundation
@@ -8,66 +10,58 @@ import CryptoKit
8
10
 
9
11
  func fail(_ msg: String) -> Never {
10
12
  fputs(msg + "\n", stderr)
11
- fflush(stderr)
12
13
  exit(1)
13
14
  }
14
15
 
15
- /// 从 argv[0] 解析包根目录(bin 的父目录)。要求 root 下存在 bin/asnip.js,否则返回 nil。
16
+ /// 从可执行路径解析包根目录(bin/asd-verify 的上级的上级)
16
17
  func getPackageRoot() -> String? {
17
- let arg0 = CommandLine.arguments[0]
18
- let cwd = FileManager.default.currentDirectoryPath
19
- var pathStr = arg0
20
- if !arg0.hasPrefix("/") {
21
- pathStr = (cwd as NSString).appendingPathComponent(arg0)
18
+ let argv0 = CommandLine.arguments[0]
19
+ let path = (argv0 as NSString).standardizingPath
20
+ var url = URL(fileURLWithPath: path)
21
+ if path != (path as NSString).resolvingSymlinksInPath {
22
+ url = URL(fileURLWithPath: (path as NSString).resolvingSymlinksInPath)
22
23
  }
23
- let url = URL(fileURLWithPath: pathStr).resolvingSymlinksInPath()
24
- let binDir = url.deletingLastPathComponent()
25
- let root = binDir.deletingLastPathComponent()
26
- let rootPath = root.path
27
- let asnipPath = (rootPath as NSString).appendingPathComponent("bin/asnip.js")
28
- guard FileManager.default.fileExists(atPath: asnipPath) else {
29
- return nil
30
- }
31
- return rootPath
24
+ var dir = url.deletingLastPathComponent().path // bin
25
+ dir = (dir as NSString).deletingLastPathComponent // package root
26
+ return dir
32
27
  }
33
28
 
34
- /// 计算文件 SHA-256 十六进制字符串
35
- func sha256Hex(fileURL: URL) -> String? {
36
- guard let data = try? Data(contentsOf: fileURL) else { return nil }
37
- let digest = SHA256.hash(data: data)
38
- return digest.map { String(format: "%02x", $0) }.joined()
29
+ /// 文件内容 SHA-256 小写 hex
30
+ func sha256Hex(filePath: String) -> String? {
31
+ guard let data = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else { return nil }
32
+ let hash = SHA256.hash(data: data)
33
+ return hash.map { String(format: "%02x", $0) }.joined()
39
34
  }
40
35
 
41
- /// 校验 checksums.json 中列出的文件。禁止 relPath 含 ".." 或为绝对路径,防止路径逃逸。
36
+ /// 校验 checksums.json 中列出的文件。拒绝 relPath 含 ".." 或为绝对路径。
42
37
  func verifyIntegrity(root: String, checksumsPath: String) -> Bool {
43
38
  guard let data = try? Data(contentsOf: URL(fileURLWithPath: checksumsPath)) else {
44
39
  fputs("asd: 无法读取 checksums.json\n", stderr)
45
40
  return false
46
41
  }
47
- guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: String] else {
42
+ guard let json = try? JSONSerialization.jsonObject(with: data),
43
+ let entries = json as? [String: String] else {
48
44
  fputs("asd: checksums.json 格式无效\n", stderr)
49
45
  return false
50
46
  }
51
- let rootURL = URL(fileURLWithPath: root)
52
- var rootNorm = rootURL.resolvingSymlinksInPath().path
53
- if rootNorm.hasSuffix("/") { rootNorm = String(rootNorm.dropLast()) }
54
- for (relPath, expectedHex) in json {
47
+ let rootNorm = (root as NSString).standardizingPath
48
+ for (relPath, expectedHex) in entries {
55
49
  if relPath.hasPrefix("/") || relPath.contains("..") {
56
50
  fputs("asd: 校验拒绝非法路径: \(relPath)\n", stderr)
57
51
  return false
58
52
  }
59
- let fileURL = rootURL.appendingPathComponent(relPath)
60
- let resolvedPath = fileURL.resolvingSymlinksInPath().path
61
- guard resolvedPath == rootNorm || resolvedPath.hasPrefix(rootNorm + "/") else {
53
+ let fullPath = (root as NSString).appendingPathComponent(relPath)
54
+ let fullNorm = (fullPath as NSString).standardizingPath
55
+ let rootSlash = rootNorm.hasSuffix("/") ? rootNorm : rootNorm + "/"
56
+ guard fullNorm == rootNorm || fullNorm.hasPrefix(rootSlash) else {
62
57
  fputs("asd: 校验拒绝路径逃逸: \(relPath)\n", stderr)
63
58
  return false
64
59
  }
65
- guard FileManager.default.fileExists(atPath: fileURL.path),
66
- let actualHex = sha256Hex(fileURL: fileURL) else {
67
- fputs("asd: 校验失败(无法读取): \(relPath)\n", stderr)
60
+ guard let actualHex = sha256Hex(filePath: fullPath) else {
61
+ fputs("asd: 完整性校验失败: \(relPath)\n", stderr)
68
62
  return false
69
63
  }
70
- if actualHex != expectedHex {
64
+ if actualHex.lowercased() != expectedHex.lowercased() {
71
65
  fputs("asd: 完整性校验失败: \(relPath)\n", stderr)
72
66
  return false
73
67
  }
@@ -75,19 +69,22 @@ func verifyIntegrity(root: String, checksumsPath: String) -> Bool {
75
69
  return true
76
70
  }
77
71
 
78
- /// 执行 node bin/asnip.js [args...],返回子进程退出码。将调用时的 cwd 传入 ASD_CWD,供 asnip 查找项目根。
79
- func spawnNode(root: String) -> Int32 {
72
+ /// 执行 node bin/asnip.js [args...],将调用时的 cwd 传入 ASD_CWD,校验通过则设 ASD_VERIFIED=1。
73
+ func spawnNode(root: String, integrityVerified: Bool) -> Int32 {
80
74
  let asnipPath = (root as NSString).appendingPathComponent("bin/asnip.js")
81
75
  guard FileManager.default.fileExists(atPath: asnipPath) else {
82
76
  fail("asd: 未找到 bin/asnip.js")
83
77
  }
84
- let nodeArgs = ["node", asnipPath] + CommandLine.arguments.dropFirst()
78
+ let nodeArgs = ["node", asnipPath] + Array(CommandLine.arguments.dropFirst())
85
79
  let process = Process()
86
80
  process.executableURL = URL(fileURLWithPath: "/usr/bin/env")
87
- process.arguments = Array(nodeArgs)
81
+ process.arguments = nodeArgs
88
82
  process.currentDirectoryURL = URL(fileURLWithPath: root)
89
83
  var env = ProcessInfo.processInfo.environment
90
84
  env["ASD_CWD"] = FileManager.default.currentDirectoryPath
85
+ if integrityVerified {
86
+ env["ASD_VERIFIED"] = "1"
87
+ }
91
88
  process.environment = env
92
89
  process.standardInput = FileHandle.standardInput
93
90
  process.standardOutput = FileHandle.standardOutput
@@ -108,11 +105,12 @@ guard let root = getPackageRoot() else {
108
105
  }
109
106
 
110
107
  let checksumsPath = (root as NSString).appendingPathComponent("checksums.json")
111
-
108
+ var integrityVerified = false
112
109
  if FileManager.default.fileExists(atPath: checksumsPath) {
113
110
  if !verifyIntegrity(root: root, checksumsPath: checksumsPath) {
114
111
  exit(1)
115
112
  }
113
+ integrityVerified = true
116
114
  }
117
115
 
118
- exit(spawnNode(root: root))
116
+ exit(spawnNode(root: root, integrityVerified: integrityVerified))
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * 构建 asd 原生入口(完整性校验 + spawn Node)。仅 macOS 且 Swift 可用时构建,失败则静默跳过。
5
- * 产物:bin/asd-verify。若不存在,bin/asd 脚本会回退到 node bin/asnip.js。
4
+ * 构建 asd 完整性校验入口(Swift,仅 macOS)。产物:bin/asd-verify。
5
+ * 若不存在或构建失败,bin/asd 将回退到 node bin/asnip.js。
6
6
  */
7
7
 
8
8
  if (process.platform !== 'darwin') process.exit(0);