@tenkara/afm-linux-arm64 0.0.0-stage → 4.0.0-alpha.11
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 +4330 -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/obligations.md +145 -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/obligations.questions.yaml +17 -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/obligations.md +27 -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-obligations/SKILL.md +31 -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/obligations/SKILL.md +31 -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-obligations/SKILL.md +31 -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,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: op
|
|
3
|
+
description: Creates a hard rule, and refuses one that has no mechanical trigger.
|
|
4
|
+
load: warm
|
|
5
|
+
writes: ["docs/rules/*.md", "docs/afm.md", "docs/tools/gate/rules/*.sh", "docs/tools/gate/rules/*.ts", "docs/guardrails/*.md"]
|
|
6
|
+
composes: []
|
|
7
|
+
---
|
|
8
|
+
# Sub-flow: new hard rule
|
|
9
|
+
|
|
10
|
+
> **Form** — effect: one file under `docs/rules/` plus its index line in `afm.md § 3` (or a principle in `§ 1.3`) · trigger: `afm rule`, composition · precondition: executable 0/1 trigger · composed by: `evolve`, `reflect`, `generalize` · **degree of freedom: low**
|
|
11
|
+
|
|
12
|
+
Triggers: `afm rule`, or natural language ("new hard rule", "this should be a rule").
|
|
13
|
+
|
|
14
|
+
The CLI owns the mechanics: the next number, the rule file, the `N. **title** — [link]` index line in
|
|
15
|
+
`afm.md § 3` (a parsing contract two hooks read), the gate runner and the index. This op owns the
|
|
16
|
+
judgment the CLI cannot make: is the trigger honest, is the constant the operator's, where does it run.
|
|
17
|
+
|
|
18
|
+
## Procedure
|
|
19
|
+
|
|
20
|
+
1. `afm preflight`. Exit 3: no instance, run `afm init` or `afm refactor` first. Read `afm.md § 3`
|
|
21
|
+
(format and numbering) and `afm path interview/triggers` § "hard rule".
|
|
22
|
+
2. **Confirm an executable trigger** the agent can run, for example `grep`, `wc -l`, `git diff` or
|
|
23
|
+
`tsc`, each returning 0 or 1. Ask for it as free text: the trigger needs description, not closed
|
|
24
|
+
options. "Code review", "PR template" and "manual review" do **not** count, because they describe a
|
|
25
|
+
human process no agent session has. `afm rule new` refuses them.
|
|
26
|
+
|
|
27
|
+
No executable trigger means the item is a **principle**, not a hard rule. Put it in `afm.md § 1.3`
|
|
28
|
+
instead (invariant principle #3 in `afm path PRINCIPLES`). **Insist hard here:** a rule nobody can
|
|
29
|
+
run is a rule nobody applies.
|
|
30
|
+
- **Anti-Goodhart guard (arXiv:2310.02304):** does the trigger measure the thing that matters, or a
|
|
31
|
+
proxy that is easy to satisfy? Test it before you accept it: can a diff pass the trigger while it
|
|
32
|
+
breaks the rule's intent? If yes, the trigger is reward-hackable. **Rewrite** it to target what
|
|
33
|
+
matters, or **downgrade** it to a principle. A `grep "test"` passes with an empty test, because
|
|
34
|
+
it counts a string and not coverage. A line count under 300 passes with one giant line. A
|
|
35
|
+
gameable trigger is worse than an honest principle.
|
|
36
|
+
3. **Check whether the trigger already exists as a versioned script**: a `test/*.sh` file, a package
|
|
37
|
+
script or a project hook may already do the check. If so, the rule cites the script, not the full
|
|
38
|
+
command. Two sources of truth for the same check drift apart: the one the human reads stops being
|
|
39
|
+
the one that runs. Also check the catalog under `afm path guardrails/README`: an entry whose
|
|
40
|
+
`sinal` matches this project has a trigger better tested than one improvised now.
|
|
41
|
+
4. **The rule's constant belongs to the operator, not to you**: a numeric limit, a name prefix, a
|
|
42
|
+
directory list. **Measure the repo, and propose the value with the measurement** ("your file p90
|
|
43
|
+
is 240 lines, I propose 300"). Never set a "best practice" value yourself. An unanswered question
|
|
44
|
+
becomes `[TBD]`, and the rule stays without a trigger until someone fills it in (#4). An invented
|
|
45
|
+
number creates a rule the owner disables at the first friction, because he never chose it.
|
|
46
|
+
5. **Choose the surface before you write the rule.** A 0/1 trigger that stays only as prose works only
|
|
47
|
+
while someone remembers to run it, and the agent forgets exactly when context gets tight. Ask
|
|
48
|
+
`afm ask rule` (questions in `content/questions/rule.questions.yaml`). The recommendation follows
|
|
49
|
+
what the project already runs (#2): `pre-commit` unless there is a reason against it.
|
|
50
|
+
- `pre-commit`: blocks every commit, including one made outside an agent session.
|
|
51
|
+
- `ci`: blocks the merge, not the local commit. Only if the project already has CI.
|
|
52
|
+
- `agent`: stays in the doc, run by judgment. Honest for a heuristic trigger that could reject
|
|
53
|
+
correct work. **Not** honest for a precise trigger nobody automated.
|
|
54
|
+
6. **Write the rule**: `afm rule new <slug> --trigger '<command returning 0 or 1>' --title "<title>" --statement "<one sentence>" --surface <pre-commit|ci|agent> [--runner ts|sh] [--does-not-cover '<text>']`. The command writes
|
|
55
|
+
`docs/rules/NN-<slug>.md` (frontmatter `type: rule`, `description`, `load: warm`, `rule_number`),
|
|
56
|
+
the `*Verification (<surface>):*` line, the index line in `afm.md § 3`, and, for `pre-commit` and
|
|
57
|
+
`ci`, a runner skeleton. `NN` in the file name IS the identity and never renumbers. A rule written
|
|
58
|
+
only as a file never reaches a session, and one written only as an index line has no body: the
|
|
59
|
+
command writes both. It prints each path and refreshes the index.
|
|
60
|
+
When the Verification command is narrower than the statement, the statement names that scope, or the
|
|
61
|
+
two are made to match. State what the gate does not check with `--does-not-cover "<text>"` (for
|
|
62
|
+
example "checks that a frontmatter exists, not the per-type schema"), so a green result is not read
|
|
63
|
+
as more than it proves. Never write boilerplate such as "edge cases". The option is optional.
|
|
64
|
+
7. **Report the paths** (invariant principle #6, autonomy-first). The operator reviews with `git diff`.
|
|
65
|
+
For `pre-commit` and `ci` continue with the next section.
|
|
66
|
+
|
|
67
|
+
## Install the gate
|
|
68
|
+
|
|
69
|
+
> Canonical procedure for "hard rule → executed guardrail". Composed of this op (step 5) and of
|
|
70
|
+
> `afm remediate` when the recurring failure is mechanizable. **Do not duplicate these steps in
|
|
71
|
+
> another file** (rule 34): point to this one.
|
|
72
|
+
|
|
73
|
+
0. **The aggregator is the CLI.** `afm gate` is the aggregator, the libraries, the ratchet reader and
|
|
74
|
+
the hook body. Do not write an aggregator or copy one from another project. You write the runner
|
|
75
|
+
body only. A project that maintains the plugin keeps its gate under `./test/`, and `afm gate`
|
|
76
|
+
resolves that fork itself. A project whose gate directory is missing is behind the plugin: it runs
|
|
77
|
+
`afm update`.
|
|
78
|
+
1. **Find the surface the project ALREADY has** (#2, mandatory before any proposal): linter config,
|
|
79
|
+
type-check, CI steps, git hooks, the test script. The proposal **extends what already runs**. A new
|
|
80
|
+
tool is a last resort, stated explicitly with its cost. A guardrail that needs a new tool to catch
|
|
81
|
+
one error usually costs more than the error.
|
|
82
|
+
2. **Pick the runner form: TypeScript is the default.** `afm rule new` writes
|
|
83
|
+
`docs/tools/gate/rules/NN-<slug>.ts`, a skeleton that throws (exit 2, never a pass) until you write the rule:
|
|
84
|
+
`({ rule }) => rule("NN").in(glob).except(glob).skipping(re).forbid(re, message)` (or `.require(re, message)`).
|
|
85
|
+
It needs no shell and no execute bit, runs on Windows, and `afm gate` runs it in a child process
|
|
86
|
+
(`afm gate ts-runner`) under the same exit contract. Check it with `afm gate --dry-run --only NN`. Full API:
|
|
87
|
+
`docs/tools/gate/rules/README.md`. Logic the builder cannot say (a diff, a count against the ratchet, another
|
|
88
|
+
tool) needs a bash runner: pass `--runner sh` to `afm rule new` in step 6 and the rest of this step applies to it.
|
|
89
|
+
Finish the runner that `afm rule new` generated. Its template (both forms) is
|
|
90
|
+
`afm path templates/guardrail-runner.template.md`. Decide the **class** there. A *diff* rule measures the index
|
|
91
|
+
with a zero budget. A *count* rule measures the whole repo against the ratchet. Swapping the two is
|
|
92
|
+
a silent false negative. Exit 0 compliant, 1 violated (one `NN<TAB>path:line<TAB>message` line),
|
|
93
|
+
2 could not decide. Exit 2 is mandatory: "could not verify" must never become "failed".
|
|
94
|
+
The generated runner takes each line the trigger PRINTS as a violation (a grep with no match exits 1
|
|
95
|
+
and passes), so the trigger prints its violations: `grep -rn`, never `grep -q` or a bare `test`.
|
|
96
|
+
3. **Measure HEAD before you turn the gate on**: `afm gate --baseline`. It runs every runner that has
|
|
97
|
+
no ratchet row, records what it measures as declared debt, and says which rules it skipped. **A gate
|
|
98
|
+
that starts red gets disabled the next day**, and it takes every other rule in the aggregator with
|
|
99
|
+
it. The mode only ever appends: it never touches a rule somebody already decided about.
|
|
100
|
+
4. **Measure the cost** and record it. A runner above the operator's budget drops to the `agent`
|
|
101
|
+
surface. The whole gate does **not** become optional because one runner is expensive.
|
|
102
|
+
5. **Doc-to-runner sync is mechanical:** `afm gate --audit` cross-references the rule and the runner in
|
|
103
|
+
both directions. A rule with a gate surface and no runner is a **phantom gate**, worse than no gate
|
|
104
|
+
because it buys trust it cannot back up. A runner with no rule is an orphan.
|
|
105
|
+
6. **Record the guardrail** with `afm new guardrail <slug> --origin hard-rule --surface <surface> --cost <low|medium|high> --description "..."`
|
|
106
|
+
(never by hand from the template). Use the `surface` chosen and the `cost` measured in step 4, then fill the
|
|
107
|
+
mandatory `## What would refute this` section. Fix any finding with `afm lint --fix`.
|
|
108
|
+
7. **Activate it: find the manager the project already uses, and propose that one.** Never ask the
|
|
109
|
+
operator to edit git config by hand. A repository cannot declare its own hooks (if it could, a
|
|
110
|
+
clone would be remote code execution), so one opt-in step is irreducible. Escalate in this order
|
|
111
|
+
(#2, the existing surface comes first):
|
|
112
|
+
|
|
113
|
+
| Tier | Signal in the repo | Where the gate lives |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| 1 | `.husky/`, `lefthook.y*ml`, `.pre-commit-config.yaml` | **in the manager already there**: add `afm gate --staged` to its config |
|
|
116
|
+
| 2 | `package.json` (and the operator agrees) | propose **husky** (or **lefthook**, for a binary with no Node) and run its install |
|
|
117
|
+
| 3 | neither one nor the other | `afm install git`, which writes the pre-commit, commit-msg and post-commit hooks into the repository's hooks directory |
|
|
118
|
+
| 4 | none of these fit (no local git, CI-only) | a CI step that runs `afm gate --worktree` |
|
|
119
|
+
|
|
120
|
+
The table is a recommendation, not a rail. **Introducing a new manager into a project with none is a
|
|
121
|
+
new tool** (step 1): state the cost explicitly, and it loses to tier 3 when the project is not Node.
|
|
122
|
+
|
|
123
|
+
**Running the install is allowed. Auto-installing is not.** The activation writes outside `docs/`,
|
|
124
|
+
so it runs only when the operator asks. When they ask, the agent runs it. What stays forbidden is
|
|
125
|
+
the gate arming itself inside a session-start or inside another hook: an opt-in that installs
|
|
126
|
+
itself is not opt-in.
|
|
127
|
+
|
|
128
|
+
7.5. **If no native surface exists** (no husky, no `core.hooksPath`), the harness hook that runs
|
|
129
|
+
`afm gate --staged` before a shell commit is the last resort. It works only as tier 3, and it steps
|
|
130
|
+
back as soon as a native surface appears: a duplicate gate reports the same finding twice and
|
|
131
|
+
teaches the operator to ignore the output. The blocking carve-out is justified by **damage
|
|
132
|
+
asymmetry**: blocking a commit delays work, blocking an edit disables the whole session.
|
|
133
|
+
|
|
134
|
+
> **The limit, stated in full.** An installed gate can be bypassed (`--no-verify`, `# noqa`,
|
|
135
|
+
> `eslint-disable`). That is not a design failure: friction is the intended effect, and a gate with no
|
|
136
|
+
> escape gets uninstalled. What the record from step 6 buys is that **bypassing leaves a trace, and
|
|
137
|
+
> removing it requires refuting the reason**, not that bypassing is impossible.
|
|
138
|
+
|
|
139
|
+
## Inalienable rule
|
|
140
|
+
|
|
141
|
+
A rule marked `INALIENABLE` in `afm.md § 3` **cannot be removed or marked N/A** by this op: `afm rule na` and `afm rule remove` refuse, under explicit invocation and under a suppressed sub-gate alike. A
|
|
142
|
+
request to do so **stops and reports**. This op is not the right channel; the right channel is the
|
|
143
|
+
plugin maintainer, with the repo open (`afm promote` records the argument with evidence).
|
|
144
|
+
|
|
145
|
+
What this op **can** do to an inalienable rule: fill in its `{{placeholder}}` (the `{{src-path}}` of
|
|
146
|
+
rule 21, for example) and adapt the `*Verification:*` line to the stack. The **form** belongs to the
|
|
147
|
+
framework, and the **constant** belongs to the operator (step 4).
|
|
148
|
+
|
|
149
|
+
**The limit, stated in full:** `docs/afm.md` is a file in the operator's repo, and nothing stops them
|
|
150
|
+
from editing it by hand. This op refuses to do so. The `afm doctor` check A5b flags the absence as
|
|
151
|
+
CRITICAL. Detection is not prevention, and promising prevention would misstate what the architecture
|
|
152
|
+
delivers.
|
|
153
|
+
|
|
154
|
+
An ordinary rule that no longer applies: `afm rule na <id> --reason "<why>"` keeps the number and
|
|
155
|
+
marks the index line N/A. `afm rule remove <id>` deletes the rule file and its runner, and its line in `docs/afm.md` § 3.
|
|
156
|
+
|
|
157
|
+
## Additional for a refactored project
|
|
158
|
+
|
|
159
|
+
> Applies only if the project was adopted through `afm refactor`. For a greenfield project skip it:
|
|
160
|
+
> every new rule goes straight into § 3.
|
|
161
|
+
|
|
162
|
+
**Consider whether the rule enters as forward-only** (`afm.md § 3.1`), because applying it
|
|
163
|
+
retroactively would break a lot of existing code. Ask `afm ask rule-scope` (questions in
|
|
164
|
+
`content/questions/rule-scope.questions.yaml`). The rule's scope is a matter of intent, not a
|
|
165
|
+
discoverable state.
|
|
166
|
+
|
|
167
|
+
- `universal`: § 3, applies to all code including legacy. May generate a large cleanup change.
|
|
168
|
+
- `forward-only`: add the rule to the § 3.1 table by hand, with the reason and where the debt is
|
|
169
|
+
tracked. New code and boy-scout edits only; tracked tech debt, not a PR blocker.
|
|
170
|
+
- `cancel`: undo with `afm rule remove <id>` if the rule was already written.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: op
|
|
3
|
+
description: Opens the feature folder and writes the spec, the what and the why with no technology.
|
|
4
|
+
load: warm
|
|
5
|
+
writes: ["docs/features/*/spec.md", "docs/ust.md"]
|
|
6
|
+
composes: []
|
|
7
|
+
---
|
|
8
|
+
# Sub-flow: open feature folder (`specify`)
|
|
9
|
+
|
|
10
|
+
> **Form** — effect: `features/NNN/spec.md` · trigger: `afm specify`, composition · pre-condition: `.afm-version`, `ust.md` · composed by: `deliver` 1.B · **degree of freedom: low**
|
|
11
|
+
|
|
12
|
+
Triggers: `afm specify <slug>`, or natural language ("open feature", "start new feature", "spec for feature X").
|
|
13
|
+
|
|
14
|
+
> **Autonomy-first (#6):** running this op alone IS the approval to open the feature. The agent computes the heuristic and the linked US from the scan (discover-first #2), writes the spec and reports the result. Review happens through `git diff`. **Under the assisted-planning protocol** (`plan=assisted`, run `afm protocol` to read it) this rule does NOT apply and the op keeps its own gate. With no protocol declared, the default applies (autonomous). See [`core/protocol.md`](../core/protocol.md) § 2. **In orchestrated mode** (called by [`deliver.md`](deliver.md)) approval moves to the single gate; see its suppression contract.
|
|
15
|
+
|
|
16
|
+
The fixed questions of this op are data: `questions/specify.questions.yaml`. Ask them with `afm ask specify --scope <slug>` and record one answer with `afm answer specify <id> <label> --scope <slug>`. A free-text value (the slug, a US number) is not a question-file entry: ask for it in plain words.
|
|
17
|
+
|
|
18
|
+
Procedure:
|
|
19
|
+
|
|
20
|
+
1. **Pre-check.** Run `afm preflight`. Exit 3 means no `docs/.afm-version`: stop and redirect to `afm init` (greenfield) or `afm refactor` (brownfield). Exit 4 or 5: report the line it printed and stop.
|
|
21
|
+
|
|
22
|
+
2. **Trigger heuristic — discover-first (#2): compute it from the scan, do not ask a blank question.** Run a quick scan (search plus the estimated change size). Does **any** criterion match: `>1 day OR >200 lines OR >3 files OR new layer/component (rule 11) OR new external boundary OR >2 unknowns`?
|
|
23
|
+
- Clearly **not** a match: stop and suggest the `core/afm.md` § 2 loop directly, with no feature folder. This applies KISS to the method itself.
|
|
24
|
+
- Clearly a match: go to step 3.
|
|
25
|
+
- **Only** if the case is borderline, ask the question `heuristic`.
|
|
26
|
+
|
|
27
|
+
2.5. **Retrieval before generate (Voyager/AWM): before you open a new folder, check whether this extends something.** Run `grep -rliE "<feature-tokens>" docs/features/*/spec.md docs/adr/ 2>/dev/null`. This is the same check as [`deliver.md` Phase 0.A0](deliver.md): a link, not a copy. If an equivalent feature or ADR exists, **propose extending it** and do not open a new number. An empty result means proceed.
|
|
28
|
+
|
|
29
|
+
3. **Next number.** Run `afm next-id feature`. It prints the zero-padded `NNN` (`001` when `docs/features/` does not exist yet).
|
|
30
|
+
|
|
31
|
+
4. **Slug.** The kebab-case of the `<slug>` the operator gave, or derived from the linked US. If it is empty, ask in plain words.
|
|
32
|
+
|
|
33
|
+
5. **Linked US — discover-first: search `docs/ust.md` before you ask.** Look for a US that matches the feature (title, persona, criteria). If there is an obvious candidate, propose it. Then ask the question `us-link`:
|
|
34
|
+
- `candidate` (recommended when a candidate was detected): confirms the link.
|
|
35
|
+
- `other-existing`: a different existing US; ask for its number in plain words.
|
|
36
|
+
- `create-first`: run the mini US flow, which mirrors `interview/bootstrap.md` § 3 to write a Gherkin criterion, and add the new US to `docs/ust.md`.
|
|
37
|
+
- `later`: mark `[NEEDS CLARIFICATION: link the US]` in the spec.
|
|
38
|
+
|
|
39
|
+
6. **Create `docs/features/NNN-slug/spec.md`** with `afm new feature-spec <slug> --description "..."` (add `--us US-NNN` when step 5 linked one; the command takes the next feature number and writes the frontmatter), then fill in the body:
|
|
40
|
+
- the title and the date.
|
|
41
|
+
- § 1 from the US description (if linked); with no US, mark `[NEEDS CLARIFICATION:]`.
|
|
42
|
+
- § 3 Gherkin scenarios copied from the US (if linked).
|
|
43
|
+
- § 5 with at least one `[NEEDS CLARIFICATION:]` that states the main gap. Do not invent it.
|
|
44
|
+
- Keep the other `{{placeholders}}` for later.
|
|
45
|
+
|
|
46
|
+
**The generator writes the frontmatter the template's own `frontmatter_for_output:` names** (hard rule 39: an op declares the keys it writes): `type: feature-spec`, `description`, `load: cold`, `status: draft` (the folder's ONE lifecycle value, no Status line in the body), `for_paths: []`, and `us: US-NNN` when step 5 linked one. `for_paths` starts empty: `plan.md` fills it once the components are known. `afm index --for <path>` routes a code path to the feature through it, and `status` tells the reader whether a route still applies (a `done` or `cancelled` feature no longer governs). A feature born without those keys stays invisible to the index.
|
|
47
|
+
|
|
48
|
+
7. **Write and report** (#6): write `spec.md`, print a summary, the path and the open `[NEEDS CLARIFICATION:]` items. Review happens through `git diff`; no blocking question.
|
|
49
|
+
|
|
50
|
+
8. **Update `docs/ust.md`**: add the line `Spec: docs/features/NNN-slug/spec.md` to the linked US metadata (if any). Write it directly (#6).
|
|
51
|
+
|
|
52
|
+
9. **Output to the operator:** the path of the spec, the numbered list of open `[NEEDS CLARIFICATION:]` items, and the suggested next step: "Resolve the clarifications and run `afm plan`, now or later." Check where the delivery stands with `afm feature status <NNN>`.
|
|
53
|
+
|
|
54
|
+
## Anti-patterns
|
|
55
|
+
|
|
56
|
+
- ❌ Do not invent a success criterion the operator did not state (universal #5, see [`../PRINCIPLES.md`](../PRINCIPLES.md)).
|
|
57
|
+
- ❌ Do not create a spec with no linked US **and** no `[NEEDS CLARIFICATION: link the US]`.
|
|
58
|
+
- ❌ Do not skip the trigger heuristic. A feature folder for a trivial change is overhead.
|
|
59
|
+
- ❌ Do not ask a blank question about the heuristic or the US. The scan or `ust.md` already show it (#2).
|
|
60
|
+
- ❌ Do not block with a question before each write. Autonomy-first (#6): write and report.
|
|
61
|
+
- ❌ Do not decide the stack or a function name in the spec. That is `plan.md` territory.
|
|
62
|
+
- ❌ Do not pick the number by hand. `afm next-id feature` is the only source.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: op
|
|
3
|
+
description: Breaks a plan into the tasks list of tasks.md in TDD order, added with afm task add.
|
|
4
|
+
load: warm
|
|
5
|
+
writes: ["docs/features/*/tasks.md"]
|
|
6
|
+
composes: []
|
|
7
|
+
needs: ["feature"]
|
|
8
|
+
---
|
|
9
|
+
# Sub-flow: make tasks for the feature (`tasks`)
|
|
10
|
+
|
|
11
|
+
> **Form** — effect: `features/NNN/tasks.md` · trigger: `afm tasks`, composition · precondition: `plan.md` has no marker · composed by: `deliver` 1.D · **degree of freedom: low**
|
|
12
|
+
|
|
13
|
+
Triggers: `afm tasks`, or natural language ("make the tasks", "break the feature into tasks").
|
|
14
|
+
|
|
15
|
+
> **Autonomy-first (#6):** running this op IS the approval to generate `tasks.md`. The agent materializes and reports (review through `git diff`), with no per-artifact question. **Under the assisted-planning protocol** (`plan=assisted`, run `afm protocol` to read it) this rule does NOT apply and the op keeps its own gate. With no declared protocol, the default applies (autonomous). See [`core/protocol.md`](../core/protocol.md) § 2. The **HARD block** for `[NEEDS CLARIFICATION:]` (step 2) STAYS: it is a mechanical refusal, not a question. **Orchestrated mode** (called by [`deliver.md`](deliver.md)): the HARD block becomes a CRITICAL finding at the single gate; see its suppression contract.
|
|
16
|
+
|
|
17
|
+
This op asks nothing a question file could hold. Choosing among several candidate features is a dynamic list: ask it in plain words.
|
|
18
|
+
|
|
19
|
+
Procedure:
|
|
20
|
+
|
|
21
|
+
1. **Pre-check.** Run `afm preflight` (exit 3: no `docs/.afm-version`, redirect to `afm init` or `afm refactor`). Find the feature: with zero plans, stop and suggest `afm plan` first. With several, **auto-detect the active feature** (discover-first #2): the branch `feat/NNN-*`, then the operator's hint, then the one `afm feature status <NNN> --json` reports with `phase` `tasks`. Ask in plain words only when two or more candidates are genuinely ambiguous.
|
|
22
|
+
|
|
23
|
+
2. **HARD block on an open `[NEEDS CLARIFICATION:]` in the plan.** `afm feature status <NNN> --json` lists them in `blockers`. When it is not empty, refuse to run and answer:
|
|
24
|
+
|
|
25
|
+
> "The plan has N open `[NEEDS CLARIFICATION:]` marker(s):
|
|
26
|
+
> - plan.md:LINE: «question text»
|
|
27
|
+
>
|
|
28
|
+
> Resolve these first (edit the plan, or run `afm plan` again to answer them), then call me back. Tasks generated with open clarifications come out incoherent."
|
|
29
|
+
|
|
30
|
+
There is no "proceed anyway" override: this is a hard gate. (`[TBD]` in the plan does **not** block. It only shows up as a flag in the output.)
|
|
31
|
+
|
|
32
|
+
3. **Read `spec.md` and `plan.md`** in full, for the feature.
|
|
33
|
+
|
|
34
|
+
4. **Make the tasks.** Create the file with `afm new feature-tasks --feature NNN-slug --description "..."` (it writes the frontmatter and the template body), then add every task with `afm task add <NNN>`. The tasks live in the frontmatter `tasks:` list, never as a checklist in the body. The CLI assigns the id, validates the item and refuses one that breaks a rule `afm feature lint` grades (exit 1, the findings printed); you fill only the fields. The body is free prose (phases, notes). Build the list in this order:
|
|
35
|
+
- **Setup:** empty files with a signature and the test fixtures: `--kind setup --files <paths> --refs <US/RF> --text "..."`, typically `--parallel`.
|
|
36
|
+
- **Foundation:** for each Gherkin scenario `N` of spec § 3, a red task (`--kind red --scenario N --files <test path>`), then the green task that closes it (`--kind green --closes <the red's id> --files <code path>`). A refactor (`--kind refactor`) follows a green or another refactor, when it adds expected clarity.
|
|
37
|
+
- **Boundary:** each new component of plan § 2 is in the `--files` of one task, with the exact path of the plan cell. For each boundary of plan § 5 (`### Boundary <name>`), a red task and the green task with the Zod validation (rule 16), added with `--validates <name>`: the lint matches the boundary by that field, never by the task text.
|
|
38
|
+
- **Reconciliation (8.5):** one task, `--kind reconcile`, always the last (`afm task add` keeps it last, and reopens it when a task lands after it was done): update `ach.md § 3` if a component is new, add an ADR (`afm adr new <slug>`, see [`adr.md`](adr.md)) if the work materializes an architectural decision, set the US status to `done`, and set the `status:` frontmatter key of `spec.md`, `plan.md` and `tasks.md` to `done` (or `cancelled` for a feature dropped; hard rule 39). No body line carries the status. It is last because those edits change the hashed documents, so the delivery approval (`afm approve --check`) goes stale by design and nothing may run after it. `afm task done` refuses it while another task is open, a green while its red is open, and a refactor while the task before it is open.
|
|
39
|
+
|
|
40
|
+
5. **Mark `--parallel`** where a task has no dependency on the task immediately before it. Heuristic:
|
|
41
|
+
- Fixture tasks, tasks that touch different test files and doc tasks are typically parallel.
|
|
42
|
+
- A green task depends on the red task it closes: never parallel.
|
|
43
|
+
- A refactor depends on its green task: never parallel.
|
|
44
|
+
|
|
45
|
+
6. **Every task references `US-NNN` or `RF-NN`** (`--refs`; the CLI refuses a task without one, except the reconcile task). The rule is `core/afm.md` § 2, step 7 COMMIT: a commit references a US or RF. If the feature has no linked US, fail earlier, at the pre-check.
|
|
46
|
+
|
|
47
|
+
7. **Mechanical validation before you report** (auto-correct, do not ask). Run `afm feature lint <NNN>`. It reads the `tasks:` list and checks: each spec scenario has a red task with its number and a later green that closes it, a green closes an earlier red (CRITICAL), a refactor follows a green or a refactor, no green or refactor is parallel, each new plan component is in the `files` of a task, each plan boundary has a task whose `validates` is its name, there is exactly one reconcile task and it is last, every `US-NNN` exists in `ust.md` and every `RF-NN` in `prd.md`, and no checklist line is left in the body. It exits 1 on CRITICAL or HIGH. Fix a finding with `afm task add` (a missing task), `afm task set <NNN> <Txxx> [--kind] [--text] [--files] [--refs] [--scenario] [--closes] [--validates] [--parallel|--no-parallel] [--clear <field>]` (the field the finding names; `--clear scenario|closes|validates|files|refs` clears one), or `afm task remove <NNN> <Txxx>` (a task that should not exist; refused while a green closes it), then re-run until the exit is 0. `afm task set` and `afm task remove` run the same rules as `afm task add` and write only the lines they change; a new `--kind` brings its own needs (a green `--closes`, a red `--scenario`). Step 6 is the declarative rule; this step is the safety net for an attention lapse.
|
|
48
|
+
|
|
49
|
+
8. **Materialize and report** (#6, see [`../PRINCIPLES.md`](../PRINCIPLES.md)): `tasks.md` is written by step 4; print the summary (step 9) with the path. Review happens through `git diff`; no blocking question.
|
|
50
|
+
|
|
51
|
+
**Check the frontmatter the template's `frontmatter_for_output:` names** (hard rule 39): `type: feature-tasks`, `description`, `load: cold`, and `for_paths`/`us`/`rf` carried from `plan.md`, unchanged, `status: planned`, and the `tasks:` list. `status` moves to `in_progress` with the first task and to `done` only in the reconcile task (step 4), never earlier.
|
|
52
|
+
|
|
53
|
+
9. **Output to the operator:**
|
|
54
|
+
- The path of `tasks.md`.
|
|
55
|
+
- The summary `N tasks total / M parallel / red, green, refactor and reconcile counts`.
|
|
56
|
+
- The `[TBD]` markers still in the plan (a flag, not a block).
|
|
57
|
+
- **Next step:** "Before you start T001, consider `afm analyze` to cross-check coverage between scenarios, tasks and hard rules. It is a read-only report in chat, a safety net and not a gate. After that, run the tasks in list order: a red task, the green task that closes it, an optional refactor (`core/afm.md` § 2). When a task is done, close it with `afm task done <NNN> <Txxx>`."
|
|
58
|
+
|
|
59
|
+
## Anti-patterns
|
|
60
|
+
|
|
61
|
+
- ❌ Skip the red task. This violates rule 1 (TDD).
|
|
62
|
+
- ❌ Create a task with no US or RF reference.
|
|
63
|
+
- ❌ Write a subjective task ("clean up code", "improve naming") with no mechanical criterion.
|
|
64
|
+
- ❌ Mark a green task `--parallel`: it depends on the red task it closes.
|
|
65
|
+
- ❌ Block with a question before writing. Materialize and report; review through `git diff`.
|
|
66
|
+
- ❌ Tolerate an open `[NEEDS CLARIFICATION:]` in the plan. The block is hard: a mechanical refusal, not a question.
|
|
67
|
+
- ❌ Write a task as a checklist line in the body, or edit the `tasks:` list by hand. `afm task add` adds a task, `afm task set` corrects one (its kind included), `afm task remove` drops one and `afm task done` closes it, atomically. When the CLI cannot write an edit it prints `held:` and a `task:` line with its verify; follow that task.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: op
|
|
3
|
+
description: Brings a materialized instance up to the current plugin version.
|
|
4
|
+
load: warm
|
|
5
|
+
writes: ["docs/.afm-version", "docs/evolution/*.md"]
|
|
6
|
+
composes: []
|
|
7
|
+
---
|
|
8
|
+
# Sub-flow: sync of the materialized instance with the current plugin version (`update`)
|
|
9
|
+
|
|
10
|
+
> **Form** — effect: deltas in the instance + bump · trigger: `afm update`, composition · precondition: pin ≠ the shipped version · composed by: `evolve` 2.2 · **degree of freedom: low**
|
|
11
|
+
|
|
12
|
+
Triggers: `afm update`, or natural language for **version bump / sync with the plugin**. Examples:
|
|
13
|
+
"update AFM here", "bump the methodology version", "sync `docs/` with the new plugin", "migrate to the new
|
|
14
|
+
AFM version", "my `.afm-version` is behind".
|
|
15
|
+
|
|
16
|
+
> **The write target is the local docs (`docs/`), NEVER the plugin.** `afm update` reads the shipped
|
|
17
|
+
> content only to **discover the deltas**. It **materializes** the consumer-facing ones
|
|
18
|
+
> in the local instance. It never edits the plugin (bright line #7). See `afm path ops/evolve` § The
|
|
19
|
+
> target of evolution.
|
|
20
|
+
>
|
|
21
|
+
> **Sync mechanism, not orchestrator.** `update` resolves the **instance-to-plugin version drift** (axis
|
|
22
|
+
> A3, which `afm diagnose` *detects* but does not fix). It runs à la carte (this doc, with its own gate)
|
|
23
|
+
> or inside the `evolve` cycle, which composes it when `afm diagnose` flags A3, with the own gate
|
|
24
|
+
> suppressed (see § Suppression contract).
|
|
25
|
+
|
|
26
|
+
The command owns everything mechanical: reading the pin, comparing versions, running the **codemod
|
|
27
|
+
registry** of every release between the pin and the binary (ADR-0013), proving each op, keeping the manifest
|
|
28
|
+
and moving the pin at the end. This op owns the three things it cannot do: the agent tasks, the language
|
|
29
|
+
migration and the post-conditions.
|
|
30
|
+
|
|
31
|
+
## When it is another op instead
|
|
32
|
+
|
|
33
|
+
- `init` / `refactor` (bootstrap) **create** the instance from zero. `update` assumes the instance
|
|
34
|
+
**already exists** and only brings it forward. `afm refactor` refuses a project with `.afm-version`;
|
|
35
|
+
`update` is the correct path for that case.
|
|
36
|
+
- `diagnose` only **detects** A3 (read-only). `update` **resolves** it. Diagnose finds it, update fixes it.
|
|
37
|
+
- `evolve` is the full health cycle. `update` is **one lens** (version sync), composed when A3 fires.
|
|
38
|
+
- `promote` **logs** a finding meant for the plugin. `update` goes plugin to **instance**. Neither op
|
|
39
|
+
touches the shipped content: an absolute boundary (#7).
|
|
40
|
+
- Not a resolver of `[TBD]`. Not an inventor of product content (#4/#5). It materializes only what the
|
|
41
|
+
plugin already provides: stack-agnostic rubrics, hygiene rules, template sections. It never fills in a
|
|
42
|
+
product decision.
|
|
43
|
+
|
|
44
|
+
## Procedure
|
|
45
|
+
|
|
46
|
+
1. **Preview:** `afm update --dry-run`. It runs the pre-check and prints every codemod with its result
|
|
47
|
+
against an overlay of the docs, writing nothing:
|
|
48
|
+
- Exit 3: no `docs/.afm-version`. Stop and run `afm init` (greenfield) or `afm refactor` (brownfield).
|
|
49
|
+
- Exit 5: the pin and the binary differ by a major version. Run `afm migrate` (the 3.x to 4.0 bridge),
|
|
50
|
+
then `afm update` again.
|
|
51
|
+
- *already up to date*: no drift means nothing to do, and an empty diagnosis is a valid result.
|
|
52
|
+
- *anomaly*: the pin is **ahead** of the binary. Someone edited the instance by hand, or the binary
|
|
53
|
+
is old. Report it, upgrade the binary, and never roll the pin back.
|
|
54
|
+
2. **Read the preview as one package, not N loose items.** The tally counts what each op did:
|
|
55
|
+
- *applied*: the op wrote (a literal replacement, a move, a created or synced file, a frontmatter key,
|
|
56
|
+
a section, a regenerated surface).
|
|
57
|
+
- *already applied or absent*: the proof of need failed (the old text is not there), so the delta was
|
|
58
|
+
already applied or never existed in this instance. Silent, and not work done.
|
|
59
|
+
- *held*: `[TBD]` content protects the text (#4), or the owner edited a file a codemod would delete.
|
|
60
|
+
**Stop and report it**: the owner's stance wins.
|
|
61
|
+
- *failed*: a proof of application did not hold. The pin stays; fix the cause and run again.
|
|
62
|
+
- *agent task(s) pending*: work only judgment can do (below).
|
|
63
|
+
- a release with nothing to materialize counts toward the bump and prints no line. Do not invent work
|
|
64
|
+
where the plugin declared there is none.
|
|
65
|
+
3. **Own gate (à la carte).** Show the preview as evidence (which version to which version, what each
|
|
66
|
+
codemod changes in the instance), then ask `afm ask update` (questions in
|
|
67
|
+
`content/questions/update.questions.yaml`). Under the `evolve` cycle this gate is **suppressed**: the
|
|
68
|
+
migration item moves up to the orchestrator's single gate.
|
|
69
|
+
4. **Apply:** `afm update`. It runs every codemod, proves each one, prints the tally and **moves
|
|
70
|
+
`docs/.afm-version` only at the end**. The exit code tells what is left:
|
|
71
|
+
- Exit 0: done, the pin moved.
|
|
72
|
+
- Exit 1: a codemod failed (or the pin is ahead). The pin stays (marker honesty: never bump as if the
|
|
73
|
+
migration were complete). Fix it and run again.
|
|
74
|
+
- **Exit 6: agent tasks are pending.** Do them (step 5), then run `afm update` again. The pin stays until
|
|
75
|
+
every task passes its check.
|
|
76
|
+
- **Protected surfaces** are never materialized by an update: the shipped content itself,
|
|
77
|
+
`PRINCIPLES`, `[TBD]` content, the format the hooks parse, the rubric that decides when the evolution
|
|
78
|
+
cycle runs (`when-to-evolve-methodology`) and the `triggers` entries that fire the evolution
|
|
79
|
+
mechanisms. That is self-reference: the sync does not materialize the document that decides when
|
|
80
|
+
the cycle runs. New stack-agnostic rubrics, yes.
|
|
81
|
+
5. **Do each agent task** listed after the tally. A line reads `agent <codemod>: <brief> (scope: <paths>)`
|
|
82
|
+
followed by the lines of its check that still fail. Work only inside the scope, through the write ops
|
|
83
|
+
(`afm adr`, `afm gotcha`, `afm rule`, `afm new <type>`), suppressing each op's own per-artifact
|
|
84
|
+
question. Run `afm lint <path>` to see the frontmatter findings and `afm lint --fix <path>` to fix
|
|
85
|
+
the ones that have one exact answer. Never invent content: what the operator did not say stays `[TBD]`.
|
|
86
|
+
A `[TBD]` placeholder alone lints clean and does not hold the pin.
|
|
87
|
+
An agent task is mechanical disambiguation: fan it out on the light model tier, per the fan-out principle
|
|
88
|
+
of `afm.md` § 1, and let the codemod do the write (re-run `afm update --dry-run` until the file is no longer
|
|
89
|
+
held; never write a `tasks:` list by hand).
|
|
90
|
+
When the run prints `commit .afm/config.toml: it is the project's AFM config`, commit that file with the update: `.afm/` holds nothing else (machine state lives in `~/.afm/state`).
|
|
91
|
+
6. **Close a sync on an edited file** with `afm update --accept <path>`. When a codemod would overwrite a
|
|
92
|
+
materialized file the owner edited (its hash in the manifest no longer matches), it hands the merge to
|
|
93
|
+
the agent instead. Merge the new shipped text into the owner's file, then run
|
|
94
|
+
`afm update --accept docs/<path>`: it records the file as it is now as the accepted version. It never
|
|
95
|
+
edits the file.
|
|
96
|
+
7. **Run `afm update` again** until it prints `pin: <from> -> <to>` (or *already up to date*). Then the
|
|
97
|
+
**post-conditions** (below) and the **log**.
|
|
98
|
+
|
|
99
|
+
## What the codemods can do
|
|
100
|
+
|
|
101
|
+
A release is a module in `packages/core/src/features/codemod-releases/data/` with an ordered list of typed ops. The rules that
|
|
102
|
+
make them safe are 0/1 and none depends on judgment:
|
|
103
|
+
|
|
104
|
+
| Op | What it does | Proof of need | When it hands over |
|
|
105
|
+
| --- | --- | --- | --- |
|
|
106
|
+
| `replace` | literal substitution in one file | the old text is in the file | `[TBD]` there: *held* |
|
|
107
|
+
| `move` | renames a file or a folder | the source exists | a target that already exists |
|
|
108
|
+
| `create` | writes a shipped file the instance lacks | the file is absent | never overwrites |
|
|
109
|
+
| `sync` | overwrites a materialized file with the new shipped text | the manifest hash still equals the file | an edited file becomes an agent task (`--accept`) |
|
|
110
|
+
| `delete` | removes a file the plugin retired | the manifest hash still equals the file | an edited file is *held* |
|
|
111
|
+
| `frontmatter` | sets or renames a key | the key is missing or old | `[TBD]` there: *held* |
|
|
112
|
+
| `section` | adds a section to a doc | the heading is absent | never rewrites an existing one |
|
|
113
|
+
| `regenerate` | rebuilds a generated surface (agents block, stubs, index) | the surface is stale | none |
|
|
114
|
+
| `custom` | a program for what the others cannot say (the OKF lift) | its own | its own agent tasks |
|
|
115
|
+
| `agent` | what only judgment can do (a rewrite, a merge) | its own | always, with a mechanical check |
|
|
116
|
+
|
|
117
|
+
**The rules:**
|
|
118
|
+
|
|
119
|
+
1. **Proof of need before a file is touched.** Zero matches means already applied: skip it silently.
|
|
120
|
+
2. **Literal substitution, never a regular expression.** A pattern on an instance the owner edited by
|
|
121
|
+
hand is how a sync destroys text nobody asked to change.
|
|
122
|
+
3. **Proof of application afterwards.** The old text is gone **and** the new text appears at least as often.
|
|
123
|
+
4. **A file missing from the instance is skipped.** A doc the consumer never materialized does not arrive
|
|
124
|
+
through the back door, except through an explicit `create`.
|
|
125
|
+
5. **`[TBD]` is untouchable** (#4): text inside a block the owner marked this way is *held*.
|
|
126
|
+
6. **The manifest is the memory of what AFM wrote.** `docs/.afm-manifest.json` maps each file AFM wrote to
|
|
127
|
+
the hash of the text it wrote. `sync` and `delete` act only on a file whose hash still matches. A file
|
|
128
|
+
the owner changed is never overwritten or removed silently: the old "update never deletes" is now
|
|
129
|
+
"update never deletes what the owner changed". Commit the manifest with the docs.
|
|
130
|
+
7. **The proof of need decides, not only the pin.** A codemod whose precondition still holds runs even when
|
|
131
|
+
the pin is past its release.
|
|
132
|
+
8. **The pin moves last**, and only when every agent task passes its check.
|
|
133
|
+
|
|
134
|
+
> **Discover-first (#2):** the proof of need is the discover-first rule. A delta the operator already copied
|
|
135
|
+
> by hand never reappears.
|
|
136
|
+
|
|
137
|
+
## Instances older than 4.0
|
|
138
|
+
|
|
139
|
+
`afm update` refuses to cross a major version (exit 5). A 3.x instance goes through `afm migrate` first
|
|
140
|
+
(`--dry-run` to preview): the bridge to 4.0, the split of `docs/gotchas.md` into `docs/gotchas/` and of
|
|
141
|
+
`afm.md` section 3 into `docs/rules/`, and the report names. Run it once and **commit before anything
|
|
142
|
+
else** (a structural migration mixed with a content edit hides both in the diff). Then run `afm update`
|
|
143
|
+
to receive the codemods of the 4.x releases (the OKF frontmatter lift among them), and
|
|
144
|
+
`afm index --materialize` (hard rule 39 fails on a missing or stale index).
|
|
145
|
+
|
|
146
|
+
## 1.6 Language migration (runs when the instance is not English)
|
|
147
|
+
|
|
148
|
+
Artifacts are English: the docs, the code, the comments, the tests, the rule text. The ONE exception is
|
|
149
|
+
the strings the product shows its own users, and that is a product decision.
|
|
150
|
+
|
|
151
|
+
**The order below is the mechanism, and reversing it is the defect.** Read the field. Derive the excluded
|
|
152
|
+
scope. Only then read a file. "Scan first, remember to skip" makes the exclusion depend on the agent
|
|
153
|
+
noticing during the work, and that is the branch that damages a product.
|
|
154
|
+
|
|
155
|
+
1. **Read `docs/prd.md` § 11.1 FIRST.** It holds the app-facing string language. An instance
|
|
156
|
+
materialized before this field existed has no such section, which reads the same as `[TBD]`.
|
|
157
|
+
2. **`[TBD]` or absent: stop and ask `afm ask update-strings`** (questions in
|
|
158
|
+
`content/questions/update-strings.questions.yaml`). Never choose a language. The irreducible mistake
|
|
159
|
+
is "no field, so nothing is a product string, so migrate everything". A translated UI is not something
|
|
160
|
+
`git revert` gives back to the user who saw it.
|
|
161
|
+
3. **Derive the excluded scope from the answer, before any file is read.** The exclusion is a file and
|
|
162
|
+
directory list computed once, not a judgment made per file. An English app language makes the excluded
|
|
163
|
+
scope empty, and the rest is unchanged. `afm lang` reports the language each file is written in.
|
|
164
|
+
4. **Ask ONE question about the artifacts: `afm ask update-artifacts`** (questions in
|
|
165
|
+
`content/questions/update-artifacts.questions.yaml`). Ask it once, never per file. `Keep the current language` records the choice, because the next update must not ask again.
|
|
166
|
+
Record the answer with `afm config set language.artifacts <tag>` (for example `pt-BR`). The command
|
|
167
|
+
validates the tag and edits `.afm/config.toml` in place. Never write that key by hand. A language agent task the
|
|
168
|
+
update prints (`artifact-language:*`) names the exact command; run it, then its 0/1 check.
|
|
169
|
+
5. **On migrate, fan out over the artifacts outside the excluded scope**: the local `docs/`, the code
|
|
170
|
+
comments, the test names. Each file gets a rewrite of its language and writing style. Then an
|
|
171
|
+
adversarial check asks what fact, number, path, marker or caveat disappeared. The checker reports; it
|
|
172
|
+
never rewrites.
|
|
173
|
+
|
|
174
|
+
**Run the fan-out as one orchestrated batch, never file by file in the main session.** A file-by-file
|
|
175
|
+
rewrite in the session context spends the capable model on mechanical volume and fills the context
|
|
176
|
+
with text it reads once. Use the orchestration primitive of the harness: a workflow script (an
|
|
177
|
+
ultracode run, where the harness has one) or, failing that, its batch of parallel subagents. Build
|
|
178
|
+
the file list from step 3 BEFORE the batch starts, and pass it to the batch as data. A file the list does not name is never touched.
|
|
179
|
+
|
|
180
|
+
**The model tier follows the task tier** (`afm.md` § 1, the fan-out principle). Translation and
|
|
181
|
+
style rewrite under a fixed contract is mechanical, high-volume work:
|
|
182
|
+
- **Rewrite: the light tier** (a Haiku-class model, Haiku 5.5 today). Set the model on each
|
|
183
|
+
agent call, never once for the whole run. Give each agent a chunk of files, not one file, so the
|
|
184
|
+
concurrency stays inside `MAX_PARALLEL_SUBAGENTS`.
|
|
185
|
+
- **Check: the light tier too.** Its contract is a comparison, not a judgment: list the facts,
|
|
186
|
+
numbers, paths, markers (`[TBD]`, `[NEEDS CLARIFICATION:]`), code spans and caveats of the original,
|
|
187
|
+
and report each one the rewrite lost. Escalate ONLY a file the check flags to a capable model, for
|
|
188
|
+
one more rewrite of that file.
|
|
189
|
+
- **The orchestrator keeps the judgment**: the excluded scope, the final gate and the report to the
|
|
190
|
+
operator. Those stay on the session model.
|
|
191
|
+
|
|
192
|
+
Each rewrite brief carries the same contract: translate the prose, keep every code span, path,
|
|
193
|
+
identifier, frontmatter key and marker byte for byte, keep the headings structure, and never shorten
|
|
194
|
+
(hard rule 17). After the batch, `afm lang` reports the language of each file: a file still in the
|
|
195
|
+
old language goes back into the batch once, then to the operator.
|
|
196
|
+
6. **Never touch the excluded scope.** Not the UI strings, not the user-facing error messages, not the
|
|
197
|
+
email copy, not the seed data a user reads. An ambiguous file stays.
|
|
198
|
+
|
|
199
|
+
**Post-condition.** `prd.md` § 11.1 is unchanged by this step. The migration reads that field and never
|
|
200
|
+
writes it. The product language is the owner's decision, not a consequence of a version bump.
|
|
201
|
+
|
|
202
|
+
## Opt-in deltas: a release that adds a capability, not a requirement
|
|
203
|
+
|
|
204
|
+
A capability delta is not a migration. Nothing breaks when the operator ignores it, and `update` must not
|
|
205
|
+
apply it silently, because opting in is the operator's decision. The release notes (`content/CHANGELOG.md`) name it, and no codemod applies it.
|
|
206
|
+
Report an opt-in delta, and never apply it. A project that has not opted in has not decided against the
|
|
207
|
+
feature: it has not been asked.
|
|
208
|
+
|
|
209
|
+
| Release | Capability | How to opt in | What happens if you do nothing |
|
|
210
|
+
| --- | --- | --- | --- |
|
|
211
|
+
| 3.10.0 | organisation policy | set `[org] dir` in the global config, pointing at a bundle that holds `policy.md` that your user owns (it was the `AFM_GLOBAL_DIR` variable before 4.0) | nothing. Every reader returns before any file test when no bundle is set |
|
|
212
|
+
| 3.11.0 | `policy_blocking` is REMOVED | delete the key if an instance added it | nothing. It decided nothing while it existed |
|
|
213
|
+
|
|
214
|
+
## Post-conditions
|
|
215
|
+
|
|
216
|
+
1. **Parseable format.** Re-extraction by the hooks over the touched docs must preserve the format:
|
|
217
|
+
`afm lint` exits 0, `afm index --check` returns 0 and `afm gate --audit` is no worse than before. Otherwise roll back the
|
|
218
|
+
delta and report it. Never fail silently.
|
|
219
|
+
2. **The instance/plugin boundary is MECHANICAL.** Run the two-branch check in `afm path ops/evolve`
|
|
220
|
+
§ Phase 4.3 at the end. The boundary must come back clean: any change in the shipped content means
|
|
221
|
+
**rollback + report CRITICAL**. `update` only reads the plugin.
|
|
222
|
+
|
|
223
|
+
## Log and handoff
|
|
224
|
+
|
|
225
|
+
- **À la carte:** report in chat what migrated (applied deltas, pending deltas, the newly pinned version).
|
|
226
|
+
A simple sync does not need its own `docs/evolution/` entry.
|
|
227
|
+
- **Under `evolve`:** it feeds the cycle report (the A3 migration becomes one line in
|
|
228
|
+
`docs/evolution/<date>-NNN.md`).
|
|
229
|
+
- **Handoff to `afm reconcile`** to watch, in the following sessions, whether the materialized delta now
|
|
230
|
+
guides well.
|
|
231
|
+
|
|
232
|
+
## Suppression contract (back-pointer)
|
|
233
|
+
|
|
234
|
+
`evolve` **composes `update`** on axis A3. Under the cycle, `evolve` suppresses the gate of step 3 and
|
|
235
|
+
moves the item up to its single gate; the log feeds the cycle report. The authoritative table lives in
|
|
236
|
+
`afm path ops/evolve` § Suppression contract. À la carte, `update` runs on its own, with its own gate.
|
|
237
|
+
|
|
238
|
+
## Anti-patterns
|
|
239
|
+
|
|
240
|
+
- ❌ **Editing the shipped content.** `update` only **reads** the plugin to discover deltas. It writes
|
|
241
|
+
only to the instance.
|
|
242
|
+
- ❌ **Materializing a consumer-noop release.** A plugin-internal patch with no content to materialize
|
|
243
|
+
only bumps the version. Do not invent work.
|
|
244
|
+
- ❌ **Re-materializing a delta that is already in the instance.** The proof of need exists for this.
|
|
245
|
+
- ❌ **Overwriting or deleting a file the owner edited.** The manifest hash is the guard: merge by hand and close it with `afm update --accept <path>`.
|
|
246
|
+
- ❌ **Materializing a protected surface:** the evolution cycle's own rubric, the evolution `triggers`,
|
|
247
|
+
and `[TBD]`. This is self-reference, and it is the operator's own stance.
|
|
248
|
+
- ❌ **Bumping `.afm-version` as if the migration were complete.** If a delta was left out, the marker
|
|
249
|
+
must show the gap honestly.
|
|
250
|
+
- ❌ **Inventing product content.** `update` materializes only what the plugin already provides
|
|
251
|
+
(stack-agnostic). A missing product decision stays `[TBD]`, for `afm reconcile` to handle (#4/#5).
|
|
252
|
+
- ❌ **Running as bootstrap.** A missing instance calls for `init` or `refactor`, not `update`.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: op
|
|
3
|
+
description: Stamps a human verification on a doc, the only writer of the OKF `verified` field.
|
|
4
|
+
load: warm
|
|
5
|
+
writes: ["docs/**/*.md"]
|
|
6
|
+
composes: []
|
|
7
|
+
---
|
|
8
|
+
# Sub-flow: stamp a human verification (`verify`)
|
|
9
|
+
|
|
10
|
+
> **Form** — effect: adds `verified:` to the frontmatter of one file · trigger: `afm verify <path>` · precondition: `.afm-version` · **degree of freedom: low**
|
|
11
|
+
|
|
12
|
+
Triggers: `afm verify <path>`. Also natural language that states a person checked a document.
|
|
13
|
+
Examples: "I reviewed this rule", "mark this gotcha as verified", "sign off on this ADR".
|
|
14
|
+
|
|
15
|
+
## What `verify` is, and is not
|
|
16
|
+
|
|
17
|
+
OKF v0.2 §5.2 keeps two facts apart. `generated` records what PRODUCED the content. `verified`
|
|
18
|
+
records who CONFIRMED it. They are separate because the writer and the checker are different
|
|
19
|
+
actors, and a document can change without re-confirmation.
|
|
20
|
+
|
|
21
|
+
From those two fields OKF derives a trust tier (§5.3):
|
|
22
|
+
|
|
23
|
+
| `verified` | Tier |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| absent | **unverified** |
|
|
26
|
+
| present, no `human:` actor | **machine-confirmed** |
|
|
27
|
+
| present with a `human:<id>` actor | **human-reviewed** |
|
|
28
|
+
|
|
29
|
+
AFM is autonomy-first. `reflect`, `reconcile` and `remediate` all WRITE documentation. Without this
|
|
30
|
+
operation, "an agent wrote this and no person read it" and "a person approved it" look the same.
|
|
31
|
+
|
|
32
|
+
## Why only a human, and why only here
|
|
33
|
+
|
|
34
|
+
An automatic writer would destroy the field. The agent commits under the operator's git identity. A
|
|
35
|
+
hook that stamps `verified` on every commit would claim human review of every agent commit. That is
|
|
36
|
+
worse than no field: it converts an honest absence into a confident lie.
|
|
37
|
+
|
|
38
|
+
So the rule is one line. **Absence means unverified, and only an explicit human stamp writes it.**
|
|
39
|
+
The command enforces it: it refuses when `AFM_ACTOR` names a role or a machine instead of a person.
|
|
40
|
+
|
|
41
|
+
## Procedure
|
|
42
|
+
|
|
43
|
+
1. `afm preflight`. Exit 3: no instance, stop.
|
|
44
|
+
2. **Confirm that a person read the file.** Show it, or ask `afm ask verify` (questions in
|
|
45
|
+
`content/questions/verify.questions.yaml`). Never infer review from ordinary work. `No` stops.
|
|
46
|
+
3. **Stamp it:** `afm verify <path under docs/>`. The actor is `human:<id>`, where `<id>` comes from
|
|
47
|
+
`--as <id>`, from `AFM_ACTOR=human:<id>` or from the git `user.name` in lowercase with no space.
|
|
48
|
+
The command appends `verified: [{ by: "human:<id>", at: <ISO 8601 UTC> }]`, keeps every other
|
|
49
|
+
key, and adds an entry when `verified` is already a list. A second check is a second event; the
|
|
50
|
+
tier reads the latest `at`.
|
|
51
|
+
4. **Report the tier that resulted**, and say what it means. The command prints it.
|
|
52
|
+
|
|
53
|
+
A file with no frontmatter carries no contract to stamp, and the command says so.
|
|
54
|
+
|
|
55
|
+
## What it must never do
|
|
56
|
+
|
|
57
|
+
- Never stamp a file the person did not read.
|
|
58
|
+
- Never write `verified` from a hook, a gate, a commit, or another op.
|
|
59
|
+
- Never remove a verification. A doc that changed after a verification stays verified at that date,
|
|
60
|
+
and the date is the signal. Silently dropping it hides that the content moved.
|
|
61
|
+
- Never write `generated` here. That belongs to whoever produced the content.
|
|
62
|
+
|
|
63
|
+
## Invariant principles
|
|
64
|
+
|
|
65
|
+
The single canonical source is `afm path PRINCIPLES`. Universals §1-8 apply. #5 applies hardest:
|
|
66
|
+
this operation records a fact about a person. Inventing one is the worst kind of invention, because
|
|
67
|
+
nothing downstream can tell.
|