Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f43f7eb
test(now): RED — the body double proposes one tiny, passive first mov…
NetDevAutomate Sep 21, 2026
4d8d9fd
feat(now): the body double proposes one tiny, passive first move (#30)
NetDevAutomate Sep 21, 2026
be3f30f
docs(rubric): row 3c — the body double's first move, emitted from 4d8…
NetDevAutomate Sep 21, 2026
0df5136
docs(rubric): row 3c (a) yes — the kind of move; #33 filed for read-a…
NetDevAutomate Sep 21, 2026
2e1c7ea
test(now): RED — the first move always names a deliberate lesson when…
NetDevAutomate Sep 21, 2026
fdfe167
test(now): RED — the first move names a lesson only from the mileston…
NetDevAutomate Sep 21, 2026
62f3d6f
feat(now): the first move names a lesson from the milestone's own con…
NetDevAutomate Sep 21, 2026
b347fc6
docs(rubric): row 3c (b) yes-with-requirement and (c) agreed — concep…
NetDevAutomate Sep 21, 2026
630cac7
test(now): RED — the first move appears once, beside the door, not al…
NetDevAutomate Sep 21, 2026
75bc9b7
feat(now): the first move appears once, beside the door — dropped fro…
NetDevAutomate Sep 21, 2026
22b6419
docs(rubric): row 3c (d1) — keep the First move line, drop it from th…
NetDevAutomate Sep 21, 2026
95e7e07
docs(rubric): row 3c — owner refines the (c) self-check: a named less…
NetDevAutomate Sep 21, 2026
2b85a80
test(now): RED — a named lesson states its evidence: the course it be…
NetDevAutomate Sep 21, 2026
089d09a
test(now): RED amendment — the seam also returns the concept that mat…
NetDevAutomate Sep 21, 2026
4459ea3
feat(now): a named lesson states its evidence — the course it belongs…
NetDevAutomate Sep 21, 2026
e157627
test(web): RED — 'Open X' actually opens X: the resolved lesson opens…
NetDevAutomate Sep 21, 2026
266afd4
feat(web): 'Open X' actually opens X — the resolved lesson opens in t…
NetDevAutomate Sep 21, 2026
084fad3
docs(rubric): row 3c (d2) — build the button, gated behind the eviden…
NetDevAutomate Sep 21, 2026
74779bd
test(web): RED — the move survives the start: the live strip carries …
NetDevAutomate Sep 21, 2026
5666f14
feat(web): the move survives the start — the live strip carries the f…
NetDevAutomate Sep 21, 2026
ef3ad8f
docs(rubric): row 3c (d3) — carry the move and the button into the li…
NetDevAutomate Sep 21, 2026
accb672
docs(rubric): row 3c (e) — owner: no, offer the move at medium energy…
NetDevAutomate Sep 21, 2026
fa5d76b
test(now): RED — a plan-related active primary carries a warm-up firs…
NetDevAutomate Sep 21, 2026
dacbea8
feat(now): a plan-related active primary carries a warm-up first move…
NetDevAutomate Sep 21, 2026
b64afd0
docs(rubric): row 3c (e1) — the warm-up into the primary, on its own …
NetDevAutomate Sep 21, 2026
eb3be21
test(web): RED — the warm-up follows Start into the Study view: picke…
NetDevAutomate Sep 21, 2026
25886ff
feat(web): the warm-up follows Start into the Study view — picker and…
NetDevAutomate Sep 21, 2026
c83ebd7
docs(rubric): row 3c (e2) — the warm-up follows Start into the Study …
NetDevAutomate Sep 21, 2026
a33249d
test(now): RED — council review 8: an unsearchable concept is not a s…
NetDevAutomate Sep 21, 2026
d029b0d
fix(now): council review 8 — a too-short concept is unsearchable, not…
NetDevAutomate Sep 21, 2026
c5065cf
fix(web): council review 8 — the move never outlives the material it …
NetDevAutomate Sep 22, 2026
357ee25
docs(design): council review 8 F4 — the record no longer claims the m…
NetDevAutomate Sep 22, 2026
79761a9
test(now): RED — council review 8: a plan-related primary with no con…
NetDevAutomate Sep 22, 2026
193b541
fix(now): council review 8 — a plan-related primary with no concept c…
NetDevAutomate Sep 22, 2026
024373b
test(now): RED — a learning row's warm-up ends at the review, not a r…
NetDevAutomate Sep 22, 2026
f8783a7
fix(now): a learning row's warm-up ends at the review, not a repair
NetDevAutomate Sep 22, 2026
3e00f2e
docs(council): review 8 — brief as sent, three seat transcripts, mani…
NetDevAutomate Sep 22, 2026
865e686
docs(changelog): [Unreleased] — the first move (#30), one entry for t…
NetDevAutomate Sep 22, 2026
a620a85
docs(openspec): archive body-double-first-move; its deltas join the m…
NetDevAutomate Sep 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,28 @@ experience may change before `1.0.0`.

## [Unreleased]

_Nothing yet._
### Added

- The `now` engine's recommendation can carry one passive **first move**
(issue #30). On a low-energy day, the sit-with proposal names one tiny
action on the deferred milestone — *Open “Advanced Sql 4H” from Complete Sql
Databases Bootcamp — the match is the phrase “window function” — and read
for ten minutes, nothing more* — naming a lesson only when the Course
Explorer index matches the milestone's own concepts, and stating the course
and the matched concept so a lexical match can be judged from the sentence;
when nothing matches it names the milestone and says why (*no indexed lesson
mentions “window frame” yet*). A plan-related active primary carries the same
move as a warm-up into itself (*… then start the repair* / *the review* /
*the milestone*), never on recall. CLI `now` prints `First move:` beneath the
door; the Today card shows the line with an **Open the lesson** control that
opens the lesson in the Course Explorer beside the current view; Body Double
and Study Session carry the move and the control from the picker into the
live session, and the Today card's **Start →** now hands the Study picker the
action's topic, energy and move (it used to hand it nothing). The move
belongs to the material it arrived beside: editing or re-picking the topic,
a hand-off during a live session, or rejoining another session never shows
it under different material. Proposal only — the companion says nothing
about it. The no-plan payload is byte-identical.

## [0.5.0] - 2026-09-21

Expand Down
1,505 changes: 1,505 additions & 0 deletions docs/architecture/plan-integration/council/brief-review8-2026-09-21.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

53 changes: 53 additions & 0 deletions docs/architecture/plan-integration/council/review8/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"run_at": "2026-09-21T19:31:19+00:00",
"brief": "docs/architecture/plan-integration/council/brief-review8-2026-09-21.md",
"brief_sha256": "8d70d90c2be1de956b46a8514804bc64082b2bf41aed4b2549dd7a07340f9de5",
"system_sha256": "06bd2b0d9d0edfacf9463f79cb598ce63560da80f22a3b8b29b64a87b7567f12",
"reviewed_tree": "c83ebd75",
"reviewed_range": "fa1b2af3..c83ebd75",
"seats": [
{
"model": "openai.gpt-6-astra",
"ok": true,
"max_tokens": 16000,
"finish_reason": "stop",
"elapsed_s": 59.4,
"usage": {
"prompt_tokens": 23930,
"completion_tokens": 3337,
"total_tokens": 27267,
"reasoning_tokens": 849
},
"error": null
},
{
"model": "grok-4.6",
"ok": true,
"max_tokens": 24000,
"finish_reason": "length",
"elapsed_s": 215.9,
"usage": {
"prompt_tokens": 25264,
"completion_tokens": 24000,
"total_tokens": 49264,
"reasoning_tokens": 123
},
"error": null
},
{
"model": "qwen3-coder",
"ok": true,
"max_tokens": 12000,
"finish_reason": "stop",
"elapsed_s": 12.7,
"usage": {
"prompt_tokens": 24306,
"completion_tokens": 1107,
"total_tokens": 25413,
"reasoning_tokens": 0
},
"error": null
}
],
"provenance": "Seat files are copies of <alias>.review.md from the gitignored run directory reviews/2026-09-21-first-move/council/ on the main checkout, copied into the repo on 2026-09-22 after the coordinating session was interrupted before it could record them. The repo's pre-commit whitespace hooks trimmed trailing whitespace and normalised the final newline on the copies; `git diff -w` between the copies and the originals is empty, so the content is unchanged. The originals' sha256: astra 9fed14fc33fe12aa22f8be9db52593a994f5e23535a76ced6326b4f361d1ca92, grok df4aec1c40f419bb41c604429bdd8ce28f60de97a9ff2b39dfcc56e34a7d3ace, qwen 3194c93241a3a18c996b562f95b58a9939da728c307362aaacd4f92084842051; brief_sha256 above is the brief as sent, before the same normalisation. Usage and finish_reason are the run's own spend.json and run.log."
}
1,286 changes: 1,286 additions & 0 deletions docs/architecture/plan-integration/council/review8/seat-grok-4.6.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<!-- verified_model: openai.gpt-6-astra -->
<!-- finish_reason: stop · 59.4s · usage: {"completion_tokens": 3337, "prompt_tokens": 23930, "total_tokens": 27267, "completion_tokens_details": {"reasoning_tokens": 849}, "prompt_tokens_details": {"cached_tokens": 0, "cache_write_tokens": 23928, "cache_creation_tokens": 23928}} -->

## 1. Verdict

**ACCEPT-WITH-CORRECTIONS** — the recommendation carriage is coherent, but the move can become detached from its material through picker edits or a second hand-off, and the resolver can report an indexed miss without searching.

## 2. Findings

Repository execution and test bodies are **UNVERIFIED** here; findings below use the supplied diffs. RED names are proposed tests, not claims about existing coverage.

### F1 — 🔴 Editing the material leaves the previous material’s authoritative move and button

**Location:** `index.html`, `#bd-activity-input`; `session-timer.js`, first-move state and topic-input handling.

The Body Double input binds directly to `activity`, while its first-move fields are cleared only on another hand-off or successful end. Receiving a Frames proposal, replacing the activity with an unrelated task, and starting therefore retains the Frames sentence and lesson button.

The Study picker has the same lifetime problem described in the brief: changing the topic does not invalidate the handed-over warm-up. Its input handler’s full implementation is **UNVERIFIED**, but no invalidation appears in the supplied changes.

This is not merely a learner choosing to ignore a proposal: the UI displays a material-specific proposal beneath a *different* activity. That recreates the confidently wrong linkage the evidence requirement was intended to prevent.

**Fix:** Invalidate all three first-move fields when the learner changes the hand-off’s material. Cover Study topic suggestions and target/course/lesson changes too—not just keyboard input. Prefer an explicit material-identity association or centralized invalidation rather than scattered field resets.

**Concrete check / REDs:**
- `test_body_double_editing_activity_invalidates_first_move`: receive activity A and lesson A, edit the actual input to B, start; neither picker nor live strip retains move A.
- `test_study_changing_target_invalidates_first_move`: repeat for typed topic, suggested topic, and another target kind.

The Body Double test needs behavioral DOM/component coverage; another substring assertion would not pin the transition.

### F2 — 🔴 A new Body Double hand-off can replace the running session’s move

**Location:** `components.js`, `bodyDoubleSession().init()`, `body-double-request` listener, approximately lines 3232–3250.

The listener unconditionally replaces `firstMove` and its lesson fields. The live strip reads those same fields, while its activity is represented separately by `liveActivity`.

Consequently, while session A is active, a request for B can install B’s move under A’s live activity. A request without a move instead erases A’s move before A ends. The listener contains no `sessionActive` or `starting` guard.

Whether every ordinary navigation path permits that request is **UNVERIFIED**; the component’s incorrect transition is directly testable without assuming navigation behavior.

**Fix:** Do not mutate active/starting-session material from an incoming picker request. Either reject/defer the request or keep pending-picker and live-session hand-offs separate. Promote the move to live state only with the corresponding successfully started session.

**Concrete check / REDs:**
- `test_body_double_request_does_not_replace_active_session_first_move`: set active session A with `liveActivity`, move and lesson A; dispatch request B; assert A’s live sentence and opener still refer to A.
- `test_body_double_request_during_start_does_not_mix_materials`: dispatch B while A’s start promise is pending, then resolve A.

Study’s existing listener guards and conflict-recovery implementations are **UNVERIFIED**. Apply the same check there rather than assuming its end/reset tests establish session ownership.

### F3 — 🟡 A one-character concept produces a false “searched miss”

**Location:** `decision.py::_resolve_lesson`, specifically:

```python
if len(q) < 2:
continue
```

`_clean_concepts` retains nonempty one-character concepts. `_resolve_lesson(("C",))` nevertheless performs no search and returns `None`; `_first_move_sentence` then says:

> no indexed lesson mentions “C” yet.

That contradicts both “one query per concept” and the claim that `None` means a *searched* miss. Programming concepts such as C and R are not inherently invalid material.

**Fix:** Search valid single-character concepts if the explorer supports them. If it cannot, distinguish “query unsupported/not consulted” from “searched and absent,” and amend the spec’s fallback wording accordingly. Do not silently turn inability to search into a content-gap assertion.

**Concrete check / REDs:**
- `test_resolve_lesson_searches_single_character_concept`: stub the explorer search to return an evidenced C lesson; assert it was called with `"C"` and the lesson resolves.
- `test_unsearched_concept_does_not_claim_indexed_absence`: exercise any deliberately unsupported-query fallback.

### F4 — 🔵 The record still contains current-tense claims superseded by the implementation

**Location:** `design.md`, decisions 3–4; `index.html`, comment preceding `.today-first-move`.

Decision 3 still says lookup cost is paid “only on the body-double path.” Decision 4 says metadata is “present only on the body double.” The Today markup comment says the line is “hidden for every other action.” All are false after the warm-up change.

Decision 11 commendably preserves the renderer correction, but these remaining statements make the design internally contradictory.

**Fix:** Annotate the original decisions as amended by decisions 10–11; update the markup comment. Preserve historical claims as history, not as current invariants.

**Concrete check:** Search those three phrases in the final tree and reconcile each with `_warm_up`. A docs-contract pin could be named `test_first_move_record_describes_both_current_carriers`; an exact-wording test is not necessary.

### Requested checks with no additional established defect

- **Shared sentence and scope:** The repair branch passes `(concept,)`, even if the stripped string were empty, so it cannot reach the builder’s *empty sequence* “this milestone names no concept” branch. The generic branch likewise supplies a singleton. Actual nonempty-concept validation is **UNVERIFIED**. Milestone warm-ups can correctly reach that clause.

The no-lesson warm-up is awkward but matches the binding spec: an indexed absence does not establish that the learner has no material elsewhere. I would prefer an explicitly optional reading route, but would not silently revise an owner-approved sentence in this patch.

- **Tails and energy:** Repair-first precedence is correct even when a repair matches the next milestone. Practice’s generic tail is acceptable, though less specific. Teachback after reading is scaffolded learning, not an unaided assessment; if teachback is used diagnostically elsewhere, inspect that contract separately. Excluding visual/audio and offering an optional ramp at high energy follow the recorded scope and are not defects.

- **Recall:** Keep the action-type exclusion. A long absence does not make priming harmless to a retrieval measurement. A deliberate study-then-retrieve activity would need its own semantics; changing this to `_review_type_for` is not justified by the supplied evidence.

- **Start behavior:** Filling the concept for *every* study action is explicitly covered by the spec and decision 11, not an undocumented scope addition. It is reasonable to fold in the missing hand-off. Browser assumptions about an initially blank picker remain **UNVERIFIED**; inspect `test_web_smoke_browser.py` and `tests/e2e/` for Today → Study flows before accepting head CI.

- **Resume and recovery:** A hand-off for different material should clear the old move. A reattached session with no stored move should show none rather than reconstruct one. But “reattached sessions have empty fields” is established only for a fresh component: existing picker state can survive unless recovery explicitly clears or replaces it. Inspect `endConflictSession` and `reattachConflictSession`; proposed probe: `test_reattach_does_not_inherit_pending_picker_first_move`. Their bodies were not supplied, so this is **UNVERIFIED**, not an additional established defect.

- **Rendering:** The supplied Today markup reads `plan?.primary` for both line and button; it does not add an alternate rendering. The new engine paths put the sentence only in metadata. Existing reason contents and the strength of the warm-up test’s negative assertion are **UNVERIFIED** without bodies. Pin both exact sentence absence and absence of a “first move” reason tail.

- **Layout:** The Body Double full-width flex item is compatible with a wrapping strip; the confirmation controls may consume an additional row, not inherently conflict. The Study row necessarily consumes height. Terminal `flex: 1`, minimum-height behavior, resize handling, and narrow-screen usability cannot be verified from these diffs. Check that a long sentence plus controls leaves a usable terminal and triggers its resize handling. No added generic terminal-mount selector is visible.

- **Tests:** Names do not establish exact-string assertions or attribute-order robustness. The reported comment-delimited `_live_strip` slicer is fragile; prefer parsing the relevant element. Node top-level tests are ordinarily sequential within a file and separate test files are process-isolated by default, so save/restore globals is not inherently unsafe. Explicit concurrency settings and whether every asynchronous operation is awaited remain **UNVERIFIED**.

- **Clock leak:** Acceptable as a separate test-isolation correction, not a blocker for this feature. Pin it with `test_due_progress_age_uses_injected_clock`: freeze the injected clock, plant progress exactly three days earlier, assert age three and the corresponding reason independently of the wall clock.

## 3. Refutations

1. **“A stale move never outlives its plan” / “the strip never shows a stale move beneath the next, unrelated activity” is not established.** Clearing on end and hand-off covers two transitions, not material edits or hand-offs received during an active session. F1–F2 provide concrete counterchecks.

2. **“`None` is a searched miss” is false for single-character concepts.** It also conflates rejected hits lacking evidence with no lexical hits. Whether evidence-incomplete rows occur in a real explorer index is **UNVERIFIED**; test that invariant before describing every such result as “no indexed lesson mentions.”

3. **“A deliberate lesson whenever the vault holds one” is broader than this resolver demonstrates.** It requests only one row per concept and examines `rows[0]`. If the best hit lacks title/course evidence, a valid second hit for that same concept is never considered. Recompute with an evidence-incomplete first hit and valid second hit; actual index guarantees preventing this are **UNVERIFIED**.

4. **The “only body double” record claims are superseded**, as identified in F4.

5. **Head CI, browser behavior, version 0.5.1, and detailed assertion quality remain UNVERIFIED here.** The supplied diffs contain no version change and no browser results. That does not prove an incorrect version or failing browser suite. The coordinator’s reported RED executions, GREEN amendments, and golden checks are accepted as supplied facts, not independently reproduced.

## 4. Gate

1. Invalidate a move when its picker material changes — REDs `test_body_double_editing_activity_invalidates_first_move` and `test_study_changing_target_invalidates_first_move`.
2. Isolate live/starting-session moves from subsequent hand-offs — REDs `test_body_double_request_does_not_replace_active_session_first_move` and `test_body_double_request_during_start_does_not_mix_materials`.
3. Stop reporting unsearched single-character concepts as indexed absences — REDs `test_resolve_lesson_searches_single_character_concept` and `test_unsearched_concept_does_not_claim_indexed_absence`.

Acceptance also remains conditional on the already-required head CI/browser and 0.5.1 release checks; their results are **UNVERIFIED** here.
Loading
Loading