Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .aider.conf.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# <!-- wellmanifest:source-links:v1 -->
# Managed standard sources: local .governance/manifest.json,
# .governance/manifest.lock.json and .governance/package-manifest.json are
# authoritative. Remote links are navigation only and are never fetched at runtime.
# Canonical instructions: https://github.com/wellmanifest/new-project/blob/main/template/files/AGENTS.template.md
# Host contract: https://github.com/wellmanifest/new-project/blob/main/governance/agent-hosts.json
# Immutable adoption/updater: https://github.com/wellmanifest/new-project/blob/main/scripts/create_adoption_lock.py
# <!-- end wellmanifest:source-links:v1 -->
# wellmanifest/new-project — fail-closed contract for aider.
# aider loads these files into every session, so the same rules apply here as
# in Cursor, Claude Code or Gemini. The pre-commit hook enforces them.
read:
- AGENTS.md
- CLAUDE.md

# Never let the tool create commits the governance hook has not seen.
auto-commits: false
attribute-commit-message-author: true

# Bounded session controls: respect the ticket's maxActiveMinutes, create a
# checkpoint before a context or tool boundary, and leave a handoff then stop
# after a deterministic failure instead of retrying indefinitely.
36 changes: 36 additions & 0 deletions .cursor/rules/new-project-standard.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description: wellmanifest/new-project fail-closed ticket contract for every Cursor session
alwaysApply: true
---

<!-- wellmanifest:source-links:v1 -->
## Managed standard sources

Local adoption files are authoritative. Remote links are navigation only and
must not be fetched or executed at runtime.

