@c956180462/awbs 0.0.1

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 (50) hide show
  1. package/AWBS_CORE_DESIGN.md +983 -0
  2. package/AWBS_CURRENT_FEATURES.md +463 -0
  3. package/LICENSE +21 -0
  4. package/README.md +265 -0
  5. package/TASK_001_VIEW_AUTHORITY.md +446 -0
  6. package/TASK_003_AUTHORITY_LEDGER_AND_DB_AUDIT.md +268 -0
  7. package/TASK_004_TRUSTED_AUTHORITY_LAYER.md +547 -0
  8. package/TASK_005_AUTHORITY_SESSION.md +218 -0
  9. package/TASK_006_TRUST_BOUNDARY_HARDENING.md +381 -0
  10. package/TASK_007_TRUSTED_OPERATION_ENTRY.md +129 -0
  11. package/bin/awbs.js +2 -0
  12. package/docs/DEVELOPMENT_LEARNING.md +319 -0
  13. package/docs/FULL_CHAIN.md +295 -0
  14. package/docs/PRODUCT.md +188 -0
  15. package/docs/USAGE.md +294 -0
  16. package/package.json +45 -0
  17. package/src/adapters/file-summary-store.ts +88 -0
  18. package/src/adapters/git-cli.ts +107 -0
  19. package/src/adapters/local-authority-session.ts +606 -0
  20. package/src/adapters/local-file-database.ts +199 -0
  21. package/src/adapters/sealed-authority.ts +725 -0
  22. package/src/adapters/session-authority-client.ts +176 -0
  23. package/src/adapters/sqlite-index-store.ts +176 -0
  24. package/src/cli.ts +491 -0
  25. package/src/domain/authority-types.ts +194 -0
  26. package/src/domain/constants.ts +11 -0
  27. package/src/domain/errors.ts +6 -0
  28. package/src/domain/hash.ts +27 -0
  29. package/src/domain/path-policy.ts +36 -0
  30. package/src/domain/paths.ts +65 -0
  31. package/src/domain/session-proof.ts +140 -0
  32. package/src/domain/session-types.ts +101 -0
  33. package/src/domain/types.ts +94 -0
  34. package/src/ports/authority-session.ts +8 -0
  35. package/src/ports/authority.ts +26 -0
  36. package/src/ports/file-database.ts +18 -0
  37. package/src/ports/git.ts +23 -0
  38. package/src/ports/index-store.ts +7 -0
  39. package/src/ports/summary-store.ts +16 -0
  40. package/src/runtime.ts +56 -0
  41. package/src/session-entry.ts +1 -0
  42. package/src/usecases/authority.ts +53 -0
  43. package/src/usecases/changeset.ts +437 -0
  44. package/src/usecases/db.ts +192 -0
  45. package/src/usecases/index.ts +136 -0
  46. package/src/usecases/init.ts +48 -0
  47. package/src/usecases/ledger.ts +146 -0
  48. package/src/usecases/session.ts +48 -0
  49. package/src/usecases/trusted-chain.ts +56 -0
  50. package/src/usecases/view.ts +166 -0
