gcf-aif-ext 0.7.10 → 0.7.13

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/README.md CHANGED
@@ -1,216 +1,223 @@
1
- # gcf-aif-ext
2
-
3
- The Game Context Framework as an [AI Factory](https://github.com/lee-to/ai-factory) extension: a documentary layer for building games with an AI agent, where the documents hold **state instead of history**.
4
-
5
- A Design Model: the design lives in `gcf/design/<system>.md` as units with typed slots — what the GDD says, quoted and anchored, and `TBD` for everything it does not. Every piece of work — a mechanic, a bug, tech, an asset, a spike, a release — is a work item `gcf/work/<id>.md` with a `type` and a `gate`: who closes it, the human after play (`verdict`), a check (`auto`), or the human without a build (`decision`). A unit taken into work moves into its item with the same body; a soft unit collapses into its subsystem's document on a human playtest verdict and the item ceases to exist. `gcf/LIVE.md` is a short snapshot of what the game is now — capabilities and known issues. `gcf/inbox.md` holds raw input waiting for triage. A history and a graveyard keep how each id ended and why things were killed. Read rules keep the token cost of a task flat as the project ages.
6
-
7
- The methodology is [Methodology.md](https://github.com/VirtualMaestro/GCF/blob/main/docs/Methodology.md) (RU).
8
-
9
- Runtime-agnostic — everything here works on any AI Factory runtime. The Claude-Code-specific orchestration lives in [`gcf-harness-aif-ext`](../gcf-harness-aif-ext), which is optional.
10
-
11
- ## Requirements
12
-
13
- - AI Factory 2.x
14
- - Node.js ≥ 22.14
15
- - git (the enforcement gate is a pre-commit hook)
16
-
17
- ## Install
18
-
19
- Install into an initialized AI Factory project from npm or a local checkout:
20
-
21
- ```bash
22
- ai-factory init
23
- ai-factory extension add gcf-aif-ext # npm
24
- # ai-factory extension add ../GCF/packages/gcf-aif-ext # local checkout
25
- ```
26
-
27
- This installs the 9 skills into each configured runtime, registers the `gcf` command and appends the injections to `aif-plan`, `aif-review` and `aif-commit`. Then set up the project — two steps, run once:
28
-
29
- ```bash
30
- ai-factory gcf init # seeds gcf/, cnlp.config.json and .gitattributes (PILLARS.md is written by /gcf-init)
31
- ai-factory gcf hooks-install # arms the pre-commit gate
32
- ```
33
-
34
- `gcf init` is idempotent: it creates what is missing and overwrites nothing. Among what it seeds: `gcf/work/` for the work items, `gcf/inbox.md` for raw input, `gcf/LIVE.md` for the project snapshot, `gcf/history.md` and `gcf/graveyard.md` — the two files that record how an id's life ended — and three lines in `.gitattributes` (`gcf/history.md merge=union`, `gcf/graveyard.md merge=union`, `gcf/inbox.md merge=union`) so that two branches appending different lines to those files never conflict there.
35
-
36
- It then offers the optional [harness](../gcf-harness-aif-ext) — orchestration and a readset guard for **Claude Code only** — and on yes runs `ai-factory extension add gcf-harness-aif-ext` and `ai-factory gcf-harness init`. It asks only in a terminal and only when `.ai-factory.json` lists the Claude Code agent; an agent or CI run is never asked and installs nothing. `--harness` installs it without asking, `--no-harness` skips the question.
37
-
38
- `hooks-install` is a separate step because an AI Factory manifest can declare skills, commands and injections but cannot install a git hook — and because arming a gate that can break your commits is your call, not the extension's. It needs a git repository, and it will not overwrite a `pre-commit` it did not write: if husky or lefthook already owns the hook, it prints the one line to chain from it instead. Without the hook GCF still works; nothing enforces it at commit time.
39
-
40
- Then `/gcf-vision` for a brand-new game, `/gcf-adopt` for a project that already has code, `/gcf-ingest <gdd.md>` for an existing design doc, or `/gcf-card` to start work by hand.
41
-
42
- ## Upgrade
43
-
44
- ```bash
45
- ai-factory extension update gcf-aif-ext # pulls the new version
46
- ai-factory extension update --force # refresh even if the version is unchanged
47
- ```
48
-
49
- Updating preserves `gcf/`, `cnlp.config.json` and the pre-commit hook. Run `ai-factory gcf init` again after an update: it adds only what a newer version seeds and the project lacks.
50
-
51
- On Windows, run `extension add` and `extension update` from PowerShell or cmd, not Git Bash: the installer unpacks with `tar`, and Git Bash's GNU tar reads `C:\…` as a remote host (`Cannot connect to C: resolve failed`). The Windows `tar.exe` handles it.
52
-
53
- ### 0.7.10
54
-
55
- - Lessons: what got in an agent's way, asked at the end of its session (Methodology §5.2.2). `gcf lessons prompt <id>` prints the questions: tools and their workarounds, project rules that are missing or conflict, trouble with GCF itself, and, with `lessons.template` in `gcf/config.yml`, what the project's template should learn; `lessons.questions` adds the operator's own, `lessons.off: true` turns them off. `gcf lessons add <id>` records each answer as an inbox entry with the source `lesson:<kind>:<item>`; `gcf board --json` gives such an entry `source: lesson`, `kind` and `item`, and each item its `lessons` count. The harness 0.9.2 asks them in a turn of its own after each session, so they stay out of the agent's working context; `/gcf-run` without the harness asks them at its end. `/gcf-triage` proposes a fix for each.
56
-
57
- - `gcf verify` kills a `dod.test` or `dod.build` command that runs longer than `dod_timeout_min` minutes (60 by default, 0 for no limit), with every process it started, and fails the DoD: `dod.test timed out after 60 min (dod_timeout_min), killed: <command>`. Before, a hung engine held the check, and a run with it, for hours.
58
- - `gcf prioritize` takes an item made with `gcf item new` and never committed, and commits it with its rank. Before, it refused the item as an uncommitted file. An item with uncommitted edits is still refused.
59
- - `gcf playtest session` lists the items awaiting a decision in a section of their own: before, they were missing from the sheet.
60
- - `plan_passes` defaults to 3 (was 5). A plan converges when an improve pass finds nothing blocking (Methodology §5.2.1); a project that sets `plan_passes` keeps its value.
61
-
62
- ### 0.7.9
63
-
64
- - `gcf done`, `gcf drop` (a design unit), `gcf pillar rename|retire`, `gcf phase next` and `gcf question answer` hold the main lock from their first read to their commit, as the work item moves do. Before, two at once could both pass their checks and write over each other: an answer lost, a unit in the history twice.
65
- - `gcf claim` says the mark lives 2 hours without a gcf command, as it does (it said 30 minutes).
66
-
67
- ### 0.7.8
68
-
69
- - An open question's field written as a YAML block (`assumed: |` or `>`, and the lines under it) reads as its text: before, `gcf board --json` gave the block sign `|`. Every reader of `gcf/open-questions.md` (the board, `design check`, the audit, `context`, the index) reads the fields the same way:
70
- - a field is a line at the record's field indent, so a block line such as `answer: x` is text: before, it made a D1 read as answered;
71
- - a blank line inside a block no longer ends the record: before, the fields after it were lost;
72
- - `gcf question answer` replaces an old answer written as a block whole: before, it replaced only the block's first line;
73
- - an empty `answer:` is no answer: `design check` now holds the D1's units, as the board already showed it unanswered.
74
-
75
- ### 0.7.7
76
-
77
- - `ai-factory gcf config [--json]` lists the human's settings of `gcf/config.yml` with what each means; `gcf config set <key> --value=<v>` sets one, checked by the rules a read applies, the comments kept, one `gcf(config)` commit. An empty number goes back to its default. `gcf board --json` carries them as `settings`.
78
- - `gcf board --json` questions also carry `assumed`, `until`, `sys`, `lens` and `sections`: a D2 shows what it assumed and when it comes back, and every record where it came from.
79
-
80
- ### 0.7.6
81
-
82
- - `ai-factory gcf permissions [--on <group>] [--off <group>] [--deny <rule>] [--undeny <rule>]` — what agent runs may not do: deny groups and the human's own rules in the `agent:` section of `gcf/config.yml`, one `gcf(permissions)` commit; with no change it lists them. On by default: `git-destroy`, `mass-delete`, `publish`, `human-moves`, `guards`; off: `secrets`, `outside`, `processes`. `agent.groups` keeps only the human's choices (`secrets: true`): a group it does not name keeps its default, so one a later version adds comes on. `git push` is in none. `gcf board --json` carries `agent` (`groups`, `deny_extra`, the resolved `deny`, `allow`).
83
- - New `config.yml` keys for harness runs: `context_handoff_pct` (60; 0 turns the handoff off), `context_window` (0: the model's) and `auto_compact_pct` (0: the agent's default; at least the handoff share + 15). `gcf board --json` carries them as `context`; a kept session that handed over reads `resume: {ended: 'handoff', handoffs}`.
84
- - The AGENTS.md block tells the agent that the permissions are the human's.
85
-
86
- ### 0.7.1
87
-
88
- - `gcf board --json`: a ready item's worktree with no run lock and no session mark is a run that stopped (a timeout, a stop, a crash), not a run: the item reads `running: false`, `resumable: true`, and keeps its moves; its next run resumes the worktree. An agent session the harness kept (`.gcf-session/runs/<id>.agent.json`) makes a ready item `resumable` too, in either run mode, and shows as `resume: {ended, resets?}` (null without one).
89
-
90
- ### 0.7.0
91
-
92
- - `priority:` on an item: a rank, lower runs sooner. `ai-factory gcf prioritize <id> <id> …` ranks items in that order in one commit; `item new --priority <n>`. `gcf board --json` lists ranked items first and carries `priority`.
93
- - `ai-factory gcf question answer <q-id> --text="<answer>"` writes the human's answer into its record of `gcf/open-questions.md` and commits. An answered D1 no longer blocks its units; the next agent on the unit puts the answer into its slot (`answer: q-NNN`) and removes the record. `gcf board --json` questions carry `question`, `proposed`, `blocks` and `answer`.
94
- - `ai-factory gcf claim <id>` / `release <id>`: an agent session working an item in the main checkout marks it running. The session's own `context`, `plan check` and `verify` on the item keep the mark fresh (so do a harness run on main and its readset-guard hook), a move on the item clears it, and 2 hours without either expire it. The board and the audit read it as `running`, so a harness run does not take the item, and the core refuses any move on it but the session's own (`submit`, `close`, `block` with `--claimed`).
95
- - `prioritize` leaves the rank of a ready item it does not name (a running one): the named items take the free ranks around it.
96
- - `run_mode: worktree | main` in `gcf/config.yml` (Methodology §7): a project whose agent works through an editor bound to the project folder sets `main`. `gcf board --json` carries it.
97
- - `/gcf-card` cuts several items along subsystem boundaries, marks two items of one subsystem that change different files `parallel: ok`, ranks them with `prioritize`, and keeps `depends` for real needs only.
98
-
99
- ### 0.6.0
100
-
101
- - `gcf board --json` carries `schema: 1`, the version of its format (Methodology §11 lists every field); a board refuses a version it does not know. New fields: per item `file`, `plan_file`, `log`, `depends`, `milestone`, `tune_count`, `since`, `model`, `effort` and `moves` (what the item takes now by Methodology §4.1, empty while a run drives it); at the top `history` and `graveyard`.
102
- - `item new --model <m> --effort <e>`: the run of the item uses them (gcf-harness-aif-ext 0.7). One word each.
103
- - `playtest session --json` and `live check --json` answer JSON: the flag went to the parent command, which printed text.
104
- - `--commit` on `item new`, `inbox add` and `inbox drop` records the move alone (`gcf(item): <id>`, `gcf(inbox): …`) for a caller with no console, a board. Without it nothing is committed, as before. Inbox lines added earlier without a commit go into the same commit: the inbox is raw input.
105
- - `inbox add --text=<text>`: a text that starts with a hyphen ("-5 fps on level 3") reads as a flag at every level of ai-factory's commands, `--` included.
106
-
107
- ### 0.5.3
108
-
109
- - `gcf init` restamps an unplayed item frozen by 0.4.0 (the hash of its body alone), so its `live:` is guarded too — only while that `live:` is still the one its close committed. Commit the restamped items it names.
110
- - While a harness run drives an item (`.gcf-session/runs/<id>.lock` held by a live process), the core refuses any move on it, `/gcf-run` on main included, and `gcf integrate` leaves its branch unmerged; the audit and the board read it as `running`. A lock left by a run that died is taken over. `gcf board --json` reports WIP as `{count, limit, over}`.
111
- - The audit reads the `until:` of every D2 record in `open-questions.md`: a fired one is a stale until record, and the assumption goes back to the human.
112
- - `gcf design check` reads the text of an HTML comment in a unit or an item for forbidden words (M4) and numbers (M5); a `gcf:allow` pragma and a tool's marked region are skipped.
113
- - An unplayed tune or kill keeps a systems-doc line the doc held before the item's close.
114
- - `gcf playtest session` groups the checklist by subsystem and warns when the build is not of main: the remote's default branch when the clone knows it, else any branch but a detached HEAD or a `work/` one.
115
-
116
- ### 0.5.2
117
-
118
- - Bundles `cnlp-kit` 3.9, so `gcf design check` and the commit gate accept a bullet written in Cyrillic alone.
119
- - With `cnlp-kit` 3.9 or later, the `AGENTS.md` block between `<!-- gcf:start -->` and `<!-- gcf:end -->` is a marked region that the project's guide profile does not shape: the `custom_sections` entries 0.5.1 asked for are no longer needed. Update the `cnlp-kit` your project runs `npx cnlp check` with.
120
-
121
- ### 0.5.1
122
-
123
- - A provenance record `assumed: q-NNN` must name a record still in `gcf/open-questions.md` (M9). A D2 record lives until its `until:` fires; once the human confirms the value, cite `answer: q-NNN` instead and remove it. After updating, run `ai-factory gcf design check`: every citation of a removed question blocks the commit gate until it is rewritten.
124
- - An unplayed item's body is frozen, so a word added to the glossary or a removed question is reported there, not blocked.
125
- - The `AGENTS.md` block has no `##` heading any more: a CNL-P guide is flat. A project that checks `AGENTS.md` with `cnlp check` adds `gcf_context`, `gcf_work`, `gcf_forbidden` and `gcf_skills` to `custom_sections` of its guide profile.
126
-
127
- ### 0.5.0
128
-
129
- The run layer of the methodology. Update both extensions together — `gcf-harness-aif-ext` 0.6.0 requires this core — and run `ai-factory gcf init` to refresh the agent block in `AGENTS.md`. New commands:
130
-
131
- - `gcf verify <id>` — the auto-DoD in code: the commit gate's checks, the item's `live:` delta against `LIVE.md`, then `dod.build` and `dod.test`. Fill both in `gcf/config.yml`; an empty one fails, `none` says the project has no such step.
132
- - `gcf phase` and `gcf phase next [--reason]` — the project phase (prototype → vertical-slice → alpha → beta → release → live), kept as `phase:` in the `LIVE.md` frontmatter, with exit criteria checked in code. `item new` and `take` refuse a type the phase does not take. **A project with no phase yet is in prototype, and a prototype takes no `asset` item**: move the phase on (`gcf phase next --reason "…"`) if your game is past it. A `phases:` list in `config.yml` replaces the set.
133
- - A release item carries `version:` and `channel:` (`item new <id> --type release --release 1.0.0 --channel steam`); its end writes the `release:` block of `LIVE.md`. `item new --milestone <name>` tags an item.
134
- - `gcf playtest session [--build]` — the build of main and a checklist per item: what to check, how to get there, what to feel.
135
- - `gcf worktree <id>`, `gcf verify <id> --mark`, `gcf integrate [<id>…]` — an item in its own worktree, the `GCF-Verified` trailer, the merge into main and the item's move. Every committed move now holds `.gcf-session/main.lock`.
136
- - `gcf board [--json]` — everything a board shows in one read.
137
-
138
- A move that writes `LIVE.md` now refuses a malformed one instead of rewriting it without what it could not read: fix the file by hand in its own commit.
139
-
140
- ### 0.4.1
141
-
142
- A fix release over 0.4.0; update in place. What you may notice:
143
-
144
- - An unplayed item's `frozen:` now covers its `live:` delta as well as its body. Items closed by 0.4.0 keep working. `tune`, `kill` and `drop` refuse an unplayed item edited since its close: restore the file first.
145
- - With `PILLARS.md` present, a mechanic or resource with `pillars: TBD` is not ready to `take`. `gcf pillar rename` and `gcf pillar retire` revise a pillar where units and items cite it.
146
- - `design check` blocks a `depends:` cycle and an id with two terminal lines (a merge of branches).
147
- - The commit gate refuses a `LIVE.md` ref into `gcf/` that names no file.
148
- - `gcf init` writes an agent instructions block into `AGENTS.md` (and into a `CLAUDE.md` that does not import it), between `<!-- gcf:start -->` and `<!-- gcf:end -->`. Any agent session then knows where work lives and which commands own it. Run `gcf init` again after an update to refresh it.
149
-
150
- ### Upgrading from 0.3.x
151
-
152
- 0.4.0 replaces the cards with work items and does **not migrate** them. A schema 2 card in `gcf/cards/` is no longer read: while any is left there, the pre-commit gate refuses every commit that touches `gcf/`, and `gcf audit` reports it first. In order:
153
-
154
- 1. Update both extensions together — `gcf-harness-aif-ext` 0.5.0 requires this core — and run `ai-factory gcf init`: it seeds `gcf/work/`, `gcf/inbox.md`, `gcf/LIVE.md` and the `gcf/inbox.md merge=union` line. Your `config.yml` is kept; the new keys `unplayed_warn_after`, `plan_passes` and `dod_attempts` take their defaults, and `dod.test` / `dod.build` (the project's test and build commands) are yours to fill in.
155
- 2. Re-create each card. A 0.3 card is a mechanic or a resource, which `item new` does not create: put its body back as a `## <id>` section in `gcf/design/<system>.md` with a `- <id>` line in `backlog.md`, commit, and run `ai-factory gcf take <id>`. A card that was `built` then goes `ai-factory gcf submit <id>` (its code is in main already); `tune_count` starts at 0 unless you carry it over by hand.
156
- 3. Delete `gcf/cards/` and commit it with the new items.
157
-
158
- What else changes: statuses are `ready`, `awaiting-verdict`, `awaiting-decision`, `blocked`, `parked` and `unplayed` (`proposed` and `tuning` are `ready`, `built` is `awaiting-verdict`); `built_at` is `since`; `submit` replaces the hand flip to `built`; `reopens:` names an ended id and rework gets a new id; a kill or drop of code already in main creates a `remove-<id>` item; graveyard lines carry the type (0.3 lines without it still read); history gains the `decided` event; every item needs a `live:` delta before it ends.
159
-
160
- ### Upgrading from 0.3.0
161
-
162
- 0.3.1 is a patch release with no migration step. It changes behavior in these ways:
163
-
164
- - the pre-commit gate ignores `gcf/.metrics/`: a `gcf audit` or `gcf context` run no longer makes the next verdict, `take`, `done` or `drop` fail with `gcf/ has changes outside the index`
165
- - `ai-factory gcf verdict <id> tune` refuses a card at `tune_limit` (`gcf/config.yml`, 3 by default): kill it, or redesign the envelope and reset `tune_count`
166
- - `gcf audit` reports open questions older than `question_stale_after_commits` (`stale_questions`) and a `PILLARS.md` with more than 7 pillars
167
- - a card with an unquoted numeric `id:` (YAML reads `id: 2024` as a number) is refused: quote the slug
168
- - `built_at` is recorded in a follow-up commit after the implementation commit, with that commit's hash
169
- - the form check is `npx --yes cnlp-kit check`; `npx cnlp check` fails on a machine without a global cnlp-kit, because no npm package is named `cnlp`
170
- - a GDD with `#` lines inside closed code fences reports a one-time delta on its next re-import: those lines were wrongly read as headings before
171
-
172
- ### Upgrading from 0.2.x
173
-
174
- 0.3.0 replaces the candidate backlog with the Design Model: a card is schema 2 (`cards/<id>.md` with a slug id, `type`, `pillars`, no `slug` and no `route`; the gate names the steps for a schema 1 card), and the design lives in `gcf/design/`. In order:
175
-
176
- 1. `ai-factory extension update` and `ai-factory gcf init` (adds `history.md` and the `.gitattributes` lines).
177
- 2. `ai-factory gcf design migrate` — deterministic, no commit. Each `cand-NNN | …` backlog line becomes a unit: the quote goes to provenance, every slot the line did not carry reads `TBD`, every gap note survives as a `TBD`, the backlog keeps its order, and `cand-NNN` references in `open-questions.md` and `graveyard.md` are rewritten. The keeps and dones of old commit messages are copied into `gcf/history.md` once.
178
- 3. Candidates you already turned into units by hand: `--skip cand-012=tile-placement` drops the line and sends dependencies on it to that unit; `--skip cand-014=take-back+undo-and-clear` does the same for a candidate you split in two. The command prints the dependencies it could not map, the units that look like duplicates of hand-written ones, and the gap notes of skipped candidates to check in their units.
179
- 4. `ai-factory gcf design check`, fix what it reports — typically a digit in a description — and commit.
180
-
181
- To remove the extension:
182
-
183
- ```bash
184
- ai-factory extension remove gcf-aif-ext
185
- ```
186
-
187
- Removal leaves `gcf/` and `cnlp.config.json` in place. It also leaves the pre-commit hook, which calls into the removed extension and will now fail every commit — delete `.git/hooks/pre-commit` (or the chained line) by hand.
188
-
189
- ## What it adds
190
-
191
- **Skills** — `gcf-init`, `gcf-adopt`, `gcf-card` (put work into `gcf/work/`: take units, group them under a feature, or create a non-design item), `gcf-verdict` (verdicts and decisions), `gcf-triage` (inbox entries into items, tunes, units or nothing, with the human approving each), `gcf-audit`, `gcf-vision`, `gcf-debate`, `gcf-ingest`.
192
-
193
- **A deterministic core** behind `ai-factory gcf …`:
194
-
195
- - work items — `take <ids...> [--feature <id>]`, `item new <id> --type <type>`, `submit`, `close`, `verdict <id> keep|tune|kill`, `decide <id> go|no|wontfix|known|close|continue|kill`, `block`, `unblock`, `drop`, `done`, `where`
196
- - inbox and snapshot — `inbox [add <text> | drop <in-id>]`, `live`, `live check`
197
- - runs and play — `context`, `plan check <id>`, `verify <id>`, `playtest`, `playtest session [--build]`, `audit`
198
- - project phase and board — `phase`, `phase next [--reason]`, `board [--json]` (everything a board shows, in one read)
199
- - branches — `worktree <id>`, `verify <id> --mark`, `integrate [<id>...]`: a run in its own worktree, the `GCF-Verified` trailer, the merge into main and the move; every committed move holds `.gcf-session/main.lock`
200
- - design and sources — `design check`, `design export`, `design import`, `design migrate`, `anchor`, `section`, `quotes`, `lint`
201
- - setup — `init`, `index`, `hooks-install`
202
-
203
- Every move of an item makes one commit `gcf(<move>): <id>` with every file it touched — the item, history or graveyard, `LIVE.md`, the systems documents, the parent feature, a removal item, the archived plan. `take`, `item new` and `inbox` do not commit. These are plain Node scripts with no LLM in the loop: the same functions the skills call, the pre-commit gate runs, and the harness gates against.
204
-
205
- **A pre-commit gate** that breaks the commit on a schema 2 card left in `gcf/cards/`, an invalid item (an unknown field, a status that is not stored or does not fit its gate or type, a file name that is not its id), a malformed `[until:]` annotation, an edited body of an `unplayed` item, a malformed `gcf/LIVE.md`, a П2 linter finding in a subsystem document, or a blocking design-check finding: a malformed unit or item, a digit in prose, a forbidden synonym, a quote that is not verbatim in its GDD section, a dependency that names no known id, an id living in two places, a backlog out of step with `gcf/design/`. An unfinished unit (`TBD`) never breaks a commit. A unit or item that leaves `gcf/design/` or `gcf/work/` without a move only draws a warning — its reason is lost, but an item still depending on it fails in that same commit. The gate regenerates `gcf/INDEX.md` and stages it for you.
206
-
207
- **CNLP profiles and a work item schema** for the documents the agent reads — one profile per unit type, shared by a unit and its item — plus injections into `aif-plan`, `aif-review` and `aif-commit`.
208
-
209
-
210
- ## Docs
211
-
212
- Full documentation lives in the [repository](https://github.com/VirtualMaestro/GCF): the [Usage Guide](https://github.com/VirtualMaestro/GCF/blob/main/docs/Usage-Guide.md) (RU) for day-to-day work, and [GCF-PRD](https://github.com/VirtualMaestro/GCF/blob/main/docs/GCF-PRD.md) for the spec — principles, data model, lifecycle, enforcement.
213
-
214
- ## License
215
-
216
- MIT
1
+ # gcf-aif-ext
2
+
3
+ The Game Context Framework as an [AI Factory](https://github.com/lee-to/ai-factory) extension: a documentary layer for building games with an AI agent, where the documents hold **state instead of history**.
4
+
5
+ A Design Model: the design lives in `gcf/design/<system>.md` as units with typed slots — what the GDD says, quoted and anchored, and `TBD` for everything it does not. Every piece of work — a mechanic, a bug, tech, an asset, a spike, a release — is a work item `gcf/work/<id>.md` with a `type` and a `gate`: who closes it, the human after play (`verdict`), a check (`auto`), or the human without a build (`decision`). A unit taken into work moves into its item with the same body; a soft unit collapses into its subsystem's document on a human playtest verdict and the item ceases to exist. `gcf/LIVE.md` is a short snapshot of what the game is now — capabilities and known issues. `gcf/inbox.md` holds raw input waiting for triage. A history and a graveyard keep how each id ended and why things were killed. Read rules keep the token cost of a task flat as the project ages.
6
+
7
+ The methodology is [Methodology.md](https://github.com/VirtualMaestro/GCF/blob/main/docs/Methodology.md) (RU).
8
+
9
+ Runtime-agnostic — everything here works on any AI Factory runtime. The Claude-Code-specific orchestration lives in [`gcf-harness-aif-ext`](../gcf-harness-aif-ext), which is optional.
10
+
11
+ ## Requirements
12
+
13
+ - AI Factory 2.x
14
+ - Node.js ≥ 22.14
15
+ - git (the enforcement gate is a pre-commit hook)
16
+
17
+ ## Install
18
+
19
+ Install into an initialized AI Factory project from npm or a local checkout:
20
+
21
+ ```bash
22
+ ai-factory init
23
+ ai-factory extension add gcf-aif-ext # npm
24
+ # ai-factory extension add ../GCF/packages/gcf-aif-ext # local checkout
25
+ ```
26
+
27
+ This installs the 9 skills into each configured runtime, registers the `gcf` command and appends the injections to `aif-plan`, `aif-review` and `aif-commit`. Then set up the project — two steps, run once:
28
+
29
+ ```bash
30
+ ai-factory gcf init # seeds gcf/, cnlp.config.json and .gitattributes (PILLARS.md is written by /gcf-init)
31
+ ai-factory gcf hooks-install # arms the pre-commit gate
32
+ ```
33
+
34
+ `gcf init` is idempotent: it creates what is missing and overwrites nothing. Among what it seeds: `gcf/work/` for the work items, `gcf/inbox.md` for raw input, `gcf/LIVE.md` for the project snapshot, `gcf/history.md` and `gcf/graveyard.md` — the two files that record how an id's life ended — and three lines in `.gitattributes` (`gcf/history.md merge=union`, `gcf/graveyard.md merge=union`, `gcf/inbox.md merge=union`) so that two branches appending different lines to those files never conflict there.
35
+
36
+ It then offers the optional [harness](../gcf-harness-aif-ext) — orchestration and a readset guard for **Claude Code only** — and on yes runs `ai-factory extension add gcf-harness-aif-ext` and `ai-factory gcf-harness init`. It asks only in a terminal and only when `.ai-factory.json` lists the Claude Code agent; an agent or CI run is never asked and installs nothing. `--harness` installs it without asking, `--no-harness` skips the question.
37
+
38
+ `hooks-install` is a separate step because an AI Factory manifest can declare skills, commands and injections but cannot install a git hook — and because arming a gate that can break your commits is your call, not the extension's. It needs a git repository, and it will not overwrite a `pre-commit` it did not write: if husky or lefthook already owns the hook, it prints the one line to chain from it instead. Without the hook GCF still works; nothing enforces it at commit time.
39
+
40
+ Then `/gcf-vision` for a brand-new game, `/gcf-adopt` for a project that already has code, `/gcf-ingest <gdd.md>` for an existing design doc, or `/gcf-card` to start work by hand.
41
+
42
+ ## Upgrade
43
+
44
+ ```bash
45
+ ai-factory extension update gcf-aif-ext # pulls the new version
46
+ ai-factory extension update --force # refresh even if the version is unchanged
47
+ ```
48
+
49
+ Updating preserves `gcf/`, `cnlp.config.json` and the pre-commit hook. Run `ai-factory gcf init` again after an update: it adds only what a newer version seeds and the project lacks.
50
+
51
+ On Windows, run `extension add` and `extension update` from PowerShell or cmd, not Git Bash: the installer unpacks with `tar`, and Git Bash's GNU tar reads `C:\…` as a remote host (`Cannot connect to C: resolve failed`). The Windows `tar.exe` handles it.
52
+
53
+ ### 0.7.13
54
+
55
+ - `gcf close` and `gcf submit` need a green `gcf verify <id>` of the code as it is now (Methodology §6). A green verify records the hash of the project's code (everything but `gcf/`, `.ai-factory/` and `.gcf-session/`, committed or not) in `.gcf-session/runs/<id>.verified`; committing that code keeps the record valid, any later edit asks for a new verify. Without one the move refuses: `<id>: no green gcf verify on record — run ai-factory gcf verify <id> until it is green; only the human may move it without one (--unverified "<why>")`; a changed code names the files that changed. `--unverified "<why>"` is the operator's way past it, and the reason goes into the move's commit (`gcf(close): <id> (unverified: <why>)`). A feature's close and the integrator's moves (they rest on the `GCF-Verified` trailer) need no record.
56
+
57
+ - `gcf board --json` gives each item `running_kind` (`run` for a harness run's lock, `session` for a session mark, null), `since_at` (the time of the `since` commit, null when git does not know it) and `waits_for` (a ready item's dependencies the run gate would refuse it on now). New fields: the schema stays 1.
58
+ - Run logs for the harness metrics (Harness-PRD §11): `gcf plan check` appends to `gcf/.metrics/plan-log.csv` (`at, item, ok, problems`), `gcf verify` to `verify-log.csv` (`at, item, ok, failed checks, mark`). A session in a worktree writes them into the main checkout's logs: its own `gcf/.metrics` is thrown away at the merge. `gcf integrate` commits a log that is new to git too.
59
+
60
+ ### 0.7.10
61
+
62
+ - Lessons: what got in an agent's way, asked at the end of its session (Methodology §5.2.2). `gcf lessons prompt <id>` prints the questions: tools and their workarounds, project rules that are missing or conflict, trouble with GCF itself, and, with `lessons.template` in `gcf/config.yml`, what the project's template should learn; `lessons.questions` adds the operator's own, `lessons.off: true` turns them off. `gcf lessons add <id>` records each answer as an inbox entry with the source `lesson:<kind>:<item>`; `gcf board --json` gives such an entry `source: lesson`, `kind` and `item`, and each item its `lessons` count. The harness 0.9.2 asks them in a turn of its own after each session, so they stay out of the agent's working context; `/gcf-run` without the harness asks them at its end. `/gcf-triage` proposes a fix for each.
63
+
64
+ - `gcf verify` kills a `dod.test` or `dod.build` command that runs longer than `dod_timeout_min` minutes (60 by default, 0 for no limit), with every process it started, and fails the DoD: `dod.test timed out after 60 min (dod_timeout_min), killed: <command>`. Before, a hung engine held the check, and a run with it, for hours.
65
+ - `gcf prioritize` takes an item made with `gcf item new` and never committed, and commits it with its rank. Before, it refused the item as an uncommitted file. An item with uncommitted edits is still refused.
66
+ - `gcf playtest session` lists the items awaiting a decision in a section of their own: before, they were missing from the sheet.
67
+ - `plan_passes` defaults to 3 (was 5). A plan converges when an improve pass finds nothing blocking (Methodology §5.2.1); a project that sets `plan_passes` keeps its value.
68
+
69
+ ### 0.7.9
70
+
71
+ - `gcf done`, `gcf drop` (a design unit), `gcf pillar rename|retire`, `gcf phase next` and `gcf question answer` hold the main lock from their first read to their commit, as the work item moves do. Before, two at once could both pass their checks and write over each other: an answer lost, a unit in the history twice.
72
+ - `gcf claim` says the mark lives 2 hours without a gcf command, as it does (it said 30 minutes).
73
+
74
+ ### 0.7.8
75
+
76
+ - An open question's field written as a YAML block (`assumed: |` or `>`, and the lines under it) reads as its text: before, `gcf board --json` gave the block sign `|`. Every reader of `gcf/open-questions.md` (the board, `design check`, the audit, `context`, the index) reads the fields the same way:
77
+ - a field is a line at the record's field indent, so a block line such as `answer: x` is text: before, it made a D1 read as answered;
78
+ - a blank line inside a block no longer ends the record: before, the fields after it were lost;
79
+ - `gcf question answer` replaces an old answer written as a block whole: before, it replaced only the block's first line;
80
+ - an empty `answer:` is no answer: `design check` now holds the D1's units, as the board already showed it unanswered.
81
+
82
+ ### 0.7.7
83
+
84
+ - `ai-factory gcf config [--json]` lists the human's settings of `gcf/config.yml` with what each means; `gcf config set <key> --value=<v>` sets one, checked by the rules a read applies, the comments kept, one `gcf(config)` commit. An empty number goes back to its default. `gcf board --json` carries them as `settings`.
85
+ - `gcf board --json` questions also carry `assumed`, `until`, `sys`, `lens` and `sections`: a D2 shows what it assumed and when it comes back, and every record where it came from.
86
+
87
+ ### 0.7.6
88
+
89
+ - `ai-factory gcf permissions [--on <group>] [--off <group>] [--deny <rule>] [--undeny <rule>]` — what agent runs may not do: deny groups and the human's own rules in the `agent:` section of `gcf/config.yml`, one `gcf(permissions)` commit; with no change it lists them. On by default: `git-destroy`, `mass-delete`, `publish`, `human-moves`, `guards`; off: `secrets`, `outside`, `processes`. `agent.groups` keeps only the human's choices (`secrets: true`): a group it does not name keeps its default, so one a later version adds comes on. `git push` is in none. `gcf board --json` carries `agent` (`groups`, `deny_extra`, the resolved `deny`, `allow`).
90
+ - New `config.yml` keys for harness runs: `context_handoff_pct` (60; 0 turns the handoff off), `context_window` (0: the model's) and `auto_compact_pct` (0: the agent's default; at least the handoff share + 15). `gcf board --json` carries them as `context`; a kept session that handed over reads `resume: {ended: 'handoff', handoffs}`.
91
+ - The AGENTS.md block tells the agent that the permissions are the human's.
92
+
93
+ ### 0.7.1
94
+
95
+ - `gcf board --json`: a ready item's worktree with no run lock and no session mark is a run that stopped (a timeout, a stop, a crash), not a run: the item reads `running: false`, `resumable: true`, and keeps its moves; its next run resumes the worktree. An agent session the harness kept (`.gcf-session/runs/<id>.agent.json`) makes a ready item `resumable` too, in either run mode, and shows as `resume: {ended, resets?}` (null without one).
96
+
97
+ ### 0.7.0
98
+
99
+ - `priority:` on an item: a rank, lower runs sooner. `ai-factory gcf prioritize <id> <id> …` ranks items in that order in one commit; `item new --priority <n>`. `gcf board --json` lists ranked items first and carries `priority`.
100
+ - `ai-factory gcf question answer <q-id> --text="<answer>"` writes the human's answer into its record of `gcf/open-questions.md` and commits. An answered D1 no longer blocks its units; the next agent on the unit puts the answer into its slot (`answer: q-NNN`) and removes the record. `gcf board --json` questions carry `question`, `proposed`, `blocks` and `answer`.
101
+ - `ai-factory gcf claim <id>` / `release <id>`: an agent session working an item in the main checkout marks it running. The session's own `context`, `plan check` and `verify` on the item keep the mark fresh (so do a harness run on main and its readset-guard hook), a move on the item clears it, and 2 hours without either expire it. The board and the audit read it as `running`, so a harness run does not take the item, and the core refuses any move on it but the session's own (`submit`, `close`, `block` with `--claimed`).
102
+ - `prioritize` leaves the rank of a ready item it does not name (a running one): the named items take the free ranks around it.
103
+ - `run_mode: worktree | main` in `gcf/config.yml` (Methodology §7): a project whose agent works through an editor bound to the project folder sets `main`. `gcf board --json` carries it.
104
+ - `/gcf-card` cuts several items along subsystem boundaries, marks two items of one subsystem that change different files `parallel: ok`, ranks them with `prioritize`, and keeps `depends` for real needs only.
105
+
106
+ ### 0.6.0
107
+
108
+ - `gcf board --json` carries `schema: 1`, the version of its format (Methodology §11 lists every field); a board refuses a version it does not know. New fields: per item `file`, `plan_file`, `log`, `depends`, `milestone`, `tune_count`, `since`, `model`, `effort` and `moves` (what the item takes now by Methodology §4.1, empty while a run drives it); at the top `history` and `graveyard`.
109
+ - `item new --model <m> --effort <e>`: the run of the item uses them (gcf-harness-aif-ext 0.7). One word each.
110
+ - `playtest session --json` and `live check --json` answer JSON: the flag went to the parent command, which printed text.
111
+ - `--commit` on `item new`, `inbox add` and `inbox drop` records the move alone (`gcf(item): <id>`, `gcf(inbox): …`) for a caller with no console, a board. Without it nothing is committed, as before. Inbox lines added earlier without a commit go into the same commit: the inbox is raw input.
112
+ - `inbox add --text=<text>`: a text that starts with a hyphen ("-5 fps on level 3") reads as a flag at every level of ai-factory's commands, `--` included.
113
+
114
+ ### 0.5.3
115
+
116
+ - `gcf init` restamps an unplayed item frozen by 0.4.0 (the hash of its body alone), so its `live:` is guarded too — only while that `live:` is still the one its close committed. Commit the restamped items it names.
117
+ - While a harness run drives an item (`.gcf-session/runs/<id>.lock` held by a live process), the core refuses any move on it, `/gcf-run` on main included, and `gcf integrate` leaves its branch unmerged; the audit and the board read it as `running`. A lock left by a run that died is taken over. `gcf board --json` reports WIP as `{count, limit, over}`.
118
+ - The audit reads the `until:` of every D2 record in `open-questions.md`: a fired one is a stale until record, and the assumption goes back to the human.
119
+ - `gcf design check` reads the text of an HTML comment in a unit or an item for forbidden words (M4) and numbers (M5); a `gcf:allow` pragma and a tool's marked region are skipped.
120
+ - An unplayed tune or kill keeps a systems-doc line the doc held before the item's close.
121
+ - `gcf playtest session` groups the checklist by subsystem and warns when the build is not of main: the remote's default branch when the clone knows it, else any branch but a detached HEAD or a `work/` one.
122
+
123
+ ### 0.5.2
124
+
125
+ - Bundles `cnlp-kit` 3.9, so `gcf design check` and the commit gate accept a bullet written in Cyrillic alone.
126
+ - With `cnlp-kit` 3.9 or later, the `AGENTS.md` block between `<!-- gcf:start -->` and `<!-- gcf:end -->` is a marked region that the project's guide profile does not shape: the `custom_sections` entries 0.5.1 asked for are no longer needed. Update the `cnlp-kit` your project runs `npx cnlp check` with.
127
+
128
+ ### 0.5.1
129
+
130
+ - A provenance record `assumed: q-NNN` must name a record still in `gcf/open-questions.md` (M9). A D2 record lives until its `until:` fires; once the human confirms the value, cite `answer: q-NNN` instead and remove it. After updating, run `ai-factory gcf design check`: every citation of a removed question blocks the commit gate until it is rewritten.
131
+ - An unplayed item's body is frozen, so a word added to the glossary or a removed question is reported there, not blocked.
132
+ - The `AGENTS.md` block has no `##` heading any more: a CNL-P guide is flat. A project that checks `AGENTS.md` with `cnlp check` adds `gcf_context`, `gcf_work`, `gcf_forbidden` and `gcf_skills` to `custom_sections` of its guide profile.
133
+
134
+ ### 0.5.0
135
+
136
+ The run layer of the methodology. Update both extensions together — `gcf-harness-aif-ext` 0.6.0 requires this core — and run `ai-factory gcf init` to refresh the agent block in `AGENTS.md`. New commands:
137
+
138
+ - `gcf verify <id>` — the auto-DoD in code: the commit gate's checks, the item's `live:` delta against `LIVE.md`, then `dod.build` and `dod.test`. Fill both in `gcf/config.yml`; an empty one fails, `none` says the project has no such step.
139
+ - `gcf phase` and `gcf phase next [--reason]` — the project phase (prototype → vertical-slice → alpha → beta → release → live), kept as `phase:` in the `LIVE.md` frontmatter, with exit criteria checked in code. `item new` and `take` refuse a type the phase does not take. **A project with no phase yet is in prototype, and a prototype takes no `asset` item**: move the phase on (`gcf phase next --reason "…"`) if your game is past it. A `phases:` list in `config.yml` replaces the set.
140
+ - A release item carries `version:` and `channel:` (`item new <id> --type release --release 1.0.0 --channel steam`); its end writes the `release:` block of `LIVE.md`. `item new --milestone <name>` tags an item.
141
+ - `gcf playtest session [--build]` — the build of main and a checklist per item: what to check, how to get there, what to feel.
142
+ - `gcf worktree <id>`, `gcf verify <id> --mark`, `gcf integrate [<id>…]` — an item in its own worktree, the `GCF-Verified` trailer, the merge into main and the item's move. Every committed move now holds `.gcf-session/main.lock`.
143
+ - `gcf board [--json]` — everything a board shows in one read.
144
+
145
+ A move that writes `LIVE.md` now refuses a malformed one instead of rewriting it without what it could not read: fix the file by hand in its own commit.
146
+
147
+ ### 0.4.1
148
+
149
+ A fix release over 0.4.0; update in place. What you may notice:
150
+
151
+ - An unplayed item's `frozen:` now covers its `live:` delta as well as its body. Items closed by 0.4.0 keep working. `tune`, `kill` and `drop` refuse an unplayed item edited since its close: restore the file first.
152
+ - With `PILLARS.md` present, a mechanic or resource with `pillars: TBD` is not ready to `take`. `gcf pillar rename` and `gcf pillar retire` revise a pillar where units and items cite it.
153
+ - `design check` blocks a `depends:` cycle and an id with two terminal lines (a merge of branches).
154
+ - The commit gate refuses a `LIVE.md` ref into `gcf/` that names no file.
155
+ - `gcf init` writes an agent instructions block into `AGENTS.md` (and into a `CLAUDE.md` that does not import it), between `<!-- gcf:start -->` and `<!-- gcf:end -->`. Any agent session then knows where work lives and which commands own it. Run `gcf init` again after an update to refresh it.
156
+
157
+ ### Upgrading from 0.3.x
158
+
159
+ 0.4.0 replaces the cards with work items and does **not migrate** them. A schema 2 card in `gcf/cards/` is no longer read: while any is left there, the pre-commit gate refuses every commit that touches `gcf/`, and `gcf audit` reports it first. In order:
160
+
161
+ 1. Update both extensions together — `gcf-harness-aif-ext` 0.5.0 requires this core — and run `ai-factory gcf init`: it seeds `gcf/work/`, `gcf/inbox.md`, `gcf/LIVE.md` and the `gcf/inbox.md merge=union` line. Your `config.yml` is kept; the new keys `unplayed_warn_after`, `plan_passes` and `dod_attempts` take their defaults, and `dod.test` / `dod.build` (the project's test and build commands) are yours to fill in.
162
+ 2. Re-create each card. A 0.3 card is a mechanic or a resource, which `item new` does not create: put its body back as a `## <id>` section in `gcf/design/<system>.md` with a `- <id>` line in `backlog.md`, commit, and run `ai-factory gcf take <id>`. A card that was `built` then goes `ai-factory gcf verify <id>` and `ai-factory gcf submit <id>` (its code is in main already; since core 0.7.13 `submit` needs that green verify); `tune_count` starts at 0 unless you carry it over by hand.
163
+ 3. Delete `gcf/cards/` and commit it with the new items.
164
+
165
+ What else changes: statuses are `ready`, `awaiting-verdict`, `awaiting-decision`, `blocked`, `parked` and `unplayed` (`proposed` and `tuning` are `ready`, `built` is `awaiting-verdict`); `built_at` is `since`; `submit` replaces the hand flip to `built`; `reopens:` names an ended id and rework gets a new id; a kill or drop of code already in main creates a `remove-<id>` item; graveyard lines carry the type (0.3 lines without it still read); history gains the `decided` event; every item needs a `live:` delta before it ends.
166
+
167
+ ### Upgrading from 0.3.0
168
+
169
+ 0.3.1 is a patch release with no migration step. It changes behavior in these ways:
170
+
171
+ - the pre-commit gate ignores `gcf/.metrics/`: a `gcf audit` or `gcf context` run no longer makes the next verdict, `take`, `done` or `drop` fail with `gcf/ has changes outside the index`
172
+ - `ai-factory gcf verdict <id> tune` refuses a card at `tune_limit` (`gcf/config.yml`, 3 by default): kill it, or redesign the envelope and reset `tune_count`
173
+ - `gcf audit` reports open questions older than `question_stale_after_commits` (`stale_questions`) and a `PILLARS.md` with more than 7 pillars
174
+ - a card with an unquoted numeric `id:` (YAML reads `id: 2024` as a number) is refused: quote the slug
175
+ - `built_at` is recorded in a follow-up commit after the implementation commit, with that commit's hash
176
+ - the form check is `npx --yes cnlp-kit check`; `npx cnlp check` fails on a machine without a global cnlp-kit, because no npm package is named `cnlp`
177
+ - a GDD with `#` lines inside closed code fences reports a one-time delta on its next re-import: those lines were wrongly read as headings before
178
+
179
+ ### Upgrading from 0.2.x
180
+
181
+ 0.3.0 replaces the candidate backlog with the Design Model: a card is schema 2 (`cards/<id>.md` with a slug id, `type`, `pillars`, no `slug` and no `route`; the gate names the steps for a schema 1 card), and the design lives in `gcf/design/`. In order:
182
+
183
+ 1. `ai-factory extension update` and `ai-factory gcf init` (adds `history.md` and the `.gitattributes` lines).
184
+ 2. `ai-factory gcf design migrate` — deterministic, no commit. Each `cand-NNN | …` backlog line becomes a unit: the quote goes to provenance, every slot the line did not carry reads `TBD`, every gap note survives as a `TBD`, the backlog keeps its order, and `cand-NNN` references in `open-questions.md` and `graveyard.md` are rewritten. The keeps and dones of old commit messages are copied into `gcf/history.md` once.
185
+ 3. Candidates you already turned into units by hand: `--skip cand-012=tile-placement` drops the line and sends dependencies on it to that unit; `--skip cand-014=take-back+undo-and-clear` does the same for a candidate you split in two. The command prints the dependencies it could not map, the units that look like duplicates of hand-written ones, and the gap notes of skipped candidates to check in their units.
186
+ 4. `ai-factory gcf design check`, fix what it reports — typically a digit in a description — and commit.
187
+
188
+ To remove the extension:
189
+
190
+ ```bash
191
+ ai-factory extension remove gcf-aif-ext
192
+ ```
193
+
194
+ Removal leaves `gcf/` and `cnlp.config.json` in place. It also leaves the pre-commit hook, which calls into the removed extension and will now fail every commit — delete `.git/hooks/pre-commit` (or the chained line) by hand.
195
+
196
+ ## What it adds
197
+
198
+ **Skills** — `gcf-init`, `gcf-adopt`, `gcf-card` (put work into `gcf/work/`: take units, group them under a feature, or create a non-design item), `gcf-verdict` (verdicts and decisions), `gcf-triage` (inbox entries into items, tunes, units or nothing, with the human approving each), `gcf-audit`, `gcf-vision`, `gcf-debate`, `gcf-ingest`.
199
+
200
+ **A deterministic core** behind `ai-factory gcf …`:
201
+
202
+ - work items — `take <ids...> [--feature <id>]`, `item new <id> --type <type>`, `submit`, `close`, `verdict <id> keep|tune|kill`, `decide <id> go|no|wontfix|known|close|continue|kill`, `block`, `unblock`, `drop`, `done`, `where`
203
+ - inbox and snapshot — `inbox [add <text> | drop <in-id>]`, `live`, `live check`
204
+ - runs and play — `context`, `plan check <id>`, `verify <id>`, `playtest`, `playtest session [--build]`, `audit`
205
+ - project phase and board — `phase`, `phase next [--reason]`, `board [--json]` (everything a board shows, in one read)
206
+ - branches — `worktree <id>`, `verify <id> --mark`, `integrate [<id>...]`: a run in its own worktree, the `GCF-Verified` trailer, the merge into main and the move; every committed move holds `.gcf-session/main.lock`
207
+ - design and sources — `design check`, `design export`, `design import`, `design migrate`, `anchor`, `section`, `quotes`, `lint`
208
+ - setup — `init`, `index`, `hooks-install`
209
+
210
+ Every move of an item makes one commit `gcf(<move>): <id>` with every file it touched — the item, history or graveyard, `LIVE.md`, the systems documents, the parent feature, a removal item, the archived plan. `take`, `item new` and `inbox` do not commit. These are plain Node scripts with no LLM in the loop: the same functions the skills call, the pre-commit gate runs, and the harness gates against.
211
+
212
+ **A pre-commit gate** that breaks the commit on a schema 2 card left in `gcf/cards/`, an invalid item (an unknown field, a status that is not stored or does not fit its gate or type, a file name that is not its id), a malformed `[until:]` annotation, an edited body of an `unplayed` item, a malformed `gcf/LIVE.md`, a П2 linter finding in a subsystem document, or a blocking design-check finding: a malformed unit or item, a digit in prose, a forbidden synonym, a quote that is not verbatim in its GDD section, a dependency that names no known id, an id living in two places, a backlog out of step with `gcf/design/`. An unfinished unit (`TBD`) never breaks a commit. A unit or item that leaves `gcf/design/` or `gcf/work/` without a move only draws a warning — its reason is lost, but an item still depending on it fails in that same commit. The gate regenerates `gcf/INDEX.md` and stages it for you.
213
+
214
+ **CNLP profiles and a work item schema** for the documents the agent reads — one profile per unit type, shared by a unit and its item — plus injections into `aif-plan`, `aif-review` and `aif-commit`.
215
+
216
+
217
+ ## Docs
218
+
219
+ Full documentation lives in the [repository](https://github.com/VirtualMaestro/GCF): the [Usage Guide](https://github.com/VirtualMaestro/GCF/blob/main/docs/Usage-Guide.md) (RU) for day-to-day work, and [GCF-PRD](https://github.com/VirtualMaestro/GCF/blob/main/docs/GCF-PRD.md) for the spec — principles, data model, lifecycle, enforcement.
220
+
221
+ ## License
222
+
223
+ MIT
package/commands/gcf.js CHANGED
@@ -19,6 +19,23 @@ const out = (json, value, human) =>
19
19
  const HARNESS = 'gcf-harness-aif-ext';
20
20
 
21
21
  const CLAIMED = 'the agent session that claimed the item (gcf claim) records its own move';
22
+ /** close and submit from a command: they need a green verify unless told why not. */
23
+ const verifiedMove = (o) => ({
24
+ commit: o.commit,
25
+ requireVerify: true,
26
+ unverified: o.unverified === undefined ? undefined : reasonOf(o.unverified),
27
+ });
28
+
29
+ /** The reason of `--unverified`, one line in the commit's subject; an empty one is no reason. */
30
+ function reasonOf(text) {
31
+ const why = String(text).replace(/\s+/g, ' ').trim();
32
+ if (!why) throw new Error('--unverified needs the reason the move goes without a green verify');
33
+
34
+ return why;
35
+ }
36
+
37
+ const UNVERIFIED =
38
+ 'move without a green gcf verify of the code as it is now; the reason goes into the commit';
22
39
 
23
40
  /** `--claimed`: this move comes from the session that holds the item's mark. */
24
41
  const claimedBy = (o, id) => {
@@ -747,10 +764,11 @@ function registerItemMoves(gcf) {
747
764
  .command('submit <id>')
748
765
  .description('ready → awaiting-verdict / awaiting-decision (commit gcf(submit))')
749
766
  .option('--claimed', CLAIMED)
767
+ .option('--unverified <why>', UNVERIFIED)
750
768
  .option('--no-commit', 'skip the commit (tests only)')
751
769
  .action(async (id, o) => {
752
770
  claimedBy(o, id);
753
- const r = await (await ops()).submitItem(process.cwd(), id, { commit: o.commit });
771
+ const r = await (await ops()).submitItem(process.cwd(), id, verifiedMove(o));
754
772
  console.log(`${r.id} → ${r.status}${shortHash(r.hash)}`);
755
773
  });
756
774
  gcf
@@ -763,12 +781,13 @@ function registerItemMoves(gcf) {
763
781
  [],
764
782
  )
765
783
  .option('--claimed', CLAIMED)
784
+ .option('--unverified <why>', UNVERIFIED)
766
785
  .option('--no-commit', 'skip the commit (tests only)')
767
786
  .action(async (id, o) => {
768
787
  claimedBy(o, id);
769
788
  const r = await (
770
789
  await ops()
771
- ).closeItem(process.cwd(), id, { constraints: o.constraint, commit: o.commit });
790
+ ).closeItem(process.cwd(), id, { ...verifiedMove(o), constraints: o.constraint });
772
791
  console.log(`closed ${r.id}${r.unplayed ? ' → unplayed' : ''}${shortHash(r.hash)}`);
773
792
  });
774
793
  }
@@ -982,6 +1001,10 @@ function registerPlan(gcf) {
982
1001
  .action(async (id, o) => {
983
1002
  await touchSession(id);
984
1003
  const r = await (await lib('plan-check')).planCheck(process.cwd(), id);
1004
+ // Harness-PRD §11: the checks a plan took to converge.
1005
+ await (
1006
+ await lib('metrics')
1007
+ ).logMetric(process.cwd(), 'plan-log.csv', [id, r.ok ? 1 : 0, r.problems?.length ?? 0]);
985
1008
  out(o.json, r, (x) =>
986
1009
  x.ok ? `${x.plan}: ${x.done}/${x.tasks} tasks done` : x.problems.join('\n'),
987
1010
  );
@@ -1000,6 +1023,26 @@ function registerVerify(gcf) {
1000
1023
  const r = o.mark
1001
1024
  ? await (await lib('integrate')).markVerified(process.cwd(), id)
1002
1025
  : await (await lib('verify')).verifyItem(process.cwd(), id);
1026
+ // Methodology §6: a green DoD on main is what close and submit ask for, for this code.
1027
+ // A record git cannot write leaves the DoD unproven for the gate: the verify fails.
1028
+ if (r.ok && !o.mark)
1029
+ try {
1030
+ (await lib('verify-stamp')).stampVerified(process.cwd(), id);
1031
+ } catch (e) {
1032
+ r.ok = false;
1033
+ r.checks.push({ name: 'record', ok: false, problems: [e.message] });
1034
+ }
1035
+
1036
+ // Harness-PRD §11: DoD attempts and what failed them.
1037
+ const failed = r.checks.filter((c) => !c.ok).map((c) => c.name);
1038
+ await (
1039
+ await lib('metrics')
1040
+ ).logMetric(process.cwd(), 'verify-log.csv', [
1041
+ id,
1042
+ r.ok ? 1 : 0,
1043
+ failed.join(' '),
1044
+ o.mark ? 1 : 0,
1045
+ ]);
1003
1046
  out(o.json, r, (x) =>
1004
1047
  [
1005
1048
  ...x.checks.flatMap((c) =>
package/extension.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/lee-to/ai-factory/2.x/schemas/extension.schema.json",
3
3
  "name": "gcf-aif-ext",
4
- "version": "0.7.10",
4
+ "version": "0.7.13",
5
5
  "description": "Game Context Framework for AI Factory",
6
6
  "skills": [
7
7
  "skills/gcf-init",
@@ -1,9 +1,10 @@
1
- ## GCF: work item status after commit (from gcf-aif-ext)
2
-
3
- gcf_commit:
4
- - when `gcf/` exists and the commit implements a work item, commit the implementation first, then move the item with its own command in a follow-up commit
5
- - gate `verdict` or `decision`: run `ai-factory gcf submit <item-id>` — it waits for the operator, `since:` records where the wait began
6
- - gate `auto`, every check green: write the item's `live:` delta, then run `ai-factory gcf close <item-id>` — never edit `status:` by hand
7
- - the work cannot go on: run `ai-factory gcf block <item-id> --reason "<why or the question>"`
8
- - do not write a terminal status into an item file: a terminal move deletes the file and writes the history or graveyard line itself
9
- - when the commit makes the tests of a hard unit still in `gcf/design/` green, run `ai-factory gcf done <id>` after it
1
+ ## GCF: work item status after commit (from gcf-aif-ext)
2
+
3
+ gcf_commit:
4
+ - when `gcf/` exists and the commit implements a work item, commit the implementation first, then move the item with its own command in a follow-up commit
5
+ - gate `verdict` or `decision`: run `ai-factory gcf submit <item-id>` — it waits for the operator, `since:` records where the wait began
6
+ - gate `auto`, every check green: write the item's `live:` delta, then run `ai-factory gcf close <item-id>` — never edit `status:` by hand
7
+ - `submit` and `close` refuse code that changed since its last green `ai-factory gcf verify <item-id>`: run it again after any fix; `--unverified` is the human's call
8
+ - the work cannot go on: run `ai-factory gcf block <item-id> --reason "<why or the question>"`
9
+ - do not write a terminal status into an item file: a terminal move deletes the file and writes the history or graveyard line itself
10
+ - when the commit makes the tests of a hard unit still in `gcf/design/` green, run `ai-factory gcf done <id>` after it
package/lib/board.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // `gcf board --json` (Methodology §11): everything a board shows, in one read. The board is a
2
2
  // view: it shows these data and runs the same commands as the console; nothing here is stored.
3
+ import { spawnSync } from 'node:child_process';
3
4
  import { readFile } from 'node:fs/promises';
4
5
  import { existsSync } from 'node:fs';
5
6
  import path from 'node:path';
@@ -14,6 +15,7 @@ import { wipOf } from './audit-core.js';
14
15
  import { runHolder } from './main-lock.js';
15
16
  import { ITEM_TYPES } from './validate-item.js';
16
17
  import { movesOf } from './transitions.js';
18
+ import { openDepends } from './work-ops.js';
17
19
  import { listQuestions } from './questions.js';
18
20
  import { sessionActive } from './session-mark.js';
19
21
  import { agentOf } from './agent-permissions.js';
@@ -90,6 +92,30 @@ const graveyardOf = async (root) =>
90
92
  };
91
93
  });
92
94
 
95
+ /**
96
+ * The commit time of each `since` hash, by hash: one git call for the whole board. A hash git
97
+ * does not know (rebased away), a short one (the core writes it whole) or text that is no hash
98
+ * gets no time.
99
+ */
100
+ function commitTimes(projectDir, hashes) {
101
+ const asked = [...new Set(hashes.filter((h) => /^([0-9a-f]{40}|[0-9a-f]{64})$/.test(h ?? '')))];
102
+ if (!asked.length) return new Map();
103
+ const res = spawnSync(
104
+ 'git',
105
+ ['log', '--no-walk=unsorted', '--ignore-missing', '--format=%H %cI', ...asked],
106
+ { cwd: projectDir, encoding: 'utf8' },
107
+ );
108
+ const found = (res.status === 0 ? res.stdout : '').split('\n').filter(Boolean);
109
+
110
+ return new Map(
111
+ asked.flatMap((h) => {
112
+ const line = found.find((l) => l.split(' ')[0] === h);
113
+
114
+ return line ? [[h, line.split(' ')[1]]] : [];
115
+ }),
116
+ );
117
+ }
118
+
93
119
  /** Verified branches; none outside a repository. */
94
120
  function verifiedOf(projectDir) {
95
121
  try {
@@ -179,8 +205,15 @@ function inRunOrder(items) {
179
205
 
180
206
  const LISTS = ['children', 'system', 'depends'];
181
207
 
208
+ /** `run`, `session` or null. A harness run's lock wins over the mark its run on main holds. */
209
+ function runningKind(projectDir, id) {
210
+ if (runHolder(projectDir, id) !== null) return 'run';
211
+
212
+ return sessionActive(projectDir, id) ? 'session' : null;
213
+ }
214
+
182
215
  /** One item of the board. */
183
- async function itemOf(projectDir, c, verified) {
216
+ async function itemOf(projectDir, c, verified, ledger) {
184
217
  const id = String(c.data.id);
185
218
  // A trailer counts only while the item waits on main for its integrate.
186
219
  const ready = c.data.status === 'ready' && verified.has(id);
@@ -188,7 +221,8 @@ async function itemOf(projectDir, c, verified) {
188
221
  // is a run; a trailer is a passed DoD. A worktree with neither is a run that stopped (a
189
222
  // timeout, a stop, a crash): the item is ready, and its next run resumes that worktree. A
190
223
  // session the harness kept (`resume`) is resumed by the next run in either mode.
191
- const running = runHolder(projectDir, id) !== null || sessionActive(projectDir, id);
224
+ const kind = runningKind(projectDir, id);
225
+ const running = kind !== null;
192
226
  const resume = c.data.status === 'ready' ? await resumeOf(projectDir, id) : null;
193
227
  const resumable =
194
228
  !running &&
@@ -206,9 +240,14 @@ async function itemOf(projectDir, c, verified) {
206
240
  ...(c.data.severity ? { severity: c.data.severity } : {}),
207
241
  ...(c.data.blocked ? { blocked: c.data.blocked } : {}),
208
242
  running,
243
+ // `run`: a harness run's lock; `session`: an operator's or agent's session mark (`gcf claim`).
244
+ running_kind: kind,
209
245
  resumable,
210
246
  resume,
211
247
  verified: ready,
248
+ // The dependencies the run gate would refuse a ready item on now (Board-PRD §5.2).
249
+ waits_for:
250
+ c.data.status === 'ready' ? openDepends(ledger, c.data.depends ?? [], { waiting: true }) : [],
212
251
  // What a board may offer (§4.1): nothing while a run drives the item.
213
252
  moves: running ? [] : movesOf(c.data),
214
253
  file: `gcf/work/${c.name}`,
@@ -224,8 +263,14 @@ export async function board(projectDir) {
224
263
  const ledger = await buildLedger(projectDir);
225
264
  const verified = verifiedOf(projectDir);
226
265
  const items = [];
227
- for (const c of ledger.items) items.push(await itemOf(projectDir, c, verified));
266
+ for (const c of ledger.items) items.push(await itemOf(projectDir, c, verified, ledger));
228
267
  inRunOrder(items);
268
+ // When the wait began: the time of the commit `since` names (Board-PRD §13).
269
+ const times = commitTimes(
270
+ projectDir,
271
+ items.map((i) => i.since),
272
+ );
273
+ for (const i of items) i.since_at = times.get(i.since) ?? null;
229
274
  const inbox = await listInbox(root);
230
275
  // Where agents struggle: the lessons in the inbox each item's sessions left.
231
276
  for (const i of items) i.lessons = inbox.filter((e) => e.item === i.id).length;
package/lib/integrate.js CHANGED
@@ -271,6 +271,9 @@ async function integrateOne(projectDir, id) {
271
271
  * branch bringing its own lines could not merge over them. Union merge joins the lines.
272
272
  */
273
273
  function commitMetrics(projectDir) {
274
+ // A log the harness started on main (run-log, guard-log) is new to git: add it first; only
275
+ // the logs, never an editor's swap file beside them.
276
+ git(projectDir, ['add', '--', 'gcf/.metrics/*.csv'], { check: false });
274
277
  const dirty = git(projectDir, [
275
278
  'status',
276
279
  '--porcelain',
@@ -278,17 +281,23 @@ function commitMetrics(projectDir) {
278
281
  '--',
279
282
  'gcf/.metrics',
280
283
  ]);
284
+ // Best effort: a main mid-rebase or mid-merge keeps its logs for the next integrate, and a
285
+ // run still starts.
281
286
  if (dirty.out)
282
- git(projectDir, [
283
- 'commit',
284
- '-q',
285
- '--no-verify',
286
- '-m',
287
- 'gcf(metrics): run logs',
288
- '--only',
289
- '--',
290
- 'gcf/.metrics',
291
- ]);
287
+ git(
288
+ projectDir,
289
+ [
290
+ 'commit',
291
+ '-q',
292
+ '--no-verify',
293
+ '-m',
294
+ 'gcf(metrics): run logs',
295
+ '--only',
296
+ '--',
297
+ 'gcf/.metrics',
298
+ ],
299
+ { check: false },
300
+ );
292
301
  }
293
302
 
294
303
  /**
@@ -302,11 +311,13 @@ export async function integrate(projectDir, ids = []) {
302
311
  throw new Error(`integrate runs in the main checkout on its branch, not on ${branch}`);
303
312
 
304
313
  return withMainLock(projectDir, async () => {
305
- // Nothing to merge leaves main alone: a run's first integrate must not refuse a dirty tree.
314
+ // The run logs go in at every integrate, a main-mode run's first one too: nothing else
315
+ // commits them there. Nothing to merge leaves the rest of main alone: a run's first
316
+ // integrate must not refuse a dirty tree.
317
+ commitMetrics(projectDir);
306
318
  if (!ids.length && !verifiedBranches(projectDir).length) return [];
307
319
  if (git(projectDir, ['status', '--porcelain', '--untracked-files=no', '--', '.', METRICS]).out)
308
320
  throw new Error('main has uncommitted changes: commit or stash them before integrating');
309
- commitMetrics(projectDir);
310
321
  for (const id of ids)
311
322
  if (!isVerified(projectDir, id))
312
323
  throw new Error(
package/lib/metrics.js ADDED
@@ -0,0 +1,41 @@
1
+ // The run logs this module writes to gcf/.metrics (Harness-PRD §11): append-only csv, one
2
+ // line per call, merged by union. A worktree's lines go to the main checkout's logs: the
3
+ // worktree's own gcf/.metrics is thrown away when its branch merges.
4
+ import { spawnSync } from 'node:child_process';
5
+ import { appendFile, mkdir } from 'node:fs/promises';
6
+ import { existsSync } from 'node:fs';
7
+ import path from 'node:path';
8
+
9
+ /** gcf/.metrics of the main checkout of `projectDir`; its own outside a repository. */
10
+ export function metricsDir(projectDir) {
11
+ const res = spawnSync('git', ['rev-parse', '--git-common-dir'], {
12
+ cwd: projectDir,
13
+ encoding: 'utf8',
14
+ });
15
+ const common = res.status === 0 ? path.resolve(projectDir, res.stdout.trim()) : '';
16
+ const main = path.basename(common) === '.git' ? path.dirname(common) : projectDir;
17
+
18
+ return path.join(main, 'gcf', '.metrics');
19
+ }
20
+
21
+ /** A field as csv takes it: no comma, no line break, at most 120 characters. */
22
+ const field = (v) =>
23
+ String(v ?? '')
24
+ .replace(/[,\r\n]+/g, ';')
25
+ .slice(0, 120);
26
+
27
+ /**
28
+ * Append one line to a log, timestamped; a project without gcf/ keeps no logs. Best effort: a
29
+ * log that cannot be written (a csv held open by a spreadsheet) never fails the command.
30
+ */
31
+ export async function logMetric(projectDir, file, fields) {
32
+ try {
33
+ const dir = metricsDir(projectDir);
34
+ if (!existsSync(path.dirname(dir))) return;
35
+ await mkdir(dir, { recursive: true });
36
+ const line = [new Date().toISOString(), ...fields].map(field).join(',');
37
+ await appendFile(path.join(dir, file), `${line}\n`, 'utf8');
38
+ } catch {
39
+ // No line this time.
40
+ }
41
+ }
@@ -0,0 +1,102 @@
1
+ // The record of a green `gcf verify` on main (Methodology §6): `close` and `submit` need one for
2
+ // the code as it is now, so a DoD run some other way cannot stand in for it. The record holds
3
+ // the hash of the project's code, not of a commit: committing the verified code keeps it valid,
4
+ // and any edit after the verify (a review's fix) asks for a new one. In a worktree the
5
+ // `GCF-Verified` trailer does this job, and the integrator's moves need no record.
6
+ import { spawnSync } from 'node:child_process';
7
+ import { copyFileSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+
11
+ // Not code: the framework's own files, the plans, the run files. A move or a plan tick there
12
+ // after the verify does not change what the DoD proved.
13
+ const NOT_CODE = ['gcf', '.ai-factory', '.gcf-session'];
14
+
15
+ const stampFile = (projectDir, id) =>
16
+ path.join(path.resolve(projectDir), '.gcf-session', 'runs', `${id}.verified`);
17
+
18
+ const git = (projectDir, args, env = {}) =>
19
+ spawnSync('git', args, { cwd: projectDir, encoding: 'utf8', env: { ...process.env, ...env } });
20
+
21
+ /**
22
+ * The git tree of the project's code as it is on disk, committed or not, new files included
23
+ * (ignored ones are not); null outside a repository. A git that fails on the code (a file it
24
+ * cannot read, `core.safecrlf`) throws: a gate that cannot see the code must not let it pass. A
25
+ * copy of the index keeps git's stat cache, so only changed files are hashed again.
26
+ */
27
+ export function codeTree(projectDir) {
28
+ const index = git(projectDir, ['rev-parse', '--git-path', 'index']);
29
+ if (index.status !== 0) return null;
30
+ const real = path.resolve(projectDir, index.stdout.trim());
31
+ const tmp = path.join(os.tmpdir(), `gcf-code-index-${process.pid}-${Date.now()}`);
32
+ const env = { GIT_INDEX_FILE: tmp };
33
+ const step = (args) => {
34
+ const r = git(projectDir, args, env);
35
+ if (r.status !== 0) throw new Error(`git ${args[0]} on the code failed: ${r.stderr.trim()}`);
36
+
37
+ return r.stdout.trim();
38
+ };
39
+
40
+ try {
41
+ if (existsSync(real)) copyFileSync(real, tmp);
42
+ step(['add', '-A', '--', '.', ...NOT_CODE.map((p) => `:(exclude)${p}`)]);
43
+ step(['rm', '-r', '-q', '--cached', '--ignore-unmatch', '--', ...NOT_CODE]);
44
+
45
+ return step(['write-tree']);
46
+ } finally {
47
+ rmSync(tmp, { force: true });
48
+ }
49
+ }
50
+
51
+ /** Record a green verify of the code as it is now. */
52
+ export function stampVerified(projectDir, id) {
53
+ const tree = codeTree(projectDir);
54
+ if (!tree) return;
55
+ const file = stampFile(projectDir, id);
56
+ mkdirSync(path.dirname(file), { recursive: true });
57
+ writeFileSync(file, `${JSON.stringify({ tree, at: new Date().toISOString() })}\n`);
58
+ }
59
+
60
+ /** A move ended the item's work: its record goes with it. */
61
+ export const dropStamp = (projectDir, id) => rmSync(stampFile(projectDir, id), { force: true });
62
+
63
+ /** The files that differ between two trees, the first few, for a refusal to name. */
64
+ function changed(projectDir, from, to) {
65
+ const r = git(projectDir, ['diff-tree', '-r', '--name-only', from, to]);
66
+ const files = r.status === 0 ? r.stdout.split('\n').filter(Boolean) : [];
67
+ const more = files.length > 5 ? ` and ${files.length - 5} more` : '';
68
+
69
+ return files.length ? ` (${files.slice(0, 5).join(', ')}${more})` : '';
70
+ }
71
+
72
+ /**
73
+ * Why `close` or `submit` may not take the item now, or null: no green verify on record, or
74
+ * the code changed since. A feature closes over its children and has no code of its own. The
75
+ * way past it is named as the human's: an agent reads this text.
76
+ */
77
+ export function unverifiedProblem(projectDir, id, type) {
78
+ if (type === 'feature') return null;
79
+ const tree = codeTree(projectDir);
80
+ if (!tree) return null;
81
+ const how =
82
+ `run ai-factory gcf verify ${id} until it is green; only the human may move it without ` +
83
+ 'one (--unverified "<why>")';
84
+ const stamp = readStamp(projectDir, id);
85
+ if (!stamp) return `${id}: no green gcf verify on record — ${how}`;
86
+ if (stamp.tree !== tree) {
87
+ const files = changed(projectDir, stamp.tree, tree);
88
+
89
+ return `${id}: the code changed since its green gcf verify${files} — ${how}`;
90
+ }
91
+
92
+ return null;
93
+ }
94
+
95
+ /** @returns {{tree: string, at: string} | null} the record, or null when there is none. */
96
+ function readStamp(projectDir, id) {
97
+ try {
98
+ return JSON.parse(readFileSync(stampFile(projectDir, id), 'utf8'));
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
package/lib/work-ops.js CHANGED
@@ -39,6 +39,7 @@ import { dropInbox } from './inbox.js';
39
39
  import { refuseByPhase } from './phase.js';
40
40
  import { locked, runHolder } from './main-lock.js';
41
41
  import { dropAgentSession, releaseSession, sessionActive } from './session-mark.js';
42
+ import { dropStamp, unverifiedProblem } from './verify-stamp.js';
42
43
 
43
44
  /**
44
45
  * Options of a committed move; each move reads the ones it needs.
@@ -232,10 +233,38 @@ async function finish(m, move, tail, commit) {
232
233
  // and no rerun resumes it.
233
234
  releaseSession(m.projectDir, String(m.item.data.id));
234
235
  dropAgentSession(m.projectDir, String(m.item.data.id));
236
+ dropStamp(m.projectDir, String(m.item.data.id));
235
237
 
236
238
  return hash;
237
239
  }
238
240
 
241
+ /**
242
+ * `close` and `submit` from a command (Methodology §6): the code as it is now needs a green
243
+ * `gcf verify`, unless the operator says why not (`unverified`, written into the move's
244
+ * commit). The integrator's moves rest on the branch's trailer instead.
245
+ */
246
+ function needVerified(m, opts) {
247
+ if (!opts.requireVerify || opts.unverified) return;
248
+ const id = String(m.item.data.id);
249
+ const problem = verifyGateProblem(m.projectDir, id, m.item.data.type);
250
+ if (problem) throw new Error(problem);
251
+ }
252
+
253
+ /** The verify gate's refusal, or null; a git that cannot hash the code refuses too. */
254
+ function verifyGateProblem(projectDir, id, type) {
255
+ try {
256
+ return unverifiedProblem(projectDir, id, type);
257
+ } catch (e) {
258
+ return (
259
+ `${id}: cannot hash the code for the verify gate (${e.message}) — fix that, or the ` +
260
+ 'human passes --unverified "<why>"'
261
+ );
262
+ }
263
+ }
264
+
265
+ /** The commit's tail of a move made without a green verify. */
266
+ const unverifiedTail = (opts) => (opts.unverified ? ` (unverified: ${opts.unverified})` : '');
267
+
239
268
  const writeItem = (item, data, body = item.body) => atomicWrite(item.file, serialize(data, body));
240
269
 
241
270
  /** `data` without the keys named — a field that no longer applies leaves the frontmatter. */
@@ -481,11 +510,12 @@ async function submitItemUnlocked(projectDir, id, opts = {}) {
481
510
  if (m.item.data.gate === 'auto')
482
511
  throw new Error(`${id} has gate auto: nobody waits for it — close it with gcf close ${id}`);
483
512
  refuseOpenDepends(m, 'submit', { waiting: true });
513
+ needVerified(m, opts);
484
514
  const status = m.item.data.gate === 'verdict' ? 'awaiting-verdict' : 'awaiting-decision';
485
515
  const data = { ...without(m.item.data, 'blocked'), status, since: headHash(projectDir) };
486
516
  m.step(() => writeItem(m.item, data));
487
517
 
488
- return { id, status, hash: await finish(m, 'submit', id, commit) };
518
+ return { id, status, hash: await finish(m, 'submit', `${id}${unverifiedTail(opts)}`, commit) };
489
519
  }
490
520
 
491
521
  /** Why a soft gate-auto item cannot close unplayed, or null: only an `added` comes back out. */
@@ -556,11 +586,13 @@ async function closeItemUnlocked(projectDir, id, opts = {}) {
556
586
  if (m.item.data.gate !== 'auto')
557
587
  throw new Error(`${id} has gate ${m.item.data.gate}: submit it, the operator closes it`);
558
588
  refuseOpenDepends(m, 'close');
589
+ needVerified(m, opts);
559
590
  if (unplayed) closeUnplayed(m);
560
591
  else await closeEnding(m, constraints);
561
592
  }
562
593
 
563
- const hash = await finish(m, 'close', `${id}${unplayed ? ' → unplayed' : ''}`, commit);
594
+ const tail = `${id}${unplayed ? ' → unplayed' : ''}${unverifiedTail(opts)}`;
595
+ const hash = await finish(m, 'close', tail, commit);
564
596
 
565
597
  return { id, hash, unplayed };
566
598
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gcf-aif-ext",
3
- "version": "0.7.10",
3
+ "version": "0.7.13",
4
4
  "description": "Game Context Framework for AI Factory",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,40 +1,42 @@
1
- gcf_context:
2
- - the game's design and its work live in `gcf/`, and the `ai-factory gcf` commands and the gcf skills change them
3
- - read `gcf/INDEX.md` before anything else under `gcf/`: it lists every subsystem, design unit and work item
4
- - `gcf/LIVE.md` says what the game does now, and `gcf/PILLARS.md` says what it must stay
5
- - `ai-factory gcf board --json` gives the whole state in one read: the phase, the items by status with plan progress, the inbox and the open questions
6
-
7
- gcf_work:
8
- - every piece of work is one work item `gcf/work/<id>.md`: find the item before writing code for a feature, a bug or a tech task, or create it with the gcf-card skill
9
- - a raw idea, a playtest note or a CI finding that is not work yet goes to `ai-factory gcf inbox add "<text>"`, and the gcf-triage skill sorts it later; a text that starts with a hyphen goes as `inbox add --text="<text>"`
10
- - before planning an item, run `ai-factory gcf context <id>` and read only the files it lists under `gcf/`
11
- - drive a ready item with the gcf-run skill when the project has gcf-harness-aif-ext (a batch at once: `ai-factory gcf-harness run <ids...>`), otherwise with aif-plan and aif-implement
12
- - after the code, write the item's `live:` delta and run `ai-factory gcf verify <id>`: fix what it reports and run it again until every check is green
13
- - then end the work on the item with its command: `ai-factory gcf submit <id>` for gate verdict or decision, `ai-factory gcf close <id>` for gate auto
14
- - work that cannot go on: run `ai-factory gcf block <id> --reason "<why or the question>"`
15
- - in a worktree on branch `work/<id>` change no `gcf/` file but the item's `live:`, commit all, then run `ai-factory gcf verify <id> --mark`: `ai-factory gcf integrate` in the main checkout merges it and records the move
16
- - a design question only the human can answer becomes a D1 record in `gcf/open-questions.md`
17
- - a word added to a `not:` of `gcf/glossary.md` leaves `gcf/design/` and every item but an unplayed one in the same commit: `ai-factory gcf design check` names each hit as M4
18
- - the human plays from `ai-factory gcf playtest session`; each free note from that play goes to `ai-factory gcf inbox add --source playtest "<note>"`
19
- - a release item carries `version:` and `channel:` (`item new <id> --type release --release <v> --channel <c>`): its end writes the `release:` block of `gcf/LIVE.md`
20
- - `item new` and `take` refuse a type the current phase does not take: run `ai-factory gcf phase` and bring the refusal to the human
21
- - an item the human wants run on a given model or effort gets `item new <id> --model <m> --effort <e>`: the harness run passes both to the agent
22
- - ready items run in `priority:` order, lower first: rank a set of items with `ai-factory gcf prioritize <id> <id> …`, and put an id in `depends` only when the item uses that one's data or code
23
- - a D1 record that carries the human's `answer:` goes into the slot of each unit it blocks, with `answer: q-NNN`, and then leaves `gcf/open-questions.md`
24
- - a pillar changes only on the human's word: `ai-factory gcf pillar rename <from> <to>`, or `ai-factory gcf pillar retire <id> --reason "<why>"`
25
-
26
- gcf_forbidden:
27
- - do not edit `status:`, `frozen:`, `gcf/LIVE.md`, `gcf/history.md`, `gcf/graveyard.md` or `gcf/INDEX.md` by hand: the commands own them
28
- - do not give a verdict or a decision: `verdict` and `decide` belong to the human, through the gcf-verdict skill
29
- - do not answer a `TBD` of a design unit yourself: ask the human and offer your draft as the proposed answer
30
- - do not run `ai-factory gcf phase next`: the phase moves on the human's word only
31
- - do not change what agent runs may do: `ai-factory gcf permissions` and its deny groups belong to the human
32
- - do not change a setting of `gcf/config.yml`: `ai-factory gcf config set` belongs to the human
33
- - do not reuse an ended id: rework gets a new id with `reopens: <old id>`
34
- - do not commit past the gate with `--no-verify`: a refused commit names what to fix
35
-
36
- gcf_skills:
37
- - gcf-vision starts a game that has no design yet, and gcf-ingest imports a GDD into `gcf/design/`
38
- - gcf-card puts work into `gcf/work/`, and gcf-triage sorts `gcf/inbox.md`
39
- - gcf-verdict records the human's verdict or decision, and gcf-audit reports the health of `gcf/`
40
- - gcf-debate stresses the design, gcf-adopt puts existing code on GCF, and gcf-init sets up `gcf/` and revises the pillars
1
+ gcf_context:
2
+ - the game's design and its work live in `gcf/`, and the `ai-factory gcf` commands and the gcf skills change them
3
+ - read `gcf/INDEX.md` before anything else under `gcf/`: it lists every subsystem, design unit and work item
4
+ - `gcf/LIVE.md` says what the game does now, and `gcf/PILLARS.md` says what it must stay
5
+ - `ai-factory gcf board --json` gives the whole state in one read: the phase, the items by status with plan progress, the inbox and the open questions
6
+
7
+ gcf_work:
8
+ - every piece of work is one work item `gcf/work/<id>.md`: find the item before writing code for a feature, a bug or a tech task, or create it with the gcf-card skill
9
+ - a raw idea, a playtest note or a CI finding that is not work yet goes to `ai-factory gcf inbox add "<text>"`, and the gcf-triage skill sorts it later; a text that starts with a hyphen goes as `inbox add --text="<text>"`
10
+ - before planning an item, run `ai-factory gcf context <id>` and read only the files it lists under `gcf/`
11
+ - drive a ready item with the gcf-run skill when the project has gcf-harness-aif-ext (a batch at once: `ai-factory gcf-harness run <ids...>`), otherwise with aif-plan and aif-implement
12
+ - a question about how the runs go (the share that reached their move, cost, plan checks, DoD attempts, guard firings) is answered by `ai-factory gcf-harness metrics`
13
+ - after the code, write the item's `live:` delta and run `ai-factory gcf verify <id>`: fix what it reports and run it again until every check is green
14
+ - `submit` and `close` refuse code that changed since its last green `gcf verify`: run it again after any fix; `--unverified "<why>"` is the human's call, never the agent's
15
+ - then end the work on the item with its command: `ai-factory gcf submit <id>` for gate verdict or decision, `ai-factory gcf close <id>` for gate auto
16
+ - work that cannot go on: run `ai-factory gcf block <id> --reason "<why or the question>"`
17
+ - in a worktree on branch `work/<id>` change no `gcf/` file but the item's `live:`, commit all, then run `ai-factory gcf verify <id> --mark`: `ai-factory gcf integrate` in the main checkout merges it and records the move
18
+ - a design question only the human can answer becomes a D1 record in `gcf/open-questions.md`
19
+ - a word added to a `not:` of `gcf/glossary.md` leaves `gcf/design/` and every item but an unplayed one in the same commit: `ai-factory gcf design check` names each hit as M4
20
+ - the human plays from `ai-factory gcf playtest session`; each free note from that play goes to `ai-factory gcf inbox add --source playtest "<note>"`
21
+ - a release item carries `version:` and `channel:` (`item new <id> --type release --release <v> --channel <c>`): its end writes the `release:` block of `gcf/LIVE.md`
22
+ - `item new` and `take` refuse a type the current phase does not take: run `ai-factory gcf phase` and bring the refusal to the human
23
+ - an item the human wants run on a given model or effort gets `item new <id> --model <m> --effort <e>`: the harness run passes both to the agent
24
+ - ready items run in `priority:` order, lower first: rank a set of items with `ai-factory gcf prioritize <id> <id> …`, and put an id in `depends` only when the item uses that one's data or code
25
+ - a D1 record that carries the human's `answer:` goes into the slot of each unit it blocks, with `answer: q-NNN`, and then leaves `gcf/open-questions.md`
26
+ - a pillar changes only on the human's word: `ai-factory gcf pillar rename <from> <to>`, or `ai-factory gcf pillar retire <id> --reason "<why>"`
27
+
28
+ gcf_forbidden:
29
+ - do not edit `status:`, `frozen:`, `gcf/LIVE.md`, `gcf/history.md`, `gcf/graveyard.md` or `gcf/INDEX.md` by hand: the commands own them
30
+ - do not give a verdict or a decision: `verdict` and `decide` belong to the human, through the gcf-verdict skill
31
+ - do not answer a `TBD` of a design unit yourself: ask the human and offer your draft as the proposed answer
32
+ - do not run `ai-factory gcf phase next`: the phase moves on the human's word only
33
+ - do not change what agent runs may do: `ai-factory gcf permissions` and its deny groups belong to the human
34
+ - do not change a setting of `gcf/config.yml`: `ai-factory gcf config set` belongs to the human
35
+ - do not reuse an ended id: rework gets a new id with `reopens: <old id>`
36
+ - do not commit past the gate with `--no-verify`: a refused commit names what to fix
37
+
38
+ gcf_skills:
39
+ - gcf-vision starts a game that has no design yet, and gcf-ingest imports a GDD into `gcf/design/`
40
+ - gcf-card puts work into `gcf/work/`, and gcf-triage sorts `gcf/inbox.md`
41
+ - gcf-verdict records the human's verdict or decision, and gcf-audit reports the health of `gcf/`
42
+ - gcf-debate stresses the design, gcf-adopt puts existing code on GCF, and gcf-init sets up `gcf/` and revises the pillars