-
Notifications
You must be signed in to change notification settings - Fork 3.1k
feat(maintainer): Pi/Minimax variant of maintainer-standup + dual-format persist #1480
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| #!/usr/bin/env bun | ||
| /** | ||
| * Reads raw synthesize-node output on stdin and writes the brief markdown + | ||
| * state.json to .archon/maintainer-standup/. Handles two formats: | ||
| * | ||
| * Preferred — delimited markers: | ||
| * # Maintainer Standup — YYYY-MM-DD | ||
| * ...brief... | ||
| * ARCHON_STATE_JSON_BEGIN | ||
| * {...state json...} | ||
| * ARCHON_STATE_JSON_END | ||
| * | ||
| * Fallback — JSON-wrapped (what Pi/Minimax tends to emit): | ||
| * [optional prose preamble] | ||
| * {"brief_markdown": "...", "next_state": {...}} | ||
| * | ||
| * The fallback path is here because Pi/Minimax M2.7 ignores the delimiter | ||
| * directive and emits the JSON-wrapper format consistently. JSON.parse can | ||
| * still recover it provided the model escaped newlines/quotes correctly. | ||
| * | ||
| * Output: one line of JSON to stdout: {"date","state_path","brief_path"}. | ||
| */ | ||
| import { mkdirSync, writeFileSync } from 'node:fs'; | ||
| import { resolve } from 'node:path'; | ||
|
|
||
| const raw = await Bun.stdin.text(); | ||
|
|
||
| type State = Record<string, unknown>; | ||
| let brief: string | null = null; | ||
| let state: State | null = null; | ||
| let source: 'delimiter' | 'json-wrapper' | null = null; | ||
|
|
||
| // ── Tier 1: delimiter-based extraction ── | ||
| const BEGIN = 'ARCHON_STATE_JSON_BEGIN'; | ||
| const END = 'ARCHON_STATE_JSON_END'; | ||
| const beginIdx = raw.indexOf(BEGIN); | ||
| const endIdx = raw.indexOf(END); | ||
| if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) { | ||
| const stateText = raw.slice(beginIdx + BEGIN.length, endIdx).trim(); | ||
|
Comment on lines
+34
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anchor closing-marker lookup to the opening marker. Line 37 searches Suggested delimiter extraction hardening const BEGIN = 'ARCHON_STATE_JSON_BEGIN';
const END = 'ARCHON_STATE_JSON_END';
const beginIdx = raw.indexOf(BEGIN);
-const endIdx = raw.indexOf(END);
+const endIdx = beginIdx === -1 ? -1 : raw.indexOf(END, beginIdx + BEGIN.length);
if (beginIdx !== -1 && endIdx !== -1 && endIdx > beginIdx) {🤖 Prompt for AI Agents |
||
| try { | ||
| state = JSON.parse(stateText) as State; | ||
| brief = raw.slice(0, beginIdx).trim(); | ||
| source = 'delimiter'; | ||
| } catch (err) { | ||
| process.stderr.write( | ||
| `Delimiter found but state JSON parse failed: ${(err as Error).message}\n`, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // ── Tier 2: JSON-wrapper fallback ({brief_markdown, next_state}) ── | ||
| if (state === null) { | ||
| const firstBrace = raw.indexOf('{'); | ||
| if (firstBrace !== -1) { | ||
| const candidate = raw.slice(firstBrace); | ||
| try { | ||
| const parsed = JSON.parse(candidate) as Record<string, unknown>; | ||
| if ( | ||
| typeof parsed.brief_markdown === 'string' && | ||
| typeof parsed.next_state === 'object' && | ||
| parsed.next_state !== null | ||
| ) { | ||
| brief = parsed.brief_markdown; | ||
| state = parsed.next_state as State; | ||
| source = 'json-wrapper'; | ||
| process.stderr.write( | ||
| 'Synth output used JSON-wrapper format (delimiter contract not followed); recovered via fallback.\n', | ||
| ); | ||
| } | ||
| } catch (err) { | ||
| process.stderr.write( | ||
| `JSON-wrapper fallback parse failed: ${(err as Error).message}\n`, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (state === null || brief === null) { | ||
| process.stderr.write( | ||
| 'PERSIST FAILED: could not extract brief and state from synth output (neither delimiter nor JSON-wrapper format matched).\n', | ||
| ); | ||
| process.stderr.write('--- BEGIN raw output (recoverable from logs) ---\n'); | ||
| process.stderr.write(raw + '\n'); | ||
| process.stderr.write('--- END raw output ---\n'); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| // Strip leading prose preamble — keep from the first '# ' heading onward. | ||
| const lines = brief.split('\n'); | ||
| const headingIdx = lines.findIndex((l) => l.startsWith('# ')); | ||
| if (headingIdx > 0) { | ||
| brief = lines.slice(headingIdx).join('\n'); | ||
| } | ||
| brief = brief.trim(); | ||
|
|
||
| const date = new Date().toLocaleDateString('sv-SE'); // local YYYY-MM-DD | ||
| const baseDir = resolve(process.cwd(), '.archon/maintainer-standup'); | ||
| const briefsDir = resolve(baseDir, 'briefs'); | ||
| mkdirSync(briefsDir, { recursive: true }); | ||
|
|
||
| const statePath = resolve(baseDir, 'state.json'); | ||
| const briefPath = resolve(briefsDir, `${date}.md`); | ||
|
|
||
| writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n'); | ||
| writeFileSync(briefPath, brief + '\n'); | ||
|
|
||
| process.stdout.write( | ||
| JSON.stringify({ | ||
| date, | ||
| source, | ||
| state_path: '.archon/maintainer-standup/state.json', | ||
| brief_path: `.archon/maintainer-standup/briefs/${date}.md`, | ||
| }) + '\n', | ||
| ); | ||
55 changes: 55 additions & 0 deletions
55
.archon/workflows/maintainer/maintainer-standup-minimax.yaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| name: maintainer-standup-minimax | ||
| description: | | ||
| Minimax variant of maintainer-standup. Identical workflow shape — same | ||
| gather scripts, same synthesizer command, same persist node — but the | ||
| synthesize node runs on Pi/Minimax M2.7 instead of Claude Sonnet. | ||
| Use when: nested-Claude-Code session hangs (#1067) block the Claude variant, | ||
| or when you'd rather not spend Claude tokens on the daily brief. | ||
| Triggers: "morning standup minimax", "standup minimax", "daily brief minimax". | ||
| NOT for: First-time setup — run the Claude variant first to validate state. | ||
|
|
||
| provider: pi | ||
| model: minimax/MiniMax-M2.7 | ||
|
|
||
| worktree: | ||
| enabled: false # Live checkout — needs to git pull and read .archon/maintainer-standup/ | ||
|
|
||
| nodes: | ||
| # ── Layer 0: gather facts in parallel ── | ||
|
|
||
| - id: git-status | ||
| script: maintainer-standup-git-status | ||
| runtime: bun | ||
| timeout: 60000 | ||
|
|
||
| - id: gh-data | ||
| script: maintainer-standup-gh-data | ||
| runtime: bun | ||
| timeout: 180000 | ||
|
|
||
| - id: read-context | ||
| script: maintainer-standup-read-context | ||
| runtime: bun | ||
| timeout: 10000 | ||
|
|
||
| # ── Layer 1: synthesize the brief (plain text + delimited state block) ── | ||
|
|
||
| - id: synthesize | ||
| command: maintainer-standup | ||
| depends_on: [git-status, gh-data, read-context] | ||
|
|
||
| # ── Layer 2: persist state and dated brief ── | ||
| # | ||
| # Bash node so the framework shell-quotes $synthesize.output (raw text | ||
| # containing markdown code fences and prose isn't a valid JS expression | ||
| # if substituted into a bun script body). The bash node pipes the synth | ||
| # output into the persist script, which handles both the preferred | ||
| # delimiter format and the fallback JSON-wrapper format Pi/Minimax emits. | ||
|
|
||
| - id: persist | ||
| depends_on: [synthesize] | ||
| timeout: 30000 | ||
| bash: | | ||
| set -uo pipefail | ||
| RAW=$synthesize.output | ||
| printf '%s' "$RAW" | bun .archon/scripts/maintainer-standup-persist.ts |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Validate required state schema before persisting.
Line 41 and Line 64 accept any parsed JSON as
state, and Line 104 writes it directly. If required fields are missing or wrong types, next runs can silently degrade or fail during progress detection.Proposed guardrail for state shape validation
Also applies to: 38-42, 57-65, 104-104
🤖 Prompt for AI Agents