codex-harness-engineering 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/AGENTS.md +18 -6
  2. package/LICENSE +21 -0
  3. package/README.md +69 -6
  4. package/docs/harness-engineering/implementation-playbook.md +232 -286
  5. package/docs/harness-engineering/index.md +7 -4
  6. package/docs/harness-engineering/research-note.md +294 -274
  7. package/docs/harness-engineering/sources.md +166 -72
  8. package/package.json +5 -4
  9. package/scripts/install-skills.mjs +73 -15
  10. package/scripts/publish.sh +2 -2
  11. package/scripts/verify-harness.mjs +61 -4
  12. package/skills/acceptance-contract/SKILL.md +39 -49
  13. package/skills/acceptance-contract/agents/openai.yaml +2 -2
  14. package/skills/cleanup-harness/SKILL.md +48 -59
  15. package/skills/cleanup-harness/agents/openai.yaml +2 -2
  16. package/skills/creator-harness/SKILL.md +79 -95
  17. package/skills/creator-harness/agents/openai.yaml +2 -2
  18. package/skills/creator-harness/references/harness-artifacts.md +63 -62
  19. package/skills/lessons-harness/SKILL.md +68 -0
  20. package/skills/lessons-harness/agents/openai.yaml +4 -0
  21. package/templates/harness/AGENTS.md +77 -0
  22. package/templates/harness/feature_list.json +16 -0
  23. package/templates/harness/init.sh +15 -0
  24. package/templates/harness/lessons.md +18 -0
  25. package/templates/harness/memory/README.md +22 -0
  26. package/templates/harness/progress.md +33 -0
  27. package/templates/harness/rotate-state.mjs +131 -0
  28. package/templates/harness/verify-state.mjs +117 -0
  29. package/templates/team/roles/evaluator.md +43 -0
  30. package/templates/team/roles/implementer.md +29 -0
  31. package/templates/team/roles/planner.md +28 -0
  32. package/templates/team/sprint-template.md +36 -0
  33. package/templates/team/verify-team.mjs +71 -0
  34. package/templates/team/workflow.md +62 -0
@@ -1,18 +1,18 @@
1
- # Harness Artifact Templates
1
+ # Mẫu Artifact Harness
2
2
 
3
- Use these templates selectively. Do not create every artifact by default.
3
+ Dùng các mẫu này một cách chọn lọc. Đừng tạo mọi artifact theo mặc định.
4
4
 
5
- Each artifact must answer at least one question:
5
+ Mỗi artifact phải trả lời ít nhất một câu hỏi:
6
6
 
7
- - What should the agent know?
8
- - What state survives context loss?
9
- - What can the agent observe?
10
- - How does the agent verify work?
11
- - What constraint is mechanically enforced?
7
+ - Agent cần biết gì?
8
+ - State nào sống sót qua mất context?
9
+ - Agent quan sát được gì?
10
+ - Agent verify công việc thế nào?
11
+ - Ràng buộc nào được cưỡng chế cơ học?
12
12
 
13
- ## Contents
13
+ ## Mục lục
14
14
 
15
- - Minimal Repository Harness
15
+ - Harness repository tối thiểu
16
16
  - AGENTS.md
17
17
  - progress.md
18
18
  - feature_list.json
@@ -21,11 +21,12 @@ Each artifact must answer at least one question:
21
21
  - Acceptance Contract
22
22
  - Sprint Contract
23
23
  - Evaluator Notes
24
+ - Legibility Map
24
25
  - Cleanup Task
25
26
 
26
- ## Minimal Repository Harness
27
+ ## Harness repository tối thiểu
27
28
 
28
- Start here unless a named failure mode requires more.
29
+ Bắt đầu từ đây trừ khi một failure mode tên đòi hỏi nhiều hơn.
29
30
 
30
31
  ```text
31
32
  AGENTS.md
@@ -33,11 +34,11 @@ README.md
33
34
  progress.md
34
35
  feature_list.json
35
36
  init.sh
36
- Makefile or task runner
37
- tests/ or smoke test
37
+ Makefile hoặc task runner
38
+ tests/ hoặc smoke test
38
39
  ```
39
40
 
40
- Optional only when needed:
41
+ Chỉ thêm khi cần:
41
42
 
42
43
  ```text
43
44
  docs/architecture.md
@@ -99,8 +100,8 @@ cleanup.md
99
100
  - ...
100
101
  ```