- Local adoption manifest: [.governance/manifest.json](.governance/manifest.json)
- Local adoption lock: [.governance/manifest.lock.json](.governance/manifest.lock.json)
- Local package map: [.governance/package-manifest.json](.governance/package-manifest.json)
- Canonical instructions: [AGENTS template](https://github.com/wellmanifest/new-project/blob/main/template/files/AGENTS.template.md)
- Host contract: [agent-hosts.json](https://github.com/wellmanifest/new-project/blob/main/governance/agent-hosts.json)
- Immutable adoption/updater: [create_adoption_lock.py](https://github.com/wellmanifest/new-project/blob/main/scripts/create_adoption_lock.py)

<!-- end wellmanifest:source-links:v1 -->

# new-project standard (host-agnostic)

Before writing code in this repository:

1. Read `AGENTS.md` and the active ticket. Reuse an `IN_PROGRESS` ticket only when workstream and scope match.
2. Otherwise run `./project/new-ticket.sh --title "..." --agent "..." --workstream "..."`.
3. Work in a dedicated worktree/branch whose name contains `ticket-NNN`. Do not commit on `main` or a dirty primary checkout.
4. Stay inside `intent.json` `allowedPaths`. Commercial SSOT / offer / brand facades need the integration workstream.
5. Run `./scripts/install-agent-hosts.sh` once per clone so `.githooks/pre-commit` is active.
6. Run `./project/governance-check.sh` before claiming done.

The pre-commit hook rejects commits that are not bound to an `IN_PROGRESS` ticket. Do not invent ticket numbers. Do not ask the human to approve a merge; invoke validator-agent when publication needs trusted approval.

Bounded session controls: respect the ticket's `maxActiveMinutes`, create a
`checkpoint` before a context or tool boundary, and leave a `handoff` then
`stop` after a deterministic failure instead of retrying indefinitely.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Wellmanifest new-project standard git attributes
project/TICKETS.md merge=wellmanifest-ticket-index
TODO.md merge=wellmanifest-ticket-index
140 changes: 140 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/usr/bin/env bash
# Managed adopter hook: bind implementation to an active ticket. The payload
# is separate from the hub's live hook so later runtime composition does not
# mutate the standard source's own enforcement while it is executing.

set -euo pipefail

root="$(git rev-parse --show-toplevel)"
branch="$(git symbolic-ref --short HEAD 2>/dev/null || true)"

run_worktree_guard() {
local runner="$root/.governance/worktree_guard.py"
if [[ -f "$runner" ]]; then
python3 "$runner" --root "$root" --once
return
fi

echo "worktree-guard: the managed pre-commit hook cannot find worktree_guard.py." >&2
echo " Restore the managed package or reinstall the repository guard:" >&2
echo " ./scripts/install-worktree-guard.sh --target $root --wire-hook" >&2
return 1
}

run_local_standard_pin_check() {
local runner="$root/.governance/work_continuity.py"
if [[ ! -f "$runner" ]]; then
# A host-files-only bootstrap predates full standard adoption and has no
# local pin to check. Once either pin file exists, the runtime is required.
if [[ ! -e "$root/.subactor/manifest.json" && ! -e "$root/.governance/manifest.lock.json" ]]; then
return 0
fi
echo "GOV-CONTINUITY-001: the managed pre-commit hook cannot validate the local standard pin." >&2
echo " Restore the managed package with the explicit standard updater; the hook never fetches." >&2
return 1
fi

# Commits read only staged local manifests and managed digests. Resolve a
# newer release explicitly in its adoption ticket, outside this boundary.
python3 "$runner" verify-pin --root "$root" --staged >/dev/null
}

run_commit_guards() {
run_local_standard_pin_check
run_worktree_guard
}

if [[ -z "$branch" || "$branch" == "HEAD" ]]; then
echo "GOV-AGENT-HOST-001: detached HEAD is not bound to ticket-NNN." >&2
exit 1
fi

if [[ ! "$branch" =~ ticket[-/]([0-9]{3,}) ]]; then
echo "GOV-AGENT-HOST-001: branch '$branch' is not bound to ticket-NNN." >&2
echo " Allocate with ./project/new-ticket.sh and commit on a ticket branch." >&2
exit 1
fi

ticket="ticket-${BASH_REMATCH[1]}"
readme_rel="project/$ticket/README.md"

ticket_storage="$(git config --local --get new-project.ticketStorage 2>/dev/null || true)"
if [[ "${ticket_storage:-files}" == sqlite ]]; then
reader="$root/.governance/ticket_input.py"
[[ -f "$reader" ]] || reader="$root/scripts/ticket_input.py"
if ! staged_readme="$(PYTHONDONTWRITEBYTECODE=1 python3 "$reader" read --root "$root" --ticket "$ticket" --file README.md)"; then
echo "GOV-AGENT-HOST-002: active SQLite ticket content is unavailable." >&2
exit 1
fi
elif [[ "${ticket_storage:-files}" == files ]]; then
if ! staged_readme="$(git show ":$readme_rel" 2>/dev/null)"; then
echo "GOV-AGENT-HOST-002: $root/$readme_rel is missing from the staged snapshot." >&2
echo " Allocate with ./project/new-ticket.sh; do not invent a ticket number." >&2
exit 1
fi
else
echo "GOV-AGENT-HOST-002: unknown ticket storage mode." >&2
exit 1
fi

governance_only_transition() {
# SQLite transitions never require a Git carrier commit.
[[ "${ticket_storage:-files}" == files ]] || return 1
if git diff --cached --quiet -- "$readme_rel"; then
return 1
fi
if ! git diff --cached --quiet --diff-filter=DRC --; then
return 1
fi

while IFS= read -r -d '' path; do
case "$path" in
"project/$ticket/"*|TODO.md|project/TICKETS.md|config/artifact-registry.json) ;;
*) return 1 ;;
esac
done < <(git diff --cached --name-only -z --diff-filter=AM --)
}

if grep -Eiq '^-[[:space:]]+\*\*Status\*\*:[[:space:]]*IN_PROGRESS([[:space:]]|$)' <<<"$staged_readme"; then
head_readme="$(git show "HEAD:$readme_rel" 2>/dev/null || true)"
if grep -Eiq '^-[[:space:]]+\*\*Status\*\*:[[:space:]]*(BACKLOG|PLAN|BLOCKED)([[:space:]]|$)' <<<"$head_readme" \
&& governance_only_transition; then
run_commit_guards
exit 0
fi

material=false
while IFS= read -r -d '' path; do
case "$path" in
project/ticket-*/*|TODO.md|project/TICKETS.md|config/artifact-registry.json) ;;
*) material=true; break ;;
esac
done < <(git diff --cached --name-only -z --diff-filter=AM --)
if [[ "$material" != true ]]; then
echo "GOV-AGENT-HOST-007: staged change contains only ticket tracking carriers." >&2
echo " Add a material deliverable, or emit an external no-change receipt without committing." >&2
exit 1
fi
run_commit_guards
exit 0
fi

if grep -Eiq '^-[[:space:]]+\*\*Status\*\*:[[:space:]]*(BACKLOG|PLAN|BLOCKED)([[:space:]]|$)' <<<"$staged_readme"; then
if governance_only_transition; then
run_commit_guards
exit 0
fi
echo "GOV-AGENT-HOST-003: $ticket non-active transition is not governance-only." >&2
echo " Stage the ticket README and only bounded governance evidence; keep implementation on IN_PROGRESS." >&2
exit 1
fi

if grep -Eiq '^-[[:space:]]+\*\*Status\*\*:[[:space:]]*(DONE|CANCELLED)([[:space:]]|$)' <<<"$staged_readme"; then
echo "GOV-AGENT-HOST-003: repository terminal closure commits are forbidden." >&2
echo " The protected delivery controller must emit the external terminal receipt without a repository write." >&2
exit 1
fi

echo "GOV-AGENT-HOST-003: $ticket is neither IN_PROGRESS nor a valid staged non-active transition." >&2
echo " Use BACKLOG, PLAN or BLOCKED only for bounded governance evidence; terminal state belongs to the protected external receipt." >&2
exit 1
36 changes: 36 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# GitHub Copilot instructions

<!-- wellmanifest:source-links:v1 -->
## Managed standard sources

The local adoption manifest, lock and package are authoritative. Remote links
are navigation only and must not be fetched or executed at runtime.

- Local adoption manifest: [.governance/manifest.json](.governance/manifest.json)
- Local adoption lock: [.governance/manifest.lock.json](.governance/manifest.lock.json)
- Local package map: [.governance/package-manifest.json](.governance/package-manifest.json)
- Canonical instructions: [AGENTS template](https://github.com/wellmanifest/new-project/blob/main/template/files/AGENTS.template.md)
- Host contract: [agent-hosts.json](https://github.com/wellmanifest/new-project/blob/main/governance/agent-hosts.json)
- Immutable adoption/updater: [create_adoption_lock.py](https://github.com/wellmanifest/new-project/blob/main/scripts/create_adoption_lock.py)

<!-- end wellmanifest:source-links:v1 -->

This repository follows the `wellmanifest/new-project` policy-as-code standard.
Same fail-closed contract as `AGENTS.md`, `CLAUDE.md`, `GEMINI.md` and the Cursor
rule. Copilot Chat and the Copilot coding agent load this file automatically.

1. Read `AGENTS.md` before proposing any change.
2. Allocate tickets only through `./project/new-ticket.sh`. Never copy a
`project/ticket-NNN` directory and never invent a ticket number.
3. Work on a branch or worktree whose name contains `ticket-NNN`. Never write on
`main` or a dirty primary checkout.
4. Stay inside that ticket's `intent.json` `allowedPaths`.
5. Run `./scripts/install-agent-hosts.sh` once per clone so `.githooks/pre-commit`
is active, then `./project/governance-check.sh` before claiming done.

Suggestions that skip these steps are rejected by the pre-commit hook and by the
`governance / enforce` CI job. Markdown is not a substitute for either.

Bounded session controls: respect the ticket's `maxActiveMinutes`, create a
`checkpoint` before a context or tool boundary, and leave a `handoff` then
`stop` after a deterministic failure instead of retrying indefinitely.
72 changes: 72 additions & 0 deletions .github/workflows/new-project-branch-hygiene.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: new-project-branch-hygiene

# `delete_branch_on_merge` covers the merge exit. This workflow covers the
# other one: a branch whose pull request was closed without merging. Nothing
# in the toolchain removes those, and `GOV-BRANCH-LIFECYCLE-002` validates the
# repository as a whole rather than the diff under review, so a single left
# behind branch fails `governance / remote lifecycle` on *every* later pull
# request, indefinitely and through no fault of its own.
#
# Closing a pull request without merging is the explicit owner decision to
# discard the branch that the check's own remediation asks for, and GitHub
# keeps the branch restorable from the pull request page afterwards.

on:
pull_request:
types: [closed]

permissions:
contents: read

jobs:
discard-unmerged-head:
# Forks cannot be written to, and the default branch is never a head to
# discard. Both guards are cheap and keep the job from ever running where
# deleting would be wrong.
if: >-
github.event.pull_request.merged == false &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.head.ref != github.event.repository.default_branch
runs-on: ${{ vars.NEW_PROJECT_RUNNER_LABEL || 'ubuntu-latest' }}
permissions:
contents: write
steps:
- name: Delete the closed pull request's head branch
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const ref = context.payload.pull_request.head.ref;

// Several pull requests may share one head branch. Deleting it
// while another is still open would break that one instead.
const owners = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
head: `${context.repo.owner}:${ref}`,
per_page: 100,
});
if (owners.length > 0) {
const numbers = owners.map(pull => `#${pull.number}`).join(', ');
core.info(`Kept ${ref}: still owned by ${numbers}.`);
return;
}

try {
await github.rest.git.deleteRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: `heads/${ref}`,
});
core.info(
`Deleted ${ref}. GitHub keeps it restorable from the pull request page.`,
);
} catch (error) {
// The branch is commonly gone already, because the person
// closing the pull request ticked "delete branch".
if (error.status === 404 || error.status === 422) {
core.info(`${ref} was already deleted.`);
return;
}
throw error;
}
Loading
Loading