@tenkara/afm-linux-arm64 4.0.0-alpha.7 → 4.0.0-alpha.9

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 (151) hide show
  1. package/bin/afm +2 -2
  2. package/content/CHANGELOG.md +50 -0
  3. package/content/PRINCIPLES.md +0 -2
  4. package/content/README-light.md +0 -2
  5. package/content/README.md +0 -2
  6. package/content/core/ach.md +0 -5
  7. package/content/core/afm.md +24 -6
  8. package/content/core/prd.md +0 -2
  9. package/content/core/protocol.md +1 -3
  10. package/content/hooks/README.md +0 -2
  11. package/content/interview/bootstrap.md +4 -2
  12. package/content/interview/promotion.md +0 -2
  13. package/content/interview/refactor.md +0 -2
  14. package/content/interview/triggers.md +0 -2
  15. package/content/modules/vireya-ui/ach.patch.md +1 -1
  16. package/content/ops/analyze.md +8 -7
  17. package/content/ops/deliver.md +23 -25
  18. package/content/ops/diagnose.md +1 -4
  19. package/content/ops/evolve.md +62 -13
  20. package/content/ops/export-template.md +1 -1
  21. package/content/ops/generalize.md +1 -4
  22. package/content/ops/policy.md +0 -2
  23. package/content/ops/reflect.md +19 -17
  24. package/content/ops/remediate.md +1 -4
  25. package/content/ops/restructure.md +3 -6
  26. package/content/ops/rule.md +0 -2
  27. package/content/ops/tasks.md +18 -18
  28. package/content/ops/update.md +29 -4
  29. package/content/ops/verify.md +0 -2
  30. package/content/questions/init.questions.yaml +13 -0
  31. package/content/roles/explorer.md +1 -1
  32. package/content/roles/reviewer.md +1 -1
  33. package/content/rubrics/failure-classification.md +0 -2
  34. package/content/rubrics/solid-triggers.md +0 -2
  35. package/content/skills/adr.md +3 -3
  36. package/content/skills/analyze.md +3 -9
  37. package/content/skills/clarify.md +2 -8
  38. package/content/skills/deliver.md +4 -20
  39. package/content/skills/diagnose.md +3 -11
  40. package/content/skills/evolve.md +6 -30
  41. package/content/skills/export-template.md +2 -5
  42. package/content/skills/generalize.md +6 -22
  43. package/content/skills/gotcha.md +3 -3
  44. package/content/skills/init.md +2 -7
  45. package/content/skills/plan.md +3 -9
  46. package/content/skills/policy.md +0 -6
  47. package/content/skills/promote.md +1 -3
  48. package/content/skills/protocol.md +2 -3
  49. package/content/skills/reconcile.md +2 -6
  50. package/content/skills/refactor.md +2 -7
  51. package/content/skills/reflect.md +7 -17
  52. package/content/skills/remediate.md +3 -9
  53. package/content/skills/restructure.md +8 -14
  54. package/content/skills/rule.md +3 -4
  55. package/content/skills/specify.md +3 -9
  56. package/content/skills/tasks.md +5 -15
  57. package/content/skills/update.md +6 -4
  58. package/content/skills/verify.md +1 -3
  59. package/content/templates/adr.template.md +1 -1
  60. package/content/templates/evolution-report.template.md +5 -2
  61. package/content/templates/feature-plan.template.md +0 -2
  62. package/content/templates/feature-research.template.md +1 -2
  63. package/content/templates/feature-spec.template.md +1 -1
  64. package/content/templates/feature-tasks.induced.template.md +58 -34
  65. package/content/templates/feature-tasks.template.md +56 -34
  66. package/content/templates/protocols/p1.md +1 -1
  67. package/content/templates/protocols/p2.md +1 -1
  68. package/content/templates/protocols/p3.md +1 -1
  69. package/package.json +1 -1
  70. package/plugin/agy/plugin.json +1 -1
  71. package/plugin/agy/skills/afm-adr/SKILL.md +7 -3
  72. package/plugin/agy/skills/afm-analyze/SKILL.md +5 -7
  73. package/plugin/agy/skills/afm-clarify/SKILL.md +4 -6
  74. package/plugin/agy/skills/afm-deliver/SKILL.md +6 -20
  75. package/plugin/agy/skills/afm-diagnose/SKILL.md +5 -9
  76. package/plugin/agy/skills/afm-evolve/SKILL.md +9 -29
  77. package/plugin/agy/skills/afm-export-template/SKILL.md +6 -5
  78. package/plugin/agy/skills/afm-generalize/SKILL.md +8 -20
  79. package/plugin/agy/skills/afm-gotcha/SKILL.md +7 -3
  80. package/plugin/agy/skills/afm-init/SKILL.md +3 -6
  81. package/plugin/agy/skills/afm-plan/SKILL.md +5 -7
  82. package/plugin/agy/skills/afm-policy/SKILL.md +2 -4
  83. package/plugin/agy/skills/afm-promote/SKILL.md +4 -2
  84. package/plugin/agy/skills/afm-protocol/SKILL.md +6 -3
  85. package/plugin/agy/skills/afm-reconcile/SKILL.md +5 -5
  86. package/plugin/agy/skills/afm-refactor/SKILL.md +3 -6
  87. package/plugin/agy/skills/afm-reflect/SKILL.md +9 -15
  88. package/plugin/agy/skills/afm-remediate/SKILL.md +5 -7
  89. package/plugin/agy/skills/afm-restructure/SKILL.md +10 -12
  90. package/plugin/agy/skills/afm-rule/SKILL.md +7 -4
  91. package/plugin/agy/skills/afm-specify/SKILL.md +5 -7
  92. package/plugin/agy/skills/afm-tasks/SKILL.md +7 -13
  93. package/plugin/agy/skills/afm-update/SKILL.md +10 -4
  94. package/plugin/agy/skills/afm-verify/SKILL.md +4 -2
  95. package/plugin/claude-code/.claude-plugin/plugin.json +1 -1
  96. package/plugin/claude-code/agents/afm-explorer.md +1 -1
  97. package/plugin/claude-code/agents/afm-reviewer.md +1 -1
  98. package/plugin/claude-code/skills/adr/SKILL.md +7 -3
  99. package/plugin/claude-code/skills/analyze/SKILL.md +5 -7
  100. package/plugin/claude-code/skills/clarify/SKILL.md +4 -6
  101. package/plugin/claude-code/skills/deliver/SKILL.md +6 -20
  102. package/plugin/claude-code/skills/diagnose/SKILL.md +5 -9
  103. package/plugin/claude-code/skills/evolve/SKILL.md +9 -29
  104. package/plugin/claude-code/skills/export-template/SKILL.md +6 -5
  105. package/plugin/claude-code/skills/generalize/SKILL.md +8 -20
  106. package/plugin/claude-code/skills/gotcha/SKILL.md +7 -3
  107. package/plugin/claude-code/skills/init/SKILL.md +3 -6
  108. package/plugin/claude-code/skills/plan/SKILL.md +5 -7
  109. package/plugin/claude-code/skills/policy/SKILL.md +2 -4
  110. package/plugin/claude-code/skills/promote/SKILL.md +4 -2
  111. package/plugin/claude-code/skills/protocol/SKILL.md +6 -3
  112. package/plugin/claude-code/skills/reconcile/SKILL.md +5 -5
  113. package/plugin/claude-code/skills/refactor/SKILL.md +3 -6
  114. package/plugin/claude-code/skills/reflect/SKILL.md +9 -15
  115. package/plugin/claude-code/skills/remediate/SKILL.md +5 -7
  116. package/plugin/claude-code/skills/restructure/SKILL.md +10 -12
  117. package/plugin/claude-code/skills/rule/SKILL.md +7 -4
  118. package/plugin/claude-code/skills/specify/SKILL.md +5 -7
  119. package/plugin/claude-code/skills/tasks/SKILL.md +7 -13
  120. package/plugin/claude-code/skills/update/SKILL.md +10 -4
  121. package/plugin/claude-code/skills/verify/SKILL.md +4 -2
  122. package/plugin/opencode/afm/index.js +245 -0
  123. package/plugin/opencode/afm/tui.js +21 -0
  124. package/plugin/opencode/agents/afm-executor.md +22 -14
  125. package/plugin/opencode/agents/afm-explorer.md +73 -28
  126. package/plugin/opencode/agents/afm-reviewer.md +73 -28
  127. package/plugin/opencode/skills/afm-adr/SKILL.md +7 -3
  128. package/plugin/opencode/skills/afm-analyze/SKILL.md +5 -7
  129. package/plugin/opencode/skills/afm-clarify/SKILL.md +4 -6
  130. package/plugin/opencode/skills/afm-deliver/SKILL.md +6 -20
  131. package/plugin/opencode/skills/afm-diagnose/SKILL.md +5 -9
  132. package/plugin/opencode/skills/afm-evolve/SKILL.md +9 -29
  133. package/plugin/opencode/skills/afm-export-template/SKILL.md +6 -5
  134. package/plugin/opencode/skills/afm-generalize/SKILL.md +8 -20
  135. package/plugin/opencode/skills/afm-gotcha/SKILL.md +7 -3
  136. package/plugin/opencode/skills/afm-init/SKILL.md +3 -6
  137. package/plugin/opencode/skills/afm-plan/SKILL.md +5 -7
  138. package/plugin/opencode/skills/afm-policy/SKILL.md +2 -4
  139. package/plugin/opencode/skills/afm-promote/SKILL.md +4 -2
  140. package/plugin/opencode/skills/afm-protocol/SKILL.md +6 -3
  141. package/plugin/opencode/skills/afm-reconcile/SKILL.md +5 -5
  142. package/plugin/opencode/skills/afm-refactor/SKILL.md +3 -6
  143. package/plugin/opencode/skills/afm-reflect/SKILL.md +9 -15
  144. package/plugin/opencode/skills/afm-remediate/SKILL.md +5 -7
  145. package/plugin/opencode/skills/afm-restructure/SKILL.md +10 -12
  146. package/plugin/opencode/skills/afm-rule/SKILL.md +7 -4
  147. package/plugin/opencode/skills/afm-specify/SKILL.md +5 -7
  148. package/plugin/opencode/skills/afm-tasks/SKILL.md +7 -13
  149. package/plugin/opencode/skills/afm-update/SKILL.md +10 -4
  150. package/plugin/opencode/skills/afm-verify/SKILL.md +4 -2
  151. package/plugin/opencode/afm.js +0 -196
