alp-code 0.9.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.
Files changed (204) hide show
  1. package/CHANGELOG.md +770 -0
  2. package/LICENSE +21 -0
  3. package/README.md +295 -0
  4. package/alp.config.yaml +5 -0
  5. package/dist/src/agents/agent-definition.js +28 -0
  6. package/dist/src/agents/capability-catalog.js +33 -0
  7. package/dist/src/agents/compaction.js +36 -0
  8. package/dist/src/agents/errors.js +12 -0
  9. package/dist/src/agents/librarian.js +38 -0
  10. package/dist/src/agents/main.js +37 -0
  11. package/dist/src/agents/memory-grant.js +29 -0
  12. package/dist/src/agents/model-context.js +70 -0
  13. package/dist/src/agents/modes.js +134 -0
  14. package/dist/src/agents/oracle.js +36 -0
  15. package/dist/src/agents/read-thread.js +38 -0
  16. package/dist/src/agents/registry.js +238 -0
  17. package/dist/src/agents/render-identity.js +38 -0
  18. package/dist/src/agents/review.js +37 -0
  19. package/dist/src/agents/search.js +37 -0
  20. package/dist/src/agents/shared/house-rules.js +33 -0
  21. package/dist/src/agents/shared/principal.js +18 -0
  22. package/dist/src/agents/shared/voice.js +29 -0
  23. package/dist/src/agents/titling.js +32 -0
  24. package/dist/src/agents/types.js +15 -0
  25. package/dist/src/backend/execution-backend.js +2 -0
  26. package/dist/src/backend/local-execution-store.js +144 -0
  27. package/dist/src/backend/local-process-backend.js +533 -0
  28. package/dist/src/backend/local-supervisor.js +104 -0
  29. package/dist/src/cli/alp.js +380 -0
  30. package/dist/src/cli/commands/context.js +203 -0
  31. package/dist/src/cli/commands/delegate.js +136 -0
  32. package/dist/src/cli/commands/identity-sync.js +31 -0
  33. package/dist/src/cli/commands/init.js +184 -0
  34. package/dist/src/cli/commands/mode.js +22 -0
  35. package/dist/src/cli/commands/principal.js +114 -0
  36. package/dist/src/cli/commands/run-main.js +90 -0
  37. package/dist/src/cli/commands/runtime.js +21 -0
  38. package/dist/src/cli/mode-preference-store.js +62 -0
  39. package/dist/src/cli/mode-selector.js +178 -0
  40. package/dist/src/cli/update-check.js +77 -0
  41. package/dist/src/context/checkpoint.js +134 -0
  42. package/dist/src/context/compact-journal.js +153 -0
  43. package/dist/src/context/compact-payload.js +121 -0
  44. package/dist/src/context/continuity.js +70 -0
  45. package/dist/src/context/types.js +2 -0
  46. package/dist/src/delegation/backend-registry.js +40 -0
  47. package/dist/src/delegation/delegation-service.js +300 -0
  48. package/dist/src/delegation/types.js +12 -0
  49. package/dist/src/execution/execution-policy.js +96 -0
  50. package/dist/src/execution/execution-service.js +115 -0
  51. package/dist/src/execution/execution-store.js +78 -0
  52. package/dist/src/execution/identity-capsule.js +65 -0
  53. package/dist/src/execution/types.js +12 -0
  54. package/dist/src/hooks/execution-bridge.js +84 -0
  55. package/dist/src/index.js +4 -0
  56. package/dist/src/memory/adapters/markdown-file-store.js +257 -0
  57. package/dist/src/memory/adapters/memory-api-client.js +2 -0
  58. package/dist/src/memory/adapters/memory-path-mapper.js +76 -0
  59. package/dist/src/memory/adapters/remote-api-store.js +25 -0
  60. package/dist/src/memory/context-ranker.js +21 -0
  61. package/dist/src/memory/errors.js +58 -0
  62. package/dist/src/memory/memory-service.js +149 -0
  63. package/dist/src/memory/memory-store.js +2 -0
  64. package/dist/src/memory/types.js +2 -0
  65. package/dist/src/policy/capability-policy.js +29 -0
  66. package/dist/src/policy/delegation-policy.js +25 -0
  67. package/dist/src/policy/errors.js +10 -0
  68. package/dist/src/policy/invariants.js +31 -0
  69. package/dist/src/policy/memory-policy.js +22 -0
  70. package/dist/src/policy/policy-engine.js +85 -0
  71. package/dist/src/policy/types.js +8 -0
  72. package/dist/src/policy/workspace-policy.js +77 -0
  73. package/dist/src/principal/principal-profile-store.js +89 -0
  74. package/dist/src/runtime/adapter-files.js +147 -0
  75. package/dist/src/runtime/claude-adapter.js +177 -0
  76. package/dist/src/runtime/codex-adapter.js +169 -0
  77. package/dist/src/runtime/permission-rules.js +156 -0
  78. package/dist/src/runtime/render-session-context.js +124 -0
  79. package/dist/src/runtime/render-task-input.js +33 -0
  80. package/dist/src/runtime/runtime-adapter.js +2 -0
  81. package/dist/src/runtime/runtime-preference-store.js +66 -0
  82. package/dist/src/runtime/runtime-selector.js +178 -0
  83. package/dist/src/runtime/types.js +2 -0
  84. package/dist/src/runtime/windows-shim.js +57 -0
  85. package/dist/src/state-paths.js +49 -0
  86. package/dist/src/workflow/output-validator.js +27 -0
  87. package/dist/src/workflow/repair-policy.js +8 -0
  88. package/dist/src/workflow/types.js +22 -0
  89. package/dist/src/workflow/workflow-runner.js +81 -0
  90. package/hooks/compact-record.cjs +109 -0
  91. package/hooks/session-boot.cjs +112 -0
  92. package/hooks/session-end.cjs +34 -0
  93. package/package.json +48 -0
  94. package/scaffold/memory/INDEX.md +27 -0
  95. package/scaffold/memory/README.md +76 -0
  96. package/scaffold/memory/projects/INDEX.md +22 -0
  97. package/scaffold/memory/projects/PROTOCOL.md +128 -0
  98. package/scaffold/memory/projects/_template/PROJECT.md +45 -0
  99. package/scripts/alp.cjs +126 -0
  100. package/scripts/alp.ps1 +4 -0
  101. package/scripts/alp.sh +3 -0
  102. package/scripts/bootstrap.cjs +144 -0
  103. package/scripts/checkout-release.cjs +30 -0
  104. package/scripts/delegate.cjs +19 -0
  105. package/scripts/doctor.cjs +158 -0
  106. package/scripts/doctor.sh +3 -0
  107. package/scripts/ensure-state.cjs +22 -0
  108. package/scripts/lib/cli-link.cjs +375 -0
  109. package/scripts/lib/codex-role.cjs +18 -0
  110. package/scripts/lib/delegation/command-runner.cjs +108 -0
  111. package/scripts/lib/delegation/config.cjs +81 -0
  112. package/scripts/lib/install-paths.cjs +154 -0
  113. package/scripts/lib/release-manifest.cjs +42 -0
  114. package/scripts/lib/semver-lite.cjs +20 -0
  115. package/scripts/lib/state.cjs +274 -0
  116. package/scripts/lib/uninstall.cjs +252 -0
  117. package/scripts/lib/update-check-worker.cjs +21 -0
  118. package/scripts/lib/update.cjs +395 -0
  119. package/scripts/run-role.cjs +42 -0
  120. package/scripts/run-role.ps1 +4 -0
  121. package/scripts/run-role.sh +3 -0
  122. package/scripts/sync-project-index.sh +167 -0
  123. package/skills/agent-memory/SKILL.md +109 -0
  124. package/skills/alp-debug/SKILL.md +90 -0
  125. package/skills/alp-debug/references/defense-in-depth.md +118 -0
  126. package/skills/alp-debug/references/investigation-methodology.md +106 -0
  127. package/skills/alp-debug/references/log-and-ci-analysis.md +96 -0
  128. package/skills/alp-debug/references/performance-diagnostics.md +112 -0
  129. package/skills/alp-debug/references/reporting-standards.md +120 -0
  130. package/skills/alp-debug/references/root-cause-tracing.md +134 -0
  131. package/skills/alp-debug/references/systematic-debugging.md +93 -0
  132. package/skills/alp-debug/references/verification.md +86 -0
  133. package/skills/alp-debug/scripts/find-polluter.sh +63 -0
  134. package/skills/alp-debug/scripts/find-polluter.test.md +102 -0
  135. package/skills/alp-plan/SKILL.md +128 -0
  136. package/skills/alp-plan/references/archive-workflow.md +77 -0
  137. package/skills/alp-plan/references/codebase-understanding.md +55 -0
  138. package/skills/alp-plan/references/output-standards.md +96 -0
  139. package/skills/alp-plan/references/plan-organization.md +129 -0
  140. package/skills/alp-plan/references/red-team-personas.md +76 -0
  141. package/skills/alp-plan/references/red-team-workflow.md +81 -0
  142. package/skills/alp-plan/references/research-phase.md +57 -0
  143. package/skills/alp-plan/references/scope-challenge.md +82 -0
  144. package/skills/alp-plan/references/solution-design.md +76 -0
  145. package/skills/alp-plan/references/validate-question-framework.md +89 -0
  146. package/skills/alp-plan/references/validate-workflow.md +83 -0
  147. package/skills/alp-predict/SKILL.md +98 -0
  148. package/skills/alp-scenario/SKILL.md +86 -0
  149. package/skills/code-review/SKILL.md +111 -0
  150. package/skills/code-review/references/code-review-reception.md +114 -0
  151. package/skills/code-review/references/edge-case-scouting.md +78 -0
  152. package/skills/code-review/references/verification-before-completion.md +117 -0
  153. package/skills/delegation/SKILL.md +46 -0
  154. package/skills/docs-seeker/.env.example +15 -0
  155. package/skills/docs-seeker/SKILL.md +87 -0
  156. package/skills/docs-seeker/package.json +25 -0
  157. package/skills/docs-seeker/references/advanced.md +82 -0
  158. package/skills/docs-seeker/references/context7-patterns.md +68 -0
  159. package/skills/docs-seeker/references/errors.md +72 -0
  160. package/skills/docs-seeker/scripts/analyze-llms-txt.js +211 -0
  161. package/skills/docs-seeker/scripts/detect-topic.js +172 -0
  162. package/skills/docs-seeker/scripts/fetch-docs.js +213 -0
  163. package/skills/docs-seeker/scripts/tests/run-tests.js +72 -0
  164. package/skills/docs-seeker/scripts/tests/test-analyze-llms.js +119 -0
  165. package/skills/docs-seeker/scripts/tests/test-detect-topic.js +112 -0
  166. package/skills/docs-seeker/scripts/tests/test-fetch-docs.js +84 -0
  167. package/skills/docs-seeker/scripts/utils/env-loader.js +94 -0
  168. package/skills/docs-seeker/workflows/library-search.md +73 -0
  169. package/skills/docs-seeker/workflows/repo-analysis.md +90 -0
  170. package/skills/docs-seeker/workflows/topic-search.md +69 -0
  171. package/skills/git/SKILL.md +121 -0
  172. package/skills/git/references/branch-management.md +90 -0
  173. package/skills/git/references/commit-standards.md +82 -0
  174. package/skills/git/references/gh-cli-guide.md +132 -0
  175. package/skills/git/references/safety-protocols.md +86 -0
  176. package/skills/git/references/workflow-commit.md +89 -0
  177. package/skills/git/references/workflow-merge.md +63 -0
  178. package/skills/git/references/workflow-pr.md +70 -0
  179. package/skills/git/references/workflow-push.md +62 -0
  180. package/skills/gkg/SKILL.md +87 -0
  181. package/skills/gkg/references/cli-commands.md +92 -0
  182. package/skills/gkg/references/http-api.md +99 -0
  183. package/skills/gkg/references/language-support.md +54 -0
  184. package/skills/problem-solving/SKILL.md +86 -0
  185. package/skills/problem-solving/references/attribution.md +48 -0
  186. package/skills/problem-solving/references/collision-zone-thinking.md +71 -0
  187. package/skills/problem-solving/references/inversion-exercise.md +88 -0
  188. package/skills/problem-solving/references/meta-pattern-recognition.md +80 -0
  189. package/skills/problem-solving/references/scale-game.md +82 -0
  190. package/skills/problem-solving/references/simplification-cascades.md +83 -0
  191. package/skills/problem-solving/references/when-stuck.md +76 -0
  192. package/skills/repomix/SKILL.md +94 -0
  193. package/skills/repomix/references/configuration.md +134 -0
  194. package/skills/repomix/references/usage-patterns.md +106 -0
  195. package/skills/repomix/scripts/.coverage +0 -0
  196. package/skills/repomix/scripts/README.md +179 -0
  197. package/skills/repomix/scripts/repomix_batch.py +455 -0
  198. package/skills/repomix/scripts/repos.example.json +15 -0
  199. package/skills/repomix/scripts/requirements.txt +15 -0
  200. package/skills/repomix/scripts/tests/test_repomix_batch.py +531 -0
  201. package/skills/research/SKILL.md +107 -0
  202. package/skills/security-scan/SKILL.md +101 -0
  203. package/skills/security-scan/references/secret-patterns.md +75 -0
  204. package/skills/security-scan/references/vulnerability-patterns.md +136 -0
