acstack 0.0.0-stage → 0.47.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +389 -2
  4. package/THIRD_PARTY_NOTICES +14 -0
  5. package/TROUBLESHOOTING.md +225 -0
  6. package/agents/codex/frontend-verifier.toml +352 -0
  7. package/agents/codex/task-checker.toml +345 -0
  8. package/agents/codex/task-executor.toml +157 -0
  9. package/agents/codex/test-writer.toml +149 -0
  10. package/agents/frontend-verifier.md +351 -0
  11. package/agents/task-checker.md +344 -0
  12. package/agents/task-executor.md +156 -0
  13. package/agents/test-writer.md +148 -0
  14. package/assets/playwriter-bridge/build-launcher.ps1 +32 -0
  15. package/assets/playwriter-bridge/doctor.ps1 +450 -0
  16. package/assets/playwriter-bridge/launcher-src.cs +131 -0
  17. package/assets/playwriter-bridge/opener.js +330 -0
  18. package/assets/playwriter-bridge/opener.ps1 +63 -0
  19. package/assets/playwriter-bridge/register-tasks.ps1 +328 -0
  20. package/assets/playwriter-bridge/relay.ps1 +50 -0
  21. package/assets/playwriter-bridge/stop.ps1 +125 -0
  22. package/assets/playwriter-bridge/sync.ps1 +271 -0
  23. package/assets/playwriter-bridge/tunnel.ps1 +325 -0
  24. package/assets/playwriter-bridge/vm-free-port.sh +166 -0
  25. package/assets/terminal-workspace/client-keybindings.md +87 -0
  26. package/assets/terminal-workspace/config.example.yaml +334 -0
  27. package/assets/terminal-workspace/setup.sh +4404 -0
  28. package/cli/README.md +47 -0
  29. package/cli/bin/acstack.js +3 -0
  30. package/cli/dist/index.js +25984 -0
  31. package/cli/dist/index.js.map +7 -0
  32. package/cli/package.json +59 -0
  33. package/docs/adr/0001-ste-writing-standard.md +16 -0
  34. package/docs/adr/0002-version-gate-not-auto-bump.md +35 -0
  35. package/docs/adr/0003-handoff-ack-command.md +19 -0
  36. package/docs/adr/0004-dev-setup-per-host.md +13 -0
  37. package/docs/adr/0005-two-orchestrators-per-host.md +25 -0
  38. package/docs/adr/0006-document-opener-service.md +89 -0
  39. package/docs/adr/0007-lead-check-reads-the-transcript.md +87 -0
  40. package/docs/adr/0008-cli-audits-skill-interviews.md +37 -0
  41. package/docs/adr/0009-verify-the-write-not-the-writer.md +51 -0
  42. package/docs/adr/0010-promote-dev-to-main-by-fast-forward.md +115 -0
  43. package/docs/adr/0011-node-header-is-the-edge-encoding.md +20 -0
  44. package/docs/adr/0012-node-kind-is-a-closed-set-of-five.md +31 -0
  45. package/docs/adr/0013-the-diagram-renderer-is-pure.md +23 -0
  46. package/docs/adr/0014-reading-a-node-hides-its-generated-diagram.md +29 -0
  47. package/docs/adr/0015-a-node-id-links-to-its-issue-through-an-anchor.md +37 -0
  48. package/docs/adr/0016-the-map-root-carries-no-status-of-its-own.md +21 -0
  49. package/docs/adr/0017-call-impeccable-copy-only-its-questions.md +13 -0
  50. package/docs/adr/index.md +17 -0
  51. package/docs/config-reference.md +572 -0
  52. package/docs/dev-setups.md +164 -0
  53. package/docs/migrate-0.8.md +28 -0
  54. package/docs/playwriter-bridge-windows.md +15 -0
  55. package/docs/terminal-workspace-keys.md +137 -0
  56. package/docs/terminal-workspace.md +1780 -0
  57. package/docs/workflows.md +309 -0
  58. package/hooks/ac-writing-style-reminder.sh +86 -0
  59. package/hooks/context-pressure.sh +583 -0
  60. package/hooks/hooks.json +50 -0
  61. package/hooks/session-start-handoff.sh +165 -0
  62. package/hooks/session-start.sh +214 -0
  63. package/orchestrator/AGENTS.md +532 -0
  64. package/output-styles/ac-writing-style.md +286 -0
  65. package/package.json +50 -4
  66. package/pi/agents/frontend-verifier.md +357 -0
  67. package/pi/agents/task-checker.md +348 -0
  68. package/pi/agents/task-executor.md +160 -0
  69. package/pi/agents/test-writer.md +154 -0
  70. package/pi/extension.ts +723 -0
  71. package/profiles/fullstack-python.yaml +29 -0
  72. package/profiles/node-typescript.yaml +23 -0
  73. package/profiles/python.yaml +24 -0
  74. package/skills/archive/SKILL.md +160 -0
  75. package/skills/bootstrap/SKILL.md +109 -0
  76. package/skills/bridge/HANDOFF.md +93 -0
  77. package/skills/bridge/SKILL-VM.md +292 -0
  78. package/skills/bridge/SKILL-WINDOWS.md +526 -0
  79. package/skills/bridge/SKILL.md +121 -0
  80. package/skills/browser-verify/SKILL.md +229 -0
  81. package/skills/code-review/SKILL.md +44 -0
  82. package/skills/commit/SKILL.md +106 -0
  83. package/skills/compact-and-continue/SKILL.md +241 -0
  84. package/skills/explore/SKILL.md +149 -0
  85. package/skills/grilling/SKILL.md +134 -0
  86. package/skills/handoff/SKILL.md +250 -0
  87. package/skills/handoff/launch-herdr.md +197 -0
  88. package/skills/handoff/launch-without-herdr.md +30 -0
  89. package/skills/handoff/lead-check.md +59 -0
  90. package/skills/handoff/outcomes.md +162 -0
  91. package/skills/handoff/running-agent.md +50 -0
  92. package/skills/handoff/watch.md +115 -0
  93. package/skills/host/SKILL.md +247 -0
  94. package/skills/host/detect-host.sh +297 -0
  95. package/skills/host/herdr.md +79 -0
  96. package/skills/host/install-herdr-block.sh +467 -0
  97. package/skills/host/install-orca.sh +512 -0
  98. package/skills/host/notes.md +97 -0
  99. package/skills/host/orca.md +160 -0
  100. package/skills/host/questions.md +163 -0
  101. package/skills/host/report.md +42 -0
  102. package/skills/implement/SKILL.md +230 -0
  103. package/skills/implement/decision-log.md +62 -0
  104. package/skills/implement/design-pass.md +32 -0
  105. package/skills/implement/relaxed-tdd.md +28 -0
  106. package/skills/map/SKILL.md +250 -0
  107. package/skills/map/frontend.md +227 -0
  108. package/skills/map/trackers/files.md +74 -0
  109. package/skills/map/trackers/github.md +180 -0
  110. package/skills/map/visuals.md +387 -0
  111. package/skills/new-worktree/SKILL.md +155 -0
  112. package/skills/new-worktree/detect-orchestrator.sh +328 -0
  113. package/skills/new-worktree/detect-orchestrator.test.sh +894 -0
  114. package/skills/new-worktree/herdr.md +84 -0
  115. package/skills/new-worktree/orca.md +101 -0
  116. package/skills/pre-commit-review/SKILL.md +242 -0
  117. package/skills/pre-commit-review/auto-fix.md +19 -0
  118. package/skills/pre-commit-review/interactive.md +35 -0
  119. package/skills/quick-fix/SKILL.md +125 -0
  120. package/skills/record-context/SKILL.md +106 -0
  121. package/skills/setup/SKILL.md +250 -0
  122. package/skills/setup/browser.md +123 -0
  123. package/skills/setup/design-review.md +97 -0
  124. package/skills/setup/first-run.md +114 -0
  125. package/skills/setup/report.md +68 -0
  126. package/skills/setup/row-fixes.md +196 -0
  127. package/skills/setup/terminal-workspace.md +153 -0
  128. package/skills/sync/SKILL.md +171 -0
  129. package/skills/teardown-worktree/SKILL.md +245 -0
  130. package/skills/teardown-worktree/detector.md +39 -0
  131. package/skills/teardown-worktree/herdr.md +113 -0
  132. package/skills/teardown-worktree/orca.md +61 -0
  133. package/skills/terminal-workspace/SKILL.md +249 -0
  134. package/skills/terminal-workspace/components.md +36 -0
  135. package/skills/terminal-workspace/config-choices.md +69 -0
  136. package/skills/terminal-workspace/emulator-keys.md +48 -0
  137. package/skills/terminal-workspace/headless-clipboard.md +175 -0
  138. package/skills/terminal-workspace/turn-off-and-update.md +104 -0
  139. package/skills/test-conventions/SKILL.md +59 -0
  140. package/skills/visual-docs/SKILL.md +246 -0
  141. package/skills/writing/SKILL.md +231 -0
  142. package/skills/writing/examples.md +153 -0
  143. package/skills/writing/vocabulary.md +96 -0