@@ -9,11 +9,11 @@ description: |
9
9
 
10
10
  > **Questions in this host.** `afm ask <op>` prompts only in a terminal, and your shell tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with the host's question tool when it has one, and otherwise in chat as a numbered list: the `prompt`, then the `options` with their descriptions, the recommended one first and marked. Wait for the answer. Then record each choice with `afm answer <op> <id> <label>` (one label per selected option). Run `afm ask <op>` again: `all questions answered` means the op may go on.
11
11
 
12
- An atomic skill. The procedure lives in the op: read it with `afm path ops/specify.md`, end to end, and follow it literally.
12
+ > **Procedure.** Run `afm read ops/specify.md` and follow it; read an on-demand section when its condition holds.
13
13
 
14
14
  ## When NOT to activate
15
15
 
16
- A trivial change (no feature folder needed) goes straight to the `afm.md § 2` loop. A full end-to-end delivery is `deliver`, which composes this op.
16
+ A trivial change (no feature folder needed) goes straight to the `afm.md § 2` loop. A full end-to-end delivery is `afm-deliver`, which composes this op.
17
17
 
18
18
  ## Pre-check
19
19
 
@@ -25,12 +25,10 @@ The number comes from `afm next-id feature`, never by hand. What is left to you
25
25
 
26
26
  ## Invariant principles
