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.
- package/CHANGELOG.md +770 -0
- package/LICENSE +21 -0
- package/README.md +295 -0
- package/alp.config.yaml +5 -0
- package/dist/src/agents/agent-definition.js +28 -0
- package/dist/src/agents/capability-catalog.js +33 -0
- package/dist/src/agents/compaction.js +36 -0
- package/dist/src/agents/errors.js +12 -0
- package/dist/src/agents/librarian.js +38 -0
- package/dist/src/agents/main.js +37 -0
- package/dist/src/agents/memory-grant.js +29 -0
- package/dist/src/agents/model-context.js +70 -0
- package/dist/src/agents/modes.js +134 -0
- package/dist/src/agents/oracle.js +36 -0
- package/dist/src/agents/read-thread.js +38 -0
- package/dist/src/agents/registry.js +238 -0
- package/dist/src/agents/render-identity.js +38 -0
- package/dist/src/agents/review.js +37 -0
- package/dist/src/agents/search.js +37 -0
- package/dist/src/agents/shared/house-rules.js +33 -0
- package/dist/src/agents/shared/principal.js +18 -0
- package/dist/src/agents/shared/voice.js +29 -0
- package/dist/src/agents/titling.js +32 -0
- package/dist/src/agents/types.js +15 -0
- package/dist/src/backend/execution-backend.js +2 -0
- package/dist/src/backend/local-execution-store.js +144 -0
- package/dist/src/backend/local-process-backend.js +533 -0
- package/dist/src/backend/local-supervisor.js +104 -0
- package/dist/src/cli/alp.js +380 -0
- package/dist/src/cli/commands/context.js +203 -0
- package/dist/src/cli/commands/delegate.js +136 -0
- package/dist/src/cli/commands/identity-sync.js +31 -0
- package/dist/src/cli/commands/init.js +184 -0
- package/dist/src/cli/commands/mode.js +22 -0
- package/dist/src/cli/commands/principal.js +114 -0
- package/dist/src/cli/commands/run-main.js +90 -0
- package/dist/src/cli/commands/runtime.js +21 -0
- package/dist/src/cli/mode-preference-store.js +62 -0
- package/dist/src/cli/mode-selector.js +178 -0
- package/dist/src/cli/update-check.js +77 -0
- package/dist/src/context/checkpoint.js +134 -0
- package/dist/src/context/compact-journal.js +153 -0
- package/dist/src/context/compact-payload.js +121 -0
- package/dist/src/context/continuity.js +70 -0
- package/dist/src/context/types.js +2 -0
- package/dist/src/delegation/backend-registry.js +40 -0
- package/dist/src/delegation/delegation-service.js +300 -0
- package/dist/src/delegation/types.js +12 -0
- package/dist/src/execution/execution-policy.js +96 -0
- package/dist/src/execution/execution-service.js +115 -0
- package/dist/src/execution/execution-store.js +78 -0
- package/dist/src/execution/identity-capsule.js +65 -0
- package/dist/src/execution/types.js +12 -0
- package/dist/src/hooks/execution-bridge.js +84 -0
- package/dist/src/index.js +4 -0
- package/dist/src/memory/adapters/markdown-file-store.js +257 -0
- package/dist/src/memory/adapters/memory-api-client.js +2 -0
- package/dist/src/memory/adapters/memory-path-mapper.js +76 -0
- package/dist/src/memory/adapters/remote-api-store.js +25 -0
- package/dist/src/memory/context-ranker.js +21 -0
- package/dist/src/memory/errors.js +58 -0
- package/dist/src/memory/memory-service.js +149 -0
- package/dist/src/memory/memory-store.js +2 -0
- package/dist/src/memory/types.js +2 -0
- package/dist/src/policy/capability-policy.js +29 -0
- package/dist/src/policy/delegation-policy.js +25 -0
- package/dist/src/policy/errors.js +10 -0
- package/dist/src/policy/invariants.js +31 -0
- package/dist/src/policy/memory-policy.js +22 -0
- package/dist/src/policy/policy-engine.js +85 -0
- package/dist/src/policy/types.js +8 -0
- package/dist/src/policy/workspace-policy.js +77 -0
- package/dist/src/principal/principal-profile-store.js +89 -0
- package/dist/src/runtime/adapter-files.js +147 -0
- package/dist/src/runtime/claude-adapter.js +177 -0
- package/dist/src/runtime/codex-adapter.js +169 -0
- package/dist/src/runtime/permission-rules.js +156 -0
- package/dist/src/runtime/render-session-context.js +124 -0
- package/dist/src/runtime/render-task-input.js +33 -0
- package/dist/src/runtime/runtime-adapter.js +2 -0
- package/dist/src/runtime/runtime-preference-store.js +66 -0
- package/dist/src/runtime/runtime-selector.js +178 -0
- package/dist/src/runtime/types.js +2 -0
- package/dist/src/runtime/windows-shim.js +57 -0
- package/dist/src/state-paths.js +49 -0
- package/dist/src/workflow/output-validator.js +27 -0
- package/dist/src/workflow/repair-policy.js +8 -0
- package/dist/src/workflow/types.js +22 -0
- package/dist/src/workflow/workflow-runner.js +81 -0
- package/hooks/compact-record.cjs +109 -0
- package/hooks/session-boot.cjs +112 -0
- package/hooks/session-end.cjs +34 -0
- package/package.json +48 -0
- package/scaffold/memory/INDEX.md +27 -0
- package/scaffold/memory/README.md +76 -0
- package/scaffold/memory/projects/INDEX.md +22 -0
- package/scaffold/memory/projects/PROTOCOL.md +128 -0
- package/scaffold/memory/projects/_template/PROJECT.md +45 -0
- package/scripts/alp.cjs +126 -0
- package/scripts/alp.ps1 +4 -0
- package/scripts/alp.sh +3 -0
- package/scripts/bootstrap.cjs +144 -0
- package/scripts/checkout-release.cjs +30 -0
- package/scripts/delegate.cjs +19 -0
- package/scripts/doctor.cjs +158 -0
- package/scripts/doctor.sh +3 -0
- package/scripts/ensure-state.cjs +22 -0
- package/scripts/lib/cli-link.cjs +375 -0
- package/scripts/lib/codex-role.cjs +18 -0
- package/scripts/lib/delegation/command-runner.cjs +108 -0
- package/scripts/lib/delegation/config.cjs +81 -0
- package/scripts/lib/install-paths.cjs +154 -0
- package/scripts/lib/release-manifest.cjs +42 -0
- package/scripts/lib/semver-lite.cjs +20 -0
- package/scripts/lib/state.cjs +274 -0
- package/scripts/lib/uninstall.cjs +252 -0
- package/scripts/lib/update-check-worker.cjs +21 -0
- package/scripts/lib/update.cjs +395 -0
- package/scripts/run-role.cjs +42 -0
- package/scripts/run-role.ps1 +4 -0
- package/scripts/run-role.sh +3 -0
- package/scripts/sync-project-index.sh +167 -0
- package/skills/agent-memory/SKILL.md +109 -0
- package/skills/alp-debug/SKILL.md +90 -0
- package/skills/alp-debug/references/defense-in-depth.md +118 -0
- package/skills/alp-debug/references/investigation-methodology.md +106 -0
- package/skills/alp-debug/references/log-and-ci-analysis.md +96 -0
- package/skills/alp-debug/references/performance-diagnostics.md +112 -0
- package/skills/alp-debug/references/reporting-standards.md +120 -0
- package/skills/alp-debug/references/root-cause-tracing.md +134 -0
- package/skills/alp-debug/references/systematic-debugging.md +93 -0
- package/skills/alp-debug/references/verification.md +86 -0
- package/skills/alp-debug/scripts/find-polluter.sh +63 -0
- package/skills/alp-debug/scripts/find-polluter.test.md +102 -0
- package/skills/alp-plan/SKILL.md +128 -0
- package/skills/alp-plan/references/archive-workflow.md +77 -0
- package/skills/alp-plan/references/codebase-understanding.md +55 -0
- package/skills/alp-plan/references/output-standards.md +96 -0
- package/skills/alp-plan/references/plan-organization.md +129 -0
- package/skills/alp-plan/references/red-team-personas.md +76 -0
- package/skills/alp-plan/references/red-team-workflow.md +81 -0
- package/skills/alp-plan/references/research-phase.md +57 -0
- package/skills/alp-plan/references/scope-challenge.md +82 -0
- package/skills/alp-plan/references/solution-design.md +76 -0
- package/skills/alp-plan/references/validate-question-framework.md +89 -0
- package/skills/alp-plan/references/validate-workflow.md +83 -0
- package/skills/alp-predict/SKILL.md +98 -0
- package/skills/alp-scenario/SKILL.md +86 -0
- package/skills/code-review/SKILL.md +111 -0
- package/skills/code-review/references/code-review-reception.md +114 -0
- package/skills/code-review/references/edge-case-scouting.md +78 -0
- package/skills/code-review/references/verification-before-completion.md +117 -0
- package/skills/delegation/SKILL.md +46 -0
- package/skills/docs-seeker/.env.example +15 -0
- package/skills/docs-seeker/SKILL.md +87 -0
- package/skills/docs-seeker/package.json +25 -0
- package/skills/docs-seeker/references/advanced.md +82 -0
- package/skills/docs-seeker/references/context7-patterns.md +68 -0
- package/skills/docs-seeker/references/errors.md +72 -0
- package/skills/docs-seeker/scripts/analyze-llms-txt.js +211 -0
- package/skills/docs-seeker/scripts/detect-topic.js +172 -0
- package/skills/docs-seeker/scripts/fetch-docs.js +213 -0
- package/skills/docs-seeker/scripts/tests/run-tests.js +72 -0
- package/skills/docs-seeker/scripts/tests/test-analyze-llms.js +119 -0
- package/skills/docs-seeker/scripts/tests/test-detect-topic.js +112 -0
- package/skills/docs-seeker/scripts/tests/test-fetch-docs.js +84 -0
- package/skills/docs-seeker/scripts/utils/env-loader.js +94 -0
- package/skills/docs-seeker/workflows/library-search.md +73 -0
- package/skills/docs-seeker/workflows/repo-analysis.md +90 -0
- package/skills/docs-seeker/workflows/topic-search.md +69 -0
- package/skills/git/SKILL.md +121 -0
- package/skills/git/references/branch-management.md +90 -0
- package/skills/git/references/commit-standards.md +82 -0
- package/skills/git/references/gh-cli-guide.md +132 -0
- package/skills/git/references/safety-protocols.md +86 -0
- package/skills/git/references/workflow-commit.md +89 -0
- package/skills/git/references/workflow-merge.md +63 -0
- package/skills/git/references/workflow-pr.md +70 -0
- package/skills/git/references/workflow-push.md +62 -0
- package/skills/gkg/SKILL.md +87 -0
- package/skills/gkg/references/cli-commands.md +92 -0
- package/skills/gkg/references/http-api.md +99 -0
- package/skills/gkg/references/language-support.md +54 -0
- package/skills/problem-solving/SKILL.md +86 -0
- package/skills/problem-solving/references/attribution.md +48 -0
- package/skills/problem-solving/references/collision-zone-thinking.md +71 -0
- package/skills/problem-solving/references/inversion-exercise.md +88 -0
- package/skills/problem-solving/references/meta-pattern-recognition.md +80 -0
- package/skills/problem-solving/references/scale-game.md +82 -0
- package/skills/problem-solving/references/simplification-cascades.md +83 -0
- package/skills/problem-solving/references/when-stuck.md +76 -0
- package/skills/repomix/SKILL.md +94 -0
- package/skills/repomix/references/configuration.md +134 -0
- package/skills/repomix/references/usage-patterns.md +106 -0
- package/skills/repomix/scripts/.coverage +0 -0
- package/skills/repomix/scripts/README.md +179 -0
- package/skills/repomix/scripts/repomix_batch.py +455 -0
- package/skills/repomix/scripts/repos.example.json +15 -0
- package/skills/repomix/scripts/requirements.txt +15 -0
- package/skills/repomix/scripts/tests/test_repomix_batch.py +531 -0
- package/skills/research/SKILL.md +107 -0
- package/skills/security-scan/SKILL.md +101 -0
- package/skills/security-scan/references/secret-patterns.md +75 -0
- package/skills/security-scan/references/vulnerability-patterns.md +136 -0
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
// cli-link.cjs — cài lệnh `alp` và đưa nó vào PATH cho terminal mở sau installer.
|
|
2
|
+
//
|
|
3
|
+
// MỤC TIÊU: chạy xong installer là gõ `alp` được, không phải đọc hướng dẫn thêm.
|
|
4
|
+
// Muốn vậy phải làm ĐỦ HAI VIỆC, thiếu một là hỏng:
|
|
5
|
+
// 1. có một file thực thi tên `alp` ở đâu đó
|
|
6
|
+
// 2. thư mục chứa nó nằm trong PATH của shell mà người dùng SẼ mở lần sau
|
|
7
|
+
//
|
|
8
|
+
// Trước đây chỉ làm việc (1) rồi in một câu khuyên cho việc (2). Trên macOS zsh mặc
|
|
9
|
+
// định KHÔNG có `~/.local/bin` trong PATH, nên "cài xong" thực tế là "cài xong một nửa".
|
|
10
|
+
//
|
|
11
|
+
// GHI VÀO SHELL PROFILE LÀ VIỆC PHẢI XIN PHÉP — và ở đây principal đã yêu cầu thẳng.
|
|
12
|
+
// Vẫn giữ ba phanh:
|
|
13
|
+
// · chỉ APPEND, không bao giờ sửa/xoá dòng có sẵn
|
|
14
|
+
// · bọc trong marker để chạy lại không nhân bản, và để gỡ ra được bằng tay
|
|
15
|
+
// · `--no-path` (hoặc ALP_NO_PATH=1) tắt hẳn cho CI và cho người tự quản PATH
|
|
16
|
+
//
|
|
17
|
+
// Windows KHÔNG dùng symlink: `fs.symlinkSync` ở đó cần quyền admin hoặc Developer Mode.
|
|
18
|
+
// Thay bằng shim `.cmd` — không cần quyền gì — và ghi PATH ở tầng User qua PowerShell.
|
|
19
|
+
//
|
|
20
|
+
// ⚠️ Nhánh Windows CHƯA CHẠY THẬT trên máy Windows nào. Logic dựng path/nội dung shim có
|
|
21
|
+
// test (`test-cli-link.cjs`), nhưng việc `setx`-tương-đương có ăn hay không thì chưa đo.
|
|
22
|
+
|
|
23
|
+
const fs = require("fs");
|
|
24
|
+
const path = require("path");
|
|
25
|
+
const os = require("os");
|
|
26
|
+
const { spawnSync } = require("child_process");
|
|
27
|
+
|
|
28
|
+
const BEGIN = "# >>> alp-code >>>";
|
|
29
|
+
const END = "# <<< alp-code <<<";
|
|
30
|
+
|
|
31
|
+
/** Thư mục chứa lệnh `alp`, theo từng nền tảng. */
|
|
32
|
+
function binDir(env, platform) {
|
|
33
|
+
const home = env.HOME || env.USERPROFILE || os.homedir();
|
|
34
|
+
if (platform === "win32")
|
|
35
|
+
return path.join(env.LOCALAPPDATA || path.join(home, "AppData", "Local"), "alp", "bin");
|
|
36
|
+
return path.join(home, ".local", "bin");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Nội dung shim `.cmd` cho Windows.
|
|
41
|
+
*
|
|
42
|
+
* `%*` giữ nguyên tham số; dấu ngoặc kép quanh đường dẫn để chịu được `C:\Program Files`.
|
|
43
|
+
* CRLF vì `cmd.exe` không chạy nổi file chỉ có LF.
|
|
44
|
+
*/
|
|
45
|
+
function cmdShim(cliPath) {
|
|
46
|
+
return ["@rem alp-code shim", "@echo off", `node "${cliPath}" %*`, ""].join("\r\n");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* File profile của shell hiện tại. Trả `null` khi không đoán được — thà im lặng còn hơn
|
|
51
|
+
* ghi nhầm vào file của shell khác.
|
|
52
|
+
*/
|
|
53
|
+
function profileFor(env, platform) {
|
|
54
|
+
const home = env.HOME || os.homedir();
|
|
55
|
+
const shell = path.basename(env.SHELL || "");
|
|
56
|
+
if (shell === "zsh") return { file: path.join(home, ".zshrc"), syntax: "posix" };
|
|
57
|
+
if (shell === "fish")
|
|
58
|
+
return { file: path.join(home, ".config", "fish", "config.fish"), syntax: "fish" };
|
|
59
|
+
if (shell === "bash")
|
|
60
|
+
// macOS: Terminal mở bash ở chế độ login ⇒ đọc .bash_profile, KHÔNG đọc .bashrc.
|
|
61
|
+
// Linux: terminal thường là non-login ⇒ đọc .bashrc. Chọn sai file = ghi xong vẫn
|
|
62
|
+
// không có PATH, và đó đúng là kiểu hỏng câm mà cả repo này đang tránh.
|
|
63
|
+
return {
|
|
64
|
+
file: path.join(home, platform === "darwin" ? ".bash_profile" : ".bashrc"),
|
|
65
|
+
syntax: "posix",
|
|
66
|
+
};
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Khối PATH sẽ append, có marker hai đầu. */
|
|
71
|
+
function pathBlock(dir, syntax) {
|
|
72
|
+
const line =
|
|
73
|
+
syntax === "fish"
|
|
74
|
+
? `fish_add_path ${dir}`
|
|
75
|
+
: `export PATH="${dir}:$PATH"`;
|
|
76
|
+
return `\n${BEGIN}\n${line}\n${END}\n`;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Thư mục `dir` đã nằm trong PATH của tiến trình hiện tại chưa. */
|
|
80
|
+
function onPath(dir, env, platform) {
|
|
81
|
+
// Khi giả lập nhánh Windows trên macOS/Linux, `path.delimiter` vẫn là `:`. Chọn theo
|
|
82
|
+
// platform được tiêm vào để test đúng dữ liệu PATH thật của Windows (`;`).
|
|
83
|
+
const delimiter = platform === "win32" ? ";" : path.delimiter;
|
|
84
|
+
const parts = (env.PATH || env.Path || "").split(delimiter).filter(Boolean);
|
|
85
|
+
const same = (a, b) =>
|
|
86
|
+
platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
87
|
+
return parts.some((p) => same(path.resolve(p), path.resolve(dir)));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Cài lệnh `alp`.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} repoRoot gốc repo alp-code
|
|
94
|
+
* @param {object} opts
|
|
95
|
+
* · env, platform — tiêm vào để test được nhánh Windows từ máy khác
|
|
96
|
+
* · skipPath — chỉ tạo file thực thi, không đụng PATH
|
|
97
|
+
* · applyWindowsPath(dir) — cho test chặn lời gọi PowerShell thật
|
|
98
|
+
* @returns {Array<{level:string, text:string}>} nhật ký để caller in ra
|
|
99
|
+
*/
|
|
100
|
+
function installCli(repoRoot, opts = {}) {
|
|
101
|
+
const env = opts.env || process.env;
|
|
102
|
+
const platform = opts.platform || process.platform;
|
|
103
|
+
const skipPath = !!opts.skipPath;
|
|
104
|
+
const log = [];
|
|
105
|
+
const say = (level, text) => log.push({ level, text });
|
|
106
|
+
|
|
107
|
+
// Stable bootstrap only; parsing and policy remain in dist/src/cli/alp.js.
|
|
108
|
+
const cli = path.join(repoRoot, "scripts", "alp.cjs");
|
|
109
|
+
const dir = binDir(env, platform);
|
|
110
|
+
const target = path.join(dir, platform === "win32" ? "alp.cmd" : "alp");
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
114
|
+
if (platform === "win32") writeShim(target, cli, say);
|
|
115
|
+
else linkUnix(target, cli, say);
|
|
116
|
+
} catch (e) {
|
|
117
|
+
say("SKIP", `không tạo được ${target} (${e.message}) — chạy trực tiếp: node ${cli}`);
|
|
118
|
+
return log;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (onPath(dir, env, platform)) {
|
|
122
|
+
say("OK", `${dir} đã có trong PATH — gõ \`alp\` là chạy`);
|
|
123
|
+
return log;
|
|
124
|
+
}
|
|
125
|
+
if (skipPath) {
|
|
126
|
+
say("PATH", `${dir} chưa trong PATH; bỏ qua theo --no-path — tự thêm rồi mở terminal mới`);
|
|
127
|
+
return log;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (platform === "win32") addWindowsPath(dir, env, opts, say);
|
|
131
|
+
else addPosixPath(dir, env, platform, say);
|
|
132
|
+
return log;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Gỡ lệnh `alp` và phần PATH do installCli tạo.
|
|
137
|
+
*
|
|
138
|
+
* Chỉ xoá shim/link nếu nó còn trỏ đúng repo này. Một file `alp` của người dùng hoặc của
|
|
139
|
+
* clone alp-code khác là FOREIGN — giữ nguyên và không rút PATH chuyên dụng trên Windows.
|
|
140
|
+
*/
|
|
141
|
+
function uninstallCli(repoRoot, opts = {}) {
|
|
142
|
+
const env = opts.env || process.env;
|
|
143
|
+
const platform = opts.platform || process.platform;
|
|
144
|
+
const log = [];
|
|
145
|
+
const say = (level, text) => log.push({ level, text });
|
|
146
|
+
const cli = path.join(repoRoot, "scripts", "alp.cjs");
|
|
147
|
+
const dir = binDir(env, platform);
|
|
148
|
+
const target = path.join(dir, platform === "win32" ? "alp.cmd" : "alp");
|
|
149
|
+
let foreign = false;
|
|
150
|
+
|
|
151
|
+
if (fs.existsSync(target) || isBrokenLink(target)) {
|
|
152
|
+
if (ownsCommand(target, cli, platform)) {
|
|
153
|
+
fs.rmSync(target);
|
|
154
|
+
say("REMOVED", target);
|
|
155
|
+
} else {
|
|
156
|
+
foreign = true;
|
|
157
|
+
say("KEEP", `${target} — không phải lệnh của repo alp-code này`);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
say("ABSENT", target);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (platform === "win32") {
|
|
164
|
+
if (!foreign) {
|
|
165
|
+
removeWindowsPath(dir, env, opts, say);
|
|
166
|
+
removeFromProcessPath(dir, env, platform);
|
|
167
|
+
removeEmptyDir(dir);
|
|
168
|
+
removeEmptyDir(path.dirname(dir));
|
|
169
|
+
}
|
|
170
|
+
} else {
|
|
171
|
+
for (const file of profileCandidates(env)) {
|
|
172
|
+
if (!fs.existsSync(file)) continue;
|
|
173
|
+
const before = fs.readFileSync(file, "utf8");
|
|
174
|
+
const after = stripPathBlock(before);
|
|
175
|
+
if (after === before) continue;
|
|
176
|
+
fs.writeFileSync(file, after);
|
|
177
|
+
say("WROTE", `${file} — gỡ khối PATH của alp-code`);
|
|
178
|
+
}
|
|
179
|
+
removeFromProcessPath(dir, env, platform);
|
|
180
|
+
}
|
|
181
|
+
return log;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ---------------------------------------------------------------- file thực thi
|
|
185
|
+
|
|
186
|
+
function linkUnix(link, cli, say) {
|
|
187
|
+
try { fs.chmodSync(cli, 0o755); } catch {}
|
|
188
|
+
|
|
189
|
+
const exists = fs.existsSync(link) || isBrokenLink(link);
|
|
190
|
+
if (!exists) {
|
|
191
|
+
fs.symlinkSync(cli, link);
|
|
192
|
+
return say("LINKED", `${link} → ${cli}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const current = fs.lstatSync(link).isSymbolicLink() ? fs.readlinkSync(link) : null;
|
|
196
|
+
if (current === cli) return say("OK", `${link} → ${cli}`);
|
|
197
|
+
if (current === null)
|
|
198
|
+
// File thật của người khác trùng tên — không đụng, chỉ nói.
|
|
199
|
+
throw new Error(`${link} đã tồn tại và không phải symlink của alp-code`);
|
|
200
|
+
|
|
201
|
+
fs.rmSync(link);
|
|
202
|
+
fs.symlinkSync(cli, link);
|
|
203
|
+
say("LINKED", `${link} → ${cli} (trỏ lại từ ${current})`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function writeShim(target, cli, say) {
|
|
207
|
+
const body = cmdShim(cli);
|
|
208
|
+
if (fs.existsSync(target)) {
|
|
209
|
+
const current = fs.readFileSync(target, "utf8");
|
|
210
|
+
if (current === body) return say("OK", `${target} → ${cli}`);
|
|
211
|
+
if (!current.startsWith("@rem alp-code shim\r\n"))
|
|
212
|
+
throw new Error(`${target} đã tồn tại và không phải shim của alp-code`);
|
|
213
|
+
}
|
|
214
|
+
fs.writeFileSync(target, body);
|
|
215
|
+
say("WROTE", `${target} → ${cli}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function ownsCommand(target, cli, platform) {
|
|
219
|
+
if (platform === "win32") {
|
|
220
|
+
try { return fs.readFileSync(target, "utf8") === cmdShim(cli); } catch { return false; }
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
if (!fs.lstatSync(target).isSymbolicLink()) return false;
|
|
224
|
+
return path.resolve(path.dirname(target), fs.readlinkSync(target)) === path.resolve(cli);
|
|
225
|
+
} catch {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function isBrokenLink(p) {
|
|
231
|
+
try { fs.lstatSync(p); return true; } catch { return false; }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------- PATH
|
|
235
|
+
|
|
236
|
+
function addPosixPath(dir, env, platform, say) {
|
|
237
|
+
const prof = profileFor(env, platform);
|
|
238
|
+
if (!prof) {
|
|
239
|
+
const shell = env.SHELL || "(không rõ)";
|
|
240
|
+
return say("PATH", `không đoán được profile của shell ${shell} — tự thêm: export PATH="${dir}:$PATH"`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
let body = "";
|
|
244
|
+
if (fs.existsSync(prof.file)) body = fs.readFileSync(prof.file, "utf8");
|
|
245
|
+
if (body.includes(BEGIN))
|
|
246
|
+
return say("OK", `${prof.file} đã có khối PATH của alp-code — mở terminal mới là gõ \`alp\` được`);
|
|
247
|
+
|
|
248
|
+
fs.mkdirSync(path.dirname(prof.file), { recursive: true });
|
|
249
|
+
fs.appendFileSync(prof.file, pathBlock(dir, prof.syntax));
|
|
250
|
+
say("WROTE", `${prof.file} — thêm ${dir} vào PATH (khối có marker, gỡ tay được)`);
|
|
251
|
+
say("PATH", `mở terminal mới, hoặc: source ${prof.file}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Ghi PATH ở tầng User của Windows.
|
|
256
|
+
*
|
|
257
|
+
* Đọc/ghi qua `[Environment]::…("Path", …, "User")` chứ KHÔNG dùng `setx`: setx cắt cụt
|
|
258
|
+
* giá trị ở 1024 ký tự, và PATH của một máy dùng lâu thì gần như chắc chắn dài hơn thế —
|
|
259
|
+
* mất PATH là hỏng cả máy, không chỉ hỏng alp.
|
|
260
|
+
*/
|
|
261
|
+
function addWindowsPath(dir, env, opts, say) {
|
|
262
|
+
const apply = opts.applyWindowsPath || applyWindowsPathReal;
|
|
263
|
+
try {
|
|
264
|
+
const r = apply(dir);
|
|
265
|
+
if (r === "present") say("OK", `${dir} đã có trong PATH (User) — mở terminal mới là gõ \`alp\` được`);
|
|
266
|
+
else say("WROTE", `PATH (User) += ${dir} — mở terminal mới là gõ \`alp\` được`);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
say("PATH", `không ghi được PATH (${e.message}) — tự thêm ${dir} vào PATH của User`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function removeWindowsPath(dir, env, opts, say) {
|
|
273
|
+
const apply = opts.removeWindowsPath || removeWindowsPathReal;
|
|
274
|
+
try {
|
|
275
|
+
const r = apply(dir);
|
|
276
|
+
if (r === "absent") say("OK", `${dir} không còn trong PATH (User)`);
|
|
277
|
+
else say("WROTE", `PATH (User) -= ${dir}`);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
say("WARN", `không gỡ được PATH (User) (${e.message}) — tự gỡ ${dir}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function applyWindowsPathReal(dir) {
|
|
284
|
+
// -NoProfile: profile của người dùng có thể in ra thứ khác, làm bẩn stdout ta đang đọc.
|
|
285
|
+
const ps = [
|
|
286
|
+
"$d = $env:ALP_BIN_DIR",
|
|
287
|
+
'$cur = [Environment]::GetEnvironmentVariable("Path", "User")',
|
|
288
|
+
'if ($null -eq $cur) { $cur = "" }',
|
|
289
|
+
'$parts = $cur.Split(";") | Where-Object { $_ -ne "" }',
|
|
290
|
+
'if ($parts -contains $d) { Write-Output "present"; exit 0 }',
|
|
291
|
+
'$new = if ($cur -eq "") { $d } else { $cur.TrimEnd(";") + ";" + $d }',
|
|
292
|
+
'[Environment]::SetEnvironmentVariable("Path", $new, "User")',
|
|
293
|
+
'Write-Output "added"',
|
|
294
|
+
].join("; ");
|
|
295
|
+
|
|
296
|
+
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
|
|
297
|
+
encoding: "utf8",
|
|
298
|
+
env: { ...process.env, ALP_BIN_DIR: dir },
|
|
299
|
+
});
|
|
300
|
+
if (r.error) throw r.error;
|
|
301
|
+
if (r.status !== 0) throw new Error((r.stderr || "").trim() || `powershell exit ${r.status}`);
|
|
302
|
+
return (r.stdout || "").trim();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function removeWindowsPathReal(dir) {
|
|
306
|
+
const ps = windowsRemovePathScript();
|
|
307
|
+
|
|
308
|
+
const r = spawnSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], {
|
|
309
|
+
encoding: "utf8",
|
|
310
|
+
env: { ...process.env, ALP_BIN_DIR: dir },
|
|
311
|
+
});
|
|
312
|
+
if (r.error) throw r.error;
|
|
313
|
+
if (r.status !== 0) throw new Error((r.stderr || "").trim() || `powershell exit ${r.status}`);
|
|
314
|
+
return (r.stdout || "").trim();
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function windowsRemovePathScript() {
|
|
318
|
+
return [
|
|
319
|
+
"$d = $env:ALP_BIN_DIR.TrimEnd('\\')",
|
|
320
|
+
'$cur = [Environment]::GetEnvironmentVariable("Path", "User")',
|
|
321
|
+
'if ([string]::IsNullOrEmpty($cur)) { Write-Output "absent"; exit 0 }',
|
|
322
|
+
'$parts = @($cur.Split(";") | Where-Object { $_ -ne "" })',
|
|
323
|
+
'$keep = @($parts | Where-Object { $_.TrimEnd("\\") -ine $d })',
|
|
324
|
+
'if ($keep.Count -eq $parts.Count) { Write-Output "absent"; exit 0 }',
|
|
325
|
+
'[Environment]::SetEnvironmentVariable("Path", ($keep -join ";"), "User")',
|
|
326
|
+
'Write-Output "removed"',
|
|
327
|
+
].join("; ");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function profileCandidates(env) {
|
|
331
|
+
const home = env.HOME || os.homedir();
|
|
332
|
+
return [
|
|
333
|
+
path.join(home, ".zshrc"),
|
|
334
|
+
path.join(home, ".bash_profile"),
|
|
335
|
+
path.join(home, ".bashrc"),
|
|
336
|
+
path.join(home, ".config", "fish", "config.fish"),
|
|
337
|
+
];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function stripPathBlock(body) {
|
|
341
|
+
const escapedBegin = BEGIN.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
342
|
+
const escapedEnd = END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
343
|
+
return body.replace(new RegExp(`(?:\\r?\\n)?${escapedBegin}\\r?\\n[\\s\\S]*?${escapedEnd}(?:\\r?\\n)?`, "g"), "\n");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function removeFromProcessPath(dir, env, platform) {
|
|
347
|
+
const delimiter = platform === "win32" ? ";" : path.delimiter;
|
|
348
|
+
const key = Object.prototype.hasOwnProperty.call(env, "Path") ? "Path" : "PATH";
|
|
349
|
+
const wanted = platform === "win32" ? dir.toLowerCase() : dir;
|
|
350
|
+
env[key] = (env[key] || "")
|
|
351
|
+
.split(delimiter)
|
|
352
|
+
.filter(Boolean)
|
|
353
|
+
.filter((entry) => (platform === "win32" ? entry.toLowerCase() : entry) !== wanted)
|
|
354
|
+
.join(delimiter);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function removeEmptyDir(dir) {
|
|
358
|
+
try {
|
|
359
|
+
if (fs.readdirSync(dir).length === 0) fs.rmdirSync(dir);
|
|
360
|
+
} catch {}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
module.exports = {
|
|
364
|
+
installCli,
|
|
365
|
+
uninstallCli,
|
|
366
|
+
binDir,
|
|
367
|
+
cmdShim,
|
|
368
|
+
profileFor,
|
|
369
|
+
pathBlock,
|
|
370
|
+
onPath,
|
|
371
|
+
stripPathBlock,
|
|
372
|
+
windowsRemovePathScript,
|
|
373
|
+
BEGIN,
|
|
374
|
+
END,
|
|
375
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Vai chạy được qua launcher Codex.
|
|
2
|
+
// `main` có mặt ở đây nhưng Codex chỉ là ĐƯỜNG PHỤ (tiết kiệm quota): Codex không nạp
|
|
3
|
+
// được `alp:plan`/`alp:cook` — đó là marketplace của Claude Code. Runtime chính của main
|
|
4
|
+
// vẫn là Claude.
|
|
5
|
+
const ALLOWED_ROLES = new Set([
|
|
6
|
+
"main",
|
|
7
|
+
"search",
|
|
8
|
+
"librarian",
|
|
9
|
+
"read-thread",
|
|
10
|
+
"review",
|
|
11
|
+
"oracle",
|
|
12
|
+
"compaction",
|
|
13
|
+
"titling",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
const isAllowedRole = (role) => ALLOWED_ROLES.has(role);
|
|
17
|
+
|
|
18
|
+
module.exports = { ALLOWED_ROLES, isAllowedRole };
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { spawnSync } = require("child_process");
|
|
4
|
+
|
|
5
|
+
function commandRunner(binary) {
|
|
6
|
+
return (args, options = {}) => {
|
|
7
|
+
const result = spawnSyncCommand(binary, args, {
|
|
8
|
+
cwd: options.cwd,
|
|
9
|
+
env: { ...process.env, ...(options.env || {}) },
|
|
10
|
+
encoding: "utf8",
|
|
11
|
+
timeout: options.timeoutMs || 30000,
|
|
12
|
+
stdio: options.stdio,
|
|
13
|
+
});
|
|
14
|
+
return {
|
|
15
|
+
status: result.status,
|
|
16
|
+
stdout: result.stdout || "",
|
|
17
|
+
stderr: result.stderr || "",
|
|
18
|
+
error: result.error || null,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Node cannot execute npm-generated `.cmd` shims directly on Windows. Resolve PATHEXT
|
|
25
|
+
* ourselves, then opt into a shell only for batch files; native executables stay on the
|
|
26
|
+
* safer direct-spawn path.
|
|
27
|
+
*/
|
|
28
|
+
function spawnSyncCommand(binary, args, options = {}, platform = process.platform) {
|
|
29
|
+
const resolved = platform === "win32"
|
|
30
|
+
? resolveWindowsCommand(binary, options.env || process.env)
|
|
31
|
+
: binary;
|
|
32
|
+
if (platform === "win32" && /\.(?:cjs|mjs|js)$/i.test(resolved))
|
|
33
|
+
return spawnSync(process.execPath, [resolved, ...args], options);
|
|
34
|
+
if (platform === "win32" && /\.(?:cmd|bat)$/i.test(resolved)) {
|
|
35
|
+
const shim = resolveNpmNodeShim(resolved);
|
|
36
|
+
if (shim) return spawnSync(process.execPath, [...shim.args, ...args], options);
|
|
37
|
+
return unsupportedBatchResult(resolved);
|
|
38
|
+
}
|
|
39
|
+
return spawnSync(resolved, args, options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Resolve npm/npm-generated Windows shims without a command shell, preserving argv. */
|
|
43
|
+
function resolveNpmNodeShim(file) {
|
|
44
|
+
const directory = path.dirname(file);
|
|
45
|
+
const base = path.basename(file).toLowerCase();
|
|
46
|
+
const npmEntry = base === "npm.cmd"
|
|
47
|
+
? path.join(directory, "node_modules", "npm", "bin", "npm-cli.js")
|
|
48
|
+
: base === "npx.cmd"
|
|
49
|
+
? path.join(directory, "node_modules", "npm", "bin", "npx-cli.js")
|
|
50
|
+
: null;
|
|
51
|
+
if (npmEntry && fs.existsSync(npmEntry)) return { args: [npmEntry] };
|
|
52
|
+
|
|
53
|
+
let text;
|
|
54
|
+
try { text = fs.readFileSync(file, "utf8"); }
|
|
55
|
+
catch { return null; }
|
|
56
|
+
const launch = text.match(/"%_prog%"\s+(.+?)\s+%\*\s*$/mi);
|
|
57
|
+
if (!launch) return null;
|
|
58
|
+
const tokens = [];
|
|
59
|
+
const pattern = /"([^"]*)"|(\S+)/g;
|
|
60
|
+
let match;
|
|
61
|
+
while ((match = pattern.exec(launch[1])))
|
|
62
|
+
tokens.push((match[1] ?? match[2]).replace(/%dp0%/gi, directory));
|
|
63
|
+
return tokens.length ? { args: tokens } : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function unsupportedBatchResult(file) {
|
|
67
|
+
const error = new Error(`Windows batch shim không được hỗ trợ an toàn: ${file}`);
|
|
68
|
+
error.code = "ENOTSUP";
|
|
69
|
+
return { status: null, stdout: "", stderr: "", error };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function resolveWindowsCommand(binary, env = process.env) {
|
|
73
|
+
const command = String(binary || "");
|
|
74
|
+
if (!command) return command;
|
|
75
|
+
|
|
76
|
+
const pathValue = envValue(env, "PATH");
|
|
77
|
+
const pathExt = envValue(env, "PATHEXT") || ".COM;.EXE;.BAT;.CMD";
|
|
78
|
+
const extensions = path.extname(command)
|
|
79
|
+
? [""]
|
|
80
|
+
: pathExt.split(";").map((value) => value.trim()).filter(Boolean);
|
|
81
|
+
const directories = /[\\/]/.test(command)
|
|
82
|
+
? [""]
|
|
83
|
+
: pathValue.split(";").map((value) => value.trim().replace(/^"|"$/g, "")).filter(Boolean);
|
|
84
|
+
// npm's per-user global bin is not guaranteed to be on PATH in existing terminals.
|
|
85
|
+
// Its Windows default is `%APPDATA%\npm`; checking it keeps ALP self-contained without
|
|
86
|
+
// mutating the user's persistent PATH just to find a tool ALP needs to spawn.
|
|
87
|
+
if (!/[\\/]/.test(command) && env.APPDATA) {
|
|
88
|
+
const npmGlobalBin = path.join(env.APPDATA, "npm");
|
|
89
|
+
if (!directories.some((value) => value.toLowerCase() === npmGlobalBin.toLowerCase()))
|
|
90
|
+
directories.push(npmGlobalBin);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (const directory of directories) {
|
|
94
|
+
for (const extension of extensions) {
|
|
95
|
+
const candidate = directory ? path.join(directory, command + extension) : command + extension;
|
|
96
|
+
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return command;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function envValue(env, name) {
|
|
103
|
+
if (Object.hasOwn(env || {}, name)) return String(env[name] || "");
|
|
104
|
+
const key = Object.keys(env || {}).find((candidate) => candidate.toUpperCase() === name);
|
|
105
|
+
return key ? String(env[key] || "") : "";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = { commandRunner, resolveNpmNodeShim, resolveWindowsCommand, spawnSyncCommand };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const P = require("../install-paths.cjs");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Cả `core/` bên cạnh file này từng giữ một cây lỗi delegation cho backend CJS. Backend đó
|
|
9
|
+
* đã gỡ ngày 2026-09-03 và mã CJS còn lại chỉ ném đúng một loại lỗi, nên nó ở ngay đây.
|
|
10
|
+
*/
|
|
11
|
+
class InvalidConfiguration extends Error {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "InvalidConfiguration";
|
|
15
|
+
this.code = "InvalidConfiguration";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseConfig(text) {
|
|
20
|
+
const root = {};
|
|
21
|
+
const stack = [{ indent: -1, value: root }];
|
|
22
|
+
for (const raw of String(text).split(/\r?\n/)) {
|
|
23
|
+
const clean = raw.replace(/^\s*#.*$/, "").replace(/\s+#.*$/, "");
|
|
24
|
+
if (!clean.trim()) continue;
|
|
25
|
+
const match = clean.match(/^(\s*)([a-zA-Z_][\w-]*):(?:\s*(.*))?$/);
|
|
26
|
+
if (!match) throw new InvalidConfiguration(`Dòng config không hợp lệ: ${raw.trim()}`);
|
|
27
|
+
const indent = match[1].length;
|
|
28
|
+
while (stack.at(-1).indent >= indent) stack.pop();
|
|
29
|
+
const parent = stack.at(-1).value;
|
|
30
|
+
const key = match[2];
|
|
31
|
+
const source = (match[3] || "").trim();
|
|
32
|
+
if (!source) {
|
|
33
|
+
parent[key] = {};
|
|
34
|
+
stack.push({ indent, value: parent[key] });
|
|
35
|
+
} else if (/^(?:true|false)$/i.test(source)) parent[key] = source.toLowerCase() === "true";
|
|
36
|
+
else parent[key] = source.replace(/^(["'])(.*)\1$/, "$2");
|
|
37
|
+
}
|
|
38
|
+
return root;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Chỉ còn đúng một thứ để cấu hình: state directory.
|
|
43
|
+
*
|
|
44
|
+
* Trước 2026-09-03 hàm này chọn backend giữa `local` và `paseo`, kèm fallback và một
|
|
45
|
+
* lựa chọn ghi đè lưu trong `$stateDir/backend`. Paseo đã gỡ, nên mọi nhánh chọn lựa
|
|
46
|
+
* đó không còn gì để chọn — giữ lại chỉ là chỗ cho cấu hình nói dối về thứ đang chạy.
|
|
47
|
+
*/
|
|
48
|
+
function loadDelegationConfig(repoRoot, env = process.env) {
|
|
49
|
+
const file = env.ALP_CONFIG || path.join(repoRoot, "alp.config.yaml");
|
|
50
|
+
let root = {};
|
|
51
|
+
if (fs.existsSync(file)) root = parseConfig(fs.readFileSync(file, "utf8"));
|
|
52
|
+
const raw = root.delegation || {};
|
|
53
|
+
const stateDir = env.ALP_DELEGATION_STATE_DIR || raw.state_dir || defaultStateDir(repoRoot, env);
|
|
54
|
+
return { file, stateDir: path.resolve(stateDir) };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Băm theo đường dẫn thư mục cài để hai clone không giẫm lên state của nhau. Đúng khi thư mục
|
|
59
|
+
* cài là một clone cố định — và chỉ khi đó. Từ v0.9.0 bản cài là artifact thay được, có thể
|
|
60
|
+
* nằm ở `versions/<tag>` khác nhau mỗi lần update, nên băm nó nghĩa là mất state mỗi bản.
|
|
61
|
+
*/
|
|
62
|
+
function defaultStateDir(repoRoot, env) {
|
|
63
|
+
const home = env.HOME || os.homedir();
|
|
64
|
+
return P.detectChannel(repoRoot) === "dev"
|
|
65
|
+
? legacyStateDir(repoRoot, env)
|
|
66
|
+
: path.join(home, ".alp", "delegation", "installed");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Đường dẫn state theo luật cũ (băm repo root) — cần để di trú bản cài trước v0.9.0. */
|
|
70
|
+
function legacyStateDir(repoRoot, env) {
|
|
71
|
+
const home = env.HOME || os.homedir();
|
|
72
|
+
const repoKey = crypto.createHash("sha256").update(path.resolve(repoRoot)).digest("hex").slice(0, 12);
|
|
73
|
+
return path.join(home, ".alp", "delegation", repoKey);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
loadDelegationConfig,
|
|
78
|
+
defaultStateDir,
|
|
79
|
+
legacyStateDir,
|
|
80
|
+
parseConfig,
|
|
81
|
+
};
|