package/docs/USAGE.md ADDED
@@ -0,0 +1,294 @@
1
+ # AWBS 使用文档
2
+
3
+ 本文档面向想直接试用 AWBS CLI 的用户。
4
+
5
+ ## 1. 环境要求
6
+
7
+ 需要:
8
+
9
+ - Node.js `>=24.0.0`
10
+ - Git
11
+ - 一个可以运行 shell 命令的终端
12
+
13
+ 查看版本:
14
+
15
+ ```powershell
16
+ node --version
17
+ git --version
18
+ ```
19
+
20
+ ## 2. 安装
21
+
22
+ 从 npm 安装:
23
+
24
+ ```powershell
25
+ npm install -g @c956180462/awbs
26
+ awbs --help
27
+ ```
28
+
29
+ 从本地 checkout 安装:
30
+
31
+ ```powershell
32
+ npm install -g .
33
+ awbs --help
34
+ ```
35
+
36
+ 开发时直接运行:
37
+
38
+ ```powershell
39
+ node src\cli.ts --help
40
+ npm run awbs -- --help
41
+ ```
42
+
43
+ ## 3. 初始化数据库
44
+
45
+ 进入一个项目目录:
46
+
47
+ ```powershell
48
+ mkdir my-awbs-db
49
+ cd my-awbs-db
50
+ ```
51
+
52
+ 初始化 AWBS:
53
+
54
+ ```powershell
55
+ awbs init
56
+ ```
57
+
58
+ 创建一些业务目录:
59
+
60
+ ```powershell
61
+ mkdir A
62
+ mkdir B
63
+ "read only context" | Set-Content A\context.md
64
+ "first draft" | Set-Content B\draft.md
65
+ ```
66
+
67
+ 创建初始 Git commit:
68
+
69
+ ```powershell
70
+ git add .
71
+ git commit -m "initialize database"
72
+ ```
73
+
74
+ 如果 Git 还没有用户名和邮箱,需要先配置:
75
+
76
+ ```powershell
77
+ git config user.name "AWBS User"
78
+ git config user.email "awbs@example.test"
79
+ ```
80
+
81
+ ## 4. 启动 Authority Session
82
+
83
+ 开发试用时可以用简单字符串。正式应用中,`recoverySecret` 和 `controllerToken` 应由上层应用的非 AI 控制层管理。
84
+
85
+ ```powershell
86
+ '{"recoverySecret":"dev-recovery","controllerToken":"dev-controller"}' |
87
+ awbs authority session start --control-stdin
88
+ ```
89
+
90
+ 检查状态:
91
+
92
+ ```powershell
93
+ awbs authority session status
94
+ ```
95
+
96
+ ## 5. 启动 Trusted Chain
97
+
98
+ ```powershell
99
+ 'dev-controller' | awbs ledger bootstrap --control-token-stdin
100
+ ```
101
+
102
+ 检查 ledger:
103
+
104
+ ```powershell
105
+ awbs ledger inspect
106
+ awbs ledger verify
107
+ ```
108
+
109
+ ## 6. 建立索引
110
+
111
+ ```powershell
112
+ awbs index rebuild
113
+ awbs index query
114
+ awbs index query draft
115
+ ```
116
+
117
+ 输出来自 `.awbs/index/files.sqlite`。索引可删除重建,不是事实源。
118
+
119
+ ## 7. 写入外部摘要
120
+
121
+ AWBS 不生成 AI 摘要。摘要由上层业务写入。
122
+
123
+ ```powershell
124
+ awbs summary set B/draft.md --text "A draft document owned by the business layer."
125
+ awbs summary get B/draft.md
126
+ awbs summary list
127
+ awbs index rebuild
128
+ awbs index query business
129
+ ```
130
+
131
+ ## 8. 创建工作空间视图
132
+
133
+ 创建一个 workspace:
134
+
135
+ ```powershell
136
+ 'dev-controller' |
137
+ awbs view create --out ..\my-awbs-workspace --read A --write B --control-token-stdin
138
+ ```
139
+
140
+ 含义:
141
+
142
+ - `A` 被投影进 workspace,但只读。
143
+ - `B` 被投影进 workspace,可写。
144
+ - workspace 保持原目录结构。
145
+
146
+ 查看 view:
147
+
148
+ ```powershell
149
+ awbs view inspect <viewId>
150
+ ```
151
+
152
+ ## 9. 在 workspace 中工作
153
+
154
+ 修改可写目录:
155
+
156
+ ```powershell
157
+ "second draft" | Set-Content ..\my-awbs-workspace\B\draft.md
158
+ ```
159
+
160
+ 如果修改只读目录,例如:
161
+
162
+ ```powershell
163
+ "changed context" | Set-Content ..\my-awbs-workspace\A\context.md
164
+ ```
165
+
166
+ AWBS 不会阻止你在 workspace 中这么做,但 collect 后 changeset 会是 invalid,apply 会拒绝。
167
+
168
+ ## 10. 收集 Changeset
169
+
170
+ ```powershell
171
+ awbs changeset collect --workspace ..\my-awbs-workspace
172
+ ```
173
+
174
+ 输出类似:
175
+
176
+ ```text
177
+ Changeset collected: changeset_...
178
+ Status: valid
179
+ ```
180
+
181
+ 查看:
182
+
183
+ ```powershell
184
+ awbs changeset inspect <changesetId>
185
+ awbs changeset inspect <changesetId> --json
186
+ ```
187
+
188
+ ## 11. 应用 Changeset
189
+
190
+ ```powershell
191
+ 'dev-controller' |
192
+ awbs changeset apply <changesetId> --control-token-stdin
193
+ ```
194
+
195
+ 成功后:
196
+
197
+ - 文件写回 AWBS 数据库。
198
+ - Git 创建 commit。
199
+ - trusted chain 前进。
200
+ - `refs/awbs/trusted` 更新。
201
+
202
+ ## 12. 撤销 View
203
+
204
+ ```powershell
205
+ 'dev-controller' |
206
+ awbs view revoke <viewId> --control-token-stdin
207
+ ```
208
+
209
+ 撤销只影响未来 collect/apply,不删除旧 workspace,不删除已提交数据,不改 Git 历史。
210
+
211
+ ## 13. 审计数据库
212
+
213
+ ```powershell
214
+ awbs db audit
215
+ ```
216
+
217
+ 它会报告:
218
+
219
+ - 当前 HEAD。
220
+ - trusted commit。
221
+ - 工作树是否 dirty。
222
+ - 是否存在外部 commit。
223
+ - authority / ledger 是否可验证。
224
+
225
+ ## 14. 从 Trusted Chain 重建干净数据库
226
+
227
+ 如果普通工作区被污染,可以执行:
228
+
229
+ ```powershell
230
+ awbs db clean-rebuild
231
+ ```
232
+
233
+ 注意:
234
+
235
+ - 运行前需要停止 authority session。
236
+ - 原目录会被整体改名为 backup。
237
+ - 干净目录从 trusted commit 重建。
238
+ - backup 不会自动删除。
239
+
240
+ 停止 session:
241
+
242
+ ```powershell
243
+ 'dev-controller' | awbs authority session stop --control-token-stdin
244
+ ```
245
+
246
+ ## 15. Session 崩溃恢复
247
+
248
+ 如果 session daemon 崩溃,`local.json` 不存在,但 `recovery.seal.json` 仍在:
249
+
250
+ ```powershell
251
+ 'dev-recovery' | awbs authority session recover --recovery-secret-stdin
252
+ ```
253
+
254
+ 错误 recovery secret 会失败,不会写出伪成功文件。
255
+
256
+ ## 16. 常用命令总览
257
+
258
+ ```text
259
+ awbs init
260
+ awbs index rebuild
261
+ awbs index query [term] [--status active|removed|all] [--json]
262
+ awbs summary set <path> (--text <summary> | --file <file>)
263
+ awbs summary get <path> [--json]
264
+ awbs summary list [--json]
265
+ awbs view create --out <workspace> [--read A] [--write B] --control-token-stdin
266
+ awbs view inspect <viewId> [--json]
267
+ awbs view revoke <viewId> --control-token-stdin
268
+ awbs changeset collect --workspace <workspace>
269
+ awbs changeset inspect <changesetDir|id> [--json]
270
+ awbs changeset apply <changesetDir|id> --control-token-stdin
271
+ awbs ledger bootstrap [--json] --control-token-stdin
272
+ awbs ledger inspect [--json]
273
+ awbs ledger verify [--json]
274
+ awbs db audit [--json]
275
+ awbs db clean-rebuild [--json]
276
+ awbs db backups list [--json]
277
+ awbs authority session start --control-stdin [--json]
278
+ awbs authority session status [--json]
279
+ awbs authority session stop --control-token-stdin [--json]
280
+ awbs authority session recover --recovery-secret-stdin [--json]
281
+ awbs authority verify [--json]
282
+ awbs authority repair-mirrors --control-token-stdin [--json]
283
+ ```
284
+
285
+ ## 17. 发布前检查
286
+
287
+ 开发者发布前建议运行:
288
+
289
+ ```powershell
290
+ npm test
291
+ node src\cli.ts --help
292
+ npm pack --dry-run
293
+ git diff --check
294
+ ```
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@c956180462/awbs",
3
+ "version": "0.0.1",
4
+ "description": "Agent Work Base Space CLI prototype for file-system database workspaces.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "awbs": "bin/awbs.js"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/a956180462/AWBS.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/a956180462/AWBS/issues"
16
+ },
17
+ "homepage": "https://github.com/a956180462/AWBS#readme",
18
+ "keywords": [
19
+ "agent",
20
+ "workspace",
21
+ "filesystem",
22
+ "git",
23
+ "cli"
24
+ ],
25
+ "files": [
26
+ "bin",
27
+ "src",
28
+ "docs",
29
+ "AWBS_CORE_DESIGN.md",
30
+ "AWBS_CURRENT_FEATURES.md",
31
+ "TASK_001_VIEW_AUTHORITY.md",
32
+ "TASK_003_AUTHORITY_LEDGER_AND_DB_AUDIT.md",
33
+ "TASK_004_TRUSTED_AUTHORITY_LAYER.md",
34
+ "TASK_005_AUTHORITY_SESSION.md",
35
+ "TASK_006_TRUST_BOUNDARY_HARDENING.md",
36
+ "TASK_007_TRUSTED_OPERATION_ENTRY.md"
37
+ ],
38
+ "scripts": {
39
+ "awbs": "node src/cli.ts",
40
+ "test": "node --test --test-concurrency=1 tests/*.test.ts"
41
+ },
42
+ "engines": {
43
+ "node": ">=24.0.0"
44
+ }
45
+ }
@@ -0,0 +1,88 @@
1
+ import { readdirSync, readFileSync } from "node:fs";
2
+ import type { IndexKind, SummaryEntry } from "../domain/types.ts";
3
+ import type { FileDatabasePort } from "../ports/file-database.ts";
4
+ import type { SummaryStorePort, SummaryWriteInput } from "../ports/summary-store.ts";
5
+
6
+ export class FileSummaryStoreAdapter implements SummaryStorePort {
7
+ private readonly files: FileDatabasePort;
8
+
9
+ constructor(files: FileDatabasePort) {
10
+ this.files = files;
11
+ }
12
+
13
+ readSummaries(summaryFile: string): SummaryEntry[] {
14
+ if (!this.files.pathExists(summaryFile)) {
15
+ return [];
16
+ }
17
+ const content = this.files.readText(summaryFile).trim();
18
+ if (!content) {
19
+ return [];
20
+ }
21
+ return content.split(/\r?\n/).map((line) => JSON.parse(line) as SummaryEntry);
22
+ }
23
+
24
+ writeSummary(summaryFile: string, input: SummaryWriteInput): SummaryEntry {
25
+ const summaries = this.readSummaries(summaryFile);
26
+ const nextEntry: SummaryEntry = {
27
+ schemaVersion: 1,
28
+ path: input.path,
29
+ kind: input.kind,
30
+ sha256: input.sha256,
31
+ commit: input.commit,
32
+ summary: input.summary,
33
+ source: "external",
34
+ updatedAt: new Date().toISOString(),
35
+ ext: {}
36
+ };
37
+
38
+ const next = summaries.filter((entry) => !(entry.path === nextEntry.path && entry.sha256 === nextEntry.sha256));
39
+ next.push(nextEntry);
40
+ next.sort((a, b) => a.path.localeCompare(b.path) || (a.sha256 ?? "").localeCompare(b.sha256 ?? ""));
41
+ this.files.writeText(summaryFile, next.map((entry) => `${JSON.stringify(entry)}\n`).join(""));
42
+ return nextEntry;
43
+ }
44
+
45
+ findSummary(summaryFile: string, path: string, sha256: string | null): SummaryEntry | null {
46
+ const summaries = this.readSummaries(summaryFile).filter((entry) => entry.path === path);
47
+ const exact = summaries.find((entry) => entry.sha256 === sha256);
48
+ if (exact) {
49
+ return exact;
50
+ }
51
+ const pathLevel = summaries.find((entry) => entry.sha256 === null);
52
+ return pathLevel ?? null;
53
+ }
54
+
55
+ fallbackSummary(absPath: string, relPath: string, kind: IndexKind): string {
56
+ if (kind === "directory") {
57
+ const count = readdirSync(absPath).length;
58
+ return `Directory ${relPath} with ${count} item${count === 1 ? "" : "s"}.`;
59
+ }
60
+
61
+ const buffer = readFileSync(absPath);
62
+ if (!looksText(buffer)) {
63
+ return `Binary file ${relPath} (${buffer.length} bytes).`;
64
+ }
65
+
66
+ if (buffer.length === 0) {
67
+ return `Empty text file ${relPath}.`;
68
+ }
69
+ return `Text file ${relPath} (${buffer.length} bytes).`;
70
+ }
71
+ }
72
+
73
+ function looksText(buffer: Buffer): boolean {
74
+ if (buffer.length === 0) {
75
+ return true;
76
+ }
77
+ const sample = buffer.subarray(0, Math.min(buffer.length, 4096));
78
+ let suspicious = 0;
79
+ for (const byte of sample) {
80
+ if (byte === 0) {
81
+ return false;
82
+ }
83
+ if (byte < 7 || (byte > 14 && byte < 32)) {
84
+ suspicious += 1;
85
+ }
86
+ }
87
+ return suspicious / sample.length < 0.05;
88
+ }
@@ -0,0 +1,107 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { AwbsError } from "../domain/errors.ts";
3
+ import type { GitCommandResult, GitPort } from "../ports/git.ts";
4
+
5
+ export class GitCliAdapter implements GitPort {
6
+ isRepository(root: string): boolean {
7
+ const result = this.runResult(["rev-parse", "--is-inside-work-tree"], root);
8
+ return result.status === 0 && result.stdout.trim() === "true";
9
+ }
10
+
11
+ init(root: string): void {
12
+ this.run(["init"], root);
13
+ }
14
+
15
+ headCommit(root: string): string | null {
16
+ const result = this.runResult(["rev-parse", "HEAD"], root);
17
+ return result.status === 0 ? result.stdout.trim() : null;
18
+ }
19
+
20
+ requireHeadCommit(root: string): string {
21
+ const commit = this.headCommit(root);
22
+ if (!commit) {
23
+ throw new AwbsError("Git HEAD is not available. Create an initial commit before running this command.");
24
+ }
25
+ return commit;
26
+ }
27
+
28
+ refCommit(root: string, ref: string): string | null {
29
+ const result = this.runResult(["rev-parse", "--verify", ref], root);
30
+ return result.status === 0 ? result.stdout.trim() : null;
31
+ }
32
+
33
+ updateRef(root: string, ref: string, commit: string): void {
34
+ this.run(["update-ref", ref, commit], root);
35
+ }
36
+
37
+ isAncestor(root: string, ancestor: string, descendant: string): boolean {
38
+ const result = this.runResult(["merge-base", "--is-ancestor", ancestor, descendant], root);
39
+ return result.status === 0;
40
+ }
41
+
42
+ revList(root: string, range: string): string[] {
43
+ const output = this.run(["rev-list", "--reverse", range], root).trim();
44
+ return output ? output.split(/\r?\n/) : [];
45
+ }
46
+
47
+ statusPorcelain(root: string): string {
48
+ return this.run(["status", "--porcelain", "-uall"], root);
49
+ }
50
+
51
+ addAll(root: string, paths: string[]): void {
52
+ this.run(["add", "-A", "--", ...paths], root);
53
+ }
54
+
55
+ commit(root: string, message: string): void {
56
+ this.run(["commit", "-m", message], root);
57
+ }
58
+
59
+ createDetachedWorktree(root: string, path: string, commit: string): void {
60
+ this.run(["worktree", "add", "--detach", path, commit], root);
61
+ }
62
+
63
+ removeWorktree(root: string, path: string): void {
64
+ this.run(["worktree", "remove", "--force", path], root);
65
+ }
66
+
67
+ cloneAtCommit(sourceRoot: string, destination: string, commit: string): void {
68
+ this.run(["clone", "--no-checkout", "--no-hardlinks", sourceRoot, destination], sourceRoot);
69
+ const sourceRemote = this.runResult(["remote", "get-url", "origin"], sourceRoot);
70
+ if (sourceRemote.status === 0 && sourceRemote.stdout.trim()) {
71
+ this.run(["remote", "set-url", "origin", sourceRemote.stdout.trim()], destination);
72
+ }
73
+ this.run(["checkout", "--detach", commit], destination);
74
+ }
75
+
76
+ diffNoIndex(baselineRoot: string, workspacePath: string): string {
77
+ const result = spawnSync("git", ["diff", "--no-index", "--binary", "--no-color", "--", baselineRoot, workspacePath], {
78
+ encoding: "utf8",
79
+ stdio: ["ignore", "pipe", "pipe"]
80
+ });
81
+ if (result.status === 0 || result.status === 1) {
82
+ return result.stdout ?? "";
83
+ }
84
+ return `git diff --no-index failed:\n${result.stderr ?? ""}`;
85
+ }
86
+
87
+ private run(args: string[], cwd: string): string {
88
+ const result = this.runResult(args, cwd);
89
+ if (result.status !== 0) {
90
+ throw new AwbsError(`git ${args.join(" ")} failed:\n${result.stderr || result.stdout}`);
91
+ }
92
+ return result.stdout;
93
+ }
94
+
95
+ private runResult(args: string[], cwd: string): GitCommandResult {
96
+ const result = spawnSync("git", args, {
97
+ cwd,
98
+ encoding: "utf8",
99
+ stdio: ["ignore", "pipe", "pipe"]
100
+ });
101
+ return {
102
+ stdout: result.stdout ?? "",
103
+ stderr: result.stderr ?? "",
104
+ status: result.status
105
+ };
106
+ }
107
+ }