27
27
 
28
- Single canonical source: `afm path PRINCIPLES.md`. Universals §1-8 apply. In particular #2 (discover-first: compute the heuristic and the US from the scan), #5 (never invent a success criterion) and #6 (autonomy-first).
28
+ Most relevant here: #2 (discover-first: compute the heuristic and the US from the scan), #5 (never invent a success criterion) and #6 (autonomy-first).
29
29
 
30
30
  ## Continuous doctrine
31
31
 
32
- Passive mode is the always-on `reconcile` skill. This skill covers only the opening of the feature.
32
+ Passive mode (`afm-reconcile`) is always on. This skill covers only the opening of the feature.
33
33
 
34
- ## Version
35
-
36
- `afm version`. History: `afm path CHANGELOG.md`.
34
+ **Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
@@ -1,34 +1,28 @@
1
1
  ---
2
2
  name: afm-tasks
3
3
  description: |
4
- Generates `tasks.md` in TDD order, and marks the parallel tasks with `[P]`. Every task references a user story or a requirement. It hard-blocks on an open `[NEEDS CLARIFICATION:]` in the plan.
4
+ Generates the `tasks:` list of `tasks.md` in TDD order with `afm task add`. Every task references a user story or a requirement. It hard-blocks on an open `[NEEDS CLARIFICATION:]` in the plan.
5
5
  Activates: `afm-tasks [<feature>]`, "generate the tasks", "break the feature into tasks".