101
102
 
102
- Keep entries short and recoverable. Prefer file paths, command names, failing
103
- test names, and artifact paths over vague prose.
103
+ Giữ mỗi entry ngắn và khôi phục được. Ưu tiên đường dẫn file, tên lệnh, tên test
104
+ fail, đường dẫn artifact hơn văn xuôi mơ hồ.
104
105
 
105
106
  ## feature_list.json
106
107
 
@@ -124,8 +125,8 @@ test names, and artifact paths over vague prose.
124
125
  ]
125
126
  ```
126
127
 
127
- Use status values consistently: `not_started`, `in_progress`, `blocked`,
128
- `verified`. Only set `verified` after listed checks pass.
128
+ Dùng giá trị `status` nhất quán: `not_started`, `in_progress`, `blocked`,
129
+ `verified`. Chỉ đặt `verified` sau khi các kiểm tra đã liệt kê pass.
129
130
 
130
131
  ## init.sh
131
132
 
@@ -161,22 +162,22 @@ smoke:
161
162
  verify: lint test build smoke
162
163
  ```
163
164
 
164
- Keep command names stable. Agent instructions should point to these targets
165
- instead of repeating long command lines across files.
165
+ Giữ tên lệnh ổn định. Hướng dẫn cho agent nên trỏ tới các target này thay vì lặp
166
+ lại dòng lệnh dài qua nhiều file.
166
167
 
167
168
  ## Acceptance Contract
168
169
 
169
- Use this for a small bug or feature when planner/evaluator would be too much.
170
+ Dùng cho một bug hoặc feature nhỏ khi planner/evaluator quá mức.
170
171
 
171
172
  ```markdown
172
173
  # Acceptance Contract
173
174
 
174
- ## Scope
175
+ ## Phạm vi
175
176
  - Feature/fix:
176
- - User-visible behavior:
177
- - Likely files:
177
+ - Hành vi nhìn thấy phía người dùng:
178
+ - File có khả năng đụng đến:
178
179
 
179
- ## Acceptance Criteria
180
+ ## Tiêu chí nghiệm thu
180
181
  - [ ] ...
181
182
  - [ ] ...
182
183
 
@@ -186,28 +187,28 @@ Use this for a small bug or feature when planner/evaluator would be too much.
186
187
  - Browser/API:
187
188
  - Log/metric/trace:
188
189
 
189
- ## Out of Scope
190
+ ## Ngoài phạm vi
190
191
  - ...
191
192
  ```
192
193
 
193
194
  ## Sprint Contract
194
195
 
195
- Use this when work spans multiple files, runtime behavior, or subjective quality.
196
+ Dùng khi công việc trải qua nhiều file, hành vi runtime, hoặc chất lượng chủ quan.
196
197
 
197
198
  ```markdown
198
199
  # Sprint Contract
199
200
 
200
- ## Scope
201
+ ## Phạm vi
201
202
  - Feature:
202
203
  - User path:
203
204
  - API/data path:
204
- - Likely files/modules:
205
+ - File/module có khả năng đụng đến:
205
206
 
206
- ## Done Means
207
+ ## Done nghĩa là
207
208
  - [ ] User can ...
208
- - [ ] API or data reflects ...
209
- - [ ] Error state handles ...
210
- - [ ] No regression in ...
209
+ - [ ] API hoặc data phản ánh ...
210
+ - [ ] Trạng thái lỗi xử lý ...
211
+ - [ ] Không regression ...
211
212
 
212
213
  ## Verification
213
214
  - Unit:
@@ -215,56 +216,56 @@ Use this when work spans multiple files, runtime behavior, or subjective quality
215
216
  - Browser/API:
216
217
  - Log/metric/trace:
217
218
 
218
- ## Evaluator Focus
219
- - Runtime behavior:
220
- - Negative cases:
221
- - UX or quality concerns:
219
+ ## Trọng tâm Evaluator
220
+ - Hành vi runtime:
221
+ - Ca âm (negative cases):
222
+ - Lo ngại về UX hoặc chất lượng:
222
223
 
223
- ## Out of Scope
224
+ ## Ngoài phạm vi
224
225
  - ...
225
226
  ```
226
227
 