@@ -0,0 +1,154 @@
1
+ // install-paths.cjs — một chỗ duy nhất trả lời "alp-code để thứ gì ở đâu".
2
+ //
3
+ // VÌ SAO TỒN TẠI: từ v0.9.0 thư mục cài là một ARTIFACT DÙNG MỘT LẦN. `npm i -g alp-code`
4
+ // xoá sạch package dir cũ rồi giải nén bản mới; installer tarball giải nén vào
5
+ // `versions/<tag>` rồi trỏ lại symlink `current`. Bất cứ thứ gì của người dùng nằm trong đó
6
+ // đều biến mất ở lần update kế tiếp — nên KHÔNG có gì của người dùng được nằm trong đó nữa.
7
+ //
8
+ // Ranh giới:
9
+ // · thư mục cài — code đã build, hooks, skills, scaffold. Chỉ đọc, thay được bất cứ lúc nào.
10
+ // · `~/.alp` — memory, identity docs, execution/delegation state, preferences. Không ai
11
+ // thay hộ, không ai xoá hộ.
12
+ //
13
+ // Đây đúng là mô hình `npm i -g @anthropic-ai/claude-code` + `~/.claude` mà Claude Code và
14
+ // Codex CLI dùng, và là lý do cả hai bỏ được bước build ở máy người dùng.
15
+
16
+ "use strict";
17
+
18
+ const fs = require("fs");
19
+ const os = require("os");
20
+ const path = require("path");
21
+
22
+ /** Thư mục cài mặc định của channel tarball — cũng là chỗ các bản < v0.9.0 clone về. */
23
+ const LEGACY_INSTALL_HOME = ".alp-code";
24
+
25
+ function homeDir(env) {
26
+ const home = env.HOME || env.USERPROFILE || os.homedir();
27
+ if (!home) throw new Error("không xác định được HOME/USERPROFILE");
28
+ return home;
29
+ }
30
+
31
+ /** `~/.alp` — gốc của mọi state cục bộ. `ALP_STATE_HOME` để test và cài song song. */
32
+ function stateHome(env = process.env) {
33
+ return env.ALP_STATE_HOME ? path.resolve(env.ALP_STATE_HOME) : path.join(homeDir(env), ".alp");
34
+ }
35
+
36
+ /**
37
+ * Gốc memory. `ALP_MEMORY_ROOT` vẫn thắng — doctor, adapter và test đã dựa vào nó từ trước,
38
+ * và đó là đường duy nhất để chạy một ALP cô lập hoàn toàn.
39
+ */
40
+ function memoryRoot(env = process.env) {
41
+ return env.ALP_MEMORY_ROOT ? path.resolve(env.ALP_MEMORY_ROOT) : path.join(stateHome(env), "memory");
42
+ }
43
+
44
+ /** `~/.alp/agents/<role>.md` — cache identity phẳng cho SessionStart hook. */
45
+ function agentsDir(env = process.env) {
46
+ return path.join(stateHome(env), "agents");
47
+ }
48
+
49
+ /** `~/.alp/hooks/` — forwarder có đường dẫn ỔN ĐỊNH; xem `hookForwarderPath`. */
50
+ function hooksDir(env = process.env) {
51
+ return path.join(stateHome(env), "hooks");
52
+ }
53
+
54
+ function executionsDir(env = process.env) {
55
+ return path.join(stateHome(env), "executions");
56
+ }
57
+
58
+ /**
59
+ * Đường dẫn hook mà `alp init` ghi vào `<project>/.claude/settings.local.json`.
60
+ *
61
+ * KHÔNG BAO GIỜ trỏ thẳng vào thư mục cài. Cấu hình đó nằm trong project của người dùng và
62
+ * sống lâu hơn mọi bản cài: đổi channel (tarball → npm), gỡ rồi cài lại chỗ khác, hay chỉ là
63
+ * lên version với tarball `versions/<tag>` mới — tất cả đều làm một đường dẫn tuyệt đối tới
64
+ * thư mục cài chết câm, và người dùng chỉ thấy phiên `claude` mất identity mà không rõ vì sao.
65
+ */
66
+ function hookForwarderPath(name, env = process.env) {
67
+ return path.join(hooksDir(env), `${name}.cjs`);
68
+ }
69
+
70
+ /** Bản ghi bản cài hiện hành. Forwarder đọc file này để tìm thư mục cài. */
71
+ function installRecordPath(env = process.env) {
72
+ return path.join(stateHome(env), "install.json");
73
+ }
74
+
75
+ function readInstallRecord(env = process.env) {
76
+ try {
77
+ const parsed = JSON.parse(fs.readFileSync(installRecordPath(env), "utf8"));
78
+ return typeof parsed?.root === "string" ? parsed : null;
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ function writeInstallRecord(record, env = process.env) {
85
+ const file = installRecordPath(env);
86
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
87
+ const tmp = `${file}.${process.pid}.tmp`;
88
+ fs.writeFileSync(tmp, `${JSON.stringify(record, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
89
+ fs.renameSync(tmp, file);
90
+ return file;
91
+ }
92
+
93
+ /**
94
+ * Channel của một thư mục cài, suy ra từ CHÍNH nó chứ không từ marker ghi lúc đóng gói:
95
+ * cùng một cây file được `npm publish` và được nén vào tarball, nên nội dung không phân biệt
96
+ * được hai bản cài — chỉ vị trí mới phân biệt được.
97
+ *
98
+ * dev — có `.git` và `src/`: bản clone của người phát triển, vẫn build tại chỗ.
99
+ * npm — nằm dưới một `node_modules/`: npm sở hữu thư mục này.
100
+ * tarball — còn lại: artifact giải nén, thường là `~/.alp-code/versions/<tag>`.
101
+ */
102
+ function detectChannel(root) {
103
+ const resolved = path.resolve(root);
104
+ if (fs.existsSync(path.join(resolved, ".git")) && fs.existsSync(path.join(resolved, "src")))
105
+ return "dev";
106
+ if (resolved.split(path.sep).includes("node_modules")) return "npm";
107
+ return "tarball";
108
+ }
109
+
110
+ /** Gốc cài mặc định cho channel tarball: `~/.alp-code`, với `current` trỏ vào version đang dùng. */
111
+ function tarballHome(env = process.env) {
112
+ return env.ALP_HOME ? path.resolve(env.ALP_HOME) : path.join(homeDir(env), LEGACY_INSTALL_HOME);
113
+ }
114
+
115
+ function tarballCurrentLink(env = process.env) {
116
+ return path.join(tarballHome(env), "current");
117
+ }
118
+
119
+ function tarballVersionsDir(env = process.env) {
120
+ return path.join(tarballHome(env), "versions");
121
+ }
122
+
123
+ /**
124
+ * Những chỗ memory có thể còn nằm lại từ bản < v0.9.0, theo thứ tự ưu tiên di trú.
125
+ * `root` là thư mục cài hiện tại; bản cũ thì chính nó là repo clone chứa `memory/`.
126
+ */
127
+ function legacyMemoryRoots(root, env = process.env) {
128
+ const candidates = [
129
+ path.join(path.resolve(root), "memory"),
130
+ path.join(tarballHome(env), "memory"),
131
+ path.join(homeDir(env), LEGACY_INSTALL_HOME, "memory"),
132
+ ];
133
+ const previous = readInstallRecord(env);
134
+ if (previous?.root) candidates.push(path.join(previous.root, "memory"));
135
+ return [...new Set(candidates)];
136
+ }
137
+
138
+ module.exports = {
139
+ LEGACY_INSTALL_HOME,
140
+ stateHome,
141
+ memoryRoot,
142
+ agentsDir,
143
+ hooksDir,
144
+ executionsDir,
145
+ hookForwarderPath,
146
+ installRecordPath,
147
+ readInstallRecord,
148
+ writeInstallRecord,
149
+ detectChannel,
150
+ tarballHome,
151
+ tarballCurrentLink,
152
+ tarballVersionsDir,
153
+ legacyMemoryRoots,
154
+ };
@@ -0,0 +1,42 @@
1
+ // release-manifest.cjs — hợp đồng nội dung của artifact phát hành.
2
+ //
3
+ // `files` trong package.json chỉ là CÁCH thực hiện hợp đồng này, và nó im lặng khi sai: một
4
+ // dấu `!` đặt lệch chỗ cắt mất nguyên một thư mục mà `npm pack` vẫn chạy xanh. Danh sách ở đây
5
+ // là thứ được kiểm lại — bởi `pack-release.cjs` lúc dựng artifact, và bởi
6
+ // `test-pack-release.cjs` ở mỗi lần chạy test, để phát hiện trước khi tới ngày phát hành.
7
+
8
+ "use strict";
9
+
10
+ /** Không có đủ những file này thì bản cài không chạy được mà không build lại. */
11
+ const REQUIRED = [
12
+ "package.json",
13
+ "LICENSE",
14
+ "dist/src/cli/alp.js",
15
+ "dist/src/agents/registry.js",
16
+ "dist/src/runtime/claude-adapter.js",
17
+ "dist/src/runtime/codex-adapter.js",
18
+ "scripts/alp.cjs",
19
+ "scripts/doctor.cjs",
20
+ "scripts/ensure-state.cjs",
21
+ "scripts/lib/install-paths.cjs",
22
+ "scripts/lib/state.cjs",
23
+ "scripts/lib/update.cjs",
24
+ "scripts/lib/uninstall.cjs",
25
+ "hooks/session-boot.cjs",
26
+ "scaffold/memory/INDEX.md",
27
+ "alp.config.yaml",
28
+ ];
29
+
30
+ /** Nguồn, test và memory của maintainer không được đi theo artifact ra ngoài. */
31
+ const FORBIDDEN = [/^src\//, /^test\//, /^dist\/test\//, /^memory\//, /^\.git\//, /^scripts\/test-/];
32
+
33
+ /** @returns {{missing: string[], leaked: string[]}} */
34
+ function verifyEntries(entries) {
35
+ const present = new Set(entries);
36
+ return {
37
+ missing: REQUIRED.filter((file) => !present.has(file)),
38
+ leaked: entries.filter((entry) => FORBIDDEN.some((pattern) => pattern.test(entry))),
39
+ };
40
+ }
41
+
42
+ module.exports = { REQUIRED, FORBIDDEN, verifyEntries };
@@ -0,0 +1,20 @@
1
+ // Minimal vX.Y.Z / X.Y.Z parsing and comparison — no pre-release/build-metadata support,
2
+ // which is all alp-code's release tags need.
3
+
4
+ function parse(tag) {
5
+ const match = /^v?(\d+)\.(\d+)\.(\d+)$/.exec(String(tag).trim());
6
+ return match && { major: Number(match[1]), minor: Number(match[2]), patch: Number(match[3]) };
7
+ }
8
+
9
+ function compare(a, b) {
10
+ const parsedA = parse(a);
11
+ const parsedB = parse(b);
12
+ if (!parsedA || !parsedB) throw new Error(`invalid semver tag: ${JSON.stringify(!parsedA ? a : b)}`);
13
+ return parsedA.major - parsedB.major || parsedA.minor - parsedB.minor || parsedA.patch - parsedB.patch;
14
+ }
15
+
16
+ function isValid(tag) {
17
+ return parse(tag) !== null;
18
+ }
19
+
20
+ module.exports = { parse, compare, isValid };
@@ -0,0 +1,274 @@
1
+ // state.cjs — dựng và giữ `~/.alp`, phần dữ liệu KHÔNG đi theo thư mục cài.
2
+ //
3
+ // Chạy được ở mọi channel và chạy được nhiều lần: installer gọi nó sau khi giải nén, và
4
+ // `scripts/alp.cjs` gọi lại ở mỗi lần chạy lệnh. Đó là chủ ý — bản cài npm không có bước
5
+ // bootstrap nào bảo đảm sẽ chạy (`npm i -g --ignore-scripts` là chuyện bình thường), nên
6
+ // điều kiện tiên quyết phải tự dựng lấy ở lần dùng đầu tiên thay vì trông vào một bước cài.
7
+ //
8
+ // LUẬT DUY NHẤT: hàm ở đây chỉ biết TẠO cái còn thiếu và DI TRÚ cái nằm sai chỗ. Không hàm
9
+ // nào ghi đè nội dung đã có — memory mất là mất thật, remote không có bản sao để lấy lại.
10
+
11
+ "use strict";
12
+
13
+ const fs = require("fs");
14
+ const path = require("path");
15
+ const P = require("./install-paths.cjs");
16
+ const D = require("./delegation/config.cjs");
17
+
18
+ /**
19
+ * @param {{ root: string, env?: NodeJS.ProcessEnv, version?: string }} options
20
+ * @returns {{ stateHome: string, memoryRoot: string, channel: string, log: Array<{level: string, text: string}> }}
21
+ */
22
+ function ensureState(options) {
23
+ const root = path.resolve(options.root);
24
+ const env = options.env || process.env;
25
+ const log = [];
26
+ const say = (level, text) => log.push({ level, text });
27
+
28
+ const stateHome = P.stateHome(env);
29
+ mkdirPrivate(stateHome);
30
+
31
+ const channel = P.detectChannel(root);
32
+ const memoryRoot = ensureMemory(root, env, say);
33
+ ensureExecutions(env, say);
34
+ ensureDelegation(root, env, channel, say);
35
+ mkdirPrivate(P.agentsDir(env));
36
+
37
+ P.writeInstallRecord(
38
+ { root, channel, version: options.version ?? readVersion(root), updatedAt: new Date().toISOString() },
39
+ env,
40
+ );
41
+ say("OK", `install record ${P.installRecordPath(env)} → ${channel} ${root}`);
42
+
43
+ writeHookForwarders(root, env, say);
44
+ repairProjectHooks(env, say);
45
+
46
+ return { stateHome, memoryRoot, channel, log };
47
+ }
48
+
49
+ // ---------------------------------------------------------------------- memory
50
+
51
+ /**
52
+ * `~/.alp/memory`, di trú từ chỗ cũ nếu cần rồi bù những file khung còn thiếu.
53
+ *
54
+ * Trước v0.9.0 memory nằm trong `<thư mục cài>/memory`. Chỗ đó giờ là artifact bị thay
55
+ * nguyên khối mỗi lần update, nên để nguyên là hẹn ngày mất dữ liệu — di trú là bắt buộc,
56
+ * không phải dọn dẹp cho gọn.
57
+ */
58
+ function ensureMemory(root, env, say) {
59
+ const target = P.memoryRoot(env);
60
+
61
+ // "Chưa có memory ở chỗ mới" không phải là "thư mục chưa tồn tại": `alp doctor` và
62
+ // MarkdownFileStore đều tạo sẵn thư mục và ghi vào đó một file index ẩn chỉ để kiểm tra
63
+ // quyền ghi. Nếu coi đó là đã có dữ liệu thì di trú không bao giờ chạy, và người dùng lên
64
+ // v0.9.0 xong mở ra thấy memory rỗng trong khi bản cũ vẫn nằm nguyên chỗ cũ.
65
+ if (isEmptyish(target)) {
66
+ const legacy = P.legacyMemoryRoots(root, env).find(
67
+ (candidate) => candidate !== target && isDirectory(candidate) && !isEmptyish(candidate),
68
+ );
69
+ if (legacy) {
70
+ fs.rmSync(target, { recursive: true, force: true });
71
+ movePath(legacy, target);
72
+ leaveBreadcrumb(legacy, target);
73
+ say("MOVED", `memory ${legacy} → ${target}`);
74
+ }
75
+ }
76
+
77
+ mkdirPrivate(target);
78
+ const seed = path.join(root, "scaffold", "memory");
79
+ if (!fs.existsSync(seed)) throw new Error(`thiếu ${seed} — bản cài hỏng, không dựng lại memory được`);
80
+
81
+ const made = [];
82
+ copyMissing(seed, target, target, made);
83
+ for (const directory of [
84
+ path.join(target, "shared", "decisions"),
85
+ path.join(target, "shared", "people"),
86
+ path.join(target, "shared", "reference"),
87
+ path.join(target, "private"),
88
+ ]) {
89
+ if (fs.existsSync(directory)) continue;
90
+ fs.mkdirSync(directory, { recursive: true });
91
+ made.push(`${path.relative(target, directory)}/`);
92
+ }
93
+
94
+ if (made.length) for (const entry of made) say("WROTE", path.join(target, entry));
95
+ else say("OK", `memory ${target} đã đủ khung — không đụng vào nội dung`);
96
+ return target;
97
+ }
98
+
99
+ /**
100
+ * Một dòng để lại chỗ cũ. Với dev clone, `memory/` biến mất khỏi repo là chuyện đủ lạ để
101
+ * người dùng đi tìm; với bản cài thì thư mục này sắp bị xoá và dòng chữ vô hại.
102
+ */
103
+ function leaveBreadcrumb(from, to) {
104
+ try {
105
+ fs.writeFileSync(
106
+ `${from}.moved-to.txt`,
107
+ `memory/ đã chuyển sang ${to} (alp-code >= v0.9.0).\nThư mục cài giờ là artifact thay được, không giữ dữ liệu người dùng.\n`,
108
+ "utf8",
109
+ );
110
+ } catch { /* breadcrumb là tiện ích, không phải điều kiện thành công */ }
111
+ }
112
+
113
+ // ------------------------------------------------------------------ state khác
114
+
115
+ function ensureExecutions(env, say) {
116
+ const root = P.executionsDir(env);
117
+ mkdirPrivate(root);
118
+ say("OK", `execution state ${root}`);
119
+ }
120
+
121
+ /**
122
+ * State delegation. Mặc định cũ băm theo đường dẫn thư mục cài — hợp lý khi thư mục cài là
123
+ * một clone cố định, vô nghĩa khi nó là artifact có thể nằm ở `versions/<tag>` khác nhau mỗi
124
+ * bản. Bản cài dùng một thư mục chung; dev clone giữ nguyên khoá băm cũ để không mất state.
125
+ */
126
+ function ensureDelegation(root, env, channel, say) {
127
+ let config;
128
+ try { config = D.loadDelegationConfig(root, env); }
129
+ catch (error) { throw new Error(`delegation config không hợp lệ: ${error.message}`); }
130
+
131
+ if (channel !== "dev" && !fs.existsSync(config.stateDir)) {
132
+ const legacy = D.legacyStateDir(root, env);
133
+ if (legacy !== config.stateDir && isDirectory(legacy)) {
134
+ movePath(legacy, config.stateDir);
135
+ say("MOVED", `delegation state ${legacy} → ${config.stateDir}`);
136
+ }
137
+ }
138
+
139
+ mkdirPrivate(config.stateDir);
140
+ say("OK", `delegation state ${config.stateDir}`);
141
+ }
142
+
143
+ // --------------------------------------------------------------- hook forwarder
144
+
145
+ /**
146
+ * Sinh `~/.alp/hooks/<tên>.cjs` cho mọi hook trong thư mục cài.
147
+ *
148
+ * Forwarder tồn tại vì `<project>/.claude/settings.local.json` do `alp init` ghi phải trỏ tới
149
+ * một đường dẫn KHÔNG đổi. Nó nằm trong repo của người dùng, sống lâu hơn mọi bản cài, và
150
+ * không có bước nào đi sửa lại nó khi ALP lên version hay đổi channel.
151
+ *
152
+ * Ghi đè vô điều kiện: đây là file do ALP sinh, không phải dữ liệu người dùng, và nội dung
153
+ * phải luôn khớp bản cài hiện hành.
154
+ */
155
+ function writeHookForwarders(root, env, say) {
156
+ const source = path.join(root, "hooks");
157
+ if (!isDirectory(source)) throw new Error(`thiếu ${source} — bản cài hỏng`);
158
+ const directory = P.hooksDir(env);
159
+ mkdirPrivate(directory);
160
+
161
+ const names = fs.readdirSync(source).filter((name) => name.endsWith(".cjs"));
162
+ for (const name of names) {
163
+ fs.writeFileSync(path.join(directory, name), forwarderSource(name), { encoding: "utf8", mode: 0o700 });
164
+ }
165
+ say("OK", `hook forwarder ${directory} (${names.join(", ")})`);
166
+ }
167
+
168
+ function forwarderSource(name) {
169
+ return `#!/usr/bin/env node
170
+ // Sinh bởi alp-code — ĐỪNG SỬA TAY. Đường dẫn ổn định để cấu hình hook trong project của bạn
171
+ // sống sót qua đổi version và đổi channel cài; nó chỉ đọc bản cài hiện hành rồi gọi hook thật.
172
+ "use strict";
173
+ const fs = require("node:fs");
174
+ const path = require("node:path");
175
+
176
+ const record = path.join(__dirname, "..", "install.json");
177
+ let root;
178
+ try {
179
+ root = JSON.parse(fs.readFileSync(record, "utf8")).root;
180
+ } catch (error) {
181
+ process.stderr.write(\`alp-code: không đọc được \${record}: \${error.message}\\n\`);
182
+ process.exit(0);
183
+ }
184
+
185
+ if (!process.env.ALP_REPO_ROOT) process.env.ALP_REPO_ROOT = root;
186
+ require(path.join(root, "hooks", ${JSON.stringify(name)}));
187
+ `;
188
+ }
189
+
190
+ /**
191
+ * Sửa lại hook path trong những project đã `alp init` trước v0.9.0.
192
+ *
193
+ * Chúng đang trỏ thẳng vào thư mục cài cũ. Nếu bản cài đó bị npm thay hoặc bị gỡ, phiên
194
+ * `claude` mở tay trong project sẽ mất identity mà không báo gì rõ ràng — hỏng câm, đúng
195
+ * kiểu repo này tránh. Chỉ đụng vào file mang marker `alp init`; file của người dùng thì thôi.
196
+ */
197
+ function repairProjectHooks(env, say) {
198
+ const forwarder = P.hookForwarderPath("session-boot", env);
199
+ const registry = path.join(P.stateHome(env), "projects.json");
200
+ let projects;
201
+ try { projects = JSON.parse(fs.readFileSync(registry, "utf8")).projects || []; }
202
+ catch { return; }
203
+
204
+ const repaired = [];
205
+ for (const entry of projects) {
206
+ const file = path.join(String(entry.path || ""), ".claude", "settings.local.json");
207
+ let parsed;
208
+ try { parsed = JSON.parse(fs.readFileSync(file, "utf8")); }
209
+ catch { continue; }
210
+ if (parsed.$generatedBy !== "alp init") continue;
211
+
212
+ const hooks = parsed.hooks?.SessionStart?.[0]?.hooks?.[0];
213
+ if (!hooks || typeof hooks.command !== "string") continue;
214
+ const command = `${JSON.stringify(process.execPath)} ${JSON.stringify(forwarder)}`;
215
+ if (hooks.command === command) continue;
216
+
217
+ hooks.command = command;
218
+ try {
219
+ fs.writeFileSync(file, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
220
+ repaired.push(file);
221
+ } catch { /* project chỉ đọc hoặc đã bị xoá — không đáng làm hỏng cả bước cài */ }
222
+ }
223
+ if (repaired.length) say("FIXED", `hook path trong ${repaired.length} project → ${forwarder}`);
224
+ }
225
+
226
+ // ------------------------------------------------------------------- tiện ích
227
+
228
+ function mkdirPrivate(directory) {
229
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
230
+ if (process.platform !== "win32") { try { fs.chmodSync(directory, 0o700); } catch { /* không sở hữu */ } }
231
+ }
232
+
233
+ function isDirectory(candidate) {
234
+ try { return fs.statSync(candidate).isDirectory(); } catch { return false; }
235
+ }
236
+
237
+ /** Không có gì ngoài file ẩn (index cache dựng lại được) — tức là chưa có dữ liệu thật. */
238
+ function isEmptyish(directory) {
239
+ try { return !fs.readdirSync(directory).some((entry) => !entry.startsWith(".")); }
240
+ catch { return true; }
241
+ }
242
+
243
+ /** rename khi cùng volume, cp+rm khi khác volume (EXDEV) — `~` và thư mục cài không chắc cùng đĩa. */
244
+ function movePath(from, to) {
245
+ fs.mkdirSync(path.dirname(to), { recursive: true });
246
+ try {
247
+ fs.renameSync(from, to);
248
+ return;
249
+ } catch (error) {
250
+ if (error.code !== "EXDEV") throw error;
251
+ }
252
+ fs.cpSync(from, to, { recursive: true });
253
+ fs.rmSync(from, { recursive: true, force: true });
254
+ }
255
+
256
+ function copyMissing(sourceDir, destinationDir, base, made) {
257
+ fs.mkdirSync(destinationDir, { recursive: true });
258
+ for (const entry of fs.readdirSync(sourceDir, { withFileTypes: true })) {
259
+ const source = path.join(sourceDir, entry.name);
260
+ const destination = path.join(destinationDir, entry.name);
261
+ if (entry.isDirectory()) copyMissing(source, destination, base, made);
262
+ else if (!fs.existsSync(destination)) {
263
+ fs.copyFileSync(source, destination);
264
+ made.push(path.relative(base, destination));
265
+ }
266
+ }
267
+ }
268
+
269
+ function readVersion(root) {
270
+ try { return JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")).version || null; }
271
+ catch { return null; }
272
+ }
273
+
274
+ module.exports = { ensureState, ensureMemory, writeHookForwarders, repairProjectHooks, movePath };