@@ -0,0 +1,572 @@
1
+ # Project configuration
2
+
3
+ *Every key in `acstack/project.yaml`, the values it takes, its default, and what changes when you change it.*
4
+
5
+ One file, `acstack/project.yaml`, holds every setting that belongs to a project.
6
+
7
+ - **It names what to run**: the test, build, lint, typecheck, and format commands
8
+ - **It names where to look**: the source and test directories, and the frontend
9
+ - **It names how strict to be**: whether tests come first, and whether the agent may write code itself
10
+ - **Every skill and every agent reads it** before it runs anything
11
+ - **A key you leave out takes the default** this document gives it
12
+
13
+ Three terms first.
14
+
15
+ - **A key** is one dotted path into the file, such as `workflow.tdd.strict`
16
+ - **A default** is what ACStack does when the file states no value
17
+ - **A rung** is one token count at which the context-pressure hook speaks up, asking the session to choose continuity or freshness
18
+
19
+ ```mermaid
20
+ flowchart TD
21
+ FILE["acstack/project.yaml<br/>(written by /acstack:setup)"] --> WF["workflow.*<br/>switches, maps, handoff"]
22
+ FILE --> RUN["commands.*, directories.*,<br/>test_markers, venv"]
23
+ FILE --> FE["frontend.*<br/>dev server, browser"]
24
+ WF --> SKILLS["commit, grilling, map,<br/>archive, handoff"]
25
+ WF --> HOOK["hooks/context-pressure.sh"]
26
+ RUN --> AGENTS["orchestrator, test-writer,<br/>task-executor, task-checker"]
27
+ FE --> FV["frontend-verifier"]
28
+ HOST["~/.config/acstack/config.json<br/>host.* keys - see dev-setups.md"] --> FV
29
+ ```
30
+
31
+ | Step in the diagram | Detail |
32
+ | --- | --- |
33
+ | The file, and what writes it | section 1 |
34
+ | `profile` | section 2 |
35
+ | `workflow.*` switches | section 3 |
36
+ | `workflow.maps` | section 4 |
37
+ | `workflow.handoff` | sections 5 and 6 |
38
+ | `commands`, `directories`, `test_markers`, `venv` | section 7 |
39
+ | `frontend.*` | sections 8 and 9 |
40
+ | `host.*`, and what is not a key here | section 10 |
41
+ | Every default | section 11 |
42
+
43
+ ## 1. Where the file lives, and what writes it
44
+
45
+ *The setup skill writes the whole file. To change one key, run `acstack config set`.*
46
+
47
+ - `acstack/project.yaml` sits in the project root, beside `acstack/domain-map.yaml`
48
+ - `/acstack:setup` writes it
49
+ - On a fresh project the skill detects the project type, then writes every key from a profile under `profiles/`
50
+ - On a project that already has the file, the skill leads with the health check and offers a menu of changes
51
+ - `acstack config set <key> <value>` changes one key without an interview
52
+ - A key the file omits takes the default the tables below give it
53
+ - The `commands`, `directories`, and `frontend` keys have no such default
54
+ - The setup skill copies them from a profile
55
+ - An omitted one names nothing at all
56
+
57
+ Four commands read the file and change nothing.
58
+
59
+ | Command | What it prints |
60
+ | --- | --- |
61
+ | `acstack config show` | the profile, the commands, and what the project says about its browser |
62
+ | `acstack config check` | an audit of the project and the machine, exiting 2 when a required host axis has no value |
63
+ | `acstack config list` | every key the file states |
64
+ | `acstack design-gate` | whether a design review runs here, as `on` and exit 0 or one `off: <reason>` line per reason and exit 1 (section 3) |
65
+
66
+ ### 1.1. Which file a write lands in
67
+
68
+ *The key decides, and the key alone.*
69
+
70
+ - A `host.*` key describes the machine
71
+ - It goes to the global config at `~/.config/acstack/config.json`
72
+ - Every other key in this document describes the project
73
+ - It goes to `acstack/project.yaml`
74
+ - Section 10 covers the `host.*` keys
75
+ - `acstack config get <key>` and `acstack config unset <key>` read the same key table and route the same way
76
+
77
+ ### 1.2. What a command refuses
78
+
79
+ *Validation happens before either file opens, so a refusal writes nothing.*
80
+
81
+ - `acstack config set` refuses a value outside the set a table gives
82
+ - It refuses a key neither schema knows, and names the keys it does know
83
+ - It refuses a project key outside an ACStack project
84
+ - No file is there to hold it
85
+ - It holds a numeric key to the range its reader honours, not merely to being a number
86
+ - A count of turns or of tokens takes a whole number of 0 or more
87
+ - A CDP port takes a whole number from 1 to 65535
88
+ - A refusal names the range
89
+ - A value the reader would reject otherwise fails somewhere else entirely, long after the command that accepted it exited 0
90
+ - `get` and `unset` both refuse a key neither schema knows
91
+ - Both refuse a project key outside an ACStack project
92
+
93
+ ### 1.3. What `get` prints, and what it exits
94
+
95
+ *One exit code means "no value here", whichever file holds the key.*
96
+
97
+ - A string prints as written, and anything else prints as JSON
98
+ - The command exits 2 when the file states no value
99
+ - An unset `host.*` axis already gives that code
100
+ - A caller branches on "no value here" without knowing which file holds the key
101
+ - The message names the documented default where a table below gives a literal one
102
+ - The message names no default where a table describes the default instead
103
+ - There is none to name
104
+
105
+ ### 1.4. What `unset` leaves behind
106
+
107
+ *`acstack config unset` takes the key out. It prunes nothing else.*
108
+
109
+ - Removing a key the file does not state is not an error
110
+ - The command writes nothing at all
111
+ - The file stays byte for byte as it was
112
+ - A block the removal emptied keeps its key with nothing after the colon
113
+ - Every reader treats that as the default
114
+ - The command prunes nothing
115
+ - Pruning would take any comment on that block with it
116
+
117
+ ### 1.5. How a write preserves the rest of the file
118
+
119
+ *Two write paths exist. The result of the first decides which one runs.*
120
+
121
+ A write keeps the file as you wrote it. Comments, blank lines, key order, and quoting all survive.
122
+
123
+ - The command first tries to change the key in place, rewriting the bytes of the old value and leaving every other byte alone
124
+ - It then checks its own work
125
+ - It parses the file it just built and compares the data, in full, against the data the second path would have written
126
+ - It keeps the in-place change only when the two agree
127
+ - The write goes through the YAML document instead, when the two disagree or when the file it built does not parse
128
+ - That path carries every comment across
129
+ - It re-emits the whole file
130
+ - A run of spaces before an inline comment shortens to one
131
+ - A collection stated in flow style stays in flow style
132
+ - Line endings survive either path
133
+ - The command writes a file that arrived with CRLF back in CRLF
134
+ - Setting one key never rewrites the ending of every line
135
+ - `acstack config unset` writes the same two ways and checks itself the same way
136
+ - It splices the whole line out, indentation and trailing comment included, then compares the data before it keeps that
137
+
138
+ So the result decides which path a write takes, and never the shape of the key.
139
+
140
+ - A hand-edited file with ordinary one-line values gets the byte-for-byte path
141
+ - That is the common case
142
+ - It is the path such a file needs
143
+ - Anything the check cannot vouch for gets re-emitted
144
+ - The command checks that emission too, because the YAML document interface can itself write a line no parser reads back
145
+ - It costs only formatting when the check passes
146
+ - A write neither path can vouch for gets refused
147
+ - The command exits 1 and names the key
148
+ - The file stays byte for byte as it was
149
+ - Losing the alignment of a comment beats losing the key that follows a block scalar
150
+
151
+ ### 1.6. Four file shapes that get a decision rather than a crash
152
+
153
+ *A file ACStack itself produced always reads back.*
154
+
155
+ - A project that holds `acstack/` and no `project.yaml` gets the file created
156
+ - A block stating nothing takes a key underneath it, whether the file spells that block as a bare colon, as `null` or as `~`
157
+ - That is the state `acstack config unset` leaves behind
158
+ - The tool accepts its own output
159
+ - A document stating nothing at all takes a key too
160
+ - A file holding only `---` works the way an empty file already does
161
+ - The command refuses a key holding a value where the path wants a block
162
+ - It names that key
163
+ - Writing there would mean discarding the value already in the file
164
+ - That value is yours rather than the command's
165
+ - The file stays byte for byte as it was
166
+
167
+ Three refusals protect a file neither command can handle.
168
+
169
+ - Both `set` and `unset` refuse a file whose bytes are not valid UTF-8
170
+ - Neither one can re-encode a byte it could not decode
171
+ - A write would stand the replacement character in its place and report success
172
+ - Both refuse a file the process may not write
173
+ - Both refuse a document that is a scalar or a list, because no key hangs under either
174
+ - Both name the file and the reason
175
+
176
+ ### 1.7. What `acstack config list` reports
177
+
178
+ *The listing answers what the file states, and `get` answers what the schema knows.*
179
+
180
+ - The listing prints every key the file states, in the file's own order
181
+ - A stated key no schema knows carries `(unknown key)`
182
+ - `acstack config get` refuses that key
183
+ - `acstack config set` will not write it
184
+ - A block header holding nothing carries no mark where the schema knows keys under it
185
+ - `acstack config unset` leaves that line standing when it removes a block's last key
186
+ - The tool wrote `workflow:` itself, so the listing does not warn about it
187
+ - A key holding a dot is a key of its own
188
+ - YAML reads `"workflow.spec_sync": true` as one key whose name contains a dot
189
+ - The same file may state the nested spelling as well
190
+ - The listing prints both rows, and marks the literal one
191
+ - Every command walks the blocks, so all of them answer out of the nested spelling
192
+ - The `--json` output gives the project side as a list of rows, in the file's own order
193
+ - Each row reads `{"key": "<dotted path>", "value": ..., "known": true|false}`
194
+ - The rows are a list, so two rows can share one dotted path
195
+
196
+ ## 2. profile
197
+
198
+ *One word naming the defaults the file started from.*
199
+
200
+ | Key | Values | Default | What changes |
201
+ | --- | --- | --- | --- |
202
+ | `profile` | `node-typescript`, `python`, `fullstack-python` | none | `/acstack:setup` reads `profiles/<profile>.yaml` for its proposed commands and directories; `acstack config show` prints the name |
203
+
204
+ - The profile decides nothing after setup has run
205
+ - The commands and directories it proposed live in the file as their own keys, and those keys are what every later reader uses
206
+
207
+ ## 3. workflow: the switches
208
+
209
+ *Five booleans sit in the block. Each one changes how much discipline ACStack applies. A sixth key decides how the agent asks you a question.*
210
+
211
+ | Key | Values | Default | What changes |
212
+ | --- | --- | --- | --- |
213
+ | `workflow.spec_sync` | `true`, `false` | `true` | the `commit` skill runs spec sync before it commits, unless this says `false`; with `false` the living specs under `acstack/specs/` stop tracking the code |
214
+ | `workflow.tdd.strict` | `true`, `false` | `true` | `false` runs the task-executor before the test-writer; all three phases still run, and the checker's gate changes with the key |
215
+ | `workflow.tdd.orchestrator_strict` | `true`, `false` | `true` | `false` lets the coordinating session read and write code itself for small tasks, instead of handing every piece to a subagent |
216
+ | `workflow.frontend_verification` | `true`, `false` | `false` | the frontend-verifier runs only when this is `true`, the file configures a `frontend` block, and the change touched frontend files |
217
+ | `workflow.design_review` | `true`, `false` | `false` | a design review runs on a UI change only when this is `true`; it needs the impeccable plugin installed and switched on, which `acstack config check` reports on |
218
+
219
+ How a boolean reads:
220
+
221
+ - Every boolean key in this document takes the same six spellings as a `host.*` axis, and case does not matter
222
+ - `true`, `yes`, and `on` all read as true
223
+ - `false`, `no`, and `off` all read as false
224
+ - The tables show `true` and `false`
225
+ - That is what the file holds after `acstack config set` writes one
226
+ - `workflow.spec_sync` reads as on unless the file states one of the three false spellings outright
227
+ - The `commit` skill treats a missing key and an explicit `true` the same way
228
+ - `workflow.frontend_verification` reads as off unless the file states one of the three true spellings outright
229
+ - The frontend-verifier is the only reader
230
+ - `orchestrator/AGENTS.md` and the `browser-verify` skill both run it on an explicit `true` alone
231
+ - The defaults block in section 11 shows `true` for this key
232
+ - That is what `/acstack:setup` writes for a project with a frontend
233
+ - That is what setup writes, and not what an absent key means
234
+ - `workflow.design_review` reads as off unless the file states one of the three true spellings outright
235
+ - `acstack design-gate` is the one command that answers whether the gate is on
236
+ - On needs both halves: the key true, and the impeccable plugin installed
237
+ - A plugin set `false` under `enabledPlugins` in `settings.json` counts as not installed
238
+ - It reads this file, the plugin registry, and `settings.json`, runs nothing, and never exits 2
239
+ - An impeccable older than major 4, or one whose version it cannot read, warns on stderr and stays on
240
+ - `--json` prints the same answer as `{"on": ..., "reasons": [...], "impeccable": ...}`
241
+
242
+ ### 3.1. What `workflow.tdd.strict` moves
243
+
244
+ *`workflow.tdd.strict` reorders the first two phases. It drops none of them.*
245
+
246
+ - The default `true` runs test-writer, then task-executor, then task-checker
247
+ - A `false` runs task-executor, then test-writer, then task-checker
248
+ - All three agents run either way
249
+ - Six files read the key: `orchestrator/AGENTS.md`, `skills/implement/SKILL.md`, `agents/test-writer.md`, `agents/task-executor.md`, `agents/task-checker.md`, and `skills/setup/SKILL.md`
250
+ - Relaxed TDD, the name for `false`, means nobody writes the tests first
251
+ - It does not mean the work goes unverified
252
+ - The rule of a checker after every executor does not move
253
+
254
+ Relaxed costs the test-writer part of its independence.
255
+
256
+ - Under `true` it cannot read an implementation
257
+ - None exists when it runs
258
+ - Under `false` the implementation sits in the working tree beside it, and only its instructions keep it from reading that code
259
+ - The two settings do not give equal independence, and `agents/test-writer.md` states the weaker guarantee as the cost of relaxed
260
+
261
+ What the checker gates on moves with the key.
262
+
263
+ - Under `true` the checker holds the task's new tests to failing before the executor and passing after
264
+ - Under `false` the implementation came first
265
+ - Nothing can show those tests failing without it
266
+ - The checker then holds the new tests to existing and passing
267
+ - It holds the full suite to passing
268
+ - The test count to not dropping
269
+
270
+ ### 3.2. How the agent asks you a question
271
+
272
+ | Key | Values | Default | What changes |
273
+ | --- | --- | --- | --- |
274
+ | `workflow.grilling.questions` | `tool`, `inline` | `tool` | `tool` asks through the host's question tool, one call per round; `inline` asks in prose |
275
+
276
+ - `tool` falls back to inline prose for any round it cannot hold
277
+ - Two things exceed it: a round of more than four questions, and any question with no discrete options
278
+
279
+ ## 4. workflow.maps
280
+
281
+ *Two keys record where a map's nodes live, and what happens to those nodes at the end.*
282
+
283
+ A **map** is a graph of question nodes and work nodes. A **tracker** is the backend that stores those nodes.
284
+
285
+ | Key | Values | Default | What changes |
286
+ | --- | --- | --- | --- |
287
+ | `workflow.maps.tracker` | `github`, `files` | none | the `map`, `implement`, and `archive` skills read the matching backend document and use its operations |
288
+ | `workflow.maps.persist` | `true`, `false` | `false` | `true` moves a cleared map's node files to `acstack/archive/[YYYY-MM-DD]-[name]/nodes/`; `false` deletes `acstack/maps/<name>/` |
289
+
290
+ - An absent `workflow.maps.tracker` means nobody has chosen yet
291
+ - The `map` skill then picks at the moment it first writes a map's nodes down, confirms the choice with the user, and writes the key
292
+ - It recommends `github` when `gh` holds a login for the repository's GitHub remote, and `files` otherwise
293
+ - Anything that resets the `workflow` block to defaults has to keep `workflow.maps` intact
294
+ - The key records where the node records already are
295
+ - Dropping it strands every one of them on a backend no later session looks at
296
+ - `workflow.maps.persist` acts on the files tracker only
297
+ - The GitHub tracker has nothing to dispose of
298
+ - A closed issue is already the archive
299
+ - The default deletes, because keeping the nodes leaves a second copy of every decision and the two copies drift apart
300
+
301
+ ## 5. workflow.handoff: launching and acknowledging
302
+
303
+ *Three keys govern the handoff skill, which moves work from one session to another.*
304
+
305
+ | Key | Values | Default | What changes |
306
+ | --- | --- | --- | --- |
307
+ | `workflow.handoff.agent` | a command template | `claude` | the command the `handoff` skill runs to launch a target session when herdr is absent; a template rather than a bare name, so a model flag or a permission-mode flag fits |
308
+ | `workflow.handoff.ack_turns` | a whole number, 0 or more | `5` | how many turns after delivery the sender waits before it reports the target as silent; `--fresh` doubles it |
309
+ | `workflow.handoff.self_invoke` | `true`, `false` | `true` | `false` stops the agent from invoking the `handoff` skill on its own initiative; it then tells the user a handoff looks warranted and asks first |
310
+
311
+ - `workflow.handoff.self_invoke` gets a behavioural check at the skill's first step, rather than a frontmatter flag
312
+ - Frontmatter model-invocation flags are static
313
+ - No project could turn one off for itself
314
+
315
+ ## 6. workflow.handoff: the context-pressure ladder
316
+
317
+ *Five keys place the rungs at which `hooks/context-pressure.sh` asks the session to choose continuity or freshness.*
318
+
319
+ How the hook decides to speak:
320
+
321
+ - The hook runs on every stop
322
+ - It reads the last assistant record in the transcript and adds three usage fields: `input_tokens`, `cache_read_input_tokens`, and `cache_creation_input_tokens`
323
+ - That sum is the context the turn sent to the model
324
+ - The hook then finds the highest rung at or below that number
325
+ - It speaks when that rung sits above the rung it last spoke for
326
+ - It records the new rung
327
+ - So a long session gets one nudge per rung, not one per stop
328
+
329
+ ### 6.1 The default thresholds, and the gap above them
330
+
331
+ | Key | Values | Default | What changes |
332
+ | --- | --- | --- | --- |
333
+ | `workflow.handoff.nudge_warn` | a whole number of tokens, 0 or more | `200000` | the ladder's first rung, and the wording below the severe mark; a small-context model wants a lower value |
334
+ | `workflow.handoff.nudge_severe` | a whole number of tokens, 0 or more | `500000` | the ladder's second rung, and the mark at or above which the wording turns urgent |
335
+ | `workflow.handoff.nudge_step` | a whole number of tokens, 0 or more | `100000` | how far apart the rungs sit above the last one, so a session that keeps growing keeps hearing from the hook |
336
+
337
+ Three guards protect the ladder from a value that would break it.
338
+
339
+ - The hook raises a `nudge_warn` below 1 to 1
340
+ - A rung of 0 would block every single turn
341
+ - It raises `nudge_severe` to `nudge_warn` when the file states a lower one
342
+ - A severe mark below the warn mark could never fire
343
+ - It treats a `nudge_step` below 1 as an instruction to stop the ladder at its top rung
344
+
345
+ What the nudge asks the agent to choose:
346
+
347
+ - Continuity means useful working context stays in the same session
348
+ - A large window can rely on billion-context
349
+ - ACP compression is an in-place host option, not something ACStack calls itself
350
+ - Freshness means a fresh context window or independent read would help more
351
+ - That route is `/acstack:handoff`
352
+ - Severe wording makes the same checkpoint urgent
353
+ - It never halts the run
354
+ - Both tiers close with the same clause
355
+ - The agent decides for itself
356
+ - It asks the user only when it genuinely cannot tell
357
+ - It hands over without asking when the user already agreed to a handoff earlier in the session
358
+
359
+ ### 6.2 An explicit ladder
360
+
361
+ | Key | Values | Default | What changes |
362
+ | --- | --- | --- | --- |
363
+ | `workflow.handoff.context_thresholds` | a list of token counts or percentage strings | none | replaces `nudge_warn` and `nudge_severe` as the ladder's rungs, as in `[150000, 300000]` or `["40%", "70%"]` |
364
+
365
+ - Two YAML shapes work: flow style on one line, `[150000, 300000]`, and block style with one `- item` per line
366
+ - The hook sorts the rungs and drops duplicates
367
+ - It drops any entry it cannot read as a number or a percentage, and any entry below 1
368
+ - It falls back to the `nudge_warn` and `nudge_severe` rungs with no usable entry left
369
+
370
+ All three of the other keys still act under an explicit ladder.
371
+
372
+ - `nudge_step` extends the ladder past the last listed rung, exactly as it does by default
373
+ - `nudge_severe` places no rung any more, and only picks the wording
374
+ - A nudge reads as severe once the rung that fired sits at or above it
375
+ - `nudge_warn` places no rung either
376
+ - It still moves that wording threshold
377
+ - The hook raises `nudge_severe` to meet a higher `nudge_warn` before it builds the ladder
378
+
379
+ ### 6.3 What a percentage is a percentage of
380
+
381
+ | Key | Values | Default | What changes |
382
+ | --- | --- | --- | --- |
383
+ | `workflow.handoff.context_window` | a whole number of tokens, 1 or more | inferred from the model id | the number a percentage rung resolves against, as in `"40%"` of `200000` |
384
+
385
+ - A positive `context_window` wins outright
386
+ - The hook infers the window from the model id with no value, or one of 0 or less
387
+ - That id comes from the same transcript record as the token count
388
+
389
+ Three rules decide the window, in order.
390
+
391
+ | The model id | The window |
392
+ | --- | --- |
393
+ | carries the `[1m]` marker | 1,000,000 |
394
+ | names the haiku line, or a 4.x family such as `claude-opus-4-8`, `claude-haiku-4-5-...` or `claude-sonnet-4-5` | 200,000 |
395
+ | anything else, including the 5.x families and a record with no model id at all | 1,000,000 |
396
+
397
+ - The `[1m]` marker outranks the second rule
398
+ - A million-token model says so in its own id
399
+ - `claude-sonnet-4-5[1m]` resolves to 1,000,000 rather than 200,000
400
+
401
+ ### 6.4 Why only the session's own stop
402
+
403
+ *The hook nudges the session that could act on it. It nudges nobody else.*
404
+
405
+ - `hooks/hooks.json` registers it for two events only
406
+ - A `Stop` event fires when the session's own turn finishes
407
+ - `SessionStart` fires when a session opens
408
+ - A `SubagentStop` payload, should one ever arrive, exits silently and creates no state
409
+ - Neither a subagent nor an in-process teammate can hand the user's work over
410
+ - A nudge there would only spend a turn
411
+ - The measurement follows the same rule
412
+ - The hook skips every transcript record marked `isSidechain`
413
+ - A subagent's tokens are not this session's context
414
+
415
+ ### 6.5 The ladder is per session, and a compaction resets it
416
+
417
+ *Two files per session hold the state, and a compaction deletes both.*
418
+
419
+ - The hook keeps both files under `$ACSTACK_CONTEXT_PRESSURE_DIR`
420
+ - That defaults to a `acstack-context-pressure` directory under `TMPDIR`, or under `/tmp`
421
+ - The `.last` file holds the rung the hook last spoke for
422
+ - The `.quiet` file is a marker
423
+ - The `handoff` and `compact-and-continue` skills leave it behind when they finish
424
+ - The hook stays silent for the rest of the session while it exists
425
+ - A compaction deletes both files
426
+ - The ladder starts again at its first rung
427
+ - A compaction throws the session's context away and rebuilds it much smaller
428
+ - The recorded rung then describes context that no longer exists
429
+ - The quiet marker records a handover the fresh context knows nothing about
430
+
431
+ Two limits are worth knowing.
432
+
433
+ - The hook reads these five keys with its own small YAML parser
434
+ - pyyaml may not exist on the machine
435
+ - That parser hands each value to Python's `int()` and accepts whatever `int()` accepts
436
+ - A `_` digit separator works
437
+ - `200_000` reads as 200,000
438
+ - Exponent and float notation do not
439
+ - `2e5` and `2.0` fall back to the default
440
+ - The hook opens `acstack/project.yaml` relative to the working directory
441
+ - A session running outside the project root therefore gets the defaults, whatever the file says
442
+
443
+ ## 7. commands, directories, test_markers and venv
444
+
445
+ *What every agent runs, and where every agent looks.*
446
+
447
+ | Key | Values | Default | What changes |
448
+ | --- | --- | --- | --- |
449
+ | `commands.test` | a shell command, or `null` | from the profile | the test-writer, task-executor, and task-checker run it; the task-checker fails a task whose tests do not pass |
450
+ | `commands.test_collect` | a shell command, or `null` | from the profile | the task-checker counts tests with it and compares the count against `.test-baseline`; `null` skips the baseline check |
451
+ | `commands.build` | a shell command, or `null` | from the profile | the task-checker verifies the build with it |
452
+ | `commands.lint` | a shell command, or `null` | from the profile | the pipeline lints with it, usually with a fix flag |
453
+ | `commands.typecheck` | a shell command, or `null` | from the profile | the pipeline type-checks with it |
454
+ | `commands.format` | a shell command, or `null` | from the profile | the pipeline runs it over the files the work touched, immediately before the task-checker; `null` means no format step |
455
+ | `directories.source` | a path | `src/` | where `/acstack:setup` explores to propose the domain map, and where the pipeline writes source |
456
+ | `directories.tests` | a path | `tests/` | where the test-writer writes tests, and the one tree the task-executor may not touch |
457
+ | `test_markers.exclude` | comma-separated markers, or `null` | `null` for Node, `slow,integration` for Python | which markers a default test run leaves out, as in `pytest -m` |
458
+ | `venv.activate` | a shell command, or `null` | `null` for Node, `source .venv/bin/activate` for Python | every agent prefixes each command with it when the file states one |
459
+
460
+ - One rule places the format step under both TDD settings
461
+ - It runs immediately before the task-checker
462
+ - Under relaxed TDD it falls after the test-writer and formats the new tests too
463
+ - A non-zero exit from `commands.format` gets reported, and never counts as a failed implementation
464
+ - Formatting is not correctness
465
+ - The task-checker runs afterwards, and its own gate decides whether the work passes
466
+ - `acstack config show` lists a `commands` entry only when its value is a non-empty string
467
+ - A `null`, a number or a nested block does not name a command anyone could run
468
+ - The listing leaves it out
469
+
470
+ ## 8. frontend
471
+
472
+ *The block's presence is itself a setting: with no `frontend` block, this project drives no browser.*
473
+
474
+ | Key | Values | Default | What changes |
475
+ | --- | --- | --- | --- |
476
+ | `frontend` | a block, or `null` | absent | a block makes the project a frontend project: the frontend-verifier becomes eligible, and `acstack config check` starts asking for the three browser axes and the frontend files |
477
+ | `frontend.directory` | a path | `frontend/` | where the frontend lives |
478
+ | `frontend.dev_server_url` | a URL | `http://localhost:3000` | the address the frontend-verifier opens |
479
+ | `frontend.dev_server_check` | a shell command | a `curl` of the URL | how the frontend-verifier decides the dev server is up |
480
+ | `frontend.helpers_dir` | a path | `<frontend.directory>/tests/helpers/` | where `VERIFICATION-KNOWLEDGE.md` lives; the `verification-knowledge` row of the check fails when the file is missing, and when the key states nothing |
481
+ | `frontend.commands.dev` | a shell command | none | how the frontend-verifier starts the dev server; without it the verifier cannot start one itself |
482
+ | `frontend.commands.build` | a shell command | from the profile | the frontend build the pipeline runs |
483
+ | `frontend.commands.lint` | a shell command | from the profile | the frontend lint the pipeline runs |
484
+ | `frontend.commands.test` | a shell command | from the profile | the frontend tests the pipeline runs |
485
+
486
+ - An explicit `frontend: null` reads the same as no block at all
487
+ - The CLI counts a frontend only when the key holds something other than `null`
488
+
489
+ ## 9. frontend.browser and the CDP port
490
+
491
+ *Three keys state what this project would like. The machine states what it can actually do.*
492
+
493
+ The Chrome DevTools Protocol (CDP) is the wire protocol the verifier drives a browser over. These three keys state a preference, and the `host.browser.*` axes in the global config say whether the machine can honour it. [dev-setups.md](dev-setups.md) documents those axes.
494
+
495
+ | Key | Values | Default | What changes |
496
+ | --- | --- | --- | --- |
497
+ | `frontend.browser.mode` | `remote`, `headless`, `local` | none | the mode `acstack config browser-mode` resolves for the frontend-verifier, weighed against the three host axes; it also picks the `cdp_port` default |
498
+ | `frontend.browser.fallback` | `none`, `remote`, `headless`, `local` | none | only `none` acts: it forbids another mode from standing in when the host does not declare the preferred one |
499
+ | `frontend.browser.cdp_port` | a whole number from 1 to 65535 | `19988` under `mode: remote`, `9555` otherwise | the port the frontend-verifier dials, and the port the `agent-browser-json` row of the check requires `agent-browser.json` to name |
500
+
501
+ The two defaults come from two different browsers.
502
+
503
+ - The mode `remote` means the Playwriter bridge, which hardcodes port 19988 and offers no way to change it
504
+ - Every other mode means a browser this machine launched itself, with the usual debug port 9555
505
+ - The default is 9555 with no `mode` at all
506
+
507
+ The port default applies wherever the CLI needs a number.
508
+
509
+ - `acstack config check` holds a project that states no `cdp_port` to the default its mode implies
510
+ - It fails when `agent-browser.json` names a different port
511
+ - `acstack config show` behaves the other way, printing only what the project states
512
+ - "What did the project say" and "which port do we dial" are different questions
513
+
514
+ Only the literal `none` gives `frontend.browser.fallback` any effect.
515
+
516
+ - Every other value, `headless` included, leaves the host's own order alone: remote first, then headless, then local
517
+ - A project that wanted a particular mode would have named it as its `mode`
518
+ - A second mode name here changes nothing
519
+ - The reader ignores any word other than `none`, and `acstack config set` still holds the key to the four names above
520
+ - A typo would read as a fallback the project never asked for
521
+ - The command refuses it rather than storing it
522
+
523
+ ## 10. What is not a project.yaml key
524
+
525
+ *Two sets of settings look like they belong here. Neither set does.*
526
+
527
+ - The `outputStyle` setting is not a `project.yaml` key
528
+ - Claude Code owns it
529
+ - It lives in three settings files
530
+ - `.claude/settings.local.json` wins over `.claude/settings.json`, which in turn wins over `~/.claude/settings.json`
531
+ - `acstack config check` reads all three and reports which file selects which style
532
+ - No ACStack command writes it, and `acstack config set` refuses it as a key neither schema knows
533
+ - `/acstack:setup` offers to write it for you, and asks first
534
+ - The `host.*` keys are not `project.yaml` keys either
535
+ - They describe the machine rather than the project
536
+ - The same repository opens on several machines with different setups
537
+ - They live in the global config at `~/.config/acstack/config.json`
538
+ - `acstack config set` is what writes them
539
+ - [dev-setups.md](dev-setups.md) documents all nine axes
540
+
541
+ ## 11. Every default in one block
542
+
543
+ *What ACStack does with a `project.yaml` that states none of these keys.*
544
+
545
+ ```yaml
546
+ workflow:
547
+ spec_sync: true
548
+ grilling:
549
+ questions: tool
550
+ tdd:
551
+ strict: true
552
+ orchestrator_strict: true
553
+ # /acstack:setup writes true for a project with a frontend.
554
+ # An absent key reads as false. Section 3 says why.
555
+ frontend_verification: true
556
+ design_review: false
557
+ maps:
558
+ # tracker has no default: the map skill picks and writes it
559
+ persist: false
560
+ handoff:
561
+ agent: "claude"
562
+ ack_turns: 5
563
+ self_invoke: true
564
+ nudge_warn: 200000
565
+ nudge_severe: 500000
566
+ nudge_step: 100000
567
+ # context_thresholds and context_window have no default
568
+ ```
569
+
570
+ - The `commands`, `directories`, `test_markers`, `venv`, and `frontend` blocks have no defaults of their own
571
+ - `/acstack:setup` copies them from the profile it detects
572
+ - Section 7 gives the values each profile proposes