The mechanism named in canon/standards/work-record-types_v0.1.md:
"Promotion is a first-class transition... manual transcription of an
intake item into other kinds is a process defect." This is what AWQ-010
needed and didn't have -- a human/agent had to notice, transcribe, and
re-register it by hand. One call now does what that manual pass did.
scripts/promote_intake.py: intake.routed -> workplan | task | decision |
engagement.
- workplan: new ADR-001 file at workplans/{ID}-{slug}.md, registered
against the hub (repo+topic resolution, POST /workplans, frontmatter
id write-back)
- task: appended as a ```task``` block to an existing --workplan-file,
registered via POST /tasks, reuses the existing
_inject_task_id_into_block writeback helper
- decision: appended as a ```yaml``` block with a fresh
{PREFIX}-DEC-{YYYY}-{NNN} id to --target-file, registered the same
way C-32 registers decisions (reuses _inject_yaml_block_field)
- engagement: appended as a ```yaml``` block with a fresh
{PREFIX}-ENG-{YYYY}-{NNN} id -- file-only, no hub entity exists yet
(same honest deferral as C-32), reported not silently skipped
In every case the intake is closed with outcome=promoted and
promoted_to=<new canonical id>; the new record carries an
origin: "intake:<id>" back-link.
Wired as `statehub promote-intake <intake-id> --to <kind> --repo-slug
<slug> --repo-path <path> --domain <domain> [--target-file ...]
[--workplan-file ...]`, matching the CLI shape named in the workplan text.
17 tests: pure helpers (_slugify, _next_number, _append_yaml_block,
frontmatter injection) offline; full promote_intake() flow with the hub
API mocked.
Live-verified against the real running API/DB and a real repo
(binky-control), not just mocks -- and the live proof caught a real bug:
the first workplan-promotion run silently produced a false success (the
intake was closed outcome=promoted, but /workplans/ actually 422'd on a
missing repo_id that the code never resolved, so no workstream was ever
created). Fixed to resolve repo_id via /repos/{slug} and to raise loudly
on registration failure instead of writing a half-registered file
silently; locked in as two regression tests. Re-verified clean:
workplan promotion (CLI direct + through `statehub promote-intake`
itself) and decision promotion both proven end-to-end against the live
hub, with all scratch artefacts (files + hub rows) cleaned up afterward.
No regressions: full state-hub suite (271 tests across
test_promote_intake, test_intake, test_work_record_registration,
test_work_record_check, test_routers_core, test_consistency_check,
test_consistency_sweep, test_mcp_smoke, test_mcp_write_tools) green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Research sweep captured in research/20260709-workstream-caller-sweep.md.
Migrate dashboard detail tasks query, custodian_cli POST bodies, and
onboarding curl to workplan-first paths; re-capture 8h legacy-meter evidence.
Add X-StateHub-Component to fix-consistency and other State Hub REST callers for
legacy-meter attribution. Archive STATE-WP-0069; open STATE-WP-0070 for
meter-gated phase-2 retirement. Task POST bodies use workplan_id only.
Introduce canonical MCP_SERVER_NAME constants, shared registration helpers,
and a migrate_mcp_config.py script for ~/.claude.json upgrades. Registration,
patch, and custodian CLI checks accept both dev-hub and legacy state-hub during
transition. API root metadata and session-protocol template reflect the new name.
custodian_cli.py:
- new ingest-sbom subcommand: auto-detects repo slug from local_path
registration, runs ingest_sbom.py --scan from the repo root
- --dry-run flag passes through to the underlying script
- --slug override for repos where path lookup fails
repos.md:
- ? button on "⚠ not ingested" now opens /docs/sbom (not /docs/repos)
docs/sbom.md:
- Ingest commands section now leads with `custodian ingest-sbom` (repo-root)
- make ingest-sbom kept as low-level alternative
- Per-ecosystem and gap-type references updated to new command
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
custodian_cli.py:
- register-project now writes CLAUDE.custodian.md (suggestion) instead
of overwriting CLAUDE.md; includes preamble with integration instructions
- registers repo via POST /repos/
- creates a "Repo Integration: {slug}" workstream in the domain's topic
with 4 onboarding tasks (integrate CLAUDE.md, first workplan, SBOM,
EPs/TDs); checks for existing workstream to be idempotent
- fixes {REPO_SLUG} template substitution (previously missing)
dashboard:
- repos.md: fetches workstreams; detects active repo-integration-* slugs;
adds "Integrating" KPI card; shows ⚙ integrating badge per repo in
coverage map and table; replaces "How to Ingest a Repo" with
"Onboard a New Repo" 4-step panel with doc help button
- docs/repo-integration.md (new): full collaboration model doc — custodian
as coach, repo agent as executor; journey, generated tasks, first session
protocol, ongoing relationship
- docs/repos.md: links to new repo-integration doc; updates "What is a
managed repo?" section; adds onboarding quick reference
- docs/reference.md: fix latent build error — code examples were in ```js
fences (executed by OF); changed to ```javascript (display only)
- observablehq.config.js: adds "Repo Integration" to Reference nav
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
register-project now creates a topic automatically if the domain has
no active topic yet, instead of exiting with an error. This makes the
"create domain → register project" flow self-contained.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The custodian CLI had a static VALID_DOMAINS list used as argparse
choices= and for in-process domain validation, preventing any domain
added after v0.5 from being used. Now fetches active domains from the
API at runtime. Also fixes t.get("domain") → t.get("domain_slug")
in two topic lookup sites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
MCP server: add create_workstream(topic_id, title, slug?, owner?,
description?, due_date?) — auto-generates slug from title if omitted;
emits workstream_created progress event. Now 12 tools total.
CLI: add two new subcommands —
custodian create-workstream --domain DOMAIN --title TITLE [--slug] [--owner] [--description]
custodian create-task --workstream ID_OR_SLUG --title TITLE [--priority] [--assignee]
create-task accepts workstream UUID or slug (resolves via API).
Dashboard: hint box below "Open Workstreams by Domain" chart listing
registered domains that have zero workstreams, with the exact
custodian create-workstream command to run.
TOOLS.md: updated tool count (11 → 12) and added create_workstream row.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
custodian register-project [--domain DOMAIN] [--path PATH]
Defaults path to cwd; auto-detects domain from project charter if
--domain is omitted. Does: API health → topic lookup → MCP check →
CLAUDE.md from template → progress event.
custodian status
Prints API health + summary totals + blocking decisions.
Installed via: make install-cli (symlinks .venv/bin/custodian → ~/.local/bin/)
Entry point declared in pyproject.toml [project.scripts].
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>