instar 0.28.96 → 0.28.97

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.
@@ -0,0 +1,175 @@
1
+ # Side-Effects Review — project-scope Phase 1.7 skill surface
2
+
3
+ **Scope.** Closes the spec § Phase 1.7 user-facing surface. Rewrites
4
+ `.claude/skills/instar-project/SKILL.md` to cover all twelve canonical
5
+ commands, adds the three small HTTP routes the spec named but hadn't
6
+ materialized (`run-round`, `resume`, `abandon`), and realigns
7
+ `skillCommandForAction()` to the canonical command names.
8
+
9
+ ## What's IN this PR
10
+
11
+ 1. **`.claude/skills/instar-project/SKILL.md`** — full rewrite. Phase 1a
12
+ placeholder language for `/project next` is removed (the connect-the-dots
13
+ PR shipped the structured payload; the skill now documents that contract).
14
+ Every command from spec § 1.7 has a section header with its backing
15
+ endpoint, body schema, response codes, and a one-line rendering rule.
16
+ Final section pins the conversational-tone rule ("never paste a curl
17
+ command in a user-facing reply") that Echo's CLAUDE.md enforces.
18
+ 2. **`POST /projects/:id/run-round`** — manual round trigger. On
19
+ `preflight.ok`, writes `autoAdvanceAt = now` to the named round and
20
+ returns 200 + `scheduledAt`. The auto-advance poller (wired in the
21
+ connect-the-dots PR) picks it up on its next tick (≤60s) and dispatches
22
+ `ProjectRoundExecution.runRound()`. No child-process spawn from the
23
+ request handler — keeps one fire path through one lock.
24
+ 3. **`POST /projects/:id/resume`** — clears `haltedAt`/`haltReason`,
25
+ schedules the round via `autoAdvanceAt = now`, transitions
26
+ `project.status` from `'halted'`/`'abandoned'` back to `'active'`.
27
+ For `status: 'failed'` rounds at the 3-attempt cap, `{force: true}`
28
+ is required AND the `resumeAttempts` counter resets to zero on
29
+ accept. Returns 409 when the round is neither halted nor failed.
30
+ 4. **`POST /projects/:id/abandon`** — sets `project.status = 'abandoned'`,
31
+ strips `autoAdvanceAt` from every remaining round so the poller
32
+ stops considering them, leaves each child's `pipelineStage`
33
+ untouched. Idempotent (`alreadyAbandoned: true` on repeat).
34
+ Refuses 409 when any round is `in-progress` — halt first.
35
+ 5. **`skillCommandForAction()` realignment.** The connect-the-dots PR
36
+ returned `/project approve` and `/project drift-check` for the
37
+ `await-user-approval` and `run-drift-check` action verbs. Spec § 1.7
38
+ uses `/project ack` and `/project drift`; the mapping is updated so
39
+ the dashboard hints + the `/projects/:id/next` payload + the skill
40
+ stay in sync.
41
+ 6. **Tests.** `tests/unit/instar-project-skill.test.ts` (28 assertions)
42
+ pins the SKILL.md contract — frontmatter, every spec § 1.7 command
43
+ has a header, every documented endpoint exists in `routes.ts`.
44
+ `tests/integration/projects-api.test.ts` adds 10 cases for the new
45
+ routes covering happy paths + every documented error case.
46
+
47
+ ## What's explicitly DEFERRED
48
+
49
+ - **`ProjectRoundRunner.resume` / `.abandon` methods.** The new
50
+ endpoints mutate state via `tracker.update()` directly rather than
51
+ routing through a runner method. The runner's value-add is the
52
+ `preflight()` gate; resume/abandon don't have preflight semantics
53
+ (they're transitions OUT of stuck states, not transitions INTO an
54
+ autonomous run). If a future need surfaces — e.g., resume needs to
55
+ hold the round-runner lock briefly, or abandon needs to release a
56
+ worktree — those can land as runner methods then. Today, the
57
+ shorter path is cleaner.
58
+ - **Resume worktree cleanup.** Spec § 1.5 step "Halt switch" says
59
+ "Worktrees retained for inspection (cleanup deferred to user
60
+ `/project resume` or `/project abandon`)." The new resume/abandon
61
+ routes do NOT prune worktrees — the autonomous child the poller
62
+ fires next will recreate them lazily. Worktree GC is a separate
63
+ follow-up (registered as a child of the spec's deferral list).
64
+ - **No telemetry on action verbs.** The dashboard hint
65
+ (`skillCommandForAction()`) is read-only suggestion; we don't yet
66
+ count which verbs fire most frequently. A simple counter on the
67
+ `/projects/:id/next` handler would surface "what's the most common
68
+ unblocking step?" — interesting telemetry, but not Phase 1 scope.
69
+ - **Skill body translation hooks.** The SKILL.md is a single file; we
70
+ don't (yet) split per-command bodies into separate files the agent
71
+ can lazy-load. At ~10KB the file is well within the context budget
72
+ for a `/project` invocation. If the surface keeps growing past
73
+ Phase 1, splitting per-command is the natural next move.
74
+
75
+ ## Side-effects review
76
+
77
+ **Over-block.** All three new routes are pure tracker.update() calls
78
+ with OCC. No child-process spawn, no git shell-out, no LLM call.
79
+ P99 latency in test runs ~20ms (single tracker write + JSON encode).
80
+ The skill body grew from ~3.5KB to ~10KB — adds one file read per
81
+ `/project` invocation; well below the context-window pressure
82
+ threshold.
83
+
84
+ **Under-block.** `resume` does not re-run drift before scheduling.
85
+ The poller's preflight (which fires after the route writes
86
+ `autoAdvanceAt`) re-runs drift if the cached verdict is stale (>24h)
87
+ per spec § Phase 1.4. So drift is enforced at fire time, not at
88
+ resume time — same level as the original `/project run-round`. This
89
+ is intentional: resume is a state mutation, not a fire trigger.
90
+
91
+ **Level-of-abstraction fit.** Each new route sits at the same layer
92
+ as the surrounding routes:
93
+ - `run-round` belongs alongside `halt` and `ack` (all three are
94
+ user-initiated state changes that thread through the runner).
95
+ - `resume` and `abandon` belong alongside `accept-partial` (all
96
+ three close out a specific round-end condition).
97
+
98
+ The skill is the highest layer — it's the conversational shell over
99
+ the HTTP API. SKILL.md is markdown the agent reads on `/project`
100
+ invocation, not executable code; the body's instructions ARE the
101
+ implementation.
102
+
103
+ **Signal vs authority.** The new routes are authority paths — they
104
+ mutate state. None of them consult an LLM signal. `run-round` does
105
+ delegate to `ProjectRoundRunner.preflight()` for the deterministic
106
+ gate checks; that's the spec's single-chokepoint pattern, not a
107
+ signal/authority mix.
108
+
109
+ **Interactions.**
110
+ - `run-round` + `auto-advance poller`: the route writes `autoAdvanceAt`,
111
+ the poller reads it. The poller's `inFlight: Set<projectId>` guard
112
+ (shipped in connect-the-dots) ensures a manual run-round followed
113
+ by a poller tick doesn't double-fire.
114
+ - `resume` + `halt`: idempotent in either direction. `halt` writes
115
+ `haltedAt`; `resume` strips it. Repeat halts on an already-halted
116
+ round are no-ops; repeat resumes on an already-resumed round
117
+ return 409 ("round is not halted or failed").
118
+ - `abandon` + `claim-ownership`: abandon sets `project.status =
119
+ 'abandoned'`; the auto-advance poller's owner filter already
120
+ excludes abandoned projects. A peer claiming ownership of an
121
+ abandoned project would just re-claim — the poller still skips
122
+ abandoned-status projects regardless of owner.
123
+ - `skillCommandForAction()` realignment: existing test
124
+ `expect(typeof res.body.skillCommand).toBe('string')` doesn't
125
+ assert exact strings, so the rename is backwards-compatible at
126
+ the test level. Dashboard consumers render the string verbatim —
127
+ they get `/project ack` instead of `/project approve`, which is
128
+ what spec § 1.7 documented.
129
+
130
+ **Rollback cost.** Three options:
131
+ 1. **Skill-only rollback** — restore the prior SKILL.md from
132
+ `git show HEAD~1:.claude/skills/instar-project/SKILL.md`. Agent
133
+ reverts to Phase 1a read-only behavior; new HTTP routes still
134
+ exist but are not surfaced. Zero state risk.
135
+ 2. **Routes-only rollback** — `git revert` the three new route
136
+ handlers. Skill still references them; agent surfaces 404 from
137
+ `/project run-round`, `/project resume`, `/project abandon`.
138
+ Surface-but-not-functional, but no data corruption — state
139
+ mutations all go through `tracker.update()` with OCC, which is
140
+ already proven safe.
141
+ 3. **Full revert** — drop the whole commit. No on-disk state from
142
+ this PR persists (the abandon/halt/resume state fields all
143
+ pre-exist in the `Initiative` schema from Phase 1a).
144
+
145
+ All three are clean. The PR introduces no new persisted state, no
146
+ new file paths, no new background work — it adds surface area over
147
+ the existing state machine.
148
+
149
+ ## Files touched
150
+
151
+ - `.claude/skills/instar-project/SKILL.md` (rewritten)
152
+ - `src/server/routes.ts` (three new route handlers + one mapping fix)
153
+ - `tests/integration/projects-api.test.ts` (10 new cases)
154
+ - `tests/unit/instar-project-skill.test.ts` (new file, 28 assertions)
155
+ - `upgrades/NEXT.md` (new release notes)
156
+
157
+ ## Spec references
158
+
159
+ - `docs/specs/PROJECT-SCOPE-SPEC.md` § Phase 1.7 — canonical
160
+ command list.
161
+ - `docs/specs/PROJECT-SCOPE-SPEC.md` § Phase 1.5 — runner preflight
162
+ contract; halt/resume worktree-retention rule.
163
+ - `docs/specs/PROJECT-SCOPE-SPEC.md` § Phase 1.3 — HTTP layer
164
+ doesn't enforce gates the runner already enforces. `run-round`
165
+ delegates to `preflight()` cleanly.
166
+
167
+ ## Coverage
168
+
169
+ - Unit: structural coverage of SKILL.md + endpoint existence —
170
+ `tests/unit/instar-project-skill.test.ts`.
171
+ - Integration: happy + error paths for all three new routes against
172
+ the real `AgentServer` + `InitiativeTracker` + `ProjectRoundRunner`
173
+ — `tests/integration/projects-api.test.ts`.
174
+ - Lint: clean.
175
+ - Type-check: clean (new `RoundStatus` import).