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,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runDelegateCommand = runDelegateCommand;
4
+ exports.runDelegationLifecycleCommand = runDelegationLifecycleCommand;
5
+ exports.createDefaultDelegationComposition = createDefaultDelegationComposition;
6
+ const modes_1 = require("../../agents/modes");
7
+ const node_module_1 = require("node:module");
8
+ const node_path_1 = require("node:path");
9
+ const registry_1 = require("../../agents/registry");
10
+ const local_process_backend_1 = require("../../backend/local-process-backend");
11
+ const delegation_service_1 = require("../../delegation/delegation-service");
12
+ const execution_service_1 = require("../../execution/execution-service");
13
+ const execution_store_1 = require("../../execution/execution-store");
14
+ const markdown_file_store_1 = require("../../memory/adapters/markdown-file-store");
15
+ const memory_service_1 = require("../../memory/memory-service");
16
+ const policy_engine_1 = require("../../policy/policy-engine");
17
+ const claude_adapter_1 = require("../../runtime/claude-adapter");
18
+ const codex_adapter_1 = require("../../runtime/codex-adapter");
19
+ const workflow_runner_1 = require("../../workflow/workflow-runner");
20
+ function required(args, index, message) {
21
+ const value = args[index];
22
+ if (!value)
23
+ throw new Error(message);
24
+ return value;
25
+ }
26
+ async function runDelegateCommand(argv, dependencies) {
27
+ const targetRole = argv[0];
28
+ if (!targetRole)
29
+ throw new Error("delegate requires a target role");
30
+ let background = false;
31
+ let timeoutMs = null;
32
+ let workspace = dependencies.cwd;
33
+ const task = [];
34
+ for (let index = 1; index < argv.length; index += 1) {
35
+ const value = argv[index];
36
+ if (value === "--runtime") {
37
+ // Nấc quyết định model, model quyết định CLI. Một cờ `--runtime` còn sót trong script
38
+ // cũ sẽ chọn sai CLI cho model của nấc, nên nó dừng ở đây chứ không bị bỏ qua.
39
+ throw new Error("`--runtime` không còn tồn tại; nấc quyết định model và runtime — dùng `alp mode set` hoặc ALP_MODE");
40
+ }
41
+ else if (value === "--background")
42
+ background = true;
43
+ else if (value === "--timeout-ms") {
44
+ timeoutMs = Number(required(argv, ++index, "--timeout-ms requires a number"));
45
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
46
+ throw new Error("--timeout-ms must be positive");
47
+ }
48
+ else if (value === "--workspace" || value === "--project") {
49
+ workspace = required(argv, ++index, `${value} requires a path`);
50
+ }
51
+ else if (value === "--parent-role" || value === "--role" || value === "--kind") {
52
+ throw new Error(`unsupported identity-aware raw-runtime shortcut \`${value}\``);
53
+ }
54
+ else if (value === "--backend") {
55
+ // Rejected rather than ignored. Unknown words fall through to the task below, so a
56
+ // stale `--backend paseo` left in a script would otherwise be handed to the agent as
57
+ // part of what it was asked to do, and the run would look fine while doing the wrong
58
+ // thing. There is one backend now; saying so is the only honest answer.
59
+ throw new Error("`--backend` was removed: delegation always runs on the local backend");
60
+ }
61
+ else if (value !== "--")
62
+ task.push(value);
63
+ }
64
+ if (!task.join(" ").trim())
65
+ throw new Error("delegate requires a task");
66
+ const parentRole = dependencies.env.ALP_DELEGATED_ROLE || dependencies.env.ALP_ROLE || "main";
67
+ const spawned = await dependencies.service.delegate({
68
+ parentRole,
69
+ parentExecutionId: dependencies.env.ALP_DELEGATION_EXECUTION_ID || null,
70
+ targetRole,
71
+ task: task.join(" "),
72
+ workspace,
73
+ workspaceMode: parentRole === "principal" && targetRole === "main"
74
+ ? "workspace-write"
75
+ : "read-only",
76
+ metadata: {},
77
+ executionOptions: { background, interactive: false, timeoutMs },
78
+ });
79
+ return !background && spawned.status === "running"
80
+ ? dependencies.service.wait(spawned.executionId, { timeoutMs })
81
+ : spawned;
82
+ }
83
+ async function runDelegationLifecycleCommand(argv, service) {
84
+ const command = argv[0];
85
+ if (command === "status")
86
+ return service.status(required(argv, 1, "status requires execution ID"));
87
+ if (command === "wait")
88
+ return service.wait(required(argv, 1, "wait requires execution ID"));
89
+ if (command === "cancel")
90
+ return service.cancel(required(argv, 1, "cancel requires execution ID"));
91
+ if (command === "cleanup")
92
+ return service.cleanup(required(argv, 1, "cleanup requires execution ID"));
93
+ if (command === "list")
94
+ return service.listExecutions();
95
+ throw new Error(`unknown delegation lifecycle command \`${command ?? ""}\``);
96
+ }
97
+ async function createDefaultDelegationComposition(repoRoot, env = process.env) {
98
+ const localRequire = (0, node_module_1.createRequire)(__filename);
99
+ const configModule = localRequire((0, node_path_1.join)(repoRoot, "scripts", "lib", "delegation", "config.cjs"));
100
+ const config = configModule.loadDelegationConfig(repoRoot, env);
101
+ // The one backend. It spawns the runtime as a child process, so it needs no daemon and
102
+ // works on a machine where nothing else is installed — and it hands the runtime its own
103
+ // settings file, which is what makes a role's `permissions.deny` real rather than
104
+ // advisory. Its state lives in `local.json` under the delegation state directory, so a
105
+ // later CLI process can run lifecycle commands against an execution this one started.
106
+ const backend = new local_process_backend_1.LocalProcessBackend({ env, stateDir: config.stateDir });
107
+ const policy = new policy_engine_1.PolicyEngine({ registry: registry_1.agentRegistry });
108
+ const memory = new memory_service_1.MemoryService({
109
+ store: new markdown_file_store_1.MarkdownFileStore({ root: (0, node_path_1.join)(repoRoot, "memory") }),
110
+ policy,
111
+ audit: { record() { } },
112
+ });
113
+ const executionService = new execution_service_1.ExecutionService({
114
+ registry: registry_1.agentRegistry,
115
+ policy,
116
+ memory,
117
+ workflowRunner: new workflow_runner_1.WorkflowRunner(),
118
+ store: new execution_store_1.FileExecutionStore({ root: (0, node_path_1.join)(config.stateDir, "execution-snapshots") }),
119
+ });
120
+ const service = new delegation_service_1.DelegationService({
121
+ registry: registry_1.agentRegistry,
122
+ policy,
123
+ memory,
124
+ executionService,
125
+ runtimeAdapters: new Map([
126
+ ["claude", new claude_adapter_1.ClaudeRuntimeAdapter({ env })],
127
+ ["codex", new codex_adapter_1.CodexRuntimeAdapter({ env })],
128
+ ]),
129
+ backend,
130
+ executionStore: new delegation_service_1.FileDelegationExecutionStore({ file: (0, node_path_1.join)(config.stateDir, "code-native-executions.json") }),
131
+ // Con kế thừa nấc của cha: một phiên `ultra` mà subagent lặng lẽ tụt về `medium` thì
132
+ // nấc chỉ còn đúng ở ghế ngoài cùng.
133
+ config: env.ALP_MODE ? { mode: (0, modes_1.parseMode)(env.ALP_MODE) } : {},
134
+ });
135
+ return { service, config: { stateDir: config.stateDir } };
136
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentDocumentPath = agentDocumentPath;
4
+ exports.syncIdentityDocuments = syncIdentityDocuments;
5
+ const promises_1 = require("node:fs/promises");
6
+ const node_path_1 = require("node:path");
7
+ const render_identity_1 = require("../../agents/render-identity");
8
+ function agentDocumentPath(directory, role) {
9
+ return (0, node_path_1.join)(directory, `${role}.md`);
10
+ }
11
+ /**
12
+ * Regenerates `<directory>/<role>.md` for every role in the registry.
13
+ *
14
+ * The registry stays the single source of truth; these files are a derived, machine-local
15
+ * cache that exists purely so the SessionStart hook can stay fast and dependency-free.
16
+ * Safe to run repeatedly — it always overwrites.
17
+ */
18
+ async function syncIdentityDocuments(input, dependencies) {
19
+ const directory = input.directory;
20
+ await (0, promises_1.mkdir)(directory, { recursive: true, mode: 0o700 });
21
+ const written = [];
22
+ for (const definition of dependencies.registry.list()) {
23
+ const file = agentDocumentPath(directory, definition.id);
24
+ await (0, promises_1.writeFile)(file, (0, render_identity_1.renderIdentityDocument)(definition), {
25
+ encoding: "utf8",
26
+ mode: 0o600,
27
+ });
28
+ written.push(file);
29
+ }
30
+ return Object.freeze(written);
31
+ }
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProjectRegistryStore = void 0;
4
+ exports.initializeProject = initializeProject;
5
+ exports.deinitializeProject = deinitializeProject;
6
+ const promises_1 = require("node:fs/promises");
7
+ const promises_2 = require("node:fs/promises");
8
+ const node_crypto_1 = require("node:crypto");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const state_paths_1 = require("../../state-paths");
12
+ function within(root, target) {
13
+ const relation = (0, node_path_1.relative)((0, node_path_1.resolve)(root), (0, node_path_1.resolve)(target));
14
+ return relation === "" || (!relation.startsWith("..") && !(0, node_path_1.isAbsolute)(relation));
15
+ }
16
+ async function exists(path) {
17
+ try {
18
+ await (0, promises_2.lstat)(path);
19
+ return true;
20
+ }
21
+ catch (error) {
22
+ if (error.code === "ENOENT")
23
+ return false;
24
+ throw error;
25
+ }
26
+ }
27
+ class ProjectRegistryStore {
28
+ file;
29
+ constructor(options = {}) {
30
+ this.file = options.file ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".alp", "projects.json");
31
+ }
32
+ async read() {
33
+ try {
34
+ const value = JSON.parse(await (0, promises_2.readFile)(this.file, "utf8"));
35
+ if (value.version !== 1 || !Array.isArray(value.projects))
36
+ throw new Error("unsupported project registry");
37
+ return { version: 1, projects: Object.freeze(value.projects.map((entry) => Object.freeze({ ...entry }))) };
38
+ }
39
+ catch (error) {
40
+ if (error.code === "ENOENT")
41
+ return { version: 1, projects: [] };
42
+ throw error;
43
+ }
44
+ }
45
+ async register(project) {
46
+ const current = await this.read();
47
+ const projects = current.projects.filter((entry) => entry.path !== project.path);
48
+ projects.push(Object.freeze({ ...project }));
49
+ projects.sort((left, right) => left.path.localeCompare(right.path));
50
+ await this.write({ version: 1, projects });
51
+ }
52
+ async unregister(projectPath) {
53
+ const current = await this.read();
54
+ await this.write({ version: 1, projects: current.projects.filter((entry) => entry.path !== projectPath) });
55
+ }
56
+ async isRegistered(projectPath) {
57
+ const canonical = await (0, promises_1.realpath)(projectPath);
58
+ const current = await this.read();
59
+ return current.projects.some((entry) => entry.path === canonical);
60
+ }
61
+ async write(value) {
62
+ const directory = (0, node_path_1.dirname)(this.file);
63
+ await (0, promises_2.mkdir)(directory, { recursive: true, mode: 0o700 });
64
+ await (0, promises_2.chmod)(directory, 0o700);
65
+ const temporary = (0, node_path_1.join)(directory, `.${(0, node_crypto_1.randomUUID)()}.projects.tmp`);
66
+ try {
67
+ await (0, promises_2.writeFile)(temporary, `${JSON.stringify(value, null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
68
+ await (0, promises_2.rename)(temporary, this.file);
69
+ await (0, promises_2.chmod)(this.file, 0o600);
70
+ }
71
+ finally {
72
+ await (0, promises_2.rm)(temporary, { force: true });
73
+ }
74
+ }
75
+ }
76
+ exports.ProjectRegistryStore = ProjectRegistryStore;
77
+ /**
78
+ * Marker string `deinitializeProject` looks for before deleting the file — it is how we
79
+ * tell a config we generated from one the user wrote themselves.
80
+ */
81
+ const EXCLUDE_ENTRY = ".claude/settings.local.json";
82
+ /**
83
+ * Keeps the generated settings file out of `git status` without touching a tracked file.
84
+ * `.git/info/exclude` is per-clone and never committed, so this stays invisible to the
85
+ * project's collaborators — unlike appending to `.gitignore`.
86
+ */
87
+ async function excludeLocally(project) {
88
+ const file = (0, node_path_1.join)(project, ".git", "info", "exclude");
89
+ try {
90
+ const current = (await exists(file)) ? await (0, promises_2.readFile)(file, "utf8") : "";
91
+ if (current.split(/\r?\n/).some((line) => line.trim() === EXCLUDE_ENTRY))
92
+ return;
93
+ await (0, promises_2.mkdir)((0, node_path_1.dirname)(file), { recursive: true });
94
+ const separator = current === "" || current.endsWith("\n") ? "" : "\n";
95
+ await (0, promises_2.writeFile)(file, `${current}${separator}${EXCLUDE_ENTRY}\n`, "utf8");
96
+ }
97
+ catch { /* not a git checkout, or exclude unwritable — the settings file still works */ }
98
+ }
99
+ async function writeProjectSettings(project, repoRoot) {
100
+ const file = (0, node_path_1.join)(project, ".claude", "settings.local.json");
101
+ if (await exists(file)) {
102
+ const content = await (0, promises_2.readFile)(file, "utf8");
103
+ if (!content.toLowerCase().includes("alp init"))
104
+ await (0, promises_2.rename)(file, `${file}.alp-backup`);
105
+ }
106
+ await (0, promises_2.mkdir)((0, node_path_1.dirname)(file), { recursive: true });
107
+ // Forwarder ở `~/.alp/hooks`, KHÔNG phải hook trong thư mục cài: file này nằm trong repo của
108
+ // người dùng và sống lâu hơn bản cài ALP đã ghi ra nó. Một đường dẫn tuyệt đối tới thư mục
109
+ // cài sẽ chết khi lên version, đổi channel hoặc cài lại chỗ khác — và phiên `claude` mở tay
110
+ // chỉ im lặng mất identity, đúng kiểu hỏng câm khó lần ra nhất.
111
+ const hook = `${JSON.stringify(process.execPath)} ${JSON.stringify((0, state_paths_1.hookForwarder)("session-boot"))}`;
112
+ await (0, promises_2.writeFile)(file, `${JSON.stringify({
113
+ $generatedBy: "alp init",
114
+ hooks: { SessionStart: [{ hooks: [{ type: "command", command: hook }] }] },
115
+ }, null, 2)}\n`, "utf8");
116
+ await excludeLocally(project);
117
+ }
118
+ async function initializeProject(input, dependencies = {}) {
119
+ const project = await (0, promises_1.realpath)(input.project);
120
+ const metadata = await (0, promises_2.lstat)(project);
121
+ if (!metadata.isDirectory())
122
+ throw new Error(`project is not a directory: ${project}`);
123
+ const store = dependencies.store ?? new ProjectRegistryStore();
124
+ const registered = Object.freeze({ path: project });
125
+ await store.register(registered);
126
+ if (input.repoRoot)
127
+ await writeProjectSettings(project, input.repoRoot);
128
+ return registered;
129
+ }
130
+ async function removeEmpty(directory) {
131
+ try {
132
+ if ((await (0, promises_2.readdir)(directory)).length === 0)
133
+ await (0, promises_2.rm)(directory, { recursive: false });
134
+ }
135
+ catch { /* absent or non-empty */ }
136
+ }
137
+ async function removeOwnedSkillLinks(project, repoRoot) {
138
+ const skillsRoot = (0, node_path_1.join)(repoRoot, "skills");
139
+ for (const directory of [(0, node_path_1.join)(project, ".claude", "skills"), (0, node_path_1.join)(project, ".agents", "skills")]) {
140
+ let entries;
141
+ try {
142
+ entries = await (0, promises_2.readdir)(directory);
143
+ }
144
+ catch {
145
+ continue;
146
+ }
147
+ for (const name of entries) {
148
+ const link = (0, node_path_1.join)(directory, name);
149
+ let metadata;
150
+ try {
151
+ metadata = await (0, promises_2.lstat)(link);
152
+ }
153
+ catch {
154
+ continue;
155
+ }
156
+ if (!metadata.isSymbolicLink())
157
+ continue;
158
+ const target = await (0, promises_2.readlink)(link);
159
+ const resolved = (0, node_path_1.resolve)((0, node_path_1.dirname)(link), target);
160
+ if (within(skillsRoot, resolved))
161
+ await (0, promises_2.rm)(link, { force: true });
162
+ }
163
+ await removeEmpty(directory);
164
+ await removeEmpty((0, node_path_1.dirname)(directory));
165
+ }
166
+ }
167
+ async function cleanupGeneratedConfig(file) {
168
+ const backup = `${file}.alp-backup`;
169
+ if (await exists(file)) {
170
+ const content = await (0, promises_2.readFile)(file, "utf8");
171
+ if (content.toLowerCase().includes("alp init"))
172
+ await (0, promises_2.rm)(file);
173
+ }
174
+ if ((await exists(backup)) && !(await exists(file)))
175
+ await (0, promises_2.rename)(backup, file);
176
+ await removeEmpty((0, node_path_1.dirname)(file));
177
+ }
178
+ async function deinitializeProject(input, dependencies = {}) {
179
+ const project = await (0, promises_1.realpath)(input.project);
180
+ await removeOwnedSkillLinks(project, input.repoRoot);
181
+ await cleanupGeneratedConfig((0, node_path_1.join)(project, ".claude", "settings.local.json"));
182
+ await cleanupGeneratedConfig((0, node_path_1.join)(project, ".codex", "config.toml"));
183
+ await (dependencies.store ?? new ProjectRegistryStore()).unregister(project);
184
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runModeCommand = runModeCommand;
4
+ const modes_1 = require("../../agents/modes");
5
+ const mode_preference_store_1 = require("../mode-preference-store");
6
+ async function runModeCommand(input, options = {}) {
7
+ const store = options.store ?? new mode_preference_store_1.FileModePreferenceStore();
8
+ const write = options.write ?? ((text) => process.stdout.write(text));
9
+ if (input.action === "set") {
10
+ if (!input.mode)
11
+ throw new Error("mode set requires low, medium, high, ultra, or puck");
12
+ await store.write(input.mode);
13
+ write(`${input.mode}\n`);
14
+ return input.mode;
15
+ }
16
+ const preference = await store.read();
17
+ if (preference.warning)
18
+ write(`WARNING ${preference.warning}\n`);
19
+ const mode = preference.mode ?? modes_1.DEFAULT_MODE;
20
+ write(`${mode}\n`);
21
+ return mode;
22
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promptPrincipalProfile = promptPrincipalProfile;
4
+ exports.ensurePrincipalProfile = ensurePrincipalProfile;
5
+ exports.runPrincipalCommand = runPrincipalCommand;
6
+ const promises_1 = require("node:readline/promises");
7
+ const principal_profile_store_1 = require("../../principal/principal-profile-store");
8
+ const STDIN_ENDED = "stdin ended before the principal profile was complete";
9
+ /**
10
+ * Reads answers off a line queue instead of `rl.question`.
11
+ *
12
+ * Two reasons. A piped stdin (`printf 'name\nanh\nem\n' | alp principal set`) flushes every
13
+ * line while only the first question is pending, and `rl.question` drops the rest; and when
14
+ * the input ends with a question outstanding, `rl.question` never settles, so the CLI would
15
+ * hang rather than fail. Queueing lines fixes the first, and the close handler turns the
16
+ * second into an error.
17
+ */
18
+ function openTerminalPrompt() {
19
+ const reader = (0, promises_1.createInterface)({ input: process.stdin, output: process.stdout });
20
+ const lines = [];
21
+ const waiting = [];
22
+ let ended = false;
23
+ reader.on("line", (line) => {
24
+ const waiter = waiting.shift();
25
+ if (waiter)
26
+ waiter.resolve(line);
27
+ else
28
+ lines.push(line);
29
+ });
30
+ reader.on("close", () => {
31
+ ended = true;
32
+ for (const waiter of waiting.splice(0))
33
+ waiter.reject(new Error(STDIN_ENDED));
34
+ });
35
+ return {
36
+ ask(question) {
37
+ process.stdout.write(question);
38
+ const buffered = lines.shift();
39
+ if (buffered !== undefined)
40
+ return Promise.resolve(buffered);
41
+ if (ended)
42
+ return Promise.reject(new Error(STDIN_ENDED));
43
+ return new Promise((resolve, reject) => { waiting.push({ resolve, reject }); });
44
+ },
45
+ close: () => reader.close(),
46
+ };
47
+ }
48
+ async function promptPrincipalProfile(prompt) {
49
+ return (0, principal_profile_store_1.normalizePrincipalProfile)({
50
+ name: await prompt.ask(" Your name: "),
51
+ addressAs: await prompt.ask(" An agent addresses you as (anh, chị, bạn…): "),
52
+ selfAs: await prompt.ask(" An agent refers to itself as (em, tôi, mình…): "),
53
+ });
54
+ }
55
+ function describe(profile) {
56
+ return `PROFILE ${profile.name} — agents say "${profile.selfAs}" and call you "${profile.addressAs}"\n`;
57
+ }
58
+ async function capturePrincipalProfile(dependencies) {
59
+ dependencies.write("\nWho is ALP serving? This is stored once, on this machine only.\n");
60
+ const prompt = (dependencies.openPrompt ?? openTerminalPrompt)();
61
+ let answered;
62
+ try {
63
+ answered = await promptPrincipalProfile(prompt);
64
+ }
65
+ finally {
66
+ prompt.close();
67
+ }
68
+ const profile = await (0, principal_profile_store_1.writePrincipalProfile)(answered, dependencies.file);
69
+ dependencies.write(describe(profile));
70
+ await dependencies.syncIdentity?.();
71
+ return profile;
72
+ }
73
+ /**
74
+ * Called by `alp init`. Asks only when the profile is missing and a terminal is attached;
75
+ * a non-interactive install (CI, scripts) keeps going with a neutral prompt rather than
76
+ * failing or guessing a name from git config.
77
+ *
78
+ * Never throws: the project registration has already happened by this point, so a refused
79
+ * or empty answer downgrades to the same note a non-interactive run gets.
80
+ */
81
+ async function ensurePrincipalProfile(input, dependencies) {
82
+ const current = (0, principal_profile_store_1.readPrincipalProfile)(dependencies.file ?? (0, principal_profile_store_1.principalProfileFile)());
83
+ if (current.warning)
84
+ dependencies.write(`WARNING ${current.warning}\n`);
85
+ if (current.profile)
86
+ return current.profile;
87
+ if (input.interactive) {
88
+ try {
89
+ return await capturePrincipalProfile(dependencies);
90
+ }
91
+ catch (error) {
92
+ dependencies.write(`WARNING ${error instanceof Error ? error.message : String(error)}\n`);
93
+ }
94
+ }
95
+ dependencies.write("NOTE principal profile is unset; run `alp principal set` to add your name and forms of address\n");
96
+ return null;
97
+ }
98
+ async function runPrincipalCommand(input, dependencies) {
99
+ const file = dependencies.file ?? (0, principal_profile_store_1.principalProfileFile)();
100
+ if (input.action === "show") {
101
+ const current = (0, principal_profile_store_1.readPrincipalProfile)(file);
102
+ if (current.warning)
103
+ dependencies.write(`WARNING ${current.warning}\n`);
104
+ if (!current.profile) {
105
+ dependencies.write("UNSET no principal profile; run `alp principal set`\n");
106
+ return 1;
107
+ }
108
+ dependencies.write(describe(current.profile));
109
+ dependencies.write(`FILE ${file}\n`);
110
+ return 0;
111
+ }
112
+ await capturePrincipalProfile(dependencies);
113
+ return 0;
114
+ }
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runMainSession = runMainSession;
4
+ const modes_1 = require("../../agents/modes");
5
+ const promises_1 = require("node:fs/promises");
6
+ const continuity_1 = require("../../context/continuity");
7
+ async function runMainSession(input, dependencies) {
8
+ const definition = dependencies.registry.get("main");
9
+ if (definition.reportsTo !== "principal")
10
+ throw new Error("main must report to principal");
11
+ const selection = await dependencies.selector.select({
12
+ ...(input.mode === undefined ? {} : { requestedMode: input.mode }),
13
+ interactive: dependencies.interactive && input.mode === undefined,
14
+ });
15
+ if (!selection.ok)
16
+ return { executionId: "cancelled", status: "cancelled" };
17
+ const mode = selection.mode;
18
+ // Runtime là **hệ quả** của model, không phải một lựa chọn riêng: nấc ghim một model cho
19
+ // `main`, và model đó chỉ chạy được trên đúng một CLI.
20
+ const runtime = (0, modes_1.runtimeForMode)(definition, mode);
21
+ const executionId = dependencies.executionId();
22
+ const workspaceMode = dependencies.workspaceModeFor
23
+ ? await dependencies.workspaceModeFor(input.cwd)
24
+ : "read-only";
25
+ const execution = await dependencies.executionService.prepare({
26
+ executionId,
27
+ parent: "principal",
28
+ target: definition.id,
29
+ // Never rendered into a turn — an interactive launch writes no task file. It exists as
30
+ // audit metadata in `identity-capsule.json`, saying what this execution was opened for.
31
+ // The real task arrives as the principal's own first message.
32
+ task: continuity_1.INTERACTIVE_TASK_SENTINEL,
33
+ workspace: input.cwd,
34
+ workspaceMode,
35
+ mode,
36
+ memoryQueries: [],
37
+ characterBudget: 0,
38
+ invariantContext: "ALP execution policy is authoritative and fails closed.",
39
+ policyContext: "Direct raw runtime launch is unsupported; use ALP workflows.",
40
+ });
41
+ const adapter = dependencies.adapters.get(runtime);
42
+ if (!adapter)
43
+ throw new Error(`runtime \`${runtime}\` is not registered`);
44
+ const health = await adapter.probe();
45
+ if (!health.ok)
46
+ throw new Error(`${health.message}${health.remediation ? `; ${health.remediation}` : ""}`);
47
+ const launchSpec = await adapter.prepare({
48
+ execution,
49
+ // Nấc thắng khai báo của vai — cùng một `main` chạy bốn model khác nhau. Lấy từ cùng
50
+ // giá trị đã đi vào policy, để `policy.json` và tiến trình thật sự chạy không lệch nhau.
51
+ model: (0, modes_1.modelForMode)(definition, mode),
52
+ reasoningEffort: (0, modes_1.reasoningEffortForMode)(definition, mode),
53
+ interactive: true,
54
+ });
55
+ // The principal is sitting in front of this one, so it must own the terminal: a backend
56
+ // that tees stdout instead of inheriting it would leave the session with no tty and no
57
+ // way to type. `interactive` is the only thing that keeps `stdio: "inherit"` here.
58
+ const spawned = await dependencies.backend.spawn({
59
+ executionId,
60
+ launchSpec,
61
+ lifecycle: {
62
+ requestId: executionId,
63
+ parentExecutionId: null,
64
+ background: false,
65
+ interactive: true,
66
+ timeoutMs: null,
67
+ },
68
+ });
69
+ const backendResult = spawned.status === "running" ? await dependencies.backend.wait(executionId) : spawned;
70
+ const stateFile = execution.artifacts?.stateFile;
71
+ if (!stateFile || !["completed", "failed", "cancelled"].includes(backendResult.status))
72
+ return backendResult;
73
+ try {
74
+ const state = JSON.parse(await (0, promises_1.readFile)(stateFile, "utf8"));
75
+ const status = ["completed", "failed", "cancelled"].includes(String(state.status))
76
+ ? state.status
77
+ : backendResult.status;
78
+ return {
79
+ ...backendResult,
80
+ status,
81
+ ...(state.output === undefined
82
+ ? {}
83
+ // Prose answers pass through unchanged; only a non-string is serialized.
84
+ : { output: typeof state.output === "string" ? state.output : JSON.stringify(state.output) }),
85
+ };
86
+ }
87
+ catch {
88
+ return backendResult;
89
+ }
90
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runRuntimeCommand = runRuntimeCommand;
4
+ const runtime_preference_store_1 = require("../../runtime/runtime-preference-store");
5
+ async function runRuntimeCommand(input, options = {}) {
6
+ const store = options.store ?? new runtime_preference_store_1.FileRuntimePreferenceStore();
7
+ const write = options.write ?? ((text) => process.stdout.write(text));
8
+ if (input.action === "set") {
9
+ if (!input.runtime)
10
+ throw new Error("runtime set requires claude or codex");
11
+ await store.write(input.runtime);
12
+ write(`${input.runtime}\n`);
13
+ return input.runtime;
14
+ }
15
+ const preference = await store.read();
16
+ if (preference.warning)
17
+ write(`WARNING ${preference.warning}\n`);
18
+ const runtime = preference.runtime ?? "claude";
19
+ write(`${runtime}\n`);
20
+ return runtime;
21
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileModePreferenceStore = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const promises_1 = require("node:fs/promises");
6
+ const node_os_1 = require("node:os");
7
+ const node_path_1 = require("node:path");
8
+ const modes_1 = require("../agents/modes");
9
+ class FileModePreferenceStore {
10
+ file;
11
+ constructor(options = {}) {
12
+ this.file = options.file ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".alp", "mode.json");
13
+ }
14
+ async read() {
15
+ let content;
16
+ try {
17
+ content = await (0, promises_1.readFile)(this.file, "utf8");
18
+ }
19
+ catch (error) {
20
+ if (error.code === "ENOENT") {
21
+ return { mode: null };
22
+ }
23
+ return {
24
+ mode: null,
25
+ warning: `invalid mode preference at ${this.file}; using \`${modes_1.DEFAULT_MODE}\``,
26
+ };
27
+ }
28
+ try {
29
+ const parsed = JSON.parse(content);
30
+ if (parsed === null || typeof parsed !== "object" || !(0, modes_1.isModeId)(parsed.mode)) {
31
+ throw new Error("invalid mode");
32
+ }
33
+ return { mode: parsed.mode };
34
+ }
35
+ catch {
36
+ return {
37
+ mode: null,
38
+ warning: `invalid mode preference at ${this.file}; using \`${modes_1.DEFAULT_MODE}\``,
39
+ };
40
+ }
41
+ }
42
+ async write(mode) {
43
+ if (!(0, modes_1.isModeId)(mode)) {
44
+ throw new Error(`invalid mode \`${String(mode)}\``);
45
+ }
46
+ const directory = (0, node_path_1.dirname)(this.file);
47
+ await (0, promises_1.mkdir)(directory, { recursive: true, mode: 0o700 });
48
+ await (0, promises_1.chmod)(directory, 0o700);
49
+ const temporary = (0, node_path_1.join)(directory, `.${(0, node_crypto_1.randomUUID)()}.mode.tmp`);
50
+ try {
51
+ await (0, promises_1.writeFile)(temporary, `${JSON.stringify({ mode })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
52
+ await (0, promises_1.chmod)(temporary, 0o600);
53
+ await (0, promises_1.rename)(temporary, this.file);
54
+ await (0, promises_1.chmod)(this.file, 0o600);
55
+ }
56
+ catch (error) {
57
+ await (0, promises_1.rm)(temporary, { force: true });
58
+ throw error;
59
+ }
60
+ }
61
+ }
62
+ exports.FileModePreferenceStore = FileModePreferenceStore;