froid-loop 0.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- froid_loop/__init__.py +11 -0
- froid_loop/__main__.py +12 -0
- froid_loop/adapters/__init__.py +3 -0
- froid_loop/adapters/base.py +254 -0
- froid_loop/adapters/entrypoints.py +63 -0
- froid_loop/adapters/env_fault.py +290 -0
- froid_loop/adapters/generic.py +2013 -0
- froid_loop/adapters/mock.py +49 -0
- froid_loop/adapters/multiplexer.py +914 -0
- froid_loop/adapters/opencode_http.py +1687 -0
- froid_loop/adapters/profile.py +650 -0
- froid_loop/adapters/psmux_backend.py +1428 -0
- froid_loop/adapters/registry.py +322 -0
- froid_loop/adapters/tmux_backend.py +35 -0
- froid_loop/adapters/tmux_base.py +630 -0
- froid_loop/checks.py +187 -0
- froid_loop/cli.py +5041 -0
- froid_loop/data/__init__.py +0 -0
- froid_loop/data/froid_loop_hook.py +228 -0
- froid_loop/data/froid_loop_probe_hook.py +88 -0
- froid_loop/data/plugins/example/plugin.toml +21 -0
- froid_loop/data/plugins/tea/plugin.toml +184 -0
- froid_loop/data/plugins/tea/tea_plugin.py +258 -0
- froid_loop/data/plugins/unity/plugin.toml +140 -0
- froid_loop/data/plugins/unity/unity_assets/FroidLoop.Unity.Editor.asmdef +16 -0
- froid_loop/data/plugins/unity/unity_assets/FroidLoop.Unity.Editor.asmdef.meta +7 -0
- froid_loop/data/plugins/unity/unity_assets/SceneAutoSaveGuard.cs +221 -0
- froid_loop/data/plugins/unity/unity_assets/SceneAutoSaveGuard.cs.meta +11 -0
- froid_loop/data/plugins/unity/unity_assets/_folders/Editor.meta +8 -0
- froid_loop/data/plugins/unity/unity_assets/_folders/FroidLoop.meta +8 -0
- froid_loop/data/plugins/unity/unity_cleanup.py +125 -0
- froid_loop/data/plugins/unity/unity_dialog_probe.py +239 -0
- froid_loop/data/plugins/unity/unity_facts.md +17 -0
- froid_loop/data/plugins/unity/unity_plugin.py +415 -0
- froid_loop/data/plugins/unity/unity_quiesce.py +234 -0
- froid_loop/data/plugins/unity/unity_ready.py +230 -0
- froid_loop/data/plugins/unity/unity_seed_assets.py +298 -0
- froid_loop/data/plugins/unity/unity_setup.py +551 -0
- froid_loop/data/plugins/unity/unity_teardown.py +362 -0
- froid_loop/data/profiles/antigravity.toml +52 -0
- froid_loop/data/profiles/claude.toml +85 -0
- froid_loop/data/profiles/codex.toml +22 -0
- froid_loop/data/profiles/copilot.toml +52 -0
- froid_loop/data/profiles/gemini.toml +26 -0
- froid_loop/data/profiles/opencode.toml +54 -0
- froid_loop/data/settings/core.toml +458 -0
- froid_loop/data/skills/README.md +93 -0
- froid_loop/data/skills/froid-loop-resolve/SKILL.md +288 -0
- froid_loop/data/skills/froid-loop-setup/SKILL.md +161 -0
- froid_loop/data/skills/froid-loop-setup/assets/module-help.csv +3 -0
- froid_loop/data/skills/froid-loop-setup/assets/module.yaml +19 -0
- froid_loop/data/skills/froid-loop-sweep/SKILL.md +100 -0
- froid_loop/data/skills/froid-loop-sweep/automation-mode.md +127 -0
- froid_loop/data/skills/froid-loop-sweep/deferred-work-format.md +302 -0
- froid_loop/data/skills/froid-loop-sweep/migration-mode.md +86 -0
- froid_loop/decisions.py +202 -0
- froid_loop/deferredwork.py +2282 -0
- froid_loop/devcontract.py +892 -0
- froid_loop/diagnostics.py +1104 -0
- froid_loop/documents.py +532 -0
- froid_loop/engine.py +7732 -0
- froid_loop/envvars.py +111 -0
- froid_loop/escalation.py +225 -0
- froid_loop/events.py +266 -0
- froid_loop/fences.py +103 -0
- froid_loop/froidconfig.py +226 -0
- froid_loop/frontmatter.py +526 -0
- froid_loop/gates.py +133 -0
- froid_loop/install.py +2936 -0
- froid_loop/journal.py +178 -0
- froid_loop/machine.py +148 -0
- froid_loop/model.py +898 -0
- froid_loop/operatoractions.py +474 -0
- froid_loop/platform_util.py +1490 -0
- froid_loop/plugins/__init__.py +64 -0
- froid_loop/plugins/bus.py +259 -0
- froid_loop/plugins/context.py +319 -0
- froid_loop/plugins/loader.py +145 -0
- froid_loop/plugins/manifest.py +279 -0
- froid_loop/plugins/model.py +296 -0
- froid_loop/plugins/registry.py +245 -0
- froid_loop/plugins/trust.py +75 -0
- froid_loop/policy.py +1569 -0
- froid_loop/probe.py +1044 -0
- froid_loop/process_host.py +408 -0
- froid_loop/recovery_flow.py +1561 -0
- froid_loop/resolve.py +283 -0
- froid_loop/runs.py +4715 -0
- froid_loop/runsetup.py +1293 -0
- froid_loop/sanitize.py +593 -0
- froid_loop/settings_schema.py +276 -0
- froid_loop/signals.py +160 -0
- froid_loop/sprintstatus.py +609 -0
- froid_loop/statemachine.py +57 -0
- froid_loop/stories.py +615 -0
- froid_loop/stories_engine.py +796 -0
- froid_loop/sweep.py +1892 -0
- froid_loop/tokens.py +196 -0
- froid_loop/tui/__init__.py +11 -0
- froid_loop/tui/app.py +1584 -0
- froid_loop/tui/data.py +840 -0
- froid_loop/tui/launch.py +1003 -0
- froid_loop/tui/screens/__init__.py +1 -0
- froid_loop/tui/screens/dashboard.py +1071 -0
- froid_loop/tui/screens/modals.py +943 -0
- froid_loop/tui/screens/settings_screen.py +477 -0
- froid_loop/tui/settings.py +135 -0
- froid_loop/tui/widgets.py +981 -0
- froid_loop/verify.py +4545 -0
- froid_loop/workspace.py +320 -0
- froid_loop/worktree_flow.py +2301 -0
- froid_loop-0.11.1.dist-info/METADATA +728 -0
- froid_loop-0.11.1.dist-info/RECORD +116 -0
- froid_loop-0.11.1.dist-info/WHEEL +4 -0
- froid_loop-0.11.1.dist-info/entry_points.txt +2 -0
- froid_loop-0.11.1.dist-info/licenses/LICENSE +30 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Automation Mode
|
|
2
|
+
|
|
3
|
+
You are running unattended inside a `froid-loop` sweep session. No human is
|
|
4
|
+
watching; a deterministic program spawned you, will validate your result.json
|
|
5
|
+
field-by-field, and will kill this session after your final turn.
|
|
6
|
+
|
|
7
|
+
## Identity & I/O contract
|
|
8
|
+
|
|
9
|
+
- `$FROID_LOOP_RUN_DIR` and `$FROID_LOOP_TASK_ID` are set in your environment.
|
|
10
|
+
- Your **result file** is `$FROID_LOOP_RUN_DIR/tasks/$FROID_LOOP_TASK_ID/result.json`.
|
|
11
|
+
Writing it is the LAST action of the run. Schema:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"workflow": "deferred-sweep-triage",
|
|
16
|
+
"open_ids": ["DW-1", "DW-3", "..."],
|
|
17
|
+
"already_resolved": [{ "id": "DW-1", "evidence": "<file:line or commit that resolved it>" }],
|
|
18
|
+
"bundles": [
|
|
19
|
+
{
|
|
20
|
+
"name": "<kebab-case-name>",
|
|
21
|
+
"dw_ids": ["DW-3"],
|
|
22
|
+
"intent": "<2-6 sentences: the one cohesive goal>"
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"blocked": [{ "id": "DW-4", "blocker": "<named story/epic that must land first>" }],
|
|
26
|
+
"skip": [{ "id": "DW-9", "reason": "<why this is moot/superseded>" }],
|
|
27
|
+
"decisions": [
|
|
28
|
+
{
|
|
29
|
+
"id": "DW-7",
|
|
30
|
+
"question": "<the choice the human must make>",
|
|
31
|
+
"context": "<2-4 sentences of code-grounded context>",
|
|
32
|
+
"options": [
|
|
33
|
+
{
|
|
34
|
+
"key": "1",
|
|
35
|
+
"label": "<short label>",
|
|
36
|
+
"effect": "build",
|
|
37
|
+
"intent": "<what a dev session would implement>"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"key": "2",
|
|
41
|
+
"label": "<short label>",
|
|
42
|
+
"effect": "close",
|
|
43
|
+
"resolution": "<optional: why closing is fine>"
|
|
44
|
+
},
|
|
45
|
+
{ "key": "3", "label": "<short label>", "effect": "keep-open" }
|
|
46
|
+
],
|
|
47
|
+
"recommendation": "1"
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"escalations": []
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Validation rules the orchestrator enforces (a violation fails the whole
|
|
55
|
+
result and burns a retry):
|
|
56
|
+
- `open_ids` must list exactly the ledger's `status: open` entries — the
|
|
57
|
+
orchestrator parses the ledger itself and compares.
|
|
58
|
+
- Every open id appears in exactly ONE of already_resolved / bundles /
|
|
59
|
+
blocked / skip / decisions. No misses, no duplicates, no invented ids.
|
|
60
|
+
- Bundle names: `^[a-z0-9][a-z0-9-]{1,39}\Z`, unique, non-empty `dw_ids`,
|
|
61
|
+
non-empty `intent`. An otherwise-valid overlong bundle name or decision
|
|
62
|
+
option `bundle_name` is truncated to 40 characters and journaled before
|
|
63
|
+
validation; post-truncation name collisions still fail validation.
|
|
64
|
+
- Every `already_resolved` entry needs non-empty `evidence`; every
|
|
65
|
+
`blocked` a `blocker`; every `skip` a `reason`.
|
|
66
|
+
- Decisions: >= 2 options with unique keys, `effect` one of
|
|
67
|
+
`build|close|keep-open`, `intent` required when effect is `build`,
|
|
68
|
+
`recommendation` must be one of the option keys.
|
|
69
|
+
|
|
70
|
+
- Write `already_resolved[].evidence` and an option's `label` and `resolution`
|
|
71
|
+
as a **single line** — each is copied onto one line of the line-oriented
|
|
72
|
+
deferred-work ledger. This is guidance, not a validation rule: a break is
|
|
73
|
+
collapsed to a space rather than rejected, so it costs nothing but reads
|
|
74
|
+
worse. Both `intent` fields are exempt — keep them at the length the schema
|
|
75
|
+
asks above (2-6 sentences for a bundle), newlines and all.
|
|
76
|
+
|
|
77
|
+
- **Migration sessions** (`--migrate`, see `./migration-mode.md`) use this
|
|
78
|
+
result schema instead:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"workflow": "deferred-sweep-migrate",
|
|
83
|
+
"mapping": [{ "key": "<manifest key>", "dw_id": "DW-12" }],
|
|
84
|
+
"escalations": []
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Validation rules: the rewritten ledger parses with ZERO legacy items;
|
|
89
|
+
pre-existing `### DW-<n>:` entries keep their ids and status; new entries
|
|
90
|
+
continue numbering past the highest existing number with `status: open` or
|
|
91
|
+
`status: done <date>`; `mapping` covers every manifest key exactly once and
|
|
92
|
+
each `dw_id` exists with the manifest's open/done state (two keys may share
|
|
93
|
+
a `dw_id` when merging duplicates of equal done-ness).
|
|
94
|
+
|
|
95
|
+
- Your **escalation file** is `$FROID_LOOP_RUN_DIR/tasks/$FROID_LOOP_TASK_ID/escalation.json`.
|
|
96
|
+
Use it only for blockers no rule resolves (e.g. the ledger is missing or
|
|
97
|
+
unreadable: `type: missing-ledger`, severity `CRITICAL`), then include the
|
|
98
|
+
same entries in result.json `escalations` and end your turn. Schema:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"escalations": [
|
|
103
|
+
{
|
|
104
|
+
"type": "<short-kebab-kind>",
|
|
105
|
+
"severity": "CRITICAL|PREFERENCE",
|
|
106
|
+
"detail": "<one or two sentences>"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Behavior rules
|
|
113
|
+
|
|
114
|
+
1. **Never HALT for input. Never ask the user anything.** No greeting, no
|
|
115
|
+
menus, no "what next" offers.
|
|
116
|
+
2. **Read-only — except migration mode.** In triage you never edit the
|
|
117
|
+
ledger, code, specs, or sprint-status; the orchestrator performs all
|
|
118
|
+
ledger edits deterministically and runs the bundles you propose through
|
|
119
|
+
separate dev/review sessions. A `--migrate` session edits exactly one
|
|
120
|
+
file: the ledger (still never code/specs/sprint-status, never commits).
|
|
121
|
+
3. **Verify before classifying.** Never trust an entry's own status or wording;
|
|
122
|
+
check the code. An entry that says "open" but is fixed goes to
|
|
123
|
+
already_resolved with evidence.
|
|
124
|
+
4. **Conservative on human territory.** Frozen-block renegotiations, scope
|
|
125
|
+
reversals, and API-shape changes are decisions, not bundles (see SKILL.md
|
|
126
|
+
Step 3). When in doubt between bundle and decision, choose decision.
|
|
127
|
+
5. **Never commit, never push, never open an editor.**
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Deferred Work Format
|
|
2
|
+
|
|
3
|
+
Canonical entry format for `{implementation_artifacts}/deferred-work.md`. The
|
|
4
|
+
orchestrator owns this file, and two eras of dev session feed it:
|
|
5
|
+
|
|
6
|
+
- **Current (Froid Plane 6.10.1-next.33+).** The unattended primitive
|
|
7
|
+
`froid-build-auto` writes nothing here: it records defer-triaged review findings
|
|
8
|
+
in its spec's frontmatter `deferred:` list, and after the session the
|
|
9
|
+
orchestrator harvests those into canonical entries below, carrying a
|
|
10
|
+
fingerprinted `origin:` that starts with `spec-deferred`, plus `source_spec:`.
|
|
11
|
+
- **Legacy and attended.** Pre-rename primitives (`froid-dev-auto`) and the
|
|
12
|
+
attended `froid-build` append flat `- source_spec:` blocks directly into this
|
|
13
|
+
file; a `froid-loop sweep --migrate` session normalizes them into the canonical
|
|
14
|
+
form, and rewrites freeform pre-DW-format content from older projects wholesale
|
|
15
|
+
(see `./migration-mode.md`; the TUI shows such legacy items read-only until
|
|
16
|
+
then).
|
|
17
|
+
|
|
18
|
+
Either way this file stays the sweep's sole read surface. Multi-goal and token
|
|
19
|
+
splits are a legacy/attended source only — the current unattended primitive does
|
|
20
|
+
not split a multi-goal spec, it records a `multiple-goals` warning in the spec's
|
|
21
|
+
`warnings:` and proceeds.
|
|
22
|
+
|
|
23
|
+
The file is append-only — never rewrite or delete existing entries. The one
|
|
24
|
+
sanctioned rewrite is operator-run archiving (below): closed entries move to
|
|
25
|
+
`deferred-work-archive.md` — body preserved, with an `archived: <date>` marker
|
|
26
|
+
appended after the status line — leaving a stub behind.
|
|
27
|
+
|
|
28
|
+
## Archiving (`froid-loop sweep --archive`)
|
|
29
|
+
|
|
30
|
+
The operator may periodically move closed entries
|
|
31
|
+
(`status: done <ISO date>`) to the sibling `deferred-work-archive.md`, leaving
|
|
32
|
+
a stub in this file:
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
### DW-7: Old closed item
|
|
36
|
+
|
|
37
|
+
status: done 2026-05-25
|
|
38
|
+
archived: 2026-08-24
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Rules for sessions reading the ledger:
|
|
42
|
+
|
|
43
|
+
- The stub's `status: done` line means what it always meant — the entry is
|
|
44
|
+
closed and not open work.
|
|
45
|
+
- An `archived:` line marks content that lives in `deferred-work-archive.md`.
|
|
46
|
+
The full body (evidence, resolution, dates) is there, keyed by the same
|
|
47
|
+
DW- id — read the archive file for anything beyond the stub. An id may own
|
|
48
|
+
more than one block there once a reopened entry has been archived twice.
|
|
49
|
+
Narrow by the date on this line; when several blocks share it — one entry
|
|
50
|
+
closed, archived, reopened and archived again inside a single day — take the
|
|
51
|
+
**last** of them. The archive file is append-only, so for one id a later
|
|
52
|
+
block is a later closure.
|
|
53
|
+
- An `archived-body:` line is that same pointer carried by an entry that was
|
|
54
|
+
archived and then **reopened** — the orchestrator writes it in place of the
|
|
55
|
+
`archived:` stamp, which would otherwise claim a live entry's body is
|
|
56
|
+
elsewhere. The entry is open work again and its `status:` says so, but the
|
|
57
|
+
body it carried before that close is still in the archive file — resolve
|
|
58
|
+
this line exactly as an `archived:` stamp above: narrow by its date, and take
|
|
59
|
+
the last of the blocks that share it. Reopening does not bring the body back, and a stub keeps neither `location:` nor `reason:`, so read that block
|
|
60
|
+
before triaging the entry. Never edit or drop the line.
|
|
61
|
+
- The archive may be absent even when a stub or an `archived-body:` line
|
|
62
|
+
references it: only the ledger is seeded into an isolated unit worktree. That
|
|
63
|
+
is not an error — the fields the stub preserves are sufficient for dedupe.
|
|
64
|
+
- Stubs keep load-bearing field lines (`gate:`, `origin:`/`source_spec:`,
|
|
65
|
+
resolution undo markers) — treat them exactly as if the entry were still
|
|
66
|
+
whole. Never edit or drop them when touching a stub.
|
|
67
|
+
- When deduping against existing entries, a stub still counts: match on the
|
|
68
|
+
id and preserved `origin:`/`source_spec:` lines, and check the archive for
|
|
69
|
+
the full substance before appending.
|
|
70
|
+
|
|
71
|
+
## Before appending: dedupe check
|
|
72
|
+
|
|
73
|
+
Scan the existing file for an entry describing the same issue or goal (same
|
|
74
|
+
location and same substance, even if worded differently). If one exists, do
|
|
75
|
+
NOT append a duplicate — add a `seen-again:` line to the existing entry
|
|
76
|
+
instead:
|
|
77
|
+
|
|
78
|
+
```markdown
|
|
79
|
+
seen-again: 2026-06-12 (code review of spec-3-3-export.md)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Entry format
|
|
83
|
+
|
|
84
|
+
Number entries sequentially (`DW-1`, `DW-2`, …) by scanning the file for the
|
|
85
|
+
highest existing number. One entry per deferred item:
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
### DW-<seq>: <one-line title>
|
|
89
|
+
|
|
90
|
+
origin: <workflow + artifact + date, e.g. "code review of spec-3-2-digest.md, 2026-06-12">
|
|
91
|
+
location: <file:line or component, or "n/a" for deferred goals>
|
|
92
|
+
severity: <critical | high | medium | low — how much it matters if never done>
|
|
93
|
+
reason: <why this was deferred rather than done now, one or two sentences>
|
|
94
|
+
status: open
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`location:` is always written. Use `n/a` whenever there is nothing to open — a
|
|
98
|
+
deferred goal, but equally a finding whose reporter recorded no place. The field
|
|
99
|
+
says "no location was recorded", not "this item has none": a reader that finds
|
|
100
|
+
`n/a` should fall back to `reason:`, which often names the file even when
|
|
101
|
+
`location:` is empty. Entries written before this rule may omit the line; read
|
|
102
|
+
an absent `location:` as `n/a`, never as "not yet known".
|
|
103
|
+
|
|
104
|
+
Entries the orchestrator harvests from a spec carry one extra line,
|
|
105
|
+
`source_spec:`, directly after `location:` — the spec the deferral came from. It
|
|
106
|
+
is half the dedupe key (with `origin:`), so never edit or drop it when touching
|
|
107
|
+
an entry; entries written by hand do not need it.
|
|
108
|
+
|
|
109
|
+
**Every field line is exactly one line, and so is the title.** The format is
|
|
110
|
+
line-oriented: readers find each field by scanning for `<name>:` at the start of
|
|
111
|
+
a line, and an entry ends at whichever comes first — the next `### DW-<n>`
|
|
112
|
+
entry, any other `#` .. `######` heading (indented up to three spaces, and
|
|
113
|
+
followed by a space, a tab or the end of the line; four spaces or a leading tab
|
|
114
|
+
makes an indented code block, which ends nothing), or a `- source_spec:`
|
|
115
|
+
flat-append bullet. A value carrying a line break therefore does not wrap; it
|
|
116
|
+
becomes new ledger content, and three things can follow:
|
|
117
|
+
|
|
118
|
+
- a break followed by `### ` mints an entry nobody filed;
|
|
119
|
+
- a break before a `status:` line leaves one entry carrying two, so the ledger
|
|
120
|
+
no longer says one thing about it;
|
|
121
|
+
- a break followed by `- source_spec:` cuts the entry short at that bullet, and
|
|
122
|
+
everything after it re-surfaces as a phantom _legacy_ item.
|
|
123
|
+
|
|
124
|
+
Keep breaks out of field values, along with `### ` and a leading
|
|
125
|
+
`- source_spec:`. If a reason needs two sentences, write them on one line.
|
|
126
|
+
|
|
127
|
+
`severity:` is optional — entries written before this field existed have none
|
|
128
|
+
and that is fine; readers must treat a missing or unrecognized value as
|
|
129
|
+
"unspecified". Use `critical` for correctness/security issues, `high` for
|
|
130
|
+
likely user-visible problems, `medium` for quality and robustness gaps, `low`
|
|
131
|
+
for polish and nice-to-haves.
|
|
132
|
+
|
|
133
|
+
When a deferred item is later completed, set its `status:` to `done` with the
|
|
134
|
+
date (e.g. `status: done 2026-06-20`) — do not delete the entry.
|
|
135
|
+
|
|
136
|
+
## Hard gates: `gate:`
|
|
137
|
+
|
|
138
|
+
Some entries are not merely deferred — they **block** specific stories. An
|
|
139
|
+
infrastructure leg nobody has wired yet is not a nice-to-have for the first story
|
|
140
|
+
that consumes it; that story must not run at all until the entry lands. Say so
|
|
141
|
+
with a `gate:` line naming the blocked story keys:
|
|
142
|
+
|
|
143
|
+
```markdown
|
|
144
|
+
### DW-1: wire the blob-storage credentials
|
|
145
|
+
|
|
146
|
+
status: open
|
|
147
|
+
gate: 3-2, 3-3
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`gate:` is optional and most entries have none. Its value is a **comma-separated**
|
|
151
|
+
list of story-key tokens; several `gate:` lines in one entry union, so an entry
|
|
152
|
+
blocking three stories may list them on one line or on three. A token matches a
|
|
153
|
+
story key when it **is** that key, or is its prefix at a key boundary — either a
|
|
154
|
+
`-`, or the split-story suffix (one lowercase letter then `-`). So `3-2` gates the
|
|
155
|
+
sprint key `3-2-invite-link-student-surface`, the stories-mode id `3-2`, and both
|
|
156
|
+
halves of a split (`3-2a-…` / `3-2b-…`), but never `3-20-later-story`. The split
|
|
157
|
+
arm matters because breakdown can split a story _after_ the gate was written, and
|
|
158
|
+
a gate that quietly stops matching is worse than one that was never there. The
|
|
159
|
+
prefix must end at a story **number** for the split arm to apply, so a word id
|
|
160
|
+
like `auth` does not gate `authz-login`.
|
|
161
|
+
|
|
162
|
+
Like `source_spec:`, a `gate:` line is never edited or dropped when an entry is
|
|
163
|
+
otherwise touched: removing it un-gates the story silently, which is the exact
|
|
164
|
+
failure this field exists to prevent. During a `froid-loop sweep --migrate` that
|
|
165
|
+
is enforced mechanically — the orchestrator refuses a rewrite that drops a token
|
|
166
|
+
a pre-existing entry declared (#519); every other ledger edit is still held by
|
|
167
|
+
this instruction alone.
|
|
168
|
+
|
|
169
|
+
Until the entry lands, the gate is enforced twice. `froid-loop validate` **fails**
|
|
170
|
+
(`deferred.hard-gate`) for every story a token matches that the queue would
|
|
171
|
+
actually dispatch — sprint-status stories at `backlog` / `ready-for-dev`, or
|
|
172
|
+
manifest entries whose spec is not yet written or sits at `draft` /
|
|
173
|
+
`ready-for-dev` / `in-progress` / `in-review`. A `blocked` manifest entry is not
|
|
174
|
+
gated, nor is one the scheduler would stop on anyway (two specs matching one id,
|
|
175
|
+
or a skeletal sentinel from a failed planning halt): the queue cannot reach that
|
|
176
|
+
story, so a gate refusing it would report work held back that was never going to
|
|
177
|
+
run. A `run` that never called `validate` **pauses** (`story-gate`) rather
|
|
178
|
+
than dispatch a gated story. Two things clear it: closing the entry
|
|
179
|
+
(`status: done <date>`), or removing the token because it no longer blocks that
|
|
180
|
+
work. This is the one deferred-work check that gates rather than advises:
|
|
181
|
+
everything else here is traceability that may be wrong, while this is work that
|
|
182
|
+
must not start.
|
|
183
|
+
|
|
184
|
+
**Only an explicit `done` retires a gate.** A status the format cannot read —
|
|
185
|
+
`status: opne`, or an entry with no `status:` line — is not evidence the work
|
|
186
|
+
landed, so the gate still holds. Write the status word exactly.
|
|
187
|
+
|
|
188
|
+
A sweep is never gated by the ledger it is draining, whatever any entry's `gate:`
|
|
189
|
+
says: closing the gating entry is what a sweep is for, so gating it would
|
|
190
|
+
deadlock the gate against its own remedy.
|
|
191
|
+
|
|
192
|
+
**Quoting the field is safe.** A `gate:` line inside a fenced code block is an
|
|
193
|
+
example, not a declaration, so an entry that documents this convention gates
|
|
194
|
+
nothing. This holds for a whole quoted entry too — heading, `status:` and `gate:`
|
|
195
|
+
inside one fence, the shape shown above: the fenced heading starts no entry, and
|
|
196
|
+
a quoted heading or bullet does not end the entry that quotes it, so a real
|
|
197
|
+
`gate:` below the example keeps gating. One exception worth knowing when you
|
|
198
|
+
write an entry: a fence you open and never close is not treated as a fence at
|
|
199
|
+
all, because swallowing the rest of the entry could silently disable a real
|
|
200
|
+
`gate:` line below it — and, at file scope, hide every entry after it. Close your
|
|
201
|
+
fences.
|
|
202
|
+
|
|
203
|
+
Four shapes declare a gate that nothing can enforce, and all four are reported as
|
|
204
|
+
`deferred.hard-gate-unstructured` while the entry is unlanded:
|
|
205
|
+
|
|
206
|
+
- a token nothing can match. It must look like a story key
|
|
207
|
+
(`[A-Za-z0-9][A-Za-z0-9._-]*`, no spaces) **and** be a shape a key can actually
|
|
208
|
+
take — alphanumeric segments joined by `-`, or a full sprint key. So a
|
|
209
|
+
space-separated `gate: 3-2 3-3` is one bad token rather than two good ones, and
|
|
210
|
+
`gate: 3.2` / `gate: 3_2` are rejected: no key spells its numbers that way.
|
|
211
|
+
Inside a sprint slug those characters are fine — `gate: 3-2-a_b` is a real gate;
|
|
212
|
+
- a `gate:` line with nothing usable after the colon (`gate:`, `gate: ,`) — each
|
|
213
|
+
such line is reported, including one sitting beside a line that does name a
|
|
214
|
+
story, since the half that names nothing is the half you are wrong about;
|
|
215
|
+
- a `gate:` that is not lowercase at the very start of its line — `Gate: 3-2`, or
|
|
216
|
+
a line that indents `gate: 3-2`. These are reported rather than read as
|
|
217
|
+
declarations: the field is a fixed spelling, and guessing at near-misses is how
|
|
218
|
+
a line that was never meant to gate ends up refusing a story;
|
|
219
|
+
- prose declaring `HARD GATE:` — the convention that predates this field —
|
|
220
|
+
anywhere on a line of an entry that carries no `gate:` line. It is matched
|
|
221
|
+
mid-line because `reason:` prose is hard-wrapped, but never directly after a
|
|
222
|
+
quote character (`"`, `'`, `` ` ``, `«`, or a curly quote): an entry that merely
|
|
223
|
+
_cites_ the phrase stays silent, as does one that writes it without the colon.
|
|
224
|
+
|
|
225
|
+
Each reads like a gate already in force while holding nothing back. Add or repair
|
|
226
|
+
the `gate:` line to make it enforceable.
|
|
227
|
+
|
|
228
|
+
## Sweep annotations
|
|
229
|
+
|
|
230
|
+
`froid-loop sweep` runs (the orchestrator and its bundle dev sessions) add two
|
|
231
|
+
optional field lines to existing entries — both directly after `status:`:
|
|
232
|
+
|
|
233
|
+
```markdown
|
|
234
|
+
resolution: <one line: what was built or why the entry was closed>
|
|
235
|
+
decision: <date> <chosen option label> — <detail>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- `resolution:` accompanies every sweep close (`status: done <date>`). Bundle
|
|
239
|
+
dev sessions write it when finishing a bundle's entries; the orchestrator
|
|
240
|
+
writes it when closing entries triage proved already resolved.
|
|
241
|
+
- `decision:` records a human's sweep-time choice on an entry. It does not by
|
|
242
|
+
itself change `status:` — a `keep-open` decision leaves the entry open.
|
|
243
|
+
|
|
244
|
+
## Closure declared by a story
|
|
245
|
+
|
|
246
|
+
A sweep bundle is not the only thing that closes an entry. A regular story may
|
|
247
|
+
declare the entries its work closes — on its `stories.yaml` entry (stories mode),
|
|
248
|
+
or in its story spec's frontmatter. The two are unioned:
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
closes_deferred: [DW-5, DW-6] # DW-<n> ids this story closes
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Both are written by a human, and breakdown time — with this file open — is where
|
|
255
|
+
it belongs, though not a deadline: the declaration is read when the story
|
|
256
|
+
commits, so one added to a spec's frontmatter mid-run still counts. No upstream
|
|
257
|
+
skill emits the field yet, and re-deriving `stories.yaml` will drop it unless the
|
|
258
|
+
intent is recorded in `.memlog.md` first.
|
|
259
|
+
|
|
260
|
+
When the story commits, the orchestrator annotates each declared id exactly as a
|
|
261
|
+
bundle close does — `status: done <date>` plus a `resolution:` line naming the
|
|
262
|
+
story:
|
|
263
|
+
|
|
264
|
+
```markdown
|
|
265
|
+
status: done 2026-07-23
|
|
266
|
+
resolution: resolved by story 3-2-export
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
The rules that keep this safe:
|
|
270
|
+
|
|
271
|
+
- **Declared, never inferred.** Closure comes only from this field; the
|
|
272
|
+
orchestrator does not guess it from a diff.
|
|
273
|
+
- **Only once the story actually lands.** The annotation is written at the
|
|
274
|
+
commit boundary — after verification, the review loop and every checkpoint,
|
|
275
|
+
and just before the story's commit is squashed. A story that fails, blocks, is
|
|
276
|
+
rejected by review, or escalates closes nothing; a commit that then fails
|
|
277
|
+
takes the annotation back with it, restored to the pre-close text.
|
|
278
|
+
- **In the story's own commit**, when this file lives inside the repo —
|
|
279
|
+
worktree isolation included: the unit's copy rides the unit commit and
|
|
280
|
+
reaches the target branch with the merge. If the artifacts dir is configured
|
|
281
|
+
outside the repo, the file is shared between worktrees and no commit can
|
|
282
|
+
carry it; the annotation is written all the same and the run journals
|
|
283
|
+
`deferred-close-external-ledger`. A location that cannot be read or written
|
|
284
|
+
when the write comes due closes nothing and is journaled — the entries stay
|
|
285
|
+
`open` for a sweep to re-verify, and an outage is never read as "no such
|
|
286
|
+
entries", never allowed to fail the story or crash the run.
|
|
287
|
+
- **Idempotent.** An id already `done` is left untouched, so a resumed run
|
|
288
|
+
re-driving the same close neither doubles the `resolution:` line nor warns.
|
|
289
|
+
- **Never a gate.** An id that matches no entry, an entry whose `status:` reads
|
|
290
|
+
as neither `open` nor `done`, and a story spec declaring a bare
|
|
291
|
+
`closes_deferred: DW-5` where a list belongs are each journaled and dropped —
|
|
292
|
+
none can fail the story. `froid-loop validate` reports the same mismatches as
|
|
293
|
+
warnings before the run starts. The one exception is that same wrong container
|
|
294
|
+
in `stories.yaml`: the manifest is a schema the parser owns, so it fails to
|
|
295
|
+
load there like any other field of the wrong type — before any story runs, and
|
|
296
|
+
reported by `validate` up front.
|
|
297
|
+
- **Read at the commit.** The declaration that counts is the one on disk when the
|
|
298
|
+
story commits, not the one it was implemented from — edit it late and the edit
|
|
299
|
+
is honored, in both directions.
|
|
300
|
+
|
|
301
|
+
Keep the ids stable when editing this file: a reworded title is fine, but
|
|
302
|
+
renumbering an entry orphans any declaration that already references it.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Migration Mode
|
|
2
|
+
|
|
3
|
+
Projects started before the DW entry format carry a freeform
|
|
4
|
+
`deferred-work.md` — "## Deferred from: ..." sections with bullets,
|
|
5
|
+
strikethrough done-markers, `### D-1.2-003: title — RESOLVED` headings,
|
|
6
|
+
topic sections suffixed "(... — DONE)". The orchestrator cannot sweep those:
|
|
7
|
+
`status:` lines don't exist to flip, and open items are invisible to its
|
|
8
|
+
parser. Your job is a one-time rewrite of every legacy item into a canonical
|
|
9
|
+
`### DW-<n>:` entry, after which the normal triage flow takes over.
|
|
10
|
+
|
|
11
|
+
This is the ONE workflow mode that edits a file: exactly the ledger at
|
|
12
|
+
`{implementation_artifacts}/deferred-work.md`. Never any other file, never
|
|
13
|
+
code, never specs, never sprint-status. Never commit — the orchestrator
|
|
14
|
+
commits the migrated ledger after validating it.
|
|
15
|
+
|
|
16
|
+
## Inputs
|
|
17
|
+
|
|
18
|
+
- `--migrate <manifest-path>`: a JSON array — the orchestrator's parse of the
|
|
19
|
+
legacy items. Each element:
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"key": "<stable identity — echo it back in your mapping>",
|
|
24
|
+
"id": "<native id like W2 / D-CAP-001, or empty>",
|
|
25
|
+
"title": "<cleaned one-line title>",
|
|
26
|
+
"section": "<enclosing heading text>",
|
|
27
|
+
"done": true,
|
|
28
|
+
"severity": "high"
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The manifest is authoritative for WHAT to convert and each item's
|
|
33
|
+
open/done state. Do not re-interpret done-ness from the prose; the
|
|
34
|
+
orchestrator validates your output against the manifest's `done` flags.
|
|
35
|
+
|
|
36
|
+
- `--feedback <path>` (retry only): the deterministic validation errors your
|
|
37
|
+
previous attempt failed on, including any items that still parse as legacy.
|
|
38
|
+
Read it FIRST and fix exactly those defects.
|
|
39
|
+
|
|
40
|
+
## The rewrite
|
|
41
|
+
|
|
42
|
+
1. Read the manifest and the full ledger.
|
|
43
|
+
2. Keep every existing `### DW-<n>:` entry **byte-identical** — the
|
|
44
|
+
orchestrator fails the migration if a pre-existing entry's status changes,
|
|
45
|
+
an entry disappears, or a `gate:` token an entry declared is no longer
|
|
46
|
+
present.
|
|
47
|
+
3. Replace all legacy content with canonical entries per
|
|
48
|
+
`./deferred-work-format.md`. Number new entries continuing
|
|
49
|
+
from the highest existing `DW-<n>` (start at DW-1 when none exist), in
|
|
50
|
+
the items' original file order. Per item:
|
|
51
|
+
- `### DW-<n>: <title>` — the manifest title, refined from the original
|
|
52
|
+
bullet when it improves clarity.
|
|
53
|
+
- `origin: migrated from legacy ledger ("<section>"), <today>` — keep the
|
|
54
|
+
original review/date context from the section text.
|
|
55
|
+
- `location:` — extract a file/component from the original text, else `n/a`.
|
|
56
|
+
- `severity:` — the manifest severity; omit the line when null.
|
|
57
|
+
- `reason:` — the substance of the original bullet, condensed but lossless
|
|
58
|
+
enough that a future dev session can act on it. Long forensic detail may
|
|
59
|
+
follow as body lines under the fields.
|
|
60
|
+
- `status: open`, or for done items `status: done <date>` using the
|
|
61
|
+
original completion date when recoverable (else today), plus a
|
|
62
|
+
`resolution:` line carrying the original resolution text when one
|
|
63
|
+
exists (e.g. the text after `→` or a `**Resolution:**` field).
|
|
64
|
+
4. Two manifest items describing the same underlying issue (e.g. a duplicate
|
|
65
|
+
`W1` re-raised in a later review) may merge into ONE DW entry — map both
|
|
66
|
+
keys to the same `dw_id`. Merge only when their `done` flags match.
|
|
67
|
+
5. The finished file must contain only the `# Deferred Work` title line and
|
|
68
|
+
canonical `### DW-<n>:` entries. Any leftover freeform section, bullet
|
|
69
|
+
list, or strikethrough item fails the orchestrator's zero-legacy check
|
|
70
|
+
and burns a retry.
|
|
71
|
+
|
|
72
|
+
## Result
|
|
73
|
+
|
|
74
|
+
Write the result file per automation-mode.md with the migration schema:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"workflow": "deferred-sweep-migrate",
|
|
79
|
+
"mapping": [{ "key": "<manifest key>", "dw_id": "DW-12" }],
|
|
80
|
+
"escalations": []
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Every manifest key appears exactly once; every `dw_id` must exist in the
|
|
85
|
+
rewritten ledger with the manifest's open/done state. State in one line how
|
|
86
|
+
many items you converted (and how many merged), then end your turn.
|