6
6
  ---
7
7
 
8
8
  # AFM — Tasks
9
9
 
10
- An atomic skill. The procedure lives in the op: read it with `afm path ops/tasks.md`, end to end, and follow it literally.
10
+ > **Procedure.** Run `afm read ops/tasks.md` and follow it; read an on-demand section when its condition holds.
11
11
 
12
12
  ## When NOT to activate
13
13
 
14
- There is no `plan.md` yet: run `afm-plan` first. An end-to-end delivery is `deliver`, which composes this op.
14
+ There is no `plan.md` yet: run `afm-plan` first. An end-to-end delivery is `afm-deliver`, which composes this op.
15
15
 
16
16
  ## Pre-check
17
17
 
18
18
  Run `afm preflight` (exit 3: stop and suggest `afm-init` or `afm-refactor`). Run `afm feature status <NNN> --json`: a non-empty `blockers` is an open `[NEEDS CLARIFICATION:]` in the plan. That is a hard refusal with no override, not a question.
19
19
 
20
- ## Procedure
20
+ ## Principles
21
21
 
22
- Break the plan into four phases (Setup, Foundation RED/GREEN, Boundary, Reconciliation) from the generator (`afm new feature-tasks --feature NNN-slug`). Mark `[P]` only where a task does not depend on the one before it; a GREEN never is. Give every task a `US-NNN` or `RF-NN`. Then run `afm feature lint <NNN>` and fix what it reports until it exits 0. The last task closes the `status:` keys. Autonomy-first: write and report.
23
-
24
- ## Invariant principles
25
-
26
- Single canonical source: `afm path PRINCIPLES.md`. Universals §1-8 apply. In particular #5 (every task traces to a US or RF) and rule 1 (TDD: no GREEN without the RED before it).
22
+ Most relevant here: #5 (every task traces to a US or RF) and rule 1 (TDD: no green task without the red task it closes).
27
23
 
28
24
  ## Continuous doctrine
29
25
 
30
- Passive mode is the always-on `reconcile` skill. This skill covers only the task list.
31
-
32
- ## Version
26
+ Passive mode (`afm-reconcile`) is always on. This skill covers only the task list.
33
27
 
34
- `afm version`. History: `afm path CHANGELOG.md`.
28
+ **Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
@@ -9,8 +9,9 @@ description: |
9
9
 
