loomctl orient: one table of contents, restating no rule

Generates .loom/orientation.md — what this repository depends on, where each copy
came from, and which facets sit beside it — for whoever arrives next, of any make.

One file rather than two, decided by loom's own rule. Generate what varies, adopt
what does not: the publishing half varies not at all and is already adopted, so
publication.md appears in the index like any other adopted document, in exactly the
repositories that adopted it. A second command would emit a file whose whole content
is a pointer to a file already in the tree, and would revive a noun declined when
published check went.

Restates no rule. The three moves are phrased as operations — what to run, what to
write, where — and every rule stays in the document that owns it. The .usages.md is
pointed at rather than summarised, because what depends on a document is free prose
and anything extracting a claim from prose eventually extracts it wrong. It says
nothing recorded when a document has no usages file, which is a finding rather than
an omission and is currently true of six of nine.

Byte-deterministic, pinned by a test that inserts records out of order and
generates three times. Written to .loom/orientation.md rather than inside
.loom/externals/, where check would report it as an unlocked external in every
repository using the feature.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UTxuSizozEA8yDitPuris
This commit is contained in:
2026-09-08 09:11:04 -04:00
co-authored by Claude Opus 5
parent 1cc528c8aa
commit da6e8b9b51
4 changed files with 263 additions and 0 deletions
+18
View File
@@ -17,6 +17,7 @@ import (
"git.hypertheory-labs.dev/loom/loom-cli/internal/config"
"git.hypertheory-labs.dev/loom/loom-cli/internal/external"
"git.hypertheory-labs.dev/loom/loom-cli/internal/orient"
)
const usage = `loomctl — fetch what you depend on, and find out when it changed.
@@ -50,6 +51,7 @@ Adopted documents live in .loom/externals/<host>/<owner>/<repo>/<name>.md, and
their origins in .loom/externals/.locks. The path is for a person to read; the
lock is what a machine uses, because the path does not round-trip to a URL.
loomctl orient [--out path] write .loom/orientation.md for whoever arrives next
loomctl config which context is current, and from where
A bare owner/repo is resolved against the current context in
@@ -87,6 +89,22 @@ func run(args []string) error {
switch args[0] {
case "external":
return runExternal(args[1:])
case "orient":
fs := flag.NewFlagSet("orient", flag.ContinueOnError)
out := fs.String("out", "", "write here instead of "+orient.File)
if err := fs.Parse(args[1:]); err != nil {
return err
}
root, err := root()
if err != nil {
return err
}
dest, err := orient.Generate(root, *out)
if err != nil {
return err
}
fmt.Fprintf(os.Stdout, "wrote %s\n", dest)
return nil
case "config":
return showConfig(os.Stdout)
default: