feature

package
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package feature models parallel feature development inside one cloop project (Task 20341).

A project has one task list and one set of run settings, because it has one .cloop/ directory and one working tree. That is the right shape for a single line of work and the wrong one for three: two features developed in the same checkout edit the same files, their tasks interleave in one plan, and there is no way to say "evolve this one, but let that one finish and stop".

A feature is the smallest unit that removes all three problems at once: a git worktree of the project's repository, on its own branch, with its own .cloop/ directory. Everything cloop already knows how to do to a project — run it, stop it, evolve it, edit its tasks, toggle innovate mode — it can then do to the feature without learning anything new, because a feature *is* a project as far as the orchestrator is concerned. What this package adds is the part that is not: where features live, how one is recognised, and what is recorded about it.

Layout

<project>/.cloop/features/<slug>/            the worktree (branch cloop/feature/<slug>)
<project>/.cloop/features/<slug>/.cloop/     the feature's own state, config and task list
<project>/.cloop/features/<slug>/.cloop/feature.json   what this package records

Inside the parent's .cloop/ rather than beside the project, for three reasons. A feature is then deleted with its project and never outlives it on disk; an executor that bind-mounts the project directory can reach it; and — the one that decides it — the parent is derivable from the path alone. Inheritance of the parent's executor binding, grants and authorization (see ParentOf) is a security property, and one that holds by construction is better than one that holds because a registry entry says so.

The cost of that choice is that everything which treats "a directory below the project" as "the project" has to learn that a feature subtree is not. The process-scoping helpers in pkg/multiui and the .cloop/ walkers in pkg/snapshot, pkg/diskusage and `cloop clean` are the places that do.

What lives here and what does not

This package is pure: it reads and writes files and never runs a process. That is deliberate, because pkg/ui — which may not spawn processes, see pkg/ui/no_direct_exec_test.go — lists features on every projects refresh. Creating, removing and publishing a feature means running git and talking to GitHub, and lives in pkg/featureops, which only the CLI imports.

Index

Constants

View Source
const (
	// ControlDir is the name of a project's control directory.
	ControlDir = ".cloop"

	// DirName is the directory under ControlDir that holds feature worktrees.
	DirName = "features"

	// MetaFile is the feature's own record, inside the feature's ControlDir.
	MetaFile = "feature.json"

	// BranchPrefix names every feature branch. It sits inside the cloop/
	// namespace on purpose: that is the git interception proxy's default
	// write-back allowlist (gitproxy.DefaultAllowedRef), so a sandbox can push
	// a feature branch through the proxy without anyone widening a policy —
	// and can still not push main.
	BranchPrefix = "cloop/feature/"

	// MaxSlugLen bounds a slug. Slugs become directory names and branch
	// components; 40 keeps both readable and far from any path limit.
	MaxSlugLen = 40

	// MaxPerProject bounds how many features one project may hold. Each one is
	// a full checkout plus a harness that can run concurrently with the rest,
	// so an unbounded count is an unbounded amount of disk and parallelism
	// reachable by anyone who can create one. The figure is generous for
	// people and small for a loop.
	MaxPerProject = 32
)

Variables

This section is empty.

Functions

func BranchName

func BranchName(slug string) string

BranchName returns the branch a feature is developed on.

func Dir

func Dir(projectDir string) string

Dir returns the directory holding a project's feature worktrees.

func InsideFeature

func InsideFeature(path string) (featureRoot string, ok bool)

InsideFeature reports whether path is a feature root or anything below one, returning that feature's root. Used by the process-scoping helpers, which see working directories such as <feature>/.cloop/worktrees/task-3 and have to attribute them to the feature rather than to its parent.

func IsFeature

func IsFeature(path string) bool

IsFeature reports whether path is the root of a real feature: shaped like one (ParentOf) and carrying a readable record for the same slug.

The record's Parent field is deliberately not compared with the parent the path implies. The path is authoritative — it is what inheritance follows — and a project directory that was moved or reached through a different mount would otherwise lose every feature it has to a string mismatch in a file nobody edits. The slug is compared because a record copied into the wrong directory is a record describing some other feature.

func MetaPath

func MetaPath(featureDir string) string

MetaPath returns the path of a feature's record.

func Orphans

func Orphans(projectDir string) (map[string]string, error)

Orphans returns the directories under .cloop/features that List skipped, with the reason, so an operator can see what is taking up room.

func PRBody

func PRBody(m *Meta, tasks []*pm.Task) string