227
- If the sprint contract becomes longer than the work, split the work or fall back
228
- to a smaller acceptance contract.
228
+ Nếu sprint contract dài hơn cả phần việc, hãy chia nhỏ công việc hoặc lùi về một
229
+ acceptance contract nhỏ hơn.
229
230
 
230
231
  ## Evaluator Notes
231
232
 
232
- Use this when generator self-review is not enough.
233
+ Dùng khi generator tự review chưa đủ.
233
234
 
234
235
  ```markdown
235
236
  # Evaluator Notes
236
237
 
237
238
  ## Contract
238
239
  - Sprint:
239
- - Expected behavior:
240
+ - Hành vi kỳ vọng:
240
241
 
241
- ## Checks Run
242
- - Command/check:
243
- - Result:
242
+ ## Kiểm tra đã chạy
243
+ - Lệnh/kiểm tra:
244
+ - Kết quả:
244
245
  - Artifact:
245
246
 
246
- ## Findings
247
+ ## Phát hiện
247
248
  - [ ] P0/P1/P2:
248
- - Evidence:
249
+ - Bằng chứng:
249
250
  - Repro:
250
- - Suggested next step:
251
+ - Bước tiếp theo đề xuất:
251
252
 
252
- ## Verdict
253
+ ## Phán quyết
253
254
  - pass/fail:
254
- - Reason:
255
+ - Lý do:
255
256
  ```
256
257
 
257
- Evaluator feedback should cite observed evidence: screenshots, DOM state, API
258
- response, database state, logs, traces, or command output.
258
+ Feedback của evaluator nên dẫn bằng chứng quan sát được: screenshot, DOM state,
259
+ API response, database state, log, trace, hoặc output của lệnh.
259
260
 
260
261
  ## Legibility Map
261
262
 
262
- Use this when the agent cannot see enough runtime behavior.
263
+ Dùng khi agent không nhìn thấy đủ hành vi runtime.
263
264
 
264
265
  ```markdown
265
266
  # Legibility Map
266
267
 
267
- | Area | Signal | How to collect | Owner/check |
268
+ | Khu vực | Tín hiệu | Cách thu thập | Owner/kiểm tra |
268
269
  | --- | --- | --- | --- |
269
270
  | UI | Screenshot/DOM | | |
270
271
  | API | Request/response | | |
@@ -276,20 +277,20 @@ Use this when the agent cannot see enough runtime behavior.
276
277
 
277
278
  ## Cleanup Task
278
279
 
279
- Use this when agent throughput creates repeated drift.
280
+ Dùng khi throughput của agent tạo ra drift lặp lại.
280
281
 
281
282
  ```markdown
282
283
  # Cleanup Task
283
284
 
284
285
  ## Trigger
285
- - Repeated pattern:
286
- - Evidence:
286
+ - Pattern lặp lại:
287
+ - Bằng chứng:
287
288
 
288
- ## Scope
289
- - Include:
290
- - Exclude:
289
+ ## Phạm vi
290
+ - Bao gồm:
291
+ - Loại trừ:
291
292
 
292
- ## Acceptance Criteria
293
+ ## Tiêu chí nghiệm thu
293
294
  - [ ] ...
294
295
 
