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,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderSessionContext = renderSessionContext;
4
+ /**
5
+ * Renders what is true for the whole session, regardless of what the principal asks next.
6
+ *
7
+ * This is the half of the old `renderCapsulePrompt` that must never become a user turn.
8
+ * It reaches both runtimes the same way — written to `session-context.md`, pointed at by
9
+ * `ALP_SESSION_CONTEXT`, and emitted by the SessionStart hook as `additionalContext`.
10
+ * Claude and Codex both land it as a developer-role message ahead of turn 1, so an
11
+ * interactive session is fully briefed while still waiting for its first real input.
12
+ *
13
+ * Nothing per-task belongs here: the current task and the memory selected for it live in
14
+ * `renderTaskInput`, because those do create a turn.
15
+ */
16
+ /**
17
+ * The `Delegates to` row grants the roles; this says how to reach them.
18
+ *
19
+ * Neither runtime carries a delegation tool — `DelegationService` is reachable only through
20
+ * the `alp delegate` CLI, so the shell is the channel. Left unsaid, a role reads its grant,
21
+ * finds no tool matching it, and correctly reports itself blocked rather than inventing a
22
+ * command: the line above forbids exactly that guess. Naming the command is what turns the
23
+ * grant into something usable.
24
+ *
25
+ * Gated on the session-wide `Bash` grant rather than `capsule.allowedTools`, which is
26
+ * narrowed to the opening workflow state and would hide the section from a role that gets a
27
+ * shell one state later. Runtime enforcement reads the same session-wide grant.
28
+ *
29
+ * No identity appears in the command. `alp delegate` takes the caller from
30
+ * `ALP_DELEGATED_ROLE` in the inherited environment and rejects `--role` and
31
+ * `--parent-role`, so a role cannot delegate as anyone but itself.
32
+ */
33
+ function delegationSection(capsule, policy) {
34
+ if (policy.delegatesTo.length === 0 || !policy.allowedTools.includes("Bash"))
35
+ return [];
36
+ return [
37
+ "## Delegation",
38
+ "",
39
+ "Specialists are separate executions, launched from your shell — there is no delegation tool. One role per call, task after `--`:",
40
+ "",
41
+ "```",
42
+ `alp delegate <role> --project ${capsule.activeWorkspace} -- "<task>"`,
43
+ "```",
44
+ "",
45
+ "Add `--background` to keep working while it runs, then follow it with `alp delegation status <id>` and `alp delegation wait <id>`.",
46
+ "",
47
+ "Pass no identity flag — the call inherits yours, and the roles in the table above are the only ones policy accepts. What comes back is a report to verify, not a result to forward unchecked.",
48
+ "",
49
+ ];
50
+ }
51
+ /**
52
+ * Teaches how to keep continuity alive across a runtime's own compaction.
53
+ *
54
+ * Gated on the same session-wide `Bash` grant as `delegationSection`, for the same reason:
55
+ * a read-only role (search, librarian, compaction…) has no shell to run `alp context pin`
56
+ * from, and a section it cannot act on would just be noise. `source: "agent"` therefore only
57
+ * ever shows up where the command is actually reachable.
58
+ */
59
+ function continuitySection(policy) {
60
+ if (!policy.allowedTools.includes("Bash"))
61
+ return [];
62
+ return [
63
+ "## Continuity",
64
+ "",
65
+ "Record decisions and constraints the moment you settle them, not at the end of the session. After the runtime compacts, only what you pinned survives:",
66
+ "",
67
+ "```",
68
+ 'alp context pin decision -- "chose X over Y because Z"',
69
+ 'alp context pin constraint -- "do not touch Z"',
70
+ 'alp context pin open-item -- "..."',
71
+ 'alp context pin next-action -- "..."',
72
+ "```",
73
+ "",
74
+ "A pin is one sentence, not a summary. Never pin a secret or a file's contents.",
75
+ "",
76
+ ];
77
+ }
78
+ function renderSessionContext(capsule, policy) {
79
+ const list = (values) => values.length === 0 ? "—" : values.join(", ");
80
+ return [
81
+ `# ${capsule.displayName} — \`${capsule.role}\``,
82
+ "",
83
+ capsule.instructions,
84
+ "",
85
+ "## Authority",
86
+ "",
87
+ "| | |",
88
+ "| --- | --- |",
89
+ `| Workspace | ${policy.workspaceAccess === "none"
90
+ ? "— (no workspace grant; memory only)"
91
+ : `\`${capsule.activeWorkspace}\` (${policy.workspaceMode})`} |`,
92
+ // The session-wide grant, which is what both runtimes actually enforce. `capsule.allowedTools`
93
+ // is narrowed to the opening workflow state and only advances at the Stop hook, so printing
94
+ // it here told `main` it held three tools for a whole session in which it held nine — under a
95
+ // table that calls itself the whole of your authority. The workflow still gates the output
96
+ // contract; it was never the runtime's tool list.
97
+ `| Tools | ${list(policy.allowedTools)} |`,
98
+ // Named grants (§5.3). A role that holds none still sees the row: "you have no MCP
99
+ // server" is authority information, and its absence reads as an unanswered question.
100
+ `| Skills | ${list(policy.skills)} |`,
101
+ `| Subagents | ${list(policy.subagents.map((subagent) => subagent.name))} |`,
102
+ `| MCP servers | ${list(policy.mcpServers.map((server) => `${server.name} (${server.egress} egress)`))} |`,
103
+ `| Memory read | ${list(policy.memory.read)} |`,
104
+ `| Memory write | ${list(policy.memory.write)} |`,
105
+ `| Delegates to | ${list(policy.delegatesTo)} |`,
106
+ "",
107
+ "That table is the whole of your authority. If something you need is blocked, report it — do not route around it.",
108
+ "",
109
+ ...delegationSection(capsule, policy),
110
+ ...continuitySection(policy),
111
+ "## Invariants",
112
+ "",
113
+ capsule.memoryContext.invariantContext,
114
+ "",
115
+ "## Policy",
116
+ "",
117
+ capsule.memoryContext.policyContext,
118
+ "",
119
+ "## Reporting",
120
+ "",
121
+ "Answer in prose. Close with your status, what you actually did, and the evidence for it — commands you ran, files you changed, output you saw. Do not claim a step you skipped.",
122
+ "",
123
+ ].join("\n");
124
+ }
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderTaskInput = renderTaskInput;
4
+ /**
5
+ * Renders the half of an execution that is *meant* to create a turn.
6
+ *
7
+ * Only a headless run has one. An interactive session gets its first turn from the
8
+ * principal, so no adapter may render this into a positional prompt there — that is the
9
+ * synthetic turn this split exists to remove.
10
+ *
11
+ * Memory sits here rather than in the session context because it is selected per task:
12
+ * `MemoryService.buildContext` runs against the task's queries and budget, and what it
13
+ * returns is state, not identity.
14
+ */
15
+ function renderTaskInput(capsule) {
16
+ const memory = capsule.memoryContext.entries.length === 0
17
+ ? "(no memory entries selected)"
18
+ : capsule.memoryContext.entries
19
+ .map((entry) => `### ${entry.id}\n\n${entry.content}`)
20
+ .join("\n\n");
21
+ return [
22
+ `# ALP execution ${capsule.executionId}`,
23
+ "",
24
+ "## Relevant memory",
25
+ "",
26
+ memory,
27
+ "",
28
+ "## Task",
29
+ "",
30
+ capsule.task,
31
+ "",
32
+ ].join("\n");
33
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FileRuntimePreferenceStore = 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
+ function isRuntimeId(value) {
9
+ return value === "claude" || value === "codex";
10
+ }
11
+ class FileRuntimePreferenceStore {
12
+ file;
13
+ constructor(options = {}) {
14
+ this.file = options.file ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".alp", "runtime.json");
15
+ }
16
+ async read() {
17
+ let content;
18
+ try {
19
+ content = await (0, promises_1.readFile)(this.file, "utf8");
20
+ }
21
+ catch (error) {
22
+ if (error.code === "ENOENT") {
23
+ return { runtime: null };
24
+ }
25
+ return {
26
+ runtime: null,
27
+ warning: `invalid runtime preference at ${this.file}; using Claude default`,
28
+ };
29
+ }
30
+ try {
31
+ const parsed = JSON.parse(content);
32
+ if (parsed === null ||
33
+ typeof parsed !== "object" ||
34
+ !isRuntimeId(parsed.runtime)) {
35
+ throw new Error("invalid runtime");
36
+ }
37
+ return { runtime: parsed.runtime };
38
+ }
39
+ catch {
40
+ return {
41
+ runtime: null,
42
+ warning: `invalid runtime preference at ${this.file}; using Claude default`,
43
+ };
44
+ }
45
+ }
46
+ async write(runtime) {
47
+ if (!isRuntimeId(runtime)) {
48
+ throw new Error(`invalid runtime \`${String(runtime)}\``);
49
+ }
50
+ const directory = (0, node_path_1.dirname)(this.file);
51
+ await (0, promises_1.mkdir)(directory, { recursive: true, mode: 0o700 });
52
+ await (0, promises_1.chmod)(directory, 0o700);
53
+ const temporary = (0, node_path_1.join)(directory, `.${(0, node_crypto_1.randomUUID)()}.runtime.tmp`);
54
+ try {
55
+ await (0, promises_1.writeFile)(temporary, `${JSON.stringify({ runtime })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
56
+ await (0, promises_1.chmod)(temporary, 0o600);
57
+ await (0, promises_1.rename)(temporary, this.file);
58
+ await (0, promises_1.chmod)(this.file, 0o600);
59
+ }
60
+ catch (error) {
61
+ await (0, promises_1.rm)(temporary, { force: true });
62
+ throw error;
63
+ }
64
+ }
65
+ }
66
+ exports.FileRuntimePreferenceStore = FileRuntimePreferenceStore;
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RuntimeSelector = void 0;
4
+ const node_stream_1 = require("node:stream");
5
+ const node_readline_1 = require("node:readline");
6
+ const runtime_preference_store_1 = require("./runtime-preference-store");
7
+ const RUNTIMES = ["claude", "codex"];
8
+ const LABELS = {
9
+ claude: "Claude",
10
+ codex: "Codex",
11
+ };
12
+ const DEFAULT_RUNTIME = "claude";
13
+ function normalizeKeypress(sequence, key = {}) {
14
+ if (key.ctrl && key.name === "c")
15
+ return "cancel";
16
+ if (key.name === "up")
17
+ return "up";
18
+ if (key.name === "down")
19
+ return "down";
20
+ if (key.name === "return" ||
21
+ key.name === "enter" ||
22
+ sequence === "\r" ||
23
+ sequence === "\n") {
24
+ return "enter";
25
+ }
26
+ return "other";
27
+ }
28
+ function createKeypressReader(input) {
29
+ const decoder = new node_stream_1.PassThrough();
30
+ const queued = [];
31
+ const waiting = [];
32
+ let terminalError = null;
33
+ const onKeypress = (sequence, key) => {
34
+ const value = normalizeKeypress(sequence, key);
35
+ const waiter = waiting.shift();
36
+ if (waiter)
37
+ waiter.resolve(value);
38
+ else
39
+ queued.push(value);
40
+ };
41
+ const onError = (error) => {
42
+ terminalError = error;
43
+ for (const waiter of waiting.splice(0))
44
+ waiter.reject(error);
45
+ };
46
+ const onData = (chunk) => {
47
+ decoder.write(chunk);
48
+ };
49
+ (0, node_readline_1.emitKeypressEvents)(decoder);
50
+ decoder.on("keypress", onKeypress);
51
+ input.on("data", onData);
52
+ input.on("error", onError);
53
+ return {
54
+ read() {
55
+ const queuedKey = queued.shift();
56
+ if (queuedKey !== undefined)
57
+ return Promise.resolve(queuedKey);
58
+ if (terminalError !== null)
59
+ return Promise.reject(terminalError);
60
+ return new Promise((resolve, reject) => {
61
+ waiting.push({ resolve, reject });
62
+ });
63
+ },
64
+ close() {
65
+ input.removeListener("data", onData);
66
+ input.removeListener("error", onError);
67
+ decoder.removeListener("keypress", onKeypress);
68
+ decoder.destroy();
69
+ },
70
+ };
71
+ }
72
+ function renderMenu(output, current, selectedIndex, redraw) {
73
+ if (redraw)
74
+ output.write(`\u001b[${RUNTIMES.length + 1}A`);
75
+ for (const [index, runtime] of RUNTIMES.entries()) {
76
+ const pointer = index === selectedIndex ? "❯" : " ";
77
+ const persisted = runtime === current ? " (current)" : "";
78
+ output.write(`\r\u001b[2K ${pointer} ${LABELS[runtime]}${persisted}\n`);
79
+ }
80
+ output.write("\r\u001b[2K↑/↓ select · Enter confirm · Ctrl+C cancel\n");
81
+ }
82
+ class RuntimeSelector {
83
+ preferenceStore;
84
+ input;
85
+ output;
86
+ injectedReadKey;
87
+ constructor(options = {}) {
88
+ this.preferenceStore = options.preferenceStore ?? new runtime_preference_store_1.FileRuntimePreferenceStore();
89
+ this.input = options.input ?? process.stdin;
90
+ this.output = options.output ?? process.stdout;
91
+ this.injectedReadKey = options.readKey;
92
+ }
93
+ async select(input) {
94
+ if (input.requestedRuntime !== undefined) {
95
+ if (!RUNTIMES.includes(input.requestedRuntime)) {
96
+ throw new Error(`invalid runtime \`${String(input.requestedRuntime)}\``);
97
+ }
98
+ return {
99
+ ok: true,
100
+ runtime: input.requestedRuntime,
101
+ source: "explicit",
102
+ };
103
+ }
104
+ const preference = await this.preferenceStore.read();
105
+ if (preference.warning) {
106
+ this.output.write(`WARNING ${preference.warning}\n`);
107
+ }
108
+ const current = preference.runtime ?? DEFAULT_RUNTIME;
109
+ if (!input.interactive) {
110
+ return {
111
+ ok: true,
112
+ runtime: current,
113
+ source: preference.runtime === null ? "default" : "persisted",
114
+ };
115
+ }
116
+ const selected = await this.prompt(current);
117
+ if (selected === null)
118
+ return { ok: false, exitCode: 130 };
119
+ await this.preferenceStore.write(selected);
120
+ return { ok: true, runtime: selected, source: "interactive" };
121
+ }
122
+ async prompt(current) {
123
+ if (!this.injectedReadKey && !this.input) {
124
+ throw new Error("interactive runtime selection requires terminal input");
125
+ }
126
+ const wasFlowing = this.input?.readableFlowing === true;
127
+ const wasRaw = Boolean(this.input?.isRaw);
128
+ const keypress = this.injectedReadKey || !this.input
129
+ ? null
130
+ : createKeypressReader(this.input);
131
+ const readKey = this.injectedReadKey ?? keypress.read;
132
+ let changedRawMode = false;
133
+ let selectedIndex = Math.max(0, RUNTIMES.indexOf(current));
134
+ this.output.write("\nSelect runtime for this ALP session:\n");
135
+ this.output.write("\u001b[?25l");
136
+ try {
137
+ if (keypress &&
138
+ !wasRaw &&
139
+ typeof this.input?.setRawMode === "function") {
140
+ this.input.setRawMode(true);
141
+ changedRawMode = true;
142
+ }
143
+ if (keypress && typeof this.input?.resume === "function") {
144
+ this.input.resume();
145
+ }
146
+ renderMenu(this.output, current, selectedIndex, false);
147
+ for (;;) {
148
+ const key = await readKey();
149
+ if (key === "up") {
150
+ selectedIndex = (selectedIndex - 1 + RUNTIMES.length) % RUNTIMES.length;
151
+ renderMenu(this.output, current, selectedIndex, true);
152
+ }
153
+ else if (key === "down") {
154
+ selectedIndex = (selectedIndex + 1) % RUNTIMES.length;
155
+ renderMenu(this.output, current, selectedIndex, true);
156
+ }
157
+ else if (key === "enter") {
158
+ return RUNTIMES[selectedIndex];
159
+ }
160
+ else if (key === "cancel") {
161
+ return null;
162
+ }
163
+ }
164
+ }
165
+ finally {
166
+ keypress?.close();
167
+ if (keypress &&
168
+ !wasFlowing &&
169
+ typeof this.input?.pause === "function") {
170
+ this.input.pause();
171
+ }
172
+ if (changedRawMode)
173
+ this.input?.setRawMode?.(false);
174
+ this.output.write("\u001b[?25h");
175
+ }
176
+ }
177
+ }
178
+ exports.RuntimeSelector = RuntimeSelector;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveSpawnCommand = resolveSpawnCommand;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ /**
7
+ * Matches the launch line of a Windows batch shim: `"<node>" "<script>" %*`, anchored to
8
+ * one line so it cannot straddle the `SET "_prog=…"` line that precedes it in npm's shims.
9
+ */
10
+ const LAUNCH_LINE = /"(?:%_prog%|[^"\r\n]*?node(?:\.exe)?)"[ \t]+([^\r\n]+?)[ \t]+%\*[ \t]*$/im;
11
+ const ARGUMENT = /"([^"]*)"|(\S+)/g;
12
+ function locate(command, env) {
13
+ if ((0, node_path_1.isAbsolute)(command) || /[\\/]/.test(command))
14
+ return (0, node_fs_1.existsSync)(command) ? command : null;
15
+ for (const directory of (env.PATH ?? env.Path ?? "").split(node_path_1.delimiter).filter(Boolean)) {
16
+ const candidate = (0, node_path_1.join)(directory, command);
17
+ if ((0, node_fs_1.existsSync)(candidate))
18
+ return candidate;
19
+ }
20
+ return null;
21
+ }
22
+ function tokenize(launchArguments, directory) {
23
+ const tokens = [];
24
+ ARGUMENT.lastIndex = 0;
25
+ for (let match = ARGUMENT.exec(launchArguments); match; match = ARGUMENT.exec(launchArguments))
26
+ tokens.push((match[1] ?? match[2]).replace(/%~?dp0%?/gi, directory));
27
+ return tokens;
28
+ }
29
+ /**
30
+ * Node cannot spawn a Windows `.cmd`/`.bat` directly — it fails with EINVAL — and
31
+ * `shell: true` is not an option here: cmd.exe would re-parse the prompt argument, which
32
+ * carries spaces and quotes. Every such shim we launch (`claude.cmd`, `codex.cmd` from
33
+ * npm) wraps a Node script, so read the wrapper and spawn that script ourselves with
34
+ * argv passed through byte for byte.
35
+ *
36
+ * Anything else — a real `.exe`, any POSIX platform, a shim we cannot parse — is returned
37
+ * untouched, so the caller still gets the original error rather than a silent substitute.
38
+ */
39
+ function resolveSpawnCommand(command, args, env = process.env, platform = process.platform, nodeExecutable = process.execPath) {
40
+ if (platform !== "win32" || !/\.(?:cmd|bat)$/i.test(command))
41
+ return { command, args };
42
+ const file = locate(command, env);
43
+ if (file === null)
44
+ return { command, args };
45
+ let text;
46
+ try {
47
+ text = (0, node_fs_1.readFileSync)(file, "utf8");
48
+ }
49
+ catch {
50
+ return { command, args };
51
+ }
52
+ const launch = LAUNCH_LINE.exec(text);
53
+ if (launch === null)
54
+ return { command, args };
55
+ const tokens = tokenize(launch[1], (0, node_path_1.dirname)(file));
56
+ return tokens.length === 0 ? { command, args } : { command: nodeExecutable, args: [...tokens, ...args] };
57
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.stateHome = stateHome;
4
+ exports.memoryRoot = memoryRoot;
5
+ exports.agentsDirectory = agentsDirectory;
6
+ exports.hookForwarder = hookForwarder;
7
+ exports.executionsDirectory = executionsDirectory;
8
+ const node_os_1 = require("node:os");
9
+ const node_path_1 = require("node:path");
10
+ /**
11
+ * Nơi ALP giữ state cục bộ, phía TypeScript.
12
+ *
13
+ * Từ v0.9.0 thư mục cài là artifact dùng một lần — `npm i -g` xoá sạch package dir cũ, bản
14
+ * tarball giải nén sang `versions/<tag>` mới — nên không thứ gì của người dùng được nằm trong
15
+ * đó. Mọi thứ sống lâu hơn một version đều ở `~/.alp`.
16
+ *
17
+ * Cùng bộ luật này có một bản CommonJS ở `scripts/lib/install-paths.cjs` cho installer và các
18
+ * script bảo trì, vì chúng phải chạy được khi `dist/` chưa tồn tại hoặc đã hỏng.
19
+ * `test/cli/state-paths.test.ts` so hai bản với nhau để chúng không trôi khỏi nhau.
20
+ */
21
+ function stateHome(env = process.env) {
22
+ if (env.ALP_STATE_HOME)
23
+ return (0, node_path_1.resolve)(env.ALP_STATE_HOME);
24
+ const home = env.HOME || env.USERPROFILE || (0, node_os_1.homedir)();
25
+ if (!home)
26
+ throw new Error("không xác định được HOME/USERPROFILE");
27
+ return (0, node_path_1.join)(home, ".alp");
28
+ }
29
+ /** `ALP_MEMORY_ROOT` vẫn thắng: đó là đường duy nhất chạy được một ALP cô lập hoàn toàn. */
30
+ function memoryRoot(env = process.env) {
31
+ return env.ALP_MEMORY_ROOT ? (0, node_path_1.resolve)(env.ALP_MEMORY_ROOT) : (0, node_path_1.join)(stateHome(env), "memory");
32
+ }
33
+ /** `~/.alp/agents/<role>.md` — cache identity phẳng mà SessionStart hook đọc. */
34
+ function agentsDirectory(env = process.env) {
35
+ return (0, node_path_1.join)(stateHome(env), "agents");
36
+ }
37
+ /**
38
+ * Đường dẫn hook ổn định để ghi vào cấu hình project.
39
+ *
40
+ * File nhận đường dẫn này nằm trong repo của người dùng và sống lâu hơn mọi bản cài ALP.
41
+ * Trỏ thẳng vào thư mục cài là hẹn ngày hỏng câm: lên version, đổi channel, hay gỡ rồi cài
42
+ * lại chỗ khác đều làm nó chết mà phiên `claude` chỉ im lặng mất identity.
43
+ */
44
+ function hookForwarder(name, env = process.env) {
45
+ return (0, node_path_1.join)(stateHome(env), "hooks", `${name}.cjs`);
46
+ }
47
+ function executionsDirectory(env = process.env) {
48
+ return (0, node_path_1.join)(stateHome(env), "executions");
49
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineOutputContract = defineOutputContract;
4
+ exports.validateOutput = validateOutput;
5
+ const zod_1 = require("zod");
6
+ function formatPath(path) {
7
+ return path.length === 0 ? "output" : path.map(String).join(".");
8
+ }
9
+ function defineOutputContract(name, schema) {
10
+ return Object.freeze({
11
+ name,
12
+ schema: (0, zod_1.toJSONSchema)(schema),
13
+ validate(value) {
14
+ const result = schema.safeParse(value);
15
+ if (result.success) {
16
+ return { ok: true, value: result.data };
17
+ }
18
+ return {
19
+ ok: false,
20
+ issues: Object.freeze(result.error.issues.map((issue) => `${formatPath(issue.path)}: ${issue.message}`)),
21
+ };
22
+ },
23
+ });
24
+ }
25
+ function validateOutput(contract, value) {
26
+ return contract.validate(value);
27
+ }
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_OUTPUT_REPAIR_ATTEMPTS = void 0;
4
+ exports.canRepairOutput = canRepairOutput;
5
+ exports.MAX_OUTPUT_REPAIR_ATTEMPTS = 1;
6
+ function canRepairOutput(repairAttempts) {
7
+ return repairAttempts < exports.MAX_OUTPUT_REPAIR_ATTEMPTS;
8
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.defineLinearWorkflow = defineLinearWorkflow;
4
+ function defineLinearWorkflow(id, linearStates) {
5
+ if (linearStates.length === 0) {
6
+ throw new Error(`workflow \`${id}\` must declare at least one state`);
7
+ }
8
+ const states = {};
9
+ linearStates.forEach((state, index) => {
10
+ const next = linearStates[index + 1];
11
+ states[state.id] = {
12
+ allowedTools: [...state.allowedTools],
13
+ transitions: next === undefined ? [] : [next.id],
14
+ ...(next === undefined ? { terminal: true } : {}),
15
+ };
16
+ });
17
+ return {
18
+ id,
19
+ initial: linearStates[0].id,
20
+ states,
21
+ };
22
+ }
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WorkflowRunner = void 0;
4
+ const output_validator_1 = require("./output-validator");
5
+ const repair_policy_1 = require("./repair-policy");
6
+ function stateSnapshot(workflowId, currentState, status, repairAttempts) {
7
+ return Object.freeze({ workflowId, currentState, status, repairAttempts });
8
+ }
9
+ function validateDefinition(definition) {
10
+ if (!definition.states[definition.initial]) {
11
+ throw new Error(`workflow \`${definition.id}\` has unknown initial state \`${definition.initial}\``);
12
+ }
13
+ for (const [stateId, state] of Object.entries(definition.states)) {
14
+ if (state.terminal && state.transitions.length > 0) {
15
+ throw new Error(`terminal workflow state \`${stateId}\` cannot declare transitions`);
16
+ }
17
+ for (const target of state.transitions) {
18
+ if (!definition.states[target]) {
19
+ throw new Error(`workflow state \`${stateId}\` has unknown transition target \`${target}\``);
20
+ }
21
+ }
22
+ }
23
+ }
24
+ function assertStateBelongsToWorkflow(definition, state) {
25
+ if (state.workflowId !== definition.id) {
26
+ throw new Error(`workflow state belongs to \`${state.workflowId}\`, not \`${definition.id}\``);
27
+ }
28
+ if (!definition.states[state.currentState]) {
29
+ throw new Error(`workflow \`${definition.id}\` has no state \`${state.currentState}\``);
30
+ }
31
+ }
32
+ class WorkflowRunner {
33
+ initialize(definition) {
34
+ validateDefinition(definition);
35
+ const initial = definition.states[definition.initial];
36
+ return stateSnapshot(definition.id, definition.initial, initial.terminal ? "awaiting-output" : "running", 0);
37
+ }
38
+ transition(definition, state, next) {
39
+ assertStateBelongsToWorkflow(definition, state);
40
+ if (state.status !== "running") {
41
+ throw new Error(`cannot transition workflow in \`${state.status}\` status`);
42
+ }
43
+ const current = definition.states[state.currentState];
44
+ if (!current.transitions.includes(next)) {
45
+ throw new Error(`transition \`${state.currentState}\` -> \`${next}\` is not declared`);
46
+ }
47
+ const target = definition.states[next];
48
+ return stateSnapshot(definition.id, next, target.terminal ? "awaiting-output" : "running", state.repairAttempts);
49
+ }
50
+ isToolAllowed(definition, state, tool) {
51
+ assertStateBelongsToWorkflow(definition, state);
52
+ return (state.status === "running" &&
53
+ definition.states[state.currentState].allowedTools.includes(tool));
54
+ }
55
+ submitOutput(state, contract, value) {
56
+ if (state.status !== "awaiting-output" && state.status !== "repairing") {
57
+ throw new Error(`cannot submit output in \`${state.status}\` status`);
58
+ }
59
+ const validation = (0, output_validator_1.validateOutput)(contract, value);
60
+ if (validation.ok) {
61
+ return Object.freeze({
62
+ state: stateSnapshot(state.workflowId, state.currentState, "completed", state.repairAttempts),
63
+ validation,
64
+ });
65
+ }
66
+ const repairAllowed = (0, repair_policy_1.canRepairOutput)(state.repairAttempts);
67
+ return Object.freeze({
68
+ state: stateSnapshot(state.workflowId, state.currentState, repairAllowed ? "repairing" : "failed", repairAllowed ? state.repairAttempts + 1 : state.repairAttempts),
69
+ validation,
70
+ });
71
+ }
72
+ cancel(state) {
73
+ if (state.status !== "running" &&
74
+ state.status !== "awaiting-output" &&
75
+ state.status !== "repairing") {
76
+ throw new Error(`cannot cancel workflow in \`${state.status}\` status`);
77
+ }
78
+ return stateSnapshot(state.workflowId, state.currentState, "cancelled", state.repairAttempts);
79
+ }
80
+ }
81
+ exports.WorkflowRunner = WorkflowRunner;