cli: make bundled help self-describing

This commit is contained in:
2026-03-22 23:37:38 +08:00
parent 5859ff219e
commit 4d8c90eb26
49 changed files with 792 additions and 29 deletions
@@ -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()
+13 -3
View File
@@ -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