295
296
  ## Verification
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: lessons-harness
3
+ description: Dùng khi một gate fail, regression, defect review, hoặc lỗi sửa đi sửa lại cần trở thành bài học bền vững — ghi lại lỗi, rút ra quy tắc, và đẩy các quy tắc lặp lại thành guardrail cơ học.
4
+ ---
5
+
6
+ # Lessons Harness
7
+
8
+ ## Điều kiện kích hoạt
9
+
10
+ Ghi một bài học vào `lessons.md` khi:
11
+
12
+ - một verify gate, test, hoặc smoke check fail vì lý do do agent gây ra;
13
+ - một regression xuất hiện ở hành vi trước đây vốn chạy đúng;
14
+ - review hoặc evaluator bắt được một lớp defect do agent tạo ra;
15
+ - cùng một cách sửa hoặc workaround được áp dụng lần thứ hai;
16
+ - một giả định sai về repo, một tool, hoặc một nguồn còn sống sót qua session
17
+ đã tạo ra nó.
18
+
19
+ Không ghi các fail red-green thường thấy trong TDD bình thường, hoặc flake môi
20
+ trường nằm ngoài tầm kiểm soát của agent.
21
+
22
+ ## Quy trình
23
+
24
+ 1. Viết bài học khi bối cảnh lỗi còn tươi. Mỗi mục một bài học: mistake, root
25
+ cause, rule, status.
26
+ 2. Giữ rule mang tính vận hành: một hành vi hoặc kiểm tra cụ thể cho lần sau,
27
+ không phải ý định mơ hồ.
28
+ 3. Tìm trong `lessons.md` và `memory/lessons/` cùng một root cause trước khi
29
+ thêm bản gần-trùng; nếu đã có, rule là lặp lại.
30
+ 4. Đẩy một rule lặp lại vào dạng cơ học nhỏ nhất giữ được nó [S1], [S3]:
31
+ - quy tắc hành vi bền vững → một dòng rule trong `AGENTS.md`;
32
+ - invariant về cấu trúc hoặc style → một lint hoặc structural test;
33
+ - invariant về state hoặc quy trình → mở rộng verify gate;
34
+ - policy phức tạp → một code wrapper tổng hợp chặn hành động không hợp lệ
35
+ trước khi thực thi [S5].
36
+ 5. Đổi dòng status của bài học thành `Status: promoted: <ở đâu>` rồi chạy
37
+ `node rotate-state.mjs` để các bài học đã promote chuyển sang
38
+ `memory/lessons/` và file nóng giữ nhỏ.
39
+ 6. Ghi lần promote vào `progress.md` như mọi thay đổi hành vi khác.
40
+
41
+ ## Định dạng một bài học
42
+
43
+ Mỗi mục trong `lessons.md` theo dạng sau (heading có ngày, bốn trường):
44
+
45
+ - Mistake: điều gì sai và quan sát ở đâu.
46
+ - Root cause: vì sao xảy ra, không chỉ là cái gì hỏng.
47
+ - Rule: hành vi hoặc kiểm tra ngăn nó lặp lại lần sau.
48
+ - Status: `pending`, hoặc `promoted: <vị trí rule hoặc gate>`.
49
+
50
+ ## Hướng dẫn promote
51
+
52
+ - Ưu tiên can thiệp nhỏ nhất loại bỏ được failure mode [S3].
53
+ - Một rule đã promote phải bảo vệ một invariant cụ thể; đừng thêm quy tắc rộng
54
+ mà không ràng buộc gì.
55
+ - Khi verify gate có thêm một kiểm tra, thêm một regression test cho chính kiểm
56
+ tra đó.
57
+ - Bài học pending là state nóng agent đọc mỗi session; bài học đã promote là
58
+ lịch sử nguội. Giữ file nóng trong ngân sách dòng của nó.
59
+
60
+ ## Ánh xạ nguồn
61
+
62
+ - Tri thức cục bộ trong repo sống lâu hơn session; lịch sử chat thì không [S1].
63
+ - Agent chạy dài cần state được externalize để khôi phục và cải thiện qua các
64
+ session [S2].
65
+ - Giữ mỗi can thiệp đơn giản đúng mức mà failure mode cho phép [S3].
66
+ - Chuyển phán đoán lặp lại thành kiểm tra cơ học mạnh hơn văn xuôi [S1].
67
+ - AutoHarness cho thấy policy có thể được tổng hợp thành code wrapper tĩnh lọc
68
+ hành động không hợp lệ trước khi thực thi [S5].
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "Lessons Harness"
3
+ short_description: "Biến lỗi thành quy tắc và gate"
4
+ default_prompt: "Dùng $lessons-harness để ghi một lỗi thành bài học kèm root cause và rule, rồi đẩy các rule lặp lại thành guardrail cơ học."
@@ -0,0 +1,77 @@
1
+ # Agent Instructions
2
+
3
+ Codex reads this file at the start of every session. Keep it a short map; the
4
+ deeper source of truth lives in the files it points to, not in chat history.
5
+
6
+ ## Start Here
7
+
8
+ 1. Read `README.md`.
9
+ 2. Read the latest entries in `progress.md` to recover what the previous
10
+ session did.
11
+ 3. Check `feature_list.json` for the current capability state.
12
+ 4. Read `lessons.md` for pending mistakes and rules from prior sessions.
13
+ 5. Run `./init.sh` to set up and smoke-test before editing anything.
14
+ 6. Review recent git history to see what the last session changed.
15
+
16
+ ## Skills
17
+
18
+ Installed under `.agents/skills/`. Invoke with `$skill-name`:
19
+
20
+ - `$acceptance-contract` — before implementing: lock scope, done criteria, and
21
+ verification commands.
22
+ - `$creator-harness` — when this harness is missing a layer for a named
23
+ failure mode (lost context, invisible runtime, optimistic self-review).
24
+ - `$cleanup-harness` — when repeated drift, duplicate helpers, or doc rot
25
+ appears across sessions.
26
+ - `$lessons-harness` — when a verify gate, test, or review catches a mistake;
27
+ log it in `lessons.md` and promote recurring rules into guardrails.
28
+
29
+ ## Memory Layout
30
+
31
+ Hot files are read every session; cold files are searched only when history is
32
+ needed. Do not let hot files grow unbounded.
33
+
34
+ - `progress.md` — hot: recent session log (latest entries only).
35
+ - `feature_list.json` — hot: active capabilities only.
36
+ - `lessons.md` — hot: pending mistakes and the rules derived from them.
37
+ - `memory/README.md` — cold: archive contract for rotated state.
38
+ - `memory/progress/<YYYY-MM>.md` — cold: archived progress entries by month.
39
+ - `memory/features-archive.json` — cold: verified capabilities with evidence.
40
+ - `memory/lessons/<YYYY-MM>.md` — cold: promoted lessons by month.
41
+ - Rotate with `node rotate-state.mjs`; the state gate fails when
42
+ `progress.md` or `lessons.md` exceeds its line budget.
43
+
44
+ ## Commands
45
+
46
+ <!-- Fill these in for your project. Codex reads this table before guessing. -->
47
+
48
+ - Setup:
49
+ - Test:
50
+ - Lint:
51
+ - Build:
52
+ - Smoke: `./init.sh`
53
+ - State gate: `node verify-state.mjs`
54
+ - Memory rotate: `node rotate-state.mjs`
55
+
56
+ ## Rules
57
+
58
+ - Work on one feature from `feature_list.json` at a time; do not widen scope
59
+ mid-session.
60
+ - If `team/` exists, multi-role work follows `team/workflow.md`: one session
61
+ plays one role (planner, implementer, or evaluator) and `node
62
+ team/verify-team.mjs` must pass alongside the state gate.
63
+ - Keep changes scoped to the requested feature or fix.
64
+ - Update a feature status in `feature_list.json` only after its `verify`
65
+ commands pass.
66
+ - When a task changes behavior, guardrails, packages, scripts, or tests, update
67
+ `feature_list.json` and `progress.md` before finishing — even for small
68
+ tasks. The latest progress entry must list the changed files.
69
+ - Run `node verify-state.mjs` before committing; it mechanically enforces the
70
+ rule above and must pass.
71
+ - When a verify gate, test, or review catches a mistake the agent caused, log
72
+ it in `lessons.md` via `$lessons-harness` before finishing the session. If
73
+ the same rule appears twice, promote it into this file or a verify gate.
74
+ - End every session with a descriptive commit and a `progress.md` entry. The
75
+ commit is the recovery point the next Codex session reverts to if a change
76
+ goes bad.
77
+ - Do not refactor unrelated code.
@@ -0,0 +1,16 @@
1
+ [
2
+ {
3
+ "id": "F001",
4
+ "title": "Replace me with the first real capability",
5
+ "status": "not_started",
6
+ "acceptance": [
7
+ "User can ...",
8
+ "System rejects ...",
9
+ "Regression check passes ..."
10
+ ],
11
+ "verify": [
12
+ "./init.sh"
13
+ ],
14
+ "evidence": []
15
+ }
16
+ ]
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ cd "$(dirname "$0")"
5
+
6
+ # Keep this script idempotent: a brand-new session runs it first, before any
7
+ # edit. It should set up the environment and run the cheapest smoke test.
8
+ #
9
+ # Replace the lines below with your project's real commands, for example:
10
+ # npm ci && npm test
11
+ # make setup && make smoke
12
+
13
+ echo "init.sh is not configured yet." >&2
14
+ echo "Edit init.sh to run this project's setup and cheapest smoke test." >&2
15
+ exit 1
@@ -0,0 +1,18 @@
1
+ # Lessons
2
+
3
+ Mistakes this project has learned from, externalized so the next session does
4
+ not repeat them. Log a lesson when a gate or test fails for an agent-caused
5
+ reason, a regression appears, review catches an agent-made defect, or the same
6
+ correction is applied twice. See `$lessons-harness` for the full workflow.
7
+
8
+ Each lesson is one dated `## YYYY-MM-DD - Title` entry with four fields:
9
+ `- Mistake:` what went wrong and where it was observed; `- Root cause:` why it
10
+ happened; `- Rule:` the concrete behavior or check that prevents it next time;
11
+ `- Status: pending` until the rule is promoted.
12
+
13
+ When the same rule appears in two or more lessons, promote it into `AGENTS.md`
14
+ or a mechanical gate, change the line to `- Status: promoted: <where>`, and run
15
+ `node rotate-state.mjs`. Promoted lessons are archived; pending lessons stay
16
+ hot and are read at the start of every session.
17
+
18
+ Promoted lessons: see `memory/lessons/`.
@@ -0,0 +1,22 @@
1
+ # Memory Archive
2
+
3
+ Cold storage for archived harness state.
4
+
5
+ Hot files live at the repository root and are read every session:
6
+
7
+ - `progress.md`
8
+ - `feature_list.json`
9
+ - `lessons.md`
10
+
11
+ Rotate hot state with `node rotate-state.mjs` when the hot files approach their
12
+ budget or when verified features / promoted lessons should move out of the hot
13
+ set.
14
+
15
+ Cold archive layout:
16
+
17
+ - `memory/progress/<YYYY-MM>.md`
18
+ - `memory/features-archive.json`
19
+ - `memory/lessons/<YYYY-MM>.md`
20
+
21
+ Do not put live task state here. This file is the contract for the cold archive
22
+ layout and recovery path.
@@ -0,0 +1,33 @@
1
+ # Progress
2
+
3
+ Keep entries short and recoverable. Prefer file paths, command names, failing
4
+ test names, and artifact paths over vague prose. Newest entry goes last.
5
+
6
+ ## YYYY-MM-DD - Harness bootstrap
7
+
8
+ ### Context
9
+
10
+ - Task: Bootstrap the repository harness.
11
+ - Current branch:
12
+ - Relevant files: `AGENTS.md`, `progress.md`, `feature_list.json`,
13
+ `memory/README.md`, `init.sh`, `verify-state.mjs`, `rotate-state.mjs`.
14
+
15
+ ### Done
16
+
17
+ - Installed the minimal harness scaffold.
18
+
19
+ ### Verification
20
+
21
+ - Command: `./init.sh`
22
+ - Result: <!-- record the real result; do not mark pass without running it -->
23
+ - Command: `node verify-state.mjs`
24
+ - Result: <!-- record the real result -->
25
+
26
+ ### Open Issues
27
+
28
+ - `init.sh` still needs the project's real setup and smoke commands.
29
+
30
+ ### Next
31
+
32
+ - Fill in the Commands section of `AGENTS.md`.
33
+ - Replace the placeholder feature in `feature_list.json` with real capabilities.
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ // Rotates harness memory so the hot state files stay small enough for an
3
+ // agent to read at the start of every session.
4
+ //
5
+ // - progress.md keeps the latest KEEP_ENTRIES entries; older entries move to
6
+ // memory/progress/<YYYY-MM>.md grouped by entry month.
7
+ // - feature_list.json keeps non-verified features; verified features move to
8
+ // memory/features-archive.json with their evidence.
9
+ // - lessons.md keeps pending lessons; promoted lessons move to
10
+ // memory/lessons/<YYYY-MM>.md grouped by entry month.
11
+ //
12
+ // Run: node rotate-state.mjs [root]
13
+ // The state gate (verify-state.mjs) reminds you when progress.md grows past
14
+ // its line budget.
15
+
16
+ import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
17
+ import path from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+
20
+ const ROOT = process.argv[2]
21
+ ? path.resolve(process.argv[2])
22
+ : path.dirname(fileURLToPath(import.meta.url));
23
+ const KEEP_ENTRIES = 5;
24
+
25
+ function splitEntries(progress) {
26
+ const lines = progress.split("\n");
27
+ const starts = [];
28
+ lines.forEach((line, index) => {
29
+ if (line.startsWith("## ")) {
30
+ starts.push(index);
31
+ }
32
+ });
33
+
34
+ const headerEnd = starts.length > 0 ? starts[0] : lines.length;
35
+ const header = lines.slice(0, headerEnd).join("\n").trimEnd();
36
+ const entries = starts.map((start, n) =>
37
+ lines.slice(start, starts[n + 1] ?? lines.length).join("\n").trimEnd()
38
+ );
39
+
40
+ return { header, entries };
41
+ }
42
+
43
+ function entryMonth(entry) {
44
+ const match = entry.match(/^## (\d{4}-\d{2})/);
45
+ return match ? match[1] : "undated";
46
+ }
47
+
48
+ async function archiveByMonth(directory, entries) {
49
+ await mkdir(directory, { recursive: true });
50
+
51
+ const byMonth = new Map();
52
+ for (const entry of entries) {
53
+ const month = entryMonth(entry);
54
+ byMonth.set(month, [...(byMonth.get(month) ?? []), entry]);
55
+ }
56
+ for (const [month, monthEntries] of byMonth) {
57
+ await appendFile(
58
+ path.join(directory, `${month}.md`),
59
+ `${monthEntries.join("\n\n")}\n\n`,
60
+ "utf8"
61
+ );
62
+ }
63
+ }
64
+
65
+ async function writeHotFile(filePath, header, pointer, entries) {
66
+ const headerWithPointer = header.includes(pointer) ? header : `${header}\n\n${pointer}`;
67
+ await writeFile(filePath, `${[headerWithPointer, ...entries].join("\n\n")}\n`, "utf8");
68
+ }
69
+
70
+ const progressPath = path.join(ROOT, "progress.md");
71
+ const { header, entries } = splitEntries(await readFile(progressPath, "utf8"));
72
+ const archivedEntries = entries.slice(0, Math.max(0, entries.length - KEEP_ENTRIES));
73
+ const keptEntries = entries.slice(-KEEP_ENTRIES);
74
+
75
+ if (archivedEntries.length > 0) {
76
+ await archiveByMonth(path.join(ROOT, "memory", "progress"), archivedEntries);
77
+ await writeHotFile(
78
+ progressPath,
79
+ header,
80
+ "Older entries: see `memory/progress/`.",
81
+ keptEntries
82
+ );
83
+ }
84
+
85
+ let archivedLessons = 0;
86
+ const lessonsPath = path.join(ROOT, "lessons.md");
87
+ try {
88
+ const { header: lessonsHeader, entries: lessonsEntries } = splitEntries(await readFile(lessonsPath, "utf8"));
89
+ const promoted = lessonsEntries.filter((entry) => /\n- Status: promoted:/.test(entry));
90
+ const pending = lessonsEntries.filter((entry) => !/\n- Status: promoted:/.test(entry));
91
+
92
+ if (promoted.length > 0) {
93
+ await archiveByMonth(path.join(ROOT, "memory", "lessons"), promoted);
94
+ await writeHotFile(
95
+ lessonsPath,
96
+ lessonsHeader,
97
+ "Promoted lessons: see `memory/lessons/`.",
98
+ pending
99
+ );
100
+ archivedLessons = promoted.length;
101
+ }
102
+ } catch (error) {
103
+ if (error.code !== "ENOENT") {
104
+ throw error;
105
+ }
106
+ }
107
+
108
+ const featuresPath = path.join(ROOT, "feature_list.json");
109
+ const features = JSON.parse(await readFile(featuresPath, "utf8"));
110
+ const verified = features.filter((feature) => feature.status === "verified");
111
+ const active = features.filter((feature) => feature.status !== "verified");
112
+
113
+ if (verified.length > 0) {
114
+ const archivePath = path.join(ROOT, "memory", "features-archive.json");
115
+ await mkdir(path.join(ROOT, "memory"), { recursive: true });
116
+
117
+ let archive = [];
118
+ try {
119
+ archive = JSON.parse(await readFile(archivePath, "utf8"));
120
+ } catch {
121
+ // first rotation: archive file does not exist yet
122
+ }
123
+
124
+ await writeFile(archivePath, `${JSON.stringify([...archive, ...verified], null, 2)}\n`, "utf8");
125
+ await writeFile(featuresPath, `${JSON.stringify(active, null, 2)}\n`, "utf8");
126
+ }
127
+
128
+ console.log(
129
+ `Archived ${archivedEntries.length} progress entries, ${verified.length} verified features, ` +
130
+ `and ${archivedLessons} promoted lessons to memory/.`
131
+ );