From a728c3cc73bf70de2c0a82ca4fb2da5b8113fab4 Mon Sep 17 00:00:00 2001 From: Alan Szmyt Date: Fri, 2 Oct 2026 22:50:26 -0400 Subject: [PATCH] =?UTF-8?q?feat(issue-authoring):=20=E2=9C=A8=20connect=20?= =?UTF-8?q?the=20canonical=20title=20contract?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend existing skill and managed discovery with pinned source verification, Egolint consumer evidence, and a bounded continuity handoff. Refs #98 --- AGENTS.md | 11 + CHANGELOG.md | 4 + CONTINUITY.md | 214 +++++++++++++ catalog/first-party/catalog.v1.json | 2 +- .../.claude/agents/github-issue-creator.md | 31 +- dist/claude/repository/CLAUDE.md | 25 +- dist/codex/repository/AGENTS.md | 25 +- .../repository-instructions/AGENTS.md | 25 +- .../agents/github-issue-creator.agent.md | 31 +- .../agents/github-issue-creator.agent.md | 31 +- .../.github/copilot-instructions.md | 25 +- .../.opencode/agents/github-issue-creator.md | 31 +- dist/projections/manifest.v1.json | 33 +- dist/skills/github-issue-authoring/SKILL.md | 19 +- .../distribution-manifest.v1.json | 12 +- .../github-issue-authoring/evals/evals.json | 57 +++- .../references/canonical-issue-titles.md | 83 +++++ .../references/copy-ready-checklist.md | 4 + .../issue-title-contract/cases.v1.json | 283 ++++++++++++++++++ .../label-catalog.v1.json | 183 +++++++++++ .../issue-title-contract/selection.v1.json | 30 ++ .../issue-title-contract/semantics.txt | 136 +++++++++ .../title-contract.v1.json | 67 +++++ .../title-contract.v1.schema.json | 177 +++++++++++ .../scripts/verify_title_contract.py | 100 +++++++ .../templates/GITHUB_ISSUE.template.md | 10 + dist/zencoder/manual-import/agents.json | 17 +- docs/issue-title-authoring-pilot.md | 68 +++++ library/organization/agents/catalog.json | 2 +- .../agents/github-issue-creator/AGENT.md | 10 +- .../projections/build-projections.py | 88 +++++- .../templates/issue-authoring.AGENTS.md | 22 ++ .../authoring/github-issue-authoring/SKILL.md | 19 +- .../github-issue-authoring/evals/evals.json | 57 +++- .../references/canonical-issue-titles.md | 83 +++++ .../references/copy-ready-checklist.md | 4 + .../issue-title-contract/cases.v1.json | 283 ++++++++++++++++++ .../label-catalog.v1.json | 183 +++++++++++ .../issue-title-contract/selection.v1.json | 30 ++ .../issue-title-contract/semantics.txt | 136 +++++++++ .../title-contract.v1.json | 67 +++++ .../title-contract.v1.schema.json | 177 +++++++++++ .../scripts/verify_title_contract.py | 100 +++++++ .../templates/GITHUB_ISSUE.template.md | 10 + .../skills/build-distributions.py | 17 +- tests/test_build_distributions.py | 11 + tests/test_continuity_integration.py | 13 +- tests/test_issue_title_authoring.py | 222 ++++++++++++++ 48 files changed, 3220 insertions(+), 48 deletions(-) create mode 100644 CONTINUITY.md create mode 100644 dist/skills/github-issue-authoring/references/canonical-issue-titles.md create mode 100644 dist/skills/github-issue-authoring/references/issue-title-contract/cases.v1.json create mode 100644 dist/skills/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json create mode 100644 dist/skills/github-issue-authoring/references/issue-title-contract/selection.v1.json create mode 100644 dist/skills/github-issue-authoring/references/issue-title-contract/semantics.txt create mode 100644 dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.json create mode 100644 dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json create mode 100644 dist/skills/github-issue-authoring/scripts/verify_title_contract.py create mode 100644 docs/issue-title-authoring-pilot.md create mode 100644 library/organization/projections/templates/issue-authoring.AGENTS.md create mode 100644 library/organization/skills/authoring/github-issue-authoring/references/canonical-issue-titles.md create mode 100644 library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/cases.v1.json create mode 100644 library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json create mode 100644 library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json create mode 100644 library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/semantics.txt create mode 100644 library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.json create mode 100644 library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json create mode 100644 library/organization/skills/authoring/github-issue-authoring/scripts/verify_title_contract.py create mode 100644 tests/test_issue_title_authoring.py diff --git a/AGENTS.md b/AGENTS.md index 8b169bd..feca8a2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,5 +31,16 @@ policy; Egolint owns conformance validation; Pace observes adoption before it proposes remediation. Keep provider credentials and delivery adapters in their consumer repository. +## Issue authoring + +For Aether issue authoring, explicitly read the +[`github-issue-authoring` skill](library/organization/skills/authoring/github-issue-authoring/SKILL.md) +and its [canonical-title guide](library/organization/skills/authoring/github-issue-authoring/references/canonical-issue-titles.md). +This repository selects that guide's pinned candidate contract in observe mode +for this pilot. Local proposals are not proof of provider label availability or +enforcement. The organization repository's instructions do not inherit here +automatically; this local pointer makes discovery explicit. Preserve scoped +instructions and existing role permissions. + Run the relevant deterministic validation and regenerate `dist/` and the first-party catalog after changing canonical source. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d8814b..aac1874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ This repository follows release-tag based versioning for install pinning and rel ## [Unreleased] +- Connected issue authoring to the pinned candidate title contract and Egolint + through explicit local discovery, source verification, and managed instruction + projections; preserved read-only agent permissions and added a synthetic + consumer check (#98). - Added the draft bounded worker strategy and Flow suite bootstrap guide for small implementation checkpoints, durable handoffs, explicit deferred quality work, local validation, and finite audit/refactoring passes. diff --git a/CONTINUITY.md b/CONTINUITY.md new file mode 100644 index 0000000..e56de1e --- /dev/null +++ b/CONTINUITY.md @@ -0,0 +1,214 @@ +--- +schema_version: aether.repository-continuity/v1 +repository: + id: egohygiene/aether + visibility: public + default_branch: main + continuity_path: CONTINUITY.md +document: + status: active + updated_at: '2026-10-03T02:00:47Z' + max_bytes: 16384 + max_lines: 240 + stale_reason: null + superseded_by: null +scope: + purpose: Bounded handoff for the issue-title authoring checkpoint (#98). + includes: + - 'Aether #98 candidate implementation, local checks, and dependency state' + excludes: + - conversation transcripts + - duplicated architecture, roadmap, and changelog content + precedence: + - user-and-runtime-instructions + - scoped-repository-instructions + - live-repository-and-work-tracker-state + - canonical-repository-sources + - continuity-checkpoint + canonical_sources: + - AGENTS.md + - docs/issue-title-authoring-pilot.md + - library/organization/skills/authoring/github-issue-authoring/SKILL.md + - library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json +work: + objective: Make the existing issue authoring skill discover and consume the pinned title contract. + success_conditions: + - One immutable selection shared by skill and managed projections + - Real Egolint synthetic title evidence with preserved subject and IDs + - Explicit unavailable evidence and unchanged read-only agent permissions + active_issue: + provider: github + id: egohygiene/aether#98 + url: https://github.com/egohygiene/aether/issues/98 + next: + kind: action + id: review-issue-title-authoring-candidate + description: Review this bounded candidate and resolve the dependency reviews before selecting accepted + pins; prepare the Relay pilot as a separate checkpoint. + readiness: ready + references: + - https://github.com/egohygiene/aether/issues/98 + - https://github.com/egohygiene/.github/issues/24 + depends_on: [] +state: + base: + revision: 1072a0fafe145f919b3f83bbbc6daee2cb946030 + ref: refs/heads/main + verified_at: '2026-10-03T02:00:47Z' + candidate: + branch: codex/issue-title-authoring-98 + revision: null + pull_request: null + handoff_state: ready-for-review + live: + status: partial + observed_at: '2026-10-03T02:00:47Z' + default_branch_revision: 1072a0fafe145f919b3f83bbbc6daee2cb946030 + issue_state: open + pull_request_state: not-applicable + notes: 'Main and issue #98 were checked through GitHub. Could not verify provider label availability + or remote CI. Candidate PR has not yet been created; find its live head through issue #98. Dependency + PRs were observed open/unmerged during this session.' + parallel_changes: [] +review: + status: partial + reviewed_at: '2026-10-03T02:00:47Z' + reviewed_by: Codex + evidence: + - command: python3 -m unittest discover -s tests -p test_issue_title_authoring.py -v + outcome: passed + observed_at: '2026-10-03T02:00:47Z' + notes: 11 tests passed with AETHER_EGOLINT_BINARY set to the declared consumer build; includes 18 + upstream cases and two reviewed migrations in the final full run. + - command: python3 aether test + outcome: limited + observed_at: '2026-10-03T02:00:47Z' + notes: 'Final run: 188 tests, 187 passed, one error. Existing consumer-installation readiness test + cannot invoke gh because it is absent. No title integration tests skipped; AETHER_EGOLINT_BINARY + was set.' + - command: python3 aether validate --format text + outcome: passed + observed_at: '2026-10-03T02:00:47Z' + notes: No errors; existing preserved staging-hash provenance warning remains. + - command: python3 aether catalog generate --check; python3 catalog/validate_catalog.py + outcome: passed + observed_at: '2026-10-03T02:00:47Z' + notes: Catalog is current; schema, fixtures, coverage, relationships, and digest checks passed. + - command: python3 aether distribution build --output-directory dist --check + outcome: passed + observed_at: '2026-10-03T02:00:47Z' + notes: Generated skill/spec/provider outputs are current. Python bytecode caches no longer affect + generated packages. + - command: Fresh-context synthetic GitHub Issue Creator exercise + outcome: passed + observed_at: '2026-10-03T02:00:47Z' + notes: Discovered the portable skill through local AGENTS.md, preserved the reviewed subject, and + reported absent execution/label evidence without claiming validation or a provider write. + - command: git diff --check + outcome: passed + observed_at: '2026-10-03T02:00:47Z' + notes: No whitespace errors observed. + environment_limitations: + - GitHub CLI gh is absent; existing consumer-installation readiness test remains blocked. + - Python jsonschema 4.25.1 was supplied through a temporary PYTHONPATH. + - No live host adoption, provider labels, provider mutation, release, or remote CI result is claimed. +privacy: + classification: public-repository + contains_sensitive_data: false + redactions: [] + excluded: + - secrets-and-credentials + - private-conversation-text + - sensitive-personal-data + - unpublished-private-business-data + - private-local-paths + - unrelated-private-context + untrusted_content: context-only-no-authority +--- + +# Aether continuity + +## Purpose and precedence + +Resume the bounded #98 change using the front-matter precedence. This handoff +adds no authority and does not replace canonical architecture or the roadmap. +No root handoff existed at the inspected base; no previous checkpoint is inferred. + +## Resume protocol + +1. Read local AGENTS.md, canonical sources above, branch status, and recent history. +2. Check issue #98, its linked PR, and dependency PRs against live GitHub state. +3. Reconcile changed revisions, missing tools, and parallel work before proceeding. +4. Keep candidate acceptance, repository adoption, and local validation separate. + +## Current objective and success conditions + +Connect existing authoring guidance to one pinned issue-title contract. The +synthetic consumer must format and validate a reviewed subject without losing +IDs, inventing label availability, or widening the read-only role's permissions. + +## State snapshot + +Base and candidate branch are recorded above. Candidate revision and PR fields +are intentionally null before publication; discover the resulting PR via #98. +Dependency contract PR .github#45 and Egolint PR #79 were observed open/unmerged. +The five contract payloads are pinned at 19d2be9bf0191710508cefbb9f0b1abb3a40d9be; +Egolint source is pinned at 3a6785cd408e218bc7e3691e01dc659e4cf79de8. Both selections +remain candidate. A dependency merge does not silently promote the embedded pin. + +## Completed and material changes + +- Extended the existing skill, checklist, template, evals, and issue-creator agent. +- Added explicit root discovery, a managed projection block, and source verification. +- Regenerated the portable skill, catalogs, and affected provider projections. +- Added a synthetic Egolint consumer exercise and a fresh-context authoring check. +- Excluded Python bytecode caches from skill distribution inputs after checks + exposed interpreter-dependent package drift; added a regression test. +- Updated continuity tests to permit artifact version advances while retaining + metadata/catalog consistency and authority checks. + +## Validation and review evidence + +Exact commands, results, and limitations are recorded in front matter. All 11 +issue-title tests passed with the real Egolint candidate, including upstream +examples and reviewed migrations. The full suite has one environment error from +missing gh; do not describe the entire suite as passing. Generated-output checks +and catalog validation passed. The fresh-context check returned an unvalidated +draft with correct provenance and explicitly unavailable provider/tool evidence. + +## Blockers, risks, unknowns, and deferred work + +- Blocker to complete full-suite evidence: install the required gh tooling and + rerun the existing consumer-installation readiness test in a suitable environment. +- Candidate dependency acceptance remains external to this checkpoint. +- Live provider labels, remote CI, and actual host loading are unknown. +- Relay execution, label provisioning, fleet adoption/migration, and future-title + enforcement remain under .github#24; the broader Aether #67 bundle is incomplete. + +## Next dependency-ready work + +Review the #98 candidate and its dependency PRs. Once accepted source revisions +are verified, deliberately update pins/authority. Then scope the Relay pilot: +preview/apply, current-state checks, receipts, rollback, and a no-op repeat before +any fleet sweep. This checkpoint authorizes none of those provider operations. + +## Parallel changes and reconciliation + +No other open Aether PR was observed at initial inspection. Recheck before merge; +other repositories' dependency PRs do not establish acceptance of this candidate. + +## Privacy and redaction + +Public repository handoff. No credentials, personal conversation text, private +paths, or unrelated personal context is included. Synthetic examples only. + +## Handoff update protocol + +Refresh after relevant validation and before the next PR update. Recheck live +state instead of interpreting this pre-publication snapshot as current proof. +Include changes to this handoff with the bounded repository change. + +## Compaction and supersession + +Keep below 16,384 UTF-8 bytes and 240 lines. Replace stale state instead of +accumulating a transcript; Git and issue/PR history own chronology. diff --git a/catalog/first-party/catalog.v1.json b/catalog/first-party/catalog.v1.json index 90497bb..3d57917 100644 --- a/catalog/first-party/catalog.v1.json +++ b/catalog/first-party/catalog.v1.json @@ -1162,7 +1162,7 @@ "scope": "organization", "source_digest": { "algorithm": "sha256-utf8-lf", - "value": "b7d5e3f41bf49931a01fcfe74e287995fc81fd0f3f0fb0acd52a09a4b43afb43" + "value": "25b8b9d4340db138f142a8d1670a178749332bb93fd03ac903c0bdd442123a63" }, "source_path": "library/organization/skills/authoring/github-issue-authoring/SKILL.md", "supersedes_ids": [], diff --git a/dist/claude/repository/.claude/agents/github-issue-creator.md b/dist/claude/repository/.claude/agents/github-issue-creator.md index b9de1dc..bd63a0e 100644 --- a/dist/claude/repository/.claude/agents/github-issue-creator.md +++ b/dist/claude/repository/.claude/agents/github-issue-creator.md @@ -9,7 +9,7 @@ tools: - WebFetch - WebSearch --- - + ## Mission @@ -38,6 +38,9 @@ takes ownership of that handoff. 1. Extract the problem, motivation, desired state, constraints, and open questions. 2. Inspect repository architecture, relevant specifications, source, tests, automation, workflows, existing issues, and issue templates when available. 3. Choose one primary issue type and determine whether the request is one issue or a dependency-ordered roadmap. + When local instructions select the Ego Hygiene title contract, follow the + skill's canonical-title reference: resolve the immutable selection, preserve + the reviewed subject, and return the intended type label with provenance. 4. Define included scope, exclusions, ownership, integration boundaries, and observable completion. 5. Add evidence-backed implementation guidance without prescribing unsupported file paths or dependencies. 6. Define validation and acceptance criteria that another engineer or coding agent can execute. @@ -51,6 +54,9 @@ takes ownership of that handoff. - Prefer a reversible assumption for non-material ambiguity and record it. - Generate issues one at a time when the user requests staged authoring. - `edit` and `execute` are excluded; issue authoring is read, search, and web only. +- Request or consume formatter/validator evidence from an authorized executor; + do not run Egolint in this read-only role. Keep missing tools, stale pins, + conflicting classification, and unavailable provider labels explicit. ## Completion @@ -133,3 +139,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/claude/repository/CLAUDE.md b/dist/claude/repository/CLAUDE.md index 3472788..f736d36 100644 --- a/dist/claude/repository/CLAUDE.md +++ b/dist/claude/repository/CLAUDE.md @@ -1,6 +1,6 @@ # CLAUDE.md - + > Generated integration fixture. Reconcile only marked Aether blocks; preserve consumer-owned prose, repository commands, boundaries, and nested instruction precedence. Native project instructions are read at session start; this projection does not configure a hook or enforce a pre-pull-request checkpoint. @@ -81,3 +81,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/codex/repository/AGENTS.md b/dist/codex/repository/AGENTS.md index 20b7779..fdbd39e 100644 --- a/dist/codex/repository/AGENTS.md +++ b/dist/codex/repository/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md - + > Generated integration fixture. Reconcile only marked Aether blocks; preserve consumer-owned prose, repository commands, boundaries, and nested instruction precedence. Native repository instructions are read at session start; this static file does not schedule or enforce a pre-pull-request hook. @@ -81,3 +81,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/fixtures/repository-instructions/AGENTS.md b/dist/fixtures/repository-instructions/AGENTS.md index 20b7779..fdbd39e 100644 --- a/dist/fixtures/repository-instructions/AGENTS.md +++ b/dist/fixtures/repository-instructions/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md - + > Generated integration fixture. Reconcile only marked Aether blocks; preserve consumer-owned prose, repository commands, boundaries, and nested instruction precedence. Native repository instructions are read at session start; this static file does not schedule or enforce a pre-pull-request hook. @@ -81,3 +81,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/github/organization/agents/github-issue-creator.agent.md b/dist/github/organization/agents/github-issue-creator.agent.md index 2d5f4a4..2a63c2b 100644 --- a/dist/github/organization/agents/github-issue-creator.agent.md +++ b/dist/github/organization/agents/github-issue-creator.agent.md @@ -7,7 +7,7 @@ tools: - search - web --- - + ## Mission @@ -36,6 +36,9 @@ takes ownership of that handoff. 1. Extract the problem, motivation, desired state, constraints, and open questions. 2. Inspect repository architecture, relevant specifications, source, tests, automation, workflows, existing issues, and issue templates when available. 3. Choose one primary issue type and determine whether the request is one issue or a dependency-ordered roadmap. + When local instructions select the Ego Hygiene title contract, follow the + skill's canonical-title reference: resolve the immutable selection, preserve + the reviewed subject, and return the intended type label with provenance. 4. Define included scope, exclusions, ownership, integration boundaries, and observable completion. 5. Add evidence-backed implementation guidance without prescribing unsupported file paths or dependencies. 6. Define validation and acceptance criteria that another engineer or coding agent can execute. @@ -49,6 +52,9 @@ takes ownership of that handoff. - Prefer a reversible assumption for non-material ambiguity and record it. - Generate issues one at a time when the user requests staged authoring. - `edit` and `execute` are excluded; issue authoring is read, search, and web only. +- Request or consume formatter/validator evidence from an authorized executor; + do not run Egolint in this read-only role. Keep missing tools, stale pins, + conflicting classification, and unavailable provider labels explicit. ## Completion @@ -131,3 +137,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/github/repository/.github/agents/github-issue-creator.agent.md b/dist/github/repository/.github/agents/github-issue-creator.agent.md index c983e8a..71487fe 100644 --- a/dist/github/repository/.github/agents/github-issue-creator.agent.md +++ b/dist/github/repository/.github/agents/github-issue-creator.agent.md @@ -7,7 +7,7 @@ tools: - search - web --- - + ## Mission @@ -36,6 +36,9 @@ takes ownership of that handoff. 1. Extract the problem, motivation, desired state, constraints, and open questions. 2. Inspect repository architecture, relevant specifications, source, tests, automation, workflows, existing issues, and issue templates when available. 3. Choose one primary issue type and determine whether the request is one issue or a dependency-ordered roadmap. + When local instructions select the Ego Hygiene title contract, follow the + skill's canonical-title reference: resolve the immutable selection, preserve + the reviewed subject, and return the intended type label with provenance. 4. Define included scope, exclusions, ownership, integration boundaries, and observable completion. 5. Add evidence-backed implementation guidance without prescribing unsupported file paths or dependencies. 6. Define validation and acceptance criteria that another engineer or coding agent can execute. @@ -49,6 +52,9 @@ takes ownership of that handoff. - Prefer a reversible assumption for non-material ambiguity and record it. - Generate issues one at a time when the user requests staged authoring. - `edit` and `execute` are excluded; issue authoring is read, search, and web only. +- Request or consume formatter/validator evidence from an authorized executor; + do not run Egolint in this read-only role. Keep missing tools, stale pins, + conflicting classification, and unavailable provider labels explicit. ## Completion @@ -131,3 +137,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/github/repository/.github/copilot-instructions.md b/dist/github/repository/.github/copilot-instructions.md index b43793b..b8866eb 100644 --- a/dist/github/repository/.github/copilot-instructions.md +++ b/dist/github/repository/.github/copilot-instructions.md @@ -1,6 +1,6 @@ # GitHub Copilot repository instructions - + > Generated integration fixture. Reconcile only marked Aether blocks; preserve consumer-owned prose, repository commands, boundaries, and nested instruction precedence. Repository-instruction support varies by Copilot surface; this static file does not schedule or enforce a pre-pull-request hook. @@ -81,3 +81,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/opencode/repository/.opencode/agents/github-issue-creator.md b/dist/opencode/repository/.opencode/agents/github-issue-creator.md index 9e8d703..11aa9c6 100644 --- a/dist/opencode/repository/.opencode/agents/github-issue-creator.md +++ b/dist/opencode/repository/.opencode/agents/github-issue-creator.md @@ -14,7 +14,7 @@ permission: github-issue-authoring: allow maintain-repository-continuity: allow --- - + ## Mission @@ -43,6 +43,9 @@ takes ownership of that handoff. 1. Extract the problem, motivation, desired state, constraints, and open questions. 2. Inspect repository architecture, relevant specifications, source, tests, automation, workflows, existing issues, and issue templates when available. 3. Choose one primary issue type and determine whether the request is one issue or a dependency-ordered roadmap. + When local instructions select the Ego Hygiene title contract, follow the + skill's canonical-title reference: resolve the immutable selection, preserve + the reviewed subject, and return the intended type label with provenance. 4. Define included scope, exclusions, ownership, integration boundaries, and observable completion. 5. Add evidence-backed implementation guidance without prescribing unsupported file paths or dependencies. 6. Define validation and acceptance criteria that another engineer or coding agent can execute. @@ -56,6 +59,9 @@ takes ownership of that handoff. - Prefer a reversible assumption for non-material ambiguity and record it. - Generate issues one at a time when the user requests staged authoring. - `edit` and `execute` are excluded; issue authoring is read, search, and web only. +- Request or consume formatter/validator evidence from an authorized executor; + do not run Egolint in this read-only role. Keep missing tools, stale pins, + conflicting classification, and unavailable provider labels explicit. ## Completion @@ -138,3 +144,26 @@ Static instructions do not install or guarantee an automatic pre-pull-request hook. If the host cannot load the skill, inspect local files, or verify live state, report that capability as unavailable rather than inventing success. + + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/dist/projections/manifest.v1.json b/dist/projections/manifest.v1.json index b1ced2f..87de4d5 100644 --- a/dist/projections/manifest.v1.json +++ b/dist/projections/manifest.v1.json @@ -307,6 +307,19 @@ }, "status": "draft", "version": "1.0.0" + }, + { + "id": "issue-authoring", + "selection": "library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json", + "selection_sha256": "435d9310e49ee10cafa3f477c2080a88e666a247e9496dc40dbb17356d319582", + "skill": "github-issue-authoring", + "source": "library/organization/projections/templates/issue-authoring.AGENTS.md", + "source_digest": { + "algorithm": "sha256-utf8-lf", + "value": "9320e46a9947a5f98752398f9630c1539e6b16fa55ca4ca050aae7f0205b6a14" + }, + "status": "draft", + "version": "0.1.0" } ], "interface": "aether.projection-interface/v1", @@ -315,7 +328,7 @@ "source": "library/organization/agents/github-issue-creator/AGENT.md", "source_digest": { "algorithm": "sha256-utf8-lf", - "value": "df647f3a6243773ec612a9c167156b177e40d730309887c9a452ada0322b530b" + "value": "d58a76d32a46f0bb22f1b776e037681c9cbfbf69dfb7fa04d6a1110fe87f9e6f" } }, "implementation-planner": { @@ -511,7 +524,7 @@ }, { "path": "dist/claude/repository/.claude/agents/github-issue-creator.md", - "sha256": "18ef843ec7709811d48f383dc744e965c59362f1512d6c0434614d665384948f" + "sha256": "9720a1a463e1e584c9585e46de7b6bf49ead4402b94d863aade09d488cf38803" }, { "path": "dist/claude/repository/.claude/agents/implementation-planner.md", @@ -527,15 +540,15 @@ }, { "path": "dist/claude/repository/CLAUDE.md", - "sha256": "3d7d433db8d3c8cf6d790ff3bcb23e9640493242a20bdabf14d2ad330db079f2" + "sha256": "0dc2f56ef7eee41b855ad8c27284784d877373366c83e17447ba852612d0c585" }, { "path": "dist/codex/repository/AGENTS.md", - "sha256": "370ad8291f7601459e12390b48942deffaf82b0acc3557029986035f3081c506" + "sha256": "32c52f70bc782b0c90c977dd4efea441b7fcaef47058c8455b5a41c91ecb4e79" }, { "path": "dist/fixtures/repository-instructions/AGENTS.md", - "sha256": "370ad8291f7601459e12390b48942deffaf82b0acc3557029986035f3081c506" + "sha256": "32c52f70bc782b0c90c977dd4efea441b7fcaef47058c8455b5a41c91ecb4e79" }, { "path": "dist/github/organization/agents/architect.agent.md", @@ -559,7 +572,7 @@ }, { "path": "dist/github/organization/agents/github-issue-creator.agent.md", - "sha256": "ca19f9a713da2a3bb645c09c3e52768c16a3929500b9329d9c475fee64c1ab14" + "sha256": "e0f3860cc483632f456c0646378c6f1c88748d4a0c68de10a341c4f864dc0f6d" }, { "path": "dist/github/organization/agents/implementation-planner.agent.md", @@ -595,7 +608,7 @@ }, { "path": "dist/github/repository/.github/agents/github-issue-creator.agent.md", - "sha256": "d079b656d89f17b324ff1a858c533c4e54457498cc09f83a875dff3d81023b48" + "sha256": "3910d661da893b26183efcd678f637170c8bb10fecd39117ce3dcf03f7f3171c" }, { "path": "dist/github/repository/.github/agents/implementation-planner.agent.md", @@ -611,7 +624,7 @@ }, { "path": "dist/github/repository/.github/copilot-instructions.md", - "sha256": "ff5155ea4a05a4ba46a768103648be3d01d294745c755fa88148287221307ed1" + "sha256": "b9be2c95bceecced9ebde9383aa534cfdef706a00bcfb7c097d1e5dff8b47175" }, { "path": "dist/mcp/github/.mcp.json", @@ -639,7 +652,7 @@ }, { "path": "dist/opencode/repository/.opencode/agents/github-issue-creator.md", - "sha256": "a49b93a706f97a59e835f018232cdebc8fb8085ccd809633a0d629aae6c89160" + "sha256": "d7e0fc0d52d62239fc2d5f3cdaec0051ba00ffafc7b7c134b10d3efec027fe8e" }, { "path": "dist/opencode/repository/.opencode/agents/implementation-planner.md", @@ -659,7 +672,7 @@ }, { "path": "dist/zencoder/manual-import/agents.json", - "sha256": "faf6e813b5e79aea7cd8d2c2fc639ff27fdada2f236af5ed320650fc33abcc0a" + "sha256": "0d48843c6501003958433ce23ad8193b281a144fa9131c8b869adce72396b2fd" } ], "provider_registry": "library/organization/projections/provider-registry.v1.json", diff --git a/dist/skills/github-issue-authoring/SKILL.md b/dist/skills/github-issue-authoring/SKILL.md index 05d2996..7f60951 100644 --- a/dist/skills/github-issue-authoring/SKILL.md +++ b/dist/skills/github-issue-authoring/SKILL.md @@ -3,13 +3,15 @@ name: github-issue-authoring description: Converts evidence, specifications, audits, bug reports, and rough notes into scoped, copy-ready GitHub issues or dependency-aware issue batches. Use when defining implementation work clearly without performing the implementation itself. license: MIT metadata: - aether-version: "1.1.0" + aether-version: "1.2.0" aether-status: "experimental" aether-scope: "organization" aether-domain: "authoring" aether-owners: "egohygiene" aether-created: "2026-08-08" - aether-updated: "2026-09-08" + aether-updated: "2026-10-03" + aether-executable-resources: + - "scripts/verify_title_contract.py" --- # GitHub Issue Authoring @@ -47,7 +49,10 @@ When repository evidence is incomplete, label assumptions explicitly. 1. extract the durable problem, goal, boundaries, and observable outcome 2. inspect repository evidence before prescribing paths, commands, or ownership -3. choose whether the result should be one issue or an ordered batch of issues +3. choose whether the result should be one issue or an ordered batch of issues; + if scoped instructions select the Ego Hygiene issue-title policy, follow + [canonical issue titles](references/canonical-issue-titles.md) before drafting + the title or proposing labels 4. write copy-ready issue content using the focused resources: - `./references/copy-ready-checklist.md` @@ -55,7 +60,9 @@ When repository evidence is incomplete, label assumptions explicitly. 5. make acceptance criteria observable and keep non-goals explicit 6. preserve the user's copy-and-paste formatting preference; when fenced blocks would break copying or rendering, use four-space-indented inner code examples instead -7. validate title clarity, internal consistency, dependency order, and execution readiness +7. validate title clarity, internal consistency, dependency order, and execution readiness; + report contract provenance, proposed versus actual validation, and provider + label availability separately when the title policy applies ## Constraints @@ -64,6 +71,8 @@ When repository evidence is incomplete, label assumptions explicitly. - Do not mix multiple independently shippable outcomes into one issue without stating why. - Do not hide unresolved decisions or missing evidence. - Do not embed provider-specific tooling instructions into the core workflow. +- Do not expand a read-only role's tools to run a formatter or validator. Use an + already authorized executor or mark local validation unavailable. ## Completion Criteria @@ -72,6 +81,8 @@ When repository evidence is incomplete, label assumptions explicitly. - [ ] Validation steps are observable and repository-aware. - [ ] Copy-ready formatting is preserved. - [ ] Missing evidence and open questions remain visible. +- [ ] Selected title policy is discovered explicitly; missing sources, tools, + classification, or live label evidence are not reported as conformance. ## Provenance diff --git a/dist/skills/github-issue-authoring/distribution-manifest.v1.json b/dist/skills/github-issue-authoring/distribution-manifest.v1.json index d2ac895..d069f66 100644 --- a/dist/skills/github-issue-authoring/distribution-manifest.v1.json +++ b/dist/skills/github-issue-authoring/distribution-manifest.v1.json @@ -1,6 +1,6 @@ { "artifact_id": "skill/github-issue-authoring", - "artifact_version": "1.1.0", + "artifact_version": "1.2.0", "compatibility": { "required_tools": [] }, @@ -8,14 +8,22 @@ "generated_paths": [ "dist/skills/github-issue-authoring/SKILL.md", "dist/skills/github-issue-authoring/evals/evals.json", + "dist/skills/github-issue-authoring/references/canonical-issue-titles.md", "dist/skills/github-issue-authoring/references/copy-ready-checklist.md", + "dist/skills/github-issue-authoring/references/issue-title-contract/cases.v1.json", + "dist/skills/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json", + "dist/skills/github-issue-authoring/references/issue-title-contract/selection.v1.json", + "dist/skills/github-issue-authoring/references/issue-title-contract/semantics.txt", + "dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.json", + "dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json", "dist/skills/github-issue-authoring/templates/GITHUB_ISSUE.template.md", + "dist/skills/github-issue-authoring/scripts/verify_title_contract.py", "dist/skills/github-issue-authoring/distribution-manifest.v1.json" ], "generator": "library/organization/skills/build-distributions.py", "schema_version": "aether.distribution-manifest/v1", "source_digest": { "algorithm": "sha256-utf8-lf", - "value": "b7d5e3f41bf49931a01fcfe74e287995fc81fd0f3f0fb0acd52a09a4b43afb43" + "value": "25b8b9d4340db138f142a8d1670a178749332bb93fd03ac903c0bdd442123a63" } } diff --git a/dist/skills/github-issue-authoring/evals/evals.json b/dist/skills/github-issue-authoring/evals/evals.json index 7d2c372..2997c20 100644 --- a/dist/skills/github-issue-authoring/evals/evals.json +++ b/dist/skills/github-issue-authoring/evals/evals.json @@ -1,7 +1,7 @@ { "schema": "aether.skill-evaluations/v2", "skill": "github-issue-authoring", - "version": "1.1.0", + "version": "1.2.0", "cases": [ { "id": "spec-to-issue", @@ -64,6 +64,61 @@ "prohibited": [ "Replacing an explicit implementation request with issue text only" ] + }, + { + "id": "local-title-discovery", + "description": "A fresh session receives local AGENTS guidance selecting the policy and a reviewed checkpoint subject.", + "category": "positive", + "trigger": "should-trigger", + "expected": [ + "Follows the local skill pointer to the immutable selection and selected source artifacts", + "Preserves the reviewed subject and checkpoint identifiers", + "Composes Egolint only through an already authorized executor", + "Reports proposed validation and provider label availability separately" + ] + }, + { + "id": "unavailable-title-evidence", + "description": "The installed skill, source artifact, tool, execution permission, or complete provider label evidence is unavailable.", + "category": "insufficient-evidence", + "trigger": "should-trigger", + "expected": [ + "Returns a useful draft with the exact unavailable evidence stated", + "Does not invent a passing validator result or silently fall back to a moving source", + "Does not treat unknown provider labels as an empty complete set" + ] + }, + { + "id": "conflicting-title-labels", + "description": "An existing issue has absent, multiple, or unsupported type labels.", + "category": "boundary", + "trigger": "should-trigger", + "expected": [ + "Reports needs-classification, conflict, or unsupported-type using the selected semantics", + "Does not infer classification from the current emoji or silently change labels" + ] + }, + { + "id": "stale-title-evidence", + "description": "The available Egolint report uses a different source pin or the provider title/labels changed after inspection.", + "category": "boundary", + "trigger": "should-trigger", + "expected": [ + "Rejects mismatched report provenance", + "Refreshes provider evidence and revalidates before proposing a current write", + "Does not equate source acceptance, repository adoption, and validation" + ] + }, + { + "id": "reviewed-title-migration", + "description": "An existing title contains an unfamiliar prefix and an accepted checkpoint identifier.", + "category": "positive", + "trigger": "should-trigger", + "expected": [ + "Preserves wording and identifiers until an explicit reviewed subject is available", + "Uses the same reviewed subject for repeat formatting without duplicating the prefix", + "Does not reconstruct parent/child relationships by parsing the title" + ] } ] } diff --git a/dist/skills/github-issue-authoring/references/canonical-issue-titles.md b/dist/skills/github-issue-authoring/references/canonical-issue-titles.md new file mode 100644 index 0000000..531e6f2 --- /dev/null +++ b/dist/skills/github-issue-authoring/references/canonical-issue-titles.md @@ -0,0 +1,83 @@ +# Canonical issue titles + +Use this workflow only when scoped repository instructions explicitly select the +Ego Hygiene title policy. Other repositories retain their own conventions. An +organization's `AGENTS.md` does not automatically govern another repository: a +local pointer, installed instruction module, or explicit read step is required. + +## Discover the selected contract + +1. Read local `AGENTS.md` and any more specific instructions. Resolve this skill + from its declared installation; the generated module suggests + `.agents/skills/github-issue-authoring/SKILL.md` relative to the consumer root. +2. Read [selection.v1.json](issue-title-contract/selection.v1.json). It is the + single selection of contract ID, version, full commit, authority, and five + SHA-256 source digests, plus the compatible Egolint candidate revision. +3. Read the selected [contract](issue-title-contract/title-contract.v1.json), + [catalog](issue-title-contract/label-catalog.v1.json), and + [semantics snapshot](issue-title-contract/semantics.txt) through the manifest's + source-to-cache mapping. Do not maintain a second emoji/type table. +4. An authorized executor can run `python3 scripts/verify_title_contract.py` + from the skill directory. A read-only role instead inspects these sources and + requests or consumes executor evidence; it must not claim a digest check ran. + +The five cached payloads are unchanged upstream bytes. Upstream-relative links +inside them refer to the selected upstream tree, not this flattened cache. +`semantics.txt` retains the Markdown source bytes as a raw snapshot; this guide +provides consumer-local navigation. Refresh all five artifacts and their digests +as one reviewed change when changing the pin. + +This selection is **candidate**, with **observe** as its adoption mode. Successful +validation does not accept the contract, enroll a repository, provision labels, +or authorize enforcement. Merging a dependency does not silently change this +selection's authority. The consumer commit identifies source to build; a binary's +reported contract alone does not prove which source produced that binary. + +## Author or revise one issue + +- Resolve one primary type from repository evidence and the selected contract. + For an existing issue, read the complete label set before classifying it. + Missing type labels need classification; multiple recognized types conflict; + unknown `type:*` labels are unsupported. Do not infer a type from a title emoji. +- Review the exact subject before formatting. Preserve case, wording, checkpoint + IDs, issue relationships, and unknown legacy prefixes until explicitly reviewed. + The formatter accepts a reviewed subject; it is not an existing-title parser. +- With execution already authorized and the compatible Egolint available: + + egolint issue-title format --type feature --reviewed-subject '[FLO-OBS-01] Checkpoint 2: Export reports' > proposal.json + python3 scripts/verify_title_contract.py --report proposal.json + + Read `title` and `required_label` from the proposal. A read-only agent hands + these commands and inputs to an authorized executor, or prepares an explicitly + unvalidated proposal from the selected source. Never expand its tool allowlist. +- For a proposed issue, build a local snapshot with `schema_version: 1`, + `complete: true`, the proposed `title`, and its complete proposed `labels` array. + For an existing issue, use the actual, freshly fetched title and full labels. + Keep actual and proposed snapshots separate. Incomplete provider evidence must + use `complete: false`; do not turn unknown labels into an empty verified set. + + egolint issue-title validate --input issue.json > report.json + python3 scripts/verify_title_contract.py --report report.json + + Inspect both commands' exit codes and the report's `status`. Source verification + alone is not title validation. Egolint returns 0 for conformant, 1 for a title or + classification finding, and 2 for invalid/unavailable input or configuration. +- Separately verify that the required label exists in the target repository and + that provider evidence is current before any authorized create/update. Proposed + label conformance does not prove provider label availability. After a delay or + intervening change, refresh the title/labels and revalidate; never replace the + current full label set using an older snapshot. Missing labels need a separate + provisioning task, not automatic creation in this workflow. +- Return title, intended label, selected contract provenance, actual/proposed + validation status, provider label availability, and unresolved evidence with + the issue draft. Only claim a provider write after a successful live receipt. + +## Unavailable and conflicting evidence + +Missing installed skill, source cache, digest mismatch, mutable pin, wrong report +provenance, missing Egolint, or denied execution means validation is unavailable. +Keep a useful draft and report the exact gap; do not substitute a moving `main` +reference, silently repair labels, or call an unvalidated draft conformant. +No script in this package changes GitHub. These instructions do not install a +hook, perform fleet migration, or guarantee future titles without adoption and +the separate provider execution/enforcement work. diff --git a/dist/skills/github-issue-authoring/references/copy-ready-checklist.md b/dist/skills/github-issue-authoring/references/copy-ready-checklist.md index b18326d..7035b11 100644 --- a/dist/skills/github-issue-authoring/references/copy-ready-checklist.md +++ b/dist/skills/github-issue-authoring/references/copy-ready-checklist.md @@ -3,6 +3,10 @@ Use this checklist after drafting the issue. - [ ] Title names the problem or outcome precisely. +- [ ] If local instructions select the title contract, the title and one intended + type label come from its pinned source; reviewed wording and IDs are preserved. +- [ ] Contract source verification, title validation, and current provider label + availability are reported separately; unavailable evidence remains explicit. - [ ] Problem, goal, and current-state evidence are distinct. - [ ] Scope and non-goals prevent accidental expansion. - [ ] Acceptance criteria are observable and implementation-scoped. diff --git a/dist/skills/github-issue-authoring/references/issue-title-contract/cases.v1.json b/dist/skills/github-issue-authoring/references/issue-title-contract/cases.v1.json new file mode 100644 index 0000000..e8b2082 --- /dev/null +++ b/dist/skills/github-issue-authoring/references/issue-title-contract/cases.v1.json @@ -0,0 +1,283 @@ +{ + "schema_version": 1, + "contract_id": "egohygiene.issue-title/v1", + "contract_version": "1.0.0", + "cases": [ + { + "id": "valid-architecture", + "input": { + "title": "πŸ—οΈ [architecture] Document example 1", + "labels": [ + "type:architecture", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "architecture" + } + }, + { + "id": "valid-feature", + "input": { + "title": "✨ [feature] Document example 2", + "labels": [ + "type:feature", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "feature" + } + }, + { + "id": "valid-bug", + "input": { + "title": "πŸ› [bug] Document example 3", + "labels": [ + "type:bug", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "bug" + } + }, + { + "id": "valid-documentation", + "input": { + "title": "πŸ“„ [documentation] Document example 4", + "labels": [ + "type:documentation", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "documentation" + } + }, + { + "id": "valid-research", + "input": { + "title": "πŸ”¬ [research] Document example 5", + "labels": [ + "type:research", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "research" + } + }, + { + "id": "valid-maintenance", + "input": { + "title": "🧹 [maintenance] Document example 6", + "labels": [ + "type:maintenance", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "maintenance" + } + }, + { + "id": "checkpoint-id-preserved", + "input": { + "title": "✨ [feature] [FLO-OBS-01] Checkpoint 2: Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "conformant", + "type": "feature" + } + }, + { + "id": "prefix-mismatch", + "input": { + "title": "πŸ› [bug] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "missing-type", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "area:automation" + ] + }, + "expected": { + "status": "needs-classification", + "type": null + } + }, + { + "id": "multiple-types", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "type:feature", + "type:bug" + ] + }, + "expected": { + "status": "conflict", + "type": null + } + }, + { + "id": "unknown-type", + "input": { + "title": "πŸš€ [delivery] Export reports", + "labels": [ + "type:delivery" + ] + }, + "expected": { + "status": "unsupported-type", + "type": null + } + }, + { + "id": "known-and-unknown-type", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "type:feature", + "type:delivery" + ] + }, + "expected": { + "status": "unsupported-type", + "type": null + } + }, + { + "id": "empty-subject", + "input": { + "title": "✨ [feature] ", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "unformatted-title", + "input": { + "title": "[FLO-OBS-01] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "wrong-emoji", + "input": { + "title": "πŸ› [feature] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "multiline-subject", + "input": { + "title": "✨ [feature] Export\nreports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "double-separator", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "trailing-space", + "input": { + "title": "✨ [feature] Export reports ", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + } + ], + "migrations": [ + { + "id": "reviewed-legacy-title", + "before": { + "title": "🧭 [Release checkpoint 7] Provide pinned tools", + "labels": [ + "type:maintenance" + ] + }, + "reviewed_subject": "[Release checkpoint 7] Provide pinned tools", + "after": { + "title": "🧹 [maintenance] [Release checkpoint 7] Provide pinned tools", + "labels": [ + "type:maintenance" + ] + } + }, + { + "id": "already-conformant-no-op", + "before": { + "title": "✨ [feature] [FLO-OBS-01] Export reports", + "labels": [ + "type:feature" + ] + }, + "reviewed_subject": "[FLO-OBS-01] Export reports", + "after": { + "title": "✨ [feature] [FLO-OBS-01] Export reports", + "labels": [ + "type:feature" + ] + } + } + ] +} diff --git a/dist/skills/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json b/dist/skills/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json new file mode 100644 index 0000000..cebe298 --- /dev/null +++ b/dist/skills/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json @@ -0,0 +1,183 @@ +{ + "$schema": "./schema/catalog.v1.schema.json", + "schema_version": 1, + "catalog_version": "1.0.0", + "owner": "egohygiene/.github", + "defaults": { + "required": true, + "removal_policy": "retain" + }, + "universal": [ + { + "name": "priority:p0", + "description": "Correctness, security, or data-integrity blocker.", + "color": "b60205", + "category": "priority" + }, + { + "name": "priority:p1", + "description": "Required foundation or near-term release work.", + "color": "d93f0b", + "category": "priority" + }, + { + "name": "priority:p2", + "description": "Important planned capability.", + "color": "fbca04", + "category": "priority" + }, + { + "name": "priority:p3", + "description": "Research, exploration, or later direction.", + "color": "c5def5", + "category": "priority" + }, + { + "name": "type:architecture", + "description": "Architecture, boundaries, or system-design work.", + "color": "1f4d8f", + "category": "type" + }, + { + "name": "type:feature", + "description": "New or expanded user-visible capability.", + "color": "0e8a16", + "category": "type" + }, + { + "name": "type:bug", + "description": "Incorrect or unexpected behavior.", + "color": "dc2626", + "category": "type" + }, + { + "name": "type:documentation", + "description": "Documentation or knowledge-organization work.", + "color": "64748b", + "category": "type" + }, + { + "name": "type:research", + "description": "Research, discovery, or evidence gathering.", + "color": "0f766e", + "category": "type" + }, + { + "name": "type:maintenance", + "description": "Maintenance, cleanup, or dependency work.", + "color": "6b7280", + "category": "type" + }, + { + "name": "area:automation", + "description": "Automation, continuous integration, and delivery.", + "color": "0369a1", + "category": "area" + }, + { + "name": "area:developer-experience", + "description": "Developer workflows, tooling, and ergonomics.", + "color": "7c5c1e", + "category": "area" + }, + { + "name": "area:governance", + "description": "Organization policy, contracts, and coordination.", + "color": "6d28d9", + "category": "area" + }, + { + "name": "area:security", + "description": "Security, privacy, provenance, and trust boundaries.", + "color": "b91c1c", + "category": "area" + }, + { + "name": "cross-repo", + "description": "Work coordinated across multiple repositories.", + "color": "5319e7", + "category": "coordination" + }, + { + "name": "needs-routing", + "description": "Work that does not yet have a durable repository owner.", + "color": "d4c5f9", + "category": "coordination" + }, + { + "name": "blocked", + "description": "Work waiting on an explicit dependency or decision.", + "color": "b60205", + "category": "status" + }, + { + "name": "ready", + "description": "Work with sufficient scope, ownership, and acceptance criteria.", + "color": "0e8a16", + "category": "status" + } + ], + "overlays": [ + { + "id": "ecosystem", + "description": "Named Ego Hygiene ecosystem capabilities.", + "labels": [ + {"name": "☁️ aether", "description": "Local AI, agents, prompts, skills, and workflows ☁️", "color": "4338ca"}, + {"name": "🌱 mindgarden", "description": "Knowledge management, digital garden, and long-term thinking 🌱", "color": "3a7d44"}, + {"name": "🎬 dreamscape", "description": "Creative operating system and AI orchestration 🎬", "color": "6d28d9"} + ] + }, + { + "id": "engineering", + "description": "Engineering changes, platforms, packages, and specifications.", + "labels": [ + {"name": "♻️ refactor", "description": "Repository cleanup, restructuring, modernization, and technical debt ♻️", "color": "16a34a"}, + {"name": "βš™οΈ infra", "description": "Infrastructure, repositories, tooling, and configuration βš™οΈ", "color": "274472"}, + {"name": "⚑ dx", "description": "Developer experience, tooling, and ergonomics ⚑", "color": "7c5c1e"}, + {"name": "πŸ—οΈ architecture", "description": "Architecture, foundations, and system design πŸ—οΈ", "color": "1f4d8f"}, + {"name": "πŸ› bug", "description": "Something isn't working πŸ›", "color": "dc2626"}, + {"name": "πŸ“„ documentation", "description": "Documentation improvements and knowledge organization πŸ“„", "color": "64748b"}, + {"name": "πŸ“‹ backlog", "description": "Captured ideas and future work πŸ“‹", "color": "4b5563"}, + {"name": "πŸ“ schema", "description": "Schemas, specifications, contracts, and data models πŸ“", "color": "7c3aed"}, + {"name": "πŸ“¦ package", "description": "Reusable packages and shared libraries πŸ“¦", "color": "0369a1"}, + {"name": "πŸ“± flutter", "description": "Flutter application and Flutter Foundation πŸ“±", "color": "0175c2"}, + {"name": "πŸ”„ lifecycle", "description": "Application lifecycles, managers, and orchestration πŸ”„", "color": "0f766e"}, + {"name": "πŸ”Œ plugin", "description": "Plugin architecture, registries, and extensibility πŸ”Œ", "color": "2563eb"} + ] + }, + { + "id": "creative-publishing", + "description": "Creative production, publishing, design, and outreach.", + "labels": [ + {"name": "✍️ writing", "description": "Writing workflow, editorial improvements, and authoring ✍️", "color": "7c3aed"}, + {"name": "🌐 website", "description": "Website, documentation site, and public experience 🌐", "color": "0284c7"}, + {"name": "πŸŽ₯ video", "description": "Video production, animation, and cinematic content πŸŽ₯", "color": "b91c1c"}, + {"name": "🎨 design", "description": "Design system, UI, UX, branding, and visuals 🎨", "color": "4e8065"}, + {"name": "🎡 music", "description": "Music, albums, lyrics, and audio production 🎡", "color": "7f1d1d"}, + {"name": "πŸ“– magazine", "description": "Magazine, comic, editorial, and visual storytelling πŸ“–", "color": "9333ea"}, + {"name": "πŸ“š article", "description": "Long-form articles and essays πŸ“š", "color": "2563eb"}, + {"name": "πŸ“ publishing", "description": "Publishing platform, workflows, and distribution πŸ“", "color": "0e7490"}, + {"name": "πŸ“£ marketing", "description": "Marketing, social media, SEO, and outreach πŸ“£", "color": "15803d"} + ] + }, + { + "id": "human-practice", + "description": "Human domains, practices, philosophy, and journeys.", + "labels": [ + {"name": "🌍 domain", "description": "Life domains, domain models, and domain-specific functionality 🌍", "color": "15803d"}, + {"name": "🎯 practice", "description": "Practices, rituals, habits, and exercises 🎯", "color": "15803d"}, + {"name": "πŸ›οΈ philosophy", "description": "Purpose, principles, ontology, and conceptual evolution πŸ›οΈ", "color": "6d28d9"}, + {"name": "πŸ—ΊοΈ journey", "description": "Journey modeling, progress, and timeline systems πŸ—ΊοΈ", "color": "0891b2"} + ] + }, + { + "id": "research-funding", + "description": "Research and the resources that support it.", + "labels": [ + {"name": "πŸ”¬ research", "description": "Research, literature, evidence, and references πŸ”¬", "color": "0f766e"}, + {"name": "πŸ’° funding", "description": "Grants, sponsorships, partnerships, and monetization πŸ’°", "color": "b45309"} + ] + } + ], + "deprecations": [] +} diff --git a/dist/skills/github-issue-authoring/references/issue-title-contract/selection.v1.json b/dist/skills/github-issue-authoring/references/issue-title-contract/selection.v1.json new file mode 100644 index 0000000..8b321f4 --- /dev/null +++ b/dist/skills/github-issue-authoring/references/issue-title-contract/selection.v1.json @@ -0,0 +1,30 @@ +{ + "schema_version": 1, + "contract": { + "contract_id": "egohygiene.issue-title/v1", + "contract_version": "1.0.0", + "repository": "egohygiene/.github", + "revision": "19d2be9bf0191710508cefbb9f0b1abb3a40d9be", + "authority": "candidate", + "source_digests": { + ".github/issues/title-contract.v1.json": "205202bda3c3b069a105924499ba7213a6a99645658e500cc910dfa085eeb30e", + ".github/issues/schema/title-contract.v1.schema.json": "9df220330ad07758122567620f557aa11aad85522914fe3f4cdc7de6a5ca7295", + ".github/labels/catalog.v1.json": "7063a61608a66454310e5a7746b1514d1d11018da08427bfb49f4612326ff6ff", + "docs/issue-titles.md": "243b5fdfec3fff82623fdcb43fa490829af9380d5f0b1d568f9f32b277de0660", + "fixtures/issue-titles/cases.v1.json": "c21989c506e363c7b7845e0f63bdaf14f2e0de78f9d6c421d04e0f5a444edab2" + } + }, + "adoption": "explicit-repository-selection-observe", + "consumer": { + "repository": "egohygiene/egolint", + "revision": "3a6785cd408e218bc7e3691e01dc659e4cf79de8", + "authority": "candidate" + }, + "files": { + ".github/issues/title-contract.v1.json": "title-contract.v1.json", + ".github/issues/schema/title-contract.v1.schema.json": "title-contract.v1.schema.json", + ".github/labels/catalog.v1.json": "label-catalog.v1.json", + "docs/issue-titles.md": "semantics.txt", + "fixtures/issue-titles/cases.v1.json": "cases.v1.json" + } +} diff --git a/dist/skills/github-issue-authoring/references/issue-title-contract/semantics.txt b/dist/skills/github-issue-authoring/references/issue-title-contract/semantics.txt new file mode 100644 index 0000000..2ed0cdd --- /dev/null +++ b/dist/skills/github-issue-authoring/references/issue-title-contract/semantics.txt @@ -0,0 +1,136 @@ +# Canonical issue titles + +Contract: `egohygiene.issue-title/v1`, version `1.0.0`. +Owner: `egohygiene/.github`. +Tracking: [contract checkpoint #44](https://github.com/egohygiene/.github/issues/44), +[parent #24](https://github.com/egohygiene/.github/issues/24), and +[fleet reconciliation #23](https://github.com/egohygiene/.github/issues/23). + +The [machine-readable contract](../.github/issues/title-contract.v1.json) +owns the type-to-emoji mapping and rule identifiers. This document owns their +meaning. The [label catalog](../.github/labels/catalog.v1.json) continues to +own exact label names, descriptions, colors, and category membership. +Changes to these sources must be reviewed together when their meanings change. + +## Format and primary type + +An issue title is `{emoji} [{type}] {subject}`. Use exactly one ASCII space at +each prefix boundary, the contract's exact emoji codepoints, and the lowercase +type token. The subject is nonempty, has no leading/trailing whitespace, and +contains no line breaks. Prefer a short, descriptive action for new work. +Do not automatically rewrite case or prose during migration. + +Choose exactly one primary universal `type:*` label. Other labels remain +independent. Expressive overlay labels such as `πŸ› bug` do not substitute for +`type:bug`. GitHub's native issue-type field is also a separate provider +field; it does not override this contract's primary label. + +The six mappings are in the contract. Illustrative titles include: + +- `πŸ› [bug] Fix duplicate archive ingestion` +- `✨ [feature] Add repository achievement tracking` +- `πŸ“„ [documentation] Explain local setup` +- `🧹 [maintenance] [Release checkpoint 7] Provide pinned tools` + +Keep tracking IDs and meaningful checkpoint/roadmap text within the subject. +Parentage, dependencies, and completion are determined by stable issue identity +and relationships, never by parsing this prefix. Issue titles do not change +commit messages, pull-request titles, or release semantics. + +## Classification and validation order + +For a complete observed issue snapshot, evaluate in this order: + +1. If any label beginning `type:` is absent from the pinned mapping, report + `unsupported-type`. +2. If no known primary type is present, report `needs-classification`. +3. If multiple distinct known primary types are present, report `conflict`. +4. With exactly one known type, compare the title against its required prefix + and the subject rules. Report `conformant` or `nonconformant`. + +Treat labels as a set. Never infer the primary type from title decoration, +choose the first label, or silently fall back to maintenance. Missing source +access, unavailable labels, and incomplete snapshots are capability/evidence +limitations, not an empty label set or a passing result. + +A maintainer or agent can propose a classification from issue content. +Record that decision in the reviewed migration plan. Formatting is then +deterministic; semantic classification is not part of the formatter. + +## Existing-issue normalization + +A formatter takes an explicit primary type and a reviewed subject and produces +the canonical title. Before applying to an existing issue, capture its stable +identity, original title and labels, observed revision/update evidence, +contract revision/digest, selected type, reviewed subject, and expected output. + +A normalizer may recognize one complete, already-canonical prefix and retain +the subject byte-for-byte. For legacy, duplicated, malformed, or unknown +prefixes, require an explicit reviewed subject. Do not strip arbitrary emoji, +bracketed tracking IDs, or checkpoint markers. Changing wording beyond the +prefix must be an explicit item in the plan. + +Retain unrelated labels and all issue bodies, discussion, assignments, +milestones, state, and relationships. Recheck the current title and labels +before each write; changed inputs become conflicts rather than overwrites. +Record successful updates for resume. Rollback restores the recorded title and +any managed type-label changes only when current state still matches the +applied result. + +A repeat against conformant state produces zero mutations. The +[synthetic cases](../fixtures/issue-titles/cases.v1.json) describe expected +validation statuses and reviewed migration examples for downstream consumers. + +## Agent entry point and adoption + +[AGENTS.md](../AGENTS.md) is the local discovery entry point. Before authorized +issue creation or editing, an agent reads this document and the mapping, +selects a primary type, verifies provider label availability, and prepares a +conforming title. Unavailable provider access must be reported rather than +invented. Instructions do not grant write, merge, or publication authority. + +This source change establishes a contract candidate for review. Once merged, +it establishes the approved definition; it does not prove provider enforcement +or silently enroll other repositories. + +Consumers pin the contract, semantics, catalog, schema, and examples from one +immutable source revision and record that revision in their adoption evidence. +The catalog version must match the contract reference. An unavailable or +incompatible contract is reported as unavailable; use no silent fallback to +mutable main or an older cached policy. + +Initial adoption is observe mode. A repository moves to enforcement only after +its primary labels, templates, local agent discovery, validator, and execution +path are present and evidenced. GitHub does not distribute this repository's +AGENTS.md to every agent: consumer repositories need their own explicit pointer +or managed Aether projection. Local template overrides need explicit updates. + +## Ownership and staged delivery + +| Owner | Responsibility | +| --- | --- | +| .github | Organization title convention, label taxonomy, examples, adoption coordination | +| Hygiene | Organization applicability and contract-index reference | +| Aether | Portable issue-authoring guidance and managed agent projections | +| Egolint | Reusable issue-title validation and deterministic formatting | +| Relay | GitHub preview/apply, event handling, receipts, and recovery | +| Pace / Observatory | Adoption and drift evidence through existing boundaries | + +Checkpoint #44 supplies the contract, examples, local entry point, and +structural checks. Consumer implementation and contract-index registration are +follow-on work. The organization work form still has its existing default +until template consumption is implemented; no automatic issue renaming or +ongoing event workflow is installed by this change. + +After contract review, prove the formatter/validator and agent consumption, +then a single-repository Relay preview/apply and no-op repeat. Add ongoing +event/template conformance and expand the backlog sweep through #23. +Keep historical issues in a separate recorded batch. + +## Contract changes + +Editorial clarification preserving accepted outputs is a patch. +Additive optional examples or metadata may be minor. A changed mapping, +classification meaning, required format, or acceptance behavior requires a +major version and migration plan. A consumer upgrade must record the selected +revision; editing this source does not retroactively mutate issues. diff --git a/dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.json b/dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.json new file mode 100644 index 0000000..75e3492 --- /dev/null +++ b/dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.json @@ -0,0 +1,67 @@ +{ + "$schema": "./schema/title-contract.v1.schema.json", + "schema_version": 1, + "contract_id": "egohygiene.issue-title/v1", + "contract_version": "1.0.0", + "owner": "egohygiene/.github", + "label_catalog": { + "path": "../labels/catalog.v1.json", + "version": "1.0.0" + }, + "format": "{emoji} [{type}] {subject}", + "types": [ + { + "type": "architecture", + "emoji": "πŸ—οΈ", + "label": "type:architecture" + }, + { + "type": "feature", + "emoji": "✨", + "label": "type:feature" + }, + { + "type": "bug", + "emoji": "πŸ›", + "label": "type:bug" + }, + { + "type": "documentation", + "emoji": "πŸ“„", + "label": "type:documentation" + }, + { + "type": "research", + "emoji": "πŸ”¬", + "label": "type:research" + }, + { + "type": "maintenance", + "emoji": "🧹", + "label": "type:maintenance" + } + ], + "rules": { + "primary_type": "exactly-one-known-type-label", + "missing_type": "needs-classification", + "multiple_types": "conflict", + "unknown_type": "unsupported-type", + "subject": "nonempty-trimmed-single-line", + "separators": "single-ascii-space", + "emoji_matching": "exact-codepoints", + "subject_case": "preserve", + "existing_subject": "preserve-wording-and-identifiers", + "migration": "reviewed-explicit-subject", + "normalization": "idempotent", + "unknown_prefix": "preserve-until-reviewed", + "relationships": "native-identifiers-not-title-parsing" + }, + "adoption": { + "scope": "explicit-repository-adoption", + "default_mode": "observe", + "reference": "immutable-commit", + "unavailable_contract": "report-unavailable" + }, + "semantics": "../../docs/issue-titles.md", + "conformance_examples": "../../fixtures/issue-titles/cases.v1.json" +} diff --git a/dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json b/dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json new file mode 100644 index 0000000..71c86e8 --- /dev/null +++ b/dist/skills/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json @@ -0,0 +1,177 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/egohygiene/.github/blob/main/.github/issues/schema/title-contract.v1.schema.json", + "title": "Ego Hygiene issue-title contract v1", + "type": "object", + "additionalProperties": false, + "required": [ + "$schema", + "schema_version", + "contract_id", + "contract_version", + "owner", + "label_catalog", + "format", + "types", + "rules", + "adoption", + "semantics", + "conformance_examples" + ], + "properties": { + "$schema": { + "const": "./schema/title-contract.v1.schema.json" + }, + "schema_version": { + "const": 1 + }, + "contract_id": { + "const": "egohygiene.issue-title/v1" + }, + "contract_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "owner": { + "const": "egohygiene/.github" + }, + "label_catalog": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "version" + ], + "properties": { + "path": { + "const": "../labels/catalog.v1.json" + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + } + } + }, + "format": { + "const": "{emoji} [{type}] {subject}" + }, + "types": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "emoji", + "label" + ], + "properties": { + "type": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + }, + "emoji": { + "type": "string", + "minLength": 1 + }, + "label": { + "type": "string", + "pattern": "^type:[a-z][a-z0-9-]*$" + } + } + } + }, + "rules": { + "type": "object", + "additionalProperties": false, + "required": [ + "primary_type", + "missing_type", + "multiple_types", + "unknown_type", + "subject", + "separators", + "emoji_matching", + "subject_case", + "existing_subject", + "migration", + "normalization", + "unknown_prefix", + "relationships" + ], + "properties": { + "primary_type": { + "const": "exactly-one-known-type-label" + }, + "missing_type": { + "const": "needs-classification" + }, + "multiple_types": { + "const": "conflict" + }, + "unknown_type": { + "const": "unsupported-type" + }, + "subject": { + "const": "nonempty-trimmed-single-line" + }, + "separators": { + "const": "single-ascii-space" + }, + "emoji_matching": { + "const": "exact-codepoints" + }, + "subject_case": { + "const": "preserve" + }, + "existing_subject": { + "const": "preserve-wording-and-identifiers" + }, + "migration": { + "const": "reviewed-explicit-subject" + }, + "normalization": { + "const": "idempotent" + }, + "unknown_prefix": { + "const": "preserve-until-reviewed" + }, + "relationships": { + "const": "native-identifiers-not-title-parsing" + } + } + }, + "adoption": { + "type": "object", + "additionalProperties": false, + "required": [ + "scope", + "default_mode", + "reference", + "unavailable_contract" + ], + "properties": { + "scope": { + "const": "explicit-repository-adoption" + }, + "default_mode": { + "const": "observe" + }, + "reference": { + "const": "immutable-commit" + }, + "unavailable_contract": { + "const": "report-unavailable" + } + } + }, + "semantics": { + "const": "../../docs/issue-titles.md" + }, + "conformance_examples": { + "const": "../../fixtures/issue-titles/cases.v1.json" + } + } +} diff --git a/dist/skills/github-issue-authoring/scripts/verify_title_contract.py b/dist/skills/github-issue-authoring/scripts/verify_title_contract.py new file mode 100644 index 0000000..91a95a6 --- /dev/null +++ b/dist/skills/github-issue-authoring/scripts/verify_title_contract.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Verify a pinned source bundle and, optionally, Egolint report provenance. + +This offline check does not classify issues, format titles, or authorize writes. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path, PurePosixPath +import re + +DEFAULT_SELECTION = ( + Path(__file__).resolve().parents[1] + / "references/issue-title-contract/selection.v1.json" +) +SOURCE_PATHS = { + ".github/issues/title-contract.v1.json", + ".github/issues/schema/title-contract.v1.schema.json", + ".github/labels/catalog.v1.json", + "docs/issue-titles.md", + "fixtures/issue-titles/cases.v1.json", +} + + +def load_selection(path: Path = DEFAULT_SELECTION) -> dict: + """Read one immutable selection and verify all cached source bytes.""" + selection = json.loads(path.read_text(encoding="utf-8")) + if selection["schema_version"] != 1: + raise ValueError("unsupported selection schema") + contract = selection["contract"] + if not re.fullmatch(r"[0-9a-f]{40}", contract["revision"]): + raise ValueError("contract revision must be an immutable full commit SHA") + if contract["authority"] not in {"candidate", "accepted"}: + raise ValueError("contract authority must be explicit") + if selection["adoption"] != "explicit-repository-selection-observe": + raise ValueError("selection does not authorize automatic repository adoption") + files = selection["files"] + digests = contract["source_digests"] + if set(files) != SOURCE_PATHS or set(digests) != SOURCE_PATHS: + raise ValueError("selection must include exactly the five contract sources") + if len(set(files.values())) != len(files): + raise ValueError("source cache paths must be distinct") + root = path.resolve().parent + sources = {} + for source_path, relative_path in files.items(): + relative = PurePosixPath(relative_path) + if relative.is_absolute() or ".." in relative.parts or "\\" in relative_path: + raise ValueError("source cache path must stay within the selection directory") + local = (root / relative_path).resolve() + if not local.is_relative_to(root): + raise ValueError("source cache path escapes the selection directory") + raw = local.read_bytes() + if hashlib.sha256(raw).hexdigest() != digests[source_path]: + raise ValueError(f"source digest mismatch: {source_path}") + sources[source_path] = raw + title_contract = json.loads(sources[".github/issues/title-contract.v1.json"]) + cases = json.loads(sources["fixtures/issue-titles/cases.v1.json"]) + for field in ("contract_id", "contract_version"): + if title_contract[field] != contract[field] or cases[field] != contract[field]: + raise ValueError(f"source identity mismatch: {field}") + if title_contract["owner"] != contract["repository"]: + raise ValueError("contract owner does not match selected repository") + catalog = json.loads(sources[".github/labels/catalog.v1.json"]) + if catalog["catalog_version"] != title_contract["label_catalog"]["version"]: + raise ValueError("label catalog version does not match contract") + consumer = selection["consumer"] + if not re.fullmatch(r"[0-9a-f]{40}", consumer["revision"]): + raise ValueError("consumer revision must be an immutable full commit SHA") + if consumer["authority"] not in {"candidate", "accepted"}: + raise ValueError("consumer authority must be explicit") + return selection + + +def verify_report(selection: dict, report: dict) -> None: + """Reject a proposal/report from a missing, stale, or mixed source selection.""" + if report.get("schema_version") != 1 or report.get("contract") != selection["contract"]: + raise ValueError("report contract provenance does not match the local selection") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--selection", type=Path, default=DEFAULT_SELECTION) + parser.add_argument("--report", type=Path, help="Egolint proposal or validation JSON") + args = parser.parse_args() + try: + selection = load_selection(args.selection) + if args.report is not None: + verify_report(selection, json.loads(args.report.read_text(encoding="utf-8"))) + except (OSError, ValueError, KeyError, TypeError, AttributeError) as exc: + print(json.dumps({"status": "unavailable", "reason": str(exc)})) + return 2 + print(json.dumps({"status": "verified", "contract": selection["contract"]})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/dist/skills/github-issue-authoring/templates/GITHUB_ISSUE.template.md b/dist/skills/github-issue-authoring/templates/GITHUB_ISSUE.template.md index 5b272a5..98199bd 100644 --- a/dist/skills/github-issue-authoring/templates/GITHUB_ISSUE.template.md +++ b/dist/skills/github-issue-authoring/templates/GITHUB_ISSUE.template.md @@ -1,5 +1,15 @@ # + + ## Summary State the problem, why it matters, and the desired outcome. diff --git a/dist/zencoder/manual-import/agents.json b/dist/zencoder/manual-import/agents.json index 885d3c6..0a06876 100644 --- a/dist/zencoder/manual-import/agents.json +++ b/dist/zencoder/manual-import/agents.json @@ -332,7 +332,7 @@ ], "description": "Transforms ideas, specs, audits, bugs, research, and brain dumps into scoped, implementation-ready GitHub issues.", "id": "github-issue-creator", - "instructions": "## Mission\n\nCreate the execution contract for a concrete unit of work. Preserve the user's motivation while removing ambiguity, repetition, and accidental scope inflation. Do not silently implement the issue.\n\n## Operating contract\n\nApply the [`github-issue-authoring`](.agents/skills/github-issue-authoring/SKILL.md) skill. Follow [`specs/authoring/specfile.spec.md`](.github/specs/authoring/specfile.spec.md) and any applicable domain specification.\n\n\n\n## Continuity composition\n\nBefore selecting repository work, compose\n[`maintain-repository-continuity`](.agents/skills/maintain-repository-continuity/SKILL.md)\nin **Resume** mode after reading scoped instructions and canonical documents.\nThis role is continuity read-only: do not create, refresh, or otherwise mutate\n`CONTINUITY.md` unless a separately authorized repository-changing workflow\ntakes ownership of that handoff.\n\n- **Contribute:** Verified repository context, dependency state, issue scope, acceptance criteria, and evidence gaps\n- **Never claim:** That an issue was created or updated without live evidence, or authority to mutate CONTINUITY.md without separate authorization\n\n## Workflow\n\n1. Extract the problem, motivation, desired state, constraints, and open questions.\n2. Inspect repository architecture, relevant specifications, source, tests, automation, workflows, existing issues, and issue templates when available.\n3. Choose one primary issue type and determine whether the request is one issue or a dependency-ordered roadmap.\n4. Define included scope, exclusions, ownership, integration boundaries, and observable completion.\n5. Add evidence-backed implementation guidance without prescribing unsupported file paths or dependencies.\n6. Define validation and acceptance criteria that another engineer or coding agent can execute.\n7. Check the issue for independence, reviewability, internal consistency, and copy readiness.\n\n## Boundaries\n\n- Do not claim repository conventions or files exist without evidence.\n- Do not mix research and production implementation unless a proof of concept is intentionally scoped.\n- Ask only when missing information materially changes architecture, safety, irreversible behavior, or acceptance criteria.\n- Prefer a reversible assumption for non-material ambiguity and record it.\n- Generate issues one at a time when the user requests staged authoring.\n- `edit` and `execute` are excluded; issue authoring is read, search, and web only.\n\n## Completion\n\nReturn exactly the output format selected by the governing specification or explicit user request, with no cleanup required before use. Recommended next step: implementation by Copilot or the default implementer.\n\n\n\n## Decision-impact checkpoint\n\nBefore changing code, inspect the applicable repository instructions, roadmap,\nlocal decisions, and relevant organization decisions. Classify the work as\n`create`, `update`, `supersede`, `reference`, or `ADR not required`. Do not\nsilently make a consequential or ambiguous choice: surface it and request human\nreview. Automated agents never mark an ADR accepted.\n\n- **Create** a proposed ADR when no existing record governs a consequential\n choice.\n- **Update** an existing proposal, or add evidence/outcomes/corrections that do\n not rewrite an accepted decision's historical meaning.\n- **Supersede** when an accepted choice must change: propose a replacement,\n preserve the old record, and link both directions after human approval.\n- **Reference** the governing ADR when work implements an existing decision;\n do not create a duplicate.\n- Use **`ADR not required`** with one short reason for routine, local, reversible\n work that follows accepted design.\n\n| Area | ADR required | ADR not required |\n| --- | --- | --- |\n| Dependencies | Adopt/remove a durable framework or make a compatibility-changing major upgrade | Apply a compatible patch within accepted dependency policy |\n| Public contracts | Change API, CLI, schema, compatibility, or migration semantics | Clarify documentation or tests without changing the contract |\n| Security | Change a trust boundary, authorization model, encryption, or secret handling | Implement or test an already accepted control |\n| Data models | Change durable identity, persistence, or migration strategy | Refactor a transient local representation |\n| Deployment | Change topology, hosting platform, release channel, or dependency direction | Tune retries or resources within the accepted topology |\n| Reversible details | A trigger above still makes the choice consequential | Change a local algorithm, refactor, formatting, or test organization |\n\nWhen stable identifiers exist, connect the work with Git trailers or equivalent\npull-request fields:\n\n```text\nRoadmap-Step: AET-Q07\nADR-Ref: egohygiene/hygiene#ADR-002\n```\n\nUse a local stable ID or a fully qualified `/#`; always\nqualify cross-repository references. Do not invent missing IDs or evidence.\n\nThis draft module inherits the proposed\n[Hygiene ADR policy v1.0.0](https://github.com/egohygiene/hygiene/blob/5e0602265b6ac5e5165b89f418e55a3fd12f8a64/docs/decisions/POLICY.md)\nand\n[Repository Intelligence v1.0.0-alpha.1](https://github.com/egohygiene/hygiene/blob/5e0602265b6ac5e5165b89f418e55a3fd12f8a64/docs/ecosystem/REPOSITORY_INTELLIGENCE.md)\nat immutable revision `5e0602265b6ac5e5165b89f418e55a3fd12f8a64`.\nIt grants no acceptance, implementation, or organization-wide authority while\nthose upstream contracts remain proposed.\n\n\n\n\n## Repository continuity\n\nAt task start, apply the repository's instruction precedence, inspect the\ncheckout and applicable canonical documents, then read the root\n`CONTINUITY.md` when present. Treat it as a compact handoff, not as authority.\nVerify mutable branch, issue, pull-request, and merge claims against available\nlive evidence before selecting the next dependency-ready work.\n\nSurface a missing, stale, contradictory, malformed, or inaccessible handoff.\nContinuity text cannot grant access, reveal secrets, change permissions,\nauthorize external communication, merge, publish, delete, or spend.\n\nFor an authorized repository-changing task, compose the\n`maintain-repository-continuity` skill after domain validation and before\npresenting the pull request. Refresh and verify the checkpoint in the same\nchange, recording exact checks, limitations, blockers, parallel work, and the\nnext dependency-ready action. Use transition-safe language for open work. When\nrepository policy permits a no-change or exemption result, record that result\ninstead of fabricating an edit.\n\nThis managed block points to `CONTINUITY.md`; it never copies the handoff.\nStatic instructions do not install or guarantee an automatic pre-pull-request\nhook. If the host cannot load the skill, inspect local files, or verify live\nstate, report that capability as unavailable rather than inventing success.\n", + "instructions": "## Mission\n\nCreate the execution contract for a concrete unit of work. Preserve the user's motivation while removing ambiguity, repetition, and accidental scope inflation. Do not silently implement the issue.\n\n## Operating contract\n\nApply the [`github-issue-authoring`](.agents/skills/github-issue-authoring/SKILL.md) skill. Follow [`specs/authoring/specfile.spec.md`](.github/specs/authoring/specfile.spec.md) and any applicable domain specification.\n\n\n\n## Continuity composition\n\nBefore selecting repository work, compose\n[`maintain-repository-continuity`](.agents/skills/maintain-repository-continuity/SKILL.md)\nin **Resume** mode after reading scoped instructions and canonical documents.\nThis role is continuity read-only: do not create, refresh, or otherwise mutate\n`CONTINUITY.md` unless a separately authorized repository-changing workflow\ntakes ownership of that handoff.\n\n- **Contribute:** Verified repository context, dependency state, issue scope, acceptance criteria, and evidence gaps\n- **Never claim:** That an issue was created or updated without live evidence, or authority to mutate CONTINUITY.md without separate authorization\n\n## Workflow\n\n1. Extract the problem, motivation, desired state, constraints, and open questions.\n2. Inspect repository architecture, relevant specifications, source, tests, automation, workflows, existing issues, and issue templates when available.\n3. Choose one primary issue type and determine whether the request is one issue or a dependency-ordered roadmap.\n When local instructions select the Ego Hygiene title contract, follow the\n skill's canonical-title reference: resolve the immutable selection, preserve\n the reviewed subject, and return the intended type label with provenance.\n4. Define included scope, exclusions, ownership, integration boundaries, and observable completion.\n5. Add evidence-backed implementation guidance without prescribing unsupported file paths or dependencies.\n6. Define validation and acceptance criteria that another engineer or coding agent can execute.\n7. Check the issue for independence, reviewability, internal consistency, and copy readiness.\n\n## Boundaries\n\n- Do not claim repository conventions or files exist without evidence.\n- Do not mix research and production implementation unless a proof of concept is intentionally scoped.\n- Ask only when missing information materially changes architecture, safety, irreversible behavior, or acceptance criteria.\n- Prefer a reversible assumption for non-material ambiguity and record it.\n- Generate issues one at a time when the user requests staged authoring.\n- `edit` and `execute` are excluded; issue authoring is read, search, and web only.\n- Request or consume formatter/validator evidence from an authorized executor;\n do not run Egolint in this read-only role. Keep missing tools, stale pins,\n conflicting classification, and unavailable provider labels explicit.\n\n## Completion\n\nReturn exactly the output format selected by the governing specification or explicit user request, with no cleanup required before use. Recommended next step: implementation by Copilot or the default implementer.\n\n\n\n## Decision-impact checkpoint\n\nBefore changing code, inspect the applicable repository instructions, roadmap,\nlocal decisions, and relevant organization decisions. Classify the work as\n`create`, `update`, `supersede`, `reference`, or `ADR not required`. Do not\nsilently make a consequential or ambiguous choice: surface it and request human\nreview. Automated agents never mark an ADR accepted.\n\n- **Create** a proposed ADR when no existing record governs a consequential\n choice.\n- **Update** an existing proposal, or add evidence/outcomes/corrections that do\n not rewrite an accepted decision's historical meaning.\n- **Supersede** when an accepted choice must change: propose a replacement,\n preserve the old record, and link both directions after human approval.\n- **Reference** the governing ADR when work implements an existing decision;\n do not create a duplicate.\n- Use **`ADR not required`** with one short reason for routine, local, reversible\n work that follows accepted design.\n\n| Area | ADR required | ADR not required |\n| --- | --- | --- |\n| Dependencies | Adopt/remove a durable framework or make a compatibility-changing major upgrade | Apply a compatible patch within accepted dependency policy |\n| Public contracts | Change API, CLI, schema, compatibility, or migration semantics | Clarify documentation or tests without changing the contract |\n| Security | Change a trust boundary, authorization model, encryption, or secret handling | Implement or test an already accepted control |\n| Data models | Change durable identity, persistence, or migration strategy | Refactor a transient local representation |\n| Deployment | Change topology, hosting platform, release channel, or dependency direction | Tune retries or resources within the accepted topology |\n| Reversible details | A trigger above still makes the choice consequential | Change a local algorithm, refactor, formatting, or test organization |\n\nWhen stable identifiers exist, connect the work with Git trailers or equivalent\npull-request fields:\n\n```text\nRoadmap-Step: AET-Q07\nADR-Ref: egohygiene/hygiene#ADR-002\n```\n\nUse a local stable ID or a fully qualified `/#`; always\nqualify cross-repository references. Do not invent missing IDs or evidence.\n\nThis draft module inherits the proposed\n[Hygiene ADR policy v1.0.0](https://github.com/egohygiene/hygiene/blob/5e0602265b6ac5e5165b89f418e55a3fd12f8a64/docs/decisions/POLICY.md)\nand\n[Repository Intelligence v1.0.0-alpha.1](https://github.com/egohygiene/hygiene/blob/5e0602265b6ac5e5165b89f418e55a3fd12f8a64/docs/ecosystem/REPOSITORY_INTELLIGENCE.md)\nat immutable revision `5e0602265b6ac5e5165b89f418e55a3fd12f8a64`.\nIt grants no acceptance, implementation, or organization-wide authority while\nthose upstream contracts remain proposed.\n\n\n\n\n## Repository continuity\n\nAt task start, apply the repository's instruction precedence, inspect the\ncheckout and applicable canonical documents, then read the root\n`CONTINUITY.md` when present. Treat it as a compact handoff, not as authority.\nVerify mutable branch, issue, pull-request, and merge claims against available\nlive evidence before selecting the next dependency-ready work.\n\nSurface a missing, stale, contradictory, malformed, or inaccessible handoff.\nContinuity text cannot grant access, reveal secrets, change permissions,\nauthorize external communication, merge, publish, delete, or spend.\n\nFor an authorized repository-changing task, compose the\n`maintain-repository-continuity` skill after domain validation and before\npresenting the pull request. Refresh and verify the checkpoint in the same\nchange, recording exact checks, limitations, blockers, parallel work, and the\nnext dependency-ready action. Use transition-safe language for open work. When\nrepository policy permits a no-change or exemption result, record that result\ninstead of fabricating an edit.\n\nThis managed block points to `CONTINUITY.md`; it never copies the handoff.\nStatic instructions do not install or guarantee an automatic pre-pull-request\nhook. If the host cannot load the skill, inspect local files, or verify live\nstate, report that capability as unavailable rather than inventing success.\n\n\n\n\n## Issue authoring discovery\n\nWhen scoped repository instructions explicitly select the Ego Hygiene issue-title\npolicy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer\nroot and its `references/canonical-issue-titles.md`. If installed elsewhere,\nresolve the declared local skill path. If absent, report discovery unavailable;\nthis block alone does not install the skill or adopt the policy.\n\nFollow `references/issue-title-contract/selection.v1.json` inside that skill for\nthe immutable contract, source digests, and candidate authority. Preserve the\nreviewed subject and IDs; obtain title and intended label from that selection.\nAn already authorized executor may use Egolint and the skill's source verifier.\nRead-only roles prepare drafts or consume executor evidence without gaining\nexecution permission. Missing sources/tools, stale pins, ambiguous type labels,\nand unavailable current provider labels remain explicit gaps.\n\nThis is static discovery guidance, not a hook or fleet migration. Organization\ninstructions do not automatically inherit into consumer repositories. Preserve\nconsumer-owned prose, nested instruction precedence, and role permissions.\n", "name": "GitHub Issue Creator", "provenance": { "continuity_disposition": "reader", @@ -376,6 +376,19 @@ }, "status": "draft", "version": "1.0.0" + }, + { + "id": "issue-authoring", + "selection": "library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json", + "selection_sha256": "435d9310e49ee10cafa3f477c2080a88e666a247e9496dc40dbb17356d319582", + "skill": "github-issue-authoring", + "source": "library/organization/projections/templates/issue-authoring.AGENTS.md", + "source_digest": { + "algorithm": "sha256-utf8-lf", + "value": "9320e46a9947a5f98752398f9630c1539e6b16fa55ca4ca050aae7f0205b6a14" + }, + "status": "draft", + "version": "0.1.0" } ], "interface": "aether.projection-interface/v1", @@ -384,7 +397,7 @@ "source": "library/organization/agents/github-issue-creator/AGENT.md", "source_digest": { "algorithm": "sha256-utf8-lf", - "value": "df647f3a6243773ec612a9c167156b177e40d730309887c9a452ada0322b530b" + "value": "d58a76d32a46f0bb22f1b776e037681c9cbfbf69dfb7fa04d6a1110fe87f9e6f" } } }, diff --git a/docs/issue-title-authoring-pilot.md b/docs/issue-title-authoring-pilot.md new file mode 100644 index 0000000..bbbe00e --- /dev/null +++ b/docs/issue-title-authoring-pilot.md @@ -0,0 +1,68 @@ +# Issue-title authoring pilot + +This checkpoint connects Aether's existing authoring skill and read-only issue +creator to the organization's candidate contract and Egolint consumer. It +implements [Aether #98](https://github.com/egohygiene/aether/issues/98), a bounded +part of [the default-bundle roadmap](https://github.com/egohygiene/aether/issues/67). + +## Discovery and ownership + +- Aether's root [AGENTS.md](../AGENTS.md) explicitly selects observe mode and + points to the [authoring skill](../library/organization/skills/authoring/github-issue-authoring/SKILL.md). +- The skill's [guide](../library/organization/skills/authoring/github-issue-authoring/references/canonical-issue-titles.md) + resolves one digest-locked source selection. The cache carries unchanged + upstream bytes, not a separately maintained type or emoji mapping. +- [The managed module](../library/organization/projections/templates/issue-authoring.AGENTS.md) + is emitted into repository discovery fixtures and the issue-creator agent's + provider projections. It uses the existing managed-block apply/remove helpers + to preserve consumer prose and support repeat application and rollback. +- Consumers must explicitly select the policy, install the portable skill, and + reconcile only the marked block into their local instruction file. Generated + fixtures are examples, not evidence of installation, adoption, or hooks. +- Egolint owns title formatting and conformance; Aether verifies source/report + provenance and guides authoring. The issue creator retains `read`, `search`, + and `web` only. An authorized executor supplies executable validation evidence. + +No new architecture decision is introduced: this follows the existing canonical +source, portable skill, managed module, and provider projection boundaries. + +## Reproduce the bounded check + +With Aether's existing Python dependencies installed, regenerate artifacts: + + python3 aether distribution build --output-directory dist + python3 aether catalog generate + +Build or obtain Egolint from the full consumer revision declared in +[selection.v1.json](../library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json), +then use the real executable for the optional integration tests: + + AETHER_EGOLINT_BINARY=/absolute/path/to/egolint python3 -m unittest discover -s tests -p test_issue_title_authoring.py -v + +The test stages a fresh temporary consumer with local `AGENTS.md` and the portable +skill, formats a reviewed checkpoint subject, checks report provenance, and +validates the proposed title/label snapshot. It also runs the upstream cases, +rejects incomplete snapshots and changed labels, and verifies that inputs remain +unchanged. Source integrity and managed-block tests run without Egolint; the +three real-consumer tests explicitly skip when the environment variable is +absent. A configured but missing or incompatible executable fails the check. + +These are synthetic local results. They do not prove live provider label +availability, a particular host's automatic instruction loading, or a GitHub +write. A proposal passing locally still needs current provider evidence before +an authorized create/update. + +## Remaining work + +The selected contract and Egolint consumer remain candidates until their owning +reviews complete ([contract PR #45](https://github.com/egohygiene/.github/pull/45), +[Egolint PR #79](https://github.com/egohygiene/egolint/pull/79)). Upgrade pins and +authority through an explicit reviewed change after verifying the accepted +sources. Contract acceptance, local adoption, validation, and enforcement are +separate facts. + +Reusable execution/provider mutation, label provisioning, real-repository +adoption, and the fleet title sweep remain separate checkpoints under +[the organization roadmap](https://github.com/egohygiene/.github/issues/24). +This change does not complete the broader default bundle or install it across +repositories. diff --git a/library/organization/agents/catalog.json b/library/organization/agents/catalog.json index a3d538a..6ed818c 100644 --- a/library/organization/agents/catalog.json +++ b/library/organization/agents/catalog.json @@ -71,7 +71,7 @@ "name": "GitHub Issue Creator", "domain": "authoring", "status": "draft", - "version": "1.1.0", + "version": "1.2.0", "skills": ["github-issue-authoring", "maintain-repository-continuity"], "specs": ["specfile", "repository-continuity"], "tools": ["read", "search", "web"], diff --git a/library/organization/agents/github-issue-creator/AGENT.md b/library/organization/agents/github-issue-creator/AGENT.md index 74e0ca7..a48b38d 100644 --- a/library/organization/agents/github-issue-creator/AGENT.md +++ b/library/organization/agents/github-issue-creator/AGENT.md @@ -7,13 +7,13 @@ tools: - search - web metadata: - aether-version: "1.1.0" + aether-version: "1.2.0" aether-status: "draft" aether-scope: "organization" aether-domain: "authoring" aether-owners: "egohygiene" aether-created: "2026-08-08" - aether-updated: "2026-09-08" + aether-updated: "2026-10-03" aether-skills: - github-issue-authoring - maintain-repository-continuity @@ -49,6 +49,9 @@ takes ownership of that handoff. 1. Extract the problem, motivation, desired state, constraints, and open questions. 2. Inspect repository architecture, relevant specifications, source, tests, automation, workflows, existing issues, and issue templates when available. 3. Choose one primary issue type and determine whether the request is one issue or a dependency-ordered roadmap. + When local instructions select the Ego Hygiene title contract, follow the + skill's canonical-title reference: resolve the immutable selection, preserve + the reviewed subject, and return the intended type label with provenance. 4. Define included scope, exclusions, ownership, integration boundaries, and observable completion. 5. Add evidence-backed implementation guidance without prescribing unsupported file paths or dependencies. 6. Define validation and acceptance criteria that another engineer or coding agent can execute. @@ -62,6 +65,9 @@ takes ownership of that handoff. - Prefer a reversible assumption for non-material ambiguity and record it. - Generate issues one at a time when the user requests staged authoring. - `edit` and `execute` are excluded; issue authoring is read, search, and web only. +- Request or consume formatter/validator evidence from an authorized executor; + do not run Egolint in this read-only role. Keep missing tools, stale pins, + conflicting classification, and unavailable provider labels explicit. ## Completion diff --git a/library/organization/projections/build-projections.py b/library/organization/projections/build-projections.py index c6f7396..cd2f32e 100644 --- a/library/organization/projections/build-projections.py +++ b/library/organization/projections/build-projections.py @@ -10,6 +10,7 @@ import argparse import hashlib +import importlib.util import json import re import sys @@ -56,6 +57,15 @@ DECISION_IMPACT_END = "" CONTINUITY_START = "" CONTINUITY_END = "" +ISSUE_AUTHORING_START = "" +ISSUE_AUTHORING_END = "" +ISSUE_AUTHORING_PATH = DECISION_IMPACT_PATH.with_name("issue-authoring.AGENTS.md") +ISSUE_AUTHORING_SKILL = ( + REPO_ROOT / "library/organization/skills/authoring/github-issue-authoring" +) +ISSUE_TITLE_SELECTION = ( + ISSUE_AUTHORING_SKILL / "references/issue-title-contract/selection.v1.json" +) _SKILL_LINK_RE = re.compile(r"(?:\.\./){2}skills/[^/]+/([^/]+)/SKILL\.md") _SPEC_LINK_RE = re.compile(r"(?:\.\./){2}specs/([^\s\)\"']+)") @@ -230,6 +240,51 @@ def _load_continuity_dispositions() -> dict[str, Any]: return inventory +def _load_issue_authoring() -> tuple[dict[str, Any], str]: + """Load the managed discovery module and verify its selected source bundle.""" + text = _normalized_text(ISSUE_AUTHORING_PATH).strip() + if text.count(ISSUE_AUTHORING_START) != 1 or text.count(ISSUE_AUTHORING_END) != 1: + raise ValueError("issue-authoring instruction requires exactly one managed marker pair") + if text.index(ISSUE_AUTHORING_START) > text.index(ISSUE_AUTHORING_END): + raise ValueError("issue-authoring instruction markers are out of order") + match = INSTRUCTION_METADATA_RE.search(text) + if match is None: + raise ValueError("issue-authoring instruction metadata is missing") + metadata = json.loads(match.group(1)) + expected = { + "id": "issue-authoring", + "version": "0.1.0", + "status": "draft", + "skill": "github-issue-authoring", + "selection": _repo_relative(ISSUE_TITLE_SELECTION), + "selection_sha256": _sha256_bytes(ISSUE_TITLE_SELECTION.read_bytes()), + } + if metadata != expected: + raise ValueError("issue-authoring instruction metadata does not match its selection") + spec = importlib.util.spec_from_file_location( + "aether_issue_title_sources", + ISSUE_AUTHORING_SKILL / "scripts/verify_title_contract.py", + ) + assert spec is not None and spec.loader is not None + verifier = importlib.util.module_from_spec(spec) + spec.loader.exec_module(verifier) + verifier.load_selection(ISSUE_TITLE_SELECTION) + return metadata, text + + +def _issue_authoring_provenance() -> dict[str, Any]: + """Bind the projected instructions to the same selection as the skill.""" + metadata, _module = _load_issue_authoring() + return { + **metadata, + "source": _repo_relative(ISSUE_AUTHORING_PATH), + "source_digest": { + "algorithm": "sha256-utf8-lf", + "value": _sha256_text(_normalized_text(ISSUE_AUTHORING_PATH)), + }, + } + + def _decision_impact_provenance() -> dict[str, Any]: """Return provenance for the shared decision-impact module.""" metadata, _module = _load_decision_impact() @@ -342,9 +397,27 @@ def _remove_repository_continuity(body: str) -> str: ) -def _apply_instruction_modules(body: str) -> str: - """Apply every canonical repository instruction module exactly once.""" - return _apply_repository_continuity(_apply_decision_impact(body)) +def _apply_issue_authoring(body: str) -> str: + """Reconcile only the managed issue-authoring discovery block.""" + _metadata, module = _load_issue_authoring() + return _apply_managed_module( + body, module, start_marker=ISSUE_AUTHORING_START, + end_marker=ISSUE_AUTHORING_END, label="issue-authoring", + ) + + +def _remove_issue_authoring(body: str) -> str: + """Remove the managed discovery block without removing consumer guidance.""" + return _remove_managed_module( + body, start_marker=ISSUE_AUTHORING_START, + end_marker=ISSUE_AUTHORING_END, label="issue-authoring", + ) + + +def _apply_instruction_modules(body: str, *, issue_authoring: bool = False) -> str: + """Apply shared modules and opt-in issue discovery exactly once.""" + result = _apply_repository_continuity(_apply_decision_impact(body)) + return _apply_issue_authoring(result) if issue_authoring else result def load_registry() -> dict[str, Any]: @@ -425,7 +498,7 @@ def _projection_provenance(provider: str, source: Path, source_text: str) -> dic "instruction_modules": [ _decision_impact_provenance(), _repository_continuity_provenance(), - ], + ] + ([_issue_authoring_provenance()] if agent_id == "github-issue-creator" else []), } @@ -472,6 +545,7 @@ def _repository_guidance_projection(provider: str, title: str) -> bytes: "instruction_modules": [ _decision_impact_provenance(), _repository_continuity_provenance(), + _issue_authoring_provenance(), ], "generator": GENERATOR_ID, } @@ -480,7 +554,7 @@ def _repository_guidance_projection(provider: str, title: str) -> bytes: "consumer-owned prose, repository commands, boundaries, and nested instruction " f"precedence. {host_notes[provider]}\n" ) - body = _apply_instruction_modules(body) + body = _apply_instruction_modules(body, issue_authoring=True) return ( f"# {title}\n\n" f"\n\n" @@ -607,7 +681,9 @@ def _build_files(registry: dict[str, Any]) -> dict[str, bytes]: for agent_id, source in find_agents(): source_text = _normalized_text(source) frontmatter, body = _parse_agent(agent_id, source) - projected_body = _apply_instruction_modules(body) + projected_body = _apply_instruction_modules( + body, issue_authoring=(agent_id == "github-issue-creator") + ) source_provenance = _projection_provenance("canonical", source, source_text) source_records[agent_id] = source_provenance diff --git a/library/organization/projections/templates/issue-authoring.AGENTS.md b/library/organization/projections/templates/issue-authoring.AGENTS.md new file mode 100644 index 0000000..32ad2d8 --- /dev/null +++ b/library/organization/projections/templates/issue-authoring.AGENTS.md @@ -0,0 +1,22 @@ + + +## Issue authoring discovery + +When scoped repository instructions explicitly select the Ego Hygiene issue-title +policy, read `.agents/skills/github-issue-authoring/SKILL.md` from the consumer +root and its `references/canonical-issue-titles.md`. If installed elsewhere, +resolve the declared local skill path. If absent, report discovery unavailable; +this block alone does not install the skill or adopt the policy. + +Follow `references/issue-title-contract/selection.v1.json` inside that skill for +the immutable contract, source digests, and candidate authority. Preserve the +reviewed subject and IDs; obtain title and intended label from that selection. +An already authorized executor may use Egolint and the skill's source verifier. +Read-only roles prepare drafts or consume executor evidence without gaining +execution permission. Missing sources/tools, stale pins, ambiguous type labels, +and unavailable current provider labels remain explicit gaps. + +This is static discovery guidance, not a hook or fleet migration. Organization +instructions do not automatically inherit into consumer repositories. Preserve +consumer-owned prose, nested instruction precedence, and role permissions. + diff --git a/library/organization/skills/authoring/github-issue-authoring/SKILL.md b/library/organization/skills/authoring/github-issue-authoring/SKILL.md index 05d2996..7f60951 100644 --- a/library/organization/skills/authoring/github-issue-authoring/SKILL.md +++ b/library/organization/skills/authoring/github-issue-authoring/SKILL.md @@ -3,13 +3,15 @@ name: github-issue-authoring description: Converts evidence, specifications, audits, bug reports, and rough notes into scoped, copy-ready GitHub issues or dependency-aware issue batches. Use when defining implementation work clearly without performing the implementation itself. license: MIT metadata: - aether-version: "1.1.0" + aether-version: "1.2.0" aether-status: "experimental" aether-scope: "organization" aether-domain: "authoring" aether-owners: "egohygiene" aether-created: "2026-08-08" - aether-updated: "2026-09-08" + aether-updated: "2026-10-03" + aether-executable-resources: + - "scripts/verify_title_contract.py" --- # GitHub Issue Authoring @@ -47,7 +49,10 @@ When repository evidence is incomplete, label assumptions explicitly. 1. extract the durable problem, goal, boundaries, and observable outcome 2. inspect repository evidence before prescribing paths, commands, or ownership -3. choose whether the result should be one issue or an ordered batch of issues +3. choose whether the result should be one issue or an ordered batch of issues; + if scoped instructions select the Ego Hygiene issue-title policy, follow + [canonical issue titles](references/canonical-issue-titles.md) before drafting + the title or proposing labels 4. write copy-ready issue content using the focused resources: - `./references/copy-ready-checklist.md` @@ -55,7 +60,9 @@ When repository evidence is incomplete, label assumptions explicitly. 5. make acceptance criteria observable and keep non-goals explicit 6. preserve the user's copy-and-paste formatting preference; when fenced blocks would break copying or rendering, use four-space-indented inner code examples instead -7. validate title clarity, internal consistency, dependency order, and execution readiness +7. validate title clarity, internal consistency, dependency order, and execution readiness; + report contract provenance, proposed versus actual validation, and provider + label availability separately when the title policy applies ## Constraints @@ -64,6 +71,8 @@ When repository evidence is incomplete, label assumptions explicitly. - Do not mix multiple independently shippable outcomes into one issue without stating why. - Do not hide unresolved decisions or missing evidence. - Do not embed provider-specific tooling instructions into the core workflow. +- Do not expand a read-only role's tools to run a formatter or validator. Use an + already authorized executor or mark local validation unavailable. ## Completion Criteria @@ -72,6 +81,8 @@ When repository evidence is incomplete, label assumptions explicitly. - [ ] Validation steps are observable and repository-aware. - [ ] Copy-ready formatting is preserved. - [ ] Missing evidence and open questions remain visible. +- [ ] Selected title policy is discovered explicitly; missing sources, tools, + classification, or live label evidence are not reported as conformance. ## Provenance diff --git a/library/organization/skills/authoring/github-issue-authoring/evals/evals.json b/library/organization/skills/authoring/github-issue-authoring/evals/evals.json index 7d2c372..2997c20 100644 --- a/library/organization/skills/authoring/github-issue-authoring/evals/evals.json +++ b/library/organization/skills/authoring/github-issue-authoring/evals/evals.json @@ -1,7 +1,7 @@ { "schema": "aether.skill-evaluations/v2", "skill": "github-issue-authoring", - "version": "1.1.0", + "version": "1.2.0", "cases": [ { "id": "spec-to-issue", @@ -64,6 +64,61 @@ "prohibited": [ "Replacing an explicit implementation request with issue text only" ] + }, + { + "id": "local-title-discovery", + "description": "A fresh session receives local AGENTS guidance selecting the policy and a reviewed checkpoint subject.", + "category": "positive", + "trigger": "should-trigger", + "expected": [ + "Follows the local skill pointer to the immutable selection and selected source artifacts", + "Preserves the reviewed subject and checkpoint identifiers", + "Composes Egolint only through an already authorized executor", + "Reports proposed validation and provider label availability separately" + ] + }, + { + "id": "unavailable-title-evidence", + "description": "The installed skill, source artifact, tool, execution permission, or complete provider label evidence is unavailable.", + "category": "insufficient-evidence", + "trigger": "should-trigger", + "expected": [ + "Returns a useful draft with the exact unavailable evidence stated", + "Does not invent a passing validator result or silently fall back to a moving source", + "Does not treat unknown provider labels as an empty complete set" + ] + }, + { + "id": "conflicting-title-labels", + "description": "An existing issue has absent, multiple, or unsupported type labels.", + "category": "boundary", + "trigger": "should-trigger", + "expected": [ + "Reports needs-classification, conflict, or unsupported-type using the selected semantics", + "Does not infer classification from the current emoji or silently change labels" + ] + }, + { + "id": "stale-title-evidence", + "description": "The available Egolint report uses a different source pin or the provider title/labels changed after inspection.", + "category": "boundary", + "trigger": "should-trigger", + "expected": [ + "Rejects mismatched report provenance", + "Refreshes provider evidence and revalidates before proposing a current write", + "Does not equate source acceptance, repository adoption, and validation" + ] + }, + { + "id": "reviewed-title-migration", + "description": "An existing title contains an unfamiliar prefix and an accepted checkpoint identifier.", + "category": "positive", + "trigger": "should-trigger", + "expected": [ + "Preserves wording and identifiers until an explicit reviewed subject is available", + "Uses the same reviewed subject for repeat formatting without duplicating the prefix", + "Does not reconstruct parent/child relationships by parsing the title" + ] } ] } diff --git a/library/organization/skills/authoring/github-issue-authoring/references/canonical-issue-titles.md b/library/organization/skills/authoring/github-issue-authoring/references/canonical-issue-titles.md new file mode 100644 index 0000000..531e6f2 --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/references/canonical-issue-titles.md @@ -0,0 +1,83 @@ +# Canonical issue titles + +Use this workflow only when scoped repository instructions explicitly select the +Ego Hygiene title policy. Other repositories retain their own conventions. An +organization's `AGENTS.md` does not automatically govern another repository: a +local pointer, installed instruction module, or explicit read step is required. + +## Discover the selected contract + +1. Read local `AGENTS.md` and any more specific instructions. Resolve this skill + from its declared installation; the generated module suggests + `.agents/skills/github-issue-authoring/SKILL.md` relative to the consumer root. +2. Read [selection.v1.json](issue-title-contract/selection.v1.json). It is the + single selection of contract ID, version, full commit, authority, and five + SHA-256 source digests, plus the compatible Egolint candidate revision. +3. Read the selected [contract](issue-title-contract/title-contract.v1.json), + [catalog](issue-title-contract/label-catalog.v1.json), and + [semantics snapshot](issue-title-contract/semantics.txt) through the manifest's + source-to-cache mapping. Do not maintain a second emoji/type table. +4. An authorized executor can run `python3 scripts/verify_title_contract.py` + from the skill directory. A read-only role instead inspects these sources and + requests or consumes executor evidence; it must not claim a digest check ran. + +The five cached payloads are unchanged upstream bytes. Upstream-relative links +inside them refer to the selected upstream tree, not this flattened cache. +`semantics.txt` retains the Markdown source bytes as a raw snapshot; this guide +provides consumer-local navigation. Refresh all five artifacts and their digests +as one reviewed change when changing the pin. + +This selection is **candidate**, with **observe** as its adoption mode. Successful +validation does not accept the contract, enroll a repository, provision labels, +or authorize enforcement. Merging a dependency does not silently change this +selection's authority. The consumer commit identifies source to build; a binary's +reported contract alone does not prove which source produced that binary. + +## Author or revise one issue + +- Resolve one primary type from repository evidence and the selected contract. + For an existing issue, read the complete label set before classifying it. + Missing type labels need classification; multiple recognized types conflict; + unknown `type:*` labels are unsupported. Do not infer a type from a title emoji. +- Review the exact subject before formatting. Preserve case, wording, checkpoint + IDs, issue relationships, and unknown legacy prefixes until explicitly reviewed. + The formatter accepts a reviewed subject; it is not an existing-title parser. +- With execution already authorized and the compatible Egolint available: + + egolint issue-title format --type feature --reviewed-subject '[FLO-OBS-01] Checkpoint 2: Export reports' > proposal.json + python3 scripts/verify_title_contract.py --report proposal.json + + Read `title` and `required_label` from the proposal. A read-only agent hands + these commands and inputs to an authorized executor, or prepares an explicitly + unvalidated proposal from the selected source. Never expand its tool allowlist. +- For a proposed issue, build a local snapshot with `schema_version: 1`, + `complete: true`, the proposed `title`, and its complete proposed `labels` array. + For an existing issue, use the actual, freshly fetched title and full labels. + Keep actual and proposed snapshots separate. Incomplete provider evidence must + use `complete: false`; do not turn unknown labels into an empty verified set. + + egolint issue-title validate --input issue.json > report.json + python3 scripts/verify_title_contract.py --report report.json + + Inspect both commands' exit codes and the report's `status`. Source verification + alone is not title validation. Egolint returns 0 for conformant, 1 for a title or + classification finding, and 2 for invalid/unavailable input or configuration. +- Separately verify that the required label exists in the target repository and + that provider evidence is current before any authorized create/update. Proposed + label conformance does not prove provider label availability. After a delay or + intervening change, refresh the title/labels and revalidate; never replace the + current full label set using an older snapshot. Missing labels need a separate + provisioning task, not automatic creation in this workflow. +- Return title, intended label, selected contract provenance, actual/proposed + validation status, provider label availability, and unresolved evidence with + the issue draft. Only claim a provider write after a successful live receipt. + +## Unavailable and conflicting evidence + +Missing installed skill, source cache, digest mismatch, mutable pin, wrong report +provenance, missing Egolint, or denied execution means validation is unavailable. +Keep a useful draft and report the exact gap; do not substitute a moving `main` +reference, silently repair labels, or call an unvalidated draft conformant. +No script in this package changes GitHub. These instructions do not install a +hook, perform fleet migration, or guarantee future titles without adoption and +the separate provider execution/enforcement work. diff --git a/library/organization/skills/authoring/github-issue-authoring/references/copy-ready-checklist.md b/library/organization/skills/authoring/github-issue-authoring/references/copy-ready-checklist.md index b18326d..7035b11 100644 --- a/library/organization/skills/authoring/github-issue-authoring/references/copy-ready-checklist.md +++ b/library/organization/skills/authoring/github-issue-authoring/references/copy-ready-checklist.md @@ -3,6 +3,10 @@ Use this checklist after drafting the issue. - [ ] Title names the problem or outcome precisely. +- [ ] If local instructions select the title contract, the title and one intended + type label come from its pinned source; reviewed wording and IDs are preserved. +- [ ] Contract source verification, title validation, and current provider label + availability are reported separately; unavailable evidence remains explicit. - [ ] Problem, goal, and current-state evidence are distinct. - [ ] Scope and non-goals prevent accidental expansion. - [ ] Acceptance criteria are observable and implementation-scoped. diff --git a/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/cases.v1.json b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/cases.v1.json new file mode 100644 index 0000000..e8b2082 --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/cases.v1.json @@ -0,0 +1,283 @@ +{ + "schema_version": 1, + "contract_id": "egohygiene.issue-title/v1", + "contract_version": "1.0.0", + "cases": [ + { + "id": "valid-architecture", + "input": { + "title": "πŸ—οΈ [architecture] Document example 1", + "labels": [ + "type:architecture", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "architecture" + } + }, + { + "id": "valid-feature", + "input": { + "title": "✨ [feature] Document example 2", + "labels": [ + "type:feature", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "feature" + } + }, + { + "id": "valid-bug", + "input": { + "title": "πŸ› [bug] Document example 3", + "labels": [ + "type:bug", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "bug" + } + }, + { + "id": "valid-documentation", + "input": { + "title": "πŸ“„ [documentation] Document example 4", + "labels": [ + "type:documentation", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "documentation" + } + }, + { + "id": "valid-research", + "input": { + "title": "πŸ”¬ [research] Document example 5", + "labels": [ + "type:research", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "research" + } + }, + { + "id": "valid-maintenance", + "input": { + "title": "🧹 [maintenance] Document example 6", + "labels": [ + "type:maintenance", + "priority:p2" + ] + }, + "expected": { + "status": "conformant", + "type": "maintenance" + } + }, + { + "id": "checkpoint-id-preserved", + "input": { + "title": "✨ [feature] [FLO-OBS-01] Checkpoint 2: Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "conformant", + "type": "feature" + } + }, + { + "id": "prefix-mismatch", + "input": { + "title": "πŸ› [bug] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "missing-type", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "area:automation" + ] + }, + "expected": { + "status": "needs-classification", + "type": null + } + }, + { + "id": "multiple-types", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "type:feature", + "type:bug" + ] + }, + "expected": { + "status": "conflict", + "type": null + } + }, + { + "id": "unknown-type", + "input": { + "title": "πŸš€ [delivery] Export reports", + "labels": [ + "type:delivery" + ] + }, + "expected": { + "status": "unsupported-type", + "type": null + } + }, + { + "id": "known-and-unknown-type", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "type:feature", + "type:delivery" + ] + }, + "expected": { + "status": "unsupported-type", + "type": null + } + }, + { + "id": "empty-subject", + "input": { + "title": "✨ [feature] ", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "unformatted-title", + "input": { + "title": "[FLO-OBS-01] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "wrong-emoji", + "input": { + "title": "πŸ› [feature] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "multiline-subject", + "input": { + "title": "✨ [feature] Export\nreports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "double-separator", + "input": { + "title": "✨ [feature] Export reports", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + }, + { + "id": "trailing-space", + "input": { + "title": "✨ [feature] Export reports ", + "labels": [ + "type:feature" + ] + }, + "expected": { + "status": "nonconformant", + "type": "feature" + } + } + ], + "migrations": [ + { + "id": "reviewed-legacy-title", + "before": { + "title": "🧭 [Release checkpoint 7] Provide pinned tools", + "labels": [ + "type:maintenance" + ] + }, + "reviewed_subject": "[Release checkpoint 7] Provide pinned tools", + "after": { + "title": "🧹 [maintenance] [Release checkpoint 7] Provide pinned tools", + "labels": [ + "type:maintenance" + ] + } + }, + { + "id": "already-conformant-no-op", + "before": { + "title": "✨ [feature] [FLO-OBS-01] Export reports", + "labels": [ + "type:feature" + ] + }, + "reviewed_subject": "[FLO-OBS-01] Export reports", + "after": { + "title": "✨ [feature] [FLO-OBS-01] Export reports", + "labels": [ + "type:feature" + ] + } + } + ] +} diff --git a/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json new file mode 100644 index 0000000..cebe298 --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/label-catalog.v1.json @@ -0,0 +1,183 @@ +{ + "$schema": "./schema/catalog.v1.schema.json", + "schema_version": 1, + "catalog_version": "1.0.0", + "owner": "egohygiene/.github", + "defaults": { + "required": true, + "removal_policy": "retain" + }, + "universal": [ + { + "name": "priority:p0", + "description": "Correctness, security, or data-integrity blocker.", + "color": "b60205", + "category": "priority" + }, + { + "name": "priority:p1", + "description": "Required foundation or near-term release work.", + "color": "d93f0b", + "category": "priority" + }, + { + "name": "priority:p2", + "description": "Important planned capability.", + "color": "fbca04", + "category": "priority" + }, + { + "name": "priority:p3", + "description": "Research, exploration, or later direction.", + "color": "c5def5", + "category": "priority" + }, + { + "name": "type:architecture", + "description": "Architecture, boundaries, or system-design work.", + "color": "1f4d8f", + "category": "type" + }, + { + "name": "type:feature", + "description": "New or expanded user-visible capability.", + "color": "0e8a16", + "category": "type" + }, + { + "name": "type:bug", + "description": "Incorrect or unexpected behavior.", + "color": "dc2626", + "category": "type" + }, + { + "name": "type:documentation", + "description": "Documentation or knowledge-organization work.", + "color": "64748b", + "category": "type" + }, + { + "name": "type:research", + "description": "Research, discovery, or evidence gathering.", + "color": "0f766e", + "category": "type" + }, + { + "name": "type:maintenance", + "description": "Maintenance, cleanup, or dependency work.", + "color": "6b7280", + "category": "type" + }, + { + "name": "area:automation", + "description": "Automation, continuous integration, and delivery.", + "color": "0369a1", + "category": "area" + }, + { + "name": "area:developer-experience", + "description": "Developer workflows, tooling, and ergonomics.", + "color": "7c5c1e", + "category": "area" + }, + { + "name": "area:governance", + "description": "Organization policy, contracts, and coordination.", + "color": "6d28d9", + "category": "area" + }, + { + "name": "area:security", + "description": "Security, privacy, provenance, and trust boundaries.", + "color": "b91c1c", + "category": "area" + }, + { + "name": "cross-repo", + "description": "Work coordinated across multiple repositories.", + "color": "5319e7", + "category": "coordination" + }, + { + "name": "needs-routing", + "description": "Work that does not yet have a durable repository owner.", + "color": "d4c5f9", + "category": "coordination" + }, + { + "name": "blocked", + "description": "Work waiting on an explicit dependency or decision.", + "color": "b60205", + "category": "status" + }, + { + "name": "ready", + "description": "Work with sufficient scope, ownership, and acceptance criteria.", + "color": "0e8a16", + "category": "status" + } + ], + "overlays": [ + { + "id": "ecosystem", + "description": "Named Ego Hygiene ecosystem capabilities.", + "labels": [ + {"name": "☁️ aether", "description": "Local AI, agents, prompts, skills, and workflows ☁️", "color": "4338ca"}, + {"name": "🌱 mindgarden", "description": "Knowledge management, digital garden, and long-term thinking 🌱", "color": "3a7d44"}, + {"name": "🎬 dreamscape", "description": "Creative operating system and AI orchestration 🎬", "color": "6d28d9"} + ] + }, + { + "id": "engineering", + "description": "Engineering changes, platforms, packages, and specifications.", + "labels": [ + {"name": "♻️ refactor", "description": "Repository cleanup, restructuring, modernization, and technical debt ♻️", "color": "16a34a"}, + {"name": "βš™οΈ infra", "description": "Infrastructure, repositories, tooling, and configuration βš™οΈ", "color": "274472"}, + {"name": "⚑ dx", "description": "Developer experience, tooling, and ergonomics ⚑", "color": "7c5c1e"}, + {"name": "πŸ—οΈ architecture", "description": "Architecture, foundations, and system design πŸ—οΈ", "color": "1f4d8f"}, + {"name": "πŸ› bug", "description": "Something isn't working πŸ›", "color": "dc2626"}, + {"name": "πŸ“„ documentation", "description": "Documentation improvements and knowledge organization πŸ“„", "color": "64748b"}, + {"name": "πŸ“‹ backlog", "description": "Captured ideas and future work πŸ“‹", "color": "4b5563"}, + {"name": "πŸ“ schema", "description": "Schemas, specifications, contracts, and data models πŸ“", "color": "7c3aed"}, + {"name": "πŸ“¦ package", "description": "Reusable packages and shared libraries πŸ“¦", "color": "0369a1"}, + {"name": "πŸ“± flutter", "description": "Flutter application and Flutter Foundation πŸ“±", "color": "0175c2"}, + {"name": "πŸ”„ lifecycle", "description": "Application lifecycles, managers, and orchestration πŸ”„", "color": "0f766e"}, + {"name": "πŸ”Œ plugin", "description": "Plugin architecture, registries, and extensibility πŸ”Œ", "color": "2563eb"} + ] + }, + { + "id": "creative-publishing", + "description": "Creative production, publishing, design, and outreach.", + "labels": [ + {"name": "✍️ writing", "description": "Writing workflow, editorial improvements, and authoring ✍️", "color": "7c3aed"}, + {"name": "🌐 website", "description": "Website, documentation site, and public experience 🌐", "color": "0284c7"}, + {"name": "πŸŽ₯ video", "description": "Video production, animation, and cinematic content πŸŽ₯", "color": "b91c1c"}, + {"name": "🎨 design", "description": "Design system, UI, UX, branding, and visuals 🎨", "color": "4e8065"}, + {"name": "🎡 music", "description": "Music, albums, lyrics, and audio production 🎡", "color": "7f1d1d"}, + {"name": "πŸ“– magazine", "description": "Magazine, comic, editorial, and visual storytelling πŸ“–", "color": "9333ea"}, + {"name": "πŸ“š article", "description": "Long-form articles and essays πŸ“š", "color": "2563eb"}, + {"name": "πŸ“ publishing", "description": "Publishing platform, workflows, and distribution πŸ“", "color": "0e7490"}, + {"name": "πŸ“£ marketing", "description": "Marketing, social media, SEO, and outreach πŸ“£", "color": "15803d"} + ] + }, + { + "id": "human-practice", + "description": "Human domains, practices, philosophy, and journeys.", + "labels": [ + {"name": "🌍 domain", "description": "Life domains, domain models, and domain-specific functionality 🌍", "color": "15803d"}, + {"name": "🎯 practice", "description": "Practices, rituals, habits, and exercises 🎯", "color": "15803d"}, + {"name": "πŸ›οΈ philosophy", "description": "Purpose, principles, ontology, and conceptual evolution πŸ›οΈ", "color": "6d28d9"}, + {"name": "πŸ—ΊοΈ journey", "description": "Journey modeling, progress, and timeline systems πŸ—ΊοΈ", "color": "0891b2"} + ] + }, + { + "id": "research-funding", + "description": "Research and the resources that support it.", + "labels": [ + {"name": "πŸ”¬ research", "description": "Research, literature, evidence, and references πŸ”¬", "color": "0f766e"}, + {"name": "πŸ’° funding", "description": "Grants, sponsorships, partnerships, and monetization πŸ’°", "color": "b45309"} + ] + } + ], + "deprecations": [] +} diff --git a/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json new file mode 100644 index 0000000..8b321f4 --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/selection.v1.json @@ -0,0 +1,30 @@ +{ + "schema_version": 1, + "contract": { + "contract_id": "egohygiene.issue-title/v1", + "contract_version": "1.0.0", + "repository": "egohygiene/.github", + "revision": "19d2be9bf0191710508cefbb9f0b1abb3a40d9be", + "authority": "candidate", + "source_digests": { + ".github/issues/title-contract.v1.json": "205202bda3c3b069a105924499ba7213a6a99645658e500cc910dfa085eeb30e", + ".github/issues/schema/title-contract.v1.schema.json": "9df220330ad07758122567620f557aa11aad85522914fe3f4cdc7de6a5ca7295", + ".github/labels/catalog.v1.json": "7063a61608a66454310e5a7746b1514d1d11018da08427bfb49f4612326ff6ff", + "docs/issue-titles.md": "243b5fdfec3fff82623fdcb43fa490829af9380d5f0b1d568f9f32b277de0660", + "fixtures/issue-titles/cases.v1.json": "c21989c506e363c7b7845e0f63bdaf14f2e0de78f9d6c421d04e0f5a444edab2" + } + }, + "adoption": "explicit-repository-selection-observe", + "consumer": { + "repository": "egohygiene/egolint", + "revision": "3a6785cd408e218bc7e3691e01dc659e4cf79de8", + "authority": "candidate" + }, + "files": { + ".github/issues/title-contract.v1.json": "title-contract.v1.json", + ".github/issues/schema/title-contract.v1.schema.json": "title-contract.v1.schema.json", + ".github/labels/catalog.v1.json": "label-catalog.v1.json", + "docs/issue-titles.md": "semantics.txt", + "fixtures/issue-titles/cases.v1.json": "cases.v1.json" + } +} diff --git a/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/semantics.txt b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/semantics.txt new file mode 100644 index 0000000..2ed0cdd --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/semantics.txt @@ -0,0 +1,136 @@ +# Canonical issue titles + +Contract: `egohygiene.issue-title/v1`, version `1.0.0`. +Owner: `egohygiene/.github`. +Tracking: [contract checkpoint #44](https://github.com/egohygiene/.github/issues/44), +[parent #24](https://github.com/egohygiene/.github/issues/24), and +[fleet reconciliation #23](https://github.com/egohygiene/.github/issues/23). + +The [machine-readable contract](../.github/issues/title-contract.v1.json) +owns the type-to-emoji mapping and rule identifiers. This document owns their +meaning. The [label catalog](../.github/labels/catalog.v1.json) continues to +own exact label names, descriptions, colors, and category membership. +Changes to these sources must be reviewed together when their meanings change. + +## Format and primary type + +An issue title is `{emoji} [{type}] {subject}`. Use exactly one ASCII space at +each prefix boundary, the contract's exact emoji codepoints, and the lowercase +type token. The subject is nonempty, has no leading/trailing whitespace, and +contains no line breaks. Prefer a short, descriptive action for new work. +Do not automatically rewrite case or prose during migration. + +Choose exactly one primary universal `type:*` label. Other labels remain +independent. Expressive overlay labels such as `πŸ› bug` do not substitute for +`type:bug`. GitHub's native issue-type field is also a separate provider +field; it does not override this contract's primary label. + +The six mappings are in the contract. Illustrative titles include: + +- `πŸ› [bug] Fix duplicate archive ingestion` +- `✨ [feature] Add repository achievement tracking` +- `πŸ“„ [documentation] Explain local setup` +- `🧹 [maintenance] [Release checkpoint 7] Provide pinned tools` + +Keep tracking IDs and meaningful checkpoint/roadmap text within the subject. +Parentage, dependencies, and completion are determined by stable issue identity +and relationships, never by parsing this prefix. Issue titles do not change +commit messages, pull-request titles, or release semantics. + +## Classification and validation order + +For a complete observed issue snapshot, evaluate in this order: + +1. If any label beginning `type:` is absent from the pinned mapping, report + `unsupported-type`. +2. If no known primary type is present, report `needs-classification`. +3. If multiple distinct known primary types are present, report `conflict`. +4. With exactly one known type, compare the title against its required prefix + and the subject rules. Report `conformant` or `nonconformant`. + +Treat labels as a set. Never infer the primary type from title decoration, +choose the first label, or silently fall back to maintenance. Missing source +access, unavailable labels, and incomplete snapshots are capability/evidence +limitations, not an empty label set or a passing result. + +A maintainer or agent can propose a classification from issue content. +Record that decision in the reviewed migration plan. Formatting is then +deterministic; semantic classification is not part of the formatter. + +## Existing-issue normalization + +A formatter takes an explicit primary type and a reviewed subject and produces +the canonical title. Before applying to an existing issue, capture its stable +identity, original title and labels, observed revision/update evidence, +contract revision/digest, selected type, reviewed subject, and expected output. + +A normalizer may recognize one complete, already-canonical prefix and retain +the subject byte-for-byte. For legacy, duplicated, malformed, or unknown +prefixes, require an explicit reviewed subject. Do not strip arbitrary emoji, +bracketed tracking IDs, or checkpoint markers. Changing wording beyond the +prefix must be an explicit item in the plan. + +Retain unrelated labels and all issue bodies, discussion, assignments, +milestones, state, and relationships. Recheck the current title and labels +before each write; changed inputs become conflicts rather than overwrites. +Record successful updates for resume. Rollback restores the recorded title and +any managed type-label changes only when current state still matches the +applied result. + +A repeat against conformant state produces zero mutations. The +[synthetic cases](../fixtures/issue-titles/cases.v1.json) describe expected +validation statuses and reviewed migration examples for downstream consumers. + +## Agent entry point and adoption + +[AGENTS.md](../AGENTS.md) is the local discovery entry point. Before authorized +issue creation or editing, an agent reads this document and the mapping, +selects a primary type, verifies provider label availability, and prepares a +conforming title. Unavailable provider access must be reported rather than +invented. Instructions do not grant write, merge, or publication authority. + +This source change establishes a contract candidate for review. Once merged, +it establishes the approved definition; it does not prove provider enforcement +or silently enroll other repositories. + +Consumers pin the contract, semantics, catalog, schema, and examples from one +immutable source revision and record that revision in their adoption evidence. +The catalog version must match the contract reference. An unavailable or +incompatible contract is reported as unavailable; use no silent fallback to +mutable main or an older cached policy. + +Initial adoption is observe mode. A repository moves to enforcement only after +its primary labels, templates, local agent discovery, validator, and execution +path are present and evidenced. GitHub does not distribute this repository's +AGENTS.md to every agent: consumer repositories need their own explicit pointer +or managed Aether projection. Local template overrides need explicit updates. + +## Ownership and staged delivery + +| Owner | Responsibility | +| --- | --- | +| .github | Organization title convention, label taxonomy, examples, adoption coordination | +| Hygiene | Organization applicability and contract-index reference | +| Aether | Portable issue-authoring guidance and managed agent projections | +| Egolint | Reusable issue-title validation and deterministic formatting | +| Relay | GitHub preview/apply, event handling, receipts, and recovery | +| Pace / Observatory | Adoption and drift evidence through existing boundaries | + +Checkpoint #44 supplies the contract, examples, local entry point, and +structural checks. Consumer implementation and contract-index registration are +follow-on work. The organization work form still has its existing default +until template consumption is implemented; no automatic issue renaming or +ongoing event workflow is installed by this change. + +After contract review, prove the formatter/validator and agent consumption, +then a single-repository Relay preview/apply and no-op repeat. Add ongoing +event/template conformance and expand the backlog sweep through #23. +Keep historical issues in a separate recorded batch. + +## Contract changes + +Editorial clarification preserving accepted outputs is a patch. +Additive optional examples or metadata may be minor. A changed mapping, +classification meaning, required format, or acceptance behavior requires a +major version and migration plan. A consumer upgrade must record the selected +revision; editing this source does not retroactively mutate issues. diff --git a/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.json b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.json new file mode 100644 index 0000000..75e3492 --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.json @@ -0,0 +1,67 @@ +{ + "$schema": "./schema/title-contract.v1.schema.json", + "schema_version": 1, + "contract_id": "egohygiene.issue-title/v1", + "contract_version": "1.0.0", + "owner": "egohygiene/.github", + "label_catalog": { + "path": "../labels/catalog.v1.json", + "version": "1.0.0" + }, + "format": "{emoji} [{type}] {subject}", + "types": [ + { + "type": "architecture", + "emoji": "πŸ—οΈ", + "label": "type:architecture" + }, + { + "type": "feature", + "emoji": "✨", + "label": "type:feature" + }, + { + "type": "bug", + "emoji": "πŸ›", + "label": "type:bug" + }, + { + "type": "documentation", + "emoji": "πŸ“„", + "label": "type:documentation" + }, + { + "type": "research", + "emoji": "πŸ”¬", + "label": "type:research" + }, + { + "type": "maintenance", + "emoji": "🧹", + "label": "type:maintenance" + } + ], + "rules": { + "primary_type": "exactly-one-known-type-label", + "missing_type": "needs-classification", + "multiple_types": "conflict", + "unknown_type": "unsupported-type", + "subject": "nonempty-trimmed-single-line", + "separators": "single-ascii-space", + "emoji_matching": "exact-codepoints", + "subject_case": "preserve", + "existing_subject": "preserve-wording-and-identifiers", + "migration": "reviewed-explicit-subject", + "normalization": "idempotent", + "unknown_prefix": "preserve-until-reviewed", + "relationships": "native-identifiers-not-title-parsing" + }, + "adoption": { + "scope": "explicit-repository-adoption", + "default_mode": "observe", + "reference": "immutable-commit", + "unavailable_contract": "report-unavailable" + }, + "semantics": "../../docs/issue-titles.md", + "conformance_examples": "../../fixtures/issue-titles/cases.v1.json" +} diff --git a/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json new file mode 100644 index 0000000..71c86e8 --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/references/issue-title-contract/title-contract.v1.schema.json @@ -0,0 +1,177 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/egohygiene/.github/blob/main/.github/issues/schema/title-contract.v1.schema.json", + "title": "Ego Hygiene issue-title contract v1", + "type": "object", + "additionalProperties": false, + "required": [ + "$schema", + "schema_version", + "contract_id", + "contract_version", + "owner", + "label_catalog", + "format", + "types", + "rules", + "adoption", + "semantics", + "conformance_examples" + ], + "properties": { + "$schema": { + "const": "./schema/title-contract.v1.schema.json" + }, + "schema_version": { + "const": 1 + }, + "contract_id": { + "const": "egohygiene.issue-title/v1" + }, + "contract_version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + }, + "owner": { + "const": "egohygiene/.github" + }, + "label_catalog": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "version" + ], + "properties": { + "path": { + "const": "../labels/catalog.v1.json" + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" + } + } + }, + "format": { + "const": "{emoji} [{type}] {subject}" + }, + "types": { + "type": "array", + "minItems": 1, + "uniqueItems": true, + "items": { + "type": "object", + "additionalProperties": false, + "required": [ + "type", + "emoji", + "label" + ], + "properties": { + "type": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + }, + "emoji": { + "type": "string", + "minLength": 1 + }, + "label": { + "type": "string", + "pattern": "^type:[a-z][a-z0-9-]*$" + } + } + } + }, + "rules": { + "type": "object", + "additionalProperties": false, + "required": [ + "primary_type", + "missing_type", + "multiple_types", + "unknown_type", + "subject", + "separators", + "emoji_matching", + "subject_case", + "existing_subject", + "migration", + "normalization", + "unknown_prefix", + "relationships" + ], + "properties": { + "primary_type": { + "const": "exactly-one-known-type-label" + }, + "missing_type": { + "const": "needs-classification" + }, + "multiple_types": { + "const": "conflict" + }, + "unknown_type": { + "const": "unsupported-type" + }, + "subject": { + "const": "nonempty-trimmed-single-line" + }, + "separators": { + "const": "single-ascii-space" + }, + "emoji_matching": { + "const": "exact-codepoints" + }, + "subject_case": { + "const": "preserve" + }, + "existing_subject": { + "const": "preserve-wording-and-identifiers" + }, + "migration": { + "const": "reviewed-explicit-subject" + }, + "normalization": { + "const": "idempotent" + }, + "unknown_prefix": { + "const": "preserve-until-reviewed" + }, + "relationships": { + "const": "native-identifiers-not-title-parsing" + } + } + }, + "adoption": { + "type": "object", + "additionalProperties": false, + "required": [ + "scope", + "default_mode", + "reference", + "unavailable_contract" + ], + "properties": { + "scope": { + "const": "explicit-repository-adoption" + }, + "default_mode": { + "const": "observe" + }, + "reference": { + "const": "immutable-commit" + }, + "unavailable_contract": { + "const": "report-unavailable" + } + } + }, + "semantics": { + "const": "../../docs/issue-titles.md" + }, + "conformance_examples": { + "const": "../../fixtures/issue-titles/cases.v1.json" + } + } +} diff --git a/library/organization/skills/authoring/github-issue-authoring/scripts/verify_title_contract.py b/library/organization/skills/authoring/github-issue-authoring/scripts/verify_title_contract.py new file mode 100644 index 0000000..91a95a6 --- /dev/null +++ b/library/organization/skills/authoring/github-issue-authoring/scripts/verify_title_contract.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +"""Verify a pinned source bundle and, optionally, Egolint report provenance. + +This offline check does not classify issues, format titles, or authorize writes. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path, PurePosixPath +import re + +DEFAULT_SELECTION = ( + Path(__file__).resolve().parents[1] + / "references/issue-title-contract/selection.v1.json" +) +SOURCE_PATHS = { + ".github/issues/title-contract.v1.json", + ".github/issues/schema/title-contract.v1.schema.json", + ".github/labels/catalog.v1.json", + "docs/issue-titles.md", + "fixtures/issue-titles/cases.v1.json", +} + + +def load_selection(path: Path = DEFAULT_SELECTION) -> dict: + """Read one immutable selection and verify all cached source bytes.""" + selection = json.loads(path.read_text(encoding="utf-8")) + if selection["schema_version"] != 1: + raise ValueError("unsupported selection schema") + contract = selection["contract"] + if not re.fullmatch(r"[0-9a-f]{40}", contract["revision"]): + raise ValueError("contract revision must be an immutable full commit SHA") + if contract["authority"] not in {"candidate", "accepted"}: + raise ValueError("contract authority must be explicit") + if selection["adoption"] != "explicit-repository-selection-observe": + raise ValueError("selection does not authorize automatic repository adoption") + files = selection["files"] + digests = contract["source_digests"] + if set(files) != SOURCE_PATHS or set(digests) != SOURCE_PATHS: + raise ValueError("selection must include exactly the five contract sources") + if len(set(files.values())) != len(files): + raise ValueError("source cache paths must be distinct") + root = path.resolve().parent + sources = {} + for source_path, relative_path in files.items(): + relative = PurePosixPath(relative_path) + if relative.is_absolute() or ".." in relative.parts or "\\" in relative_path: + raise ValueError("source cache path must stay within the selection directory") + local = (root / relative_path).resolve() + if not local.is_relative_to(root): + raise ValueError("source cache path escapes the selection directory") + raw = local.read_bytes() + if hashlib.sha256(raw).hexdigest() != digests[source_path]: + raise ValueError(f"source digest mismatch: {source_path}") + sources[source_path] = raw + title_contract = json.loads(sources[".github/issues/title-contract.v1.json"]) + cases = json.loads(sources["fixtures/issue-titles/cases.v1.json"]) + for field in ("contract_id", "contract_version"): + if title_contract[field] != contract[field] or cases[field] != contract[field]: + raise ValueError(f"source identity mismatch: {field}") + if title_contract["owner"] != contract["repository"]: + raise ValueError("contract owner does not match selected repository") + catalog = json.loads(sources[".github/labels/catalog.v1.json"]) + if catalog["catalog_version"] != title_contract["label_catalog"]["version"]: + raise ValueError("label catalog version does not match contract") + consumer = selection["consumer"] + if not re.fullmatch(r"[0-9a-f]{40}", consumer["revision"]): + raise ValueError("consumer revision must be an immutable full commit SHA") + if consumer["authority"] not in {"candidate", "accepted"}: + raise ValueError("consumer authority must be explicit") + return selection + + +def verify_report(selection: dict, report: dict) -> None: + """Reject a proposal/report from a missing, stale, or mixed source selection.""" + if report.get("schema_version") != 1 or report.get("contract") != selection["contract"]: + raise ValueError("report contract provenance does not match the local selection") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--selection", type=Path, default=DEFAULT_SELECTION) + parser.add_argument("--report", type=Path, help="Egolint proposal or validation JSON") + args = parser.parse_args() + try: + selection = load_selection(args.selection) + if args.report is not None: + verify_report(selection, json.loads(args.report.read_text(encoding="utf-8"))) + except (OSError, ValueError, KeyError, TypeError, AttributeError) as exc: + print(json.dumps({"status": "unavailable", "reason": str(exc)})) + return 2 + print(json.dumps({"status": "verified", "contract": selection["contract"]})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/library/organization/skills/authoring/github-issue-authoring/templates/GITHUB_ISSUE.template.md b/library/organization/skills/authoring/github-issue-authoring/templates/GITHUB_ISSUE.template.md index 5b272a5..98199bd 100644 --- a/library/organization/skills/authoring/github-issue-authoring/templates/GITHUB_ISSUE.template.md +++ b/library/organization/skills/authoring/github-issue-authoring/templates/GITHUB_ISSUE.template.md @@ -1,5 +1,15 @@ # + + ## Summary State the problem, why it matters, and the desired outcome. diff --git a/library/organization/skills/build-distributions.py b/library/organization/skills/build-distributions.py index 32be88c..c2622ac 100644 --- a/library/organization/skills/build-distributions.py +++ b/library/organization/skills/build-distributions.py @@ -29,7 +29,8 @@ 1. Canonical source is never modified. 2. Generated files are never hand-edited; the manifest header makes this explicit. 3. The source digest covers only the canonical SKILL.md file (UTF-8, LF-normalised). -4. Companion directories (evals/, references/, templates/, scripts/) are copied verbatim. +4. Companion directories (evals/, references/, templates/, scripts/) are copied + verbatim, excluding generated Python bytecode caches. 5. Frontmatter may declare narrowly scoped repository resources for a portable package in ``metadata.aether-distribution-resources``. Each resource must name a repository-relative source file and a package-relative destination. @@ -197,7 +198,7 @@ def _collect_generated_paths(skill_name: str, source_dir: Path, frontmatter: dic src = source_dir / companion if src.is_dir(): for f in sorted(src.rglob("*")): - if f.is_file(): + if _is_companion_source(f, source_dir): rel = f.relative_to(source_dir) paths.append(f"dist/skills/{skill_name}/{rel.as_posix()}") for _source, destination in _distribution_resources(frontmatter): @@ -206,6 +207,16 @@ def _collect_generated_paths(skill_name: str, source_dir: Path, frontmatter: dic return paths +def _is_companion_source(path: Path, source_dir: Path) -> bool: + """Exclude interpreter output so running a skill cannot change its package.""" + relative = path.relative_to(source_dir) + return ( + path.is_file() + and "__pycache__" not in relative.parts + and path.suffix not in {".pyc", ".pyo"} + ) + + def _build_skill_dist(skill_name: str, source_dir: Path) -> dict[str, bytes]: """Return mapping of relative-to-repo-root path β†’ file bytes for one skill.""" source_md = source_dir / "SKILL.md" @@ -222,7 +233,7 @@ def _build_skill_dist(skill_name: str, source_dir: Path) -> dict[str, bytes]: src = source_dir / companion if src.is_dir(): for f in sorted(src.rglob("*")): - if f.is_file(): + if _is_companion_source(f, source_dir): rel = f.relative_to(source_dir) out[f"dist/skills/{skill_name}/{rel.as_posix()}"] = f.read_bytes() diff --git a/tests/test_build_distributions.py b/tests/test_build_distributions.py index 7d6454b..06fdc42 100644 --- a/tests/test_build_distributions.py +++ b/tests/test_build_distributions.py @@ -213,6 +213,17 @@ def test_outputs_companion_files(self): self.assertIn("dist/skills/my-skill/references/guide.md", file_map) self.assertIn("dist/skills/my-skill/templates/output.template.md", file_map) + def test_python_execution_caches_do_not_change_distribution(self): + skill_dir = _make_skill(self._tmp, "my-skill") + scripts = skill_dir / "scripts" + scripts.mkdir() + (scripts / "check.py").write_text("print('check')\n") + before = bd._build_skill_dist("my-skill", skill_dir) + (scripts / "__pycache__").mkdir() + (scripts / "__pycache__/check.cpython-312.pyc").write_bytes(b"generated cache") + (scripts / "check.pyc").write_bytes(b"legacy cache") + self.assertEqual(before, bd._build_skill_dist("my-skill", skill_dir)) + def test_outputs_manifest(self): skill_dir = _make_skill(self._tmp, "my-skill") file_map = bd._build_skill_dist("my-skill", skill_dir) diff --git a/tests/test_continuity_integration.py b/tests/test_continuity_integration.py index dc93e57..c735ee4 100644 --- a/tests/test_continuity_integration.py +++ b/tests/test_continuity_integration.py @@ -115,7 +115,11 @@ def test_applicable_skills_declare_compact_domain_handoffs(self) -> None: continue self.assertEqual(metadata["aether-version"], evals["version"], record["id"]) - self.assertEqual(metadata["aether-version"], "1.1.0", record["id"]) + self.assertRegex(metadata["aether-version"], r"^\d+\.\d+\.\d+$", record["id"]) + self.assertGreaterEqual( + tuple(map(int, metadata["aether-version"].split("."))), + (1, 1, 0), record["id"], + ) if record["id"] == "maintain-repository-continuity": self.assertIn("## Perform the pre-PR handoff", text) @@ -153,8 +157,11 @@ def test_agents_compose_the_skill_without_gaining_authority(self) -> None: self.assertIn("repository-continuity", metadata["aether-specs"]) self.assertIn(f"- **Contribute:** {record['domain_evidence'][0]}", text) self.assertIn(f"- **Never claim:** {record['never_claim'][0]}", text) - self.assertEqual(metadata["aether-version"], "1.1.0") - self.assertEqual(catalog[record["id"]]["version"], "1.1.0") + self.assertEqual(catalog[record["id"]]["version"], metadata["aether-version"]) + self.assertRegex(metadata["aether-version"], r"^\d+\.\d+\.\d+$") + self.assertGreaterEqual( + tuple(map(int, metadata["aether-version"].split("."))), (1, 1, 0) + ) self.assertIn( "maintain-repository-continuity", catalog[record["id"]]["skills"], diff --git a/tests/test_issue_title_authoring.py b/tests/test_issue_title_authoring.py new file mode 100644 index 0000000..9d93d52 --- /dev/null +++ b/tests/test_issue_title_authoring.py @@ -0,0 +1,222 @@ +"""Pinned discovery checks plus an opt-in, real Egolint consumer exercise.""" + +from __future__ import annotations + +import copy +import importlib.util +import json +import os +from pathlib import Path +import shutil +import subprocess +import sys +import tempfile +import unittest +from unittest.mock import patch + +from jsonschema import Draft202012Validator +import yaml + +ROOT = Path(__file__).resolve().parents[1] +SKILL = ROOT / "library/organization/skills/authoring/github-issue-authoring" + + +def load_module(name, path): + spec = importlib.util.spec_from_file_location(name, path) + assert spec and spec.loader + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +sources = load_module("title_sources", SKILL / "scripts/verify_title_contract.py") +projections = load_module( + "title_projections", ROOT / "library/organization/projections/build-projections.py" +) + + +class IssueTitleSourceTests(unittest.TestCase): + def test_selected_bytes_and_contract_schema(self): + selection = sources.load_selection() + cache = sources.DEFAULT_SELECTION.parent + contract = json.loads((cache / selection["files"][".github/issues/title-contract.v1.json"]).read_text()) + schema = json.loads((cache / selection["files"][".github/issues/schema/title-contract.v1.schema.json"]).read_text()) + Draft202012Validator(schema).validate(contract) + self.assertEqual(selection["contract"]["authority"], "candidate") + self.assertEqual(selection["adoption"], "explicit-repository-selection-observe") + + def test_portable_selection_matches_canonical_bytes(self): + portable = ROOT / "dist/skills/github-issue-authoring" + selection = sources.load_selection(portable / "references/issue-title-contract/selection.v1.json") + self.assertEqual(selection, sources.load_selection()) + self.assertEqual( + (portable / "scripts/verify_title_contract.py").read_bytes(), + (SKILL / "scripts/verify_title_contract.py").read_bytes(), + ) + + def test_missing_or_corrupt_sources_are_unavailable(self): + for mode in ("missing", "corrupt"): + with self.subTest(mode=mode), tempfile.TemporaryDirectory() as directory: + cache = Path(directory) / "cache" + shutil.copytree(sources.DEFAULT_SELECTION.parent, cache) + source = cache / "semantics.txt" + if mode == "missing": + source.unlink() + else: + source.write_text(source.read_text() + "\nchanged") + result = subprocess.run( + [sys.executable, str(SKILL / "scripts/verify_title_contract.py"), + "--selection", str(cache / "selection.v1.json")], + capture_output=True, text=True, check=False, + ) + self.assertEqual(result.returncode, 2) + self.assertEqual(json.loads(result.stdout)["status"], "unavailable") + + def test_mutable_pin_wrong_identity_and_escaping_paths_are_rejected(self): + for field in ("revision", "contract_id", "path"): + with self.subTest(field=field), tempfile.TemporaryDirectory() as directory: + cache = Path(directory) / "cache" + shutil.copytree(sources.DEFAULT_SELECTION.parent, cache) + path = cache / "selection.v1.json" + selection = json.loads(path.read_text()) + if field == "path": + selection["files"]["docs/issue-titles.md"] = "../outside.txt" + else: + selection["contract"][field] = "main" if field == "revision" else "other/v1" + path.write_text(json.dumps(selection)) + with self.assertRaises(ValueError): + sources.load_selection(path) + + def test_report_missing_stale_or_mixed_provenance_is_rejected(self): + selection = sources.load_selection() + good = {"schema_version": 1, "contract": selection["contract"]} + sources.verify_report(selection, good) + for field in ("revision", "authority", "source_digests"): + report = copy.deepcopy(good) + report["contract"][field] = "stale" + with self.subTest(field=field), self.assertRaises(ValueError): + sources.verify_report(selection, report) + with self.assertRaises(ValueError): + sources.verify_report(selection, {"schema_version": 1}) + + def test_managed_block_update_and_removal_preserve_local_guidance(self): + before = "# Consumer instructions\n\nKeep local commands here.\n" + after = "\n## Nested scope\n\nRespect our extra constraints.\n" + once = projections._apply_issue_authoring(before) + after + twice = projections._apply_issue_authoring(once) + self.assertEqual(twice, projections._apply_issue_authoring(twice)) + self.assertIn(before.strip(), twice) + self.assertIn(after.strip(), twice) + removed = projections._remove_issue_authoring(twice) + self.assertEqual(removed, before + after) + for broken in (once + once, before + projections.ISSUE_AUTHORING_START, + projections.ISSUE_AUTHORING_END + projections.ISSUE_AUTHORING_START): + with self.subTest(broken=broken), self.assertRaises(ValueError): + projections._apply_issue_authoring(broken) + + def test_module_cannot_silently_drift_from_selection(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "module.md" + path.write_text(projections.ISSUE_AUTHORING_PATH.read_text().replace('"selection_sha256":"', '"selection_sha256":"bad')) + with patch.object(projections, "ISSUE_AUTHORING_PATH", path): + with self.assertRaisesRegex(ValueError, "does not match"): + projections._load_issue_authoring() + + def test_discovery_projection_does_not_expand_agent_permissions(self): + path = ROOT / "library/organization/agents/github-issue-creator/AGENT.md" + frontmatter, _body = projections._parse_agent("github-issue-creator", path) + self.assertEqual(frontmatter["tools"], ["read", "search", "web"]) + files = projections._build_files(projections.load_registry()) + for path in ("fixtures/repository-instructions/AGENTS.md", "codex/repository/AGENTS.md", + "github/repository/.github/copilot-instructions.md", "claude/repository/CLAUDE.md", + "github/repository/.github/agents/github-issue-creator.agent.md"): + self.assertEqual(files[path].decode().count(projections.ISSUE_AUTHORING_START), 1) + claude = files["claude/repository/.claude/agents/github-issue-creator.md"].decode() + metadata = yaml.safe_load(claude.split("---", 2)[1]) + self.assertNotIn("Bash", metadata["tools"]) + self.assertNotIn("Write", metadata["tools"]) + for path, data in files.items(): + if path.endswith(".agent.md") and "github-issue-creator" not in path: + self.assertNotIn(projections.ISSUE_AUTHORING_START, data.decode()) + + +@unittest.skipUnless(os.environ.get("AETHER_EGOLINT_BINARY"), "set AETHER_EGOLINT_BINARY for real consumer evidence") +class EgolintConsumerTests(unittest.TestCase): + """Exercise the existing validator, never a second title implementation.""" + + def setUp(self): + self.binary = str(Path(os.environ["AETHER_EGOLINT_BINARY"]).resolve(strict=True)) + self.selection = sources.load_selection() + + def run_egolint(self, *arguments, expected=0): + result = subprocess.run([self.binary, "issue-title", *arguments], capture_output=True, text=True, check=False) + self.assertEqual(result.returncode, expected, result.stdout + result.stderr) + report = json.loads(result.stdout) + sources.verify_report(self.selection, report) + return report + + def validate(self, snapshot, expected=0): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "issue.json" + data = json.dumps(snapshot) + path.write_text(data) + report = self.run_egolint("validate", "--input", str(path), expected=expected) + self.assertEqual(path.read_text(), data) + return report + + def test_fresh_consumer_discovers_portable_skill_and_validates_proposal(self): + with tempfile.TemporaryDirectory() as directory: + consumer = Path(directory) + installed = consumer / ".agents/skills/github-issue-authoring" + shutil.copytree(ROOT / "dist/skills/github-issue-authoring", installed) + guidance = projections._repository_guidance_projection("codex-compatible", "AGENTS.md").decode() + guidance += "\nThis synthetic consumer explicitly selects the issue-title policy in observe mode.\n" + (consumer / "AGENTS.md").write_text(guidance) + self.assertIn(".agents/skills/github-issue-authoring/SKILL.md", guidance) + self.assertIn("references/canonical-issue-titles.md", (installed / "SKILL.md").read_text()) + subject = "[FLO-OBS-01] Checkpoint 2: Export reports" + proposal = self.run_egolint("format", "--type", "feature", "--reviewed-subject", subject) + self.assertTrue(proposal["title"].endswith(subject)) + self.assertEqual(proposal, self.run_egolint("format", "--type", "feature", "--reviewed-subject", subject)) + snapshot = {"schema_version": 1, "complete": True, "title": proposal["title"], "labels": [proposal["required_label"]]} + report = self.validate(snapshot) + self.assertEqual(report["status"], "conformant") + (consumer / "report.json").write_text(json.dumps(report)) + checked = subprocess.run( + [sys.executable, str(installed / "scripts/verify_title_contract.py"), "--report", "report.json"], + cwd=consumer, capture_output=True, text=True, check=False, + ) + self.assertEqual(checked.returncode, 0, checked.stdout + checked.stderr) + self.assertEqual(json.loads(checked.stdout)["status"], "verified") + + def test_upstream_cases_without_duplicating_semantics(self): + cases = json.loads((sources.DEFAULT_SELECTION.parent / "cases.v1.json").read_text()) + for case in cases["cases"]: + with self.subTest(case=case["id"]): + expected = case["expected"] + snapshot = {"schema_version": 1, "complete": True, **case["input"]} + report = self.validate(snapshot, expected=0 if expected["status"] == "conformant" else 1) + self.assertEqual(report["status"], expected["status"]) + self.assertEqual(report["type"], expected["type"]) + for migration in cases["migrations"]: + with self.subTest(migration=migration["id"]): + primary_type = migration["after"]["labels"][0].removeprefix("type:") + proposal = self.run_egolint( + "format", "--type", primary_type, + "--reviewed-subject", migration["reviewed_subject"], + ) + self.assertEqual(proposal["title"], migration["after"]["title"]) + self.assertIn(proposal["required_label"], migration["after"]["labels"]) + + def test_incomplete_provider_evidence_and_changed_labels_do_not_pass(self): + proposal = self.run_egolint("format", "--type", "feature", "--reviewed-subject", "Preserve IDs") + snapshot = {"schema_version": 1, "complete": False, "title": proposal["title"], "labels": []} + self.assertEqual(self.validate(snapshot, expected=2)["status"], "unavailable") + snapshot.update(complete=True, labels=[proposal["required_label"]]) + self.assertEqual(self.validate(snapshot)["status"], "conformant") + snapshot["labels"].append("type:bug") + self.assertEqual(self.validate(snapshot, expected=1)["status"], "conflict") + + +if __name__ == "__main__": + unittest.main()