10
10
  > **Questions in this host.** `afm ask <op>` prompts only in a terminal, and your shell tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with the host's question tool when it has one, and otherwise in chat as a numbered list: the `prompt`, then the `options` with their descriptions, the recommended one first and marked. Wait for the answer. Then record each choice with `afm answer <op> <id> <label>` (one label per selected option). Run `afm ask <op>` again: `all questions answered` means the op may go on.
11
11
 
12
- This is the sync skill of `afm`. The canonical procedure is the op `afm path ops/update`. The CLI runs the
13
- codemod registry, proves each op and moves the pin. This skill keeps the judgment.
12
+ > **Procedure.** Run `afm read ops/update.md` and follow it; read an on-demand section when its condition holds.
13
+
14
+ The CLI runs the codemod registry, proves each op and moves the pin. This skill keeps the judgment.
14
15
 
15
16
  > **Carve-out (does not overlap existing ops):** `afm-init` and `afm-refactor` **create** the instance
16
17
  > (bootstrap). `update` brings an **existing** instance forward, the path that `afm-refactor` refuses.
@@ -44,15 +45,20 @@ the run. A pin **ahead** of the binary is an anomaly: report it, never roll it b
44
45
  4. **Language migration** (when the instance is not English): read `prd.md` § 11.1 FIRST and derive the
45
46
  excluded scope from it, and only then read a file. `[TBD]` there stops the step and asks
46
47
  `afm ask update-strings`. It never picks a language. The artifacts question is
47
- `afm ask update-artifacts`.
48
+ `afm ask update-artifacts`. On migrate, run the rewrite as one orchestrated batch through
49
+ parallel subagents through the `task` tool, with a light model per agent (the smallest model of the configured provider) for the rewrite and the loss
50
+ check. Escalate only a flagged file to a capable model. The session model keeps the excluded scope and
51
+ the final gate.
48
52
  5. Post-conditions: `afm index --check` returns 0 and the plugin boundary stays clean. Report in chat what
49
53
  migrated and what stayed pending. Under `afm-evolve`, feed it to `docs/evolution/`.
50
54
 
51
55
  ## Invariant principles
52
56
 
53
- Single canonical source: `afm path PRINCIPLES`. Universals §1-8 apply. Follow #2 (discover-first): the proof of
57
+ Most relevant here: #2 (discover-first): the proof of
54
58
  need is the grep that shows what is missing. Follow #4/#5: materialize only what the plugin already provides
55
59
  ready-made, and never invent missing product content. Follow #6 (autonomy-first with a gate). Bright line
56
60
  #7 applies: only **read** the plugin, and write only to the instance.
57
61
 
58
62
  **No new principle here.** `update` is composed materialization. The doctrine lives in the op file.
63
+
64
+ **Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
@@ -9,7 +9,7 @@ description: |
9
9
 
10
10
  > **Questions in this host.** `afm ask <op>` prompts only in a terminal, and your shell tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with the host's question tool when it has one, and otherwise in chat as a numbered list: the `prompt`, then the `options` with their descriptions, the recommended one first and marked. Wait for the answer. Then record each choice with `afm answer <op> <id> <label>` (one label per selected option). Run `afm ask <op>` again: `all questions answered` means the op may go on.
11
11
 
12
- Atomic skill of `afm`. The canonical procedure is the op `afm path ops/verify`.
12
+ > **Procedure.** Run `afm read ops/verify.md` and follow it; read an on-demand section when its condition holds.
13
13
 
14
14
  ## When not to activate
15
15
 
@@ -30,4 +30,6 @@ agent actor. Report the tier that resulted and what it means.
30
30
 
31
31
  ## Invariant principles
32
32
 
