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

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 (63) hide show
  1. package/bin/afm +2 -2
  2. package/content/CHANGELOG.md +28 -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 +23 -5
  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 +1 -4
  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 +1 -4
  24. package/content/ops/remediate.md +1 -4
  25. package/content/ops/restructure.md +1 -4
  26. package/content/ops/rule.md +0 -2
  27. package/content/ops/tasks.md +18 -18
  28. package/content/ops/update.md +3 -2
  29. package/content/ops/verify.md +0 -2
  30. package/content/questions/init.questions.yaml +13 -0
  31. package/content/rubrics/failure-classification.md +0 -2
  32. package/content/rubrics/solid-triggers.md +0 -2
  33. package/content/skills/analyze.md +1 -1
  34. package/content/skills/deliver.md +2 -2
  35. package/content/skills/tasks.md +3 -3
  36. package/content/templates/adr.template.md +1 -1
  37. package/content/templates/evolution-report.template.md +0 -2
  38. package/content/templates/feature-plan.template.md +0 -2
  39. package/content/templates/feature-research.template.md +1 -2
  40. package/content/templates/feature-spec.template.md +1 -1
  41. package/content/templates/feature-tasks.induced.template.md +58 -34
  42. package/content/templates/feature-tasks.template.md +56 -34
  43. package/content/templates/protocols/p1.md +1 -1
  44. package/content/templates/protocols/p2.md +1 -1
  45. package/content/templates/protocols/p3.md +1 -1
  46. package/package.json +1 -1
  47. package/plugin/agy/plugin.json +1 -1
  48. package/plugin/agy/skills/afm-analyze/SKILL.md +1 -1
  49. package/plugin/agy/skills/afm-deliver/SKILL.md +2 -2
  50. package/plugin/agy/skills/afm-tasks/SKILL.md +3 -3
  51. package/plugin/claude-code/.claude-plugin/plugin.json +1 -1
  52. package/plugin/claude-code/skills/analyze/SKILL.md +1 -1
  53. package/plugin/claude-code/skills/deliver/SKILL.md +2 -2
  54. package/plugin/claude-code/skills/tasks/SKILL.md +3 -3
  55. package/plugin/opencode/afm/index.js +245 -0
  56. package/plugin/opencode/afm/tui.js +21 -0
  57. package/plugin/opencode/agents/afm-executor.md +22 -14
  58. package/plugin/opencode/agents/afm-explorer.md +64 -28
  59. package/plugin/opencode/agents/afm-reviewer.md +64 -28
  60. package/plugin/opencode/skills/afm-analyze/SKILL.md +1 -1
  61. package/plugin/opencode/skills/afm-deliver/SKILL.md +2 -2
  62. package/plugin/opencode/skills/afm-tasks/SKILL.md +3 -3
  63. package/plugin/opencode/afm.js +0 -196
@@ -21,7 +21,7 @@ Run `afm preflight` (exit 3: no `docs/.afm-version`, stop and suggest `afm-init`
21
21
 
22
22
  ## Procedure
23
23
 
24
- Start from the mechanical findings: `afm feature lint <NNN> --json` covers the passes A, C, D, E and the structure of `tasks.md`. Add the judgment on top (constitution violations, duplication, the semantic side of inconsistency and ambiguity), build the coverage table, assign severities by the matrix, and report in chat with metrics. Close with the `remediation` question (`afm ask analyze`). Gating mode sorts findings into buckets instead of reporting.
24
+ Start from the mechanical findings: `afm feature lint <NNN> --json` covers the passes A, C, D, E and the structure of `tasks.md`. Add the judgment on top (constitution violations, duplication, the semantic side of inconsistency and ambiguity), build the coverage table, assign severities by the matrix, and report in chat with metrics. Close with the `remediation` question (`afm ask analyze`); a remediation that adds a task runs `afm task add`, never a hand edit of the `tasks:` list, and a task is closed only with `afm task done`. Gating mode sorts findings into buckets instead of reporting.
25
25
 
26
26
  ## Invariant principles
27
27
 
@@ -26,10 +26,10 @@ The CLI holds the state; the skill keeps only the judgment. Drive the loop with
26
26
  - **Discovery:** scan scoped to the feature, external unknowns through the nasca research flow (`nasca-research`, or `nasca-campaign` for a deep study; ask the operator if nasca is missing), unknowns sorted into resolved-by-scan, researchable and irreducible. One batched, recommendation-first round, capped by the protocol (`afm-protocol`).
27
27
  - **Draft:** spec, plan and tasks, verified with `afm feature lint <NNN>` plus the judgment passes of `analyze`.
28
28
  - **Gate:** the earned questions plus the `outcome` question (`afm ask deliver`), then `afm approve <NNN>`. An explicit "go" skips the questions, never the record.
29
- - **Execute:** each task is one `core/afm.md` § 2 cycle, checked with `afm gate --staged`, then `afm task done <NNN> <Txxx>`. Halt on a non-`[P]` failure after the capped retry.
29
+ - **Execute:** the tasks of the `tasks:` list run in order (`core/afm.md` § 2: a red task, the green task that closes it, an optional refactor), each checked with `afm gate --staged`, then closed with `afm task done <NNN> <Txxx>`. Tasks are added with `afm task add`, never by hand. Halt on the failure of a task that is not `parallel` after the capped retry.
30
30
  - **Reconcile:** § 8.5 plus a passive sweep.
31
31
 
32
- If artifacts already exist, resume from the correct phase, including "tasks already exist, run from the first `[ ]`".
32
+ If artifacts already exist, resume from the correct phase, including "tasks already exist, run from the first task that is not done".
33
33
 
34
34
  ## Invariant principles
35
35
 
@@ -1,7 +1,7 @@
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
 
@@ -19,11 +19,11 @@ Run `afm preflight` (exit 3: stop and suggest `afm-init` or `afm-refactor`). Run
19
19
 
20
20
  ## Procedure
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.
22
+ Create the file with the generator (`afm new feature-tasks --feature NNN-slug`), then add every task with `afm task add <NNN> --kind <k> --text <t>`: setup tasks, then for each spec scenario a red task (`--scenario N`) and the green task that closes it (`--closes Txxx`), an optional refactor, the validation task of each plan boundary (`--validates <boundary name>`), and one reconcile task, last. Never write a task by hand: the tasks live in the frontmatter `tasks:` list, and the CLI assigns the id and validates the item. Mark `--parallel` 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` (`--refs`). The CLI refuses an item that breaks a lint rule. Then run `afm feature lint <NNN>` and fix what it reports until it exits 0: a missing task with `afm task add`, a wrong field or kind with `afm task set <NNN> <Txxx> --<field> <value>` (`--clear <field>` clears one), a task that should not exist with `afm task remove <NNN> <Txxx>`. Never edit the YAML by hand. The reconcile task closes the `status:` keys; close each task with `afm task done`. Autonomy-first: write and report.
23
23
 
24
24
  ## Invariant principles
25
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).
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 task without the red task it closes).
27
27
 
28
28
  ## Continuous doctrine
29
29
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "afm",
3
3
  "description": "Agent Flow Methodology: autonomous discovery, a strict TDD delivery loop, and documentation that evolves from measured evidence. The CLI owns state, ids, gates and lint; the skills carry the judgment.",
4
- "version": "4.0.0-alpha.7",
4
+ "version": "4.0.0-alpha.8",
5
5
  "author": {
6
6
  "name": "Mapree"
7
7
  },
@@ -21,7 +21,7 @@ Run `afm preflight` (exit 3: no `docs/.afm-version`, stop and suggest `/afm:init
21
21
 
22
22
  ## Procedure
23
23
 
24
- Start from the mechanical findings: `afm feature lint <NNN> --json` covers the passes A, C, D, E and the structure of `tasks.md`. Add the judgment on top (constitution violations, duplication, the semantic side of inconsistency and ambiguity), build the coverage table, assign severities by the matrix, and report in chat with metrics. Close with the `remediation` question (`afm ask analyze`). Gating mode sorts findings into buckets instead of reporting.
24
+ Start from the mechanical findings: `afm feature lint <NNN> --json` covers the passes A, C, D, E and the structure of `tasks.md`. Add the judgment on top (constitution violations, duplication, the semantic side of inconsistency and ambiguity), build the coverage table, assign severities by the matrix, and report in chat with metrics. Close with the `remediation` question (`afm ask analyze`); a remediation that adds a task runs `afm task add`, never a hand edit of the `tasks:` list, and a task is closed only with `afm task done`. Gating mode sorts findings into buckets instead of reporting.
25
25
 
26
26
  ## Invariant principles
27
27
 
@@ -26,10 +26,10 @@ The CLI holds the state; the skill keeps only the judgment. Drive the loop with
26
26
  - **Discovery:** scan scoped to the feature, external unknowns through the nasca research flow (`nasca-research`, or `nasca-campaign` for a deep study; ask the operator if nasca is missing), unknowns sorted into resolved-by-scan, researchable and irreducible. One batched, recommendation-first round, capped by the protocol (`/afm:protocol`).
27
27
  - **Draft:** spec, plan and tasks, verified with `afm feature lint <NNN>` plus the judgment passes of `analyze`.
28
28
  - **Gate:** the earned questions plus the `outcome` question (`afm ask deliver`), then `afm approve <NNN>`. An explicit "go" skips the questions, never the record.
29
- - **Execute:** each task is one `core/afm.md` § 2 cycle, checked with `afm gate --staged`, then `afm task done <NNN> <Txxx>`. Halt on a non-`[P]` failure after the capped retry.
29
+ - **Execute:** the tasks of the `tasks:` list run in order (`core/afm.md` § 2: a red task, the green task that closes it, an optional refactor), each checked with `afm gate --staged`, then closed with `afm task done <NNN> <Txxx>`. Tasks are added with `afm task add`, never by hand. Halt on the failure of a task that is not `parallel` after the capped retry.
30
30
  - **Reconcile:** § 8.5 plus a passive sweep.
31
31
 
32
- If artifacts already exist, resume from the correct phase, including "tasks already exist, run from the first `[ ]`".
32
+ If artifacts already exist, resume from the correct phase, including "tasks already exist, run from the first task that is not done".
33
33
 
34
34
  ## Invariant principles
35
35
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: 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
 
@@ -19,11 +19,11 @@ Run `afm preflight` (exit 3: stop and suggest `/afm:init` or `/afm:refactor`). R
19
19
 
20
20
  ## Procedure
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.
22
+ Create the file with the generator (`afm new feature-tasks --feature NNN-slug`), then add every task with `afm task add <NNN> --kind <k> --text <t>`: setup tasks, then for each spec scenario a red task (`--scenario N`) and the green task that closes it (`--closes Txxx`), an optional refactor, the validation task of each plan boundary (`--validates <boundary name>`), and one reconcile task, last. Never write a task by hand: the tasks live in the frontmatter `tasks:` list, and the CLI assigns the id and validates the item. Mark `--parallel` 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` (`--refs`). The CLI refuses an item that breaks a lint rule. Then run `afm feature lint <NNN>` and fix what it reports until it exits 0: a missing task with `afm task add`, a wrong field or kind with `afm task set <NNN> <Txxx> --<field> <value>` (`--clear <field>` clears one), a task that should not exist with `afm task remove <NNN> <Txxx>`. Never edit the YAML by hand. The reconcile task closes the `status:` keys; close each task with `afm task done`. Autonomy-first: write and report.
23
23
 
24
24
  ## Invariant principles
25
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).
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 task without the red task it closes).
27
27
 
28
28
  ## Continuous doctrine
29
29
 
@@ -0,0 +1,245 @@
1
+ // AFM for opencode 2, server side: forwards what the hooks decide on to `afm hook <Event>` with the
2
+ // native tool name, the harness marker and the session, and carries the answer back: a deny throws,
3
+ // injected context rides in the tool's result or the system prompt, and a notice for the operator
4
+ // goes to the terminal client (tui.js) as the `notice` event of the afm RPC. A hook that cannot
5
+ // answer never blocks the tool. All decisions live in the afm CLI; this file only translates
6
+ // opencode's plugin API.
7
+ import { spawn } from "node:child_process";
8
+
9
+ const MARKER = "opencode";
10
+ const TIMEOUT_MS = 20000;
11
+ const LOOKUP_TIMEOUT_MS = 2000;
12
+ // The tools whose calls AFM decides on or records; every other call is not worth a process.
13
+ const FORWARDED = new Set(["write", "edit", "shell", "skill"]);
14
+ // The events that end a turn: opencode cannot hold a session open, so each one only records the handoff.
15
+ const ENDED = new Set([
16
+ "session.execution.succeeded",
17
+ "session.execution.failed",
18
+ "session.execution.interrupted",
19
+ ]);
20
+
21
+ function afmHook(event, payload) {
22
+ return new Promise((resolve) => {
23
+ let out = "";
24
+ const child = spawn("afm", ["hook", event], {
25
+ stdio: ["pipe", "pipe", "ignore"],
26
+ });
27
+ const timer = setTimeout(() => {
28
+ child.kill();
29
+ resolve("");
30
+ }, TIMEOUT_MS);
31
+ child.stdout.on("data", (chunk) => {
32
+ out += chunk;
33
+ });
34
+ child.on("error", () => {
35
+ clearTimeout(timer);
36
+ resolve("");
37
+ });
38
+ child.on("close", () => {
39
+ clearTimeout(timer);
40
+ resolve(out);
41
+ });
42
+ child.stdin.on("error", () => {});
43
+ child.stdin.end(JSON.stringify(payload));
44
+ });
45
+ }
46
+
47
+ /** The `hookSpecificOutput` of an `afm hook` answer; empty when there is none or it is not JSON. */
48
+ function decisionOf(out) {
49
+ try {
50
+ return JSON.parse(out).hookSpecificOutput ?? {};
51
+ } catch {
52
+ return {};
53
+ }
54
+ }
55
+
56
+ /** The `systemMessage` of an `afm hook` answer: what the operator must read, not the model; undefined when there is none. */
57
+ function noticeOf(out) {
58
+ try {
59
+ const notice = JSON.parse(out).systemMessage;
60
+ return typeof notice === "string" && notice !== "" ? notice : undefined;
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ }
65
+
66
+ /** The afm RPC: one event, the notice the terminal client shows as a toast. Plain JSON Schema, no Effect. */
67
+ const RPC = {
68
+ id: "afm",
69
+ methods: {},
70
+ events: {
71
+ notice: {
72
+ schema: {
73
+ type: "object",
74
+ properties: { message: { type: "string" } },
75
+ required: ["message"],
76
+ },
77
+ },
78
+ },
79
+ };
80
+
81
+ const bounded = (promise) => {
82
+ let timer;
83
+ const limit = new Promise((resolve) => {
84
+ timer = setTimeout(resolve, LOOKUP_TIMEOUT_MS, undefined);
85
+ });
86
+ return Promise.race([promise.catch(() => undefined), limit]).finally(() =>
87
+ clearTimeout(timer),
88
+ );
89
+ };
90
+
91
+ /** A tool result's content as a list of parts: the host may hand a plain string. */
92
+ const partsOf = (content) =>
93
+ typeof content === "string"
94
+ ? [{ type: "text", text: content }]
95
+ : Array.isArray(content)
96
+ ? content
97
+ : [];
98
+
99
+ /** The text the model reads in a tool result. */
100
+ const textOf = (result) =>
101
+ partsOf(result?.content)
102
+ .filter((part) => part?.type === "text")
103
+ .map((part) => part.text)
104
+ .join("\n");
105
+
106
+ /**
107
+ * Whether a session is a sub-agent's (it has a parent). The lookup is bounded and its failure is not an
108
+ * error: the call is then treated as the main thread's. Only an answered lookup is remembered, so a failed
109
+ * one is tried again.
110
+ */
111
+ async function isChild(session, sessionID, cache) {
112
+ if (typeof sessionID !== "string") return false;
113
+ if (cache.has(sessionID)) return cache.get(sessionID);
114
+ const info = await bounded(
115
+ Promise.resolve().then(() => session?.get?.({ sessionID })),
116
+ );
117
+ if (info === undefined || info === null) return false;
118
+ const child = typeof info.parentID === "string";
119
+ cache.set(sessionID, child);
120
+ return child;
121
+ }
122
+
123
+ export default {
124
+ id: "afm",
125
+ async setup(ctx) {
126
+ const directory = ctx.location?.directory;
127
+ // A host whose RPC is missing or refuses the definition still runs the hooks: only the toast is lost.
128
+ const rpc = await Promise.resolve()
129
+ .then(() => ctx.rpc?.register?.(RPC, {}))
130
+ .catch(() => undefined);
131
+ const notify = async (message) => {
132
+ try {
133
+ await rpc?.events?.emit?.("notice", { message });
134
+ } catch {
135
+ // nowhere to show it: the next session start tries again
136
+ }
137
+ };
138
+ const children = new Map();
139
+ const starts = new Map();
140
+ const pending = new Map();
141
+ const childOf = (sessionID) => isChild(ctx.session, sessionID, children);
142
+ const base = (sessionID, child) => ({
143
+ harness: MARKER,
144
+ session_id: sessionID,
145
+ cwd: directory,
146
+ ...(child ? { agent_id: sessionID } : {}),
147
+ });
148
+ // The digest a session starts with: the main thread's, or the sub-agent's rules. Asked once.
149
+ const startOf = (sessionID, agent) => {
150
+ if (typeof sessionID !== "string") return Promise.resolve(undefined);
151
+ if (!starts.has(sessionID))
152
+ starts.set(
153
+ sessionID,
154
+ (async () => {
155
+ const child = await childOf(sessionID);
156
+ if (!child) {
157
+ const out = await afmHook("SessionStart", {
158
+ ...base(sessionID, false),
159
+ source: "startup",
160
+ });
161
+ const notice = noticeOf(out);
162
+ if (notice) await notify(notice);
163
+ return decisionOf(out).additionalContext;
164
+ }
165
+ if (typeof agent !== "string") return undefined;
166
+ return decisionOf(
167
+ await afmHook("SubagentStart", {
168
+ ...base(sessionID, true),
169
+ agent_type: agent,
170
+ }),
171
+ ).additionalContext;
172
+ })(),
173
+ );
174
+ return starts.get(sessionID);
175
+ };
176
+
177
+ // Runs before every model request: the system prompt is rebuilt each time, so the digest is added each time.
178
+ await ctx.session.hook("context", async (event) => {
179
+ const text = await startOf(event.sessionID, event.agent);
180
+ if (text) event.system.push({ type: "text", text });
181
+ });
182
+ await ctx.tool.hook("execute.before", async (event) => {
183
+ if (!FORWARDED.has(event.tool)) return;
184
+ const child = await childOf(event.sessionID);
185
+ const decision = decisionOf(
186
+ await afmHook("PreToolUse", {
187
+ ...base(event.sessionID, child),
188
+ tool_name: event.tool,
189
+ tool_input: event.input ?? {},
190
+ }),
191
+ );
192
+ if (decision.permissionDecision === "deny")
193
+ throw new Error(decision.permissionDecisionReason);
194
+ // A PreToolUse cannot speak to the agent before the tool runs: its context joins the result.
195
+ if (decision.additionalContext && event.id)
196
+ pending.set(event.id, decision.additionalContext);
197
+ });
198
+ await ctx.tool.hook("execute.after", async (event) => {
199
+ if (!FORWARDED.has(event.tool)) return;
200
+ const child = await childOf(event.sessionID);
201
+ const exit =
202
+ event.status === "completed" ? event.result?.metadata?.exit : undefined;
203
+ const failed =
204
+ event.tool === "shell" && typeof exit === "number" && exit !== 0;
205
+ const decision = decisionOf(
206
+ await afmHook(failed ? "PostToolUseFailure" : "PostToolUse", {
207
+ ...base(event.sessionID, child),
208
+ tool_name: event.tool,
209
+ tool_input: event.input ?? {},
210
+ ...(failed ? { exit, error: textOf(event.result) } : {}),
211
+ }),
212
+ );
213
+ const context = [pending.get(event.id), decision.additionalContext]
214
+ .filter(Boolean)
215
+ .join("\n\n");
216
+ pending.delete(event.id);
217
+ if (context && event.status === "completed")
218
+ event.result = {
219
+ ...event.result,
220
+ content: [
221
+ ...partsOf(event.result?.content),
222
+ { type: "text", text: context },
223
+ ],
224
+ };
225
+ });
226
+
227
+ const controller = new AbortController();
228
+ (async () => {
229
+ for await (const event of ctx.event.subscribe({
230
+ signal: controller.signal,
231
+ })) {
232
+ if (!ENDED.has(event?.type)) continue;
233
+ const sessionID = event.data?.sessionID;
234
+ if (!(await childOf(sessionID)))
235
+ await afmHook("Stop", base(sessionID, false));
236
+ }
237
+ })().catch(() => {
238
+ // the stream ends with the plugin: nothing is left to record
239
+ });
240
+ return () => {
241
+ controller.abort();
242
+ return rpc?.dispose?.();
243
+ };
244
+ },
245
+ };
@@ -0,0 +1,21 @@
1
+ // AFM for opencode 2, terminal-client side: shows the notice the server side (index.js) emits on the
2
+ // afm RPC as a toast. opencode loads this file in the terminal client only; a server plugin has no screen.
3
+
4
+ /** How long a notice stays: long enough to read a sentence about a failed update. */
5
+ const DURATION_MS = 20000;
6
+
7
+ export default {
8
+ id: "afm",
9
+ setup(ctx) {
10
+ return ctx.data.on("rpc.afm.notice", (event) => {
11
+ const message = event?.data?.message;
12
+ if (typeof message !== "string" || message === "") return;
13
+ ctx.ui.toast.show({
14
+ title: "AFM",
15
+ message,
16
+ variant: "warning",
17
+ duration: DURATION_MS,
18
+ });
19
+ });
20
+ },
21
+ };
@@ -1,20 +1,28 @@
1
1
  ---
2
2
  description: Writer of one bounded change. Makes the edit its brief names, test first, inside the files the brief grants, and reports what changed; dispatched with a brief.
3
3
  mode: subagent
4
- permission:
5
- "*": deny
6
- read: allow
7
- edit: allow
8
- glob: allow
9
- grep: allow
10
- list: allow
11
- webfetch: deny
12
- websearch: deny
13
- external_directory: deny
14
- task: deny
15
- bash:
16
- "*": allow
17
- "afm_*": allow
4
+ permissions:
5
+ - action: "*"
6
+ resource: "*"
7
+ effect: deny
8
+ - action: "read"
9
+ resource: "*"
10
+ effect: allow
11
+ - action: "glob"
12
+ resource: "*"
13
+ effect: allow
14
+ - action: "grep"
15
+ resource: "*"
16
+ effect: allow
17
+ - action: "edit"
18
+ resource: "*"
19
+ effect: allow
20
+ - action: "shell"
21
+ resource: "*"
22
+ effect: allow
23
+ - action: "afm_*"
24
+ resource: "*"
25
+ effect: allow
18
26
  ---
19
27
  You are an executor. The message that dispatched you is your brief: follow it.
20
28
 
@@ -2,34 +2,70 @@
2
2
  description: Read-only investigator. Answers one question from the code, the docs or the web, cites what it read, and changes nothing; dispatched with a brief.
3
3
  mode: subagent
4
4
  steps: 60
5
- permission:
6
- "*": deny
7
- read: allow
8
- edit: deny
9
- glob: allow
10
- grep: allow
11
- list: allow
12
- webfetch: allow
13
- websearch: allow
14
- external_directory: deny
15
- task: deny
16
- bash:
17
- "*": deny
18
- "afm_approve": allow
19
- "afm_config": allow
20
- "afm_detect": allow
21
- "afm_doctor": allow
22
- "afm_failures_promotable": allow
23
- "afm_feature_lint": allow
24
- "afm_feature_status": allow
25
- "afm_gate": allow
26
- "afm_index": allow
27
- "afm_lang": allow
28
- "afm_next_id": allow
29
- "afm_path": allow
30
- "afm_policy": allow
31
- "afm_preflight": allow
32
- "afm_version": allow
5
+ permissions:
6
+ - action: "*"
7
+ resource: "*"
8
+ effect: deny
9
+ - action: "read"
10
+ resource: "*"
11
+ effect: allow
12
+ - action: "glob"
13
+ resource: "*"
14
+ effect: allow
15
+ - action: "grep"
16
+ resource: "*"
17
+ effect: allow
18
+ - action: "webfetch"
19
+ resource: "*"
20
+ effect: allow
21
+ - action: "websearch"
22
+ resource: "*"
23
+ effect: allow
24
+ - action: "afm_approve"
25
+ resource: "*"
26
+ effect: allow
27
+ - action: "afm_config"
28
+ resource: "*"
29
+ effect: allow
30
+ - action: "afm_detect"
31
+ resource: "*"
32
+ effect: allow
33
+ - action: "afm_doctor"
34
+ resource: "*"
35
+ effect: allow
36
+ - action: "afm_failures_promotable"
37
+ resource: "*"
38
+ effect: allow
39
+ - action: "afm_feature_lint"
40
+ resource: "*"
41
+ effect: allow
42
+ - action: "afm_feature_status"
43
+ resource: "*"
44
+ effect: allow
45
+ - action: "afm_gate"
46
+ resource: "*"
47
+ effect: allow
48
+ - action: "afm_index"
49
+ resource: "*"
50
+ effect: allow
51
+ - action: "afm_lang"
52
+ resource: "*"
53
+ effect: allow
54
+ - action: "afm_next_id"
55
+ resource: "*"
56
+ effect: allow
57
+ - action: "afm_path"
58
+ resource: "*"
59
+ effect: allow
60
+ - action: "afm_policy"
61
+ resource: "*"
62
+ effect: allow
63
+ - action: "afm_preflight"
64
+ resource: "*"
65
+ effect: allow
66
+ - action: "afm_version"
67
+ resource: "*"
68
+ effect: allow
33
69
  ---
34
70
  You are an explorer. The message that dispatched you is your brief: follow it.
35
71
 
@@ -1,34 +1,70 @@
1
1
  ---
2
2
  description: Blind auditor. Checks one piece of work it did not write against its sources and rules, and returns a verdict with evidence; dispatched with a brief.
3
3
  mode: subagent
4
- permission:
5
- "*": deny
6
- read: allow
7
- edit: deny
8
- glob: allow
9
- grep: allow
10
- list: allow
11
- webfetch: allow
12
- websearch: allow
13
- external_directory: deny
14
- task: deny
15
- bash:
16
- "*": deny
17
- "afm_approve": allow
18
- "afm_config": allow
19
- "afm_detect": allow
20
- "afm_doctor": allow
21
- "afm_failures_promotable": allow
22
- "afm_feature_lint": allow
23
- "afm_feature_status": allow
24
- "afm_gate": allow
25
- "afm_index": allow
26
- "afm_lang": allow
27
- "afm_next_id": allow
28
- "afm_path": allow
29
- "afm_policy": allow
30
- "afm_preflight": allow
31
- "afm_version": allow
4
+ permissions:
5
+ - action: "*"
6
+ resource: "*"
7
+ effect: deny
8
+ - action: "read"
9
+ resource: "*"
10
+ effect: allow
11
+ - action: "glob"
12
+ resource: "*"
13
+ effect: allow
14
+ - action: "grep"
15
+ resource: "*"
16
+ effect: allow
17
+ - action: "webfetch"
18
+ resource: "*"
19
+ effect: allow
20
+ - action: "websearch"
21
+ resource: "*"
22
+ effect: allow
23
+ - action: "afm_approve"
24
+ resource: "*"
25
+ effect: allow
26
+ - action: "afm_config"
27
+ resource: "*"
28
+ effect: allow
29
+ - action: "afm_detect"
30
+ resource: "*"
31
+ effect: allow
32
+ - action: "afm_doctor"
33
+ resource: "*"
34
+ effect: allow
35
+ - action: "afm_failures_promotable"
36
+ resource: "*"
37
+ effect: allow
38
+ - action: "afm_feature_lint"
39
+ resource: "*"
40
+ effect: allow
41
+ - action: "afm_feature_status"
42
+ resource: "*"
43
+ effect: allow
44
+ - action: "afm_gate"
45
+ resource: "*"
46
+ effect: allow
47
+ - action: "afm_index"
48
+ resource: "*"
49
+ effect: allow
50
+ - action: "afm_lang"
51
+ resource: "*"
52
+ effect: allow
53
+ - action: "afm_next_id"
54
+ resource: "*"
55
+ effect: allow
56
+ - action: "afm_path"
57
+ resource: "*"
58
+ effect: allow
59
+ - action: "afm_policy"
60
+ resource: "*"
61
+ effect: allow
62
+ - action: "afm_preflight"
63
+ resource: "*"
64
+ effect: allow
65
+ - action: "afm_version"
66
+ resource: "*"
67
+ effect: allow
32
68
  ---
33
69
  You are a reviewer. The message that dispatched you is your brief: follow it.
34
70
 
@@ -21,7 +21,7 @@ Run `afm preflight` (exit 3: no `docs/.afm-version`, stop and suggest `afm-init`
21
21
 
22
22
  ## Procedure
23
23
 
24
- Start from the mechanical findings: `afm feature lint <NNN> --json` covers the passes A, C, D, E and the structure of `tasks.md`. Add the judgment on top (constitution violations, duplication, the semantic side of inconsistency and ambiguity), build the coverage table, assign severities by the matrix, and report in chat with metrics. Close with the `remediation` question (`afm ask analyze`). Gating mode sorts findings into buckets instead of reporting.
24
+ Start from the mechanical findings: `afm feature lint <NNN> --json` covers the passes A, C, D, E and the structure of `tasks.md`. Add the judgment on top (constitution violations, duplication, the semantic side of inconsistency and ambiguity), build the coverage table, assign severities by the matrix, and report in chat with metrics. Close with the `remediation` question (`afm ask analyze`); a remediation that adds a task runs `afm task add`, never a hand edit of the `tasks:` list, and a task is closed only with `afm task done`. Gating mode sorts findings into buckets instead of reporting.
25
25
 
26
26
  ## Invariant principles
27
27
 
@@ -26,10 +26,10 @@ The CLI holds the state; the skill keeps only the judgment. Drive the loop with
26
26
  - **Discovery:** scan scoped to the feature, external unknowns through the nasca research flow (`nasca-research`, or `nasca-campaign` for a deep study; ask the operator if nasca is missing), unknowns sorted into resolved-by-scan, researchable and irreducible. One batched, recommendation-first round, capped by the protocol (`afm-protocol`).
27
27
  - **Draft:** spec, plan and tasks, verified with `afm feature lint <NNN>` plus the judgment passes of `analyze`.
28
28
  - **Gate:** the earned questions plus the `outcome` question (`afm ask deliver`), then `afm approve <NNN>`. An explicit "go" skips the questions, never the record.
29
- - **Execute:** each task is one `core/afm.md` § 2 cycle, checked with `afm gate --staged`, then `afm task done <NNN> <Txxx>`. Halt on a non-`[P]` failure after the capped retry.
29
+ - **Execute:** the tasks of the `tasks:` list run in order (`core/afm.md` § 2: a red task, the green task that closes it, an optional refactor), each checked with `afm gate --staged`, then closed with `afm task done <NNN> <Txxx>`. Tasks are added with `afm task add`, never by hand. Halt on the failure of a task that is not `parallel` after the capped retry.
30
30
  - **Reconcile:** § 8.5 plus a passive sweep.
31
31
 
32
- If artifacts already exist, resume from the correct phase, including "tasks already exist, run from the first `[ ]`".
32
+ If artifacts already exist, resume from the correct phase, including "tasks already exist, run from the first task that is not done".
33
33
 
34
34
  ## Invariant principles
35
35