@tenkara/afm-linux-arm64 0.0.0-stage → 4.0.0-alpha.10
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 +4 -0
- package/content/.coupling-baseline.tsv +1 -0
- package/content/.pending-commands.txt +2 -0
- package/content/CHANGELOG-archive.md +806 -0
- package/content/CHANGELOG.md +4320 -0
- package/content/PRINCIPLES.md +119 -0
- package/content/README-light.md +158 -0
- package/content/README.md +150 -0
- package/content/code-style/_any/file-size.md +62 -0
- package/content/code-style/_any/imports.md +68 -0
- package/content/code-style/_any/naming.md +72 -0
- package/content/code-style/_any/test-placement.md +57 -0
- package/content/code-style/nextjs-app-router/imports.md +50 -0
- package/content/code-style/nextjs-app-router/naming.md +50 -0
- package/content/code-style/prisma-postgres/error-boundary.md +55 -0
- package/content/code-style/prisma-postgres/imports.md +61 -0
- package/content/code-style/shadcn-ui/naming.md +48 -0
- package/content/code-style/stripe-billing/naming.md +76 -0
- package/content/code-style/trigger-dev-v4/imports.md +58 -0
- package/content/code-style/trigger-dev-v4/naming.md +49 -0
- package/content/code-style/trigger-dev-v4/test-placement.md +61 -0
- package/content/code-style/trpc-v11/error-boundary.md +51 -0
- package/content/code-style/trpc-v11/file-size.md +44 -0
- package/content/code-style/trpc-v11/naming.md +60 -0
- package/content/code-style/trpc-v11/test-placement.md +52 -0
- package/content/core/ach.md +260 -0
- package/content/core/afm.md +514 -0
- package/content/core/gotchas.md +18 -0
- package/content/core/prd.md +141 -0
- package/content/core/protocol.md +158 -0
- package/content/core/ust.md +65 -0
- package/content/guardrails/README.md +55 -0
- package/content/guardrails/dsl-com-ordem-obrigatoria.md +47 -0
- package/content/guardrails/exaustividade-de-union.md +48 -0
- package/content/guardrails/fronteira-de-import.md +57 -0
- package/content/hooks/README.md +70 -0
- package/content/interview/bootstrap.md +417 -0
- package/content/interview/promotion.md +115 -0
- package/content/interview/refactor.md +379 -0
- package/content/interview/triggers.md +107 -0
- package/content/modules/nextjs-app-router/ach.patch.md +44 -0
- package/content/modules/nextjs-app-router/gotchas.seed.md +78 -0
- package/content/modules/nextjs-app-router/rules.patch.md +35 -0
- package/content/modules/prisma-postgres/ach.patch.md +65 -0
- package/content/modules/prisma-postgres/rules.patch.md +28 -0
- package/content/modules/shadcn-ui/ach.patch.md +42 -0
- package/content/modules/stripe-billing/ach.patch.md +46 -0
- package/content/modules/stripe-billing/gotchas.seed.md +73 -0
- package/content/modules/stripe-billing/rules.patch.md +30 -0
- package/content/modules/trigger-dev-v4/ach.patch.md +64 -0
- package/content/modules/trigger-dev-v4/gotchas.seed.md +53 -0
- package/content/modules/trigger-dev-v4/rules.patch.md +35 -0
- package/content/modules/trpc-v11/ach.patch.md +56 -0
- package/content/modules/trpc-v11/rules.patch.md +34 -0
- package/content/modules/vireya-ui/ach.patch.md +85 -0
- package/content/modules/vireya-ui/gotchas.seed.md +98 -0
- package/content/modules/vireya-ui/rules.patch.md +36 -0
- package/content/ops/adr.md +54 -0
- package/content/ops/analyze.md +130 -0
- package/content/ops/clarify.md +86 -0
- package/content/ops/deliver.md +209 -0
- package/content/ops/diagnose.md +143 -0
- package/content/ops/evolve.md +366 -0
- package/content/ops/export-template.md +97 -0
- package/content/ops/generalize.md +117 -0
- package/content/ops/gotcha.md +46 -0
- package/content/ops/plan.md +61 -0
- package/content/ops/policy.md +61 -0
- package/content/ops/promote.md +72 -0
- package/content/ops/protocol.md +51 -0
- package/content/ops/reconcile.md +44 -0
- package/content/ops/reflect.md +182 -0
- package/content/ops/remediate.md +143 -0
- package/content/ops/restructure.md +142 -0
- package/content/ops/rule.md +170 -0
- package/content/ops/specify.md +62 -0
- package/content/ops/tasks.md +67 -0
- package/content/ops/update.md +252 -0
- package/content/ops/verify.md +67 -0
- package/content/patterns/adapter.md +46 -0
- package/content/patterns/facade.md +47 -0
- package/content/patterns/factory.md +46 -0
- package/content/patterns/null-object.md +55 -0
- package/content/patterns/result-vs-exception.md +52 -0
- package/content/patterns/strategy.md +45 -0
- package/content/patterns/template-method-vs-composition.md +50 -0
- package/content/questions/adr.questions.yaml +14 -0
- package/content/questions/analyze.questions.yaml +12 -0
- package/content/questions/bootstrap-mapping.questions.yaml +14 -0
- package/content/questions/bootstrap-rules.questions.yaml +10 -0
- package/content/questions/bootstrap-stack.questions.yaml +23 -0
- package/content/questions/clarify.questions.yaml +10 -0
- package/content/questions/code-style.questions.yaml +12 -0
- package/content/questions/confirm.questions.yaml +12 -0
- package/content/questions/deliver.questions.yaml +23 -0
- package/content/questions/evolve.questions.yaml +15 -0
- package/content/questions/export-template-apply.questions.yaml +11 -0
- package/content/questions/export-template.questions.yaml +23 -0
- package/content/questions/gates.questions.yaml +27 -0
- package/content/questions/generalize.questions.yaml +13 -0
- package/content/questions/gotcha.questions.yaml +11 -0
- package/content/questions/init-template.questions.yaml +10 -0
- package/content/questions/init.questions.yaml +41 -0
- package/content/questions/plan.questions.yaml +14 -0
- package/content/questions/refactor.questions.yaml +23 -0
- package/content/questions/reflect.questions.yaml +13 -0
- package/content/questions/remediate.questions.yaml +27 -0
- package/content/questions/restructure.questions.yaml +15 -0
- package/content/questions/rf-mapping.questions.yaml +12 -0
- package/content/questions/rule-scope.questions.yaml +12 -0
- package/content/questions/rule.questions.yaml +12 -0
- package/content/questions/specify.questions.yaml +23 -0
- package/content/questions/update-artifacts.questions.yaml +10 -0
- package/content/questions/update-strings.questions.yaml +14 -0
- package/content/questions/update.questions.yaml +10 -0
- package/content/questions/verify.questions.yaml +9 -0
- package/content/roles/executor.md +18 -0
- package/content/roles/explorer.md +22 -0
- package/content/roles/reviewer.md +23 -0
- package/content/rubrics/enum-vs-union-vs-branded.md +40 -0
- package/content/rubrics/episodic-vs-semantic-boundary.md +37 -0
- package/content/rubrics/error-classification.md +98 -0
- package/content/rubrics/failure-classification.md +111 -0
- package/content/rubrics/negative-filters.md +38 -0
- package/content/rubrics/solid-triggers.md +119 -0
- package/content/rubrics/template-vs-streaming-precedence.md +34 -0
- package/content/rubrics/validation-boundary.md +94 -0
- package/content/rubrics/when-to-create-dsl.md +65 -0
- package/content/rubrics/when-to-create-lib.md +49 -0
- package/content/rubrics/when-to-create-module.md +39 -0
- package/content/rubrics/when-to-evolve-methodology.md +62 -0
- package/content/skills/adr.md +42 -0
- package/content/skills/analyze.md +28 -0
- package/content/skills/clarify.md +28 -0
- package/content/skills/deliver.md +26 -0
- package/content/skills/diagnose.md +42 -0
- package/content/skills/evolve.md +43 -0
- package/content/skills/export-template.md +37 -0
- package/content/skills/generalize.md +31 -0
- package/content/skills/gotcha.md +40 -0
- package/content/skills/init.md +77 -0
- package/content/skills/plan.md +28 -0
- package/content/skills/policy.md +25 -0
- package/content/skills/promote.md +39 -0
- package/content/skills/protocol.md +41 -0
- package/content/skills/reconcile.md +45 -0
- package/content/skills/refactor.md +79 -0
- package/content/skills/reflect.md +43 -0
- package/content/skills/remediate.md +33 -0
- package/content/skills/restructure.md +46 -0
- package/content/skills/rule.md +43 -0
- package/content/skills/specify.md +28 -0
- package/content/skills/tasks.md +24 -0
- package/content/skills/update.md +58 -0
- package/content/skills/verify.md +29 -0
- package/content/templates/adr.template.md +51 -0
- package/content/templates/evolution-report.template.md +132 -0
- package/content/templates/feature-plan.template.md +84 -0
- package/content/templates/feature-research.template.md +56 -0
- package/content/templates/feature-spec.template.md +86 -0
- package/content/templates/feature-tasks.induced.template.md +91 -0
- package/content/templates/feature-tasks.template.md +82 -0
- package/content/templates/focus.template.md +30 -0
- package/content/templates/gotcha.template.md +31 -0
- package/content/templates/guardrail-runner.template.md +97 -0
- package/content/templates/guardrail.template.md +63 -0
- package/content/templates/learning.template.md +40 -0
- package/content/templates/procedure.template.md +47 -0
- package/content/templates/protocols/p1.md +29 -0
- package/content/templates/protocols/p2.md +28 -0
- package/content/templates/protocols/p3.md +47 -0
- package/content/templates/research.template.md +50 -0
- package/content/templates/session-handoff.template.md +58 -0
- package/content/templates/us.template.md +39 -0
- package/content/tools/gate/core-rules/PLUGIN-13-secrets.sh +36 -0
- package/content/tools/gate/core-rules/PLUGIN-21-code-language.sh +27 -0
- package/content/tools/gate/core-rules/PLUGIN-35-gate-complete.sh +19 -0
- package/content/tools/gate/core-rules/PLUGIN-36-absolute-path.sh +38 -0
- package/content/tools/gate/core-rules/PLUGIN-37-prose-language.sh +56 -0
- package/content/tools/gate/core-rules/PLUGIN-38-parser-marker.sh +82 -0
- package/content/tools/gate/core-rules/PLUGIN-39-frontmatter-ops.sh +325 -0
- package/content/tools/gate/core-rules/PLUGIN-40-dead-reference.sh +74 -0
- package/content/tools/gate/core-rules/README.md +55 -0
- package/content/tools/gate/guardrails.sh +740 -0
- package/content/tools/gate/lib/ratchet.sh +119 -0
- package/content/tools/gate/lib/scope.sh +85 -0
- package/content/tools/gate/ratchet.seed.tsv +1 -0
- package/content/tools/gate/rules/README.md +50 -0
- package/package.json +19 -5
- package/plugin/agy/hooks.json +29 -0
- package/plugin/agy/mcp_config.json +7 -0
- package/plugin/agy/plugin.json +6 -0
- package/plugin/agy/skills/afm-adr/SKILL.md +48 -0
- package/plugin/agy/skills/afm-analyze/SKILL.md +34 -0
- package/plugin/agy/skills/afm-clarify/SKILL.md +34 -0
- package/plugin/agy/skills/afm-deliver/SKILL.md +30 -0
- package/plugin/agy/skills/afm-diagnose/SKILL.md +46 -0
- package/plugin/agy/skills/afm-evolve/SKILL.md +49 -0
- package/plugin/agy/skills/afm-export-template/SKILL.md +43 -0
- package/plugin/agy/skills/afm-generalize/SKILL.md +37 -0
- package/plugin/agy/skills/afm-gotcha/SKILL.md +46 -0
- package/plugin/agy/skills/afm-init/SKILL.md +81 -0
- package/plugin/agy/skills/afm-plan/SKILL.md +34 -0
- package/plugin/agy/skills/afm-policy/SKILL.md +29 -0
- package/plugin/agy/skills/afm-promote/SKILL.md +43 -0
- package/plugin/agy/skills/afm-protocol/SKILL.md +45 -0
- package/plugin/agy/skills/afm-reconcile/SKILL.md +49 -0
- package/plugin/agy/skills/afm-refactor/SKILL.md +83 -0
- package/plugin/agy/skills/afm-reflect/SKILL.md +49 -0
- package/plugin/agy/skills/afm-remediate/SKILL.md +39 -0
- package/plugin/agy/skills/afm-restructure/SKILL.md +52 -0
- package/plugin/agy/skills/afm-rule/SKILL.md +49 -0
- package/plugin/agy/skills/afm-specify/SKILL.md +34 -0
- package/plugin/agy/skills/afm-tasks/SKILL.md +28 -0
- package/plugin/agy/skills/afm-update/SKILL.md +64 -0
- package/plugin/agy/skills/afm-verify/SKILL.md +35 -0
- package/plugin/claude-code/.claude-plugin/marketplace.json +15 -0
- package/plugin/claude-code/.claude-plugin/plugin.json +9 -0
- package/plugin/claude-code/.mcp.json +7 -0
- package/plugin/claude-code/agents/afm-executor.md +18 -0
- package/plugin/claude-code/agents/afm-explorer.md +20 -0
- package/plugin/claude-code/agents/afm-reviewer.md +21 -0
- package/plugin/claude-code/hooks/hooks.json +85 -0
- package/plugin/claude-code/skills/adr/SKILL.md +48 -0
- package/plugin/claude-code/skills/analyze/SKILL.md +34 -0
- package/plugin/claude-code/skills/clarify/SKILL.md +34 -0
- package/plugin/claude-code/skills/deliver/SKILL.md +30 -0
- package/plugin/claude-code/skills/diagnose/SKILL.md +46 -0
- package/plugin/claude-code/skills/evolve/SKILL.md +49 -0
- package/plugin/claude-code/skills/export-template/SKILL.md +43 -0
- package/plugin/claude-code/skills/generalize/SKILL.md +37 -0
- package/plugin/claude-code/skills/gotcha/SKILL.md +46 -0
- package/plugin/claude-code/skills/init/SKILL.md +81 -0
- package/plugin/claude-code/skills/plan/SKILL.md +34 -0
- package/plugin/claude-code/skills/policy/SKILL.md +29 -0
- package/plugin/claude-code/skills/promote/SKILL.md +43 -0
- package/plugin/claude-code/skills/protocol/SKILL.md +45 -0
- package/plugin/claude-code/skills/reconcile/SKILL.md +49 -0
- package/plugin/claude-code/skills/refactor/SKILL.md +83 -0
- package/plugin/claude-code/skills/reflect/SKILL.md +49 -0
- package/plugin/claude-code/skills/remediate/SKILL.md +39 -0
- package/plugin/claude-code/skills/restructure/SKILL.md +52 -0
- package/plugin/claude-code/skills/rule/SKILL.md +49 -0
- package/plugin/claude-code/skills/specify/SKILL.md +34 -0
- package/plugin/claude-code/skills/tasks/SKILL.md +28 -0
- package/plugin/claude-code/skills/update/SKILL.md +64 -0
- package/plugin/claude-code/skills/verify/SKILL.md +35 -0
- package/plugin/opencode/afm/index.js +245 -0
- package/plugin/opencode/afm/tui.js +21 -0
- package/plugin/opencode/agents/afm-executor.md +39 -0
- package/plugin/opencode/agents/afm-explorer.md +92 -0
- package/plugin/opencode/agents/afm-reviewer.md +93 -0
- package/plugin/opencode/skills/afm-adr/SKILL.md +48 -0
- package/plugin/opencode/skills/afm-analyze/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-clarify/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-deliver/SKILL.md +30 -0
- package/plugin/opencode/skills/afm-diagnose/SKILL.md +46 -0
- package/plugin/opencode/skills/afm-evolve/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-export-template/SKILL.md +43 -0
- package/plugin/opencode/skills/afm-generalize/SKILL.md +37 -0
- package/plugin/opencode/skills/afm-gotcha/SKILL.md +46 -0
- package/plugin/opencode/skills/afm-init/SKILL.md +81 -0
- package/plugin/opencode/skills/afm-plan/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-policy/SKILL.md +29 -0
- package/plugin/opencode/skills/afm-promote/SKILL.md +43 -0
- package/plugin/opencode/skills/afm-protocol/SKILL.md +45 -0
- package/plugin/opencode/skills/afm-reconcile/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-refactor/SKILL.md +83 -0
- package/plugin/opencode/skills/afm-reflect/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-remediate/SKILL.md +39 -0
- package/plugin/opencode/skills/afm-restructure/SKILL.md +52 -0
- package/plugin/opencode/skills/afm-rule/SKILL.md +49 -0
- package/plugin/opencode/skills/afm-specify/SKILL.md +34 -0
- package/plugin/opencode/skills/afm-tasks/SKILL.md +28 -0
- package/plugin/opencode/skills/afm-update/SKILL.md +64 -0
- package/plugin/opencode/skills/afm-verify/SKILL.md +35 -0
- package/README.md +0 -3
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: remediate
|
|
3
|
+
description: |
|
|
4
|
+
Reads the open failures of the state store (`afm failures promotable`) and proposes the durable artifact: a procedure when the fix is mechanical, a learning when it is conceptual. A failure enters by external signal, never by self-judgment.
|
|
5
|
+
Activates: `/afm:remediate`, "this failed again, fix it for real", "create a runbook for this", "record this lesson for the next agent sessions".
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# AFM — Remediate (CRITIC loop)
|
|
10
|
+
|
|
11
|
+
> **Questions in this host.** `afm ask <op>` prompts only in a terminal, and the Bash tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with `AskUserQuestion`: its `header`, `prompt` and `options` as they come, the recommended option first, `multiSelect` as given. 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.
|
|
12
|
+
|
|
13
|
+
> **Procedure.** Run `afm read ops/remediate.md` and follow it; read an on-demand section when its condition holds.
|
|
14
|
+
|
|
15
|
+
## When NOT to activate
|
|
16
|
+
|
|
17
|
+
This is a disambiguation for a skill that already activated. It is not an activation trigger. That is why it lives in the body and not in the resident listing.
|
|
18
|
+
|
|
19
|
+
Use `afm.md § 5.1` for an in-task retry at the moment of the error. Use `/afm:gotcha` for an SDK surprise with no failure of its own. Do not invent a failure. Only act on a recorded failure that `afm failures promotable` lists, or on an explicit user correction.
|
|
20
|
+
|
|
21
|
+
## Pre-check
|
|
22
|
+
|
|
23
|
+
1. **Does `<consumer>/docs/.afm-version` exist?** If not, stop. Suggest `/afm:init` for a greenfield project or `/afm:refactor` for a brownfield project.
|
|
24
|
+
2. **Is there an open failure?** Run `afm failures promotable`. With `no failure is promotable` and no explicit user correction, report "nothing to remediate" and stop. An empty diagnosis is a valid result.
|
|
25
|
+
3. **Does `afm preflight` exit 0?** A non-zero exit says why (docs not bootstrapped, content unresolved, pin mismatch). Report it to the user. Do not fall back silently.
|
|
26
|
+
|
|
27
|
+
## Procedure
|
|
28
|
+
|
|
29
|
+
The op includes the promotion gate (#5): 1 user correction OR at least 2 mechanical occurrences promote a failure. A transient failure does not promote. It also includes classification through the rubric `rubrics/failure-classification.md` (`afm path rubrics/failure-classification.md`). For each delta, propose the artifact (procedure or learning) and cite the source `sig=` (hard rule 19). Ask the artifact question with `afm ask remediate --scope <sig>`, materialize the result, close the signature with `afm failures close --sig <sig> --as remediated`, and report it (#6).
|
|
30
|
+
|
|
31
|
+
## Invariant principles
|
|
32
|
+
|
|
33
|
+
Most relevant here: #3 (CRITIC corollary — recognition by tool, not self-judgment). See also #5 (do not promote a failure without recurrence or an oracle) and #6 (propose only, the human approves).
|
|
34
|
+
|
|
35
|
+
## Continuous doctrine
|
|
36
|
+
|
|
37
|
+
Passive mode (`/afm:reconcile`) is always on. It reads `afm failures promotable` at the end of the turn and can propose remediation with no explicit command. The `/afm:evolve` cycle uses the failure events as evidence for `/afm:reflect`, toward a final remediation. This skill is the active record of the loop.
|
|
38
|
+
|
|
39
|
+
**Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: restructure
|
|
3
|
+
description: |
|
|
4
|
+
Checks the context architecture of the local docs. It looks at staleness against the code, findability across layers, and the hot versus on-demand partition. It relocates content that already exists and writes no new content.
|
|
5
|
+
Activates: `/afm:restructure`, "the doc is stale versus the code", "this should load only on demand", "the guidance exists but I cannot find it in the doc".
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
# AFM — Restructure (context architecture health)
|
|
10
|
+
|
|
11
|
+
> **Questions in this host.** `afm ask <op>` prompts only in a terminal, and the Bash tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with `AskUserQuestion`: its `header`, `prompt` and `options` as they come, the recommended option first, `multiSelect` as given. 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.
|
|
12
|
+
|
|
13
|
+
> **Procedure.** Run `afm read ops/restructure.md` and follow it; read an on-demand section when its condition holds.
|
|
14
|
+
|
|
15
|
+
> **Carve-out (does not overlap existing ops):** `/afm:diagnose` A1 finds a **dead reference** (the path does not exist). `restructure` staleness finds a path that **exists but changed** while the doc did not. `/afm:reconcile` adds **missing** information. `restructure` relocates information that **exists but sits in the wrong place**. `/afm:reflect` targets **clarity**. `restructure` targets **location and freshness**. `/afm:evolve` is the **orchestrator** that composes this skill when `/afm:diagnose` flags staleness or bloat.
|
|
16
|
+
>
|
|
17
|
+
> **Maturity (honest YAGNI):** the actionable core today is **staleness** (from git) plus **findability**. **Temperature tiering** (always-hot vs on-demand) is an **opt-in** ADR proposal, only for a doc that has actually outgrown its size. Small docs do not need it.
|
|
18
|
+
|
|
19
|
+
## When NOT to activate
|
|
20
|
+
|
|
21
|
+
This is a disambiguation guide for a skill that has already activated. It does not gate activation, so it stays in the body, not in the resident listing.
|
|
22
|
+
|
|
23
|
+
A path that does not exist belongs to `/afm:diagnose` (A1). Staleness means the path exists but changed. Information never documented belongs to passive mode (`/afm:reconcile`) → `/afm:gotcha` or `/afm:rule`. An ambiguous doc belongs to `/afm:reflect`. Merging gotchas belongs to `/afm:generalize`. The full cycle belongs to `/afm:evolve`.
|
|
24
|
+
|
|
25
|
+
## Pre-check
|
|
26
|
+
|
|
27
|
+
1. **Does `<consumer>/docs/.afm-version` exist?** If not, stop. Suggest `/afm:init` (greenfield) or `/afm:refactor` (brownfield) first.
|
|
28
|
+
2. **Does `afm preflight` exit 0?** A non-zero exit says why (docs not bootstrapped, content unresolved, pin mismatch). Report it to the user. Do not fall back silently.
|
|
29
|
+
3. **Is git available?** The staleness scan needs the git history. Without git, `afm doctor` marks this axis "not covered: no git". Never omit it.
|
|
30
|
+
|
|
31
|
+
## Procedure
|
|
32
|
+
|
|
33
|
+
Use these five steps.
|
|
34
|
+
|
|
35
|
+
1. **Staleness scan.** `afm doctor --json` runs it (findings with axis `staleness` and `dead-gotcha`); read them. The git trigger fires in two cases. First case: a path cited in the doc has a later last commit than the doc. Second case: the doc has been stale for more than N commits. A hit is a candidate, not a verdict. The scan never rewrites the doc on its own.
|
|
36
|
+
2. **Findability.** Relocate or cross-reference information that exists but sits buried or in the wrong doc. Cite both locations. Route information that is genuinely missing to passive mode (`/afm:reconcile`).
|
|
37
|
+
3. **Temperature partition.** Propose a hot/on-demand loading strategy as an opt-in ADR. Do this only when a doc has outgrown its size. Preserve 100% of the content.
|
|
38
|
+
4. **Its own gate.** This gate is load-bearing and earned, one `afm ask restructure --scope <item-slug>` per finding. It follows the **shared-proposal discipline** in `evolve.md` (`afm path ops/evolve.md`), instead of a duplicate copy of that discipline. `/afm:evolve` suppresses this gate when it runs.
|
|
39
|
+
5. **Record.** Write the result to `docs/evolution/NNN-<date>.md`. Suggest `/afm:promote` if the finding generalizes.
|
|
40
|
+
|
|
41
|
+
## Invariant principles
|
|
42
|
+
|
|
43
|
+
Most relevant here:
|
|
44
|
+
|
|
45
|
+
- #2: get git evidence before you propose a change.
|
|
46
|
+
- #3: staleness needs an executable git trigger.
|
|
47
|
+
- #4/#5: missing product content becomes `[TBD]` or goes to passive mode (`/afm:reconcile`). Never invent it.
|
|
48
|
+
- #6/#8: self-check the work, then pass an earned gate.
|
|
49
|
+
|
|
50
|
+
**No new principle here.** `restructure` is a composed materialization. The doctrine lives in the op file.
|
|
51
|
+
|
|
52
|
+
**Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rule
|
|
3
|
+
description: |
|
|
4
|
+
Adds a hard rule to `<consumer>/docs/afm.md § 3`. A mechanical trigger is mandatory and must return 0 or 1, such as `grep`, `wc -l`, `git diff` or `tsc`. A code review or a PR template is not a trigger. Without a trigger the entry becomes a principle in § 1.3.
|
|
5
|
+
Activates: `/afm:rule`, "new hard rule", "this should be a rule".
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AFM — Rule
|
|
9
|
+
|
|
10
|
+
> **Questions in this host.** `afm ask <op>` prompts only in a terminal, and the Bash tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with `AskUserQuestion`: its `header`, `prompt` and `options` as they come, the recommended option first, `multiSelect` as given. 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
|
+
|
|
12
|
+
> **Procedure.** Run `afm read ops/rule.md` and follow it; read an on-demand section when its condition holds.
|
|
13
|
+
|
|
14
|
+
The CLI owns the number, the rule file, the index line and the runner skeleton, and refuses a rule with no
|
|
15
|
+
honest trigger form. This skill keeps only the judgment.
|
|
16
|
+
|
|
17
|
+
## When NOT to activate
|
|
18
|
+
|
|
19
|
+
This is a disambiguation for a skill that already activated. Use `/afm:adr` for a one-off architectural
|
|
20
|
+
decision. Use `/afm:gotcha` for a counterintuitive surprise. Use `/afm:generalize` to merge rules or gotchas
|
|
21
|
+
that already exist.
|
|
22
|
+
|
|
23
|
+
## Pre-check
|
|
24
|
+
|
|
25
|
+
`afm preflight`. Exit 3 means there is no instance: stop and suggest `/afm:init` or `/afm:refactor` first.
|
|
26
|
+
|
|
27
|
+
## Procedure (judgment only)
|
|
28
|
+
|
|
29
|
+
1. Confirm the mechanical trigger as free text, and push back hard: no trigger means a principle in § 1.3.
|
|
30
|
+
Run the anti-Goodhart test: can a diff pass the trigger while it breaks the rule's intent? If yes, rewrite
|
|
31
|
+
or downgrade it.
|
|
32
|
+
2. Measure the repo for any constant and propose the value with the measurement. An unanswered constant is
|
|
33
|
+
`[TBD]`. Check for an existing versioned script and cite it.
|
|
34
|
+
3. Choose the surface: `afm ask rule`. Run `afm rule new <slug> --trigger '<command>' --surface <surface>` and
|
|
35
|
+
report the paths (#6). For `pre-commit` or `ci`, follow "Install the gate" in the op: `afm gate --baseline`
|
|
36
|
+
before the gate goes on.
|
|
37
|
+
4. In a refactored project only, ask where the rule enters: `afm ask rule-scope` (universal § 3 or
|
|
38
|
+
forward-only § 3.1). The scope decision reflects intent, so it stays fixed once made.
|
|
39
|
+
|
|
40
|
+
## Invariant principles
|
|
41
|
+
|
|
42
|
+
Most relevant here: #3 (a hard rule needs
|
|
43
|
+
a mechanical trigger, or it is a principle instead) and #6 (autonomy-first: materialize the change and
|
|
44
|
+
report it).
|
|
45
|
+
|
|
46
|
+
Passive mode (`/afm:reconcile`) is always on and can propose `/afm:rule` when it detects the signal "the user
|
|
47
|
+
corrected an approach with a reusable rule".
|
|
48
|
+
|
|
49
|
+
**Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: specify
|
|
3
|
+
description: |
|
|
4
|
+
Opens a feature folder at `docs/features/NNN-slug/spec.md`, linked to a user story. A mechanical heuristic decides first, because a feature folder for a trivial change is overhead.
|
|
5
|
+
Activates: `/afm:specify <slug>`, "open a feature", "start a new feature", "create a feature folder".
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AFM — Specify
|
|
9
|
+
|
|
10
|
+
> **Questions in this host.** `afm ask <op>` prompts only in a terminal, and the Bash tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with `AskUserQuestion`: its `header`, `prompt` and `options` as they come, the recommended option first, `multiSelect` as given. 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
|
+
|
|
12
|
+
> **Procedure.** Run `afm read ops/specify.md` and follow it; read an on-demand section when its condition holds.
|
|
13
|
+
|
|
14
|
+
## When NOT to activate
|
|
15
|
+
|
|
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
|
+
|
|
18
|
+
## Pre-check
|
|
19
|
+
|
|
20
|
+
Run `afm preflight`. Exit 3 means no `docs/.afm-version`: stop and suggest `/afm:init` or `/afm:refactor`. Exit 4 or 5: report the line it printed.
|
|
21
|
+
|
|
22
|
+
## Procedure
|
|
23
|
+
|
|
24
|
+
The number comes from `afm next-id feature`, never by hand. What is left to you is judgment: compute the trigger heuristic from the scan (ask the `heuristic` question only when borderline), find the linked US in `docs/ust.md` before asking (`afm ask specify`), create the spec with `afm new feature-spec <slug>` (the generator writes the frontmatter the template names) and fill its body, update the `Spec:` line of the US, and report the open `[NEEDS CLARIFICATION:]` items. Autonomy-first: write and report, review through `git diff`.
|
|
25
|
+
|
|
26
|
+
## Invariant principles
|
|
27
|
+
|
|
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
|
+
|
|
30
|
+
## Continuous doctrine
|
|
31
|
+
|
|
32
|
+
Passive mode (`/afm:reconcile`) is always on. This skill covers only the opening of the feature.
|
|
33
|
+
|
|
34
|
+
**Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tasks
|
|
3
|
+
description: |
|
|
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
|
+
Activates: `/afm:tasks [<feature>]`, "generate the tasks", "break the feature into tasks".
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AFM — Tasks
|
|
9
|
+
|
|
10
|
+
> **Procedure.** Run `afm read ops/tasks.md` and follow it; read an on-demand section when its condition holds.
|
|
11
|
+
|
|
12
|
+
## When NOT to activate
|
|
13
|
+
|
|
14
|
+
There is no `plan.md` yet: run `/afm:plan` first. An end-to-end delivery is `/afm:deliver`, which composes this op.
|
|
15
|
+
|
|
16
|
+
## Pre-check
|
|
17
|
+
|
|
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
|
+
|
|
20
|
+
## Principles
|
|
21
|
+
|
|
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).
|
|
23
|
+
|
|
24
|
+
## Continuous doctrine
|
|
25
|
+
|
|
26
|
+
Passive mode (`/afm:reconcile`) is always on. This skill covers only the task list.
|
|
27
|
+
|
|
28
|
+
**Principles and version.** `afm path PRINCIPLES.md` (universals §1-8 apply). `afm version` prints the version; history: `afm path CHANGELOG.md`.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: update
|
|
3
|
+
description: |
|
|
4
|
+
Syncs `<consumer>/docs/` with the current plugin version. It materializes the consumer-facing deltas between the pinned version and the shipped version, then moves the pin. It reads the plugin and never edits it.
|
|
5
|
+
Activates: `/afm:update`, "bump the methodology version", "sync docs with the new plugin", "my `.afm-version` is behind".
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AFM — Update (sync instance-plugin)
|
|
9
|
+
|
|
10
|
+
> **Questions in this host.** `afm ask <op>` prompts only in a terminal, and the Bash tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with `AskUserQuestion`: its `header`, `prompt` and `options` as they come, the recommended option first, `multiSelect` as given. 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
|
+
|
|
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.
|
|
15
|
+
|
|
16
|
+
> **Carve-out (does not overlap existing ops):** `/afm:init` and `/afm:refactor` **create** the instance
|
|
17
|
+
> (bootstrap). `update` brings an **existing** instance forward, the path that `/afm:refactor` refuses.
|
|
18
|
+
> `/afm:diagnose` only **detects** drift on axis A3 (read-only). `update` **resolves** it. `/afm:promote`
|
|
19
|
+
> **records** a finding meant for the plugin. `update` reads the plugin and materializes it into the
|
|
20
|
+
> instance, and neither touches the shipped content: an absolute boundary (#7). `/afm:evolve` is the
|
|
21
|
+
> **cycle** that composes `update` on axis A3.
|
|
22
|
+
|
|
23
|
+
## When NOT to activate
|
|
24
|
+
|
|
25
|
+
This is a disambiguation for a skill that already activated. Use `/afm:init` or `/afm:refactor` if no instance
|
|
26
|
+
is bootstrapped yet. Skip `update` if the pinned version already equals the shipped version (no drift). Use
|
|
27
|
+
`/afm:promote` to carry a learning from the instance TO the plugin (the opposite direction). Use
|
|
28
|
+
`/afm:diagnose` to only CHECK for drift. Use `/afm:evolve` for the full cycle.
|
|
29
|
+
|
|
30
|
+
## Pre-check
|
|
31
|
+
|
|
32
|
+
`afm update --dry-run` does it: exit 3 means no instance (suggest `/afm:init` or `/afm:refactor`), exit 4 means
|
|
33
|
+
the shipped content does not resolve (report it, do not fall back silently), and "already up to date" ends
|
|
34
|
+
the run. A pin **ahead** of the binary is an anomaly: report it, never roll it back.
|
|
35
|
+
|
|
36
|
+
## Procedure (judgment only)
|
|
37
|
+
|
|
38
|
+
1. Read the dry run as one package and show it as evidence. Ask `afm ask update`. Under `/afm:evolve` this gate
|
|
39
|
+
is suppressed.
|
|
40
|
+
2. Run `afm update`. It moves the pin only at the end, and keeps it where it was when a codemod fails.
|
|
41
|
+
3. Exit 6 means agent tasks are pending: do each one inside its scope (`afm lint --fix <path>` fixes the
|
|
42
|
+
frontmatter findings), then run `/afm:update` again. `afm update --accept <path>` closes a sync on a file
|
|
43
|
+
you merged by hand. An opt-in delta is reported and never applied. Exit 5 (a major crossing) is
|
|
44
|
+
`afm migrate`.
|
|
45
|
+
4. **Language migration** (when the instance is not English): read `prd.md` § 11.1 FIRST and derive the
|
|
46
|
+
excluded scope from it, and only then read a file. `[TBD]` there stops the step and asks
|
|
47
|
+
`afm ask update-strings`. It never picks a language. The artifacts question is
|
|
48
|
+
`afm ask update-artifacts`. On migrate, run the rewrite as one orchestrated batch through
|
|
49
|
+
the `Workflow` tool (an ultracode run), with a light model per agent (`haiku` as the `model` of each agent call) 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.
|
|
52
|
+
5. Post-conditions: `afm index --check` returns 0 and the plugin boundary stays clean. Report in chat what
|
|
53
|
+
migrated and what stayed pending. Under `/afm:evolve`, feed it to `docs/evolution/`.
|
|
54
|
+
|
|
55
|
+
## Invariant principles
|
|
56
|
+
|
|
57
|
+
Most relevant here: #2 (discover-first): the proof of
|
|
58
|
+
need is the grep that shows what is missing. Follow #4/#5: materialize only what the plugin already provides
|
|
59
|
+
ready-made, and never invent missing product content. Follow #6 (autonomy-first with a gate). Bright line
|
|
60
|
+
#7 applies: only **read** the plugin, and write only to the instance.
|
|
61
|
+
|
|
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`.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify
|
|
3
|
+
description: |
|
|
4
|
+
Stamps a human verification on one doc under `docs/`. It writes the OKF `verified` field and is the only writer of that field. Use it when a person states they reviewed a document.
|
|
5
|
+
Activates: `/afm:verify <path>`, "I reviewed this rule", "mark this gotcha as verified", "sign off on this ADR".
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# AFM — Verify (stamp a human review)
|
|
9
|
+
|
|
10
|
+
> **Questions in this host.** `afm ask <op>` prompts only in a terminal, and the Bash tool has none: it exits 2. Run `afm ask <op> --json` instead and put each pending question to the operator with `AskUserQuestion`: its `header`, `prompt` and `options` as they come, the recommended option first, `multiSelect` as given. 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
|
+
|
|
12
|
+
> **Procedure.** Run `afm read ops/verify.md` and follow it; read an on-demand section when its condition holds.
|
|
13
|
+
|
|
14
|
+
## When not to activate
|
|
15
|
+
|
|
16
|
+
Never on ordinary work, and never on your own output. This skill records that a PERSON read a document. An
|
|
17
|
+
agent stamping its own writing produces a lie the trust tier cannot detect.
|
|
18
|
+
|
|
19
|
+
## Pre-check
|
|
20
|
+
|
|
21
|
+
1. `afm preflight`. Exit 3 means there is no instance: stop.
|
|
22
|
+
2. **Did a person state they reviewed the file?** If not, stop and ask: `afm ask verify`. Inference is not
|
|
23
|
+
review.
|
|
24
|
+
3. The file must carry a frontmatter block, or there is no contract to stamp.
|
|
25
|
+
|
|
26
|
+
## Procedure (judgment only)
|
|
27
|
+
|
|
28
|
+
Run `afm verify <path under docs/>` (add `--as <id>` when no git user name is set). The command refuses an
|
|
29
|
+
agent actor. Report the tier that resulted and what it means.
|
|
30
|
+
|
|
31
|
+
## Invariant principles
|
|
32
|
+
|
|
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`.
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
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
|
+
mode: subagent
|
|
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
|
|
26
|
+
---
|
|
27
|
+
You are an executor. The message that dispatched you is your brief: follow it.
|
|
28
|
+
|
|
29
|
+
You make the change the brief names, and only that change.
|
|
30
|
+
|
|
31
|
+
- Touch only the files the brief grants. Another file you need is a question for the caller, not an edit.
|
|
32
|
+
- Test first: a failing test (or a type that does not compile yet), then the smallest change that
|
|
33
|
+
makes it pass, then the refactor. Run the tests and the type check before you report.
|
|
34
|
+
- Reuse what exists before you add a helper. Add no option, hook or abstraction the brief did not ask for.
|
|
35
|
+
- Never print, write or commit a secret.
|
|
36
|
+
- Never push, and never commit unless the brief says to.
|
|
37
|
+
|
|
38
|
+
Reply with the files you changed, the checks you ran with their exact result, and anything you left
|
|
39
|
+
undone or doubt.
|