diff --git a/cli/plugin-kit-ai/tools/authoring-docs/export.go b/cli/plugin-kit-ai/tools/authoring-docs/export.go new file mode 100644 index 00000000..cfbf7c55 --- /dev/null +++ b/cli/plugin-kit-ai/tools/authoring-docs/export.go @@ -0,0 +1,244 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "sort" + "strings" + + "github.com/777genius/plugin-kit-ai/cli/internal/agentpluginscli" + "github.com/777genius/plugin-kit-ai/cli/internal/authoring/commands" + "github.com/777genius/plugin-kit-ai/cli/internal/authoringcli" + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" + "github.com/spf13/pflag" +) + +const namespace = "prepared-authoring-v2" + +// This is intentionally a separate envelope, not the v1 manifest array. +type manifest struct { + Schema string `json:"schema"` + Namespace string `json:"namespace"` + Status string `json:"status"` + Released bool `json:"released"` + FactoryBaseline string `json:"factory_baseline_sha"` + SourceSHA string `json:"source_sha"` + Sources []sourcePin `json:"sources"` + Surfaces []surface `json:"surfaces"` +} +type surface struct { + Identity string `json:"identity"` + CommandPath string `json:"command_path"` + Commands []entry `json:"commands"` +} +type entry struct { + Identity string `json:"identity"` + CommandPath string `json:"command_path"` + Slug string `json:"slug"` + FileName string `json:"file_name"` + Use string `json:"use"` + Short string `json:"short"` + Long string `json:"long"` + Example string `json:"example"` + Aliases []string `json:"aliases,omitempty"` + Deprecated string `json:"deprecated,omitempty"` + LocalFlags []flagFact `json:"local_flags"` + InheritedFlags []flagFact `json:"inherited_flags"` +} +type flagFact struct { + Name string `json:"name"` + Shorthand string `json:"shorthand,omitempty"` + Type string `json:"type"` + Default string `json:"default"` + NoOptDefault string `json:"no_opt_default,omitempty"` + Usage string `json:"usage"` + Deprecated string `json:"deprecated,omitempty"` +} + +// ReleaseSelection is the existing exported, non-executing construction seam. +// Nil args only observes the root; no ParseFlags, Execute, Args, Run, completion, +// project service, or installer callback is invoked by this adapter. +func trees() ([]*cobra.Command, error) { + app := commands.App{PublicContract: true, Release: &commands.ReleaseOptions{}} + plugin, _, _, err := app.ReleaseSelection(nil, authoringcli.NewReleasePluginKitRoot) + if err != nil { + return nil, err + } + installer, _, _, err := app.ReleaseSelection(nil, func(factories ...authoringcli.Factory) (*cobra.Command, error) { + root := agentpluginscli.NewRoot(agentpluginscli.App{}) + author, err := authoringcli.NewReleaseAuthorCommand(factories...) + if err != nil { + return nil, err + } + root.AddCommand(author) + return root, nil + }) + if err != nil { + return nil, err + } + for _, c := range installer.Commands() { + if c.Name() == "author" { + return []*cobra.Command{plugin, c}, nil + } + } + return nil, fmt.Errorf("shared factory did not construct author subtree") +} + +func visible(c *cobra.Command) bool { + return !c.Hidden && c.Annotations[authoringcli.RejectionKey] == "" && !strings.HasPrefix(c.Name(), "__") +} + +func flags(fs *pflag.FlagSet) []flagFact { + out := []flagFact{} + fs.VisitAll(func(f *pflag.Flag) { + if !f.Hidden { + out = append(out, flagFact{f.Name, f.Shorthand, f.Value.Type(), f.DefValue, f.NoOptDefVal, f.Usage, f.Deprecated}) + } + }) + return out +} + +// Drop excluded descendants before Markdown generation so SEE ALSO cannot leak +// them. Keep author attached to its genuine installer ancestor for inheritance. +func prepare(c *cobra.Command) { + c.InitDefaultHelpCmd() + c.InitDefaultHelpFlag() + c.DisableAutoGenTag = true + c.SetOut(io.Discard) + c.SetErr(io.Discard) + for _, child := range c.Commands() { + if !visible(child) { + c.RemoveCommand(child) + } else { + prepare(child) + } + } +} + +func render(sha string, pins []sourcePin, roots []*cobra.Command) (map[string][]byte, error) { + result := map[string][]byte{} + m := manifest{Schema: "authoring-docs-manifest-v1", Namespace: namespace, Status: "prepared-not-release", SourceSHA: sha, FactoryBaseline: factoryBaselineSHA, Sources: pins} + for _, root := range roots { + prepare(root) + s := surface{Identity: namespace + ":" + root.CommandPath(), CommandPath: root.CommandPath(), Commands: []entry{}} + var walk func(*cobra.Command) error + walk = func(c *cobra.Command) error { + if !visible(c) { + return nil + } + path := c.CommandPath() + name := namespace + "/" + strings.ReplaceAll(path, " ", "_") + ".md" + slug := namespace + "-" + strings.ReplaceAll(path, " ", "-") + e := entry{Identity: namespace + ":" + path, CommandPath: path, Slug: slug, FileName: name, Use: c.Use, Short: c.Short, Long: c.Long, Example: c.Example, Aliases: append([]string(nil), c.Aliases...), Deprecated: c.Deprecated, LocalFlags: flags(c.LocalFlags()), InheritedFlags: flags(c.InheritedFlags())} + s.Commands = append(s.Commands, e) + var body bytes.Buffer + fmt.Fprintf(&body, "\n\nPrepared reference only; not a public release.\n\n", namespace, sha) + // The author-only surface has no installer root page. Its parent link is an + // explicit source link rather than an invented local installer reference. + link := func(target string) string { + if target == "agentplugins.md" { + return "https://github.com/777genius/universal-agent-plugins/blob/" + sha + "/cli/plugin-kit-ai/internal/agentpluginscli/root.go" + } + return target + } + if err := doc.GenMarkdownCustom(c, &body, link); err != nil { + return err + } + result[name] = body.Bytes() + for _, child := range c.Commands() { + if err := walk(child); err != nil { + return err + } + } + return nil + } + if err := walk(root); err != nil { + return nil, err + } + m.Surfaces = append(m.Surfaces, s) + } + body, err := json.MarshalIndent(m, "", " ") + if err != nil { + return nil, err + } + result[namespace+"/manifest.json"] = append(body, '\n') + return result, nil +} + +func export(checkout, sha, out string) error { + return exportTrees(checkout, sha, out, trees) +} + +func exportTrees(checkout, sha, out string, load func() ([]*cobra.Command, error)) error { + if out == "" { + return fmt.Errorf("--out-dir is required") + } + pins, err := validateSource(checkout, sha) + if err != nil { + return err + } + roots, err := load() + if err != nil { + return err + } + if err := validateProjection(roots); err != nil { + return err + } + files, err := render(sha, pins, roots) + if err != nil { + return err + } + // Exclusive directory creation refuses v1 outputs and all existing exports. + // No replacement, cleanup, or recursive deletion is performed. + if err := os.Mkdir(out, 0755); err != nil { + return err + } + if err := os.Mkdir(filepath.Join(out, namespace), 0755); err != nil { + return err + } + for name, body := range files { + if err := os.WriteFile(filepath.Join(out, filepath.FromSlash(name)), body, 0644); err != nil { + return err + } + } + return nil +} + +// Hash the actual deterministic rendered facts, with fixed provenance rather +// than caller SHA/host paths. Length framing and sorted names bind every byte. +// This is a reviewed golden, never regenerated automatically during export. +const reviewedProjection = "2e2b7d165e600a29a5c5ff730508a67470c5535917ca9921432da666814aefb3" + +func projectionFingerprint(roots []*cobra.Command) (string, error) { + files, err := render("SOURCE_SHA", nil, roots) + if err != nil { + return "", err + } + names := make([]string, 0, len(files)) + for name := range files { + names = append(names, name) + } + sort.Strings(names) + h := sha256.New() + for _, name := range names { + fmt.Fprintf(h, "%d:%s%d:", len(name), name, len(files[name])) + h.Write(files[name]) + } + return fmt.Sprintf("%x", h.Sum(nil)), nil +} +func validateProjection(roots []*cobra.Command) error { + got, err := projectionFingerprint(roots) + if err != nil { + return err + } + if got != reviewedProjection { + return fmt.Errorf("loaded documentation projection mismatch: %s", got) + } + return nil +} diff --git a/cli/plugin-kit-ai/tools/authoring-docs/export_test.go b/cli/plugin-kit-ai/tools/authoring-docs/export_test.go new file mode 100644 index 00000000..b943de8e --- /dev/null +++ b/cli/plugin-kit-ai/tools/authoring-docs/export_test.go @@ -0,0 +1,303 @@ +package main + +import ( + "bytes" + "encoding/json" + "os" + "path/filepath" + "reflect" + "regexp" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +func rendered(t *testing.T) (map[string][]byte, manifest) { + t.Helper() + roots, err := trees() + if err != nil { + t.Fatal(err) + } + // Every executable hook is a trap: generation must only inspect definitions. + var trap func(*cobra.Command) + trap = func(c *cobra.Command) { + c.Run = func(*cobra.Command, []string) { t.Fatal("Run called") } + c.RunE = func(*cobra.Command, []string) error { t.Fatal("RunE called"); return nil } + c.Args = func(*cobra.Command, []string) error { t.Fatal("Args called"); return nil } + c.PreRunE = c.RunE + c.PersistentPreRunE = c.RunE + c.PostRunE = c.RunE + c.PersistentPostRunE = c.RunE + c.ValidArgsFunction = func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) { + t.Fatal("completion called") + return nil, 0 + } + for _, ch := range c.Commands() { + trap(ch) + } + } + for _, root := range roots { + trap(root) + } + files, err := render(factoryBaselineSHA, factoryPins, roots) + if err != nil { + t.Fatal(err) + } + var m manifest + if err := json.Unmarshal(files[namespace+"/manifest.json"], &m); err != nil { + t.Fatal(err) + } + return files, m +} + +func TestDeterministicSurfacesAndNoActions(t *testing.T) { + sandbox := t.TempDir() + t.Setenv("HOME", sandbox) + t.Setenv("USERPROFILE", sandbox) + t.Setenv("TMPDIR", sandbox) + first, m := rendered(t) + second, _ := rendered(t) + if !reflect.DeepEqual(first, second) { + t.Fatal("fresh exports differ") + } + if m.Released || m.Status != "prepared-not-release" || m.SourceSHA != factoryBaselineSHA || m.Namespace != namespace { + t.Fatal("identity") + } + if len(m.Surfaces) != 2 || m.Surfaces[0].CommandPath != "plugin-kit-ai" || m.Surfaces[1].CommandPath != "agentplugins author" { + t.Fatal("surfaces") + } + authorNames := []string{"", "capabilities", "compat", "doctor", "init", "inspect", "skills", "skills init", "skills validate", "help", "skills help", "test", "validate", "version"} + for i, s := range m.Surfaces { + want := map[string]bool{} + for _, n := range authorNames { + want[strings.TrimSpace(s.CommandPath+" "+n)] = true + } + if i == 0 { + for _, n := range []string{"completion", "completion bash", "completion fish", "completion help", "completion powershell", "completion zsh"} { + want[s.CommandPath+" "+n] = true + } + } + for _, e := range s.Commands { + if !want[e.CommandPath] { + t.Fatalf("unexpected command %s", e.CommandPath) + } + delete(want, e.CommandPath) + body, ok := first[e.FileName] + if !ok || !bytes.Contains(body, []byte(e.Short)) { + t.Fatalf("missing Markdown/help %s", e.CommandPath) + } + if !strings.HasPrefix(e.Identity, namespace+":") { + t.Fatal("v1 identity reuse") + } + } + if len(want) > 0 { + t.Fatalf("missing commands: %v", want) + } + } + for _, body := range first { + for _, bad := range []string{"Auto generated by", sandbox, "__docs", "__complete", "plugin-kit-ai generate", "skills install"} { + if bytes.Contains(body, []byte(bad)) { + t.Fatalf("unwanted output %q", bad) + } + } + } + entries, err := os.ReadDir(sandbox) + if err != nil || len(entries) != 0 { + t.Fatalf("generation mutated sandbox: %v %v", entries, err) + } +} + +func TestFlagFidelity(t *testing.T) { + files, m := rendered(t) + for _, s := range m.Surfaces { + var init entry + for _, e := range s.Commands { + if e.CommandPath == s.CommandPath+" init" { + init = e + } + } + local := map[string]flagFact{} + inherited := map[string]flagFact{} + for _, f := range init.LocalFlags { + local[f.Name] = f + } + for _, f := range init.InheritedFlags { + inherited[f.Name] = f + } + if init.Use != "init " || local["mcp-template"].Usage != "hybrid MCP choice: mcp-remote or mcp-stdio" || local["runtime"].Usage != "stdio template runtime: node" { + t.Fatal("local facts lost") + } + for _, bad := range []string{"mcp", "platform", "strict", "force", "output"} { + if _, ok := local[bad]; ok { + t.Fatal("retired flag", bad) + } + } + if inherited["format"].Default != "human" || inherited["no-color"].Type != "bool" || inherited["no-color"].NoOptDefault != "true" { + t.Fatal("inherited facts lost") + } + if s.CommandPath == "agentplugins author" { + if inherited["scope"].Default != "user" || inherited["accept-security-risk"].Type != "bool" || !strings.Contains(init.Long, "are rejected") { + t.Fatal("installer inheritance lost") + } + } else if _, ok := inherited["scope"]; ok { + t.Fatal("installer flags leaked") + } + for _, f := range append(init.LocalFlags, init.InheritedFlags...) { + if !bytes.Contains(files[init.FileName], []byte("--"+f.Name)) { + t.Fatalf("Markdown omitted flag %s", f.Name) + } + } + } +} + +func TestExclusionPrunesLinksAndDescendants(t *testing.T) { + root := &cobra.Command{Use: "probe"} + for _, c := range []*cobra.Command{ + {Use: "hidden", Hidden: true}, + {Use: "shim", Annotations: map[string]string{"authoringcli.v1-rejection": "v1"}}, + {Use: "__internal"}, + } { + c.AddCommand(&cobra.Command{Use: "visible-child", Run: func(*cobra.Command, []string) { t.Fatal("executed") }}) + root.AddCommand(c) + } + root.Flags().String("secret", "", "hidden flag") + _ = root.Flags().MarkHidden("secret") + files, err := render(factoryBaselineSHA, nil, []*cobra.Command{root}) + if err != nil { + t.Fatal(err) + } + for _, body := range files { + for _, bad := range []string{"visible-child", "shim", "__internal", "secret"} { + if bytes.Contains(body, []byte(bad)) { + t.Fatal("exclusion leaked", bad) + } + } + } +} + +func TestSourceIdentityAndNoOverwrite(t *testing.T) { + checkout := committedFixture(t) + sha := checkoutSHA(t, checkout) + if _, err := validateSource(checkout, sha); err != nil { + t.Fatal(err) + } + for _, sha := range []string{"", "070663e", strings.Repeat("0", 40), sha + "\n"} { + dest := filepath.Join(t.TempDir(), "output") + if err := export(checkout, sha, dest); err == nil { + t.Fatal("accepted bad source", sha) + } + if _, err := os.Stat(dest); !os.IsNotExist(err) { + t.Fatal("wrote output for bad source") + } + } + dest := t.TempDir() + marker := filepath.Join(dest, "v1.md") + if err := os.WriteFile(marker, []byte("keep v1"), 0600); err != nil { + t.Fatal(err) + } + if err := export(checkout, sha, dest); err == nil { + t.Fatal("accepted existing output") + } + body, _ := os.ReadFile(marker) + if string(body) != "keep v1" { + t.Fatal("overwrote v1") + } + if _, err := validateSource(t.TempDir(), sha); err == nil { + t.Fatal("accepted non-checkout") + } +} + +func TestExportBytesAndLinks(t *testing.T) { + checkout := committedFixture(t) + sha := checkoutSHA(t, checkout) + base := t.TempDir() + a, b := filepath.Join(base, "a"), filepath.Join(base, "b") + for _, dest := range []string{a, b} { + if err := export(checkout, sha, dest); err != nil { + t.Fatal(err) + } + } + roots, err := trees() + if err != nil { + t.Fatal(err) + } + expected, err := render(sha, factoryPins, roots) + if err != nil { + t.Fatal(err) + } + var m manifest + if err := json.Unmarshal(expected[namespace+"/manifest.json"], &m); err != nil { + t.Fatal(err) + } + if len(m.Surfaces) != 2 || len(m.Surfaces[0].Commands) != 20 || len(m.Surfaces[1].Commands) != 14 || m.FactoryBaseline != factoryBaselineSHA || m.SourceSHA != sha || m.Released || m.Status != "prepared-not-release" { + t.Fatal("untouched tree inventory/provenance") + } + for _, surface := range m.Surfaces { + for _, entry := range surface.Commands { + if _, err := os.Stat(filepath.Join(a, entry.FileName)); err != nil { + t.Fatal(err) + } + } + } + seen := 0 + err = filepath.WalkDir(a, func(path string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + rel, _ := filepath.Rel(a, path) + first, err := os.ReadFile(path) + if err != nil { + return err + } + seen++ + if !bytes.Equal(first, expected[filepath.ToSlash(rel)]) { + t.Fatalf("disk differs from untouched tree: %s", rel) + } + second, err := os.ReadFile(filepath.Join(b, rel)) + if err != nil { + return err + } + if !bytes.Equal(first, second) { + t.Fatalf("nondeterministic file %s", rel) + } + if strings.HasSuffix(path, ".md") { + for _, match := range regexp.MustCompile(`\[[^\]]*\]\(([^)]+)\)`).FindAllStringSubmatch(string(first), -1) { + target := match[1] + if strings.HasPrefix(target, "https://github.com/777genius/universal-agent-plugins/blob/"+sha+"/") { + continue + } + if _, err := os.Stat(filepath.Join(filepath.Dir(path), target)); err != nil { + t.Fatalf("broken generated link %s -> %s", rel, target) + } + } + } + return nil + }) + if err != nil { + t.Fatal(err) + } + if seen != 35 { + t.Fatalf("disk file count: %d", seen) + } +} + +func TestCheckoutHEADMismatch(t *testing.T) { + // Minimal disposable Git metadata; no commit, checkout, or real profile edits. + checkout := t.TempDir() + for _, name := range []string{".git/objects", ".git/refs"} { + if err := os.MkdirAll(filepath.Join(checkout, name), 0700); err != nil { + t.Fatal(err) + } + } + if err := os.WriteFile(filepath.Join(checkout, ".git/HEAD"), []byte(strings.Repeat("1", 40)+"\n"), 0600); err != nil { + t.Fatal(err) + } + _, err := validateSource(checkout, factoryBaselineSHA) + if err == nil || !strings.Contains(err.Error(), "differs from checkout HEAD") { + t.Fatalf("wrong mismatch result: %v", err) + } +} diff --git a/cli/plugin-kit-ai/tools/authoring-docs/main.go b/cli/plugin-kit-ai/tools/authoring-docs/main.go new file mode 100644 index 00000000..2205927f --- /dev/null +++ b/cli/plugin-kit-ai/tools/authoring-docs/main.go @@ -0,0 +1,26 @@ +// authoring-docs is a preparation-only exporter, never a product entrypoint. +package main + +import ( + "flag" + "fmt" + "os" +) + +func main() { + flags := flag.NewFlagSet("authoring-docs", flag.ContinueOnError) + source := flags.String("source-sha", "", "exact checked-out source commit (required)") + checkout := flags.String("checkout", ".", "repository checkout") + out := flags.String("out-dir", "", "absent destination for prepared reference (required)") + if err := flags.Parse(os.Args[1:]); err != nil { + os.Exit(2) + } + if flags.NArg() != 0 { + fmt.Fprintln(os.Stderr, "unexpected positional arguments") + os.Exit(2) + } + if err := export(*checkout, *source, *out); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cli/plugin-kit-ai/tools/authoring-docs/source.go b/cli/plugin-kit-ai/tools/authoring-docs/source.go new file mode 100644 index 00000000..2b564ac6 --- /dev/null +++ b/cli/plugin-kit-ai/tools/authoring-docs/source.go @@ -0,0 +1,196 @@ +package main + +import ( + "bytes" + "crypto/sha256" + "fmt" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +// Audited factory identity, independent of the integrated adapter commit. +const factoryBaselineSHA = "070663efb27f69ecae8609e6b839f86f843efbb0" + +type sourcePin struct { + Path string `json:"path"` + SHA256 string `json:"sha256"` +} + +func git(checkout string, args ...string) ([]byte, error) { + cmd := exec.Command("git", append([]string{"-C", checkout}, args...)...) + cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") + body, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("source checkout verification failed") + } + return body, nil +} +func validateSource(checkout, sha string) ([]sourcePin, error) { + if !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(sha) { + return nil, fmt.Errorf("source identity must be a lowercase full Git SHA") + } + if checkout == "" { + return nil, fmt.Errorf("--checkout is required") + } + head, err := git(checkout, "rev-parse", "HEAD") + if err != nil { + return nil, err + } + if strings.TrimSpace(string(head)) != sha { + return nil, fmt.Errorf("source identity differs from checkout HEAD") + } + // A simple tracked cleanliness check includes staged and unstaged changes. + changed, err := git(checkout, "status", "--porcelain", "--untracked-files=no") + if err != nil { + return nil, err + } + if len(bytes.TrimSpace(changed)) != 0 { + return nil, fmt.Errorf("tracked checkout differs from source identity") + } + if err := validateInventory(checkout); err != nil { + return nil, err + } + pins := make([]sourcePin, 0, len(factoryPins)) + for _, pin := range factoryPins { + info, err := os.Lstat(filepath.Join(checkout, filepath.FromSlash(pin.Path))) + if err != nil { + return nil, fmt.Errorf("source input unavailable: %s", pin.Path) + } + if !info.Mode().IsRegular() { + return nil, fmt.Errorf("source input is not a regular file: %s", pin.Path) + } + if _, err := git(checkout, "ls-files", "--error-unmatch", "--", pin.Path); err != nil { + return nil, fmt.Errorf("source input not tracked: %s", pin.Path) + } + body, err := os.ReadFile(filepath.Join(checkout, filepath.FromSlash(pin.Path))) + if err != nil { + return nil, fmt.Errorf("source input unavailable: %s", pin.Path) + } + if fmt.Sprintf("%x", sha256.Sum256(body)) != pin.SHA256 { + return nil, fmt.Errorf("source input mismatch: %s", pin.Path) + } + pins = append(pins, pin) + } + return pins, nil +} + +// Pins cover construction packages (all non-test Go files, including definitions +// beside callbacks), target registry, composition references and all five +// workspace module controls. They do not recursively attest installer engines. +var factoryPins = []sourcePin{ + {"cli/plugin-kit-ai/cmd/agentplugins/release_root.go", "c0465f90903c7ad3fcdc2283c241558d1b73bd9633f0e6af247ccd692a0e155e"}, + {"cli/plugin-kit-ai/cmd/plugin-kit-ai/release_compat.go", "d2f11a845c116160114ddd1b0ba8b825e9e5a4794354742749db7122c0c674c8"}, + {"cli/plugin-kit-ai/go.mod", "c19d163f4cab4b7a3cdd6c71bec885809c81beaf4aee705071b90780bc9a15eb"}, + {"cli/plugin-kit-ai/go.sum", "8d0b8892b2f6528b9bfecf83d8efda5533ae696d2506c061df9bf4d88ede3eb0"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/add.go", "9a118d98465055c7793ab2fd31c5693757406e146164a3df9a64a46a410e42ba"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/add_multi.go", "f09cd6557f77f79641959f425b8065457972f3b1f35e1461620de0ff3e4cce0e"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/app.go", "763c3a847ffe3100eae78bb9d469948c228fd6eb48548a1752025ae35af91777"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/binding.go", "7f8d411ecdd59db01519c8a820ff64455f276ac575e29e7534cd4e716dc5bd52"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/engine_boundary.go", "68f3a74da353d3a12d506f948f542e1501ba05bbfc6d7b23b6c5130631518b52"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/interactive_targets.go", "ed172b27715c8c9bf69b01c37da47e84f4cb2df606f6bf701630987d5e27a705"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/legacy_controller.go", "4ba1296edf3c0cdc6e8caf0b27b577ad8715231f4201d4e8e2f56679f1713326"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/lifecycle.go", "083f12310c10c7f784d04f607b0948662cc3a92c32c852454114985d9c8b5b16"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/outdated.go", "3ecf284023dfb158e6c1c60572a57da99062ff1c68a1ecf105db677e7cb1f723"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/output.go", "571e944a73ac3cc89e8b66b84fb0274943913dd4ca2a28f953c5563d400ce8dc"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/preflight.go", "eb71e71febc417494f98445dcef15024878a034bc690f51ccaba31b9b89b7875"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/read.go", "02be006371e139e34f580f3340d23a70b7c6e85133f4bebfd6327720e47af6e1"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/read_directory.go", "a2ba8aa03cd1e8efc2a8b3b9aa8718aceb13822bb19d083d6fa32a76e316b9d1"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/read_reconciliation.go", "f04a6bcccb75beb83d93002dff694e9af240a8b29410d4bbafeaf52ab2edb102"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/remove_multi.go", "52dbe20bd0b3ce5a7b3df188f41cb863af3024667eda456410a1bffa48b983a2"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/repair_multi.go", "f0bc6745c9ec4b94cb066d28f705ebf421f8a524b71f40b70857617ecaf4d004"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/root.go", "97dac1600168a64835cc9aeeb998f56d5796e566572d9530f3f6537f8ad5ae04"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/search.go", "d954c3b68d6bcfe6b821855aed6c14e712b9ff0eff0bf67206a1191f714fa998"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/security.go", "ef8126625f12bdcf1aeedefdc11d243ea700c0b6050e44c9f566061426d06894"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/source.go", "fc9dca352dae1ea9bbe963195c4d6e2ab6d378002649faf84f5474044fb892b5"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/state_migration.go", "be360a4d68b99b40c983f4d01e64f4e81f2c29e2659400b58bd90a0b2f8bd28d"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/target_batch.go", "afa9dc9ba6a417229c57afda8a1eacbc397b38c0802880e1c14f5f8fc98fc81c"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/update_all.go", "31361a5cf6043f6a27c17cb89e0e88a34f11e4e96daf83454d9c38f729d7ba40"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/update_multi.go", "e80ac96ec11e57fbebe8f0d7ab780e80688060eb94480838be2804ca653463b5"}, + {"cli/plugin-kit-ai/internal/agentpluginscli/validate.go", "8c643a08c657431d623197364f5f47d7f23ed0b4e6cf926f40a521a8bac7ee9d"}, + {"cli/plugin-kit-ai/internal/authoring/commands/commands.go", "6f581be57435b8f269b80a6c1efca62e8369a1a7ca39dd215cecf15a1e59d568"}, + {"cli/plugin-kit-ai/internal/authoring/commands/public_contract.go", "39c79f491f0733d352ffc0fa3a8ff4eaa169612e8876e92e5fb74c856663ec65"}, + {"cli/plugin-kit-ai/internal/authoring/commands/skills.go", "566e36e02d58c5d76e92369987d901feb1bcb6f3e59d3225e02961fa041d606b"}, + {"cli/plugin-kit-ai/internal/authoring/commands/version.go", "ffe6cfef352faeb9a3c00722a628a2093876c14120cd6725131b3e105fda6b29"}, + {"cli/plugin-kit-ai/internal/authoringcli/command.go", "eaf18b18c8aeaecbc2e63c780a1472097855315a4bcdc22e7eb0386aa1375012"}, + {"cli/plugin-kit-ai/internal/authoringcli/flags.go", "5f745c810e90586899cff2170433dbc58483ba17496167738387388f29be49c0"}, + {"cli/plugin-kit-ai/internal/authoringcli/release.go", "45cb7a7000a31bb8477d1301fc70ce4521140b9f2a4760d5f84da3283134877f"}, + {"go.mod", "352e528c2b2c34df21276c1f9b54c32f79656dad42c310da6b551f022119ce8c"}, + {"go.work", "b5c80635a9629f9f894201bb9ac80a9e29413f0a90d7a2f761ef4dbe1387623a"}, + {"go.work.sum", "c5123e0ef46e576f2663bd0d0f86a68b567b29b0d4682d64b2ebaa5f5d7fcefa"}, + {"install/integrationctl/agentplugins/domain/acquisition.go", "ea60178232db888d8df99a7b6722bb26b7a3f1a23776a6be041cf9c84f7a2cb3"}, + {"install/integrationctl/agentplugins/domain/catalog.go", "e70853260635bed305ce77fef0c420e2028a57d77a9ce0565ab9cf8b275a98bc"}, + {"install/integrationctl/agentplugins/domain/clients.go", "152167e659bb40114f06532769e85bc1b58668465e2c4c6adc011433c0a0fd63"}, + {"install/integrationctl/agentplugins/domain/directory.go", "d917f80acd770d5749c0b95ce714df9166438aec9c51ca1c16b2d52f23b36168"}, + {"install/integrationctl/agentplugins/domain/errors.go", "7229bf792c60bcbb290050a6659f129f09e85e22174d32ac681a4c6e5bb6efd8"}, + {"install/integrationctl/agentplugins/domain/identity.go", "e4886804b8d35c7b54ce3b87a96e50de69a6248781bd8349a4f59d32512c6c8d"}, + {"install/integrationctl/agentplugins/domain/security.go", "6773e2d0fc94bf6cf8a521c1c1dd8a339648bf1634ef5eebebc2917b06ff0abd"}, + {"install/integrationctl/agentplugins/domain/state.go", "2db19f678782e8d9644e85ca17517cc54ff395b63a4b1b0bb52c8a0454a1869c"}, + {"install/integrationctl/agentplugins/domain/types.go", "30757c8faac4a3b67771169e1acc5aa798c61340e2db50b634614ddbe7b1b9da"}, + {"install/integrationctl/go.mod", "17c94b7bcbace5f9e4ee6164e0ff8499b7994923105c9430c84de7964b416116"}, + {"install/integrationctl/go.sum", "55d21b3e3f4a7928cf9064f7ccd06cf54b643097cb13c1b776b07a6449b85458"}, + {"install/plugininstall/go.mod", "7d0745754d1ae04fb31d5056cbd2f4fa82d711d0536c1c868f40452cf5e6bb53"}, + {"sdk/go.mod", "79e8f3d4903364c498fffcf34e9394f84437ae198dcefcd1c0c99cb41c80ba5a"}, +} + +// Exact direct file sets, regardless of ignore rules or platform suffixes. +// Adapter bytes are reviewed with its commit, not self-hashed into that commit. +var constructionDirs = []string{ + "cli/plugin-kit-ai/internal/agentpluginscli", + "cli/plugin-kit-ai/internal/authoring/commands", + "cli/plugin-kit-ai/internal/authoringcli", + "install/integrationctl/agentplugins/domain", +} +var adapterFiles = []string{"export.go", "export_test.go", "main.go", "source.go", "source_test.go"} + +func validateInventory(checkout string) error { + allowed := map[string]bool{} + for _, pin := range factoryPins { + allowed[pin.Path] = true + } + const adapter = "cli/plugin-kit-ai/tools/authoring-docs" + for _, name := range adapterFiles { + allowed[adapter+"/"+name] = true + } + for _, dir := range append(append([]string{}, constructionDirs...), adapter) { + entries, err := os.ReadDir(filepath.Join(checkout, filepath.FromSlash(dir))) + if err != nil { + return fmt.Errorf("source directory unavailable: %s", dir) + } + for _, e := range entries { + name := dir + "/" + e.Name() + if !strings.HasSuffix(e.Name(), ".go") { + continue + } + if dir != adapter && strings.HasSuffix(e.Name(), "_test.go") { + continue + } + if !allowed[name] || e.Type()&os.ModeSymlink != 0 || e.IsDir() { + return fmt.Errorf("unexpected Go input: %s", name) + } + } + } + for _, name := range adapterFiles { + path := adapter + "/" + name + if _, err := os.Stat(filepath.Join(checkout, path)); err != nil { + return fmt.Errorf("adapter input unavailable: %s", path) + } + if _, err := git(checkout, "ls-files", "--error-unmatch", "--", path); err != nil { + return fmt.Errorf("adapter input not tracked: %s", path) + } + } + // Absent control files are also part of the effective workspace contract. + for _, dir := range []string{"", "cli/plugin-kit-ai/", "install/integrationctl/", "install/plugininstall/", "sdk/"} { + for _, name := range []string{"go.mod", "go.sum", "go.work", "go.work.sum", "vendor"} { + path := dir + name + if !allowed[path] { + if _, err := os.Lstat(filepath.Join(checkout, path)); !os.IsNotExist(err) { + return fmt.Errorf("unexpected dependency control: %s", path) + } + } + } + } + return nil +} diff --git a/cli/plugin-kit-ai/tools/authoring-docs/source_test.go b/cli/plugin-kit-ai/tools/authoring-docs/source_test.go new file mode 100644 index 00000000..f257024e --- /dev/null +++ b/cli/plugin-kit-ai/tools/authoring-docs/source_test.go @@ -0,0 +1,262 @@ +package main + +import ( + "bytes" + "encoding/json" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/spf13/cobra" +) + +func fixtureGit(t *testing.T, dir string, args ...string) string { + t.Helper() + c := exec.Command("git", append([]string{"-C", dir}, args...)...) + c.Env = append(os.Environ(), "GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null", "GIT_AUTHOR_NAME=Docs Fixture", "GIT_AUTHOR_EMAIL=docs@example.invalid", "GIT_COMMITTER_NAME=Docs Fixture", "GIT_COMMITTER_EMAIL=docs@example.invalid") + b, e := c.CombinedOutput() + if e != nil { + t.Fatalf("git %v: %v: %s", args, e, b) + } + return strings.TrimSpace(string(b)) +} +func checkoutSHA(t *testing.T, dir string) string { + t.Helper() + return fixtureGit(t, dir, "rev-parse", "HEAD") +} +func writeFixture(t *testing.T, dir, name string, body []byte) { + t.Helper() + path := filepath.Join(dir, filepath.FromSlash(name)) + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, body, 0600); err != nil { + t.Fatal(err) + } +} +func commitFixture(t *testing.T, dir string) string { + t.Helper() + fixtureGit(t, dir, "add", ".") + fixtureGit(t, dir, "-c", "commit.gpgsign=false", "commit", "-qm", "docs fixture") + return checkoutSHA(t, dir) +} + +// Mutations use disposable Git metadata only. This minimal source-contract +// fixture is supplemental; DOCS_TEST_CHECKOUT makes the disk tests use the full +// clean integrated checkout on which the focused suite is being built. +func committedFixture(t *testing.T) string { + t.Helper() + if dir := os.Getenv("DOCS_TEST_CHECKOUT"); dir != "" { + return dir + } + return newSourceFixture(t) +} +func newSourceFixture(t *testing.T) string { + t.Helper() + root, err := filepath.Abs("../../../..") + if err != nil { + t.Fatal(err) + } + dir := t.TempDir() + paths := []string{"docs/PHASE6_CLI_EXPORTER_PREPARATION.md"} + for _, pin := range factoryPins { + paths = append(paths, pin.Path) + } + for _, name := range adapterFiles { + paths = append(paths, "cli/plugin-kit-ai/tools/authoring-docs/"+name) + } + for _, path := range paths { + b, e := os.ReadFile(filepath.Join(root, path)) + if e != nil { + t.Fatal(e) + } + writeFixture(t, dir, path, b) + } + fixtureGit(t, dir, "init", "-q") + commitFixture(t, dir) + return dir +} +func rejectedBeforeOutput(t *testing.T, dir, sha, want string, load func() ([]*cobra.Command, error)) { + t.Helper() + out := filepath.Join(t.TempDir(), "output") + err := exportTrees(dir, sha, out, load) + if err == nil || !strings.Contains(err.Error(), want) { + t.Fatalf("want %q, got %v", want, err) + } + if _, err := os.Stat(out); !os.IsNotExist(err) { + t.Fatal("rejected export created output") + } +} +func TestCommittedSourceAndDocsOnlyCommit(t *testing.T) { + dir := newSourceFixture(t) + first := checkoutSHA(t, dir) + a := filepath.Join(t.TempDir(), "a") + if err := export(dir, first, a); err != nil { + t.Fatal(err) + } + writeFixture(t, dir, "docs/later.md", []byte("docs only\n")) + second := commitFixture(t, dir) + b := filepath.Join(t.TempDir(), "b") + if err := export(dir, second, b); err != nil { + t.Fatal(err) + } + for _, base := range []struct{ path, sha string }{{a, first}, {b, second}} { + body, err := os.ReadFile(filepath.Join(base.path, namespace, "manifest.json")) + if err != nil { + t.Fatal(err) + } + var m manifest + if err := json.Unmarshal(body, &m); err != nil { + t.Fatal(err) + } + if m.SourceSHA != base.sha || m.FactoryBaseline != factoryBaselineSHA || m.Released || m.Status != "prepared-not-release" { + t.Fatal("provenance/status") + } + count := 0 + for _, s := range m.Surfaces { + for _, e := range s.Commands { + count++ + if _, err := os.Stat(filepath.Join(base.path, e.FileName)); err != nil { + t.Fatal(err) + } + } + } + if count != 34 { + t.Fatal(count) + } + } + err := filepath.WalkDir(a, func(path string, d os.DirEntry, err error) error { + if err != nil || d.IsDir() { + return err + } + rel, _ := filepath.Rel(a, path) + x, err := os.ReadFile(path) + if err != nil { + return err + } + y, err := os.ReadFile(filepath.Join(b, rel)) + if err != nil { + return err + } + if !bytes.Equal(bytes.ReplaceAll(x, []byte(first), []byte("SHA")), bytes.ReplaceAll(y, []byte(second), []byte("SHA"))) { + t.Fatalf("non-provenance difference: %s", rel) + } + return nil + }) + if err != nil { + t.Fatal(err) + } +} +func TestSourceDriftRejections(t *testing.T) { + cases := []struct { + name, path, body, want string + remove, stage, commit bool + }{ + {name: "unstaged", path: "docs/PHASE6_CLI_EXPORTER_PREPARATION.md", body: "dirty", want: "tracked checkout"}, + {name: "staged", path: "docs/PHASE6_CLI_EXPORTER_PREPARATION.md", body: "dirty", want: "tracked checkout", stage: true}, + {name: "factory-root", path: "cli/plugin-kit-ai/internal/agentpluginscli/root.go", body: "package agentpluginscli\n", want: "source input mismatch", commit: true}, + {name: "factory-flags", path: "cli/plugin-kit-ai/internal/authoringcli/flags.go", body: "package authoringcli\n", want: "source input mismatch", commit: true}, + {name: "target-helper", path: "cli/plugin-kit-ai/internal/agentpluginscli/target_batch.go", body: "package agentpluginscli\n", want: "source input mismatch", commit: true}, + {name: "client-registry", path: "install/integrationctl/agentplugins/domain/clients.go", body: "package domain\n", want: "source input mismatch", commit: true}, + {name: "workspace-replace", path: "go.work", body: "go 1.25.0\nuse ./cli/plugin-kit-ai\nreplace github.com/spf13/pflag => ./override\n", want: "source input mismatch", commit: true}, + {name: "workspace-module-dependency", path: "install/integrationctl/go.mod", body: "module github.com/777genius/plugin-kit-ai/install/integrationctl\ngo 1.25.0\nrequire github.com/spf13/pflag v1.0.8\n", want: "source input mismatch", commit: true}, + {name: "missing-factory", path: "cli/plugin-kit-ai/internal/agentpluginscli/target_batch.go", remove: true, want: "source input unavailable", commit: true}, + {name: "new-committed-go", path: "cli/plugin-kit-ai/internal/authoringcli/new.go", body: "package authoringcli\nfunc init() {}\n", want: "unexpected Go input", commit: true}, + {name: "untracked-go", path: "cli/plugin-kit-ai/internal/authoringcli/new.go", body: "package authoringcli\n", want: "unexpected Go input"}, + {name: "ignored-go", path: "cli/plugin-kit-ai/internal/authoringcli/ignored.go", body: "package authoringcli\n", want: "unexpected Go input"}, + {name: "adapter-go", path: "cli/plugin-kit-ai/tools/authoring-docs/new.go", body: "package main\n", want: "unexpected Go input"}, + {name: "ignored-adapter-go", path: "cli/plugin-kit-ai/tools/authoring-docs/ignored.go", body: "package main\n", want: "unexpected Go input"}, + {name: "missing-adapter", path: "cli/plugin-kit-ai/tools/authoring-docs/main.go", remove: true, want: "adapter input unavailable", commit: true}, + {name: "nested-workspace", path: "cli/plugin-kit-ai/go.work", body: "go 1.25.0\nuse .\n", want: "unexpected dependency control"}, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + dir := newSourceFixture(t) + if strings.Contains(c.name, "ignored") { + writeFixture(t, dir, ".git/info/exclude", []byte("ignored.go\n")) + } + if c.remove { + if err := os.Remove(filepath.Join(dir, c.path)); err != nil { + t.Fatal(err) + } + } else { + writeFixture(t, dir, c.path, []byte(c.body)) + } + if c.stage { + fixtureGit(t, dir, "add", c.path) + } + if c.commit { + commitFixture(t, dir) + } + rejectedBeforeOutput(t, dir, checkoutSHA(t, dir), c.want, trees) + }) + } + t.Run("unrelated-untracked", func(t *testing.T) { + dir := newSourceFixture(t) + writeFixture(t, dir, "unrelated/new.go", []byte("unrelated")) + if _, err := validateSource(dir, checkoutSHA(t, dir)); err != nil { + t.Fatal(err) + } + }) + t.Run("empty-checkout", func(t *testing.T) { rejectedBeforeOutput(t, "", factoryBaselineSHA, "--checkout is required", trees) }) +} +func TestLoadedProjectionMismatch(t *testing.T) { + dir := newSourceFixture(t) + for _, fact := range []string{"help", "flag", "markdown"} { + t.Run(fact, func(t *testing.T) { + rejectedBeforeOutput(t, dir, checkoutSHA(t, dir), "loaded documentation projection mismatch", func() ([]*cobra.Command, error) { + roots, err := trees() + if err != nil { + return nil, err + } + switch fact { + case "help": + roots[0].Short += " changed" + case "flag": + roots[1].Parent().PersistentFlags().Lookup("target").Usage += " changed" + case "markdown": + roots[0].Run = func(*cobra.Command, []string) { t.Fatal("executed") } + } + return roots, nil + }) + }) + } +} + +// Exercise Go's effective selection, not just arbitrary changed control bytes. +// Only module metadata is queried; no product compilation or dependency fetch. +func TestEffectiveDependencyDrift(t *testing.T) { + for _, control := range []string{"go.work", "install/integrationctl/go.mod"} { + t.Run(control, func(t *testing.T) { + dir := newSourceFixture(t) + replacement := filepath.Join(filepath.Dir(control), "docs-pflag-replacement") + writeFixture(t, dir, filepath.ToSlash(filepath.Join(replacement, "go.mod")), []byte("module github.com/spf13/pflag\n\ngo 1.22\n")) + path := filepath.Join(dir, control) + body, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + body = append(body, []byte("\nreplace github.com/spf13/pflag => ./docs-pflag-replacement\n")...) + writeFixture(t, dir, control, body) + sha := commitFixture(t, dir) + cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "list", "-m", "-json", "github.com/spf13/pflag") + cmd.Dir = filepath.Join(dir, "cli/plugin-kit-ai") + cmd.Env = append(os.Environ(), "GOWORK="+filepath.Join(dir, "go.work"), "GOFLAGS=", "GOENV=off", "GOTOOLCHAIN=local", "GOPROXY=off", "GOSUMDB=off", "GOMAXPROCS=2") + selected, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("effective dependency query: %v: %s", err, selected) + } + var module struct{ Replace *struct{ Dir string } } + if err := json.Unmarshal(selected, &module); err != nil { + t.Fatal(err) + } + if module.Replace == nil || module.Replace.Dir != filepath.Join(dir, replacement) { + t.Fatalf("replacement did not become effective: %s", selected) + } + rejectedBeforeOutput(t, dir, sha, "source input mismatch", trees) + }) + } +} diff --git a/docs/PHASE6_CLI_EXPORTER_PREPARATION.md b/docs/PHASE6_CLI_EXPORTER_PREPARATION.md new file mode 100644 index 00000000..ed7c5a28 --- /dev/null +++ b/docs/PHASE6_CLI_EXPORTER_PREPARATION.md @@ -0,0 +1,148 @@ +# Phase 6 D2a CLI exporter preparation + +Status: prepared reference only; not a public release. Source baseline: +`070663efb27f69ecae8609e6b839f86f843efbb0`. + +The separate executable lives at `cli/plugin-kit-ai/tools/authoring-docs` in +module `github.com/777genius/plugin-kit-ai/cli`, allowing existing internal +imports. It adds no runtime registration and changes no production factory. +The old `__docs` exporter, website extraction, navigation, and generated v1 +reference remain untouched. D2b integration follows D1 and independent review. + +## Existing integration seam + +`internal/authoring/commands/version.go:61` exports `App.ReleaseSelection`. +It calls the shared `releaseTree`, constructs implemented command factories +using `App.command` and `skillsCommand`, adds the shared version command, +initializes help flags and completion utilities, and observes selection. +With nil arguments it does not execute commands or completion hooks. This is +the usable API; the unexported `App.command` alone would not be sufficient. +No interception of `App.Execute`, copied definitions, or engine fork is needed. + +The adapter sets `PublicContract: true` explicitly and uses +`authoringcli.NewReleasePluginKitRoot` for the plugin-kit-ai surface. For the +second surface it composes `agentpluginscli.NewRoot` and +`authoringcli.NewReleaseAuthorCommand`, matching +`cmd/agentplugins/release_root.go:20`. Only the attached author subtree is +exported; keeping the real ancestor preserves installer persistent flag facts. +No installer service is configured and no product command is executed. + +The runtime plugin-kit-ai wrapper in `cmd/plugin-kit-ai/release_compat.go:82` +adds hidden compatibility flags and hidden rejection nodes. Its visible +commands and flags come from the same shared root. The adapter deliberately +uses that shared root without installing retirement shims. Visible Cobra help +and completion utility definitions are included, but their callbacks are never +called. Hidden ancestors, rejection annotations and internal `__` names are +pruned before rendering, including their descendant links. + +## Prepared output contract + +The caller supplies an exact `--source-sha`, `--checkout` repository root and +an absent `--out-dir` whose parent already exists. `source_sha` is the exact +clean integrated checkout HEAD; `factory_baseline_sha` separately remains +`070663efb27f69ecae8609e6b839f86f843efbb0`. A committed adapter and subsequent +docs-only commits work without repinning their own commit identity. Ancestry +alone is never acceptance: the factory pins and bounded file inventory must +still match. All tracked staged/unstaged changes fail the simple cleanliness +check. Missing or unexpected relevant Go inputs fail even when ignored. + +The source inventory in `source.go` covers all non-test Go files directly in +`internal/authoring/commands`, `internal/authoringcli`, +`internal/agentpluginscli` (under the CLI module), and +`install/integrationctl/agentplugins/domain`. This includes constructor +configuration, the installer root's other constructed commands, the +`target_batch.go` → `domain/clients.go` help/registry chain, and other same-package +initializers. Runtime callbacks in these packages are pinned as part of their +files; their transitive engine implementations, YAML assets and examples are +outside the help inventory. Existing `_test.go` files in factory packages do not +contribute to the docs binary. The adapter's complete Go file set, including its +focused tests, must be tracked; additional adapter Go files are rejected. +The two `cmd/*` wrapper pins are composition references, not linked packages. + +All five workspace modules' existing go.mod/go.sum, root go.work/go.work.sum, +and the absence of nested workspace controls, extra sum files and vendor +directories are checked. These controls fix workspace use/local replacements +and module version selection, including Cobra v1.10.2, pflag v1.0.9 and the +Cobra/doc dependency go-md2man/v2 v2.0.6. This is a bounded documentation input +contract, not a digest of the repository or installer runtime. + +Before any output creation the tool renders the actual loaded trees with the +literal provenance token `SOURCE_SHA`, no source pin array, and no host paths. +It hashes sorted, length-framed filenames and bytes (both manifest facts and +Cobra Markdown), and compares with `reviewedProjection`. Thus a binary compiled +with different help/flags cannot stamp the validated checkout SHA on different +facts. The golden covers untouched factory trees, not the hook-trap trees used +by the no-action test. Normal output uses the caller's exact SHA, including the +author parent source link. Across commits only these provenance bytes differ. + +Supported builds use the reviewed adapter source, Go 1.25.13, the root workspace +and the existing checksum-verified module cache. Set GOENV=off, +GOTOOLCHAIN=local, GOPROXY=off, GOSUMDB=off, GOFLAGS empty and GOWORK to the +absolute checkout go.work; use private HOME/TMP/GOCACHE and GOMAXPROCS=2 with +`go test -p 2` / `go build -p 2`. No overlays, build tags, alternate workspaces, +vendor mode, linker substitutions or modified module-cache source are supported. +The fingerprint verifies exported facts even for an ordinary mismatched source +build; it does not authenticate a deliberately altered verifier or arbitrary +binary. Factory changes require a fresh bounded source/help review, updated +input pins and an explicitly reviewed projection golden, not an automatic +regeneration or self-commit SHA constant. Adapter-only changes that preserve +facts need no new factory baseline. + +Output lives only under `prepared-authoring-v2/` inside the new destination: + +- `manifest.json`: `authoring-docs-manifest-v1` envelope with namespace, + `prepared-not-release` status, `released: false`, exact source SHA, audited factory baseline SHA and relative + source file SHA-256 pins, plus the two surfaces. +- Markdown files named from the actual command path with underscores, e.g. + `plugin-kit-ai_init.md` and `agentplugins_author_init.md`. Every page carries + prepared status and the source SHA. Cobra's timestamp footer is disabled. +- Command identities use `prepared-authoring-v2:` and slugs use + the same prepared prefix. The manifest contains actual Use/Short/Long/Example, + aliases, deprecation text and separate local/inherited flag arrays with type, + default, optional-value default, shorthand and help text. + +The manifest intentionally differs from the old `docsManifestEntry[]` format, +which had command facts but no source envelope or flag arrays. D2b must consume +this namespace explicitly, never silently replace v1 records or mark these +records public-stable. Ordinary Markdown links remain within this namespace. +The author group's parent link points to the pinned installer root source, +because this bounded export has no installer root reference page. + +Flag presence is a help fact, not a promise of accepted execution. In particular, +installer-only inherited flags are retained with the shared rejection wording; +`--dry-run` and `--target` retain their actual help and support qualifications. +The exporter never interprets a successful generation as product validation, +provider readiness, profile discovery, installation, or publication approval. + +Output creation refuses any existing directory, including a v1 destination. +A write failure may leave a partial new directory; use another disposable +output directory after addressing the failure. It performs no cleanup or +replacement. Output content is deterministic; filesystem metadata is not part +of the contract. Git is used only for source identity reads. + +## Bounded validation and downstream gates + +Run only the adapter package with Go 1.25.13, `-p 2`, `GOMAXPROCS=2`, offline +module lookup and private HOME/TMPDIR/GOCACHE. The supplied host contains the +requested version at `toolchain/go/bin/go`; `go1.25.13` is not a filename there. +The external handoff records the exact verified executable and environment. +No dependency was added; the existing Cobra docs dependency renders Markdown. + +Focused tests cover fresh-tree and on-disk byte determinism, the exact two +surfaces and utility inventory, Markdown links, local/inherited flag defaults +and help, hidden/rejection/internal descendant exclusion, source identity +rejection before writes, clean committed and later docs-only fixtures, changed +factory/target-registry/workspace/dependency inputs, ignored/adapter Go additions, +changed loaded help/flag/Markdown facts, no v1 overwrite, hook traps and untouched private +profile directories. They do not claim product/native/npm suite coverage or +OS release acceptance. Set DOCS_TEST_CHECKOUT to the absolute clean checkout +being tested to run the disk checks against that full committed tree; otherwise +they use a disposable committed source-contract fixture. Mutation tests always +use private fixture Git metadata and never mutate the source checkout. Existing Windows/macOS holds remain unchanged; no +restricted Windows reproduction was attempted or rerouted. + +ROOT mechanical integration and independent medium review remain downstream. +D2b must preserve v1 source and routes, use a separately accepted integrated +source identity, and consume these prepared records only after D1 integration. +No website build, package publication, push, PR, or public activation is part +of D2a. Migration remains unavailable in v2; historical v1 baseline is 1.2.4.