@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.
- package/bin/afm +2 -2
- package/content/CHANGELOG.md +50 -0
- package/content/PRINCIPLES.md +0 -2
- package/content/README-light.md +0 -2
- package/content/README.md +0 -2
- package/content/core/ach.md +0 -5
- package/content/core/afm.md +24 -6
- package/content/core/prd.md +0 -2
- package/content/core/protocol.md +1 -3
- package/content/hooks/README.md +0 -2
- package/content/interview/bootstrap.md +4 -2
- package/content/interview/promotion.md +0 -2
- package/content/interview/refactor.md +0 -2
- package/content/interview/triggers.md +0 -2
- package/content/modules/vireya-ui/ach.patch.md +1 -1
- package/content/ops/analyze.md +8 -7
- package/content/ops/deliver.md +23 -25
- package/content/ops/diagnose.md +1 -4
- package/content/ops/evolve.md +62 -13
- package/content/ops/export-template.md +1 -1
- package/content/ops/generalize.md +1 -4
- package/content/ops/policy.md +0 -2
- package/content/ops/reflect.md +19 -17
- package/content/ops/remediate.md +1 -4
- package/content/ops/restructure.md +3 -6
- package/content/ops/rule.md +0 -2
- package/content/ops/tasks.md +18 -18
- package/content/ops/update.md +29 -4
- package/content/ops/verify.md +0 -2
- package/content/questions/init.questions.yaml +13 -0
- package/content/roles/explorer.md +1 -1
- package/content/roles/reviewer.md +1 -1
- package/content/rubrics/failure-classification.md +0 -2
- package/content/rubrics/solid-triggers.md +0 -2
- package/content/skills/adr.md +3 -3
- package/content/skills/analyze.md +3 -9
- package/content/skills/clarify.md +2 -8
- package/content/skills/deliver.md +4 -20
- package/content/skills/diagnose.md +3 -11
- package/content/skills/evolve.md +6 -30
- package/content/skills/export-template.md +2 -5
- package/content/skills/generalize.md +6 -22
- package/content/skills/gotcha.md +3 -3
- package/content/skills/init.md +2 -7
- package/content/skills/plan.md +3 -9
- package/content/skills/policy.md +0 -6
- package/content/skills/promote.md +1 -3
- package/content/skills/protocol.md +2 -3
- package/content/skills/reconcile.md +2 -6
- package/content/skills/refactor.md +2 -7
- package/content/skills/reflect.md +7 -17
- package/content/skills/remediate.md +3 -9
- package/content/skills/restructure.md +8 -14
- package/content/skills/rule.md +3 -4
- package/content/skills/specify.md +3 -9
- package/content/skills/tasks.md +5 -15
- package/content/skills/update.md +6 -4
- package/content/skills/verify.md +1 -3
- package/content/templates/adr.template.md +1 -1
- package/content/templates/evolution-report.template.md +5 -2
- package/content/templates/feature-plan.template.md +0 -2
- package/content/templates/feature-research.template.md +1 -2
- package/content/templates/feature-spec.template.md +1 -1
- package/content/templates/feature-tasks.induced.template.md +58 -34
- package/content/templates/feature-tasks.template.md +56 -34
- package/content/templates/protocols/p1.md +1 -1
- package/content/templates/protocols/p2.md +1 -1
- package/content/templates/protocols/p3.md +1 -1
- package/package.json +1 -1
- package/plugin/agy/plugin.json +1 -1
- package/plugin/agy/skills/afm-adr/SKILL.md +7 -3
- package/plugin/agy/skills/afm-analyze/SKILL.md +5 -7
- package/plugin/agy/skills/afm-clarify/SKILL.md +4 -6
- package/plugin/agy/skills/afm-deliver/SKILL.md +6 -20
- package/plugin/agy/skills/afm-diagnose/SKILL.md +5 -9
- package/plugin/agy/skills/afm-evolve/SKILL.md +9 -29
- package/plugin/agy/skills/afm-export-template/SKILL.md +6 -5
- package/plugin/agy/skills/afm-generalize/SKILL.md +8 -20
- package/plugin/agy/skills/afm-gotcha/SKILL.md +7 -3
- package/plugin/agy/skills/afm-init/SKILL.md +3 -6
- package/plugin/agy/skills/afm-plan/SKILL.md +5 -7
- package/plugin/agy/skills/afm-policy/SKILL.md +2 -4
- package/plugin/agy/skills/afm-promote/SKILL.md +4 -2
- package/plugin/agy/skills/afm-protocol/SKILL.md +6 -3
- package/plugin/agy/skills/afm-reconcile/SKILL.md +5 -5
- package/plugin/agy/skills/afm-refactor/SKILL.md +3 -6
- package/plugin/agy/skills/afm-reflect/SKILL.md +9 -15
- package/plugin/agy/skills/afm-remediate/SKILL.md +5 -7
- package/plugin/agy/skills/afm-restructure/SKILL.md +10 -12
- package/plugin/agy/skills/afm-rule/SKILL.md +7 -4
- package/plugin/agy/skills/afm-specify/SKILL.md +5 -7
- package/plugin/agy/skills/afm-tasks/SKILL.md +7 -13
- package/plugin/agy/skills/afm-update/SKILL.md +10 -4
- package/plugin/agy/skills/afm-verify/SKILL.md +4 -2
- package/plugin/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugin/claude-code/agents/afm-explorer.md +1 -1
- package/plugin/claude-code/agents/afm-reviewer.md +1 -1
- package/plugin/claude-code/skills/adr/SKILL.md +7 -3
- package/plugin/claude-code/skills/analyze/SKILL.md +5 -7
- package/plugin/claude-code/skills/clarify/SKILL.md +4 -6
- package/plugin/claude-code/skills/deliver/SKILL.md +6 -20
- package/plugin/claude-code/skills/diagnose/SKILL.md +5 -9
- package/plugin/claude-code/skills/evolve/SKILL.md +9 -29
- package/plugin/claude-code/skills/export-template/SKILL.md +6 -5
- package/plugin/claude-code/skills/generalize/SKILL.md +8 -20
- package/plugin/claude-code/skills/gotcha/SKILL.md +7 -3
- package/plugin/claude-code/skills/init/SKILL.md +3 -6
- package/plugin/claude-code/skills/plan/SKILL.md +5 -7
- package/plugin/claude-code/skills/policy/SKILL.md +2 -4
- package/plugin/claude-code/skills/promote/SKILL.md +4 -2
- package/plugin/claude-code/skills/protocol/SKILL.md +6 -3
- package/plugin/claude-code/skills/reconcile/SKILL.md +5 -5
- package/plugin/claude-code/skills/refactor/SKILL.md +3 -6
- package/plugin/claude-code/skills/reflect/SKILL.md +9 -15
- package/plugin/claude-code/skills/remediate/SKILL.md +5 -7
- package/plugin/claude-code/skills/restructure/SKILL.md +10 -12
- package/plugin/claude-code/skills/rule/SKILL.md +7 -4
- package/plugin/claude-code/skills/specify/SKILL.md +5 -7
- package/plugin/claude-code/skills/tasks/SKILL.md +7 -13
- package/plugin/claude-code/skills/update/SKILL.md +10 -4
- package/plugin/claude-code/skills/verify/SKILL.md +4 -2
- package/plugin/opencode/afm/index.js +245 -0
- package/plugin/opencode/afm/tui.js +21 -0
- package/plugin/opencode/agents/afm-executor.md +22 -14
- package/plugin/opencode/agents/afm-explorer.md +73 -28
- package/plugin/opencode/agents/afm-reviewer.md +73 -28
- package/plugin/opencode/skills/afm-adr/SKILL.md +7 -3
- package/plugin/opencode/skills/afm-analyze/SKILL.md +5 -7
- package/plugin/opencode/skills/afm-clarify/SKILL.md +4 -6
- package/plugin/opencode/skills/afm-deliver/SKILL.md +6 -20
- package/plugin/opencode/skills/afm-diagnose/SKILL.md +5 -9
- package/plugin/opencode/skills/afm-evolve/SKILL.md +9 -29
- package/plugin/opencode/skills/afm-export-template/SKILL.md +6 -5
- package/plugin/opencode/skills/afm-generalize/SKILL.md +8 -20
- package/plugin/opencode/skills/afm-gotcha/SKILL.md +7 -3
- package/plugin/opencode/skills/afm-init/SKILL.md +3 -6
- package/plugin/opencode/skills/afm-plan/SKILL.md +5 -7
- package/plugin/opencode/skills/afm-policy/SKILL.md +2 -4
- package/plugin/opencode/skills/afm-promote/SKILL.md +4 -2
- package/plugin/opencode/skills/afm-protocol/SKILL.md +6 -3
- package/plugin/opencode/skills/afm-reconcile/SKILL.md +5 -5
- package/plugin/opencode/skills/afm-refactor/SKILL.md +3 -6
- package/plugin/opencode/skills/afm-reflect/SKILL.md +9 -15
- package/plugin/opencode/skills/afm-remediate/SKILL.md +5 -7
- package/plugin/opencode/skills/afm-restructure/SKILL.md +10 -12
- package/plugin/opencode/skills/afm-rule/SKILL.md +7 -4
- package/plugin/opencode/skills/afm-specify/SKILL.md +5 -7
- package/plugin/opencode/skills/afm-tasks/SKILL.md +7 -13
- package/plugin/opencode/skills/afm-update/SKILL.md +10 -4
- package/plugin/opencode/skills/afm-verify/SKILL.md +4 -2
- package/plugin/opencode/afm.js +0 -196
|
@@ -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
|
-
|
|
5
|
-
"*"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
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,79 @@
|
|
|
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
|
-
|
|
6
|
-
"*"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
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_read"
|
|
67
|
+
resource: "*"
|
|
68
|
+
effect: allow
|
|
69
|
+
- action: "afm_sessions"
|
|
70
|
+
resource: "*"
|
|
71
|
+
effect: allow
|
|
72
|
+
- action: "afm_sessions_signals"
|
|
73
|
+
resource: "*"
|
|
74
|
+
effect: allow
|
|
75
|
+
- action: "afm_version"
|
|
76
|
+
resource: "*"
|
|
77
|
+
effect: allow
|
|
33
78
|
---
|
|
34
79
|
You are an explorer. The message that dispatched you is your brief: follow it.
|
|
35
80
|
|
|
@@ -1,34 +1,79 @@
|
|
|
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
|
-
|
|
5
|
-
"*"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
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_read"
|
|
66
|
+
resource: "*"
|
|
67
|
+
effect: allow
|
|
68
|
+
- action: "afm_sessions"
|
|
69
|
+
resource: "*"
|
|
70
|
+
effect: allow
|
|
71
|
+
- action: "afm_sessions_signals"
|
|
72
|
+
resource: "*"
|
|
73
|
+
effect: allow
|
|
74
|
+
- action: "afm_version"
|
|
75
|
+
resource: "*"
|
|
76
|
+
effect: allow
|
|
32
77
|
---
|
|
33
78
|
You are a reviewer. The message that dispatched you is your brief: follow it.
|
|
34
79
|
|
|
@@ -9,8 +9,10 @@ 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
|
-
|
|
13
|
-
|
|
12
|
+
> **Procedure.** Run `afm read ops/adr.md` and follow it; read an on-demand section when its condition holds.
|
|
13
|
+
|
|
14
|
+
The CLI owns the number, the template, the frontmatter, the supersede link and the index. This skill keeps
|
|
15
|
+
only the judgment.
|
|
14
16
|
|
|
15
17
|
## When NOT to activate
|
|
16
18
|
|
|
@@ -37,8 +39,10 @@ project (signal: `afm.md` § 3.1 forward-only exists). A greenfield project forb
|
|
|
37
39
|
|
|
38
40
|
## Invariant principles
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
Additional #9 (a retroactive ADR is
|
|
41
43
|
load-bearing-only) applies ONLY in a refactored project.
|
|
42
44
|
|
|
43
45
|
Passive mode (`afm-reconcile`) is always on and can propose `afm-adr` when it detects a "novel structural
|
|
44
46
|
decision" signal. This skill covers only the active creation.
|
|
47
|
+
|
|
48
|
+
**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
|
-
|
|
12
|
+
> **Procedure.** Run `afm read ops/analyze.md` and follow it; read an on-demand section when its condition holds.
|
|
13
13
|
|
|
14
14
|
## When NOT to activate
|
|
15
15
|
|
|
@@ -21,16 +21,14 @@ 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`)
|
|
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
|
|
|
28
|
-
|
|
28
|
+
Most relevant here: #2 (detection is an autonomous scan, not a question), #5 (no finding without evidence) and #6 (analyze is read-only: it reports in chat, writes no file, and remediates only on request).
|
|
29
29
|
|
|
30
30
|
## Continuous doctrine
|
|
31
31
|
|
|
32
|
-
Passive mode is
|
|
32
|
+
Passive mode (`afm-reconcile`) is always on. This skill covers only active auditing.
|
|
33
33
|
|
|
34
|
-
|
|
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`.
|
|
@@ -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
|
-
|
|
12
|
+
> **Procedure.** Run `afm read ops/clarify.md` and follow it; read an on-demand section when its condition holds.
|
|
13
13
|
|
|
14
14
|
## When NOT to activate
|
|
15
15
|
|
|
@@ -25,12 +25,10 @@ Judgment only: a 9-category scan of the spec, a prioritized queue (at most 5 que
|
|
|
25
25
|
|
|
26
26
|
## Invariant principles
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
Most relevant here: #2 is discover-first: resolve each question yourself through the scan or the docs, then ask. #4 says `[TBD]` beats invention, and a recommendation is a rejectable hypothesis, not a guess. #6 is autonomy-first: write the integration into spec § 7 and report. #8 is recommendation-first questions.
|
|
29
29
|
|
|
30
30
|
## Continuous doctrine
|
|
31
31
|
|
|
32
|
-
Passive mode is
|
|
32
|
+
Passive mode (`afm-reconcile`) is always on. This skill covers only the active resolution of ambiguity.
|
|
33
33
|
|
|
34
|
-
|
|
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`.
|
|
@@ -7,9 +7,9 @@ description: |
|
|
|
7
7
|
|
|
8
8
|
# AFM — Deliver (orchestrated autonomous delivery)
|
|
9
9
|
|
|
10
|
-
> **
|
|
10
|
+
> **Procedure.** Run `afm read ops/deliver.md` and follow it; read an on-demand section when its condition holds.
|
|
11
11
|
|
|
12
|
-
The **orchestrator** of the delivery loop. One request takes "build feature X" from discovery to a delivered feature.
|
|
12
|
+
The **orchestrator** of the delivery loop. One request takes "build feature X" from discovery to a delivered feature. It does not duplicate the sub-steps: it reads and follows `afm-specify`, `afm-plan`, `afm-tasks`, `afm-clarify`, `afm-analyze` and `afm-reconcile` through a suppression contract. External unknowns go through `nasca-research`, or `nasca-campaign` for a deep study; ask the operator if nasca is missing.
|
|
13
13
|
|
|
14
14
|
## When NOT to activate
|
|
15
15
|
|
|
@@ -19,26 +19,12 @@ An isolated step ("just open the spec", "just generate the tasks") is the matchi
|
|
|
19
19
|
|
|
20
20
|
Run `afm preflight`. Exit 0 continues. Exit 3 means no `docs/.afm-version`: stop and suggest `afm-init` (greenfield) or `afm-refactor` (brownfield). Exit 4 or 5: report the line it printed. Do not fall back silently.
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## Most relevant here
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
- **Draft:** spec, plan and tasks, verified with `afm feature lint <NNN>` plus the judgment passes of `analyze`.
|
|
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.
|
|
30
|
-
- **Reconcile:** § 8.5 plus a passive sweep.
|
|
31
|
-
|
|
32
|
-
If artifacts already exist, resume from the correct phase, including "tasks already exist, run from the first `[ ]`".
|
|
33
|
-
|
|
34
|
-
## Invariant principles
|
|
35
|
-
|
|
36
|
-
Single canonical source: `afm path PRINCIPLES.md`. Universals §1-8 apply. The backbone is **#2** (discover-first: resolve unknowns by scan and research, ask only the irreducible part, never guess product or criteria) and **#6** (autonomy-first: approval at checkpoint granularity, the consolidated plan IS the proposal).
|
|
24
|
+
**#2** (discover-first: resolve unknowns by scan and research, ask only the irreducible part, never guess product or criteria) and **#6** (autonomy-first: approval at checkpoint granularity, the consolidated plan IS the proposal).
|
|
37
25
|
|
|
38
26
|
## Continuous doctrine
|
|
39
27
|
|
|
40
|
-
Passive mode is
|
|
41
|
-
|
|
42
|
-
## Version
|
|
28
|
+
Passive mode (`afm-reconcile`) is always on. It runs in parallel with `afm-deliver` and takes the closing turn of Phase 4 (`core/afm.md` § 8.5 plus a passive sweep).
|
|
43
29
|
|
|
44
|
-
`afm version
|
|
30
|
+
**Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
|