@staix/agent-hub 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +66 -3
- package/README.md +2 -2
- package/docs/agent-notes/README.md +11 -0
- package/docs/agent-notes/adapters.md +7 -3
- package/docs/agent-notes/benchmarks.md +27 -0
- package/docs/agent-notes/budget.md +2 -1
- package/docs/agent-notes/bus.md +3 -3
- package/docs/agent-notes/daemon.md +17 -8
- package/docs/agent-notes/local-worker.md +4 -0
- package/docs/agent-notes/models.md +2 -0
- package/docs/agent-notes/tasks.md +17 -9
- package/docs/agent-notes/tests.md +9 -2
- package/docs/bench.md +1 -1
- package/docs/data-policy.md +2 -0
- package/docs/dogfood.md +39 -4
- package/docs/events.md +2 -1
- package/docs/memory.md +9 -3
- package/docs/operations.md +610 -62
- package/docs/profiles.md +13 -1
- package/docs/quickstart.md +18 -9
- package/docs/research.md +7 -4
- package/docs/sdlc.md +54 -10
- package/docs/security.md +396 -34
- package/docs/smoke.md +133 -0
- package/docs/specs/2026-09-19-agent-hub-design.md +1101 -22
- package/docs/verification/2026-10-27-0.19.0.md +5 -5
- package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-bash-permission.json +635 -0
- package/docs/verification/2026-10-28-claude-2.1.311-2.1.312-permission-transport.json +403 -0
- package/docs/verification/2026-10-28-claude-implementer-permission-triage.md +123 -0
- package/docs/verification/2026-10-28-jcode-0.99.1-usage.md +41 -0
- package/docs/verification/2026-10-28-realpath-name-audit.md +222 -0
- package/docs/verification/2026-10-28-routing-f0bb8ad0.md +1000 -0
- package/docs/verification/2026-10-29-0.21.0.md +61 -0
- package/docs/verification/2026-10-29-model-effort-capabilities.md +123 -0
- package/docs/verification/2026-10-29-modelrouting-b152ddb9.md +953 -0
- package/docs/verification/2026-10-30-0.22.0.md +49 -0
- package/docs/verification/2026-10-30-445-capability-probes.md +172 -0
- package/docs/verification/2026-10-30-bus-delivery-operational-settings.md +77 -0
- package/docs/verification/2026-10-30-jcode-ac4.md +24 -0
- package/docs/verification/2026-10-30-jcode-ac6.md +52 -0
- package/docs/verification/2026-10-30-recovery-wait-settings.md +141 -0
- package/docs/verification/claude-implementer-permission-triage.ts +160 -0
- package/docs/verification/claude-permission-transport-native.py +71 -14
- package/docs/verification/profiles/YYYY-MM-DD-research.md +1 -1
- package/docs/verified.json +15 -15
- package/package.json +2 -2
- package/plugins/agent-hub/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-hub/server.js +1016 -280
- package/src/adapters/acp.ts +79 -3
- package/src/adapters/channel-peer.ts +4 -0
- package/src/adapters/claude-channel.ts +13 -6
- package/src/adapters/claude-implementer.ts +60 -7
- package/src/adapters/codex-appserver.ts +77 -4
- package/src/adapters/conformance/fakes/jcode-agent.ts +6 -2
- package/src/adapters/conformance/kit.ts +3 -9
- package/src/adapters/definitions/builtin-metadata.ts +3 -114
- package/src/adapters/definitions/native-config-defaults.ts +1 -1
- package/src/adapters/definitions/native-config-policy.ts +3 -2
- package/src/adapters/definitions/native-context.ts +12 -0
- package/src/adapters/external-metadata.ts +48 -0
- package/src/adapters/external-modules.ts +63 -3
- package/src/adapters/jcode.ts +6 -2
- package/src/adapters/local-worker.ts +40 -25
- package/src/adapters/native-proof-ports.ts +10 -0
- package/src/adapters/registry-types.ts +4 -0
- package/src/broker/protocol.ts +203 -0
- package/src/broker/server.ts +314 -0
- package/src/broker/store.ts +548 -0
- package/src/cli/bench.ts +3 -2
- package/src/cli/broker.ts +58 -0
- package/src/cli/conductor-recovery.ts +142 -0
- package/src/cli/configured-acp-entry.ts +26 -6
- package/src/cli/console-state.ts +59 -17
- package/src/cli/console.ts +17 -2
- package/src/cli/facts-hook.ts +3 -1
- package/src/cli/help.ts +29 -16
- package/src/cli/identity.ts +25 -10
- package/src/cli/init.ts +34 -7
- package/src/cli/intake-output.ts +33 -0
- package/src/cli/learn-output.ts +38 -0
- package/src/cli/main.ts +334 -73
- package/src/cli/output.ts +41 -1
- package/src/cli/recovery-package.ts +18 -1
- package/src/cli/recovery-waits.ts +26 -0
- package/src/cli/role-holders.ts +35 -0
- package/src/cli/room.ts +304 -0
- package/src/cli/say-input.ts +16 -0
- package/src/cli/schedule-output.ts +49 -0
- package/src/cli/sdlc-output.ts +44 -6
- package/src/cli/smoke-pty-bridge.ts +82 -0
- package/src/cli/smoke-pty.ts +153 -0
- package/src/cli/smoke-run.ts +139 -0
- package/src/cli/smoke-target.ts +130 -0
- package/src/cli/source-output.ts +29 -0
- package/src/cli/task-output.ts +3 -2
- package/src/cli/upgrade-runtime.ts +112 -28
- package/src/cli/upgrade.ts +68 -8
- package/src/hub/ask.ts +76 -12
- package/src/hub/board.ts +132 -22
- package/src/hub/bus.ts +416 -65
- package/src/hub/child-process.ts +2 -0
- package/src/hub/ci-watch.ts +42 -20
- package/src/hub/claude-failure-stage.ts +85 -0
- package/src/hub/claude-shell-wrapper.ts +11 -5
- package/src/hub/conductor.ts +17 -4
- package/src/hub/config-apply.ts +34 -6
- package/src/hub/config-reload.ts +7 -5
- package/src/hub/config-trust.ts +7 -1
- package/src/hub/context-window.ts +33 -3
- package/src/hub/control-client.ts +14 -2
- package/src/hub/corpus-config.ts +34 -0
- package/src/hub/corpus-evidence.ts +184 -0
- package/src/hub/corpus.ts +373 -0
- package/src/hub/cost-governance.ts +26 -0
- package/src/hub/daemon.ts +1209 -183
- package/src/hub/deliverable-files.ts +8 -2
- package/src/hub/delivery-journal.ts +200 -73
- package/src/hub/dogfood-profile.ts +32 -5
- package/src/hub/dogfood-read.ts +6 -2
- package/src/hub/envelope.ts +11 -0
- package/src/hub/eval-gate.ts +131 -0
- package/src/hub/events.ts +1 -1
- package/src/hub/external-instance.ts +69 -0
- package/src/hub/facts.ts +15 -5
- package/src/hub/folder-trigger.ts +223 -0
- package/src/hub/hub-tools.ts +2 -1
- package/src/hub/inference.ts +20 -5
- package/src/hub/instance-config.ts +69 -3
- package/src/hub/instance-execution.ts +6 -2
- package/src/hub/instances.ts +32 -4
- package/src/hub/intake.ts +125 -0
- package/src/hub/learning.ts +117 -0
- package/src/hub/lifecycle.ts +7 -3
- package/src/hub/limits.ts +12 -5
- package/src/hub/native-permission-request.ts +77 -0
- package/src/hub/operational-authority.ts +63 -5
- package/src/hub/operational-settings.ts +154 -0
- package/src/hub/operational-view.ts +1 -1
- package/src/hub/packs.ts +132 -0
- package/src/hub/path-policy.ts +7 -0
- package/src/hub/peer-doing.ts +130 -0
- package/src/hub/peers.ts +12 -0
- package/src/hub/queue-backpressure.ts +58 -0
- package/src/hub/read-tools.ts +2 -0
- package/src/hub/recovery-authority.ts +151 -0
- package/src/hub/recovery-policy.ts +220 -0
- package/src/hub/research.ts +24 -14
- package/src/hub/restart.ts +7 -0
- package/src/hub/routing.ts +54 -11
- package/src/hub/schedules.ts +266 -0
- package/src/hub/sdlc-merge.ts +38 -14
- package/src/hub/sdlc-metrics.ts +32 -10
- package/src/hub/sdlc-queue.ts +7 -1
- package/src/hub/sdlc-release.ts +1 -1
- package/src/hub/sdlc.ts +37 -7
- package/src/hub/security-baseline.ts +38 -0
- package/src/hub/settings.ts +27 -2
- package/src/hub/shipped-packs.ts +8 -0
- package/src/hub/snapshots.ts +72 -6
- package/src/hub/source-read.ts +160 -0
- package/src/hub/sources.ts +311 -0
- package/src/hub/tasks.ts +987 -110
- package/src/hub/workflow.ts +1 -1
- package/src/local/doc-read.ts +69 -0
- package/src/local/proxy.ts +4 -13
- package/src/local/tools.ts +37 -3
- package/src/local/web-fetch-config.ts +43 -0
- package/src/local/web-fetch.ts +183 -0
- package/src/memory/brief.ts +85 -13
- package/src/memory/capture.ts +1 -1
- package/src/rooms/application.ts +114 -0
- package/src/rooms/boundary.ts +103 -0
- package/src/rooms/commands.ts +294 -0
- package/src/rooms/crypto.ts +371 -0
- package/src/rooms/link.ts +584 -0
- package/src/rooms/policy.ts +82 -0
- package/src/rooms/remote-peer.ts +80 -0
- package/src/rooms/service.ts +426 -0
- package/src/rooms/status.ts +146 -0
- package/src/rooms/store.ts +1210 -0
- package/src/rooms/wire.ts +465 -0
- package/src/ui/index.html +91 -4
- package/templates/packs/example.pack.json +13 -0
package/src/cli/help.ts
CHANGED
|
@@ -11,7 +11,7 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
|
|
|
11
11
|
["ahub projects [--json] [--full] [--color=auto|always|never]", "list registered projects and live status"],
|
|
12
12
|
["ahub projects remove <id>", "forget a stopped registration (keeps project files)"],
|
|
13
13
|
["ahub setup [--yes]", "install or update the Claude Code channel plugin from this package, then run doctor"],
|
|
14
|
-
["ahub init [--profile dev|research|writing|general] [--shadow] [--release-guard] [--dry-run --json]", "preview or write .agenthub/config.json and the AGENTS.md marker block (drops a legacy CLAUDE.md block)"],
|
|
14
|
+
["ahub init [--profile dev|research|writing|general|<pack>] [--shadow] [--release-guard] [--dry-run --json]", "preview or write .agenthub/config.json and the AGENTS.md marker block (drops a legacy CLAUDE.md block); a pack is a project file .agenthub/roles/<name>.pack.json"],
|
|
15
15
|
["ahub --live-test [--project <path|id>] <command> [args...]", "user-authorized operational console delegation; ordinary calls retain peer identity; person-authored content refuses"],
|
|
16
16
|
["ahub help [command]", "this help, or one command's entries; also --help and -h"],
|
|
17
17
|
["ahub version | --version", "print the installed version"],
|
|
@@ -26,7 +26,7 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
|
|
|
26
26
|
["ahub pet --palette <blue|teal|violet|ochre>", "save the floating window's base color to pet.palette in .agenthub/config.local.json"],
|
|
27
27
|
["ahub sdlc status [task] | explain <task> [--json] [--color=auto|always|never] | export <task> | record <task> <receipt-json>", "read head-bound lifecycle gates and the next actor/action; status and explain are open to any agent as a public view (private tasks stay stubs), export and record are person only; export stays JSON"],
|
|
28
28
|
["ahub sdlc queue <owner/repo> | release <owner/repo> [live-issue...] [--json] [--color=auto|always|never]", "read-only merge and release evidence; open to any agent as public readback of the checkout's repositories, never an authorization"],
|
|
29
|
-
["ahub sdlc merge <task> [--dry-run]", "guarded squash merge of an approved task at its approved head after every gate is re-read; follows sdlc.toml auto_merge (off, dry-run, on); --dry-run only reports (person or conductor)"],
|
|
29
|
+
["ahub sdlc merge <task> [--dry-run] [--json] [--color=auto|always|never]", "guarded squash merge of an approved task at its approved head after every gate is re-read; follows sdlc.toml auto_merge (off, dry-run, on); --dry-run only reports (person or conductor)"],
|
|
30
30
|
["ahub sdlc authorize-release <version> [--json] [--color=auto|always|never]", "approve this daemon checkout commit, version and fresh docs (person only; restart revokes)"],
|
|
31
31
|
["ahub sdlc release-guard -- <literal git push|gh release create|npm publish argv>", "read-only authorization preflight; init --release-guard installs the tag pre-push hook"],
|
|
32
32
|
["ahub sdlc release-guard-hook <remote-name> <remote-url>", "internal pre-push readback; validates version tags and forwards exact stdin on success"],
|
|
@@ -47,18 +47,19 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
|
|
|
47
47
|
["ahub agents [--json]", "show each gated agent CLI's installed version and verdict (verified, in-line, trusted, refused or unknown)"],
|
|
48
48
|
["ahub agents trust <id> <version> [--revoke]", "accept an untested agent CLI version on this machine (person only; runtime proofs still run)"],
|
|
49
49
|
["ahub kill", "stop this project's hub"],
|
|
50
|
+
["ahub down", "stop this project's hub, the counterpart of ahub up (same as ahub kill)"],
|
|
50
51
|
["ahub reset [--all] [--yes]", "list, then with --yes stop the hub and discard every queued and needs_review delivery, clear holds and pauses and drop session pointers; --all moves the state directory to .agenthub/archive instead"],
|
|
51
|
-
["ahub restart [--dry-run] [--yes] [--json] [--verbose] [--color=auto|always|never]", "recover this project's runtime;
|
|
52
|
-
["ahub upgrade [--to <version>] [--dry-run] [--yes] [--json] [--verbose] [--color=auto|always|never]", "review and upgrade running projects; --to defaults to
|
|
53
|
-
["ahub recovery [status|resume|abort] [<operation-id>] [--json] [--color=auto|always|never]", "inspect
|
|
52
|
+
["ahub restart [--dry-run] [--yes] [--json] [--verbose] [--color=auto|always|never]", "recover this project's runtime; a configured conductor's --yes requires a same-project, source-issued continuation proof, otherwise run it as the person"],
|
|
53
|
+
["ahub upgrade [--to <version>] [--dry-run] [--yes] [--json] [--verbose] [--color=auto|always|never]", "review and upgrade running projects; --to defaults to latest for interactive review or dry-run, while --yes requires --to <exact-version>; conductor apply needs the same project's supported proof, and a newer-coordinator handoff is person-only"],
|
|
54
|
+
["ahub recovery [status|resume|abort] [<operation-id>] [--json] [--color=auto|always|never]", "inspect a preflight; without an id, resume/abort select the operation holding the lock; a bare terminal opens the person's screen; only its initiating conductor may continue its own supported restart/upgrade with fresh proof"],
|
|
54
55
|
["ahub recovery reattach <operation-id> codex|claude|pi", "person only: repair that blocked peer with its recorded session; leaves the rest for recovery resume"],
|
|
55
56
|
["ahub recovery dispose [<operation-id>] --fresh-session codex|claude --reason <text>", "end a partial operation by starting that peer as a new session, recording the lost conversation"],
|
|
56
57
|
["ahub recovery dispose [<operation-id>] --stop-and-archive --reason <text>", "end a partial operation: stop its own targets, archive its snapshot and release the lock; the upgrade is abandoned, not completed"],
|
|
57
58
|
["ahub daemon [--unattended]", "internal: the detached hub process that ahub up or the dashboard manager starts"],
|
|
58
|
-
["ahub recovery-run <operation-id>", "internal:
|
|
59
|
+
["ahub recovery-run <operation-id>", "internal: a detached runner; conductor use is limited to the captured operation continuation, not a general command"],
|
|
59
60
|
]],
|
|
60
61
|
["Agents", [
|
|
61
|
-
["ahub <configured-kind> [--mode headless] [--as <instance>] [--session-id <id>] [--print-command | --dry-run]", "start a trusted config.local.json ACP kind (default off roster); native argv is withheld in previews"],
|
|
62
|
+
["ahub <configured-kind> [--mode headless] [--as <instance>] [--session-id <id>] [--print-command | --dry-run]", "start a trusted config.local.json ACP kind (default off roster); native argv is withheld in previews; an external module instance needs the person's start"],
|
|
62
63
|
["ahub role list [--group <group>] | show <id>", "read bounded catalog guidance with layer and content hash"],
|
|
63
64
|
["ahub role assign <peer> <id> | clear <peer> [<id>]", "person only: add or clear catalog guidance, applied at the next peer start"],
|
|
64
65
|
["ahub stop <peer>", "person or configured conductor: stop a hub-owned headless peer; end TUI agents in their terminals"],
|
|
@@ -83,7 +84,7 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
|
|
|
83
84
|
["ahub queue discard --peer <id> --reason <text> [--state queued|needs_review] [--yes] [--json]", "person or configured conductor: without --yes count one peer's queued (default) deliveries; with --yes discard them all with the reason, one recorded discard each; other peers' queues are untouched"],
|
|
84
85
|
]],
|
|
85
86
|
["Tasks and review", [
|
|
86
|
-
["ahub board import github --label <label> [--milestone <milestone>] [--repo <owner/repo>]", "person
|
|
87
|
+
["ahub board import github --label <label> [--milestone <milestone>] [--repo <owner/repo>]", "person or configured conductor: read bounded open issues into deduplicated Backlog; never writes GitHub"],
|
|
87
88
|
["ahub board [state | --ready] [--columns] [--release <name> | --release-id <id>] [--json] [--full] [--color=auto|always|never]", "the task board, one line per task at a terminal width; --full: whole titles and SDLC advice; --ready: proposed tasks with nothing left to wait for"],
|
|
88
89
|
["ahub task propose [--class <c> | <class>] <title...> [--owner <peer>] [--path <p>]... [--after <id>]... [--urgent] [--backlog] [--detail <text>]", "put a task on the board; a first word that names a class is taken as the class; while a task named by --after is not approved yet, --owner reserves the task for that peer, offered to it first once it is ready; otherwise --owner assigns it now"],
|
|
89
90
|
["ahub backlog list [--json] [--full] [--color=auto|always|never] | add <class> <title...> | plan <id> <revision> <fields-json> <reason...> | pin <id> <revision> true|false <reason...> | undo <id> <revision> [--json] [--color=auto|always|never]", "person only: plan work with a reason; {} records a note-only revision, fields.sdlc sets backlog repo/issue identity and clears old spec acceptance; preserve pins/frozen scope and refuse stale edits; list is by rank (--json: raw listing)"],
|
|
@@ -91,13 +92,25 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
|
|
|
91
92
|
["ahub backlog split <id> <revision> <parts-json> <reason...> [--json] [--color=auto|always|never]", "person only: stage2..8 child prerequisites; original task retains integration, undo refuses used or changed children"],
|
|
92
93
|
["ahub backlog pull-ready <id> <revision> <reason...> [--json] [--color=auto|always|never]", "person only: reserve an eligible WIP slot before routing; pending unowned work can be undone"],
|
|
93
94
|
["ahub backlog pull|return <id>", "person only: pull backlog into To Do or return ownerless proposed work; --backlog on task propose stages unoffered work"],
|
|
94
|
-
["ahub
|
|
95
|
+
["ahub smoke run qwen-dual-output --project-id <id> --receipt <file.json> [--timeout-ms <100..120000>] [--json]", "requires the leading --live-test flag; disposable temporary project only: Q2 dual-file submit and queued second submit; isolated native HOME, owned-process cleanup and a counts/timings receipt; requires OPENAI_API_KEY, OPENAI_BASE_URL and OPENAI_MODEL; real-account live proof remains a separate person leg"],
|
|
96
|
+
["ahub broker serve [--hostname <host>] [--port <0..65535>] [--public-url <ws[s]://host/>] [--owner <fingerprint>]... [--state-dir <dir>]", "person only: standalone ciphertext relay; default loopback port 8788, public WSS needs owner allowlist and TLS termination"],
|
|
97
|
+
["ahub broker room drop <room> --yes [--state-dir <dir>]", "person only, broker stopped: forget one room's pins, floors and queued ciphertext; other rooms stay intact"],
|
|
98
|
+
["ahub room create <name> --broker <url> [--label <id>] | invite <name> [--accept <request>] | join <token> [--name <name>] [--label <id>] | join <name> --roster <signed-roster> | leave <name> | revoke <name> <fingerprint> | expose <name> [<peer>...] [--confirm <member-fingerprint-prefix>]", "person only, never --live-test: every mutation displays both fingerprints, then requires TTY y/N (default No) or --confirm matching 16..64 lowercase hex of the member fingerprint; exchange artifacts out of band, never invitation secrets through the broker"],
|
|
99
|
+
["ahub room resolve <name> --frame <digest> --action accept|discard [--confirm <member-fingerprint-prefix>]", "person only, never --live-test: dispose one uncertain incoming application after fingerprint confirmation; no republish or signed receipt"],
|
|
100
|
+
["ahub room list [--json] | status [<name>] [--json]", "read-only room ids, owner/member fingerprints, epochs and link states; agent shells may read; no message bodies or invitation secrets"],
|
|
101
|
+
["ahub room inbox [<name>] [--json] | inbox show <id> | inbox done|discard <id>", "person only, never --live-test: metadata list, one untrusted console body, or local body purge; completed (inbox) means admitted, never read"],
|
|
102
|
+
["ahub source add <path|url> | list [--json] [--color=auto|always|never]", "list is person only, add the person's or the configured conductor's: register an input as a content-hashed source (src:<16 hex of the SHA-256>) under the hub state dir sources/<id>/ (meta.json, raw, text); a URL is fetched once by this command, https only, at most 5 redirects; up to 16 MiB, never truncated; a path under pii.paths or text matching signals.pii_patterns is private, and private is never downgraded"],
|
|
103
|
+
["ahub schedule list | run <id> | pause <id> | resume <id> [--json] [--color=auto|always|never]", "list is person only; run, pause and resume are the person's or the configured conductor's: the machine-local schedules (`schedules` in .agenthub/config.local.json); list shows each one's next run, run proposes one now through the same hold, open-task and PII path as the timer and restarts its period, pause stops the timer proposing it without counting a skip, resume starts a fresh period so a pause is never replayed as a run"],
|
|
104
|
+
["ahub intake \"<text>\" [--source <id>...] | approve <id> | discard <id> [--json] [--color=auto|always|never]", "person only, except approve (person or configured conductor): turn a plain request into a draft backlog card that nothing offers or pulls until you approve it; the configured planner is asked to split it with hub_backlog_split, or the hub model proposes the split (a PII request only on an on-prem model, never to a planner); each --source must be a registered src: id; approve clears the draft on it and its parts, discard cancels them through task cancel"],
|
|
105
|
+
["ahub learn list [--all] | accept <id> | reject <id> [--json] [--color=auto|always|never]", "person only, never a conductor or an agent shell: review a recurring-unmet-criterion candidate the hub filed (#514a); accept writes its one review checklist line, added to your current checklist, to .agenthub/config.local.json through the settings writer (this machine only, never a tracked file, ahub settings undo puts it back, applies after a restart) and closes the card; reject only closes it"],
|
|
106
|
+
["ahub deliverables [--task <id>] [--approved] [--check] [--json] [--color=auto|always|never]", "what each task delivered and a reviewer approved, by id, hash and size (a path only where the PII screen allows); --check measures the files now and marks one changed since review; an agent sees only the tasks hub_task_show shows it"],
|
|
95
107
|
["ahub task show <id> [--json] [--full] [--color=auto|always|never] | escalate <id>", "full task with history (the person sees PII text, an agent the public view) / hand it to the next peer in escalate_to (person or configured conductor)"],
|
|
96
|
-
["ahub task role <id> <role|none>", "person
|
|
108
|
+
["ahub task role <id> <role|none>", "the person, or an agent on a task it owns: change bounded catalog guidance on an open task; routing and authority are unchanged"],
|
|
97
109
|
["ahub task assign <id> <peer> [--role owner|reviewer|planner] [--revision N]", "name the owner (default), reviewer or person-only planner; a task that still waits reserves its owner; a peer that cannot take it is refused with the reason; --revision refuses stale console actions"],
|
|
98
|
-
["ahub task unassign <id> [--role owner|reviewer] [--revision N]", "person
|
|
99
|
-
["ahub task
|
|
100
|
-
["ahub task
|
|
110
|
+
["ahub task unassign <id> [--role owner|reviewer] [--revision N]", "person or configured conductor: remove the owner/reservation or reviewer without routing a replacement or stopping native work; preserves the actual actor"],
|
|
111
|
+
["ahub task claim <id> <slot> <supported|unsupported|unclear> [--model <label>] [--model-said <supported|unsupported|unclear>]", "person only: record your verdict on one declared plan claim (by its 1-based slot, never its text), with the model label and what the model said, so the research scorecard can show agreement; a later verdict for the slot replaces the earlier one"],
|
|
112
|
+
["ahub task cancel <id> --reason <text> [--superseded-by <ref>] [--revision N] [--json] [--color=auto|always|never]", "the person, or an agent on its own task as hub_task_cancel allows: close obsolete work with a reason; retain its history without stopping native work or releasing dependents; eligibility follows the task-board policy"],
|
|
113
|
+
["ahub task label <id> ok|regressed|reverted|incomplete|wrong|abandoned [--created <time>]", "your later verdict on an approved task, kept in the research records (research must be on); an agent labels only a task it owns, and its record names it; --created is the person's"],
|
|
101
114
|
["ahub review <id> approved|changes_requested [note...] [--unmet <item>]... [--head <sha>] [--findings <json>]", "give a review verdict on a task; the person can do the same in ahub console: Tasks, select the task, r, then a (approve) or c (request changes, with a note)"],
|
|
102
115
|
["ahub review brief <id>", "print the review brief of a task's commit: reviewable and excluded files, rules, coverage to account for"],
|
|
103
116
|
["ahub route explain <id>", "why a task went where it went, its reserved owner and the planner walk included"],
|
|
@@ -124,8 +137,8 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
|
|
|
124
137
|
["ahub research [--since 30d] [--all] [--json]", "success, first-pass, rework and check-failure rates, tokens and wall time per approved task, from the opt-in research records"],
|
|
125
138
|
["ahub research addons [--since 30d] [--all] [--json]", "approved tasks with each add-on (claude-mem, ponytail, ripwire, ...) on against off, with intervals; association, not cause"],
|
|
126
139
|
["ahub research export [--format jsonl|csv] [--since 30d] [--all]", "the research records for outside analysis: ids, counts and tokens, never text"],
|
|
127
|
-
["ahub research backfill", "build this project's research records once from events.jsonl"],
|
|
128
|
-
["ahub bench run <suite.json> --arm <label> [--repeat N] [--tasks a,b] [--addon <name>=on|off]... [--memory off|claude-mem|native|both]", "run a benchmark suite against the peers attached here, in a project kept for benchmarks (it resets the work tree); --addon and --memory declare the arm's add-on and memory state and refuse a setup that differs"],
|
|
140
|
+
["ahub research backfill", "build this project's research records once from events.jsonl (person or configured conductor)"],
|
|
141
|
+
["ahub bench run <suite.json> --arm <label> [--repeat N] [--tasks a,b] [--addon <name>=on|off]... [--memory off|claude-mem|native|both]", "run a benchmark suite against the peers attached here, in a project kept for benchmarks (it resets the work tree; person or configured conductor); --addon and --memory declare the arm's add-on and memory state and refuse a setup that differs"],
|
|
129
142
|
["ahub bench list|status [--json]", "benchmark runs on this machine / the one running now"],
|
|
130
143
|
["ahub bench report <run> [--json]", "pass rate, first pass, rework, tokens and wall time per task and overall"],
|
|
131
144
|
["ahub bench compare <run|arm>... [--suite <name>] [--mixed] [--json]", "compare arms of one suite with 95% bootstrap intervals; inconclusive below 5 counted attempts per arm"],
|
|
@@ -139,7 +152,7 @@ const SECTIONS: [string, [usage: string, description: string][]][] = [
|
|
|
139
152
|
["ahub memory decay [--days N] [--json]", "person only: list long-term entries older than N days (default 90) that no open goal links; review only, nothing is changed"],
|
|
140
153
|
["ahub memory profile-check [--json]", "read-only check of the running hubs' memory bindings against their memory profiles; exits 1 when one is not ready"],
|
|
141
154
|
["ahub goal [list] [--all] | show <id> [--json]", "long-term tasks: state, which machine holds each and until when, linked board tasks"],
|
|
142
|
-
["ahub goal new <title> [--milestone <text>]... [--depends <id>]... [--shared] | link <id> <task> | state <id> <state> | done <id> | take <id> [--force]", "a person's changes to long-term tasks; take --force voids other machines' claims"],
|
|
155
|
+
["ahub goal new <title> [--milestone <text>]... [--depends <id>]... [--shared] | link <id> <task> | state <id> <state> | done <id> | take <id> [--force]", "a person's changes to long-term tasks (link also the configured conductor's); take --force voids other machines' claims"],
|
|
143
156
|
]],
|
|
144
157
|
["Hooks", [
|
|
145
158
|
["ahub check-path --hook", "check-path as a Claude Code PreToolUse hook (templates/claude-hooks.json); never blocks"],
|
package/src/cli/identity.ts
CHANGED
|
@@ -18,7 +18,8 @@ export function liveTestRefusal(command: string, args: readonly string[], contex
|
|
|
18
18
|
if (command === "sdlc" && (args[0] === "authorize-release" || args[0] === "plan-review" || args[0] === "release-assign" || args[0] === "merge" || args[0] === "train" && args[1] !== "report")) return true;
|
|
19
19
|
if (args.some(arg => arg === "--as-user" || arg.startsWith("--as-user="))) return true;
|
|
20
20
|
const sub = command === "recovery" ? recoveryAction(args) : args.find(arg => arg !== "--json");
|
|
21
|
-
return ["task", "role", "goal", "deliverables"].includes(command)
|
|
21
|
+
return ["task", "role", "goal", "deliverables", "source", "broker", "schedule", "intake"].includes(command)
|
|
22
|
+
|| (command === "room" && !["list", "status"].includes(sub ?? ""))
|
|
22
23
|
|| (command === "backlog" && sub !== "list") || (command === "release" && !["list", "show"].includes(sub ?? ""))
|
|
23
24
|
|| (command === "memory" && sub !== "graph")
|
|
24
25
|
|| (command === "recovery" && (sub === "reattach" || sub === "dispose" && context.repairedFresh === true))
|
|
@@ -73,6 +74,7 @@ interface PeerReadForm {
|
|
|
73
74
|
const publicReadDisplayFlags = ["--json", "--full", "--color=auto", "--color=always", "--color=never"] as const;
|
|
74
75
|
const sdlcReadDisplayFlags = ["--json", "--color=auto", "--color=always", "--color=never"] as const;
|
|
75
76
|
const peerReadForms: Readonly<Record<string, Readonly<Record<string, PeerReadForm>>>> = {
|
|
77
|
+
room: { list: { displayFlags: ["--json"] }, status: { positional: 1, displayFlags: ["--json"] } },
|
|
76
78
|
queue: { list: { valueFlags: ["--peer"], displayFlags: publicReadDisplayFlags } },
|
|
77
79
|
roster: { "": { displayFlags: publicReadDisplayFlags } },
|
|
78
80
|
settings: { "": { displayFlags: ["--json"] }, list: { displayFlags: ["--json"] }, get: { positional: 1, minimum: 1, displayFlags: ["--json"] } },
|
|
@@ -135,21 +137,23 @@ export function classifyPeerCommand(command: string, args: readonly string[], ca
|
|
|
135
137
|
if (command === "sdlc" && args[0] === "merge") return "conductor";
|
|
136
138
|
if (command === "role") return ["list", "show"].includes(args[0] ?? "") ? "allowed" : "console";
|
|
137
139
|
if (command === "task") {
|
|
138
|
-
|
|
140
|
+
// #933: cancel, label and role act on the caller's own task only; the daemon refuses another peer's task by role (Tasks.cancel, task_catalog_role, task_label_check).
|
|
141
|
+
if (["propose", "show", "cancel", "label", "role"].includes(args[0] ?? "")) return "allowed";
|
|
139
142
|
if (["assign", "escalate"].includes(args[0] ?? "")) return "conductor";
|
|
140
143
|
return "console";
|
|
141
144
|
}
|
|
142
|
-
// #
|
|
143
|
-
if (command === "deliverables") return "
|
|
145
|
+
// #933: an agent's deliverable list holds only the rows hub_task_show would show it, through the same public screening (#317).
|
|
146
|
+
if (command === "deliverables") return "allowed";
|
|
147
|
+
// #933 lists `route` beyond `explain` as conductor, but the command has no other form: anything else is a usage error and stays closed.
|
|
144
148
|
if (command === "route") return args[0] === "explain" ? "allowed" : "console";
|
|
145
149
|
// #691: reading agent CLI versions is harmless; trusting an untested version on this machine is a person's.
|
|
146
150
|
if (command === "agents") return args.every(arg => arg === "--json") ? "allowed" : "console";
|
|
147
|
-
// #247: reading the research measures and records is harmless (ids and counts only);
|
|
151
|
+
// #247: reading the research measures and records is harmless (ids and counts only); #933 made backfill a conductor operation (above).
|
|
148
152
|
if (command === "research") return args[0] === "backfill" ? "console" : "allowed";
|
|
149
|
-
// #258: reading hub memory is harmless; promote, demote, forget, supersede and
|
|
153
|
+
// #258: reading hub memory is harmless; promote, demote, forget, supersede, rebuild and goal changes stay a person's (#933: amem records their actor as "user"; goal link is a conductor operation, above).
|
|
150
154
|
if (command === "memory") { const sub = args.find((arg) => arg !== "--json"); return sub === undefined || sub.startsWith("--") || ["list", "search", "show", "suggestions"].includes(sub) || (sub === "machine" && !args.includes("--label")) ? "allowed" : "console"; } // graph labels are unscreened note text: a person's
|
|
151
155
|
if (command === "goal") { const sub = args.find((arg) => arg !== "--json"); return sub === undefined || sub.startsWith("--") || ["list", "show"].includes(sub) ? "allowed" : "console"; }
|
|
152
|
-
// #251: a run resets the bench project's tree and proposes work; reading results is harmless.
|
|
156
|
+
// #251: a run resets the bench project's tree and proposes work (#933: a conductor operation, above); reading results is harmless.
|
|
153
157
|
// The subcommand is the first argument that is not --json, which the command accepts anywhere.
|
|
154
158
|
if (command === "bench") return args.find((arg) => arg !== "--json") === "run" ? "console" : "allowed";
|
|
155
159
|
if (command === "budget") return !args.length || args.every((arg) => arg === "--json" || arg === "--full" || arg.startsWith("--color=")) ? "allowed" : "console";
|
|
@@ -163,7 +167,6 @@ export function classifyPeerCommand(command: string, args: readonly string[], ca
|
|
|
163
167
|
if ((command === "upgrade" || command === "restart") && !args.includes("--yes") && args.includes("--dry-run")
|
|
164
168
|
&& readFormArgs(args.filter((arg) => arg !== "--dry-run"), { valueFlags: ["--to"] })) return "allowed"; // a preview that applies nothing; --yes stays console
|
|
165
169
|
if (isPeerReadForm(command, args)) return "allowed"; // exact read forms; every mutating neighbour remains below its existing authority gate
|
|
166
|
-
if (native?.cli.authority === "person") return "console";
|
|
167
170
|
if (native || ["pause", "resume"].includes(command)) return "conductor";
|
|
168
171
|
if (command === "board" && args[0] === "import") return "console";
|
|
169
172
|
if (["help", "version", "--version", "completion", "say", "status", "pet", "board", "report", "usage", "turns", "review", "remember", "facts", "check-path"].includes(command)) return "allowed";
|
|
@@ -174,11 +177,23 @@ export function classifyPeerCommand(command: string, args: readonly string[], ca
|
|
|
174
177
|
export function cliCommandLabel(command: string, args: readonly string[], catalog?: ProjectAdapterCatalog): string {
|
|
175
178
|
if (command === "backlog") return ["list", "add", "pull", "pull-ready", "split", "return", "plan", "pin", "undo"].includes(args[0] ?? "") ? `backlog ${args[0]}` : "backlog";
|
|
176
179
|
if (command === "board" && args[0] === "import") return "board import";
|
|
180
|
+
// #508: the registry's two forms are named in the audit and in a refusal, never their operands (a path or a URL can carry a secret).
|
|
181
|
+
if (command === "smoke") return args[0] === "run" ? "smoke run" : "smoke";
|
|
182
|
+
if (command === "broker") return args[0] === "serve" ? "broker serve" : args[0] === "room" && args[1] === "drop" ? "broker room drop" : "broker";
|
|
183
|
+
if (command === "room" && args[0] === "inbox") return ["show", "done", "discard"].includes(args[1] ?? "") && args[2] && args[2] !== "--json" ? `room inbox ${args[1]}` : "room inbox list";
|
|
184
|
+
if (command === "room") return ["create", "invite", "join", "leave", "revoke", "expose", "resolve", "list", "status"].includes(args[0] ?? "") ? `room ${args[0]}` : "room";
|
|
185
|
+
// #335: a schedule id is config text, so the audit names the subcommand only.
|
|
186
|
+
if (command === "schedule") return ["list", "run", "pause", "resume"].includes(args.find(arg => arg !== "--json") ?? "") ? `schedule ${args.find(arg => arg !== "--json")}` : "schedule";
|
|
187
|
+
// #334: intake names its two subcommands in the audit and a refusal, never the request text (it can carry a secret).
|
|
188
|
+
if (command === "intake") return ["approve", "discard"].includes(args.find(arg => arg !== "--json") ?? "") ? `intake ${args.find(arg => arg !== "--json")}` : "intake";
|
|
189
|
+
// #514b: the audit and a refusal name the subcommand only, never the card id or any text.
|
|
190
|
+
if (command === "learn") return ["list", "accept", "reject"].includes(args.find(arg => arg !== "--json") ?? "") ? `learn ${args.find(arg => arg !== "--json")}` : "learn";
|
|
191
|
+
if (command === "source") return ["add", "list"].includes(args.find(arg => arg !== "--json") ?? "") ? `source ${args.find(arg => arg !== "--json")}` : "source";
|
|
177
192
|
if (command === "release") return ["new", "list", "show", "plan", "activate", "freeze", "ship", "drop"].includes(args[0] ?? "") ? `release ${args[0]}` : "release";
|
|
178
|
-
if (!(catalog?.definitionOf(command) ?? definitionOf(command)) && !["upgrade", "restart", "recovery", "recovery-run", "help", "--help", "--version", "version", "completion", "projects", "ui", "manager", "setup", "init", "daemon", "up", "console", "stop", "models", "say", "queue", "tail", "watch", "budget", "board", "task", "review", "remember", "ask", "route", "pause", "resume", "permit", "permission", "settings", "roster", "status", "pet", "logs", "export", "report", "research", "memory", "goal", "bench", "sdlc", "role", "facts", "check-path", "turns", "undo", "kill", "reset", "doctor", "agents"].includes(command)) return "unknown";
|
|
193
|
+
if (!(catalog?.definitionOf(command) ?? definitionOf(command)) && !["upgrade", "restart", "recovery", "recovery-run", "help", "--help", "--version", "version", "completion", "projects", "ui", "manager", "setup", "init", "daemon", "up", "console", "stop", "models", "say", "queue", "tail", "watch", "budget", "board", "task", "review", "remember", "ask", "route", "pause", "resume", "permit", "permission", "settings", "roster", "status", "pet", "logs", "export", "report", "research", "memory", "goal", "bench", "sdlc", "role", "facts", "check-path", "turns", "undo", "kill", "down", "reset", "doctor", "agents", "source", "broker", "schedule", "intake", "learn"].includes(command)) return "unknown";
|
|
179
194
|
if (["task", "queue", "budget", "route", "recovery", "models", "projects", "research", "memory", "goal", "bench", "agents", "roster", "settings"].includes(command)) {
|
|
180
195
|
const sub = command === "bench" || command === "memory" || command === "goal" ? args.find((arg) => arg !== "--json") : args[0];
|
|
181
|
-
if (sub && ["propose", "show", "assign", "unassign", "escalate", "cancel", "label", "list", "add", "resolve", "resume", "set", "execution", "explain", "status", "abort", "dispose", "reattach", "setup", "start", "stop", "remove", "discard", "export", "backfill", "addons", "run", "report", "compare", "search", "graph", "promote", "demote", "forget", "supersede", "dedupe", "decay", "rebuild", "sync", "machine", "import", "suggestions", "confirm", "reject", "new", "link", "state", "done", "take", "trust"].includes(sub)) return `${command} ${sub}`;
|
|
196
|
+
if (sub && ["propose", "show", "assign", "unassign", "escalate", "cancel", "claim", "label", "list", "add", "resolve", "resume", "set", "execution", "explain", "status", "abort", "dispose", "reattach", "setup", "start", "stop", "remove", "discard", "export", "backfill", "addons", "run", "report", "compare", "search", "graph", "promote", "demote", "forget", "supersede", "dedupe", "decay", "rebuild", "sync", "machine", "import", "suggestions", "confirm", "reject", "new", "link", "state", "done", "take", "trust"].includes(sub)) return `${command} ${sub}`;
|
|
182
197
|
}
|
|
183
198
|
return (/^[a-z][a-z0-9-]*$/.test(command) && !/\s/.test(command)) || ["--help", "--version"].includes(command) ? command : "unknown";
|
|
184
199
|
}
|
package/src/cli/init.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { initShadow, repoOf } from "../hub/snapshots.ts";
|
|
3
|
-
import { profileName, projectProfile, resolveProfile, mergeProfileOverrides, type ProfileName } from "../hub/profiles.ts";
|
|
3
|
+
import { PROFILE_NAMES, profileName, projectProfile, resolveProfile, mergeProfileOverrides, type ProfileName } from "../hub/profiles.ts";
|
|
4
|
+
import { canonicalJson, loadPacks, packOverrides, type DomainPack } from "../hub/packs.ts";
|
|
4
5
|
import { JCODE_MCP_FILE } from "./jcode-mcp.ts";
|
|
5
6
|
import { existsSync, lstatSync, mkdirSync, readFileSync, statSync, unlinkSync, writeFileSync, renameSync } from "node:fs";
|
|
6
7
|
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
@@ -100,7 +101,17 @@ fi
|
|
|
100
101
|
if (current !== wrapper || original && !(original.mode & 0o111)) plan.push({ action: original ? "modify" : "create", path: hook, reason: "install release authorization pre-push guard", next: wrapper, executable: true });
|
|
101
102
|
return plan;
|
|
102
103
|
}
|
|
103
|
-
|
|
104
|
+
/** `--profile` takes a shipped profile or a project pack (#513a): a pack selects its base profile and brings its rubric and example checks as `profile_overrides`. */
|
|
105
|
+
function selection(cwd: string, requested: string | undefined): { profile?: ProfileName; pack?: DomainPack } {
|
|
106
|
+
if (requested === undefined) return {};
|
|
107
|
+
if ((PROFILE_NAMES as readonly string[]).includes(requested)) return { profile: requested as ProfileName };
|
|
108
|
+
const loaded = loadPacks(cwd), pack = loaded.packs.get(requested);
|
|
109
|
+
if (pack) return { profile: pack.profile, pack };
|
|
110
|
+
const bad = loaded.errors.filter(error => error.id === requested || error.id === "project").map(error => `${error.id}: ${error.reason}`);
|
|
111
|
+
throw new Error(`unknown profile or pack "${requested.slice(0, 48)}"; profiles: ${PROFILE_NAMES.join(", ")}; packs in .agenthub/roles: ${[...loaded.packs.keys()].join(", ") || "none"}${bad.length ? `; not loaded: ${bad.join("; ")}` : ""}`);
|
|
112
|
+
}
|
|
113
|
+
function changes(cwd: string, requestedName?: string, shadow = false, releaseGuard = false): PlannedWrite[] {
|
|
114
|
+
const { profile: requested, pack } = selection(cwd, requestedName);
|
|
104
115
|
const originalAgents = existsSync(join(cwd, "AGENTS.md")) ? readFileSync(join(cwd, "AGENTS.md"), "utf8") : "";
|
|
105
116
|
const guarded = releaseGuard || originalAgents.includes("<!-- AGENT_HUB_RELEASE_GUARD:BEGIN");
|
|
106
117
|
const hooks = guarded ? releaseHookPlan(cwd) : [];
|
|
@@ -127,6 +138,14 @@ function changes(cwd: string, requested?: ProfileName, shadow = false, releaseGu
|
|
|
127
138
|
}
|
|
128
139
|
}
|
|
129
140
|
if (requested !== undefined && activeProfile !== undefined && requested !== activeProfile) throw new Error("init profile differs from existing config; edit the project profile explicitly before refreshing instructions");
|
|
141
|
+
// A pack's overrides are applied once. Existing `profile_overrides` that hold the same data (compared with sorted keys, so the order the file was written in does not matter) mean the pack is already applied and the
|
|
142
|
+
// file is left as it is; anything else already there is the project's to edit explicitly.
|
|
143
|
+
let packApplied = false;
|
|
144
|
+
if (pack) {
|
|
145
|
+
const wanted = packOverrides(pack);
|
|
146
|
+
if (overrides !== undefined && canonicalJson(overrides) !== canonicalJson(wanted)) throw new Error(`project already has profile_overrides that differ from pack ${pack.name}; edit them explicitly before applying a pack`);
|
|
147
|
+
if (overrides !== undefined) packApplied = true; else overrides = wanted;
|
|
148
|
+
}
|
|
130
149
|
const profile = resolveProfile(requested ?? activeProfile, overrides);
|
|
131
150
|
const changed: PlannedWrite[] = [];
|
|
132
151
|
if (shadow && !repository?.shadow) changed.push({ action: "create", path: join(cwd, ".agenthub/shadow.git"), reason: "create opt-in shadow Git metadata without .git", shadow: true });
|
|
@@ -140,10 +159,15 @@ function changes(cwd: string, requested?: ProfileName, shadow = false, releaseGu
|
|
|
140
159
|
const path = join(cwd, ".agenthub", name);
|
|
141
160
|
if (!existsSync(path)) {
|
|
142
161
|
const template = readFileSync(join(TEMPLATES, name), "utf8");
|
|
143
|
-
const
|
|
144
|
-
|
|
162
|
+
const withProfile = name === "config.json" && profile.name !== "dev", withPack = name === "config.json" && !!pack;
|
|
163
|
+
const next = withProfile || withPack ? JSON.stringify({ ...JSON.parse(template), ...(withProfile ? { profile: profile.name } : {}), ...(withPack ? { profile_overrides: overrides } : {}) }, null, 2) + "\n" : template;
|
|
164
|
+
write(path, next, pack && name === "config.json" ? packReason(pack, "create project defaults with") : "create missing project defaults");
|
|
145
165
|
}
|
|
146
166
|
}
|
|
167
|
+
if (pack && !packApplied && existsSync(configPath)) {
|
|
168
|
+
const current = JSON.parse(readFileSync(configPath, "utf8"));
|
|
169
|
+
write(configPath, JSON.stringify({ ...current, profile_overrides: overrides }, null, 2) + "\n", packReason(pack, "add to the project's profile_overrides"));
|
|
170
|
+
}
|
|
147
171
|
const agents = join(cwd, "AGENTS.md");
|
|
148
172
|
const existing = existsSync(agents) ? readFileSync(agents, "utf8") : "";
|
|
149
173
|
const instructions = upsertBlock(existing, overrides && Object.hasOwn(overrides as object, "agents") ? profile.agents : profile.agents || readFileSync(join(TEMPLATES, "AGENTS.block.md"), "utf8"));
|
|
@@ -174,12 +198,15 @@ function changes(cwd: string, requested?: ProfileName, shadow = false, releaseGu
|
|
|
174
198
|
write(ignore, lines, "ignore runtime state and machine-local configuration");
|
|
175
199
|
return [...hooks, ...changed];
|
|
176
200
|
}
|
|
177
|
-
/**
|
|
178
|
-
|
|
201
|
+
/** What a pack adds, by id only: the preview names the criteria and checks, never their text. */
|
|
202
|
+
const packReason = (pack: DomainPack, how: string) =>
|
|
203
|
+
`${how} pack ${pack.name} (profile ${pack.profile}): rubric ${pack.rubric.map(item => item.id).join(", ")}; checks ${pack.checks.map(check => check.id).join(", ") || "none"}; expects roles ${pack.roles.join(", ") || "none"} from the catalog`;
|
|
204
|
+
/** Read-only metadata, deliberately omitting project/user file contents. `profile` is a shipped profile name or a project pack name. */
|
|
205
|
+
export function planInit(cwd: string, profile?: string, shadow = false, releaseGuard = false): InitChange[] {
|
|
179
206
|
return changes(cwd, profile, shadow, releaseGuard).map(({ next, shadow: _shadow, directory, executable, moveFrom, expected, ...change }) => change);
|
|
180
207
|
}
|
|
181
208
|
/** Returns the paths it changed, applying the same plan as the preview. */
|
|
182
|
-
export function init(cwd: string, profile?:
|
|
209
|
+
export function init(cwd: string, profile?: string, shadow = false, releaseGuard = false): string[] {
|
|
183
210
|
const plan = changes(cwd, profile, shadow, releaseGuard);
|
|
184
211
|
// Recheck hook parents and rename identities before any project write.
|
|
185
212
|
for (const change of plan.filter(change => change.executable || change.moveFrom)) {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Human output for `ahub intake` (#334, #511a). Pure: parsed hub-tool JSON in, spans out, no I/O. `--json` is the caller's branch and prints the same text.
|
|
2
|
+
// A private request arrives from the hub as a stub (`[pii]` title and detail); this module shows that stub and never asks for more.
|
|
3
|
+
import { blank, grid, heading, pair, s } from "./sdlc-output.ts";
|
|
4
|
+
import type { Span } from "./console-state.ts";
|
|
5
|
+
|
|
6
|
+
const list = (value: unknown): any[] => Array.isArray(value) ? value : [];
|
|
7
|
+
const dash = (value: unknown) => value === undefined || value === null || value === "" ? "-" : String(value);
|
|
8
|
+
const ids = (values: unknown) => list(values).map(id => `#${id}`).join(" ") || "-";
|
|
9
|
+
|
|
10
|
+
const HOW: Record<string, string> = {
|
|
11
|
+
planner: "asked the planner to split it with hub_backlog_split",
|
|
12
|
+
hub: "split by the hub's model",
|
|
13
|
+
unsplit: "left whole: nothing split it",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** `ahub intake "<text>"`: the draft, who is splitting it, and the children if the hub split it. */
|
|
17
|
+
export function renderIntake(result: unknown, columns?: number): Span[][] {
|
|
18
|
+
const r = result as any, draft = r?.draft, children = list(r?.children);
|
|
19
|
+
const out: Span[][] = [...heading(`Intake draft #${draft?.id ?? "?"}`, columns)];
|
|
20
|
+
out.push(...pair("title", dash(draft?.title), columns, 8), ...pair("state", "draft: held until you approve it", columns, 8, "attention"), ...pair("split", HOW[r?.split] ?? dash(r?.split), columns, 8));
|
|
21
|
+
if (r?.reason) out.push(...pair("because", String(r.reason), columns, 8, "attention"));
|
|
22
|
+
if (children.length) out.push(blank, [s(`${children.length} draft part${children.length === 1 ? "" : "s"}`, "muted")], ...grid(["ID", "CLASS", "AFTER", "TITLE"], children.map((c: any) => [s(`#${c.id}`, "taskRef"), s(dash(c.profileClass ?? c.class)), s(ids(c.deps)), s(dash(c.title))]), columns));
|
|
23
|
+
out.push(blank, [s(`approve with ahub intake approve ${draft?.id ?? "<id>"}, or discard with ahub intake discard ${draft?.id ?? "<id>"}`, "muted")]);
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** `ahub intake approve|discard <id>`: every card the command changed. */
|
|
28
|
+
export function renderIntakeChange(op: "approve" | "discard", result: unknown, columns?: number): Span[][] {
|
|
29
|
+
const rows = list(result);
|
|
30
|
+
const out: Span[][] = [...heading(op === "approve" ? `Intake approved: ${rows.length} card${rows.length === 1 ? "" : "s"} released to the backlog` : `Intake discarded: ${rows.length} card${rows.length === 1 ? "" : "s"} cancelled`, columns)];
|
|
31
|
+
out.push(...grid(["ID", "STATE", "TITLE"], rows.map((c: any) => [s(`#${c.id}`, "taskRef"), s(dash(c.state), c.state === "cancelled" ? "muted" : undefined), s(dash(c.title))]), columns));
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Human output for `ahub learn` (#514b). Pure: parsed hub-tool JSON in, spans out, no I/O. `--json` is the caller's branch and prints the same text.
|
|
2
|
+
// Ids, counts, the key parts and the one line the hub itself generates are all it shows: the card's own text is never echoed (a card is data to the parser, not to this printer).
|
|
3
|
+
import { blank, grid, heading, pair, s } from "./sdlc-output.ts";
|
|
4
|
+
import type { Span } from "./console-state.ts";
|
|
5
|
+
|
|
6
|
+
const list = (value: unknown): any[] => Array.isArray(value) ? value : [];
|
|
7
|
+
const dash = (value: unknown) => value === undefined || value === null || value === "" ? "-" : String(value);
|
|
8
|
+
|
|
9
|
+
/** `ahub learn list [--all]`: one row per candidate, and what each command would do. */
|
|
10
|
+
export function renderLearnList(result: unknown, columns?: number, all = false): Span[][] {
|
|
11
|
+
const rows = list(result);
|
|
12
|
+
const open = rows.filter(row => row.status === "open").length;
|
|
13
|
+
const out: Span[][] = [...heading(all ? `Review checklist candidates: ${open} open, ${rows.length - open} decided` : `Review checklist candidates: ${open} open`, columns)];
|
|
14
|
+
if (!rows.length) {
|
|
15
|
+
out.push(blank, [s(all ? "no candidate has been filed" : "no open candidate; pass --all to include decided ones", "muted")]);
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
out.push(...grid(["ID", "STATUS", "PROFILE/CLASS", "CRITERION", "TASKS", "LINE TO ADD"], rows.map((row: any) => [
|
|
19
|
+
s(`#${row.id}`, "taskRef"), s(dash(row.status), row.status === "open" ? "attention" : "muted"), s(`${dash(row.profile)}/${dash(row.class)}`), s(dash(row.criterion)), s(String(list(row.tasks).length)), s(dash(row.line)),
|
|
20
|
+
]), columns));
|
|
21
|
+
if (open) out.push(blank, [s("accept writes the line to .agenthub/config.local.json (ahub settings undo puts it back); reject closes the card. Nothing else changes.", "muted")]);
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** `ahub learn accept|reject <id>`: what was decided, and for accept exactly what was written and where. */
|
|
26
|
+
export function renderLearnChange(op: "accept" | "reject", result: unknown, columns?: number): Span[][] {
|
|
27
|
+
const r = (result ?? {}) as any;
|
|
28
|
+
const card = op === "accept" ? r.card : r;
|
|
29
|
+
const out: Span[][] = [...heading(op === "accept" ? `Candidate #${dash(card?.id)} accepted` : `Candidate #${dash(card?.id)} rejected and closed`, columns)];
|
|
30
|
+
if (op === "reject") return [...out, ...pair("written", "nothing", columns, 8, "muted")];
|
|
31
|
+
const a = r.accepted ?? {};
|
|
32
|
+
if (a.alreadyAccepted) return [...heading(`Candidate #${dash(card?.id)} was already accepted`, columns), ...pair("profile", `${dash(a.profile)}/${dash(a.class)}`, columns, 8), ...pair("criterion", a.criterion, columns, 8), ...pair("written", "nothing: it was accepted before (ahub settings undo still puts back that write if it is the latest)", columns, 8, "muted")];
|
|
33
|
+
out.push(...pair("profile", `${dash(a.profile)}/${dash(a.class)}`, columns, 8), ...pair("criterion", a.criterion, columns, 8), ...pair("line", a.line, columns, 8),
|
|
34
|
+
...pair("file", `${dash(a.file)} (this machine only; the shared config is untouched)`, columns, 8), ...pair("now", a.alreadyPresent ? `the checklist already had this line: nothing was written` : `${dash(a.lines)} lines in the checklist`, columns, 8),
|
|
35
|
+
...pair("applies", dash(a.applies), columns, 8, "attention"));
|
|
36
|
+
if (a.undo) out.push(blank, [s(`put it back with ${a.undo}`, "muted")]);
|
|
37
|
+
return out;
|
|
38
|
+
}
|