PRBody renders the description of a feature's pull request from its record and its task list: what the feature is for, which tasks were completed, and which were not — so a reviewer can tell a finished feature from one being proposed early without opening the dashboard.

It is deterministic on purpose. The PR is opened from a button, often by someone other than the person reviewing it, and a description that changes on every click — or that needs a model call to exist at all — is a worse default than one that says exactly what the plan says.

func PRTitle

func PRTitle(m *Meta) string

PRTitle is the title a feature's pull request gets when none is given.

func ParentOf

func ParentOf(path string) (parent, slug string, ok bool)

ParentOf reports whether path is, by its shape, the root of a feature worktree — <parent>/.cloop/features/<slug> with a valid slug — and if so returns the parent project directory and the slug.

It is purely lexical. It does not check that anything exists, which is what makes it usable from hot paths (every executor dispatch consults it) and what makes it trustworthy: the answer cannot be changed by writing a file. Callers that need to know a feature is real rather than merely well-named use IsFeature.

A nested feature — a feature of a feature — is not recognised: the parent of such a path would itself be a feature, and ParentOf reports false for it. Features are one level deep by design (see pkg/featureops.Create).

func Path

func Path(projectDir, slug string) string

Path returns where the feature named slug lives inside projectDir.

func SaveMeta

func SaveMeta(featureDir string, m *Meta) error

SaveMeta writes a feature's record atomically. The control directory must exist — it is created with the feature's state.

func Slugify

func Slugify(name string) string

Slugify derives a slug from a human name: lowercase, runs of anything other than a letter or digit collapsed to one hyphen, trimmed, and cut to MaxSlugLen. It returns "" when nothing usable is left, which ValidSlug then rejects with a message naming the input.

func ValidSlug

func ValidSlug(slug string) error

ValidSlug reports whether slug can name a feature.

Types

type Info

type Info struct {
	// Path is the feature's worktree, absolute.
	Path string
	Meta *Meta
}

Info is one discovered feature.

func List

func List(projectDir string) ([]Info, error)

List returns the features of projectDir, oldest first.

It only reads files. A directory under .cloop/features that has no valid record — a half-created feature, a worktree someone made by hand, the leftovers of a crash — is skipped rather than reported: listing is what the dashboard does every two seconds, and it must describe what is usable, not fail on what is not. `cloop feature list` reports the skipped ones.

A project that is itself a feature has no features.

type Meta

type Meta struct {
	Version int    `json:"version"`
	Slug    string `json:"slug"`
	Title   string `json:"title"`
	// Description is the feature's goal as the user wrote it.
	Description string `json:"description,omitempty"`
	Branch      string `json:"branch"`
	// Base is the branch the feature was cut from and the one its pull
	// request targets.
	Base string `json:"base"`
	// BaseCommit is the commit Base pointed at when the feature was created.
	BaseCommit string `json:"base_commit,omitempty"`
	// Parent is the project directory this feature belongs to, absolute.
	Parent    string    `json:"parent"`
	CreatedAt time.Time `json:"created_at"`
	CreatedBy string    `json:"created_by,omitempty"`
	// AutoPR asks the hub to open the pull request by itself once the
	// feature's plan completes.
	AutoPR bool `json:"auto_pr,omitempty"`
	PR     *PR  `json:"pr,omitempty"`
}

Meta is a feature's record, persisted as .cloop/feature.json inside the feature's worktree.

func LoadMeta

func LoadMeta(featureDir string) (*Meta, error)

LoadMeta reads a feature's record from its worktree. The record must be a regular file: see realFeatureDir for why a link is never followed here.

func (*Meta) Validate

func (m *Meta) Validate() error

Validate checks a record's invariants before it is written or trusted.

type PR

type PR struct {
	Number int    `json:"number"`
	URL    string `json:"url"`
	// State is GitHub's state as last seen: open, closed or merged.
	State string `json:"state"`
	// Repo is owner/name on the forge the PR was opened on.
	Repo string `json:"repo,omitempty"`
	Head string `json:"head,omitempty"`
	Base string `json:"base,omitempty"`
	// HeadSHA is the commit the branch pointed at when the PR was opened or
	// last refreshed, so the dashboard can tell "the PR shows this work" from
	// "there are commits the PR does not have yet".
	HeadSHA   string    `json:"head_sha,omitempty"`
	Draft     bool      `json:"draft,omitempty"`
	CreatedAt time.Time `json:"created_at"`
}

PR is what is recorded about a feature's pull request.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL