sisyphi 1.2.18 → 1.2.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +673 -439
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +225 -287
- package/dist/daemon.js.map +1 -1
- package/dist/templates/agent-plugin/agents/review/CLAUDE.md +0 -1
- package/dist/templates/agent-plugin/agents/spec.md +18 -7
- package/dist/templates/agent-suffix.md +9 -1
- package/dist/templates/orchestrator-base.md +13 -0
- package/dist/templates/orchestrator-plugin/hooks/goal-length-guard.sh +60 -0
- package/dist/templates/orchestrator-plugin/hooks/goal-read-advisory.sh +54 -0
- package/dist/templates/orchestrator-plugin/hooks/hooks.json +20 -0
- package/dist/tui.js +98 -79
- package/dist/tui.js.map +1 -1
- package/package.json +1 -1
- package/templates/agent-plugin/agents/review/CLAUDE.md +0 -1
- package/templates/agent-plugin/agents/spec.md +18 -7
- package/templates/agent-suffix.md +9 -1
- package/templates/orchestrator-base.md +13 -0
- package/templates/orchestrator-plugin/hooks/goal-length-guard.sh +60 -0
- package/templates/orchestrator-plugin/hooks/goal-read-advisory.sh +54 -0
- package/templates/orchestrator-plugin/hooks/hooks.json +20 -0
package/package.json
CHANGED
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
- **`reuse` dismissed entries cite `existing-file:line`** (the existing utility evaluated), not `file:line` (the new code) — the validation wave parses reuse dismissals differently from all other sub-agents.
|
|
2
1
|
- **No output ≠ clean**: a sub-agent that produces no output is treated as failed. The explicit clean sentence ("No X concerns — ...") is the signal the validation wave uses to skip spawning a validator.
|
|
3
2
|
- **Adding a sub-agent**: create `{name}.md` with frontmatter, add `subagent_type: {name}` to the scaling table in `review.md` step 4, and update the scaling guidance table if conditionally spawned — without the registration, the sub-agent is silently never spawned.
|
|
@@ -77,7 +77,7 @@ Run this pass on **every startup**, **before** entering resume logic.
|
|
|
77
77
|
|
|
78
78
|
### 1. Explore
|
|
79
79
|
|
|
80
|
-
Use Bash + Glob + Grep + Read to explore the codebase relevant to the user's stated topic. Identify files, modules, and existing patterns that will be affected.
|
|
80
|
+
Use Bash + Glob + Grep + Read + the Agent tool to explore the codebase relevant to the user's stated topic. Identify files, modules, and existing patterns that will be affected.
|
|
81
81
|
|
|
82
82
|
### 2. Gauge Clarity
|
|
83
83
|
|
|
@@ -227,7 +227,7 @@ Dispatch a single `requirements-writer` subagent for the entire design (see "Sub
|
|
|
227
227
|
|
|
228
228
|
Validate the chunk (parseable JSON + `groups` array where each group has `id` and `requirements`). If invalid, increment N and re-dispatch; if it fails twice, bail.
|
|
229
229
|
|
|
230
|
-
**Snapshot prior approvals before merging.** If `context/requirements.json` already exists (this is a re-dispatch — §5.1 bounce-return or §5.2 writer-
|
|
230
|
+
**Snapshot prior approvals before merging.** If `context/requirements.json` already exists (this is a re-dispatch — §5.1 bounce-return or §5.2 fresh-writer escalation; the §5.2 lead-patch path never reaches here), read its current `groups[].requirements[]` and `groups[].safeAssumptions[]` and build an in-memory **approval map**: for every item where `status === 'approved'` AND `userNotes` is empty/absent, record `contentKey(item) → true`. The content key is the SHA-256 of the canonical-JSON `{title, ears}` after normalizing every string with `trim()` + collapse runs of whitespace to a single space. Items with `status: 'draft'`/`'rejected'` or non-empty `userNotes` are NOT added — the re-dispatch was triggered by their unresolved state, and the writer is meant to revisit them.
|
|
231
231
|
|
|
232
232
|
**Merge.** Replace `groups` entirely with the new chunk; preserve `meta`. Set `meta.stage = 'stage-2-in-progress'`. Then walk the new `groups[].requirements[]` and `groups[].safeAssumptions[]`: for each item, compute its content key; if the approval map contains it, set `status = 'approved'` and clear `userNotes`. Otherwise leave whatever the writer emitted untouched. Delete the chunk file.
|
|
233
233
|
|
|
@@ -283,22 +283,33 @@ With `meta.stage === 'stage-2-verdict-pending'`:
|
|
|
283
283
|
|
|
284
284
|
1. All requirements (incl. safeAssumptions) `status === 'approved'` → atomic-write `meta.stage = 'stage-2-done'`; proceed to Stage 3.
|
|
285
285
|
2. Any `status === 'rejected'` → §5.1 bounce-to-design.
|
|
286
|
-
3. Else (some `draft` with non-empty `userNotes`, no `rejected`) → §5.2
|
|
286
|
+
3. Else (some `draft` with non-empty `userNotes`, no `rejected`) → §5.2 comment resolution.
|
|
287
287
|
|
|
288
288
|
### §5.1 Bounce-to-design
|
|
289
289
|
|
|
290
290
|
Increment `meta.bounceIterations` (init 0 if absent; **never decrements**). If new value > 3, bail. Quote rejected items + `userNotes` to user. Dispatch engineer in revision mode (Stage 1 revision contract) with rejected items as feedback. After engineer returns, run `crtr human show` to display the revised design; re-sign-off. Re-render to text via `hl doc render`. Atomic-write `meta.stage = 'stage-2-in-progress'`. Return to Step 2. REQ ids may shift — each pass is independent. User comments flow to the engineer; the writer re-extracts from the revised design.
|
|
291
291
|
|
|
292
|
-
### §5.2
|
|
292
|
+
### §5.2 Comment resolution
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
The design is unchanged here — design-level objections route through bounce-to-design (§5.1, where the user picked "Bounce"). What remains is `draft` items carrying `userNotes`. **Resolve them in place by default; escalate to a fresh writer only when the comments demand broad re-extraction.** Patching in place is what keeps already-approved items from being reworded and re-asked: the writer regenerates the whole document and its wording drifts, so re-dispatching it for a localized tweak forces the human to re-approve everything.
|
|
295
|
+
|
|
296
|
+
**Triage the commented items.** Classify every `draft`-with-`userNotes` item:
|
|
297
|
+
|
|
298
|
+
- **Localized** — satisfiable by editing that requirement's own fields: reword its EARS clause, fix/add/remove a criterion, tighten or split its scope, correct a detail, clarify wording.
|
|
299
|
+
- **Broad** — implies behavior not captured anywhere (a coverage gap / a missing requirement), a regrouping or restructure across the document, or otherwise can't be met by editing the named items alone. (If a comment reveals the *design* is wrong rather than the requirement text, that is a bounce — tell the user and route to §5.1.)
|
|
300
|
+
|
|
301
|
+
**All comments localized → lead patch (no writer).** Edit each commented requirement in place to satisfy its `userNotes`. Hold the same bar as the writer (`agents/spec/requirements-writer.md`): keep the EARS shape valid (exactly one of `when`/`while`/`if`/`where` plus `shall`) and behavioral, not technical — verbs an external observer sees; no function names, file paths, or algorithms. Stay within the approved design; do not invent behavior it doesn't support. For each patched item: apply the edit, record a one-line rationale in `agentNotes` (e.g. `Revised per review: <what changed>`), clear `userNotes`, and set `status = 'draft'`. **Leave every `approved` item byte-for-byte untouched** — do not run the Step 2 snapshot/merge; that path is for the writer only. Atomic-write `requirements.json` with `meta.stage = 'stage-2-in-progress'` and return to Step 3. Only the patched items reappear as fresh decisions; approved items keep their `status` and surface with the `preAnswered` ◆ marker. Tell the user: `"Revised the N commented requirement(s) in place — only those are back for review; previously-approved items carry forward (◆)."`
|
|
302
|
+
|
|
303
|
+
**Any comment broad → fresh-writer escalation.** Increment `meta.writerRedispatchIterations` (init 0 if absent; **never decrements**). If new value > 3, bail: `"Stage 2 writer-redispatch cap reached after 3 passes. Latest comments preserved in requirements.json. Re-spawn spec fresh or escalate."` Atomic-write `meta.stage = 'writer-redispatch-pending'` BEFORE re-dispatching. Tell user: `"Comments span more than the named items — re-running the writer over the design; re-flag anything it misses on the next pass."` After the writer chunk merge (Step 2, which resets `meta.stage = 'stage-2-in-progress'`), return to Step 3.
|
|
304
|
+
|
|
305
|
+
**Patch not converging.** If an item you already patched comes back with another comment, the localized edit isn't landing — escalate that round via fresh-writer (or bounce via §5.1 if the new comment points at the design) rather than patching the same item a third time.
|
|
295
306
|
|
|
296
307
|
### Step 6 — Stage-2 state-machine table
|
|
297
308
|
|
|
298
309
|
| `meta.stage` | Set at | Resume action on lead respawn |
|
|
299
310
|
|---|---|---|
|
|
300
|
-
| `stage-2-in-progress` | Step 2 merge / §5.1 bounce-returns / §5.2 writer-merge | If `meta.openAskId` set → Resume Logic re-attach; else → Step 3 (issue review deck) |
|
|
301
|
-
| `writer-redispatch-pending` | §5.2 entry | §5.2 writer re-dispatch |
|
|
311
|
+
| `stage-2-in-progress` | Step 2 merge / §5.1 bounce-returns / §5.2 writer-merge / §5.2 lead patch | If `meta.openAskId` set → Resume Logic re-attach; else → Step 3 (issue review deck) |
|
|
312
|
+
| `writer-redispatch-pending` | §5.2 fresh-writer escalation entry | §5.2 fresh-writer re-dispatch |
|
|
302
313
|
| `stage-2-verdict-pending` | Step 4 atomic writeback | Step 5 verdict |
|
|
303
314
|
| `stage-2-done` | Step 5 case 1 | Stage 3 entry |
|
|
304
315
|
|
|
@@ -14,9 +14,17 @@ You are an agent in a sisyphus session.
|
|
|
14
14
|
|
|
15
15
|
If you're blocked by ambiguity, contradictions, or unclear requirements — **don't guess**. Submit what you found instead. A clear report is more valuable than a wrong implementation.
|
|
16
16
|
|
|
17
|
+
## The sis CLI
|
|
18
|
+
|
|
19
|
+
You operate inside a sisyphus session driven by the `sis` CLI. Run `sis <group> -h` to drill into any command.
|
|
20
|
+
|
|
21
|
+
{{HELP:.}}
|
|
22
|
+
|
|
17
23
|
## The User
|
|
18
24
|
|
|
19
|
-
A human may interact with you directly in your pane — if they do, prioritize their input over your original instruction. Otherwise, communicate through the orchestrator via reports.
|
|
25
|
+
A human may interact with you directly in your pane — if they do, prioritize their input over your original instruction. Otherwise, communicate through the orchestrator via reports.
|
|
26
|
+
|
|
27
|
+
**If the user complains about sisyphus itself** — a crash, a CLI that misbehaved, a confusing or broken workflow, behavior they disliked — file it with `sis feedback "<summary>"` (run `sis feedback -h`). Low-cost side-action; do it without asking. It's for the tool, not your task.
|
|
20
28
|
|
|
21
29
|
## Context
|
|
22
30
|
|
|
@@ -101,6 +101,16 @@ goal.md is a plain statement of what "done" looks like — scope boundaries and
|
|
|
101
101
|
**What belongs in goal.md:** the desired end state, what's in scope, what's out of scope.
|
|
102
102
|
**What doesn't:** approach decisions, technical choices, stage plans — those belong in strategy.md and context docs.
|
|
103
103
|
|
|
104
|
+
**Scope files keep goal.md lean.** goal.md is inlined into every wakeup and capped at 100 lines, so concrete detail about one slice of the goal does not belong inline — it taxes every cycle, including ones abstracted far away from that slice. When a part of the goal needs maintained detail (a subsystem, a workstream, a newly-authorized expansion), write `context/scope-<topic>.md` for it and add a one-line pointer under a `## Scope` list in goal.md:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
## Scope
|
|
108
|
+
- context/scope-backend.md — DB + API-layer refactors for X
|
|
109
|
+
- context/scope-frontend.md — render-path cleanup for X
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This is how scope *grows* without rewriting the goal: a mid-session "let's also do the microservices" becomes a new scope file linked from goal.md, never a condensed or deleted goal. Scope files are maintained like other context docs (current understanding, not history) and read on demand; strategy.md and roadmap.md point at the scope file a stage is focused on rather than restating it. A hook rejects any goal.md edit that leaves the file over 100 lines and tells you to offload into scope files.
|
|
113
|
+
|
|
104
114
|
### strategy.md — Your problem-solving map
|
|
105
115
|
|
|
106
116
|
strategy.md defines **how to approach this problem** — the stages, gates, backtrack edges, and behavioral style for this session. It is generated during discovery and progressively updated as the goal crystallizes or shifts.
|
|
@@ -225,6 +235,7 @@ Context dir contents are listed in your prompt each cycle. Read files when you n
|
|
|
225
235
|
- Roadmap items should **reference** context files: `"See context/{plan-lead-agent-id}/plan-stage-1-auth.md for detail."` Copy the path from the plan lead's submission report; don't reconstruct it.
|
|
226
236
|
- Agents writing requirements and designs save to the context dir with descriptive filenames: `requirements-auth.md`, `design-auth.md`. Plan agents save plans under their own subdirectory `context/{agent-id}/plan-*.md`; treat those paths as authoritative from the plan lead's report.
|
|
227
237
|
- **Implementation plans belong here**, not in roadmap.md
|
|
238
|
+
- **Scope files** (`context/scope-<topic>.md`) hold maintained detail for one slice of the goal, linked from goal.md's `## Scope` list — see the goal.md section. You write and maintain them directly, like strategy.md.
|
|
228
239
|
|
|
229
240
|
### Session Directory
|
|
230
241
|
|
|
@@ -284,6 +295,8 @@ You have unlimited cycles. Failed implementations, deferred issues, and skipped
|
|
|
284
295
|
|
|
285
296
|
## CLI Reference
|
|
286
297
|
|
|
298
|
+
{{HELP:.}}
|
|
299
|
+
|
|
287
300
|
{{HELP:session clone}}
|
|
288
301
|
|
|
289
302
|
## File Conflicts
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse(Write|Edit|MultiEdit) hook for the orchestrator: enforce the
|
|
3
|
+
# 100-line cap on goal.md. The edit has already landed (PostToolUse runs after
|
|
4
|
+
# success); if goal.md is now over the cap, emit a decision:block so the
|
|
5
|
+
# orchestrator must trim it and move the detail into context/scope-*.md before
|
|
6
|
+
# proceeding. Under the cap → silent passthrough.
|
|
7
|
+
|
|
8
|
+
if [ -z "$SISYPHUS_SESSION_ID" ] || [ -z "$SISYPHUS_SESSION_DIR" ]; then exit 0; fi
|
|
9
|
+
|
|
10
|
+
STDIN_JSON=$(cat)
|
|
11
|
+
|
|
12
|
+
FP=$(printf '%s' "$STDIN_JSON" | python3 -c "
|
|
13
|
+
import json, sys
|
|
14
|
+
try:
|
|
15
|
+
d = json.load(sys.stdin)
|
|
16
|
+
print((d.get('tool_input') or {}).get('file_path') or '')
|
|
17
|
+
except Exception:
|
|
18
|
+
pass
|
|
19
|
+
" 2>/dev/null)
|
|
20
|
+
|
|
21
|
+
[ -z "$FP" ] && exit 0
|
|
22
|
+
|
|
23
|
+
GOAL_FILE="$SISYPHUS_SESSION_DIR/goal.md"
|
|
24
|
+
|
|
25
|
+
SAME=$(python3 -c "
|
|
26
|
+
import os, sys
|
|
27
|
+
try:
|
|
28
|
+
print('1' if os.path.realpath(sys.argv[1]) == os.path.realpath(sys.argv[2]) else '0')
|
|
29
|
+
except Exception:
|
|
30
|
+
print('0')
|
|
31
|
+
" "$FP" "$GOAL_FILE" 2>/dev/null)
|
|
32
|
+
|
|
33
|
+
[ "$SAME" = "1" ] || exit 0
|
|
34
|
+
[ -f "$GOAL_FILE" ] || exit 0
|
|
35
|
+
|
|
36
|
+
LINES=$(python3 -c "
|
|
37
|
+
import sys
|
|
38
|
+
try:
|
|
39
|
+
with open(sys.argv[1], encoding='utf-8') as f:
|
|
40
|
+
print(sum(1 for _ in f))
|
|
41
|
+
except Exception:
|
|
42
|
+
print(0)
|
|
43
|
+
" "$GOAL_FILE" 2>/dev/null)
|
|
44
|
+
|
|
45
|
+
[ -z "$LINES" ] && exit 0
|
|
46
|
+
if [ "$LINES" -le 100 ]; then exit 0; fi
|
|
47
|
+
|
|
48
|
+
REASON=$(cat <<TXT
|
|
49
|
+
goal.md is now ${LINES} lines — over the 100-line cap. Trim it back to the north-star paragraph plus a "## Scope" reference list before continuing.
|
|
50
|
+
|
|
51
|
+
Move the detail you just added into context/scope-<topic>.md (the maintained home for one slice of the goal) and leave only a one-line pointer in goal.md:
|
|
52
|
+
- context/scope-<topic>.md — one-line description
|
|
53
|
+
|
|
54
|
+
Why the cap: goal.md is inlined into every orchestrator wakeup, so length here taxes every future cycle, even ones working far from this detail. Scope files are read on demand and can be referenced from strategy.md / roadmap.md. Restructure the over-cap detail into scope files rather than deleting still-relevant scope to fit.
|
|
55
|
+
TXT
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
ESCAPED=$(printf '%s' "$REASON" | python3 -c "import json,sys; print(json.dumps(sys.stdin.read()))")
|
|
59
|
+
echo "{\"decision\":\"block\",\"reason\":$ESCAPED,\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\"}}"
|
|
60
|
+
exit 0
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# PostToolUse(Read) hook for the orchestrator: when the orchestrator reads
|
|
3
|
+
# goal.md, surface the scope-file convention. goal.md is inlined into every
|
|
4
|
+
# wakeup, so the orchestrator only Reads it when it intends to edit — that's
|
|
5
|
+
# the moment to remind it to push concrete detail into context/scope-*.md
|
|
6
|
+
# rather than into the goal. Neutral guidance (additionalContext), not a block.
|
|
7
|
+
|
|
8
|
+
if [ -z "$SISYPHUS_SESSION_ID" ] || [ -z "$SISYPHUS_SESSION_DIR" ]; then exit 0; fi
|
|
9
|
+
|
|
10
|
+
STDIN_JSON=$(cat)
|
|
11
|
+
|
|
12
|
+
FP=$(printf '%s' "$STDIN_JSON" | python3 -c "
|
|
13
|
+
import json, sys
|
|
14
|
+
try:
|
|
15
|
+
d = json.load(sys.stdin)
|
|
16
|
+
print((d.get('tool_input') or {}).get('file_path') or '')
|
|
17
|
+
except Exception:
|
|
18
|
+
pass
|
|
19
|
+
" 2>/dev/null)
|
|
20
|
+
|
|
21
|
+
[ -z "$FP" ] && exit 0
|
|
22
|
+
|
|
23
|
+
GOAL_FILE="$SISYPHUS_SESSION_DIR/goal.md"
|
|
24
|
+
|
|
25
|
+
# Only fire for the session's own goal.md (compare resolved paths).
|
|
26
|
+
SAME=$(python3 -c "
|
|
27
|
+
import os, sys
|
|
28
|
+
try:
|
|
29
|
+
print('1' if os.path.realpath(sys.argv[1]) == os.path.realpath(sys.argv[2]) else '0')
|
|
30
|
+
except Exception:
|
|
31
|
+
print('0')
|
|
32
|
+
" "$FP" "$GOAL_FILE" 2>/dev/null)
|
|
33
|
+
|
|
34
|
+
[ "$SAME" = "1" ] || exit 0
|
|
35
|
+
|
|
36
|
+
ADVISORY=$(cat <<'TXT'
|
|
37
|
+
Editing goal.md? Keep it to the north-star paragraph plus a `## Scope` list of references — nothing more. Put any concrete detail about one slice of the goal (a subsystem, a workstream, a newly-authorized expansion) in `context/scope-<topic>.md`, and link it from goal.md with a one-liner:
|
|
38
|
+
- context/scope-backend.md — DB + API-layer refactors for X
|
|
39
|
+
- context/scope-frontend.md — render-path cleanup for X
|
|
40
|
+
|
|
41
|
+
Why: goal.md is inlined into every orchestrator wakeup and capped at 100 lines, so per-slice detail here taxes every future cycle — even ones working far from that slice. Routing detail into scope files lets scope grow without rewriting the goal: a mid-session "let's also do the microservices" becomes a new scope file linked from goal.md, never a condensed or deleted goal. Maintain scope files like other context docs (current understanding, not history); they are read on demand, and strategy.md/roadmap.md point at whichever scope a stage is focused on.
|
|
42
|
+
TXT
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
printf '%s' "$ADVISORY" | python3 -c "
|
|
46
|
+
import json, sys
|
|
47
|
+
print(json.dumps({
|
|
48
|
+
'hookSpecificOutput': {
|
|
49
|
+
'hookEventName': 'PostToolUse',
|
|
50
|
+
'additionalContext': sys.stdin.read(),
|
|
51
|
+
}
|
|
52
|
+
}))
|
|
53
|
+
"
|
|
54
|
+
exit 0
|
|
@@ -9,6 +9,26 @@
|
|
|
9
9
|
}
|
|
10
10
|
]
|
|
11
11
|
}
|
|
12
|
+
],
|
|
13
|
+
"PostToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "Read",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/goal-read-advisory.sh"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
25
|
+
"hooks": [
|
|
26
|
+
{
|
|
27
|
+
"type": "command",
|
|
28
|
+
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/goal-length-guard.sh"
|
|
29
|
+
}
|
|
30
|
+
]
|
|
31
|
+
}
|
|
12
32
|
]
|
|
13
33
|
}
|
|
14
34
|
}
|