formwork-kit 0.1.0__py3-none-any.whl
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.
- formwork_cli/__init__.py +326 -0
- formwork_cli/kit/COSTS.md +111 -0
- formwork_cli/kit/adapters/claude-code/README.md +53 -0
- formwork_cli/kit/adapters/claude-code/settings.json +46 -0
- formwork_cli/kit/adapters/codex/README.md +43 -0
- formwork_cli/kit/adapters/cursor/README.md +45 -0
- formwork_cli/kit/adapters/gemini-cli/README.md +47 -0
- formwork_cli/kit/build +410 -0
- formwork_cli/kit/check/checks/config-shape +123 -0
- formwork_cli/kit/check/checks/decision-ids +159 -0
- formwork_cli/kit/check/checks/doc-links +133 -0
- formwork_cli/kit/check/checks/generated-current +74 -0
- formwork_cli/kit/check/checks/guard-wired +139 -0
- formwork_cli/kit/check/checks/kit-integrity +199 -0
- formwork_cli/kit/check/checks/predictions-first +127 -0
- formwork_cli/kit/check/checks/role-shape +172 -0
- formwork_cli/kit/check/checks/rule-labels +135 -0
- formwork_cli/kit/check/fixtures/config-shape/must-fail/documents-a-section-that-does-not-exist/.formwork.toml +5 -0
- formwork_cli/kit/check/fixtures/config-shape/must-fail/documents-a-section-that-does-not-exist/formwork/guide.md +13 -0
- formwork_cli/kit/check/fixtures/config-shape/must-fail/rules-as-a-switchboard/.formwork.toml +8 -0
- formwork_cli/kit/check/fixtures/config-shape/must-pass/layers-kept-apart/.formwork.toml +5 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-fail/a-placeholder-shipped/docs/decisions/0003-still-pending.md +7 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-fail/superseded-by-nothing/docs/decisions/0002-old.md +6 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-fail/two-decisions-one-number/docs/decisions/0007-first.md +6 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-fail/two-decisions-one-number/docs/decisions/0007-second.md +6 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-pass/clean-numbering/docs/decisions/0001-the-first.md +6 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-pass/clean-numbering/docs/decisions/0002-the-second.md +6 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-pass/clean-numbering/docs/decisions/0003-the-third.md +6 -0
- formwork_cli/kit/check/fixtures/decision-ids/must-pass/nothing-recorded-yet/docs/decisions/README.md +3 -0
- formwork_cli/kit/check/fixtures/doc-links/must-fail/never-written/index.md +7 -0
- formwork_cli/kit/check/fixtures/doc-links/must-fail/renamed-file/architecture-notes.md +3 -0
- formwork_cli/kit/check/fixtures/doc-links/must-fail/renamed-file/guide.md +8 -0
- formwork_cli/kit/check/fixtures/doc-links/must-pass/links-resolve/architecture-notes.md +1 -0
- formwork_cli/kit/check/fixtures/doc-links/must-pass/links-resolve/guide.md +5 -0
- formwork_cli/kit/check/fixtures/generated-current/must-fail/a-generated-file-was-edited/.claude/agents/sample.md +22 -0
- formwork_cli/kit/check/fixtures/generated-current/must-fail/a-generated-file-was-edited/.codex/agents/sample.toml +22 -0
- formwork_cli/kit/check/fixtures/generated-current/must-fail/a-generated-file-was-edited/.formwork.toml +1 -0
- formwork_cli/kit/check/fixtures/generated-current/must-fail/a-generated-file-was-edited/.gemini/agents/sample.md +23 -0
- formwork_cli/kit/check/fixtures/generated-current/must-fail/a-generated-file-was-edited/formwork/build +349 -0
- formwork_cli/kit/check/fixtures/generated-current/must-fail/a-generated-file-was-edited/formwork/roles/method/sample.md +18 -0
- formwork_cli/kit/check/fixtures/generated-current/must-pass/generated-and-current/.claude/agents/sample.md +20 -0
- formwork_cli/kit/check/fixtures/generated-current/must-pass/generated-and-current/.codex/agents/sample.toml +22 -0
- formwork_cli/kit/check/fixtures/generated-current/must-pass/generated-and-current/.formwork.toml +1 -0
- formwork_cli/kit/check/fixtures/generated-current/must-pass/generated-and-current/.gemini/agents/sample.md +23 -0
- formwork_cli/kit/check/fixtures/generated-current/must-pass/generated-and-current/formwork/build +349 -0
- formwork_cli/kit/check/fixtures/generated-current/must-pass/generated-and-current/formwork/roles/method/sample.md +18 -0
- formwork_cli/kit/check/fixtures/generated-current/must-pass/nothing-is-generated-here/README.md +3 -0
- formwork_cli/kit/check/fixtures/guard-wired/must-fail/declared-but-no-file/.formwork.toml +1 -0
- formwork_cli/kit/check/fixtures/guard-wired/must-fail/file-but-not-wired/.claude/settings.json +1 -0
- formwork_cli/kit/check/fixtures/guard-wired/must-fail/file-but-not-wired/.formwork.toml +1 -0
- formwork_cli/kit/check/fixtures/guard-wired/must-pass/declared-and-wired/.claude/settings.json +1 -0
- formwork_cli/kit/check/fixtures/guard-wired/must-pass/declared-and-wired/.formwork.toml +1 -0
- formwork_cli/kit/check/fixtures/guard-wired/must-pass/nothing-declared/README.md +1 -0
- formwork_cli/kit/check/fixtures/kit-integrity/must-fail/a-check-went-missing/formwork/check/checks/still-here +2 -0
- formwork_cli/kit/check/fixtures/kit-integrity/must-fail/a-check-went-missing/state/fingerprints.txt +2 -0
- formwork_cli/kit/check/fixtures/kit-integrity/must-fail/a-guard-was-altered/formwork/guard/git-boundary +3 -0
- formwork_cli/kit/check/fixtures/kit-integrity/must-fail/a-guard-was-altered/state/fingerprints.txt +1 -0
- formwork_cli/kit/check/fixtures/kit-integrity/must-pass/everything-matches/formwork/guard/git-boundary +2 -0
- formwork_cli/kit/check/fixtures/kit-integrity/must-pass/everything-matches/state/fingerprints.txt +1 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-fail/argued-with-no-predictions/docs/rounds/0004-the-storage-question/architect.md +3 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-fail/argued-with-no-predictions/docs/rounds/0004-the-storage-question/researcher.md +3 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-fail/argued-with-no-predictions/docs/rounds/0004-the-storage-question/round.md +4 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-pass/a-round-that-has-not-argued-yet/docs/rounds/0006-not-started/round.md +3 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-pass/no-rounds-at-all/docs/README.md +3 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-pass/predictions-on-record/docs/rounds/0005-the-shape-of-a-brief/architect.md +3 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-pass/predictions-on-record/docs/rounds/0005-the-shape-of-a-brief/predictions.md +4 -0
- formwork_cli/kit/check/fixtures/predictions-first/must-pass/predictions-on-record/docs/rounds/0005-the-shape-of-a-brief/researcher.md +3 -0
- formwork_cli/kit/check/fixtures/role-shape/must-fail/claims-a-grant-binds-everywhere/formwork/roles/README.md +6 -0
- formwork_cli/kit/check/fixtures/role-shape/must-fail/claims-a-grant-binds-everywhere/formwork/roles/complete.md +18 -0
- formwork_cli/kit/check/fixtures/role-shape/must-fail/missing-a-section/formwork/roles/vague.md +16 -0
- formwork_cli/kit/check/fixtures/role-shape/must-fail/spawn-without-being-lead/formwork/roles/eager.md +18 -0
- formwork_cli/kit/check/fixtures/role-shape/must-fail/two-roles-one-job/formwork/roles/first.md +18 -0
- formwork_cli/kit/check/fixtures/role-shape/must-fail/two-roles-one-job/formwork/roles/second.md +18 -0
- formwork_cli/kit/check/fixtures/role-shape/must-pass/well-formed/formwork/roles/complete.md +18 -0
- formwork_cli/kit/check/fixtures/rule-labels/must-fail/claims-enforcement-that-does-not-exist/formwork/rules/core.md +9 -0
- formwork_cli/kit/check/fixtures/rule-labels/must-fail/no-catches/formwork/rules/core.md +9 -0
- formwork_cli/kit/check/fixtures/rule-labels/must-fail/unlabelled/formwork/rules/core.md +7 -0
- formwork_cli/kit/check/fixtures/rule-labels/must-pass/well-formed/formwork/rules/core.md +10 -0
- formwork_cli/kit/check/run +340 -0
- formwork_cli/kit/check/test_gate.py +222 -0
- formwork_cli/kit/first-run.md +204 -0
- formwork_cli/kit/fw +121 -0
- formwork_cli/kit/glossary.md +160 -0
- formwork_cli/kit/guard/git-boundary +627 -0
- formwork_cli/kit/guard/protected-files +748 -0
- formwork_cli/kit/guard/quality-gate +260 -0
- formwork_cli/kit/guard/test_boundary.py +273 -0
- formwork_cli/kit/guard/test_protection.py +254 -0
- formwork_cli/kit/guard/test_quality_gate.py +156 -0
- formwork_cli/kit/install +395 -0
- formwork_cli/kit/limits.md +141 -0
- formwork_cli/kit/loop.md +82 -0
- formwork_cli/kit/roles/HOW-TO-ADD-A-ROLE.md +105 -0
- formwork_cli/kit/roles/TEMPLATE.md +26 -0
- formwork_cli/kit/roles/method/architect.md +269 -0
- formwork_cli/kit/roles/method/challenger.md +243 -0
- formwork_cli/kit/roles/method/lead.md +280 -0
- formwork_cli/kit/roles/method/record-keeper.md +206 -0
- formwork_cli/kit/roles/method/researcher.md +246 -0
- formwork_cli/kit/roles/method/reviewer.md +207 -0
- formwork_cli/kit/roles/packs/accessibility.md +236 -0
- formwork_cli/kit/roles/packs/ai.md +248 -0
- formwork_cli/kit/roles/packs/analyst.md +233 -0
- formwork_cli/kit/roles/packs/backend.md +425 -0
- formwork_cli/kit/roles/packs/brainstormer.md +190 -0
- formwork_cli/kit/roles/packs/data.md +212 -0
- formwork_cli/kit/roles/packs/devops.md +203 -0
- formwork_cli/kit/roles/packs/frontend.md +224 -0
- formwork_cli/kit/roles/packs/integrations.md +215 -0
- formwork_cli/kit/roles/packs/legal.md +251 -0
- formwork_cli/kit/roles/packs/marketing.md +206 -0
- formwork_cli/kit/roles/packs/mobile.md +202 -0
- formwork_cli/kit/roles/packs/performance.md +192 -0
- formwork_cli/kit/roles/packs/product.md +217 -0
- formwork_cli/kit/roles/packs/security.md +267 -0
- formwork_cli/kit/roles/packs/sre.md +203 -0
- formwork_cli/kit/roles/packs/tester.md +246 -0
- formwork_cli/kit/roles/packs/user-researcher.md +218 -0
- formwork_cli/kit/roles/packs/ux.md +205 -0
- formwork_cli/kit/roles/packs/visual.md +199 -0
- formwork_cli/kit/roles/packs/writer.md +198 -0
- formwork_cli/kit/round.md +131 -0
- formwork_cli/kit/rules/core.md +195 -0
- formwork_cli/kit/rules/full.md +493 -0
- formwork_cli/kit/templates/brief.md +68 -0
- formwork_cli/kit/templates/decision.md +93 -0
- formwork_cli/kit/templates/predictions.md +54 -0
- formwork_cli/kit/templates/report.md +52 -0
- formwork_cli/kit/templates/round.md +77 -0
- formwork_cli/kit/test_install.py +165 -0
- formwork_cli/kit/troubleshooting.md +247 -0
- formwork_cli/kit-page/FORMWORK.md +182 -0
- formwork_kit-0.1.0.dist-info/METADATA +308 -0
- formwork_kit-0.1.0.dist-info/RECORD +137 -0
- formwork_kit-0.1.0.dist-info/WHEEL +4 -0
- formwork_kit-0.1.0.dist-info/entry_points.txt +2 -0
- formwork_kit-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Brief
|
|
2
|
+
|
|
3
|
+
Copy this. Fill it in. Hand it over.
|
|
4
|
+
|
|
5
|
+
**For a task, one line is the whole brief.** Do not use this page for a rename.
|
|
6
|
+
Scale the paperwork to the work, or the work stops happening.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Goal
|
|
11
|
+
|
|
12
|
+
What this is for, and why it exists. One paragraph.
|
|
13
|
+
|
|
14
|
+
**And one line that is easy to skip and should not be: what happens if we do
|
|
15
|
+
not do this at all?** If the honest answer is "nothing much", you have saved
|
|
16
|
+
yourself the work.
|
|
17
|
+
|
|
18
|
+
## 2. Scope
|
|
19
|
+
|
|
20
|
+
What may be changed, file by file where you can name them.
|
|
21
|
+
|
|
22
|
+
This is also the fence. What is listed is what may be touched.
|
|
23
|
+
|
|
24
|
+
**Out of scope:** name the things that must not be touched, not only the things
|
|
25
|
+
that may. "Everything else" is not an answer — name the ones somebody might
|
|
26
|
+
reasonably assume were included.
|
|
27
|
+
|
|
28
|
+
## 3. Must not happen
|
|
29
|
+
|
|
30
|
+
What is forbidden here. The standing ones always apply:
|
|
31
|
+
|
|
32
|
+
- do not write to version control
|
|
33
|
+
- do not decide anything; stop and report instead
|
|
34
|
+
- do not change a check because it failed
|
|
35
|
+
- where something cannot be established, say so rather than estimating
|
|
36
|
+
|
|
37
|
+
Add the ones specific to this work.
|
|
38
|
+
|
|
39
|
+
## 4. Done when
|
|
40
|
+
|
|
41
|
+
How anyone can tell it is finished — testable, not a matter of opinion.
|
|
42
|
+
|
|
43
|
+
**Something that cannot come out false is not a criterion.** Where a check
|
|
44
|
+
requires a clean working tree and yours will be dirty, record that here.
|
|
45
|
+
|
|
46
|
+
## 5. Checked by
|
|
47
|
+
|
|
48
|
+
`formwork check`, named as the whole thing.
|
|
49
|
+
|
|
50
|
+
You may add commands on top. You may never swap in a hand-picked few instead.
|
|
51
|
+
|
|
52
|
+
If the gate cannot prove the thing this work changes, say so here and name what
|
|
53
|
+
the real evidence will be.
|
|
54
|
+
|
|
55
|
+
## 6. The report must contain
|
|
56
|
+
|
|
57
|
+
The standing list in `formwork/templates/report.md`, plus anything else you
|
|
58
|
+
want back.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## The task version
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
Rename `foo` to `bar` in the parser. Nothing else.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
That is a complete brief. It has a goal, a scope, and a fence.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Decision record
|
|
2
|
+
|
|
3
|
+
One file per decision, in `docs/decisions/`, numbered in the order you accepted
|
|
4
|
+
them.
|
|
5
|
+
|
|
6
|
+
**This shape is borrowed, not invented.** It follows the widely used Markdown
|
|
7
|
+
decision-record format. The survey found decision records already solved, twice
|
|
8
|
+
over, so this kit does not design its own. If you want the fuller original, go
|
|
9
|
+
and take it — it has four sizes and translations.
|
|
10
|
+
|
|
11
|
+
**Only you write these.** An agent may copy in wording you have accepted, word
|
|
12
|
+
for word. It may never author one.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
```markdown
|
|
17
|
+
---
|
|
18
|
+
status: accepted
|
|
19
|
+
date: 2026-09-12
|
|
20
|
+
deciders: [who actually decided]
|
|
21
|
+
consulted: [who was asked, and answered]
|
|
22
|
+
informed: [who was told afterwards]
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<!-- Save as docs/decisions/0007-short-title.md — the number and the hyphen
|
|
26
|
+
are required. `formwork check` verifies the numbering. For the next free
|
|
27
|
+
number, run `formwork/check/checks/decision-ids --next .` and ignore any
|
|
28
|
+
other output it prints: run on its own it also scans the kit's test
|
|
29
|
+
fixtures. Do not count the files yourself, because gaps are allowed and
|
|
30
|
+
counting collides. -->
|
|
31
|
+
|
|
32
|
+
# 0007. Short title in plain words
|
|
33
|
+
|
|
34
|
+
## What made this a decision
|
|
35
|
+
|
|
36
|
+
The situation. What forced a choice. Keep it to what a stranger needs.
|
|
37
|
+
|
|
38
|
+
## What matters here
|
|
39
|
+
|
|
40
|
+
The two or three things any answer has to satisfy. Name them before the
|
|
41
|
+
options, or you will pick an option and then invent the reasons.
|
|
42
|
+
|
|
43
|
+
## Options we looked at
|
|
44
|
+
|
|
45
|
+
- **Option A** — one line.
|
|
46
|
+
- **Option B** — one line.
|
|
47
|
+
- **Do nothing** — one line. Always include this one.
|
|
48
|
+
|
|
49
|
+
## What we chose, and why
|
|
50
|
+
|
|
51
|
+
Chose **Option B**, because …
|
|
52
|
+
|
|
53
|
+
The "because" is the whole point of the file. A record without it is a note
|
|
54
|
+
saying what happened, and in six months you will not be able to tell whether
|
|
55
|
+
the reasoning still holds.
|
|
56
|
+
|
|
57
|
+
## What follows
|
|
58
|
+
|
|
59
|
+
**Good:**
|
|
60
|
+
- …
|
|
61
|
+
|
|
62
|
+
**Bad:**
|
|
63
|
+
- …
|
|
64
|
+
|
|
65
|
+
Both lists. A decision with no bad column was not a decision, it was a
|
|
66
|
+
preference.
|
|
67
|
+
|
|
68
|
+
## What would make us revisit this
|
|
69
|
+
|
|
70
|
+
The thing that would have to become true. If you cannot name one, say so.
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Numbering
|
|
76
|
+
|
|
77
|
+
The number comes from the check, not from counting:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
formwork/check/checks/decision-ids --next .
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Do not count the files.** Gaps are allowed, and counting collides with a
|
|
84
|
+
number that is already taken. Nobody
|
|
85
|
+
assigns it by hand and nobody guesses.
|
|
86
|
+
|
|
87
|
+
Gaps are fine. **Renumbering is never fine**, because references break quietly.
|
|
88
|
+
|
|
89
|
+
## Superseding
|
|
90
|
+
|
|
91
|
+
Do not delete and do not rewrite. Change the status to say it is superseded,
|
|
92
|
+
naming the number of the record that replaces it, and
|
|
93
|
+
leave the rest alone. The old reasoning is the useful part.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Predictions
|
|
2
|
+
|
|
3
|
+
**The challenger writes this before anybody proposes anything.**
|
|
4
|
+
|
|
5
|
+
It is the file the whole round rests on. A prediction written after the answer
|
|
6
|
+
is not a prediction, it is agreement.
|
|
7
|
+
|
|
8
|
+
Save it as `docs/rounds/<name>/predictions.md`.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## What I expect to go wrong
|
|
13
|
+
|
|
14
|
+
One line each. Be specific enough to be wrong.
|
|
15
|
+
|
|
16
|
+
**Bad:** it might not scale.
|
|
17
|
+
**Good:** past ten thousand rows the list screen will take more than two
|
|
18
|
+
seconds, because nothing pages it.
|
|
19
|
+
|
|
20
|
+
-
|
|
21
|
+
-
|
|
22
|
+
-
|
|
23
|
+
|
|
24
|
+
## What would show me I was wrong
|
|
25
|
+
|
|
26
|
+
For each one above, what result would change your mind.
|
|
27
|
+
|
|
28
|
+
This is the part people skip, and skipping it is how a prediction becomes a
|
|
29
|
+
thing to argue about later instead of a thing to check.
|
|
30
|
+
|
|
31
|
+
-
|
|
32
|
+
-
|
|
33
|
+
-
|
|
34
|
+
|
|
35
|
+
## What I think this round will decide
|
|
36
|
+
|
|
37
|
+
Your guess at the outcome, before you have read anybody else's work.
|
|
38
|
+
|
|
39
|
+
Write it even when you are unsure. Especially then.
|
|
40
|
+
|
|
41
|
+
## What nobody has asked
|
|
42
|
+
|
|
43
|
+
The question you expect to be missing from every file in this round.
|
|
44
|
+
|
|
45
|
+
## How confident am I
|
|
46
|
+
|
|
47
|
+
Plainly. "Fairly sure" and "guessing" are both useful and honest. A number you
|
|
48
|
+
invented is not.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
**When the round is over, come back and mark each prediction.** Right, wrong,
|
|
53
|
+
or never tested. That record is worth more than the round itself, because it is
|
|
54
|
+
the only way anybody learns whether this challenger is any good.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Report
|
|
2
|
+
|
|
3
|
+
What comes back when the work stops.
|
|
4
|
+
|
|
5
|
+
**Scale it.** A one-line task gets a three-line report. The full list below is
|
|
6
|
+
for a checkpoint.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What changed
|
|
11
|
+
|
|
12
|
+
Each file, one line on why.
|
|
13
|
+
|
|
14
|
+
## What was run
|
|
15
|
+
|
|
16
|
+
Every command that wrote something to disk. Installs, generated files,
|
|
17
|
+
migrations, anything that touched the world outside this repository.
|
|
18
|
+
|
|
19
|
+
## The check
|
|
20
|
+
|
|
21
|
+
The full result of `formwork check`, named as the whole thing rather than a
|
|
22
|
+
part of it.
|
|
23
|
+
|
|
24
|
+
If it is red, say so first. A red gate is the headline, not a footnote.
|
|
25
|
+
|
|
26
|
+
## Git status
|
|
27
|
+
|
|
28
|
+
Pasted, so you can see nothing was staged.
|
|
29
|
+
|
|
30
|
+
## The three that matter
|
|
31
|
+
|
|
32
|
+
Everything above is routine. These are what you are actually reading for.
|
|
33
|
+
|
|
34
|
+
- **Done but not asked for** — and why.
|
|
35
|
+
- **Asked for but not done** — and why.
|
|
36
|
+
- **Wrong in the brief** — anything that turned out not to match the files.
|
|
37
|
+
|
|
38
|
+
Work that followed the brief exactly needs no defence. What you are scanning
|
|
39
|
+
for happens at the edges of an instruction, and only these three show it.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## The task version
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
Renamed foo -> bar in parser.py and its two tests.
|
|
47
|
+
Gate green.
|
|
48
|
+
Nothing staged.
|
|
49
|
+
Nothing unasked, nothing skipped, brief was accurate.
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Four lines. Still answers all three.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Round record
|
|
2
|
+
|
|
3
|
+
What comes out of several agents arguing about one question.
|
|
4
|
+
|
|
5
|
+
A file in the repository, not a chat summary. You will read it months later.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
---
|
|
11
|
+
question: the one thing this round was called to settle
|
|
12
|
+
date: 2026-09-12
|
|
13
|
+
lead: who ran it
|
|
14
|
+
participants: [who argued]
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Round 004. The question in one line
|
|
18
|
+
|
|
19
|
+
## What is proposed, and who proposed it
|
|
20
|
+
|
|
21
|
+
Every part attributed to whoever put it forward.
|
|
22
|
+
|
|
23
|
+
Attribution is not politeness. Being able to follow any position back to its holder, and to the grounds they
|
|
24
|
+
held it on, is how you come to read the team.
|
|
25
|
+
|
|
26
|
+
## Where we disagreed
|
|
27
|
+
|
|
28
|
+
Per disagreement: the argument on each side, the evidence each of them carried,
|
|
29
|
+
the resolution, and **whether whoever lost was convinced or simply overruled.**
|
|
30
|
+
|
|
31
|
+
Where a participant shifted position, record what shifted it.
|
|
32
|
+
|
|
33
|
+
**An empty section here means the round failed.** Agreement with no friction is
|
|
34
|
+
the normal failure, not the normal success. Say what you did about it.
|
|
35
|
+
|
|
36
|
+
## What we turned down, and why
|
|
37
|
+
|
|
38
|
+
This is the section that stops a future round proposing the same thing again.
|
|
39
|
+
|
|
40
|
+
## Still open, for you
|
|
41
|
+
|
|
42
|
+
What the team could not settle, or should not have. Do not resolve these to
|
|
43
|
+
look decisive.
|
|
44
|
+
|
|
45
|
+
For each: the question, both positions, and what changes depending on the
|
|
46
|
+
answer.
|
|
47
|
+
|
|
48
|
+
## What this costs
|
|
49
|
+
|
|
50
|
+
- What it makes harder.
|
|
51
|
+
- What it rules out later.
|
|
52
|
+
- What fails quietly.
|
|
53
|
+
- Who maintains it in six months, and what they will have forgotten.
|
|
54
|
+
|
|
55
|
+
## What the round itself cost
|
|
56
|
+
|
|
57
|
+
Money, if you can read it from the tool. Time. How many exchanges.
|
|
58
|
+
|
|
59
|
+
Never estimate a figure you were unable to observe. State which quantities you
|
|
60
|
+
counted, and which stayed out of reach.
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Before the round starts
|
|
66
|
+
|
|
67
|
+
The challenger writes its predictions **first**, before reading anybody else's
|
|
68
|
+
work — what each participant will propose, where each will be weak, and what
|
|
69
|
+
the group will settle on without being pushed.
|
|
70
|
+
|
|
71
|
+
Afterwards it records what it got right and what it got wrong.
|
|
72
|
+
|
|
73
|
+
Reading somebody's work shapes any objection composed afterwards. Predictions
|
|
74
|
+
escape that. One that turns out accurate shows the weakness was built in rather
|
|
75
|
+
than accidental, which is a much stronger result.
|
|
76
|
+
|
|
77
|
+
This ordering is checkable and will be checked.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Prove the installer keeps its three promises, and refuses when it should.
|
|
3
|
+
|
|
4
|
+
The installer's promises are the reason anybody will run it on a project that
|
|
5
|
+
already has work in it:
|
|
6
|
+
|
|
7
|
+
it never needs a clean working tree
|
|
8
|
+
it never moves or deletes a file
|
|
9
|
+
it never demands a document
|
|
10
|
+
|
|
11
|
+
A promise nobody has tried to break is not evidence, so each one is tried here.
|
|
12
|
+
|
|
13
|
+
Python 3, standard library only, no dependencies.
|
|
14
|
+
"""
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import shutil
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
import tempfile
|
|
21
|
+
|
|
22
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
23
|
+
INSTALL = os.path.join(HERE, "install")
|
|
24
|
+
results = []
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def check(name, got, want, output=""):
|
|
28
|
+
ok = got == want
|
|
29
|
+
results.append(ok)
|
|
30
|
+
print(" [%s] %-52s expected %s, got %s"
|
|
31
|
+
% ("pass" if ok else "FAIL", name, want, got))
|
|
32
|
+
if not ok and output:
|
|
33
|
+
for line in output.strip().split("\n")[:8]:
|
|
34
|
+
print(" %s" % line)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Every test runs the real installer, and the real installer writes a
|
|
38
|
+
# fingerprint record outside the project. Without this the suite wrote
|
|
39
|
+
# twenty-one records into the developer's own home directory, one per test.
|
|
40
|
+
STATE = tempfile.mkdtemp(prefix="fw-test-state-")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def install(project, *args):
|
|
44
|
+
env = dict(os.environ)
|
|
45
|
+
env["FORMWORK_STATE_DIR"] = os.path.join(STATE, os.path.basename(project))
|
|
46
|
+
p = subprocess.run([sys.executable, INSTALL] + list(args),
|
|
47
|
+
capture_output=True, text=True, cwd=project, env=env)
|
|
48
|
+
return p.returncode, p.stdout + p.stderr
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def project(**dirs):
|
|
52
|
+
"""A throwaway project. dirs maps a path to its contents, or None for a dir."""
|
|
53
|
+
d = tempfile.mkdtemp(prefix="fw-install-")
|
|
54
|
+
for rel, content in dirs.items():
|
|
55
|
+
full = os.path.join(d, rel)
|
|
56
|
+
if content is None:
|
|
57
|
+
os.makedirs(full, exist_ok=True)
|
|
58
|
+
else:
|
|
59
|
+
os.makedirs(os.path.dirname(full), exist_ok=True)
|
|
60
|
+
open(full, "w", encoding="utf-8").write(content)
|
|
61
|
+
return d
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def tree(root):
|
|
65
|
+
out = {}
|
|
66
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
67
|
+
for fn in filenames:
|
|
68
|
+
full = os.path.join(dirpath, fn)
|
|
69
|
+
out[os.path.relpath(full, root)] = open(full, encoding="utf-8").read()
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
print("A fresh project")
|
|
74
|
+
p = project(**{".claude/": None})
|
|
75
|
+
code, out = install(p)
|
|
76
|
+
check("wires a project that has .claude/", code, 0, out)
|
|
77
|
+
check("wrote .formwork.toml", os.path.exists(os.path.join(p, ".formwork.toml")), True)
|
|
78
|
+
settings = os.path.join(p, ".claude", "settings.json")
|
|
79
|
+
check("wrote .claude/settings.json", os.path.exists(settings), True)
|
|
80
|
+
check("and it calls the guard",
|
|
81
|
+
"formwork/guard/git-boundary" in open(settings).read(), True)
|
|
82
|
+
|
|
83
|
+
print("Running it twice")
|
|
84
|
+
before = tree(p)
|
|
85
|
+
code, out = install(p)
|
|
86
|
+
check("second run is happy", code, 0, out)
|
|
87
|
+
check("second run changed nothing", tree(p) == before, True, out)
|
|
88
|
+
|
|
89
|
+
print("It does not need a clean working tree")
|
|
90
|
+
p = project(**{".claude/": None, "src/half-finished.py": "x = 1 # mid-edit\n",
|
|
91
|
+
".git/HEAD": "ref: refs/heads/main\n"})
|
|
92
|
+
code, out = install(p)
|
|
93
|
+
check("installs with uncommitted work sitting there", code, 0, out)
|
|
94
|
+
check("did not touch the work in progress",
|
|
95
|
+
open(os.path.join(p, "src", "half-finished.py")).read(),
|
|
96
|
+
"x = 1 # mid-edit\n")
|
|
97
|
+
|
|
98
|
+
print("It adds, and never removes")
|
|
99
|
+
p = project(**{".claude/settings.json": json.dumps({
|
|
100
|
+
"hooks": {"PreToolUse": [{"matcher": "Bash", "hooks": [
|
|
101
|
+
{"type": "command", "command": "my-own-hook"}]}]},
|
|
102
|
+
"somethingElse": {"kept": True}}, indent=2)})
|
|
103
|
+
code, out = install(p)
|
|
104
|
+
check("merges into settings that already exist", code, 0, out)
|
|
105
|
+
after = json.load(open(os.path.join(p, ".claude", "settings.json")))
|
|
106
|
+
commands = [h.get("command")
|
|
107
|
+
for entries in after["hooks"].values()
|
|
108
|
+
for e in entries for h in e.get("hooks", [])]
|
|
109
|
+
check("kept the hook that was already there", "my-own-hook" in commands, True)
|
|
110
|
+
check("kept settings it knows nothing about",
|
|
111
|
+
after.get("somethingElse"), {"kept": True})
|
|
112
|
+
check("added the guard", any("git-boundary" in c for c in commands), True)
|
|
113
|
+
check("kept a copy of the original",
|
|
114
|
+
os.path.exists(os.path.join(p, ".claude", "settings.json.before-formwork")),
|
|
115
|
+
True)
|
|
116
|
+
|
|
117
|
+
print("It demands no document")
|
|
118
|
+
p = project(**{".claude/": None})
|
|
119
|
+
code, out = install(p)
|
|
120
|
+
files = set(tree(p))
|
|
121
|
+
check("wrote only the two things it said it would",
|
|
122
|
+
files, {".formwork.toml", ".claude/settings.json"}, out)
|
|
123
|
+
|
|
124
|
+
print("When it cannot tell")
|
|
125
|
+
p = project(**{"src/main.py": "pass\n"})
|
|
126
|
+
code, out = install(p)
|
|
127
|
+
check("no runtime anywhere: refuses rather than guessing", code, 2, out)
|
|
128
|
+
check("and says how to tell it", "--runtime" in out, True, out)
|
|
129
|
+
|
|
130
|
+
p = project(**{".claude/": None, ".cursor/": None})
|
|
131
|
+
code, out = install(p)
|
|
132
|
+
check("two runtimes: refuses rather than picking one", code, 2, out)
|
|
133
|
+
|
|
134
|
+
print("An untested runtime is not reported as finished")
|
|
135
|
+
p = project(**{".codex/": None})
|
|
136
|
+
code, out = install(p)
|
|
137
|
+
check("exits 1, not 0", code, 1, out)
|
|
138
|
+
check("and says the word untested", "untested" in out, True, out)
|
|
139
|
+
|
|
140
|
+
print("It will not overwrite a configuration that disagrees")
|
|
141
|
+
p = project(**{".claude/": None,
|
|
142
|
+
".formwork.toml": '[bindings]\nruntime = "cursor"\n'})
|
|
143
|
+
code, out = install(p)
|
|
144
|
+
check("leaves the existing .formwork.toml alone", code, 1, out)
|
|
145
|
+
check("and it still says cursor",
|
|
146
|
+
'runtime = "cursor"' in open(os.path.join(p, ".formwork.toml")).read(),
|
|
147
|
+
True)
|
|
148
|
+
|
|
149
|
+
print("Broken JSON is left alone rather than replaced")
|
|
150
|
+
p = project(**{".claude/settings.json": "{ this is not json"})
|
|
151
|
+
code, out = install(p)
|
|
152
|
+
check("refuses to clobber it", code, 1, out)
|
|
153
|
+
check("and it is untouched",
|
|
154
|
+
open(os.path.join(p, ".claude", "settings.json")).read(),
|
|
155
|
+
"{ this is not json")
|
|
156
|
+
|
|
157
|
+
print("--dry-run")
|
|
158
|
+
p = project(**{".claude/": None})
|
|
159
|
+
code, out = install(p, "--dry-run")
|
|
160
|
+
check("says what it would do", code, 0, out)
|
|
161
|
+
check("and wrote nothing at all", tree(p), {}, out)
|
|
162
|
+
|
|
163
|
+
print("")
|
|
164
|
+
print("%d passed, %d failed" % (results.count(True), results.count(False)))
|
|
165
|
+
sys.exit(1 if False in results else 0)
|