33
- Single canonical source: `afm path PRINCIPLES`. Universals §1-8 apply. #5 is load-bearing here.
33
+ Most relevant here: #5 is load-bearing.
34
+
35
+ **Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
@@ -1,196 +0,0 @@
1
- // AFM for opencode: forwards what the hooks decide on to `afm hook <Event>` with the native tool
2
- // name, the harness marker and the session, and carries the answer back: a deny throws, injected
3
- // context rides in the tool's output or the system prompt. A hook that cannot answer never blocks
4
- // the tool. All decisions live in the afm CLI; this file only translates opencode's plugin API.
5
- import { spawn } from "node:child_process";
6
-
7
- const MARKER = "opencode";
8
- const TIMEOUT_MS = 20000;
9
- const LOOKUP_TIMEOUT_MS = 2000;
10
- // The tools whose calls AFM decides on or records; every other call is not worth a process.
11
- const FORWARDED = new Set(["write", "edit", "multiedit", "bash", "skill"]);
12
-
13
- function afmHook(event, payload) {
14
- return new Promise((resolve) => {
15
- let out = "";
16
- const child = spawn("afm", ["hook", event], {
17
- stdio: ["pipe", "pipe", "ignore"],
18
- });
19
- const timer = setTimeout(() => {
20
- child.kill();
21
- resolve("");
22
- }, TIMEOUT_MS);
23
- child.stdout.on("data", (chunk) => {
24
- out += chunk;
25
- });
26
- child.on("error", () => {
27
- clearTimeout(timer);
28
- resolve("");
29
- });
30
- child.on("close", () => {
31
- clearTimeout(timer);
32
- resolve(out);
33
- });
34
- child.stdin.on("error", () => {});
35
- child.stdin.end(JSON.stringify(payload));
36
- });
37
- }
38
-
39
- /** The `hookSpecificOutput` of an `afm hook` answer; empty when there is none or it is not JSON. */
40
- function decisionOf(out) {
41
- try {
42
- return JSON.parse(out).hookSpecificOutput ?? {};
43
- } catch {
44
- return {};
45
- }
46
- }
47
-
48
- /** The `systemMessage` of an `afm hook` answer: what the operator must read, not the model; undefined when there is none. */
49
- function noticeOf(out) {
50
- try {
51
- const notice = JSON.parse(out).systemMessage;
52
- return typeof notice === "string" && notice !== "" ? notice : undefined;
53
- } catch {
54
- return undefined;
55
- }
56
- }
57
-
58
- /** Puts the notice on the operator's screen as a toast. The host may lack the API, and a notice is never worth a failure. */
59
- async function showNotice(client, message) {
60
- try {
61
- await client?.tui?.showToast?.({
62
- body: { title: "AFM", message, variant: "warning", duration: 20000 },
63
- });
64
- } catch {
65
- // nowhere to show it: the next session start tries again
66
- }
67
- }
68
-
69
- const bounded = (promise) => {
70
- let timer;
71
- const limit = new Promise((resolve) => {
72
- timer = setTimeout(resolve, LOOKUP_TIMEOUT_MS, undefined);
73
- });
74
- return Promise.race([promise.catch(() => undefined), limit]).finally(() =>
75
- clearTimeout(timer),
76
- );
77
- };
78
-
79
- /**
80
- * What the host says of a session: whether it is a sub-agent's (it has a parent) and the agent its
81
- * last user message names (a sub-agent session starts with the task prompt addressed to it). The
82
- * lookups are bounded and their failure is not an error: the call is then treated as the main
83
- * thread's. Only an answered lookup is remembered, so a failed one is tried again.
84
- */
85
- async function sessionOf(client, sessionID, cache) {
86
- if (typeof sessionID !== "string") return { child: false };
87
- const known = cache.get(sessionID);
88
- if (known) return known;
89
- const [info, messages] = await Promise.all([
90
- bounded(
91
- Promise.resolve(client?.session?.get?.({ path: { id: sessionID } })),
92
- ),
93
- bounded(
94
- Promise.resolve(client?.session?.messages?.({ path: { id: sessionID } })),
95
- ),
96
- ]);
97
- const users = (messages?.data ?? []).filter(
98
- (message) => message?.info?.role === "user",
99
- );
100
- const agent = users.at(-1)?.info?.agent;
101
- const session = {
102
- child: typeof info?.data?.parentID === "string",
103
- ...(typeof agent === "string" ? { agent } : {}),
104
- };
105
- if (info?.data) cache.set(sessionID, session);
106
- return session;
107
- }
108
-
109
- export const AfmHooks = async ({ directory, client }) => {
110
- const sessions = new Map();
111
- const starts = new Map();
112
- const pending = new Map();
113
- const base = (sessionID, session) => ({
114
- harness: MARKER,
115
- session_id: sessionID,
116
- cwd: directory,
117
- ...(session?.child ? { agent_id: sessionID } : {}),
118
- });
119
- // The digest a session starts with: the main thread's, or the sub-agent's rules. Asked once.
120
- const startOf = (sessionID) => {
121
- if (typeof sessionID !== "string") return Promise.resolve(null);
122
- if (!starts.has(sessionID))
123
- starts.set(
124
- sessionID,
125
- (async () => {
126
- const session = await sessionOf(client, sessionID, sessions);
127
- if (!session.child) {
128
- const out = await afmHook("SessionStart", {
129
- ...base(sessionID, session),
130
- source: "startup",
131
- });
132
- const notice = noticeOf(out);
133
- if (notice) await showNotice(client, notice);
134
- return decisionOf(out).additionalContext;
135
- }
136
- if (!session.agent) return undefined;
137
- return decisionOf(
138
- await afmHook("SubagentStart", {
139
- ...base(sessionID, session),
140
- agent_type: session.agent,
141
- }),
142
- ).additionalContext;
143
- })(),
144
- );
145
- return starts.get(sessionID);
146
- };
147
- return {
148
- "experimental.chat.system.transform": async (input, output) => {
149
- const text = await startOf(input.sessionID);
150
- if (text) output.system.push(text);
151
- },
152
- "tool.execute.before": async (input, output) => {
153
- if (!FORWARDED.has(input.tool)) return;
154
- const session = await sessionOf(client, input.sessionID, sessions);
155
- const decision = decisionOf(
156
- await afmHook("PreToolUse", {
157
- ...base(input.sessionID, session),
158
- tool_name: input.tool,
159
- tool_input: output.args ?? {},
160
- }),
161
- );
162
- if (decision.permissionDecision === "deny")
163
- throw new Error(decision.permissionDecisionReason);
164
- // A PreToolUse cannot speak to the agent before the tool runs: its context joins the output.
165
- if (decision.additionalContext && input.callID)
166
- pending.set(input.callID, decision.additionalContext);
167
- },
168
- "tool.execute.after": async (input, output) => {
169
- if (!FORWARDED.has(input.tool)) return;
170
- const session = await sessionOf(client, input.sessionID, sessions);
171
- const exit = output.metadata?.exit;
172
- const failed =
173
- input.tool === "bash" && typeof exit === "number" && exit !== 0;
174
- const decision = decisionOf(
175
- await afmHook(failed ? "PostToolUseFailure" : "PostToolUse", {
176
- ...base(input.sessionID, session),
177
- tool_name: input.tool,
178
- tool_input: input.args ?? {},
179
- ...(failed ? { exit, error: output.output ?? "" } : {}),
180
- }),
181
- );
182
- const context = [pending.get(input.callID), decision.additionalContext]
183
- .filter(Boolean)
184
- .join("\n\n");
185
- pending.delete(input.callID);
186
- if (context) output.output = `${output.output ?? ""}\n\n${context}`;
187
- },
188
- // opencode cannot hold a session open, so a Stop only records the handoff.
189
- event: async ({ event }) => {
190
- if (event?.type !== "session.idle") return;
191
- const sessionID = event.properties?.sessionID;
192
- const session = await sessionOf(client, sessionID, sessions);
193
- if (!session.child) await afmHook("Stop", base(sessionID, session));
194
- },
195
- };
196
- };