cli: make bundled help self-describing
This commit is contained in:
@@ -113,6 +113,15 @@ Rules:
|
||||
|
||||
The binary name is `inbox`.
|
||||
|
||||
Built-in help should be sufficient for first use:
|
||||
|
||||
- root help should explain the worker role and the normal fetch -> claim -> update -> wait-reply -> done/fail loop
|
||||
- command help should explain when to use the command, not just list flags
|
||||
- high-frequency commands should include concrete examples that can be copied directly
|
||||
- `list` help should explain how it differs from `fetch`
|
||||
- `show` help should explain that it is the thread-history inspection command
|
||||
- `watch` help should explain how it differs from `wait-reply`
|
||||
|
||||
### Global Flags
|
||||
|
||||
- `--db PATH`
|
||||
|
||||
@@ -110,6 +110,16 @@ The leader should block on `orch wait`, not on ad hoc `sleep`.
|
||||
|
||||
The binary name is `orch`.
|
||||
|
||||
Built-in help should be sufficient for first use:
|
||||
|
||||
- root help should explain the leader role and the normal run -> task -> dep -> ready -> dispatch -> wait/status loop
|
||||
- command help should explain the scheduling contract, not just list flags
|
||||
- `dispatch` help should explicitly explain `--execution-mode analysis|code` and which flags only apply to code mode
|
||||
- high-frequency commands should include concrete examples that can be copied directly
|
||||
- `status` help should explain that it is the main operational dashboard command
|
||||
- `blocked` help should explain that it is the compact queue to inspect before `answer`
|
||||
- `cleanup` help should explain how `--task`, `--attempt`, and `--all-completed` change cleanup scope
|
||||
|
||||
### Global Flags
|
||||
|
||||
- `--db PATH`
|
||||
|
||||
@@ -22,6 +22,12 @@ func newCancelCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cancel",
|
||||
Short: "Cancel a thread",
|
||||
Long: helpLong(
|
||||
"Use cancel to terminate a thread and record a control message explaining why it should stop.",
|
||||
"cancel is usually a leader or operator action rather than a normal worker completion step.",
|
||||
"Use a reason when you need later readers to understand why the thread was stopped.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db cancel --agent leader --thread thr_123 --reason "Task superseded by a new plan."`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -22,6 +22,14 @@ func newClaimCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "claim",
|
||||
Short: "Acquire a lease on a pending thread",
|
||||
Long: helpLong(
|
||||
"Use claim to acquire the active worker lease for one thread.",
|
||||
"claim is the step that turns a candidate thread into owned work.",
|
||||
"After claim, the worker should inspect the thread, send an in_progress update when real work starts, and finish with done or fail.",
|
||||
"Only one active lease may exist per thread at a time.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db claim --agent worker-a --thread thr_123
|
||||
inbox --db .agents/coord.db claim --agent worker-a --thread thr_123 --lease-seconds 1800`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -33,6 +33,22 @@ func newCompleteCmd(root *rootOptions, mode string) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: mode,
|
||||
Short: map[string]string{"done": "Mark a thread complete", "fail": "Mark a thread failed"}[mode],
|
||||
Long: map[string]string{
|
||||
"done": helpLong(
|
||||
"Use done to close a claimed thread successfully and record the final result summary.",
|
||||
"done is a terminal operation; use it only when the work is complete enough to hand back to the leader.",
|
||||
"Include a short summary and optionally a detailed body or artifacts for the final result.",
|
||||
),
|
||||
"fail": helpLong(
|
||||
"Use fail to close a claimed thread unsuccessfully and record the failure summary.",
|
||||
"fail is a terminal operation; use it when the attempt cannot complete successfully.",
|
||||
"Include a short summary and enough detail for the leader to decide on retry, reassignment, or cancellation.",
|
||||
),
|
||||
}[mode],
|
||||
Example: map[string]string{
|
||||
"done": ` inbox --db .agents/coord.db done --agent worker-a --thread thr_123 --summary "Implemented retry policy" --body "Retries now cover read timeouts."`,
|
||||
"fail": ` inbox --db .agents/coord.db fail --agent worker-a --thread thr_123 --summary "Blocked by migration issue" --body "Previous schema state is inconsistent."`,
|
||||
}[mode],
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -23,6 +23,14 @@ func newFetchCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "List candidate threads for an agent",
|
||||
Long: helpLong(
|
||||
"Use fetch to discover threads that are candidates for one agent to claim.",
|
||||
"fetch does not grant ownership; it is only the discovery step before claim.",
|
||||
"The normal worker loop is fetch -> claim -> show -> update/done/fail.",
|
||||
"Use --unread when you only want threads whose latest message has not been consumed by that agent.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db fetch --agent worker-a --status pending
|
||||
inbox --db .agents/coord.db fetch --agent worker-a --status pending,blocked --unread --limit 5`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package inbox
|
||||
|
||||
import "strings"
|
||||
|
||||
func helpLong(purpose string, constraints ...string) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(strings.TrimSpace(purpose))
|
||||
if len(constraints) == 0 {
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
builder.WriteString("\n\nConstraints:\n")
|
||||
for _, constraint := range constraints {
|
||||
constraint = strings.TrimSpace(constraint)
|
||||
if constraint == "" {
|
||||
continue
|
||||
}
|
||||
builder.WriteString("- ")
|
||||
builder.WriteString(constraint)
|
||||
builder.WriteByte('\n')
|
||||
}
|
||||
|
||||
return strings.TrimRight(builder.String(), "\n")
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package inbox
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestInboxRootHelpExplainsWorkerLoop(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeInboxCommand("--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "Workers should use inbox directly") {
|
||||
t.Fatalf("expected root help to explain worker role, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected root help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "fetch --agent worker-a --status pending") {
|
||||
t.Fatalf("expected root help to include a concrete workflow example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxUpdateHelpExplainsBlockedQuestions(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeInboxCommand("update", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "Blocked updates should include") {
|
||||
t.Fatalf("expected update help to explain blocked questions, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected update help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, `--payload-json '{"question":"Use stdout or stderr?"}'`) {
|
||||
t.Fatalf("expected update help to include payload-json example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxWaitReplyHelpExplainsBlockingPrimitive(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeInboxCommand("wait-reply", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "worker-side blocking primitive") {
|
||||
t.Fatalf("expected wait-reply help to explain its role, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "--after-event 42 --timeout-seconds 900") {
|
||||
t.Fatalf("expected wait-reply help to include resume example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboxListHelpExplainsDifferenceFromFetch(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeInboxCommand("list", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "Compared with fetch") {
|
||||
t.Fatalf("expected list help to explain how it differs from fetch, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected list help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "--created-by orch --status done,failed") {
|
||||
t.Fatalf("expected list help to include inspection-focused example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,12 @@ func newInitCmd(opts *rootOptions) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialize the shared SQLite database schema",
|
||||
Long: helpLong(
|
||||
"Use init to create or migrate the shared inbox SQLite schema at one database path.",
|
||||
"Run init once before first real use on a new database path.",
|
||||
"init is safe to rerun when you need to ensure the schema exists before another command.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db init`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -23,6 +23,14 @@ func newListCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List threads with filters",
|
||||
Long: helpLong(
|
||||
"Use list for broad inbox inspection across many threads.",
|
||||
"Compared with fetch: fetch is the worker discovery step before claim, while list is the general inspection surface for leaders, operators, and debugging.",
|
||||
"Use list when you want to scan by assigned worker, creator, or status without implying that the current agent plans to claim the work.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db list --status blocked,in_progress
|
||||
inbox --db .agents/coord.db list --assigned-to worker-a --status pending --limit 10
|
||||
inbox --db .agents/coord.db list --created-by orch --status done,failed`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
@@ -73,11 +81,11 @@ func newListCmd(root *rootOptions) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.agent, "agent", "", "Assigned agent filter shortcut")
|
||||
cmd.Flags().StringVar(&opts.statuses, "status", "", "Comma-separated status filter")
|
||||
cmd.Flags().StringVar(&opts.createdBy, "created-by", "", "Created-by filter")
|
||||
cmd.Flags().StringVar(&opts.assignedTo, "assigned-to", "", "Assigned-to filter")
|
||||
cmd.Flags().IntVar(&opts.limit, "limit", 20, "Maximum number of threads")
|
||||
cmd.Flags().StringVar(&opts.agent, "agent", "", "Shortcut for filtering threads assigned to one agent")
|
||||
cmd.Flags().StringVar(&opts.statuses, "status", "", "Comma-separated status filter such as pending,blocked,done")
|
||||
cmd.Flags().StringVar(&opts.createdBy, "created-by", "", "Only include threads created by this agent")
|
||||
cmd.Flags().StringVar(&opts.assignedTo, "assigned-to", "", "Only include threads currently assigned to this agent")
|
||||
cmd.Flags().IntVar(&opts.limit, "limit", 20, "Maximum number of threads to return")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -21,6 +21,13 @@ func newRenewCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "renew",
|
||||
Short: "Extend an existing lease",
|
||||
Long: helpLong(
|
||||
"Use renew to extend an active worker lease on one claimed thread.",
|
||||
"renew only applies when the caller already owns the active lease.",
|
||||
"Use renew for long-running work instead of risking lease expiry mid-execution.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db renew --agent worker-a --thread thr_123
|
||||
inbox --db .agents/coord.db renew --agent worker-a --thread thr_123 --lease-seconds 1800`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ func newReplyCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "reply",
|
||||
Short: "Reply inside an existing thread",
|
||||
Long: helpLong(
|
||||
"Use reply to append a directed answer, control note, or follow-up message to an existing thread.",
|
||||
"Leaders usually use reply or orch answer when a worker is blocked.",
|
||||
"Workers may also use reply for non-terminal coordination that should stay inside the same thread history.",
|
||||
"reply is for one existing thread; use send when you need to create a new thread.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db reply --from leader --to worker-a --thread thr_123 --summary "Use stdout" --body "Use stdout for MVP."
|
||||
inbox --db .agents/coord.db reply --from leader --to worker-a --thread thr_123 --kind control --summary "Proceed with option A" --payload-json '{"decision":"option_a"}'`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -14,8 +14,22 @@ func NewRootCmd() *cobra.Command {
|
||||
opts := &rootOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "inbox",
|
||||
Short: "Worker-facing durable coordination bus",
|
||||
Use: "inbox",
|
||||
Short: "Worker-facing durable coordination bus",
|
||||
Long: helpLong(
|
||||
"Use inbox to coordinate durable worker-side execution through SQLite-backed threads, messages, leases, and artifacts.",
|
||||
"Workers should use inbox directly to fetch or inspect one assigned thread, claim it, report progress, ask blocked questions, wait for answers, and finish with done or fail.",
|
||||
"Leaders usually use orch for planning and dispatch, then use inbox mainly for inspection or manual repair.",
|
||||
"fetch does not grant ownership; claim is the step that acquires the active lease.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db init
|
||||
inbox --db .agents/coord.db fetch --agent worker-a --status pending
|
||||
inbox --db .agents/coord.db claim --agent worker-a --thread thr_123
|
||||
inbox --db .agents/coord.db show --thread thr_123
|
||||
inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status in_progress --summary "Started work"
|
||||
inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status blocked --summary "Need API decision" --payload-json '{"question":"Use v1 or v2?"}'
|
||||
inbox --db .agents/coord.db wait-reply --thread thr_123 --after-event 10
|
||||
inbox --db .agents/coord.db done --agent worker-a --thread thr_123 --summary "Finished"`,
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ func newSendCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "send",
|
||||
Short: "Create a thread with an initial directed message",
|
||||
Long: helpLong(
|
||||
"Use send to create a new directed thread and first message.",
|
||||
"This is the low-level thread creation primitive.",
|
||||
"Leaders often create task threads through orch dispatch instead; use inbox send for manual repair, ad hoc coordination, or a thread that does not belong to an orch-managed task.",
|
||||
"Creating a new thread requires a subject unless you are explicitly targeting an existing thread ID.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db send --from leader --to worker-a --subject "Investigate flaky test" --summary "Check latest failures" --run run_blog_001 --task T1
|
||||
inbox --db .agents/coord.db send --from leader --to worker-a --subject "Review logs" --summary "See attached stacktrace" --body-file ./brief.md --artifact ./failure.log --artifact-kind log`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -20,6 +20,14 @@ func newShowCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show one thread with message history",
|
||||
Long: helpLong(
|
||||
"Use show to inspect one thread together with its message history.",
|
||||
"Workers should usually inspect the assigned thread before making assumptions about task body, payload JSON, or prior coordination.",
|
||||
"Leaders can also use show for manual debugging and repair.",
|
||||
"Use show when you need the exact message sequence inside one thread, not just a filtered thread list.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db show --thread thr_123
|
||||
inbox --db .agents/coord.db --agent worker-a show --thread thr_123 --mark-read`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
@@ -71,7 +79,7 @@ func newShowCmd(root *rootOptions) *cobra.Command {
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.threadID, "thread", "", "Thread ID")
|
||||
cmd.Flags().BoolVar(&opts.markRead, "mark-read", false, "Advance the caller's read cursor to the latest message")
|
||||
cmd.Flags().BoolVar(&opts.markRead, "mark-read", false, "Advance the caller's read cursor to the latest message in this thread")
|
||||
_ = cmd.MarkFlagRequired("thread")
|
||||
|
||||
return cmd
|
||||
|
||||
@@ -26,6 +26,14 @@ func newUpdateCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Append a progress or blocked update to a thread",
|
||||
Long: helpLong(
|
||||
"Use update to append worker progress inside one claimed thread.",
|
||||
"in_progress means real work has started.",
|
||||
"blocked means the worker cannot continue without a precise answer or dependency.",
|
||||
"Blocked updates should include a concise summary and usually a payload_json question so the leader can answer deterministically.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status in_progress --summary "Reading current auth flow"
|
||||
inbox --db .agents/coord.db update --agent worker-a --thread thr_123 --status blocked --summary "Need logging decision" --payload-json '{"question":"Use stdout or stderr?"}'`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -24,6 +24,15 @@ func newWaitReplyCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "wait-reply",
|
||||
Short: "Block until a reply-like message appears in a thread",
|
||||
Long: helpLong(
|
||||
"Use wait-reply after a worker has marked a thread blocked and needs a leader response.",
|
||||
"This is the worker-side blocking primitive; prefer it over ad hoc sleep loops.",
|
||||
"Resume with --after-event or --after-message when you already know the last event or message you processed.",
|
||||
"wait-reply watches one thread, unlike watch which can observe broader inbox activity.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db wait-reply --thread thr_123
|
||||
inbox --db .agents/coord.db wait-reply --thread thr_123 --after-event 42 --timeout-seconds 900
|
||||
inbox --db .agents/coord.db wait-reply --thread thr_123 --kinds answer,result`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -23,6 +23,13 @@ func newWatchCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "watch",
|
||||
Short: "Block until new matching activity appears",
|
||||
Long: helpLong(
|
||||
"Use watch to wait for new matching thread activity across the inbox.",
|
||||
"This is useful for operator-style inspection or lightweight agents that need to observe when new work, blocked work, or terminal results appear without polling manually.",
|
||||
"watch is broader than wait-reply: it watches many matching threads, while wait-reply is the worker-side primitive for one blocked thread.",
|
||||
),
|
||||
Example: ` inbox --db .agents/coord.db watch --status pending,blocked
|
||||
inbox --db .agents/coord.db watch --agent worker-a --status pending --after-event 100 --timeout-seconds 300`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
@@ -81,10 +88,10 @@ func newWatchCmd(root *rootOptions) *cobra.Command {
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.agent, "agent", "", "Assigned agent filter")
|
||||
cmd.Flags().StringVar(&opts.statuses, "status", "pending,blocked,done,failed", "Comma-separated status filter")
|
||||
cmd.Flags().IntVar(&opts.timeoutSeconds, "timeout-seconds", 0, "Maximum time to wait; 0 waits forever")
|
||||
cmd.Flags().Int64Var(&opts.afterEventID, "after-event", 0, "Resume after a known event ID")
|
||||
cmd.Flags().StringVar(&opts.agent, "agent", "", "Only wake for threads assigned to this agent")
|
||||
cmd.Flags().StringVar(&opts.statuses, "status", "pending,blocked,done,failed", "Comma-separated thread status filter")
|
||||
cmd.Flags().IntVar(&opts.timeoutSeconds, "timeout-seconds", 0, "Maximum time to wait; 0 means wait forever")
|
||||
cmd.Flags().Int64Var(&opts.afterEventID, "after-event", 0, "Resume after a known inbox event ID")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -23,6 +23,13 @@ func newAnswerCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "answer",
|
||||
Short: "Answer the active blocked question for a task",
|
||||
Long: helpLong(
|
||||
"Use answer to write one response back into the active blocked attempt thread for a task.",
|
||||
"Use body for human-readable guidance, payload-json for structured decisions, or both when workers need a stable machine-readable contract.",
|
||||
"answer targets the current active blocked attempt for the task; it is not a generic freeform message append.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db answer --run blog_mvp_001 --task T2 --body "Use stdout for MVP."
|
||||
orch --db .agents/coord.db answer --run blog_mvp_001 --task T2 --payload-json '{"decision":"stdout","source":"leader"}'`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
body, err := resolveBodyValue(opts.body, opts.bodyFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,12 @@ func newBlockedCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "blocked",
|
||||
Short: "List blocked tasks and their latest question",
|
||||
Long: helpLong(
|
||||
"Use blocked to list tasks whose active attempt is blocked together with the latest question the leader needs to answer.",
|
||||
"Use blocked before answer when you want a compact queue of unresolved worker questions instead of the full run view from status.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db blocked --run blog_mvp_001
|
||||
orch --db .agents/coord.db --json blocked --run blog_mvp_001`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -21,6 +21,13 @@ func newCancelCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cancel",
|
||||
Short: "Cancel a task or an entire run",
|
||||
Long: helpLong(
|
||||
"Use cancel to stop one task or an entire run.",
|
||||
"Pass --task when you want to cancel one task inside the run; omit it when you want to cancel the whole run.",
|
||||
"Use a reason when later readers need to understand why the work was stopped.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db cancel --run blog_mvp_001 --task T3 --reason "Superseded by a new plan."
|
||||
orch --db .agents/coord.db cancel --run blog_mvp_001 --reason "User cancelled the entire request."`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -23,6 +23,16 @@ func newCleanupCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "cleanup",
|
||||
Short: "Remove completed or abandoned attempt worktrees",
|
||||
Long: helpLong(
|
||||
"Use cleanup to remove worktrees that belong to completed, abandoned, or explicitly forced attempt cleanup.",
|
||||
"cleanup only affects attempts that actually have worktree-backed execution state; analysis-mode attempts have no worktree to remove.",
|
||||
"Pass --task when you want to clean one task's latest cleanup candidate.",
|
||||
"Pass --task plus --attempt when you want one exact attempt.",
|
||||
"Pass --all-completed when you want a run-wide cleanup sweep.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db cleanup --run blog_mvp_001 --task T1
|
||||
orch --db .agents/coord.db cleanup --run blog_mvp_001 --task T1 --attempt 2
|
||||
orch --db .agents/coord.db cleanup --run blog_mvp_001 --all-completed`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
@@ -74,9 +84,9 @@ func newCleanupCmd(root *rootOptions) *cobra.Command {
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.runID, "run", "", "Run ID")
|
||||
cmd.Flags().StringVar(&opts.taskID, "task", "", "Optional task ID")
|
||||
cmd.Flags().IntVar(&opts.attemptNo, "attempt", 0, "Specific attempt number")
|
||||
cmd.Flags().BoolVar(&opts.allCompleted, "all-completed", false, "Clean all completed or abandoned worktrees in the run")
|
||||
cmd.Flags().StringVar(&opts.taskID, "task", "", "Limit cleanup candidates to one task")
|
||||
cmd.Flags().IntVar(&opts.attemptNo, "attempt", 0, "Limit cleanup to one specific attempt number")
|
||||
cmd.Flags().BoolVar(&opts.allCompleted, "all-completed", false, "Clean every completed or abandoned worktree in the run")
|
||||
cmd.Flags().BoolVar(&opts.force, "force", false, "Force cleanup even for non-terminal worktrees")
|
||||
_ = cmd.MarkFlagRequired("run")
|
||||
|
||||
|
||||
@@ -6,6 +6,14 @@ func newCouncilCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "council",
|
||||
Short: "Council review workflow commands",
|
||||
Long: helpLong(
|
||||
"Use council commands for the three-reviewer analysis workflow built on top of orch runs, tasks, and inbox threads.",
|
||||
"council is analysis-oriented and is meant for grouped review output rather than direct code-writing execution.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db council start --run council_blog_001 --target "Review the current architecture."
|
||||
orch --db .agents/coord.db council wait --run council_blog_001 --timeout-seconds 600
|
||||
orch --db .agents/coord.db council tally --run council_blog_001
|
||||
orch --db .agents/coord.db council report --run council_blog_001`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(newCouncilStartCmd(root))
|
||||
|
||||
@@ -23,6 +23,12 @@ func newCouncilReportCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "report",
|
||||
Short: "Render the final grouped council output",
|
||||
Long: helpLong(
|
||||
"Use council report to render the final council output from previously tallied grouped recommendations and persist the markdown artifact on disk.",
|
||||
"Run report after tally so the grouped recommendations and vote buckets are already available.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db council report --run council_blog_001
|
||||
orch --db .agents/coord.db council report --run council_blog_001 --show consensus,majority`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -27,6 +27,12 @@ func newCouncilStartCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Create and dispatch a three-reviewer council run",
|
||||
Long: helpLong(
|
||||
"Use council start to create one three-reviewer council run and immediately dispatch the fixed reviewer tasks.",
|
||||
"This is analysis-oriented workflow setup on top of orch.",
|
||||
"The result is a normal run with reviewer tasks that later feed council wait, tally, and report.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db council start --run council_blog_001 --target "Review the current blog architecture." --target-type mixed --output both`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -20,6 +20,11 @@ func newCouncilTallyCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "tally",
|
||||
Short: "Group reviewer findings and compute council support counts",
|
||||
Long: helpLong(
|
||||
"Use council tally to read reviewer outputs, group similar proposals, and compute support counts such as consensus, majority, and minority.",
|
||||
"Run tally after reviewer completion and before rendering the final report.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db council tally --run council_blog_001 --similarity normal`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -21,6 +21,11 @@ func newCouncilWaitCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "wait",
|
||||
Short: "Block until all council reviewers complete or timeout is reached",
|
||||
Long: helpLong(
|
||||
"Use council wait to block until all reviewer tasks in one council run have completed or the timeout is reached.",
|
||||
"Use this after council start instead of polling reviewer task state manually.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db council wait --run council_blog_001 --timeout-seconds 900`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -10,15 +10,20 @@ import (
|
||||
)
|
||||
|
||||
type depAddOptions struct {
|
||||
runID string
|
||||
taskID string
|
||||
dependsOn string
|
||||
runID string
|
||||
taskID string
|
||||
dependsOn string
|
||||
}
|
||||
|
||||
func newDepCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "dep",
|
||||
Short: "Task dependency commands",
|
||||
Long: helpLong(
|
||||
"Use dep commands to gate one task on another task's completion.",
|
||||
"Dependencies affect the ready queue; they do not themselves launch or cancel work.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db dep add --run blog_mvp_001 --task T2 --depends-on T1`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(newDepAddCmd(root))
|
||||
@@ -31,6 +36,11 @@ func newDepAddCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add a dependency edge to a task",
|
||||
Long: helpLong(
|
||||
"Use dep add to make one task wait for another task to finish before it becomes ready.",
|
||||
"The dependency target and dependent task must already exist in the same run.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db dep add --run blog_mvp_001 --task frontend --depends-on backend`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -27,6 +27,16 @@ func newDispatchCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "dispatch",
|
||||
Short: "Dispatch a ready task to a worker through inbox",
|
||||
Long: helpLong(
|
||||
"Use dispatch to turn one ready task into a concrete attempt and inbox thread.",
|
||||
"You must choose exactly one execution mode.",
|
||||
"analysis: create the attempt and thread only; do not allocate a worktree.",
|
||||
"code: allocate a Git worktree for the attempt and record workspace metadata.",
|
||||
"repo-path, workspace-root, and base-ref only apply to execution-mode code.",
|
||||
"dispatch creates handoff state only; a separate worker runtime or worker agent must still claim the assigned inbox thread.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db dispatch --run blog_mvp_001 --task T1 --execution-mode analysis --to qa-worker --body "Summarize the latest failures."
|
||||
orch --db .agents/coord.db dispatch --run blog_mvp_001 --task T2 --execution-mode code --to backend-worker --repo-path /path/to/repo --workspace-root .orch/worktrees --base-ref main --body-file ./tasks/t2.md`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
normalizedOpts, err := normalizeDispatchOptions(*opts)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package orch
|
||||
|
||||
import "strings"
|
||||
|
||||
func helpLong(purpose string, constraints ...string) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString(strings.TrimSpace(purpose))
|
||||
if len(constraints) == 0 {
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
builder.WriteString("\n\nConstraints:\n")
|
||||
for _, constraint := range constraints {
|
||||
constraint = strings.TrimSpace(constraint)
|
||||
if constraint == "" {
|
||||
continue
|
||||
}
|
||||
builder.WriteString("- ")
|
||||
builder.WriteString(constraint)
|
||||
builder.WriteByte('\n')
|
||||
}
|
||||
|
||||
return strings.TrimRight(builder.String(), "\n")
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package orch
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOrchRootHelpExplainsLeaderWorkflow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeOrchCommand("--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "orch is the control plane") {
|
||||
t.Fatalf("expected root help to explain control-plane role, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected root help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "dispatch --run blog_mvp_001 --task T1 --execution-mode analysis") {
|
||||
t.Fatalf("expected root help to include execution-mode example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchDispatchHelpExplainsExecutionModes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeOrchCommand("dispatch", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "analysis: create the attempt and thread only") {
|
||||
t.Fatalf("expected dispatch help to explain analysis mode, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected dispatch help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "repo-path, workspace-root, and base-ref only apply to execution-mode code") {
|
||||
t.Fatalf("expected dispatch help to explain code-only flags, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchCouncilStartHelpExplainsWorkflow(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeOrchCommand("council", "start", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "three-reviewer council run") {
|
||||
t.Fatalf("expected council start help to explain purpose, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, `--target "Review the current blog architecture."`) {
|
||||
t.Fatalf("expected council start help to include an example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchStatusHelpExplainsDashboardRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeOrchCommand("status", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "main leader dashboard command") {
|
||||
t.Fatalf("expected status help to explain dashboard role, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected status help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchCleanupHelpExplainsScopeFlags(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeOrchCommand("cleanup", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(strings.ToLower(combined), "pass --task plus --attempt when you want one exact attempt") {
|
||||
t.Fatalf("expected cleanup help to explain scope flags, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected cleanup help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "--task T1 --attempt 2") {
|
||||
t.Fatalf("expected cleanup help to include exact-attempt example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,12 @@ func newReadyCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "ready",
|
||||
Short: "List tasks that are ready for dispatch",
|
||||
Long: helpLong(
|
||||
"Use ready to list tasks whose dependencies are already satisfied and can be dispatched now.",
|
||||
"ready is a queue-inspection command only; it does not create attempts or threads.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db ready --run blog_mvp_001
|
||||
orch --db .agents/coord.db ready --run blog_mvp_001 --limit 5`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -22,6 +22,12 @@ func newReassignCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "reassign",
|
||||
Short: "Reassign a blocked or failed task to another worker",
|
||||
Long: helpLong(
|
||||
"Use reassign to cancel the current active attempt and create a new attempt for another worker.",
|
||||
"Like retry, reassign preserves the prior execution contract: analysis attempts stay analysis-only, and code attempts receive a fresh worktree.",
|
||||
"Use reassign when the worker target should change, not just when the same worker should try again.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db reassign --run blog_mvp_001 --task T3 --to worker-b --reason "Worker-a is blocked on a missing dependency."`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -19,6 +19,12 @@ func newReconcileCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "reconcile",
|
||||
Short: "Reconcile inbox thread state back into orch task state",
|
||||
Long: helpLong(
|
||||
"Use reconcile to fold inbox thread state back into orch task and run state.",
|
||||
"reconcile is the bridge from worker-side inbox activity to leader-side scheduler state.",
|
||||
"Call reconcile before making new dispatch, retry, or status decisions when you need fresh state.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db reconcile --run blog_mvp_001`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -23,6 +23,12 @@ func newRetryCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "retry",
|
||||
Short: "Retry a failed task by creating a new attempt",
|
||||
Long: helpLong(
|
||||
"Use retry to create a fresh attempt and inbox thread for one failed task.",
|
||||
"If the previous attempt used a worktree, retry provisions a new worktree from the recorded workspace contract.",
|
||||
"If the previous attempt was analysis-only, retry stays analysis-only.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db retry --run blog_mvp_001 --task T7 --to backend-worker --body "Retry after fixing the contract mismatch."`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
body, err := resolveBodyValue(opts.body, opts.bodyFile)
|
||||
if err != nil {
|
||||
|
||||
@@ -13,8 +13,19 @@ func NewRootCmd() *cobra.Command {
|
||||
opts := &rootOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "orch",
|
||||
Short: "Leader-facing scheduler and control plane",
|
||||
Use: "orch",
|
||||
Short: "Leader-facing scheduler and control plane",
|
||||
Long: helpLong(
|
||||
"Use orch to manage leader-side scheduling for runs, tasks, dependencies, dispatch, retries, reassignment, blocked-task answers, and worktree-backed code attempts.",
|
||||
"orch is the control plane; it creates durable handoff state in inbox but does not launch workers by itself.",
|
||||
"After dispatch, a separate worker runtime or worker agent should claim the assigned inbox thread.",
|
||||
"Use execution-mode analysis for thread-only work and execution-mode code for worktree-backed repository changes.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db run init --run blog_mvp_001 --goal "Build blog MVP"
|
||||
orch --db .agents/coord.db task add --run blog_mvp_001 --task T1 --title "Summarize flaky tests" --default-to qa-worker
|
||||
orch --db .agents/coord.db ready --run blog_mvp_001
|
||||
orch --db .agents/coord.db dispatch --run blog_mvp_001 --task T1 --execution-mode analysis --to qa-worker --body "Read the latest failures."
|
||||
orch --db .agents/coord.db status --run blog_mvp_001`,
|
||||
SilenceErrors: true,
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ func newRunCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run management commands",
|
||||
Long: helpLong(
|
||||
"Use run commands to create or inspect one orchestration run.",
|
||||
"A run is the durable container for tasks, dependencies, attempts, and events for one user request or project slice.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db run init --run blog_mvp_001 --goal "Build blog MVP"
|
||||
orch --db .agents/coord.db run show --run blog_mvp_001`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(newRunInitCmd(root))
|
||||
@@ -37,6 +43,12 @@ func newRunInitCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Create a new orchestration run",
|
||||
Long: helpLong(
|
||||
"Use run init to create one durable run that will own tasks, dependencies, attempts, and events for a single user request or project slice.",
|
||||
"run IDs should be stable within the database path and are later reused by every other orch command.",
|
||||
"Create the run before adding tasks, dependencies, or dispatching work.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db run init --run blog_mvp_001 --goal "Build blog MVP" --summary "Public blog plus admin CRUD"`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
@@ -86,6 +98,11 @@ func newRunShowCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show run metadata and aggregate state",
|
||||
Long: helpLong(
|
||||
"Use run show to inspect the lightweight run view with run metadata and aggregate task counts.",
|
||||
"Prefer status when you need the full task list and latest attempt/message context.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db run show --run blog_mvp_001`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -19,6 +19,13 @@ func newStatusCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show task state summary for the run",
|
||||
Long: helpLong(
|
||||
"Use status to show the full operational view for one run.",
|
||||
"status reconciles inbox state first, then returns the run summary, aggregate task counts, the task list, and latest attempt/message context.",
|
||||
"Use status as the main leader dashboard command. Prefer it over run show when you need the latest task-level execution picture before making the next dispatch, answer, retry, or cleanup decision.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db status --run blog_mvp_001
|
||||
orch --db .agents/coord.db --json status --run blog_mvp_001`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ func newTaskCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "task",
|
||||
Short: "Task management commands",
|
||||
Long: helpLong(
|
||||
"Use task commands to define schedulable work inside one run.",
|
||||
"Tasks should be small enough to inspect, dispatch, retry, and reconcile independently.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db task add --run blog_mvp_001 --task T1 --title "Implement backend" --default-to backend-worker`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(newTaskAddCmd(root))
|
||||
@@ -35,6 +40,12 @@ func newTaskAddCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "Add a task to a run",
|
||||
Long: helpLong(
|
||||
"Use task add to register one schedulable task inside a run.",
|
||||
"Tasks may include a default worker target, priority, and optional acceptance JSON that downstream tooling can inspect.",
|
||||
"A task must belong to an existing run before it can become ready or be dispatched.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db task add --run blog_mvp_001 --task T1 --title "Implement backend" --summary "Ship the first API slice" --default-to backend-worker --priority high`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -24,6 +24,12 @@ func newWaitCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "wait",
|
||||
Short: "Block until matching run-scoped task events become available",
|
||||
Long: helpLong(
|
||||
"Use wait as the leader-side blocking primitive.",
|
||||
"Instead of polling with manual sleep loops, wait blocks until later matching task events exist for the run, such as ready, blocked, done, or failed.",
|
||||
"Use --after-event when resuming from a known cursor so you do not reprocess earlier events.",
|
||||
),
|
||||
Example: ` orch --db .agents/coord.db wait --run blog_mvp_001 --for task_blocked,task_done --after-event 0 --timeout-seconds 900`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -30,6 +31,21 @@ func Execute(args []string, stdout, stderr io.Writer) int {
|
||||
usage()
|
||||
return 2
|
||||
}
|
||||
if args[0] == "help" {
|
||||
if len(args) == 1 {
|
||||
usage()
|
||||
return 0
|
||||
}
|
||||
if err := runCommand([]string{args[1], "--help"}); err != nil {
|
||||
_, _ = fmt.Fprintln(commandStderr, err)
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
if isHelpToken(args[0]) {
|
||||
usage()
|
||||
return 0
|
||||
}
|
||||
|
||||
if err := runCommand(args); err != nil {
|
||||
_, _ = fmt.Fprintln(commandStderr, err)
|
||||
@@ -39,6 +55,15 @@ func Execute(args []string, stdout, stderr io.Writer) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
func isHelpToken(value string) bool {
|
||||
switch strings.TrimSpace(value) {
|
||||
case "-h", "--help":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func runCommand(args []string) error {
|
||||
switch args[0] {
|
||||
case "init":
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRepoMemoryRootHelpShowsWorkflowAndCommands(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeRepoMemoryCommand("--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "Store durable repository knowledge in SQLite") {
|
||||
t.Fatalf("expected root help to explain purpose, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected root help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "repo-memory verify --db ~/.codex/data/repo-memory.db --repo /path/to/repo") {
|
||||
t.Fatalf("expected root help to include workflow example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoMemoryCommandHelpWorksThroughHelpSubcommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeRepoMemoryCommand("help", "add")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "Insert or update one durable knowledge entry") {
|
||||
t.Fatalf("expected add help summary, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "Constraints:") {
|
||||
t.Fatalf("expected add help to include constraints section, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, "--kind") || !strings.Contains(combined, "--dep") {
|
||||
t.Fatalf("expected add help to print flags, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoMemoryCommandHelpWorksWithDashHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stdout, stderr, exitCode := executeRepoMemoryCommand("search", "--help")
|
||||
if exitCode != 0 {
|
||||
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
|
||||
}
|
||||
|
||||
combined := stdout + stderr
|
||||
if !strings.Contains(combined, "Search stored repository knowledge before a deeper code dive") {
|
||||
t.Fatalf("expected search help summary, got:\n%s", combined)
|
||||
}
|
||||
if !strings.Contains(combined, `--query "actionCode fill"`) {
|
||||
t.Fatalf("expected search help example, got:\n%s", combined)
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -32,8 +33,19 @@ func (s *stringSliceFlag) Set(value string) error {
|
||||
func runInit(args []string) error {
|
||||
fs := flag.NewFlagSet("init", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "init",
|
||||
"Create or migrate the SQLite schema for one repo-memory database.",
|
||||
[]string{
|
||||
"Run init once before first real use on a new database path.",
|
||||
"init is safe to rerun when you need to ensure the schema exists before another command.",
|
||||
},
|
||||
`repo-memory init --db ~/.codex/data/repo-memory.db`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -54,10 +66,21 @@ func runInit(args []string) error {
|
||||
func runIngest(args []string) error {
|
||||
fs := flag.NewFlagSet("ingest", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "ingest",
|
||||
"Scan markdown knowledge under one repository and import it into repo-memory.",
|
||||
[]string{
|
||||
"ingest expects a repository root and scans markdown under the configured relative path.",
|
||||
"Use ingest for curated docs; use add when you want to record one specific fact manually.",
|
||||
},
|
||||
`repo-memory ingest --db ~/.codex/data/repo-memory.db --repo /path/to/repo --path docs/ai`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
repoPath := fs.String("repo", "", "Repository root")
|
||||
scanPath := fs.String("path", "docs/ai", "Relative path under repo to scan for markdown")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(*repoPath) == "" {
|
||||
@@ -120,6 +143,14 @@ func runIngest(args []string) error {
|
||||
func runAdd(args []string) error {
|
||||
fs := flag.NewFlagSet("add", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "add",
|
||||
"Insert or update one durable knowledge entry with evidence, aliases, and dependencies.",
|
||||
[]string{
|
||||
"add requires a repository root because entries are anchored to one repo.",
|
||||
"Prefer confirmed only for well-supported facts; use dependencies and evidence fields whenever possible.",
|
||||
},
|
||||
`repo-memory add --db ~/.codex/data/repo-memory.db --repo /path/to/repo --kind term --key AITask --summary "Plan task model" --source-path app/AITask.java --source-line 42 --status confirmed --alias "AI Task" --dep file:app/AITask.java:hard`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
repoPath := fs.String("repo", "", "Repository root")
|
||||
kind := fs.String("kind", "", "Knowledge kind, e.g. term|chain|danger")
|
||||
@@ -139,6 +170,9 @@ func runAdd(args []string) error {
|
||||
fs.Var(&aliases, "alias", "Alias for this entry (repeatable)")
|
||||
fs.Var(&deps, "dep", "Dependency in type:locator[:hard|soft] format (repeatable)")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(*repoPath) == "" {
|
||||
@@ -202,11 +236,22 @@ func runAdd(args []string) error {
|
||||
func runSearch(args []string) error {
|
||||
fs := flag.NewFlagSet("search", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "search",
|
||||
"Search stored repository knowledge before a deeper code dive.",
|
||||
[]string{
|
||||
"search requires a non-empty query string.",
|
||||
"Use repo filtering when you want to narrow results to one repository name or path fragment.",
|
||||
},
|
||||
`repo-memory search --db ~/.codex/data/repo-memory.db --repo zeus --query "actionCode fill" --limit 10`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
query := fs.String("query", "", "Search query")
|
||||
repo := fs.String("repo", "", "Optional repo path filter (substring match)")
|
||||
limit := fs.Int("limit", 10, "Result limit")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(*query) == "" {
|
||||
@@ -243,8 +288,18 @@ func runSearch(args []string) error {
|
||||
func runRepos(args []string) error {
|
||||
fs := flag.NewFlagSet("repos", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "repos",
|
||||
"List repositories currently tracked in one repo-memory database.",
|
||||
[]string{
|
||||
"repos reads only from the memory database; it does not rescan the filesystem.",
|
||||
},
|
||||
`repo-memory repos --db ~/.codex/data/repo-memory.db`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -272,12 +327,23 @@ func runRepos(args []string) error {
|
||||
func runList(args []string) error {
|
||||
fs := flag.NewFlagSet("list", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "list",
|
||||
"List entries with optional repo, kind, and status filters.",
|
||||
[]string{
|
||||
"list is for broad inspection; use search when you need ranked text matching.",
|
||||
"Filters are optional and can be combined to narrow the result set.",
|
||||
},
|
||||
`repo-memory list --db ~/.codex/data/repo-memory.db --repo zeus --kind term --status confirmed --limit 20`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
repo := fs.String("repo", "", "Optional repo path filter (substring match)")
|
||||
kind := fs.String("kind", "", "Optional knowledge kind filter")
|
||||
status := fs.String("status", "", "Optional status filter")
|
||||
limit := fs.Int("limit", 20, "Result limit")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -311,6 +377,14 @@ func runList(args []string) error {
|
||||
func runEvents(args []string) error {
|
||||
fs := flag.NewFlagSet("events", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "events",
|
||||
"Show verification and status-change history for one entry.",
|
||||
[]string{
|
||||
"Resolve the entry either by --id or by the combination of --repo, --kind, and --key.",
|
||||
"events is history-only; it does not modify entries.",
|
||||
},
|
||||
`repo-memory events --db ~/.codex/data/repo-memory.db --id 1`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
id := fs.Int64("id", 0, "Entry id")
|
||||
repo := fs.String("repo", "", "Repo root when resolving by kind/key")
|
||||
@@ -318,6 +392,9 @@ func runEvents(args []string) error {
|
||||
key := fs.String("key", "", "Knowledge key when resolving by kind/key")
|
||||
limit := fs.Int("limit", 20, "Result limit")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -361,11 +438,22 @@ func runEvents(args []string) error {
|
||||
func runLink(args []string) error {
|
||||
fs := flag.NewFlagSet("link", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "link",
|
||||
"Create a relationship edge between two stored entries.",
|
||||
[]string{
|
||||
"link expects two existing entry IDs.",
|
||||
"Use links for durable relationships such as related_to, depends_on, or implements.",
|
||||
},
|
||||
`repo-memory link --db ~/.codex/data/repo-memory.db --from-id 1 --to-id 2 --relation related_to`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
fromID := fs.Int64("from-id", 0, "From entry id")
|
||||
toID := fs.Int64("to-id", 0, "To entry id")
|
||||
relation := fs.String("relation", "", "Link relation")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -386,9 +474,20 @@ func runLink(args []string) error {
|
||||
func runVerify(args []string) error {
|
||||
fs := flag.NewFlagSet("verify", flag.ContinueOnError)
|
||||
fs.SetOutput(commandStderr)
|
||||
setCommandUsage(fs, "verify",
|
||||
"Re-check stored entries against current repository state and downgrade stale knowledge.",
|
||||
[]string{
|
||||
"verify uses current git state to detect changed or missing hard dependencies.",
|
||||
"Pass --repo to verify one repository; omit it to verify every tracked repository in the database.",
|
||||
},
|
||||
`repo-memory verify --db ~/.codex/data/repo-memory.db --repo /path/to/repo`,
|
||||
)
|
||||
dbPath := fs.String("db", "repo-memory.db", "SQLite database path")
|
||||
repo := fs.String("repo", "", "Optional repo root to verify; if omitted, verify all known repos")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
if errors.Is(err, flag.ErrHelp) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -468,18 +567,64 @@ func usage() {
|
||||
_, _ = fmt.Fprintf(commandStderr, `repo-memory: repo memory CLI
|
||||
|
||||
Usage:
|
||||
repo-memory init --db repo-memory.db
|
||||
repo-memory add --db repo-memory.db --repo /path/to/repo --kind term --key AITask --summary "..."
|
||||
repo-memory ingest --db repo-memory.db --repo /path/to/repo [--path docs/ai]
|
||||
repo-memory search --db repo-memory.db --query "actionCode fill" [--repo zeus]
|
||||
repo-memory list --db repo-memory.db [--repo zeus] [--kind term] [--status confirmed]
|
||||
repo-memory events --db repo-memory.db --id 1
|
||||
repo-memory link --db repo-memory.db --from-id 1 --to-id 2 --relation related_to
|
||||
repo-memory verify --db repo-memory.db [--repo /path/to/repo]
|
||||
repo-memory repos --db repo-memory.db
|
||||
repo-memory <command> [flags]
|
||||
|
||||
Purpose:
|
||||
Store durable repository knowledge in SQLite so agents can search prior findings,
|
||||
ingest curated docs, add confirmed facts, inspect history, and verify stale entries.
|
||||
|
||||
Constraints:
|
||||
- Write durable repository knowledge, not short-lived chat conclusions.
|
||||
- Prefer search before add so repeated work starts from existing knowledge.
|
||||
- Use verify when code has moved enough that stored entries may be stale.
|
||||
|
||||
Commands:
|
||||
init Initialize or migrate one SQLite database
|
||||
ingest Import markdown knowledge from docs under one repo
|
||||
add Insert or update one durable knowledge entry
|
||||
search Search stored knowledge by query text
|
||||
list List entries with optional filters
|
||||
events Show history for one entry
|
||||
link Link two entries together
|
||||
verify Re-check entries against current repo state
|
||||
repos List tracked repositories
|
||||
|
||||
Examples:
|
||||
repo-memory init --db ~/.codex/data/repo-memory.db
|
||||
repo-memory search --db ~/.codex/data/repo-memory.db --repo zeus --query "router auth"
|
||||
repo-memory add --db ~/.codex/data/repo-memory.db --repo /path/to/repo --kind term --key AuthRouter --summary "..."
|
||||
repo-memory verify --db ~/.codex/data/repo-memory.db --repo /path/to/repo
|
||||
|
||||
More help:
|
||||
repo-memory --help
|
||||
repo-memory help add
|
||||
repo-memory add --help
|
||||
`)
|
||||
}
|
||||
|
||||
func setCommandUsage(fs *flag.FlagSet, name, summary string, constraints []string, example string) {
|
||||
fs.Usage = func() {
|
||||
_, _ = fmt.Fprintf(commandStderr, "repo-memory %s\n\n", name)
|
||||
_, _ = fmt.Fprintf(commandStderr, "Usage:\n repo-memory %s [flags]\n\n", name)
|
||||
_, _ = fmt.Fprintf(commandStderr, "Purpose:\n %s\n\n", summary)
|
||||
if len(constraints) > 0 {
|
||||
_, _ = fmt.Fprintln(commandStderr, "Constraints:")
|
||||
for _, constraint := range constraints {
|
||||
if strings.TrimSpace(constraint) == "" {
|
||||
continue
|
||||
}
|
||||
_, _ = fmt.Fprintf(commandStderr, " - %s\n", constraint)
|
||||
}
|
||||
_, _ = fmt.Fprintln(commandStderr)
|
||||
}
|
||||
if strings.TrimSpace(example) != "" {
|
||||
_, _ = fmt.Fprintf(commandStderr, "Example:\n %s\n\n", example)
|
||||
}
|
||||
_, _ = fmt.Fprintln(commandStderr, "Flags:")
|
||||
fs.PrintDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
type gitState struct {
|
||||
branch string
|
||||
commit string
|
||||
|
||||
@@ -12,6 +12,7 @@ Use the bundled `./assets/inbox` CLI to communicate through a durable SQLite-bac
|
||||
- Invoke `./assets/inbox` relative to this skill directory.
|
||||
- Pass `--db` explicitly for every command.
|
||||
- Prefer `--json` whenever another agent or script will read the output.
|
||||
- The bundled CLI help is intended to be self-describing; start with `./assets/inbox --help` or `./assets/inbox <command> --help` when you need command usage.
|
||||
- Run `init` before first use on a new database path.
|
||||
|
||||
## Rules
|
||||
|
||||
Binary file not shown.
@@ -31,6 +31,7 @@ Use the bundled `./assets/orch` CLI to control leader-side orchestration through
|
||||
- Invoke `./assets/orch` relative to this skill directory.
|
||||
- Pass `--db` explicitly for every command.
|
||||
- Prefer `--json` whenever another agent or script will read the output.
|
||||
- The bundled CLI help is intended to be self-describing; start with `./assets/orch --help` or `./assets/orch <command> --help` when you need command usage.
|
||||
- Initialize a new database path once through the bundled `inbox init` command before the first real run.
|
||||
- Use `status` as the main operational view. It reconciles worker thread state first, then returns run, task, latest-attempt, and latest-message context.
|
||||
- For Codex worker launch, standardize the handoff through `./assets/orch-worker-brief` instead of improvising the worker prompt every time.
|
||||
|
||||
Binary file not shown.
@@ -11,6 +11,7 @@ Use the bundled `./assets/repo-memory` CLI to maintain and query durable reposit
|
||||
|
||||
- Invoke `./assets/repo-memory` relative to this skill directory.
|
||||
- Default database path: `~/.codex/data/repo-memory.db`.
|
||||
- The bundled CLI help is intended to be self-describing; start with `./assets/repo-memory --help`, `./assets/repo-memory help <command>`, or `./assets/repo-memory <command> --help`.
|
||||
- Prefer searching before a deep repo dive so repeated work starts from existing knowledge.
|
||||
- Run `init` before first use on a new database path.
|
||||
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user