Skip to content

OpenSpec: prefer-triad-in-project-new (task 5.6 of openxFactory prefer-triad-project-shape) - #5

Merged
brettheap merged 10 commits into
mainfrom
change/prefer-triad-in-project-new
Oct 7, 2026
Merged

brettheap merged 10 commits into
mainfrom
change/prefer-triad-in-project-new

Conversation

@brettheap

@brettheap brettheap commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openRepoProject-1

What this is

The OpenSpec change prefer-triad-in-project-new: phases 1 to 6 of this repository's /opsx:propose flow are complete (scaffold, proposal.md, alignment review, council, verdicts, then the spec delta, design.md and the governance tasks.md). Nothing here is ratified, and no code byte moves.

Awaiting Brett Heap's word; the single Speckit feature is created after ratification with /speckit.specify.

  • Change: openspec/changes/prefer-triad-in-project-new/
    • .openspec.yaml
    • proposal.md
    • clarifications.md
    • specs/project-command/spec.md
    • design.md
    • tasks.md
  • Modified capability: project-command. "Delegate project creation" is MODIFIED, and six requirements are ADDED (47 scenarios in all). No new capability.
  • Checks at 11d3e9c: openspec validate prefer-triad-in-project-new --strict and openspec validate --all --strict pass; openspec status shows every artifact done; python3 -m unittest discover -s tests runs 43 tests OK, as on main.

Governing pointers

What the proposal says, in brief

  • project new offers the Triad first to a person at the terminal who has not chosen by flag.
    • One helper decides "a person at the terminal": stdin and stdout are terminals and CI is false-y.
    • The Triad is listed first and pre-selected, with its posture; a single repository is accepted without a reason asked.
    • The answer is not a confirmation. openRepoShape's typed confirmation and the bench path's Type yes are both kept; no repository is created that the person has not confirmed.
  • A flag-chosen or non-interactive single-repository creation keeps today's behaviour and adds a two-line warning: advisory on stderr once the repository exists (ratified design D2).
  • A <user>-wip name gets neither the question nor the advisory.
  • The ratified rules are kept as non-negotiables: the Triad is preferred, elective, and confers nothing; a single repository stays a fully conformant answer; nothing is converted or created unconfirmed; the preference is never a gate, check or review input.

Revision after alignment review

  • Scope is creation-only. The project status / project doctor advisory sketched in project new offers the Triad first (openxFactory prefer-triad-project-shape, task 5.6) #3 is outside the ratified forms of the advisory; it would need the doctrine owner's amendment or Brett Heap's word. Its detection findings are recorded in the proposal's Out of Scope section.
  • OQ-1: inspection advisory not admitted; creation-only.
  • OQ-2: D2's stderr line is allowed and owed; stdout, prompts, refusals, files, .project.json and exit status stay as today.
  • OQ-3: moot at creation; only the -wip name form is read.
  • OQ-4: Triad always first and default; organisation and visibility asked (no default visibility); missing openRepoShape stated and refused; the CamelCase name constraint stated, refusal stays openRepoShape's.
  • Full resolution table and both reports: resolution comment.

Council review

  • 13 concerns raised: Product Advocate 4 plus a lower-impact scenario, Systems Architect 4, Adversary Engineer 5 plus smaller issues.
  • Verdicts (assigned by the lead): 11 VALID applied to proposal.md at 92e4d2c; 6 NOTED recorded in the new clarifications.md; 3 DISMISSED.
  • Chief changes: the Triad's known obstacles (the assembly-root name form, openRepoShape on PATH, the parent directory) are read before the question, named beside the Triad, and refused with exit 2 on a Triad answer before any prompt or network; the second offer that --workflow brings from setup-openspeckit is recorded, with a workBenches follow-up; the organisation and visibility prompts are specified; the advisory write is guarded; and the workBenches pin advance becomes a tracked handoff issue.
  • Verdict table and the three reports verbatim: council resolution comment.

Spec delta and design

  • Spec delta (specs/project-command/spec.md): the MODIFIED "Delegate project creation" restates the whole requirement, with "Preview" reworded so that it holds for both answers. The ADDED requirements cover who is asked the creation question and where it sits; its entries and answers; the known Triad obstacles; the organization and visibility prompts; the creation advisory; and the order under --workflow.
  • Design (design.md): decisions D1 to D14 answer the council's noted constraints N1 to N6 and fix every new string as ASCII text for the tests to pin. No open question changes what gets built.
  • Tasks (tasks.md): governance and handoff only. Ratification is left unticked, and the one Speckit feature (specs/002-<slug>/) is still to be created.

Refs #3. The realization PR closes the issue; this PR is governance.

🤖 Generated with Claude Code

Summary by Sourcery

Establish the OpenSpec proposal and governance artifacts for preferring Triad-shaped projects during eligible interactive project new flows without implementing the behavior yet.

Enhancements:

  • Define the governance, requirements, design decisions, and handoff plan for offering the Triad first during interactive project creation while preserving single-repository support.

Chores:

  • Record council clarifications, ratification gates, Speckit handoff requirements, and downstream workBenches coordination for the future implementation.

Scaffold the change and draft proposal.md only: an interactive
`project new` offers the Triad first, explicit flags keep today's
behaviour, and `status`/`doctor` give the advisory once for a
non-exempt single repository, never as a gate, check or review input.
Alignment review, council and design follow in the same pull request.

Refs #3

Lane: openRepoProject-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 7, 2026

Copy link
Copy Markdown

Reviewer's Guide

This proposal introduces a Triad-first interactive path for project creation and a terminal-only advisory for non-exempt single repositories, while preserving existing non-interactive behavior and keeping the preference elective and non-gating. Review should focus on the four documented open questions—especially whether inspection advisories are authorized, whether the flagged stderr line violates byte-for-byte compatibility, how exemptions can be determined offline, and behavior when Triad prerequisites are unavailable—before ratification.

Sequence diagram for the Triad-first project creation flow

sequenceDiagram
    actor User
    participant Project as project new
    participant OpenRepoShape as openRepoShape
    participant Bench as Bench generator

    User->>Project: new()
    alt Terminal and no shape-selection flag
        Project-->>User: Ask Triad or single, Triad preselected
        alt User selects Triad
            Project->>OpenRepoShape: --shape
            OpenRepoShape-->>User: Typed confirmation
            User->>OpenRepoShape: Confirm creation
        else User selects single
            Project->>Bench: Create single repository
            Bench-->>User: Type yes confirmation
            User->>Bench: Confirm creation
        end
    else Explicit flag or non-terminal
        Project->>Project: Preserve existing behavior
    end
Loading

Flow diagram for terminal-only single-repository advisories

flowchart TD
    Start[project status or project doctor] --> Terminal{Human terminal and CI unset or false?}
    Terminal -->|No| Existing[Existing report unchanged]
    Terminal -->|Yes| Kind{Inspected root reports kind single?}
    Kind -->|No| Existing
    Kind -->|Yes| Exempt{Declared exemption applies?}
    Exempt -->|Yes| Existing
    Exempt -->|No| Record{Valid single-repository.yaml?}
    Record -->|Yes| Existing
    Record -->|No| Advisory[Print one advisory in human report]
    Advisory --> Existing
Loading

File-Level Changes

Change Details Files
Adds a proposal-only OpenSpec change defining Triad-first interactive project creation while preserving existing explicit and non-interactive behavior.
  • Defines a terminal-only Triad-or-single prompt with Triad preselected and routes each answer through the existing creation confirmations.
  • Preserves current prompts, outputs, files, and exit statuses for flag-selected and non-terminal invocations, with an unresolved question about an additional stderr advisory.
  • Documents prerequisite handling, confirmation boundaries, and delegation to existing openRepoShape behavior.
openspec/changes/prefer-triad-in-project-new/.openspec.yaml
openspec/changes/prefer-triad-in-project-new/proposal.md
Proposes a local, non-gating advisory for non-exempt single repositories during status and doctor inspection.
  • Limits the advisory to human terminal reports, excluding JSON, checks, strict-mode effects, CI, and unattended runs.
  • Defines exemptions and local inspection rules, including black-box handling of single-repository.yaml without fetching, validating beyond the published kind rule, writing, or refusing.
  • Leaves several alignment and implementation decisions open, including whether inspection is in scope and how exemption facts should be mirrored.
openspec/changes/prefer-triad-in-project-new/proposal.md
Records the proposal’s governance, capability scope, ownership boundaries, documentation expectations, and intended test coverage.
  • Links the change to the governing issue, ratified upstream task, and sibling implementations.
  • Scopes implementation to project, README, and tests while explicitly excluding workBenches forwarders, openRepoShape tooling, conversion, gates, and new dependencies.
  • Specifies pseudo-terminal, byte-identity, exemption, JSON, strict, CI, and failure-path test scenarios.
openspec/changes/prefer-triad-in-project-new/.openspec.yaml
openspec/changes/prefer-triad-in-project-new/proposal.md

Assessment against linked issues

Issue Objective Addressed Explanation
#3 Make interactive project new offer the Triad first, with Triad as the default while keeping single-repository creation supported and requiring explicit confirmation before creation. ❌ The PR only adds an OpenSpec proposal describing the intended behavior. It makes no changes to the project new implementation, prompts, routing, or creation flow.
#3 Preserve existing behavior for explicit flags and non-interactive invocations, ensuring the Triad preference is elective and never a gate, check, review input, or unconfirmed creation. ❌ These constraints are documented as proposed requirements, but no production code or tests implement or verify them.
#3 Implement any agreed inspection advisories and exemption handling for single repositories without changing exit statuses, JSON output, or repository metadata. ❌ The PR explicitly leaves this work for a later design and implementation phase and contains no changes to project status, project doctor, inspection logic, or documentation beyond the proposal.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

The capabilities note cited chore/archive-completed-changes as having no
pull request; #4 was opened for it at 0e7f029 while this proposal was
drafted, so the note now names it.

Refs #3

Lane: openRepoProject-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r-triad-in-project-new

Lane: openRepoProject-1

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane: openRepoProject-1

Proposal drafted at 5e9f408; archive PR #4 landed on main as 67efa80 while it was being written, so origin/main is merged in at 30e5fdb (a rebase was declined: the organisation ruleset forbids force pushes). The five canonical specs now sit under openspec/specs/, which is what this change's delta specs will reference. Alignment review (stack architect + QA lead) is running on the proposal; council follows.

Apply the lead's rulings R1 to R14 on both alignment reports.

- Scope is creation-only: the status/doctor advisory and its exemption
  reader leave What Changes, Impact and Rules; Out of Scope records why,
  the detection and record-rule divergences, and the pre-existing
  doctor --validate --strict exit.
- D2's stderr advisory is owed after a flag-chosen or non-interactive
  single-repository creation, before any --workflow step; the rule that
  contradicted it is restated.
- One helper decides a person at the terminal (stdin and stdout ttys, CI
  false-y); the choosing flags, the question's position, its answers, the
  Triad prerequisites, the name constraint and the -wip exemption are
  stated.
- Modified Capabilities now names project-command (archive PR #4 merged
  as 67efa80); the standalone capability is dropped.
- Why adds the routing through workBenches lane project-command's claim;
  Impact adds the pty test helper, pinned fixtures and the one Speckit
  handoff.

Lane: openRepoProject-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@brettheap

brettheap commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Lane: openRepoProject-1

Alignment review: resolution

Both alignment reports (Stack Architect, QA Lead) were ruled on by the lead (R1 to R15) and applied to proposal.md at 7f7c91d, with a wording pass at b8d566b (current head). Ruling R1 makes the change creation-only, so every finding about the project status / project doctor advisory or its exemption reader is MOOT, or OUT-OF-SCOPE where the finding is kept as a record in the proposal's Out of Scope section. Sections named below are those of the revised proposal.md.

Stack Architect (5 MISMATCH, 15 DRIFT)

Finding Severity Resolution Where / reason
SA 1.1 second "person at the terminal" DRIFT APPLIED What Changes, "One helper decides": stdin and stdout ttys, CI false-y, a new pattern in project (R4)
SA 1.2 reader leaks into --json / update DRIFT MOOT creation-only: no inspection reader, print_report() untouched (R1)
SA 1.3 "already chosen" flags open-ended DRIFT APPLIED What Changes, "Flags that choose": the nine choosing flags; --dry-run, --workflow, --into/parent, --workbenches still ask (R5)
SA 1.4 pty tests; harness inherits CI=true DRIFT APPLIED Impact, tests: pty helper beside run_cli; CI and AGENT_PROTOCOL_ROOT set explicitly (R13)
SA 2.1 onp inherits only via the pin MISMATCH APPLIED Why, routing paragraph: launcher runs pin a0407904; behaviour arrives when workBenches advances it (R11)
SA 2.2 Modified Capabilities stale MISMATCH APPLIED Capabilities: #4 merged as 67efa80; MODIFIED "Delegate project creation" plus ADDED requirements in specs/project-command/spec.md (R10)
SA 2.3 hyphenated name vs CamelCase DRIFT APPLIED What Changes, "Names": question states the constraint, refusal stays openRepoShape's; hyphenated-name test (R8, R13)
SA 3.1 "never printed without a person" contradicts D2 MISMATCH APPLIED Rules, "Never a gate": never a checks row, --json field or review output; no exit status; on stderr, no stdout byte (R2)
SA 3.2 two record rules conflated MISMATCH OUT-OF-SCOPE creation reads no record; the openRepoShape / setup-openspeckit / PyYAML divergence is recorded in Out of Scope (R1)
SA 3.3 sixth exemption (non-Git directory) DRIFT APPLIED Out of Scope: the ratified five kept as the list; a non-Git directory "is not a repository at all" (R12)
SA 3.4 routing through lane project-command DRIFT APPLIED Why: this change edits nothing the claim holds; merge sha posted on #3 (R11)
SA 3.5 "one line" cannot carry the elements DRIFT APPLIED What Changes, "The advisory's words": two warning: lines with every ratified element (R3)
SA 4.1 kind single silences non-Triad manifests MISMATCH MOOT creation-only: no inspection-time shape reading (R1)
SA 4.2 leg-clone reachability DRIFT MOOT creation-only (R1)
SA 4.3 four <user>-wip divergences DRIFT OUT-OF-SCOPE recorded one sentence each in Out of Scope; creation reads only the name form (What Changes, "Silent for a workspace name", R1, R9)
SA 4.4 PyYAML absent DRIFT MOOT creation reads no YAML (R1)
SA 4.5 record and advisory vs "Inspect and diagnose" DRIFT MOOT Capabilities: "Inspect and diagnose" untouched because the inspection advisory is out of scope (R1, R10)
SA 4.6 doctor --validate --strict exits 1 today DRIFT APPLIED Out of Scope, "Pre-existing and untouched" (project:812-813, :828-829) (R1)
SA 5.1 no Speckit handoff DRIFT APPLIED Impact, "Speckit handoff": exactly one specs/002-<slug>/; tasks.md holds governance boxes and the handoff (R14)
SA 5.2 standalone capability reason gone DRIFT APPLIED Capabilities: project-triad-preference dropped; delta goes in project-command (R10)

QA Lead (8 MISMATCH, 21 GAP)

Finding Severity Resolution Where / reason
QA 1.1 Modified Capabilities stale MISMATCH 1 APPLIED Capabilities (R10)
QA 1.2 "no workBenches change" wrong MISMATCH 2 APPLIED Why, routing paragraph; the claim is gone from What Changes (R11)
QA 2.1 CI value set MISMATCH 3 APPLIED What Changes, "One helper decides": unset, or stripped and case-insensitive "", 0, false, no (R4)
QA 2.2 rules contradict D2's stderr line MISMATCH 4 APPLIED Rules, "Never a gate" (R2)
QA 2.3 "run by a person deciding" missing MISMATCH 5 APPLIED What Changes, "The advisory's words": elements mirror openRepoShape's advisory_lines(); setup-openspeckit's omission recorded, not this change's to fix; exact strings are design's (R3)
QA 2.4 kind single adds an exemption MISMATCH 6 MOOT creation-only (R1)
QA 3.2 malformed record vs "Inspect and diagnose" MISMATCH 7 MOOT creation-only; "Inspect and diagnose" untouched (R1, R10)
QA 3.3 never-refuse vs "YAML decoding errors are structured refusals" MISMATCH 8 MOOT creation reads no YAML; project-review-safety untouched (R1, R10)
QA 2.5 terminal test weaker than sibling GAP APPLIED What Changes, "One helper decides" (R4)
QA 2.6 "already chosen" flag set ambiguous GAP APPLIED What Changes, "Flags that choose"; the "byte for byte" heading is gone (R5)
QA 2.7 workspace config root and keys GAP OUT-OF-SCOPE recorded in Out of Scope (configuration root, keys read); creation reads no workspace configuration (R1, R9)
QA 2.8 leg-clone instruction form GAP MOOT creation-only (R1)
QA 2.9 advisory stream, position, parser GAP APPLIED creation advisory: stderr, warning: prefix, stdout flushed first, after the repository exists and before any --workflow step; the parser question is OUT-OF-SCOPE and recorded (R1, R2, R3)
QA 3.1 MODIFIED "Delegate project creation" GAP APPLIED Capabilities: question, asked organisation and visibility, stderr line (R10)
QA 3.4 Preview at a terminal GAP APPLIED What Changes, "Flags that choose": a dry run asks; Triad answer prints today's --shape --dry-run plan; no advisory on a dry run (R5)
QA 3.5 question vs today's refusals GAP APPLIED What Changes, "Where the question sits": after every pre-flight refusal; flagged runs keep today's order (R6)
QA 3.6 update shares the report printer GAP MOOT creation-only (R1)
QA 4.1 harness cannot see a tty GAP APPLIED Impact, tests: pty helper; non-terminal stdout and CI=true cases (R13)
QA 4.2 CI / AGENT_PROTOCOL_ROOT leak GAP APPLIED Impact, tests: both set explicitly (R13)
QA 4.3 in-process tests use the runner's tty GAP APPLIED Impact, tests: in-process module.main() tests patch isatty and the environment (R13)
QA 4.4 no byte-identity baseline GAP APPLIED Impact, tests: expected strings pinned as fixtures, no comparison against "today" (depth-1 checkout) (R13)
QA 4.5 Triad path test underspecified GAP APPLIED Impact, tests: fake openRepoShape on PATH recording argv; OQ-4 decided (R8, R13)
QA 4.6 answers undefined GAP APPLIED What Changes, "The answers": Enter and 1 Triad, 2 single, re-ask once then exit 2, EOF exit 2, interrupt 130; prompt text DEFERRED-TO-DESIGN (R7)
QA 4.7 fixture values not pinned GAP APPLIED the -wip pattern is pinned with its source commit (What Changes, "Silent for a workspace name"); leg-opening and unreadable-record fixtures are MOOT (R1, R9)
QA 4.8 when the D2 line prints GAP APPLIED What Changes: prints once the repository exists, before any --workflow step, so a later failure does not suppress it; a -wip name suppresses it (R2, R9)
QA 4.9 missing PyYAML outcome GAP MOOT creation reads no YAML (R1)
QA 5.1 Triad name rule GAP APPLIED What Changes, "Names" (R8)
QA 5.2 routing through lane project-command GAP APPLIED Why: the pin move is workBenches' act; merge sha posted on #3 per comment 6028076490 (R11)
QA 5.3 README Configuration table GAP APPLIED Impact, README: a CI row, and the "asks for yes" sentence corrected; "Understand a project" unchanged (R13)

Open questions, as ruled

  • OQ-1 (inspection advisory): not admitted; the ratified forms are a closed list, so the change is creation-only and still discharges task 5.6 (R1).
  • OQ-2 (D2's stderr line): allowed and owed: flag-chosen or non-interactive single-repository creations keep today's stdout, prompts, refusals, files, .project.json and exit status, and add the advisory on stderr once the repository exists (R2).
  • OQ-3 (offline exemption reading): moot at creation; the one exemption read is the <user>-wip name form of the pinned naming policy, and the inspection-time divergences are recorded for any later scope extension (R1, R9).
  • OQ-4 (Triad prerequisites): the Triad stays first and the default in every case; organisation and visibility are asked (no default visibility; a typed answer is explicit); a missing openRepoShape is stated in the question and a Triad answer refuses with exit 2 and today's install message; the CamelCase constraint is stated and its refusal stays openRepoShape's (R8).

Carried to clarifications: whether the creation question is owed for a workspace (-wip) name, on which the ratified text is silent.

openspec validate prefer-triad-in-project-new still reports "Change must have at least one delta", as expected before the spec phase; openspec status shows proposal done, specs and design ready.

Reviewer reports, verbatim

The Stack Architect's report is below. The QA Lead's report is in the next comment (#5 (comment)), because both together exceed a comment's size limit.

Stack Architect alignment report (verbatim, sha256 7896ef88...)

Alignment review: Stack Architect, prefer-triad-in-project-new

Lane openRepoProject-1, phase 3 of /opsx:propose. Read-only review.

Material read:


1. Pattern alignment

1.1 Finding: The proposal introduces a second, undeclared definition of "a person at the terminal". project has no CI handling anywhere, and its only terminal test is on stdin. The question and the inspection advisory would use different tests.

  • Source: project:146-148, def ask(prompt): if not sys.stdin.isatty(): raise Refused(...). grep '"CI"' project finds nothing. setup-openspeckit 9fbe609c line 2269-2281 is_interactive_run(): "stdin and stdout are both terminals, and not CI", with CI_FALSE_VALUES: Final = ("", "0", "false", "no") (line 127). Its spec decision 1 (specs/021-triad-shape-advisory/spec.md:233) says the same.
  • Proposal reference: What Changes bullet 1: "when a person is at a terminal". Impact: "ask()'s terminal rule kept". Bullet 3: "only when stdout is a terminal and CI is unset or false".
  • Fix: In bullet 3 replace "only when stdout is a terminal and CI is unset or false" with: "only when a person is at the terminal. One helper decides this for both the question and the advisory, mirroring setup-openspeckit's decision 1: stdin and stdout are both terminals, and CI is unset or one of "", 0, false, no (case-insensitive). This is a new pattern in project, whose ask() reads only sys.stdin.isatty()." In bullet 1 replace "when a person is at a terminal" with "when a person is at the terminal (the same helper)".
  • Severity: DRIFT

1.2 Finding: The reader would leak into --json or into update's report unless it is kept out of report. print_report() dumps report verbatim for --json, and update also uses print_report() for its default report.

  • Source: project:761-763: def print_report(report, as_json): if as_json: print(json.dumps(report, indent=2)). project:853-855: if not args.apply: print_report(report, args.json) (inside update).
  • Proposal reference: Impact: "a small offline advisory reader used by inspect_project() and print_report() for status and doctor". Out of Scope: "project update, project clean and project benches output".
  • Fix: Replace it with: "a small offline advisory reader called only from inspect_project(). Its answer is passed to print_report() as a separate argument and is never stored in report, because print_report() dumps report verbatim for --json and is also update's default report."
  • Severity: DRIFT

1.3 Finding: The list of flags that "have already chosen" is open-ended, and several existing flags are not classified.

  • Source: project:930-973 defines --into, --workflow, --dry-run, --workbenches, --family and --elected-by alongside the named ones. project:237-238: "--org, --visibility, --family and --elected-by require --shape."
  • Proposal reference: What Changes bullet 2: "(--shape or a shape option, --bench, --type, --description, or --yes)".
  • Fix: Replace it with: "(--shape, --org, --visibility, --family, --elected-by, --bench, --type, --description or --yes). --dry-run, --workflow, --into or the positional parent, and --workbenches choose nothing, so a dry run at a terminal also asks (as setup-openspeckit's decision 10, 'Dry runs ask too')."
  • Severity: DRIFT

1.4 Finding: Pseudo-terminal tests are a new test pattern, and the existing harness passes the CI runner's CI=true through to every test.

  • Source: tests/test_project.py:39-42: self.env = {**os.environ, ...}. tests/test_project.py:47-50: run_cli(...) uses input="", so stdin is always a pipe. There is no pty anywhere in the suite. .github/workflows/tests.yml runs on GitHub Actions, which sets CI=true.
  • Proposal reference: Impact, tests: "pseudo-terminal runs of the question".
  • Fix: Append: "These runs use a new pty-based helper beside run_cli (Linux and macOS, as CI runs). Every question and advisory test sets CI explicitly, because self.env copies os.environ and GitHub Actions sets CI=true."
  • Severity: DRIFT

2. Interface accuracy

Verified as described:

  • --shape, --bench, --type, --description, --yes, --org, and --visibility with choices public/private/internal (project:930-973).
  • "Install openRepoShape through workBenches first." is verbatim (project:228). So is "Shape creation requires --org and --visibility." (project:222).
  • The bench Type yes to run this plan: confirmation (project:153).
  • The shape path passes no --yes, so openRepoShape keeps its typed confirmation. See project:253 ("Shape's own confirmation is always retained, including with --yes here"), and openRepoShape setup-project.py plan_and_confirm refuses without a terminal and without its own --yes.
  • An interrupt exits 130 (project:1001-1003).
  • "requires a future adapter" (README.md:55-56).
  • The openRepoShape --doctor launcher clones the standard when no checkout is beside it, and does not pass --json through. Usage text: "run from anywhere else it clones the standard into a temporary directory first". Line 465-466: "--json is shape-doctor.py's and reaches it from a checkout, deliberately not plumbed through".

2.1 Finding: onp and new-project.sh do not inherit the new behaviour "with no workBenches change". They run the pinned artifact, which is today's code. This contradicts the proposal's own Impact.

  • Source: workBenches d86ba59:
    • scripts/onp:8 runs exec bash "$script_dir/new-project.sh" "$@".
    • scripts/new-project.sh:5 runs exec bash "$script_dir/project" new "$@".
    • scripts/project:46 runs exec python3 -I "$installer" ... --exec-owned -- "$@".
    • setup-project-command.py:457-477 execute_project runs the verified payload in-process with exec(code, namespace).
    • config/openrepoproject-pin.json has "commit": "a0407904adfff6f784cfef78a10953c6bf103b67".
  • Proposal reference: What Changes bullet 2: "onp and new-project.sh exec into project new, so they inherit both behaviours with no workBenches change." It conflicts with Impact: "workBenches moves its pinned project artifact on its owner's act".
  • Fix: Replace that sentence with: "onp and new-project.sh forward to project new through workBenches' launcher. The launcher runs the pinned artifact (config/openrepoproject-pin.json, today a0407904) in the same process and terminal. The scripts need no change, and they inherit both behaviours once workBenches advances that pin on its owner's act."
  • Severity: MISMATCH

2.2 Finding: "Modified Capabilities" is stale. PR #4 has merged, and project-command is now canonical on main.

  • Source: gh pr view 4 shows "state":"MERGED","mergedAt":"2026-10-07T00:28:29Z","mergeCommit":{"oid":"67efa806..."}. openspec/specs/project-command/spec.md exists on main 67efa80, with requirements "Delegate project creation" and "Inspect and diagnose".
  • Proposal reference: "None. openspec/specs/ is empty on main (openspec list --specs: "No specs found."), and project-command's requirements live only in its unarchived change. If Archive completed OpenSpec changes: add-project-clean-command, resolve-pr-review-findings, project-command #4 (chore/archive-completed-changes, open at 0e7f029) lands first ..."
  • Fix: Replace the paragraph with: "Archive completed OpenSpec changes: add-project-clean-command, resolve-pr-review-findings, project-command #4 merged on 2026-10-07 as 67efa80, so openspec/specs/project-command/spec.md is canonical on main. This change contradicts neither 'Delegate project creation' nor 'Inspect and diagnose'. The question adds a step before generator selection. A typed organization and visibility are still 'explicit'. The advisory and the record are not report state (see 4.5). The spec phase either records that reading or writes MODIFIED blocks (see 5.2)."
  • Severity: MISMATCH

2.3 Finding: Pressing Enter does not always reach openRepoShape's confirmation. project new accepts names that openRepoShape refuses as an assembly root.

  • Source: project:206 accepts [A-Za-z0-9][A-Za-z0-9_-]*. openRepoShape 1a9fc537 setup-project.py:133 says "--project <Project> the assembly-root name: ONE CamelCase token, no hyphen, underscore, dot or space."
  • Proposal reference: Impact, tests: "Enter reaches the shape path with the owner's confirmation kept".
  • Fix: Add to bullet 1: "The question is asked after the name is known. If the name is not one CamelCase token, the Triad is still listed first, and the question says openRepoShape will refuse that name as an assembly root." Add a test for a hyphenated name.
  • Severity: DRIFT

3. Governing-text consistency

Verified as accurate:

  • Task 5.6, quoted verbatim (tasks.md:210-215 at e6380965).
  • The ratifying word "ratify 1249 as recommended" (design.md:4).
  • The D2 quote (design.md:83-85).
  • openRepoShape's record rule: "kind: DECIDES ... a record that leaves one out is still silent" (contracts/single-repository-record.yaml:33-39).
  • setup-openspeckit decision 7: "a second validator here would be a second schema" (specs/021-triad-shape-advisory/spec.md:294-297).
  • TRIAD_ADVISORY_LINES is as summarised (setup-openspeckit 9fbe609c:111-118).
  • The shared protocol's creation sentence: "When a person asks to create a new project, offer the Triad first as the default" (~/.agents/protocols/openspec-speckit-workflow.md:52-53, e081c5ab).

3.1 Finding: The proposal contradicts itself. It says the advisory is never printed without a person at the terminal, but it also adds D2's stderr line to invocations whose stdin is not a terminal.

  • Source: Proposal, What Changes bullet 2 ("or whose stdin is not a terminal ... when such an invocation creates a single repository, it writes one advisory line to stderr") against Rules bullet 4.
  • Proposal reference: Rules This Change Keeps, "Never a gate, check or review input": "and it is never printed without a person at the terminal, so no unattended run, CI log or review output carries it."
  • Fix: Replace that clause with: "The inspection advisory is never printed without a person at the terminal. The one creation advisory (ratified D2's non-interactive form) goes to stderr after a single repository is created without the question. It changes no stdout byte and no exit status, and it is never a checks row, a --json field or review output."
  • Severity: MISMATCH

3.2 Finding: The proposal conflates two record rules. "Checking kind only, as setup-openspeckit does" is not openRepoShape's rule, and project would use a third parser.

  • Source:
    • openRepoShape scripts/shape_advisory.py:654-664: data = parse_yaml(...), then if not isinstance(data, dict): return RECORD_SILENCES_NOTHING, ["it is not a YAML mapping"]. parse_yaml is repo_shape.py's stdlib "supported YAML subset". Tree.kind uses is_file(), which follows symlinks.
    • setup-openspeckit 9fbe609c:2208-2233 reads only manifest_top_level_value(lines, "kind"). Its read_root_text "never follows a symlink" (line 2050ff). Spec line 294: "Its top-level kind: reads single-repository-record, and the file is regular, readable UTF-8."
    • project:37-48 read_yaml uses PyYAML yaml.safe_load.
  • Proposal reference: OQ-3: "apply openRepoShape's published rule locally, checking kind only, as setup-openspeckit does". Compare bullet 5: "the file parses as a YAML mapping whose kind is single-repository-record".
  • Fix: Replace it with: "apply openRepoShape's published rule locally. The root single-repository.yaml silences the advisory when it is a file that parses as a YAML mapping whose kind is single-repository-record. A leading BOM is dropped, and no other field is checked. That follows setup-openspeckit's decision 7 ('a second validator here would be a second schema'). setup-openspeckit's own test reads only the top-level kind: line and refuses symlinks. That divergence, and PyYAML parsing where openRepoShape uses its own subset, are recorded in the design."
  • Severity: MISMATCH

3.3 Finding: The proposal lists six silent classes and then says no other class is exempted. The ratified list has five, and the sixth is never justified.

  • Source: openxFactory spec.md:203-204: "No surface SHALL exempt a repository by guessing its class: the list above is the whole list." openRepoShape shape_advisory.py:53-54: "A directory that is not a repository at all is not advised either: the sentence begins 'this is a single repository', and there is none." setup-openspeckit spec FR-004 (line 187): "silent for exactly these, and for nothing else" (five, with no non-repository).
  • Proposal reference: Bullet 4: "... and a directory that is not a Git repository. ... No other class is exempted (the packet's ruled OQ-2)."
  • Fix: Replace "and a directory that is not a Git repository" with "and a directory that is not a Git repository, which is not a repository at all (openRepoShape's not-a-repository: 'the sentence begins "this is a single repository", and there is none'; setup-openspeckit does not make this distinction)". Replace the last sentence with "No repository class beyond the ratified five is exempted (the packet's ruled OQ-2)."
  • Severity: DRIFT

3.4 Finding: The proposal addresses only half of task 5.6's routing. It covers this repository's governance and says nothing about workBenches lane project-command's claim.

3.5 Finding: The creation advisory is specified as "one line", but the ratified advisory must carry the posture and both exits. Both realized surfaces needed two lines for that.

  • Source: openxFactory spec.md:103-105: "The advisory SHALL name the way to convert — openRepoShape's adopt-project.py, run by a person deciding for that project — and the way to stop meeting it". spec.md:58-59: "Wherever the preference is stated, the posture above SHALL be stated with it". setup-openspeckit TRIAD_ADVISORY_LINES is two lines, and line 2370 writes f"warning: {line}" to stderr. openRepoShape advisory_lines() is two lines.
  • Proposal reference: Bullet 2: "it writes one advisory line to stderr afterwards".
  • Fix: Replace it with: "it writes the advisory to stderr afterwards: setup-openspeckit's two TRIAD_ADVISORY_LINES, each prefixed warning: (its decision 8), so the posture and both exits travel with the preference".
  • Severity: DRIFT

4. Integration surface (exemption detection; feeds OQ-3)

Family holder: clean. manifest() returns family-manifest for family.yaml with that kind. Any other kind refuses (project:274-282), so the report never says single. That matches openRepoShape (shape_advisory.py:708-713) and setup-openspeckit (declares_family_holder).

Non-Git directory: detectable from report["repository"]["present"] is False (project:323-325; discover() reaches it via .project.json/.devcontainer, project:317-318). That is equivalent to openRepoShape's (root / ".git").exists() (shape_advisory.py:714). See 3.3 for the setup-openspeckit divergence.

4.1 Finding: Keying the advisory on the reported kind single also silences a project.yaml that says kind: project-manifest but does not declare the Triad. All three governing readers advise that case.

  • Source:
    • project:274-282: manifest() returns project-manifest whenever data.get("kind") == "project-manifest", with no check of schema or legs.
    • openRepoShape shape_advisory.py:404-411 is_triad requires schema: project-repo-schema and {"spec","code"} <= roles.
    • setup-openspeckit spec edge case (line 164): "A project.yaml that is not a Triad manifest (wrong kind, wrong schema, or a missing leg): the repository is single and gets the advisory."
    • Shared protocol "Detecting the shape": "Anything else is a single repository."
    • openxFactory spec.md:203-204: "the list above is the whole list."
  • Proposal reference: Bullet 3: "only where the command already reports kind single". Bullet 4: "The first and third are never reported as single, so they are silent by construction."
  • Fix: Replace "only where the command already reports kind single" with "only where the inspected root is not a declared Triad: kind single, or kind project-manifest whose manifest lacks schema: project-repo-schema or a spec or code leg". Replace "The first and third are never reported as single, so they are silent by construction." with "The Triad root is read by the Triad detector (kind, schema, a spec and a code leg), not by the reported kind. A family holder is never reported as single, so it is silent by construction."
  • Severity: MISMATCH

4.2 Finding: The leg-clone exemption reaches only standalone clones, and the three readers detect a leg differently.

  • Source:
    • Reachability: project:310-313 makes discover() return the nearest ancestor holding project.yaml. A leg mounted at <root>/<spec>/ (template: "This leg is mounted there at {{SPEC_PATH}}/") is reported as the assembly root and never as single.
    • openRepoShape shape_advisory.py:154 uses \AThis is the \*\*(spec|code) leg\*\* of after stripping a BOM. It adds a name route (_leg_by_name, lines 456-474): ^[A-Za-z][A-Za-z0-9]*-spec$ and -code$ (repository-naming.yaml:353,359), matched against the directory name and the origin name.
    • setup-openspeckit 9fbe609c:107 uses (?m)^This is the \*\*(spec|code) leg\*\* of \S, which matches any line. It requires no project.yaml and has no name route.
  • Proposal reference: Bullet 4: "a leg clone (its AGENTS.md opens as openRepoShape's leg templates write it)". OQ-3: "which to mirror".
  • Fix: Replace the parenthesis with: "(a standalone checkout whose root AGENTS.md opens, after any BOM, with This is the **spec leg** of or This is the **code leg** of , openRepoShape's LEG_OPENING_RE. A leg mounted inside its assembly root is never reached, because discover() reports the root)". Then the design decides whether to add openRepoShape's name route.
  • Severity: DRIFT

4.3 Finding: The <user>-wip exemption has four concrete divergences between project's existing code and the two governing readers.

  • Source:
    • (a) Configuration root: project:626 uses Path.home() / ".agents/workspace.yaml" (and the SPECKIT_WORKSPACE_PATH override). openRepoShape shape_advisory.py:518 and setup-openspeckit both use ${AGENT_PROTOCOL_ROOT:-$HOME/.agents}.
    • (b) Keys: project:645 reads override or orgs[<org>].path or path. openRepoShape (:525-529) reads path plus every orgs.*.path. setup-openspeckit (named_by_workspace_config, :2172) reads path plus repository: matched against origin's owner/name. The shared protocol says the file "carries exactly two keys, repository: ... and path:".
    • (c) Name: openRepoShape checks both the directory name and the origin name (repository_names, :430-446). setup-openspeckit checks origin only: "No directory name, other remote, or other heuristic is read" (spec :293).
    • (d) Origin parsing: project:618-621 remote_repository_identity accepts only github.com URLs. Both other readers take the last path segment of any remote.
  • Proposal reference: Bullet 4: "(the naming policy's workspace form on the origin name, or a checkout that ~/.agents/workspace.yaml names)".
  • Fix: Replace the parenthesis with: "(the naming policy's workspace form ^[a-z0-9]+(?:-[a-z0-9]+)*-wip$ at openRepoShape 1a9fc537 on the last segment of the origin URL, whatever its host; or ${AGENT_PROTOCOL_ROOT:-$HOME/.agents}/workspace.yaml naming this checkout by path: or any orgs.*.path, or naming its origin by repository:. handoff()'s path resolution and its GitHub-only identity parser are not reused)". Record (c) in the design.
  • Severity: DRIFT

4.4 Finding: The proposal does not say what the reader answers when the record exists but PyYAML is unavailable. Under workBenches that is a live path.

  • Source:
    • project:38-41 raises Refused("YAML inspection needs PyYAML ...").
    • workBenches scripts/project:46 runs python3 -I, which disables user site-packages, so a pip install --user PyYAML is invisible.
    • openRepoShape answers silently for an unexpected error (NOT_ANSWERED, shape_advisory.py:132-139,693-698) and "silences nothing" for an unparsable record (:655-657).
  • Proposal reference: Impact: "The reader must never raise: a missing PyYAML, an unreadable manifest or an unreadable record must not turn status or doctor of a single repository into a refusal."
  • Fix: Append: "When PyYAML is absent the reader gives no advisory and no record line (openRepoShape's not-answered). It does not report a possibly valid record as one that 'does not silence'."
  • Severity: DRIFT

4.5 Finding: Showing record problems only in the human report conflicts on its face with the canonical "Inspect and diagnose" requirement, and the proposal does not reconcile them.

  • Source: Canonical project-command/spec.md:23-25: "Status and doctor SHALL support human and JSON reports ... Missing or malformed state SHALL be visible". openxFactory spec.md:195-196: "Its only reader SHALL be the advisory."
  • Proposal reference: Bullet 5: "Present but not valid: the advisory plus one line saying why". Rules: "never ... a --json field".
  • Fix: Add under Capabilities: "single-repository.yaml and the advisory are not report state under 'Inspect and diagnose'. The ratified rule makes the advisory the record's only reader, so neither appears in --json, and the spec delta says so."
  • Severity: DRIFT

4.6 Finding: A shape-keyed exit status already exists. doctor --validate adds a warning row only because the repository is single, and under --strict that exits 1. The proposal's "never a gate" framing does not mention it.

  • Source: project:812-813: checks.append({"level": "warning", "check": "validators", "detail": "No shape validators apply to this single repository"}). project:828-829: args.strict and r["level"] == "warning" gives exit 1.
  • Proposal reference: Rules: "Never a gate, check or review input ... changes no exit status".
  • Fix: Add to Out of Scope: "The existing doctor --validate row 'No shape validators apply to this single repository' (level warning, so exit 1 under --strict) predates this change and is unchanged by it. Whether it should be an ok or informational row is a separate question."
  • Severity: DRIFT

5. OpenSpec conventions

Clean:

  • .openspec.yaml (schema: spec-driven, created:) matches the three archived changes.
  • Lane: header matches 2026-10-07-project-command/proposal.md.
  • The Why, What Changes, Capabilities and Impact headings are present.
  • Proposal-only is lawful at this phase.

5.1 Finding: The proposal names no Speckit handoff.

  • Source: ~/.agents/protocols/openspec-speckit-workflow.md:149-150: "The OpenSpec change hands off to exactly one Speckit feature under specs/NNN-*". Precedent archive/2026-10-07-project-command/tasks.md: "## Speckit handoff / Implementation is tracked exclusively in specs/001-project-command/tasks.md." .specify/init-options.json has "feature_numbering": "sequential", and only specs/001-project-command exists.
  • Proposal reference: Impact, which says nothing about the Speckit feature.
  • Fix: Add to Impact: "Implementation is handed to exactly one Speckit feature, specs/002-<slug>/. OpenSpec tasks.md carries governance boxes and that one handoff, as 2026-10-07-project-command/tasks.md does, and lists no implementation steps."
  • Severity: DRIFT

5.2 Finding: The archived reason for separate capabilities no longer holds now that the baseline is archived.

  • Source: archive/2026-10-07-resolve-pr-review-findings/proposal.md: "None. The existing change specifications have not yet been archived into the baseline specification set; this remediation records the tightened contracts as standalone review-safety capabilities." Main 67efa80 now has specs/project-command/spec.md.
  • Proposal reference: New Capabilities: "project-triad-preference".
  • Fix: Either write the delta as ## ADDED Requirements in specs/project-command/spec.md, the command's own baseline capability. Or keep project-triad-preference and add: "kept as its own capability because its requirements carry another repository's ratified doctrine (openxFactory prefer-triad-project-shape) and archive with that evidence."
  • Severity: DRIFT

Open questions, answered from the governing text

OQ-1 (is the inspection advisory admitted?): decided by the text, and the answer is no without an amendment.

  • openxFactory spec.md:91-93 (e6380965): "the advisory SHALL be given only at such surfaces and only in these forms:"
  • The third form, spec.md:97: "openRepoShape's scaffold, adopt and doctor surfaces MAY report it beside what they already report, and SHALL NOT change an exit status on its account."
  • design.md:63-64: "The place proposed is the moment a person STARTS WORK on a repository, and only there".
  • design.md:256-258: "This packet puts ... the ADVISORY in openRepoShape's mechanics, in the workstation bootstrap and in the shared agent protocol."
  • Issue project new offers the Triad first (openxFactory prefer-triad-project-shape, task 5.6) #3's "One possible shape ... The owning lane decides." is a handoff note. It cannot widen a ratified "only".

The forms are named surfaces, and project status and project doctor are not among them. Recommended reading: drop the inspection bullets and make this change creation-only. That still discharges 5.6 ("New-project creation offers the Triad first"). If the inspection form is wanted, ask the doctrine owner (openxFactory, lane codeXfactory-5) for an amendment that adds it as a fourth form, and ship nothing before that.

Dropping it also removes 1.2, 4.1-4.6 and most of OQ-3. A freshly created single repository is never exempt, so creation needs no reader.

OQ-2 (does "byte for byte" admit D2's stderr line?): decided by the text, and the answer is yes.

  • design.md:84-85: "A non-interactive invocation does what its flags say, and prints the recommendation when it creates a single repository."
  • "Explicit flags keep today's behaviour" comes from issue project new offers the Triad first (openxFactory prefer-triad-project-shape, task 5.6) #3's non-binding "One possible shape". "Byte for byte" is the proposal's own phrase.
  • The one tension is spec.md:95: "a subagent, a delegated worker and an unattended run address no person and SHALL NOT say it". That clause sits inside the agent-session bullet, and D2's table gives it the reason "it addresses no person".
  • The tool precedent prints. The bootstrap's non-interactive form "SHALL print it as a warning" (spec.md:96). setup-openspeckit writes warning: lines to stderr whenever a run is not interactive, CI included (9fbe609c:2361-2370).

Recommended reading: keep stdout, prompts, refusals, created files, .project.json and exit status byte-identical, and write the two advisory lines to stderr after a single repository is created without the question. The text makes no CI carve-out. Fix 3.1 and 3.5 to match.

OQ-3 (offline exemption detection): partly decided.

  • spec.md:179-181: "each read from a declared fact — the repository's own tree, the pinned naming policy, or the person's own configuration — and never inferred".
  • spec.md:198-199: "Its schema SHALL be owned by openRepoShape as mechanics".
  • design.md:143-147: the leg route is "the AGENTS.md every leg ships".

The text is silent on the mechanism: a local copy, an import, or delegation. The proposal's two exclusions hold:

  • Delegation is out, because the launcher clones the standard and does not pass --json.
  • Import is out, because project cannot assume a checkout.

One nuance on the second reason: openRepoShape's importer test scans only "nothing else this repository ships" (tests/test_shape_advisory.py:680-702), so it does not bind project. What it does say is that "a fourth importer is somebody wiring the shape into a check".

Recommended reading: a local, never-raising reader that mirrors openRepoShape's read() order:

  1. Triad detector
  2. Family holder
  3. Not a repository
  4. Leg
  5. Workspace
  6. Record

openRepoShape owns the mechanics, so project doctor and openRepoShape --doctor should then agree on the same directory. Hard-code the naming forms with their source commit, as setup-openspeckit's decision 6 did ("openRepoShape main 39d5c986"). Take the specific choices from 4.1-4.4, and record the divergences named there. For OQ-3's "which to mirror", the remaining differences are setup-openspeckit's: it reads no directory name and has no name-route leg, while openRepoShape has both.

OQ-4 (Triad chosen without --org/--visibility, or without openRepoShape): the text is silent on prerequisites.

  • The deciding sentences only fix the order and default. design.md:82-83: "In an interactive creation the Triad is listed first and is the pre-selected answer."
  • spec.md:99-101: "the creating tool or agent SHALL offer the Triad first, as the recommended default, and SHALL create no repository the person has not confirmed".
  • Canonical project-command/spec.md:11-12: "Shape creation SHALL require explicit organization and visibility and retain owner prompts."

Recommended reading:

  • The Triad stays first and is the default unconditionally, since the text admits no exception.
  • Ask for the organization (validated by project:223) and the visibility (one of public/private/internal, with no default value). A typed answer is "explicit", so no MODIFIED block is needed. openRepoShape itself would default visibility to private (setup-project.py, "default: private"), so project must ask rather than pass it through.
  • Without openRepoShape on PATH, the question says so, and the Triad answer refuses with exit 2 and today's message, creating nothing.
  • Add the name case from 2.3. The text is silent on that too.

Summary

Dimension MISMATCH DRIFT
1. Pattern alignment 0 4
2. Interface accuracy 2 1
3. Governing-text consistency 2 3
4. Integration surface 1 5
5. OpenSpec conventions 0 2
Total 5 15

@brettheap

Copy link
Copy Markdown
Contributor Author

Lane: openRepoProject-1

Alignment review: QA Lead report, verbatim

Attached to the resolution comment above (#5 (comment)); split off because both reports together exceed a comment's size limit.

QA Lead alignment report (verbatim, sha256 ef00ef0a...)

Alignment review: QA Lead, prefer-triad-in-project-new

Lane: openRepoProject-1. Read-only review of openspec/changes/prefer-triad-in-project-new/proposal.md (PR #5, head 5e9f408), checked against:

1. Requirement accuracy

These citations were checked and are faithful:

  • Task 5.6, verbatim (packet tasks.md:210-215).
  • The ratifying word and the claim comment 6027807471.
  • D2's sentence (design.md:83-85).
  • "SHALL be given only at such surfaces and only in these forms" (spec.md:91-93).
  • openRepoShape's "MAY report it beside what they already report, and SHALL NOT change an exit status [on its account]" (spec.md:97).
  • The packet's ruled OQ-2 (design.md:206-211).
  • #163 closed as completed, and #164 merged as 1a9fc537.
  • workBenches #139 merged as 9fbe609c, and new-workstation #52 merged as e081c5ab, whose protocol text says "offer the Triad first as the default, accept a single repository without asking why".
  • Decision 7, quoted correctly.
  • Decision 3.
  • The launcher clones when no checkout is present and does not plumb --json (openRepoShape:91-96, 462-467).
  • openRepoShape's importer confinement (tests/test_shape_advisory.py:691-702).
  • project-command's "inspection SHALL never fetch, reset, or bootstrap" and "Shape creation SHALL require explicit organization and visibility".

1.1 "Modified Capabilities: None" is stale

1.2 "No workBenches change" is wrong

  • Finding: onp and new-project.sh only reach the new behaviour after workBenches advances its pin, which is a workBenches change.
  • Requirement source:
    • workBenches main scripts/new-project.sh: exec bash "$script_dir/project" new "$@". The scripts/project shim then runs setup-project-command.py --exec-owned, which executes the PINNED artifact.
    • config/openrepoproject-pin.json: "commit": "a0407904adfff6f784cfef78a10953c6bf103b67".
  • Proposal reference: What Changes: "onp and new-project.sh exec into project new, so they inherit both behaviours with no workBenches change."
  • Fix: "...inherit both behaviours once workBenches advances config/openrepoproject-pin.json past this change's merge; that pin move is workBenches' (lane project-command) act." Task 5.6's evidence for the onp route depends on that pin move.
  • Severity: MISMATCH

2. Value and wording consistency

2.1 The CI value set differs from the sibling's

  • Finding: The proposal's CI rule names fewer values than the one setup-openspeckit uses.
  • Requirement source:
    • workBenches 9fbe609c specs/021-triad-shape-advisory/spec.md:233-234 (decision 1): "interactive only when stdin and stdout are both terminals and CI is unset, empty, 0, false or no".
    • setup-openspeckit:127: CI_FALSE_VALUES = ("", "0", "false", "no"). The value is stripped and lower-cased (:2271).
  • Proposal reference: "only when stdout is a terminal and CI is unset or false".
  • Fix: "CI unset, or (stripped, case-insensitive) one of "", 0, false, no."
  • Severity: MISMATCH

2.2 The rules section contradicts D2's stderr line

  • Finding: "Never printed without a person at the terminal" cannot hold for the creation line.
  • Requirement source:
    • design.md:83-85 (ratified D2): "A non-interactive invocation does what its flags say, and prints the recommendation when it creates a single repository."
    • The proposal's own OQ-2 prints it for any non-terminal stdin.
  • Proposal reference: Rules This Change Keeps: "it is never printed without a person at the terminal, so no unattended run, CI log or review output carries it".
  • Fix: Scope that sentence to the status/doctor advisory. Say instead that the creation recommendation goes to stderr on every single-repository creation that did not pass through the question, and that it never reaches stdout, --json, a check or an exit status. Precedent: setup-openspeckit decision 8 writes non-interactive output to stderr with a warning: prefix, after flushing stdout (spec.md:298-300).
  • Severity: MISMATCH

2.3 A ratified wording element is missing

  • Finding: The proposal's list of required elements leaves out "run by a person deciding for that project", and the text it says it mirrors leaves it out too.
  • Requirement source:
    • Packet spec.md:103-104: "The advisory SHALL name the way to convert — openRepoShape's adopt-project.py, run by a person deciding for that project — and the way to stop meeting it".
    • The doctrine (openxFactory main, docs/project-repo-schema.md § Where it is said) repeats it.
    • openRepoShape shape_advisory.py:211-214 carries it: "converts a repository in place when a person deciding for this project runs it".
    • workBenches TRIAD_ADVISORY_LINES (setup-openspeckit:111-118) does NOT carry it.
  • Proposal reference: "Its wording mirrors setup-openspeckit's TRIAD_ADVISORY_LINES ... the preference with the posture, openRepoShape's adopt-project.py as the way to convert, single-repository.yaml as the way to stop meeting it, and that nothing changes". The D2 stderr "one advisory line" has no stated elements at all.
  • Fix: Add the element "run by a person deciding for that project", and apply the same element list to the stderr line. Better: fix the exact strings in design. openRepoShape's advisory_lines() (shape_advisory.py:207-215) already meets every element.
  • Severity: MISMATCH

2.4 Keying on reported kind single adds an exemption outside the ratified list

  • Finding: Advising only where the command reports kind single silences non-Triad project manifests, which the ratified text says must be advised.
  • Requirement source:
    • Packet spec.md:183: the Triad exemption is project.yaml with kind: project-manifest, schema: project-repo-schema AND spec and code legs.
    • spec.md:201-204: "Every other repository ... SHALL receive the advisory ... the list above is the whole list."
    • In project, manifest() (project:274-282) reports project-manifest for ANY kind: project-manifest. The test fixtures use kind: project-manifest\nlegs: [].
    • openRepoShape is_triad() (shape_advisory.py:393-411) and setup-openspeckit declares_triad() (:2096-2112) advise such a root.
  • Proposal reference: "only where the command already reports kind single" and "The first and third are never reported as single, so they are silent by construction."
  • Fix: Either advise when kind is project-manifest and the manifest is not a Triad (missing schema: project-repo-schema or a spec/code leg), or record this narrowing as a decision that needs a ruling. As written it is a sixth, undeclared exemption.
  • Severity: MISMATCH

2.5 The question's terminal test is weaker than the sibling's

  • Finding: The question checks only stdin, while setup-openspeckit requires stdin, stdout and a false-y CI.
  • Requirement source:
    • Decision 1 (spec.md:233-237).
    • Decision 8 (spec.md:301-302): the interactive question goes "to stdout, the stream the interactive test proved to be the terminal, so the question can never land in a redirected file".
    • project:146-149: ask() tests sys.stdin.isatty() only, and input() writes its prompt to stdout.
  • Proposal reference: "or whose stdin is not a terminal, asks nothing new" and "ask()'s terminal rule kept".
  • Fix: Ask the question only when stdin AND stdout are terminals and CI is false-y (per 2.1). Otherwise take the non-interactive branch (D2 line).
  • Severity: GAP

2.6 The "already chosen" flag set is ambiguous

  • Finding: The heading promises byte-for-byte behaviour for explicit flags, but the list it gives is narrower.
  • Requirement source: project:930-973 parser. The list excludes --dry-run, --workflow, --into, positional parent, --workbenches and positional name. "A shape option" is not enumerated.
  • Proposal reference: "Explicit flags keep today's behaviour byte for byte. An invocation that has already chosen by flag (--shape or a shape option, --bench, --type, --description, or --yes)".
  • Fix: State the exact set. Skip the question when any of --shape, --org, --visibility, --family, --elected-by, --bench, --type, --description or --yes is given. Say explicitly that --dry-run, --workflow, --into/parent and --workbenches alone still ask. setup-openspeckit decision 10 precedent: "Dry runs ask too." Rename the heading to match.
  • Severity: GAP

2.7 The workspace configuration's location and keys are unspecified

  • Finding: The proposal hard-codes the config path and does not say which keys "name" a checkout.
  • Requirement source:
    • openRepoShape shape_advisory.py:503-538 reads ${AGENT_PROTOCOL_ROOT:-$HOME/.agents}/workspace.yaml, keys path: and orgs.*.path:.
    • setup-openspeckit :2172-2197 reads the same root, keys path: and repository: (compared to origin owner/name).
    • project:626 handoff() hard-codes Path.home()/".agents/workspace.yaml".
  • Proposal reference: "or a checkout that ~/.agents/workspace.yaml names".
  • Fix: Name the root (${AGENT_PROTOCOL_ROOT:-$HOME/.agents}) and the exact keys that "name" a checkout.
  • Severity: GAP

2.8 The leg-clone instruction's form is unspecified

  • Finding: The proposal says a leg clone gets the instruction but not where, when or in what words.
  • Requirement source:
    • Packet spec.md:210-213: "no advisory is given ... the instruction to move to the assembly root is given instead".
    • openRepoShape leg_instruction() (shape_advisory.py:259-287).
    • setup-openspeckit :2256-2259.
  • Proposal reference: "a leg clone ..., which gets the instruction to work from its assembly root instead".
  • Fix: State the instruction's stream, its gate (the same terminal/CI gate? --json?) and its text.
  • Severity: GAP

2.9 The advisory's output form and the YAML parser are unspecified

  • Finding: The stream, placement and parser are not named.
  • Requirement source:
    • project:761-786 prints the human report to stdout, then the checks rows.
    • openRepoShape decides "parses" with its stdlib parse_yaml (shape_advisory.py:654).
    • setup-openspeckit reads only a top-level kind: line (:2225-2228).
  • Proposal reference: "the advisory plus one line saying why the file does not silence it" and "the file parses as a YAML mapping".
  • Fix: Name the stream (stdout), the position (after the report body, before or after checks), and the parser (PyYAML safe_load). Accept the divergence for files that only one parser accepts.
  • Severity: GAP

3. Spec coverage and conflicts

No canonical scenario asserts exact stdout of new, status or doctor.

3.1 The change to project new's requirement is not named

  • Finding: The change modifies project-command / "Delegate project creation" without saying so.
  • Requirement source: project-command/spec.md:9-12: "SHALL list usable generators, accept a name and parent, present a plan ... Shape creation SHALL require explicit organization and visibility and retain owner prompts."
  • Proposal reference: What Changes, bullets 1–2, and OQ-4 ("missing --org/--visibility are asked at the terminal").
  • Fix: Add a MODIFIED delta on "Delegate project creation" covering:
    • the question, its default and the conditions for asking it;
    • routing to today's paths;
    • the D2 stderr line;
    • org and visibility asked at a terminal with no default value.
  • Severity: GAP

3.2 Malformed-record visibility conflicts with "Inspect and diagnose"

  • Finding: A malformed single-repository.yaml becomes invisible in most runs, which the canonical requirement forbids.
  • Requirement source: project-command/spec.md:23-25: "Status and doctor SHALL support human and JSON reports ... Missing or malformed state SHALL be visible".
  • Proposal reference: The why-line for a malformed single-repository.yaml is given "only in the human report, and only when stdout is a terminal and CI is unset or false ... never in --json".
  • Fix: Add a MODIFIED delta on "Inspect and diagnose" stating that the staying-single record and the advisory are outside "state" for that sentence, citing packet spec.md:195-196 ("Its only reader SHALL be the advisory"). Otherwise the malformed record is invisible in JSON and in non-terminal runs.
  • Severity: MISMATCH

3.3 The never-refuse rule conflicts with "YAML decoding errors are structured refusals"

  • Finding: The canonical spec requires a refusal on every undecodable YAML input; the proposal requires none for the record.
  • Requirement source: project-review-safety/spec.md:78-82: "Every YAML input read by the command SHALL turn an invalid text encoding into a normal refusal. JSON mode MUST preserve its structured error output and exit code 2".
  • Proposal reference:
    • "The reader must never raise: a missing PyYAML, an unreadable manifest or an unreadable record must not turn status or doctor of a single repository into a refusal".
    • "Present but not valid ... nothing refuses".
  • Fix: Add a MODIFIED delta on project-review-safety "YAML decoding errors are structured refusals", exempting the advisory's inputs (single-repository.yaml, and workspace.yaml / project.yaml / family.yaml as read by the advisory reader). Without it, an undecodable record must refuse with exit 2.
  • Severity: MISMATCH

3.4 The "Preview" scenario is undefined at a terminal

  • Finding: It is not said whether --dry-run alone asks the question, or what a Triad answer prints.
  • Requirement source: project-command/spec.md:14-16: "WHEN creation is requested with --dry-run THEN the command prints the selected generator and destination without writes."
  • Proposal reference: --dry-run is absent from the skip list.
  • Fix: State whether --dry-run alone at a terminal asks, and what a Triad answer under --dry-run prints: the openRepoShape plan, with no generator.
  • Severity: GAP

3.5 The order of the question and today's refusals is unspecified

  • Finding: If the question comes first, refusals that are byte-identical today would change.
  • Requirement source:
    • project-command/spec.md:18-20 ("Existing project ... refuses before running the generator").
    • setup-openspeckit decision 11 (spec.md:312-315): "Every refusal that already ends a run happens before the advisory".
  • Proposal reference: Silent on ordering.
  • Fix: Run today's checks (name, parent/--into, destination exists) before the question, so today's refusals stay byte-identical at a terminal.
  • Severity: GAP

3.6 project update shares the report printer

  • Finding: Putting the advisory in print_report() would also print it in project update's report, which the proposal puts out of scope.
  • Requirement source: project:844-855 (update() calls print_report(report, args.json)) and project-command/spec.md:31-38.
  • Proposal reference: Impact: "a small offline advisory reader used by inspect_project() and print_report()"; Out of Scope: "project update ... output".
  • Fix: Gate the advisory on args.command in ("status","doctor"), and add a test that update's report is unchanged.
  • Severity: GAP

4. Acceptance criteria and testability

4.1 The harness cannot see the advisory

  • Finding: Without a pseudo-terminal, every advisory test passes vacuously.
  • Requirement source: tests/test_project.py:47-50: run_cli uses input="" and capture_output=True, so stdin and stdout are pipes and there is no pty helper.
  • Proposal reference: "--json, --strict and CI=true unchanged on a non-exempt single repository"; "one fixture per exemption".
  • Fix: Add a run_pty() helper (pty.openpty() for stdin and stdout, stderr captured separately). Specify that the advisory, exemption, --json, --strict and CI tests all run under it, each paired with a non-terminal control.
  • Severity: GAP

4.2 The test environment leaks CI and AGENT_PROTOCOL_ROOT

  • Finding: Advisory tests would pass locally and fail in CI.
  • Requirement source: tests/test_project.py:39-42: self.env = {**os.environ, ...} inherits CI (GitHub Actions sets CI=true) and AGENT_PROTOCOL_ROOT.
  • Proposal reference: Impact § tests.
  • Fix: Tests pop CI and AGENT_PROTOCOL_ROOT, and set them explicitly where they are under test.
  • Severity: GAP

4.3 In-process tests depend on the runner's terminal

  • Finding: Tests that call module.main([...]) behave differently at a developer's terminal than in CI.
  • Requirement source: tests/test_project.py:242-248 calls module.main([...]) in-process, so the real sys.stdin/sys.stdout.isatty() of the runner decides.
  • Proposal reference: Impact § tests.
  • Fix: Require in-process tests to patch isatty and the environment.
  • Severity: GAP

4.4 There is no baseline for byte-identity

  • Finding: The tests have nothing to compare "today" against, and git history is unavailable in CI.
  • Requirement source: .github/workflows/tests.yml uses actions/checkout@v4 with no fetch-depth, so depth is 1.
  • Proposal reference: "byte-identity of stdout, created tree and exit status against today for every explicit-flag and non-terminal invocation".
  • Fix: An enumerated invocation matrix with golden expected stdout, stderr, tree and exit status embedded in the tests.
  • Severity: GAP

4.5 The Triad path test is underspecified

  • Finding: "Enter reaches the shape path" cannot be written deterministically yet.
  • Requirement source: project:227-229 checks shutil.which("openRepoShape") and runs it. The harness has no fake openRepoShape executable, and the name and org/visibility outcome depend on OQ-4 and 5.1.
  • Proposal reference: "Enter reaches the shape path with the owner's confirmation kept".
  • Fix: A fake openRepoShape on PATH in self.env that records argv (and that --yes is absent) and reads a typed confirmation. Decide OQ-4 first.
  • Severity: GAP

4.6 The question's answers are undefined

  • Finding: There is no exact prompt or answer table to test against.
  • Requirement source: project:1001-1003: EOF and Ctrl-C both become "Cancelled." with exit 130. Precedent: setup-openspeckit decision 2 (spec.md:238-246).
  • Proposal reference: "The Triad is listed first and is the pre-selected answer".
  • Fix: Give the exact prompt text and what each of these does: Enter, 1/2, triad/single, unrecognised input, EOF, Ctrl-C.
  • Severity: GAP

4.7 Fixture values are not pinned

  • Finding: The exemption fixtures need exact values, and one of the proposed fixtures fails under root.
  • Requirement source:
    • openRepoShape templates/{spec,code}-root/AGENTS.md@1a9fc537 open with "This is the spec leg of " and "This is the code leg of ".
    • The naming policy's workspace pattern is ^[a-z0-9]+(?:-[a-z0-9]+)*-wip$ (contracts/repository-naming.yaml@1a9fc537:412).
  • Proposal reference: "one fixture per exemption plus valid, wrong-kind, unparsable and unreadable records".
  • Fix: Pin these strings and their source commit in the spec. Use invalid UTF-8, or a directory named single-repository.yaml, for "unreadable", because chmod 000 does not deny root in a bench container.
  • Severity: GAP

4.8 When the D2 line prints is not fully stated

  • Finding: "After a successful single-repository creation" leaves the edge cases open.
  • Requirement source: project:265-269: a --workflow follow-up failure prints "Project created at ...; workflow setup failed." and exits with that code.
  • Proposal reference: "one stderr line after a successful single-repository creation that did not go through the question".
  • Fix: State whether the line prints when the generator succeeds but --workflow fails, and whether exemptions (e.g. creating brett-wip) suppress it.
  • Severity: GAP

4.9 Missing PyYAML has no stated outcome

  • Finding: The proposal says a missing PyYAML must not refuse, but not what the reader answers instead.
  • Requirement source:
    • project:37-41: read_yaml refuses without PyYAML.
    • openRepoShape precedent (shape_advisory.py:132-139, 686-698): an unexpected failure means NOT_ANSWERED, which is silent. A record that cannot be read means advise, plus the reason.
  • Proposal reference: "a missing PyYAML ... must not turn status or doctor ... into a refusal".
  • Fix: State the answer when PyYAML is missing and a record is present. Silent is the recommended answer.
  • Severity: GAP

5. Scope and gaps

No gap on these; the proposal handles them explicitly or they need no change:

  • The --json advisory field is excluded on purpose, and that is consistent with D3.
  • --strict is covered.
  • .project.json needs no migration.
  • CI runs Linux and macOS only; README:191-192 says to use WSL2 on Windows.
  • workBenches' forwarding tests use fake payloads (devcontainer.test/test-project-command.py) and are unaffected.

5.1 The Triad default conflicts with project's name rule

  • Finding: Names project accepts can be refused by openRepoShape after the person takes the Triad default.
  • Requirement source:
    • project:206 accepts [A-Za-z0-9][A-Za-z0-9_-]*.
    • openRepoShape setup-project.py:133-135@1a9fc537: "ONE CamelCase token, no hyphen, underscore, dot or space".
  • Proposal reference: "The Triad is listed first and is the pre-selected answer". Legacy onp my-app reaches it.
  • Fix: Check the Triad name rule before or inside the question. When the name cannot be a Triad, say so and explain how to choose one.
  • Severity: GAP

5.2 Routing through the project-command lane's claim is not addressed

5.3 The README's Configuration table is not updated

  • Finding: New environment inputs would go undocumented.
  • Requirement source: README:155-165 lists the environment and config inputs.
  • Proposal reference: README edits are limited to "Create a project" and "Understand a project".
  • Fix: Add rows for CI (and AGENT_PROTOCOL_ROOT, if adopted). Correct README:37-38 ("shows the generator and destination and asks for yes") for the new first question.
  • Severity: GAP

Open questions: what the governing text decides

OQ-1: is the inspection advisory admitted?

Decided by the text: NOT admitted as a realization.

  • Packet spec.md:91-97: "the advisory SHALL be given only at such surfaces and only in these forms:", with exactly three forms. The third is "openRepoShape's scaffold, adopt and doctor surfaces".
  • D2's heading is "at three surfaces" (design.md:61).
  • The realized doctrine (openxFactory a2dc658d, § Where it is said) repeats the same three.
  • The packet's code_surface: scopes this repository to "opensoft/openRepoProject (new-project creation, reached through workBenches' project-command change)" (packet proposal.md:2).
  • Issue project new offers the Triad first (openxFactory prefer-triad-project-shape, task 5.6) #3's "One possible shape ... The owning lane decides" is not ratified text, and it cannot widen "only".

Recommended reading: the list is closed, and a project status/doctor advisory would be a fourth form. That needs an amendment of the packet on Brett Heap's word. Otherwise drop the inspection bullets. Creation alone, including D2's line, still discharges 5.6.

OQ-2: does "byte for byte" admit D2's stderr line?

Decided by the text: yes, the line is owed.

The text is silent on whether a flagged run at a terminal counts as "non-interactive". The scenario "WHEN a person creates a new project ... THEN the Triad is offered first" (spec.md:78-81) supports printing the line there too, since it is the only offer such a run gets. The text is also silent on CI.

Recommended reading: the proposal's reading, plus fixes 2.2, 2.3 and 4.8.

OQ-3: how are the exemptions read without the network?

Partly decided.

  • The record rule is decided:
    • openRepoShape contracts/single-repository-record.yaml:33-36: "kind: DECIDES, not the filename: a single-repository.yaml that parses and declares kind: single-repository-record silences the advisory; one that does not parse, or declares any other kind, silences nothing and is reported beside the advisory, and nothing fails on its account".
    • Decision 7 (workBenches spec.md:294-297): "No other field is validated ... a second validator here would be a second schema".
  • Delegating to the launcher is ruled out by project-command/spec.md:25: "inspection SHALL never fetch".
  • The sources:
    • Packet spec.md:179-181: "each read from a declared fact — the repository's own tree, the pinned naming policy, or the person's own configuration — and never inferred".
    • D4 (design.md:143-147) names only the leg-AGENTS.md route for legs.
    • spec.md:186 names the wip route as the policy's naming family "or because the person's own workspace configuration names it". It does not say which name, which keys, which config root, or which parser.

Recommended reading:

  • Mirror setup-openspeckit, the offline sibling (decisions 5 and 6):
    • the leg AGENTS.md opening;
    • the origin name against ^[a-z0-9]+(?:-[a-z0-9]+)*-wip$, citing repository-naming.yaml@1a9fc537;
    • path: and repository: in ${AGENT_PROTOCOL_ROOT:-$HOME/.agents}/workspace.yaml.
  • Do not use the directory's own name. It is the weakest "declared" fact and the closest to guessing.
  • Record openRepoShape's extra -spec/-code name route and directory-name route as a known divergence.
  • Be silent on an environment failure such as missing PyYAML (4.9).

OQ-4: what happens to the Triad answer when its prerequisites are missing?

Silent on prerequisites. The governing sentences are:

  • Packet spec.md:99-101: "SHALL offer the Triad first, as the recommended default, and SHALL create no repository the person has not confirmed".
  • D2 (design.md:82-83): "In an interactive creation the Triad is listed first and is the pre-selected answer". This is unconditional.
  • project-command/spec.md:11-12: "Shape creation SHALL require explicit organization and visibility and retain owner prompts".

Recommended reading:

  • Keep the Triad first and pre-selected, even when openRepoShape is missing. The proposal's up-front notice plus today's refusal is consistent with D2.
  • Asking org and visibility at the terminal satisfies "explicit" only if no default is offered. openRepoShape's own default is --visibility ... (default: private) (setup-project.py:138), and project must not inherit it.
  • Encode this as a MODIFIED delta on "Delegate project creation" (3.1).
  • Add the CamelCase name check (5.1).

Summary

Dimension MISMATCH GAP
1. Requirement accuracy 2 0
2. Value and wording consistency 4 5
3. Spec coverage and conflicts 2 4
4. Acceptance criteria and testability 0 9
5. Scope and gaps 0 3
Total 8 21

brettheap and others added 2 commits October 7, 2026 00:54
Reflow the terminal-helper paragraph and say plainly why project asks for
the visibility: openRepoShape would otherwise default it to private.

Lane: openRepoProject-1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Apply the lead's eleven VALID verdicts (V1 to V11) to proposal.md and
record the six NOTED ones (N1 to N6) in a new clarifications.md.

- Known obstacles: the assembly-root name form, openRepoShape on PATH
  and the parent directory are read before the question, named beside
  the Triad, and refused with exit 2 on a Triad answer before any prompt
  or network; openRepoShape's own refusals keep its status. "CamelCase"
  is gone; Preview is reworded for both answers.
- --workflow: project keeps its advisory; setup-openspeckit's follows
  as a second ratified form, and the double offer is recorded with its
  cases and a workBenches follow-up.
- The organisation and visibility prompts get the question's rules and
  a restating line; the --workflow refusal is hoisted only when the
  question is asked.
- The prompt-stream wording and the pty helper are corrected; the
  advisory write is guarded and ASCII-only; a bench creation counts as
  a single repository whether or not Git is initialised.
- Declining, agents at a pty, the -wip reading at creation, the
  workBenches pin-advance handoff issue, the y/yes/n/no answers and four
  small corrections.

Lane: openRepoProject-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane: openRepoProject-1

Council review: resolution

The three council reports (Product Advocate, Systems Architect, Adversary Engineer) on proposal.md at b8d566b were cross-examined by the lead, who assigned every verdict below. They are applied at 92e4d2c (current head): the VALID verdicts edit proposal.md, the NOTED verdicts are recorded in the new openspec/changes/prefer-triad-in-project-new/clarifications.md, and the DISMISSED ones are recorded here only. Sections named below are those of the revised proposal.md.

Counts: 13 concerns raised (Product Advocate 4 plus a lower-impact scenario, Systems Architect 4, Adversary Engineer 5 plus smaller issues). 11 VALID applied, 6 NOTED to clarifications.md, 3 DISMISSED.

VALID (applied to proposal.md)

Verdict Raised by Ruling, in brief Landed in
V1 Known obstacles checked before the question and named beside the Triad PA1 HIGH, SA4 LOW, AE1 HIGH assembly-root form ^[A-Za-z][A-Za-z0-9]*$ pinned (contracts/repository-naming.yaml:345 @ openRepoShape 1a9fc537) and stated in plain words; "CamelCase" dropped everywhere; the name, openRepoShape on PATH and the parent directory are read before the question and named beside the Triad; a Triad answer with an obstacle exits 2 before the organisation and visibility prompts and before any network; openRepoShape's validator stays authoritative and its other refusals keep its own status; a terminal --dry-run with an obstacle exits 2; no --into named for a parent the person did not type; Preview reworded for both answers What Changes: first bullet, "Where the question sits", "Known obstacles are checked before the question and named beside the Triad", "Names"; Capabilities (Preview); Impact, tests/test_project.py; Questions Resolved, OQ-4
V2 --workflow and the second offer PA3c/d MEDIUM, SA2 MEDIUM, AE2 HIGH project keeps its own advisory in every case D2 covers; it neither suppresses nor coordinates with setup-openspeckit's bootstrap advisory, a second ratified form; the double offer is recorded with its three cases (answer 2 then n exits 130; --yes --workflow prints two advisories; a -wip creation is still advised by setup-openspeckit); current behaviour cited at workBenches d86ba59, merged advisory at 9fbe609c; real suppression is workBenches' follow-up What Changes, "--workflow"; Out of Scope (suppression item); Impact, "Handoffs" and tests/test_project.py (echoing fake setup-openspeckit)
V3 Delivery to onp is a tracked handoff PA2 HIGH, AE smaller (task 5.6 tick) on merge this lane opens exactly one opensoft/workBenches issue to lane project-command carrying the merge sha and the merged project file's sha256 (plus the V2 follow-up); its link goes with the merge sha on #3, saying onp and new-project.sh offer nothing until the pin moves; opening an issue edits nothing the claim holds Why (routing paragraph); Impact, "Handoffs"
V4 The organisation and visibility prompts PA4 MEDIUM, SA3 MEDIUM, AE4 MEDIUM the question's rules: full words, case-insensitive, listed private, public, internal, no numbers, no default; ask once more then exit 2; end-of-file exits 2; interrupt 130; every refusal says nothing was created; organisation checked before the visibility prompt; one stdout line restates name, organisation and visibility and names a public answer's consequence; the --workflow refusal (today project:242-244) is hoisted only when the question will be asked; new() computes "will the question be asked" once What Changes, "The organisation and visibility prompts" and "Where the question sits"; Impact, project (design constraint) and tests/test_project.py
V5 Prompt stream and the pty harness SA1 HIGH the question's lines go to stdout; its prompt goes through ask(), which at a terminal CPython writes to stderr (bpo-1927); tests never pin a prompt's stream; pty helper respecified (os.openpty() for stdin and stdout, stderr on a pipe, start_new_session=True, select loop, EIO or empty read as end, answer after prompt, repeated SIGINT, \r\n normalised, timeout, no pty.fork); in-process tests patch input and isatty What Changes, "One helper decides"; Impact, project (design constraint) and tests/test_project.py
V6 Guarded stderr write AE3 MEDIUM closed stderr, EPIPE, ENOSPC swallowed; never written to stdout; exit status never changes; ASCII-only text; tests for 2>&- and 2>/dev/full What Changes, "The advisory cannot fail the run"; Impact, tests/test_project.py
V7 "Created a single repository" defined AE5 MEDIUM a successful bench creation counts (directory plus .project.json) whether or not the generator initialised Git; a creation-time rule that does not contradict openRepoShape's inspection-time not-a-repository silence What Changes, "What counts as creating a single repository"; Out of Scope (exemptions paragraph, "At inspection")
V8 Declining PA3a, PA3b (a) the single line names single-repository.yaml in one clause, text only; (b) no setting stops the question (packet spec.md:99-100, design A3) (a) What Changes, first bullet; (b) Rules, "No setting stops the question"
V9 Agents at a pty PA lower-impact scenario, AE4 last point AGENTS.md and README tell an agent to offer the Triad in conversation, then run project new with a choosing flag, never answering the question for a person; residual risk to clarifications Impact, README.md and AGENTS.md; What Changes, "README, AGENTS.md and tests"; clarifications.md (N2)
V10 Workspace exemption at creation AE smaller only the -wip name form is read at creation; the workspace-configuration form is not, since a path that does not yet exist is no declared fact and creation reads no YAML What Changes, "Silent for a workspace name"
V11 Small corrections AE smaller (a, b, c), PA (d), AE2 (e) (a) AGENT_PROTOCOL_ROOT dropped from the test plan, CI kept; (b) the impossible "--yes the person typed" after a single answer reworded; (c) the fake openRepoShape is asserted to be what PATH resolves and GH_TOKEN, GITHUB_TOKEN, OPENREPOSHAPE_* are dropped; (d) one sentence of value to the person in Why; (e) y/yes take the Triad, n/no the single repository (a), (c) Impact, tests/test_project.py; (b) Rules, "No repository is created that the person has not confirmed"; (d) Why; (e) What Changes, "The answers"

NOTED (recorded in clarifications.md)

Verdict Raised by clarifications.md section
N1 The interactive double question under --workflow PA3c, AE2 "The interactive double question under --workflow"
N2 Agents at a pty look like people PA lower-impact scenario, AE4 "Agents at a pseudo-terminal look like people"
N3 Ctrl-C during openRepoShape AE smaller "Ctrl-C while openRepoShape runs"
N4 Is the creation question owed for a -wip name? alignment review (carried) "Is the creation question owed for a -wip name?"
N5 Prompt stream differs between harnesses SA1 "The prompt's stream differs between harnesses"
N6 Exit codes on the Triad path SA4, AE1 "Exit codes on the Triad path"

DISMISSED (recorded here only)

Verdict Raised by Reason
D1 Omit project's advisory when --workflow will run SA2 (alternative; AE2's option (a) is the same) loses D2 whenever setup-openspeckit refuses before its advisory step, which it can: its main() runs ensure_safe_specify_destination, preflight_source_roots and resolve_project_shape first (workBenches d86ba59, setup-openspeckit:4059-4078); V2 is the chosen reading
D2 Measurability PA ("Where the proposal is solid") the advisory records nothing by ratified design ("it records nothing, and no later act reads whether it was given", packet spec.md:107-108); the tests are the evidence
D3 Windows and locale PA ("Where the proposal is solid") no regression: where no terminal is detected the flag-and-advisory path runs, and the answers are ASCII digits and words

Two points of precision in applying the verdicts

  • openRepoShape's passed-through status. Its name check exits 1 (die(..., 1), setup-project.py:1532-1535), but its die() defaults to 2 (setup-project.py:224-225): 18 of its 22 die() calls use that default, and four pass 1 (the name check, two --family naming checks, and a declined typed confirmation, :1622). The proposal therefore says openRepoShape's refusals pass through "with openRepoShape's own exit status (1 for its name check; most of its other refusals exit 2)", and promises no particular status for them; clarifications.md (N6) says the same.
  • /dev/full is Linux-only. CI runs ubuntu and macOS (.github/workflows/tests.yml), so the test plan keeps 2>&- and 2>/dev/full and has the macOS job cover the same guard with a stderr pipe whose read end is closed (EPIPE, which V6 names).

The council reports, verbatim

Product Advocate council report (verbatim, sha256 004b82ff...)

Council: Product Advocate, prefer-triad-in-project-new

Lane openRepoProject-1, phase 4 of /opsx:propose. Read-only review of proposal.md at worktree HEAD b8d566b (PR #5, issue #3, openxFactory task 5.6). This review works within OQ-1 (the change is creation-only).

Sources: the openxFactory packet at e6380965, openRepoShape at 1a9fc537, workBenches at 9fbe609c and origin/main d86ba59, project at b8d566b, and issue #3.

Concerns, ranked by impact

1. HIGH: for many ordinary names, the pre-selected answer always ends in a refusal, and the refusal comes late. The proposal's description of the name rule is also wrong, and it can be read two ways.

Concern: For any name with a hyphen, an underscore or a leading digit, pressing Enter leads the person through four steps before openRepoShape refuses: the organisation prompt, the visibility prompt, openRepoShape's self-bootstrap clone, and its preflight. The proposal's "CamelCase" wording would also turn away valid names.

Evidence:

  • Proposal, "Names": "The question states that constraint beside the Triad. project adds no second validator: a Triad answer for a name such as my-app reaches openRepoShape, and the refusal is openRepoShape's own."

  • project:206 accepts [A-Za-z0-9][A-Za-z0-9_-]*.

  • openRepoShape's actual rule is the naming-policy form project-leg/assembly, ^[A-Za-z][A-Za-z0-9]*$ (contracts/repository-naming.yaml:345, 1a9fc537). It is not "CamelCase".

  • I ran openRepoShape's own validator at 1a9fc537, as setup-project.py step (4) runs it:

    Name Result
    atlas passes (exit 0)
    Atlas passes (exit 0)
    MyApp passes (exit 0)
    my-app fails (exit 1)
    my_app fails (exit 1)
    2fa fails (exit 1)
    Atlas-Install fails (exit 1)
  • Order in setup-project.py main (lines 1836-1852): self_bootstrap (the launcher "clones the standard into a temporary directory first"), then preflight, resolve_org, resolve_project_and_elector, and only then check_names. check_names dies with exit 1.

  • "States that constraint" has two readings:

    • The question always prints a fixed sentence.
    • The question names the given name when it does not fit. That needs a reading the proposal says it does not add.

Impact:

  • Two implementers will build different questions.
  • A person who types project new my-app and presses Enter does not get the safe default they expected:
    • They answer two more prompts and may wait for a network clone and preflight offers.
    • Then they get exit 1 and must start again.
    • Nothing is created, so this is safe, but the first run fails.
  • "CamelCase" wording makes a person with atlas think they must rename or choose single, when atlas is valid.

Suggestion: In "Names", replace "The question states that constraint beside the Triad." with:

Beside the Triad, the question states openRepoShape's assembly-root name rule in plain words: a letter first, then letters and digits only, with no hyphen, underscore, dot or space (project-leg/assembly, ^[A-Za-z][A-Za-z0-9]*$, contracts/repository-naming.yaml at 1a9fc537). It does not say "CamelCase". When the given name does not match that pattern, the Triad line names it, for example "my-app cannot be a Triad name; a Triad needs a name such as MyApp". This reading only selects a sentence. The Triad stays first and the default, project refuses nothing, and the verdict stays openRepoShape's (setup-project.py step (4), exit 1, passed through).

Add a test: a hyphenated name sees the name-specific line, and the fake openRepoShape's exit 1 is passed through.

If the lead reads "no second validator" as ruling out even a message-only reading, the fixed sentence must still state the accurate rule. The README must then say that a Triad answer for such a name is refused only after the organisation prompt, the visibility prompt and openRepoShape's preflight.

2. HIGH: onp will not offer the Triad until the workBenches pin is advanced, nobody is tracking that advance, and task 5.6 will be ticked anyway.

Concern: The estate's documented creation command does not change until a workBenches pin advance happens. No tracked item asks for it, and the lane that owns it has no register row. Task 5.6, and then the parent's archive, will close on this repository's merge sha.

Evidence:

  • Proposal, Why: "they carry this change's behaviour only once workBenches advances that pin on its owner's act."
  • Proposal, Impact: "Handoffs this change does not perform: workBenches advances its pinned project artifact on its owner's act (lane project-command); the realization's merge sha is posted on issue project new offers the Triad first (openxFactory prefer-triad-project-shape, task 5.6) #3, and openxFactory's archiving actor checks task 5.6's box citing it".
  • Issue project new offers the Triad first (openxFactory prefer-triad-project-shape, task 5.6) #3 body: "Lane project-command has no row and no log in the register."
  • workBenches specs/003-project-command/tasks.md has one pin task, [x] T001 Pin and install the upstream executable, already done. There is no open workBenches issue about openrepoproject-pin.
  • config/openrepoproject-pin.json is still at a0407904.
  • workBenches docs present onp as the way people create projects:
    • docs/spec-driven-development.md:211: "Use onp to create new projects from benches".
    • docs/setup-script-ui.md:154: "onp (opensoft new project) - Quick project creation".
  • Packet tasks.md 6.1 archives on "merged plus green realization evidence".

Impact:

  • The doctrine records 5.6 as realized and the parent change archives.
  • Every person who creates projects with onp or new-project.sh still runs a0407904: no question, no advisory.
  • The behaviour reaches users on an unnamed act by an inactive owner, which in practice may be never.

Suggestion: Add to Impact, under handoffs:

On merge, this lane opens one issue on opensoft/workBenches addressed to lane project-command. It asks for config/openrepoproject-pin.json to move to the merge sha, and states that sha and the merged project file's sha256 so the advance is mechanical. This lane posts the issue's link with the merge sha on #3. The comment says that onp and new-project.sh run a0407904 and offer nothing until that pin moves, so the 5.6 tick reads as "realized in project new; reaches onp with workBenches#N".

Opening an issue edits nothing the claim holds. In README "Legacy onp…", add "from the pinned release that includes it".

3. MEDIUM: a person who declines meets the preference again in the same run, is never told how to stop meeting it, and the proposal does not say that "stop asking" is not available.

Concern:

  • The interactive path never names single-repository.yaml.
  • --workflow repeats the advisory within one run.
  • Nothing tells the person who always wants a single repository that no "stop asking" setting exists, or why.

Evidence:

  • Proposal: the question states "its posture beside it: preferred, not required; elective; confers nothing", with no exits named. Also: "A creation that went through the question prints none, because the question was the offer."
  • Only the flag path names the record: "single-repository.yaml as the way to stop meeting it".
  • --workflow "choose[s] nothing", so it still asks. project:265-268 then runs setup-openspeckit --repo <dest> on the new single repository in the same terminal.
  • setup-openspeckit's give_triad_advisory (9fbe609c:2344ff) prints its two lines on that repository. At a terminal it then asks TRIAD_QUESTION: "Press Enter to continue (s = show the steps; n or Ctrl-C stops…)". None of the five exemptions covers a repository created seconds earlier, and the packet closes the list ("the list above is the whole list", spec.md:204).
  • On the flag path with --workflow, the person gets project's two warning: lines and then setup-openspeckit's near-identical pair.
  • The requirement is titled "A person starting work outside a Triad is advised once" (spec.md:90).
  • On "stop asking":
    • The packet requires "the creating tool or agent SHALL offer the Triad first" (spec.md:99-100).
    • Design A3 rejects "Per-person suppression in the person's workspace manifest" (design.md:372-381).
    • So the ratified text forbids a "stop asking" setting, and the proposal never says so.

Impact:

  • The person who declined most deliberately, by answering 2, gets the least information. They then meet the advisory in every later agent session and bootstrap in that repository, without having been told of the one ratified way to stop.
  • project new foo --workflow asks about shape twice within seconds.
  • With no statement that "stop asking" is forbidden, a later issue or implementer will likely add a PROJECT_NO_TRIAD-style switch that the doctrine rejects.

Suggestion: Add a bullet "Declining" to What Changes:

(a) The single-repository line of the question names single-repository.yaml as the way the new project can later stop meeting the preference. This is text only: project writes nothing.

(b) No setting stops the question. The ratified text requires the offer at every interactive creation (packet spec.md:99-100) and rejects per-person suppression (design A3). A person who always wants a single repository answers 2, or chooses by flag (--bench, --type); either skips the question and gets the stderr advisory instead.

(c) With --workflow, setup-openspeckit gives its own advisory on the new repository: at a terminal, its Enter-to-continue question; otherwise two warning: lines. The README says so and a test pins the order.

For a flag-chosen creation with --workflow, the proposal names its choice between two options:

  • project keeps its own lines. The person reads the advisory twice in one run, against "advised once".
  • project leaves the advisory to setup-openspeckit, which gives it at the start of its run. The design must confirm that setup-openspeckit cannot refuse on a fresh repository before advising. --workflow's missing-setup-openspeckit case already refuses before creation.

Whichever it picks, the double encounter on the interactive path comes from the doctrine itself. Carry it to the follow-up and to workBenches as a note, not into this change.

4. MEDIUM: the prompts that pressing Enter leads to are not defined, and the only way back from an accidental Enter is an interrupt.

Concern: The question's answers are fully specified, but the organisation and visibility prompts that follow a Triad answer are not. This is the path every reflexive Enter takes.

Evidence:

  • Proposal: "project asks for the organisation, checked by today's organisation check (project:223), and then for the visibility, one of public, private or internal, with no default."
  • Today's check refuses at once with "Invalid GitHub organization name." (project:223-224). An empty answer would therefore get that message.
  • ask()'s end-of-file falls through to "Cancelled." with exit 130 (project:1001-1003). The question itself refuses end-of-file with exit 2. One flow would have two end-of-file outcomes.
  • The proposal leaves undefined:
    • whether visibility is typed or numbered;
    • case handling (Private);
    • re-ask versus refuse on bad input;
    • how a person who meant "single" gets back.

Impact:

  • Two implementers will differ in four places: empty input, invalid input, end-of-file, and visibility input form.
  • The common mistake (Enter pressed by habit, a single repository wanted) has no stated exit. The person sees an organisation prompt, may type an empty answer, and gets a misleading "Invalid GitHub organization name." with exit 2.

Suggestion: Append to "The Triad answer and its prerequisites":

The organisation and visibility prompts follow the question's answer rules:

  • Surrounding whitespace is stripped.
  • Visibility is typed as public, private or internal, compared case-insensitively, with no default.
  • An empty or unrecognised answer is asked again once, then refused with exit 2.
  • End-of-file refuses with exit 2.
  • An interrupt exits 130 with Cancelled..
  • Every refusal ends with "nothing was created".

The organisation prompt's first line says that Ctrl-C cancels with nothing created, and that a single repository is answer 2 on a re-run.

Add tests for an empty organisation, an invalid visibility, and end-of-file at each prompt.

Where the proposal is solid

  • Problem clarity: the Why is complete for the doctrine but says nothing about what the person at project new gains. Add one sentence: "Creation is the one moment the Triad costs nothing extra. Converting later is adopt-project.py, a deliberate migration, and today the Triad is reached only by knowing to type --shape --org --visibility."
  • Scope: right-sized. One question, one stderr pair, no new flag. The MVP is clear, and what the inspection follow-up needs is recorded under Out of Scope.
  • The no-unconfirmed-repository guarantee: holds. An organisation and visibility with no defaults, then openRepoShape's "Type yes to continue", mean that repeated Enter can never create three repositories.
  • Measurability: not measurable in use, by design (the advisory "records nothing"). The pinned test cases are the evidence, which is acceptable.
  • openRepoShape on PATH: explicit. The question says it is missing, and a Triad answer refuses at once.
  • Speckit handoff: explicit (one specs/002-<slug>/).
  • -wip exemption: well defined (pattern and commit pinned). The open question is correctly carried to clarifications.
  • Non-English locales: the answers are Enter, 1 and 2, so they work in any locale. Optionally, keep the new text ASCII, as setup-openspeckit does (9fbe609c:108-110, "ASCII only, so no locale can turn the advisory into an encoding failure"). The question goes to strict-encoded stdout.
  • Windows terminals: no regression. Where isatty() is false (Git Bash/mintty), the run takes the flag path with the advisory, and today's ask() already refuses there. The pty tests are POSIX-only, as stated.

Missing scenario, lower impact

  • Agents and scripts driving project new through a pty (for example, a tmux lane):
    • Gap: the helper cannot tell such an agent from a person. The agent meets a new prompt between Project name: and Type yes, and Enter takes the Triad.
    • Fix: add to Impact a line for AGENTS.md (this repository has one; it is not in Impact) and for the README: "An agent offers the Triad in conversation (shared protocol, openspec-speckit-workflow.md:52-54), then runs project new with a choosing flag. It never answers the question for a person, as openRepoShape's AGENTS.md already says of its typed confirmation (design D2)."

Side effects of this review

  • git fetch -q origin in /workspace/projects/workBenches updated its remote-tracking refs only. No working-tree or branch change.
  • openRepoShape 1a9fc537's scripts/ and contracts/ were extracted into scratchpad/pa-shape-1a9/ to run its naming validator.
  • Nothing else was written.
Systems Architect council report (verbatim, sha256 3e1529af...)

Lane: openRepoProject-1

Council, Systems Architect: prefer-triad-in-project-new @ b8d566b

Verdict: this can be built in the single project file with no new dependency and no change to ask(). There are four concerns. One is HIGH: the test harness, the riskiest piece. One MEDIUM is a behaviour gap the proposal does not name (--workflow). The rest are wording and design constraints.

Concerns, ranked by impact

1. HIGH: the question's prompt does not reach stdout at a terminal, and the planned pty harness assumes it does

Concern: At a real terminal, input(prompt) writes its prompt to stderr. Tests built as the proposal describes them (stderr captured apart, pinned fixtures, the question "shown on stdout") will fail or flake on the four-job ubuntu/macOS × 3.10/3.12 matrix. That holds unless the harness is designed around this and around the platform differences between ptys.

Evidence:

  • What the proposal says.
    • "The helper decides whether the question, the interactive form of the offer, is shown on stdout."
    • Impact: "a new pty-based helper ... (stdin and stdout on a pseudo-terminal, stderr captured apart)".
    • "expected prompts, refusals and advisory lines pinned as fixtures".
  • Probe of project:149 (return input(prompt + " ").strip()). Setup: Python 3.12.3 on Linux, with and without -I; stdin and stdout on os.openpty(), stderr on a pipe.
    • print() output came back on the pty.
    • Both input() prompts came back on the stderr pipe: b'QUESTION-PROMPT:SECOND:'.
    • Why: CPython's no-readline path writes the prompt to stderr (bpo-1927), and a script never loads readline, so this applies under -I too.
  • Probe of the in-process case. The same input() with sys.stdout swapped by redirect_stdout, as an in-process module.main() test does, wrote the prompt to the captured stdout. So the same prompt lands on different streams in the two harnesses.
  • Alignment premise. The alignment review assumed the opposite: alignment-qa-lead.md:91 says "input() writes its prompt to stdout".
  • Prior art: setup-openspeckit's run_on_terminal (devBenches/devcontainer.test/test-openspeckit-bootstrap.sh:3794-3814 @ 9fbe609c).
    • It puts stdin, stdout and stderr all on the pty and uses start_new_session=True.
    • It writes each answer only after the question appears.
    • It sends SIGINT again every second, because "a signal landing between the prompt and the blocking read is absorbed without interrupting that read".
    • It runs only in the Linux devcontainer. No pty-driven test in the estate runs on macOS.
  • pty differences the harness has to absorb:
    • ONLCR turns \n into \r\n, and typed answers are echoed into the master stream.
    • When the child exits, Linux ends a master read with EIO. BSD-family ptys, macOS included, can return an empty read instead (see bpo-26228, CPython's pty.spawn hang on BSD).
    • Writing ^C to the master signals nothing, because a Popen child has no controlling terminal.

Impact: HIGH. The CI matrix gates every merge.

  • Tests written to the proposal's words assert two things that are false at a terminal: that the question is on stdout, and that stderr holds only the advisory or REFUSED:. Those tests fail on their first run.
  • An interrupt test written naively flakes on the slower macOS runners. It gets the signal's own status (-2) or a hang instead of 130.
  • A fix chosen under that pressure, such as writing the question with sys.stdout.write, quietly makes the question behave unlike every ask() prompt.

Suggestion:

  • Replace the harness sentence in Impact/tests with: "a pty helper beside run_cli:
    • os.openpty() for stdin and stdout, and a separate pipe for stderr;
    • a Popen child started with start_new_session=True;
    • a select loop that drains both the master and the stderr pipe until the child exits, treating both EIO and an empty read as end of output;
    • each answer written only after its prompt has appeared on stderr;
    • SIGINT sent with send_signal and repeated until the child exits;
    • \r\n normalised and echoed input ignored;
    • every run bounded by a timeout."
  • Correct "shown on stdout" to: "the question's lines go to stdout; its prompt, like every prompt ask() shows at a terminal, goes to stderr".
  • Add this design constraint. The question's prompt goes through ask(), so it behaves exactly like today's prompts. Pty fixtures pin prompts on stderr. In-process tests patch input as well as isatty, and pin no prompt stream.

2. MEDIUM: --workflow runs setup-openspeckit, which gives its own Triad advisory and question on the repository just created

Concern: project's own silence rules hold, but the --workflow follow-up advises again by setup-openspeckit's rules. In such a run, "the question was the offer" and "silent for a workspace name" fail for the run as a whole.

Evidence:

  • How project runs the follow-up. project:242-245,265-269 run setup-openspeckit --repo <destination>, which inherits project's stdin, stdout and stderr.
  • What setup-openspeckit does on every run (9fbe609c:3966-3968). It calls give_triad_advisory() once, before its first write.
    • On a single repository with a person at the terminal, it prints TRIAD_ADVISORY_LINES on stdout and asks "Press Enter to continue (s = show the steps; Ctrl-C stops ...)" (:2357-2363).
    • Otherwise it writes two warning: lines to stderr (:2364-2370).
  • Its workspace exemption reads the origin name or workspace.yaml (:2200-2205). A freshly generated brett-wip has no origin, so it is advised unless workspace.yaml names it.
  • What the proposal says.
    • "A creation that went through the question prints none, because the question was the offer."
    • A -wip creation "gets neither the question nor the stderr advisory".
    • The only --workflow test fakes a failing setup-openspeckit ("the stderr advisory after a --workflow failure").

Impact: MEDIUM, and only for runs with --workflow:

  • In onp myapp --workflow at a terminal, the person answers 2, and seconds later setup-openspeckit advises again and asks a second question.
  • A flag-chosen or CI --workflow run prints four warning: lines back to back: project's two, then setup-openspeckit's two, worded differently. The proposal itself records that wording divergence.
  • A -wip creation with --workflow is still advised.
  • Because the follow-up is faked in tests, none of this is seen before release.

Suggestion: Name the interaction in What Changes and decide it explicitly.

  • Smallest consistent option. Add: "When --workflow runs, setup-openspeckit's bootstrap advisory follows. It is a ratified form of its own. project neither suppresses it nor coordinates with it, and the second offer is recorded as known."
  • Alternative. project omits its own stderr advisory when --workflow will run. This loses the advisory whenever setup-openspeckit refuses before give_triad_advisory, so say so if it is chosen.
  • Real suppression needs a signal from project to setup-openspeckit, which is a workBenches change. Record it in Out of Scope as a follow-up for that owner.
  • Test. Add one test with an echoing fake setup-openspeckit that pins whichever behaviour is chosen.

3. MEDIUM: the placement bullet misstates today's refusal order, and handing the answers over "as if given by flag" drops a check

Concern: Three placement details need restating, so that an implementer cannot satisfy one bullet by breaking another.

Evidence:

  • The --workflow check is not where the proposal says it is.
    • The proposal says the question comes "after every pre-flight refusal today makes before a path is chosen: ... and --workflow without setup-openspeckit on PATH". It also says "Invocations that skip the question keep today's order of refusals".
    • In the code, the --workflow check runs after the path is chosen: project:242-244 follows the shape refusals (:219-228) and choose() (:239).
    • choose() can itself prompt "Select a generator number" before that refusal.
    • So the check must move ahead of the question only when the question will be asked, and stay where it is otherwise.
  • A typed visibility is not checked.
    • argparse checks the --visibility flag against choices (project:939), but the shape branch only checks that a value is present (project:221).
    • A typed Private or priv put into args.visibility passes project and reaches openRepoShape. openRepoShape refuses it (setup-project.py:576-578), but only after its launcher has fetched setup.sh from GitHub.
    • The proposal names the three values but not what an invalid or empty answer does.
  • Triad prerequisites that need no answer are checked late.
    • "Shape creation requires an existing --into parent directory." (project:225-226) is checked after the Triad answer.
    • If it fires after the organisation and visibility prompts, the person typed both for nothing.
    • The message names a --into they never typed: the default parent is ~/projects (project:210).
  • End-of-input at the new prompts is unspecified. Through ask(), end-of-input at the organisation or visibility prompt becomes Cancelled. and 130 (project:1001-1003). At the question it is a refusal with exit 2.

Impact: MEDIUM. All of it can be built:

  • The question goes between project:217 and :218.
  • A Triad answer sets args.shape and falls into :218-235.
  • The destination at :213-215 is unchanged, because --family is a choosing flag and so is absent.

As written, though, two bullets contradict each other on the --workflow check, and an unchecked typed value can reach openRepoShape, which runs over the network.

Suggestion:

  • Placement bullet. Reword it to: "...an invalid name, parent together with --into, an existing destination, and, for an invocation that will be asked the question, --workflow without setup-openspeckit on PATH. Today that check comes after the path's own refusals (project:242-244), and it keeps that place when the question is skipped."
  • Triad answer. Add: "A Triad answer first checks what needs no answer: that openRepoShape is on PATH and the parent directory exists. It then asks for the organisation and the visibility. A typed visibility is checked against the flag's three values. Empty or unrecognised input is asked again once and then refused with exit 2, as at the question. End-of-input at either prompt exits [2 or 130, stated by design]."
  • Implementation. Compute "will the question be asked" once in new(). Use that single predicate for the moved --workflow check, for the question and for the advisory.

4. LOW: the default answer sends every hyphen, underscore or digit-led name through three answers and a network run of openRepoShape to an exit-1 refusal

Concern: "project adds no second validator" makes the name refusal arrive late and remotely. Yet the proposal already pins one pattern from the same naming contract.

Evidence:

  • The two name rules.
    • project:206 accepts my-app, my_app and 2048game.
    • openRepoShape's assembly-root form is ^[A-Za-z][A-Za-z0-9]*$ (contracts/repository-naming.yaml:345 @ 1a9fc537).
    • Every form in that file except workspace starts with a letter, and none admits _.
    • A hyphenated root produces leg names that no form admits.
  • Where openRepoShape refuses, and with what status.
    • check_names is step (4) (setup-project.py:1852).
    • It runs after preflight (:1848), which runs gh auth status and may offer gh auth login.
    • It refuses with die(..., 1) (:1532-1535): exit 1, not 2.
  • The network fetch before it. Before any of that, the launcher fetches setup.sh from GitHub at ${OPENREPOSHAPE_REF:-main} (openRepoShape:31,549). It skips the fetch only if a checkout sits beside it or OPENREPOSHAPE_SETUP_SH is set.
  • The existing pin. The proposal already pins ^[a-z0-9]+(?:-[a-z0-9]+)*-wip$ from the same file at the same sha.

Impact: LOW. Nothing is created. But pressing Enter for my-app costs three answers (the question, organisation, visibility), a GitHub fetch and possibly a gh login offer. Only then comes a refusal that project could predict offline. The spec must not promise exit 2 for this refusal.

Suggestion: Take one of two routes.

  • Keep the decision, and record that this refusal exits with openRepoShape's status, 1.

  • Or pin the root form beside the -wip form (same file, same sha), and use it only to:

    • state, beside the Triad, that openRepoShape will refuse this particular name;
    • refuse a Triad answer before the organisation prompt, with exit 2 and openRepoShape's rule quoted.

    This only rejects names openRepoShape is certain to refuse. It is not a second schema for what is accepted.

Dimensions that are solid

  • Architecture fit.
    • No new flag. ask(), confirm() and choose() are unchanged.
    • The new helper sits beside ask() (project:146), and both the question and the advisory use it.
    • Exit codes keep today's rules: Refused gives 2 and an interrupt gives 130 (project:995-1003).
    • End-of-input at the question is caught around a single ask() call, so ask() itself does not change.
  • Advisory placement.
    • The advisory belongs to the bench path only, between the .project.json write (project:259-264) and the follow-up loop (:265).
    • The Triad path prints none, so project never needs to know when openRepoShape has finished. What "single repository" would mean on the shape path never comes up.
  • Delivery to onp.
    • onp → new-project.sh (exec ... project new "$@", which adds no flags) → launcher → setup-project-command.py --exec-owned.
    • That last step exec()s the pinned artifact in the same process under python3 -I (setup-project-command.py:457-480), so the terminal reaches project unchanged.
    • The question therefore reaches onp users once the pin advances. The proposal says the pin advance is workBenches' act.
  • Tech stack.
    • project needs only isatty, os.environ and re.
    • Because the -wip rule is a literal, the creation path reads no YAML and needs no PyYAML.
    • Tests need os.openpty, select and signal, which are stdlib on Linux and macOS for 3.10 and 3.12.
    • Avoid pty.fork and os.forkpty: 3.12 warns about forking a threaded process. termios is not needed.
  • -wip pinning. The literal matches contracts/repository-naming.yaml:412 and setup-openspeckit :103. Cite the source sha in a comment and pin the literal in a test. Drift risk is low, because the contract states that adding this form changed no existing name's classification.
  • Performance.
    • The question and the advisory read only the arguments, CI and PATH (two shutil.which calls). They touch neither git nor the network.
    • The only network access is openRepoShape's run on a Triad answer. That is today's --shape path; the change makes it the default answer.

Probes ran on Python 3.12.3 only. 3.10 is not installed here; the prompt-to-stderr path is long-standing CPython behaviour (bpo-1927).

Adversary Engineer council report (verbatim, sha256 abd43761...)

Council: Adversary Engineer, prefer-triad-in-project-new

Target: openspec/changes/prefer-triad-in-project-new/proposal.md at worktree HEAD b8d566b (PR #5).
Sources read: project, tests/test_project.py, the canonical specs, and the README at that HEAD. openRepoShape at 1a9fc537 (the openRepoShape launcher and setup-project.py). setup-openspeckit at workBenches 9fbe609c and d86ba59. The workBenches forwarders, launcher and pin on origin/main. The packet at openxFactory e6380965.
Verified by running Python 3.12.3 -I: the stderr edge cases in concern 3, and the interrupt behaviour in the last list.

Ranked by impact. All five stay inside the settled scope.


1. The pre-selected Triad answer runs into refusals that project could foresee locally, and the name refusal only arrives after network and gh steps — HIGH

Concern. Enter takes the Triad. For names, parents and machines that project already knows will fail, that default ends in a refusal, and only after two more prompts.

Evidence.

  • Name.
    • project:206 accepts [A-Za-z0-9][A-Za-z0-9_-]*.
    • The assembly-root form in openRepoShape contracts/repository-naming.yaml@1a9fc537 is ^[A-Za-z][A-Za-z0-9]*$.
    • The proposal copies the -wip regex from that same file ("Silent for a workspace name"). It also says "project adds no second validator" ("Names"). These two positions contradict each other.
  • Where openRepoShape's name refusal actually happens.
    • The launcher fetches setup.sh at ${OPENREPOSHAPE_REF:-main} (openRepoShape:31), not at 1a9fc537. It then self-bootstraps a clone.
    • setup-project.py _main then runs, in order:
      1. preflight: is gh installed, gh auth status, and interactive offers to install gh or log in (:1110-1140, :982-1007).
      2. resolve_org.
      3. resolve_project_and_elector, which calls gh api user. A Triad answer never carries --elected-by, so this call always happens.
      4. check_names → die(..., 1), which exits 1, not 2 (:1525-1534).
    • So for my-app, the default path is: Enter, type an org, type a visibility, a network fetch and a clone, possibly a gh auth login, and then exit 1.
    • Offline, the person gets "could not fetch setup.sh" and exit 2. The name is never mentioned.
  • Parent directory.
    • The default parent is projects_dirs()[0] (project:210).
    • The bench path creates it (project:252).
    • The shape path refuses "Shape creation requires an existing --into parent directory." (project:225-226). That message names a flag the person never typed.
  • openRepoShape missing. By the proposal's own rule, Enter means an immediate exit 2.
  • Dry run.
    • Today, project new App --dry-run at a terminal exits 0 and prints the generator plan. The bench dry run never needs the parent (test_dry_run_creates_no_parent).
    • With Enter it now exits 2 when openRepoShape or the parent is missing. When it succeeds, it prints no generator.
    • Canonical "Delegate project creation" → Scenario "Preview" ("prints the selected generator and destination without writes") is false for the Triad answer.
    • The Capabilities paragraph does not say that the MODIFIED block restates Preview.

Impact. HIGH.

  • onp my-app, project new my_tool and project new 2fa-proxy are all accepted by project. For each one, the pre-selected answer is a guaranteed refusal after two prompts and network round-trips.
  • The feature ships a default whose most likely outcome for any non-CamelCase name is a wall.

Suggestion. Add a "Triad prerequisites" requirement to the spec delta:

  • Before asking, project checks the obstacles it can see locally:
    • openRepoShape is on PATH;
    • the resolved parent exists;
    • the name matches the assembly-root form, copied from contracts/repository-naming.yaml@1a9fc537 with a citation, exactly as the -wip pattern is.
  • The question names the specific obstacle beside the Triad. The Triad stays first and the default (D2).
  • A Triad answer with an obstacle refuses at once, with exit 2:
    • before the organisation and visibility prompts;
    • before any network access;
    • naming the next command: project new <CamelName> …, --into <existing dir>, or answer 2.
  • Restate "Preview" in the MODIFIED block for both answers.
  • Add scenarios:
    • a terminal dry run with Enter and no openRepoShape → exit 2, nothing written;
    • a hyphenated name answered Triad → local refusal, no fake openRepoShape invoked.
  • If the local name check is rejected anyway, the hyphenated-name test must pin openRepoShape's exit 1.

2. --workflow re-offers the Triad through setup-openspeckit, which breaks "advised once", and n means opposite things in the two consecutive questions — HIGH

Concern. With --workflow, project runs setup-openspeckit on the new repository. setup-openspeckit is itself a ratified advisory surface. It advises again, or asks again, right after project's own question or advisory.

Evidence.

  • How project runs the workflow. project:242-245 and :265-269 run setup-openspeckit --repo <destination> through execute() on the inherited terminal.
  • What setup-openspeckit does.
    • Its main() calls give_triad_advisory before the first write (9fbe609c :3968).
    • It treats the fresh bench directory as a non-exempt single repository.
    • Its workspace test reads only origin (is_workspace_repository, :2200-2205), and a fresh bench directory has no origin. So project new brett-wip --workflow: project stays silent by name, then setup-openspeckit advises.
  • What its question means now. At a terminal it asks: "Press Enter to continue (s = show the steps; n or Ctrl-C stops before anything is written)". Since workBenches d86ba59 (#140, D004), n/no stop the run by SIGINT.
  • What the proposal says.
    • "A creation that went through the question prints none, because the question was the offer."
    • The non-interactive advisory is printed "before any --workflow step".
  • The ratified rule. The requirement's title is "A person starting work outside a Triad is advised once".

Impact. HIGH. This hits a documented invocation: README project new MyApp ~/projects --bench flutterBench --type flutter --workflow.

  • Terminal, single answer.
    1. The person answers 2 and types yes. The repository is created.
    2. setup-openspeckit asks about the Triad again.
    3. Having just declined, the person types n.
    4. setup-openspeckit kills itself with SIGINT, and execute returns 130.
    5. project prints "Project created at …; workflow setup failed." and exits 130.
  • --yes --workflow. Two advisories are printed back to back, with the divergent wording the proposal itself records.
  • Grammar mismatch. At project's question, n is "unrecognised", asked again, then exit 2. At the very next Triad question in the same command, n means stop.

Suggestion. Add an "Interaction with --workflow" section, and decide it explicitly:

  • Option (a). When --workflow will run setup-openspeckit on a single repository, project gives no stderr advisory of its own. This is a static rule about the delegate, not a record of having advised, so the packet's "no later act reads whether it was given" is kept. The cost: if the workflow fails before setup-openspeckit's advisory, the person gets no advisory.
  • Option (b). Accept the double offer and document it.

Either way:

  • Define what y, yes, n and no do at project's question. Either align with the bootstrap or refuse them with a message that names 1/2. Do not leave them as "unrecognised".
  • Add tests for:
    • --workflow plus the single answer under the pty helper, with a fake setup-openspeckit that asks;
    • --yes --workflow, asserting exactly one advisory block from project, or none under option (a).
  • Cite d86ba59, not only 9fbe609c, as setup-openspeckit's current behaviour.

3. As specified, the stderr advisory can change stdout bytes and the exit status, which breaks two of the proposal's own Rules — MEDIUM

Concern. The proposal requires outcomes ("changes no stdout byte", "changes no exit status"). It specifies only a mechanism: "flushes stdout and writes the advisory to stderr". Inside project's error handling, that mechanism can be fatal.

Evidence.

  • The handler. project:995-1000, except (Refused, OSError) → print("REFUSED…", file=sys.stderr), exit 2. A failed advisory write is an OSError, so this handler catches it.
  • Stderr closed (2>&-), verified. sys.stderr is None, and print("warning: …", file=sys.stderr) writes to stdout, with exit 0.
  • Stderr unwritable (2>/dev/full), verified. The write raises OSError. The handler's own REFUSED print raises again, and the process exits 120.
  • Both sibling realizations hardened exactly this:
    • setup-openspeckit emit_advisory_line (9fbe609c:2284-2292): "no encoding or closed pipe can turn into a failure". It swallows AttributeError, LookupError, OSError and ValueError. TRIAD_ADVISORY_LINES is ASCII-only "so no locale can turn the advisory into an encoding failure that would change the exit status" (:108-110).
    • openRepoShape 1a9fc537's review round F1–F9 "made the advisory unable to crash a run or change an exit status".

Impact. MEDIUM. Consider project new App --type t --yes --workflow with stderr closed or full, for example under a daemon, cron or CI wrapper:

  • the repository exists;
  • project exits 2 or 120;
  • --workflow is silently skipped;
  • Created:/Next: is never printed.

With 2>&-, the advisory lands in the stdout that a calling script parses.

Suggestion. Add a requirement, "The advisory cannot fail the run":

  • write through one guarded helper;
  • skip the write when sys.stderr is None;
  • swallow OSError, ValueError, UnicodeError and AttributeError on both the stdout flush and the stderr write;
  • use ASCII-only text.

Add scenarios and tests for:

  • stderr closed: preexec_fn=lambda: os.close(2);
  • stderr unwritable, using a pipe whose read end is closed. This works on macOS too, which has no /dev/full.

Each asserts: exit 0, the workflow runs, and stdout is unchanged.


4. The organisation and visibility prompts have no grammar, ordering, re-ask or EOF rule, so public can be one keystroke away — MEDIUM

Concern. The question's answers are fully specified. The two prompts that actually decide where three repositories go, and who can read them, are not.

Evidence.

  • What the proposal says. It asks for "the visibility, one of public, private or internal, with no default".
    • That lists public first, in the argparse order (project:939).
    • openRepoShape's order is VISIBILITY_CHOICES = ("private", "public", "internal") (setup-project.py:80).
  • The question is numbered. Its answers are numbered "as choose() numbers". Nothing stops the design from numbering visibility the same way.
  • No re-ask or EOF rule is given for either prompt.
    • Through ask(), end-of-file becomes Cancelled., exit 130 (project:1001-1003). At the question it is exit 2.
    • An invalid organisation refuses at once (project:223). The question gets one re-ask.
  • The final gate does not repeat visibility. openRepoShape says only "This will create THREE repositories in ''. Type yes to continue:" (setup-project.py:1599-1622).
  • pty drivers. The Rule says "The question is asked only of a person at the terminal".
    • The settled helper cannot tell tmux send-keys or expect from a person, and this estate runs its lanes in tmux.
    • The packet's D2 rests the guard against three repositories on openRepoShape's AGENTS.md, not on any mechanism in project.

Impact. MEDIUM. If the design numbers visibility, a person who just pressed 1 for the Triad can press 1 again. That makes three repositories public, and the final yes does not restate the choice.

Suggestion. Specify the visibility prompt:

  • the full words only, case-insensitive and stripped;
  • listed private, public, internal;
  • no numbers and no default;
  • unrecognised input → asked once more → exit 2.

Specify both prompts:

  • end-of-file at the organisation or visibility prompt → exit 2, nothing created, the same as the question;
  • a public answer is echoed with its consequence before delegating.

Test every branch. Reword the Rule to "asked only where the terminal helper is true". Record the residual risk explicitly: a pty-driven agent cannot be told from a person, and the "no unconfirmed repository" guarantee then rests on openRepoShape's typed confirmation and the agent instructions (packet D2).


5. "Created a single repository" is undefined: bench generators create non-repositories, and the proposal itself says those get no advisory — MEDIUM

Concern. The advisory's trigger is a directory plus .project.json. That is a class the proposal, and openRepoShape, say is not a repository.

Evidence.

  • The trigger. "Once the repository exists (the generator succeeded, the destination is a directory, .project.json is written)".
  • The proposal's own Out of Scope. "A directory that is not a Git repository is not a repository at all (openRepoShape's not-a-repository)".
  • openRepoShape. 1a9fc537's advisory is "silent for … a non-repository".
  • The packet. D2 says it "prints the recommendation when it creates a single repository".
  • The generators.
    • cppBench new-cpp-project.sh runs no git at all.
    • flutterBench new-flutter-project.sh:248-249 runs rm -rf .git after copying specKit.
    • The test fixture generator is mkdir -p (tests/test_project.py:31).

Impact. MEDIUM.

  • On the bench path, project advises exactly where openRepoShape is silent.
  • The advisory points at adopt-project.py, which plans from a commit, and at a root single-repository.yaml in something that has no repository.
  • The new tests would pin that contradiction in place.

Suggestion. Define the trigger in the spec delta. Choose one:

  • (a) A successful bench creation is "the creation of a single-repository project" whether or not the generator ran git init. Write down why the inspection-time not-a-repository rule does not apply at creation.
  • (b) Give the advisory only when <destination>/.git exists, a declared fact of the new tree read locally with no network. Otherwise stay silent.

Add a fixture for each case.


Smaller issues (one line each)

  • Interrupt during the Triad delegate (verified).
    • On Ctrl-C, subprocess.run kills its child 0.25 s later (run()'s except: process.kill()).
    • That child is the openRepoShape launcher, so the launcher's trap 'rm -rf -- "$WORKDIR"' EXIT (openRepoShape:114) never fires.
    • setup-project.py and gh keep running after project has printed Cancelled. and exited 130. Repositories may exist by then.
    • Scope "An interrupt exits 130 … as today" to the question, and record or fix this case, for example by ignoring SIGINT in the parent while the shape delegate runs.
  • Narrowed exemption. The packet's -wip exemption also applies "because the person's own workspace configuration names it" (packet spec.md:186). "The one ratified exemption readable … at creation is the -wip form" therefore narrows it. Record that as deliberate, or carry it to clarifications along with the -wip question.
  • Leftover test setting. Impact says the change "reads nothing beyond the arguments, PATH and CI". The tests still set AGENT_PROTOCOL_ROOT, which is left over from the dropped inspection reader. Drop it, or name what reads it.
  • Rules wording. "The single answer ends at … the --yes the person typed" cannot happen, because --yes skips the question. Reword it.
  • Task 5.6 tick. The tick is booked on the merge sha, but onp and new-project.sh, the entrypoints task 5.6 names, keep running pinned a0407904 until workBenches moves the pin. File the pin-advance request on workBenches and cite it in the 5.6 evidence.
  • The fake openRepoShape in tests.
    • It is safe with a sealed PATH in the subprocess tests.
    • The risk is in-process module.main(). With execute unpatched, it resolves the real launcher from the developer's PATH, and the pty-typed answers go to it.
    • Assert that shutil.which("openRepoShape") is the fake, and scrub GH_TOKEN, GITHUB_TOKEN and OPENREPOSHAPE_* from self.env.

brettheap and others added 3 commits October 7, 2026 01:44
Write the change's delta on the project-command capability.

- MODIFIED "Delegate project creation": the whole requirement restated,
  adding the Triad-first offer, typed organization and visibility as
  explicit, the creation advisory, and that nothing is created without
  the person's confirmation; "Preview" now holds for both answers.
- ADDED six requirements: who is asked the creation question and where
  it sits; the question's entries and answers; the known Triad
  obstacles, named beside the Triad and refused with exit 2 on a Triad
  answer; the organization and visibility prompts and the restating
  line; the creation advisory and its guarded write; and the order
  under --workflow.
- 47 scenarios, behaviour only; openRepoShape's refusals promise no
  status beyond passing through.

Lane: openRepoProject-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Decide how project new builds the spec delta, answering the council's
noted constraints N1 to N6.

- One terminal rule and one offer value computed once in new(); the
  --workflow refusal is hoisted only for an asked invocation.
- Both answers re-enter today's shape and bench branches; the question
  and the Triad prompts share one retry and end-of-input rule through
  ask().
- Known obstacles are three local reads; the assembly-root and -wip
  forms are pinned from openRepoShape 1a9fc537.
- The advisory is written with os.write on stderr's descriptor:
  probes show a swallowed buffered write exits 120 on /dev/full and
  print(file=sys.stderr) lands on stdout when stderr is closed.
- Under --workflow, project's offer comes first and setup-openspeckit
  follows unchanged; Ctrl-C while openRepoShape runs keeps today's
  behaviour, documented, with the wait-for-child alternative recorded.
- Fixed ASCII text, exit statuses, the pty helper and the fakes.

Lane: openRepoProject-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tasks.md is the governance and handoff record only: the governance
boxes (ratification by Brett Heap's word left open), the one Speckit
feature still to be created by /speckit.specify, and the landing and
archive steps. Implementation tasks belong to that feature alone.

Lane: openRepoProject-1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane: openRepoProject-1

Phase 6: the spec delta, design and governance tasks (11d3e9c)

Commits ef358ee (spec delta), 7db218b (design) and 11d3e9c (tasks), all on top of 92e4d2c and pushed fast-forward.

Spec delta: specs/project-command/spec.md (47 scenarios)

Operation Requirement Scenarios
MODIFIED Delegate project creation ("Preview" reworded so that it holds for both answers; "Existing project" kept) 4
ADDED Creation question is asked only of a person choosing at the terminal 9
ADDED Creation question offers the Triad first 8
ADDED Known Triad obstacles are named before the question and refused on a Triad answer 7
ADDED A Triad answer asks for the organization and the visibility 8
ADDED Creation advisory follows a single repository created without the question 7
ADDED Creation offer precedes the workflow follow-up 4

The scenarios describe behaviour only. Where the proposal left the exact text to design, the spec states the required elements. An openRepoShape refusal is promised only to pass through with its own status (N6).

Design decisions: design.md

  • D1: One terminal rule decides the question: stdin and stdout are both terminals, and CI is false-y. new() computes offer once, and the hoisted check, the question and the advisory all read that one value.
  • D2: The order inside new(). The --workflow refusal is hoisted only for an invocation that will be asked; every other invocation keeps today's refusal order.
  • D3: The known obstacles are three local reads, with no network. The assembly-root form is pinned from openRepoShape 1a9fc537.
  • D4: The question's lines go to stdout and its prompt goes through ask(). The question and the two Triad prompts share one rule: a retry once, then exit 2; end of input exits 2; an interrupt prints Cancelled. and exits 130.
  • D5: The organization uses today's check, and an answer that fails it counts as unrecognised. The visibility is a full word, with no number and no default. The restating line is printed before the plan, in a dry run too.
  • D6: The single-repository answer re-enters today's bench path, and no advisory follows it.
  • D7: The advisory is written with os.write on stderr's descriptor, and is never written when sys.stderr is None. Probes showed that a swallowed buffered write exits 120 on /dev/full, and that print(file=sys.stderr) lands on stdout when stderr is closed.
  • D8 (N1): Under --workflow, project's offer comes first and setup-openspeckit follows unchanged, with no suppression and no coordination. The double offer is recorded.
  • D9 (N3): Ctrl-C while openRepoShape runs keeps today's behaviour, and the README documents it. The alternative, waiting for the child, is described and not taken: it would change --shape and update --component shape beyond this change's scope.
  • D10 (N4): A -wip name gets neither the question nor the advisory. Only the name form is read, and the regex is pinned from 1a9fc537.
  • D11 (N2): Agents at a pseudo-terminal are not detected. AGENTS.md and the README tell an agent to offer the Triad in conversation and then run project new with a choosing flag.
  • D12 (N6): A table of exit statuses. project's own refusals exit 2; openRepoShape, the generator and the follow-up pass their own statuses through.
  • D13: Every new string is fixed here as ASCII text, for the tests to pin as fixtures.
  • D14: The tests: the pty helper as the proposal specifies it, in-process tests that patch input and isatty, a fake openRepoShape and a fake setup-openspeckit on a controlled PATH, a scrubbed environment, and the three guarded-write cases.

Tasks: tasks.md

tasks.md holds governance and the handoff only. Governance boxes 1.1 to 1.5 are ticked; ratification by Brett Heap's word (1.6) is left open. The Speckit handoff names specs/002-<slug>/, which /speckit.specify creates after ratification, and /opsx:apply is not run until that section names exactly one feature.

Open questions

None changes what gets built. Two are left to the lead:

  1. Whether to file D9's alternative, waiting for the delegate on new --shape and update --component shape, as an issue on this repository now.
  2. Whether the workBenches handoff issue should also note that setup-openspeckit's advisory writer exits 120 when stderr is a full device, and that its advisory omits "run by a person deciding for that project".

Checks

  • openspec validate prefer-triad-in-project-new --strict and openspec validate --all --strict pass (6 of 6).
  • openspec status shows proposal, specs, design and tasks all done.
  • python3 -m unittest discover -s tests runs 43 tests OK, as on main.

@brettheap brettheap left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lane: openRepoProject-1
VERDICT: READY at 11d3e9c

Independent re-verification of PR #5, prefer-triad-in-project-new

Verifier: Sonnet (lane openRepoProject-1's re-verifier; wrote none of the change). Read-only throughout: no edit, commit, push or register line. Worktree orp-triad was at 11d3e9c456b58a1e5f5bb33cffa4573deb0813ce, branch change/prefer-triad-in-project-new, clean (git status --short printed nothing); origin/change/prefer-triad-in-project-new is the same sha.

Checklist

# Item Result Evidence
1 Delta vs canonical PASS grep -n '^### Requirement: Delegate project creation$' hits canonical spec.md:9 and delta spec.md:3, byte-identical (cat -A). The delta restates the whole requirement text and keeps both canonical scenarios: "Preview" (reworded for both answers, spec.md:21-24) and "Existing project" (verbatim, :26-28). The only MODIFIED header is that one; the other four canonical requirements are untouched (no REMOVED or RENAMED block); the six ADDED headers collide with none. Archive simulated in a throwaway copy (openspec archive prefer-triad-in-project-new --yes): + 6 added, ~ 1 modified, - 0, then openspec validate --all --strict on the result: 5 passed, 0 failed. Canonical specs on this branch equal origin/main (git diff --quiet origin/main HEAD -- openspec/specs).
2 Delta vs proposal PASS Every What Changes bullet maps (details below the table). All 13 options of new (project:931-972; the two positionals aside): 9 choosing (--shape --org --visibility --family --elected-by --bench --type --description --yes) and 4 non-choosing (--dry-run, --workflow, --into/positional parent, --workbenches), identical lists in proposal :87-91, spec :50-56, design :91-96. No contradiction found.
3 Spec hygiene PASS 7 requirements, each body has SHALL; 47 #### Scenario: headers (none at another depth), 47 of 47 carry WHEN and THEN (script); no function, library, line-number or implementation-step text in the delta (grep of ask, isatty, os., sys., fullmatch, subprocess, .py:, project:NNN: nothing but ordinary words). openspec validate ... --json: "issues": [].
4 Design vs clarifications FAIL (non-blocking) N1 to N6 are the six clarifications.md headings in file order; design :9-14 maps N1 D8, N2 D11, N3 D9, N4 D10, N5 D4+D14, N6 D12, and each decision exists and answers its note. All Impact constraints appear: prompt through ask() (D4), one offer value (D1), hoist only when asked (D2 step 3), guarded ASCII os.write skipped when sys.stderr is None (D7 steps 2 and 4), the full pty helper (D14 bullet 1: os.openpty, start_new_session, select, EIO or empty read, answer after prompt, repeated send_signal SIGINT, \r\n normalised, echo ignored, timeout, no pty.fork), in-process input and isatty patches with no stream pinned (D14 bullet 2), scrubbed env without GH_TOKEN, GITHUB_TOKEN, OPENREPOSHAPE_* (D14 bullet 3). One gap: the on-PATH assertion is stated for the fake openRepoShape only (see note 1).
5 D13 strings PASS Script over design.md:431-484: all ASCII; advisory is exactly 2 lines (:477-478), both start warning:; together they contain preferred, not required, elective, confers nothing, adopt-project.py, a person deciding for this project, single-repository.yaml, Nothing here changes. Question: entry 1 is Triad (default) and precedes entry 2; entry 1 carries the posture; single-repository.yaml is on entry 2 only; prompt Create it as a Triad? [Y/n or 1/2]: reads naturally with y/yes = Triad and n/no = single.
6 Cross-document identity PASS -wip regex ^[a-z0-9]+(?:-[a-z0-9]+)*-wip$: identical in proposal :217, spec :52, :90, design :382, clarifications :102. Root-name regex ^[A-Za-z][A-Za-z0-9]*$: identical in proposal :123, spec :175, design :157. git show 1a9fc537:contracts/repository-naming.yaml: 345: pattern: "^[A-Za-z][A-Za-z0-9]*$" (project-leg role assembly) and 412: pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*-wip$" (family workspace). CI false values: workBenches 9fbe609c CI_FALSE_VALUES: Final = ("", "0", "false", "no") (line 127) equals proposal :74, spec :48 and :85-87, design :79-80. Choosing-flag list: identical (above). Exit codes 2 and 130 are consistent across proposal, spec and design D12; the spec makes no promise about openRepoShape's own refusals (spec.md:278-280: "exits with that same status"; :242-244).
7 tasks.md PASS Governance and handoff only; - [ ] / - [x] numbered format; openspec instructions apply --change ... --json parses 13 total, 5 complete; 1.1 to 1.5 ticked, 1.6 open (tasks.md:11-16). ## Speckit Handoff (:31-43) names exactly one future feature specs/002-<slug>/, created by /speckit.specify, and says /opsx:apply is not run until it names exactly one feature. grep -rn project-triad-preference: one hit, proposal.md:322, the "dropped" note. .openspec.yaml is schema: spec-driven and created: 2026-10-07.
8 Tooling PASS openspec validate prefer-triad-in-project-new --strict: Change ... is valid, exit 0. openspec validate --all --strict: 6 passed, 0 failed. openspec status --change ... --json: proposal, specs, design, tasks all done, isComplete: true. python3 -m unittest discover -s tests: Ran 43 tests in 7.178s OK. git diff --stat origin/main...HEAD -- . ':!openspec/changes/prefer-triad-in-project-new': empty (the whole diff is the six change files, 1664 insertions). The design's five stream probes were re-run on Python 3.12.3 and all reproduce (rows 1 to 4: 2>&- puts ADVISORY on stdout; buffered write on /dev/full swallowed but exit 120; os.write swallowed and exit 0; closed-read pipe BrokenPipeError swallowed, exit 0; row 5: pty probe puts PROMPT? on the stderr pipe, echo yes\r\n on the pty).
9 PR #5 PASS gh pr view 5: body line 1 Lane: openRepoProject-1; line 61 Refs #3. The realization PR closes the issue; this PR is governance. (no Closes #3); closingIssuesReferences: []; isDraft: true; headRefOid: 11d3e9c4.... Body lists phases 1 to 6 and the six files, and states 47 scenarios and six ADDED requirements (both confirmed by script). gh pr checks 5 and statusCheckRollup: 9 of 9 SUCCESS (SonarCloud, and ubuntu and macos on Python 3.10 and 3.12, each run on push and on pull_request).

Item 2 mapping, bullet by bullet (proposal What Changes to delta)

  • Offers the Triad first, one question, Triad listed first and default with posture, obstacle named beside it, single accepted without a reason and naming single-repository.yaml in one clause, question creates nothing and is not a confirmation: "Delegate project creation" :10-19, "Creation question offers the Triad first" :112-135, scenarios "The question's entries", "An answer is not a confirmation".
  • One terminal rule (stdin and stdout terminals, CI), lines to stdout, prompt behaves as existing prompts, existing prompts unchanged: "Creation question is asked only of..." :43-59; "offers the Triad first" :120-122.
  • Nine choosing and four non-choosing flags, dry run asks and prints each plan: :50-56, scenarios "A choosing flag skips the question", "A dry run asks too".
  • Where the question sits (after name and the three pre-flight refusals; --workflow refusal hoisted only for an asked invocation; other invocations keep today's order): :61-68, scenarios :99-110.
  • Answers Enter/1/2/y/yes/n/no, case-insensitive, whitespace ignored, ask once more then exit 2, EOF exit 2, interrupt Cancelled. and 130: :124-130, scenarios :142-170.
  • Three known obstacles read before the question, named beside the Triad, Triad answer refused with exit 2 before any prompt or network, dry run too, single answer unaffected, openRepoShape authoritative: "Known Triad obstacles..." :173-221.
  • Organization then visibility, full words in order private, public, internal, no default, retry once then exit 2, EOF exit 2, interrupt 130, restating line with the public consequence, openRepoShape run without --yes, its refusals passed through with its own status: :224-280.
  • Single-repository advisory only when the question was not asked, after the destination and .project.json exist and before any --workflow step, two ASCII warning: lines on stderr, guarded write, never stdout, never exit status, never checks, never --json, silent for -wip, dry run, Triad, generator failure: :283-335.
  • -wip name silent: :52-53, scenario "A workspace name" :89-92.
  • --workflow order, no suppression and no coordination, failure after the advisory: "Creation offer precedes the workflow follow-up" :338-364.

Blocking findings

None.

Non-blocking notes

  1. Item 4, the on-PATH assertion covers only openRepoShape. design.md:509-514 (D14, "The environment") and proposal.md:375-379 (Impact) say each Triad test asserts shutil.which("openRepoShape", path=env["PATH"]) is the fake and each missing test asserts None. The fake setup-openspeckit (design.md:522-526) has no such sentence. Because PATH is built from the directories of bash, git and sys.executable, a setup-openspeckit installed in one of those directories would silently defeat the --workflow refusal test. Fix: add after design.md:514 "The --workflow tests assert shutil.which("setup-openspeckit", path=env["PATH"]) is the fake, and the refusal test asserts it is None", and the same clause in proposal.md:394-395. Adds an assertion only; no behaviour changes.
  2. The ratified "convert nothing" rule is in the proposal but not in the delta. proposal.md:275-278 (Rules This Change Keeps) says project never runs adopt-project.py, never converts an existing repository and never writes a manifest or record on the advisory's account (packet spec.md:103-107: "SHALL convert nothing"). The delta carries only "SHALL NOT write single-repository.yaml" (spec.md:120-121) and "SHALL record nothing about having given it" (spec.md:303-304). It contradicts nothing and is not a What Changes bullet, so it is not a mapping failure. Suggested sentence at spec.md:297: "The command SHALL NOT run adopt-project.py, convert an existing repository, or write a manifest or record on the advisory's account."
  3. Capitalisation of the posture phrase. design.md:440 has Preferred, not required (capital P) in the question's entry 1; the scenario at spec.md:139 quotes preferred, not required. The advisory (design.md:477) is lower-case and exact. Tests pin the D13 fixtures, so nothing breaks; either lower-case the clause in D13 or let the scenario say "in any letter case".
  4. Parenthetical in the advisory requirement. spec.md:283-284 says "(chosen by flag, or not at a terminal)"; the CI case, which spec.md:311-313 covers by scenario, is "at a terminal" yet not asked. The governing clause ("without the creation question having been asked") is correct, so this is wording only. Suggested: "(chosen by flag, or the terminal rule does not hold)".
  5. Restating line wording. proposal.md:158 says "Before delegating to openRepoShape"; spec.md:237 and design D5 say "before the plan is shown", so it also prints in a dry run (D5 gives the reason). Consistent, and the spec is the stricter; noted so the implementer follows the spec.
  6. Ruling count. tasks.md:11 says alignment rulings "R1 to R14"; the resolution comment 6028469515 header says "(R1 to R15)" but its tables cite only R1 to R14 and R15 is defined nowhere. The tasks.md text matches the evidence; the comment header is off by one. No repository change needed.
  7. Why section length. openspec archive prints "Why section should not exceed 1000 characters" (the section is 2293 characters, proposal.md:20-54). A warning only; validate --strict passes and the archive proceeds. The archive simulation also shows INFO-level "Requirement text is very long (>500 characters)" on seven requirements; informational.
  8. PR body. After the lane's text, the body carries a bot-appended "Summary by Sourcery" block. Harmless, and it contains no closing keyword.

Every external citation spot-checked was accurate: openRepoShape 1a9fc537 (openRepoShape:53, :114, :549; setup-project.py:224-225, :428, :1532-1535, :1622, :1848-1852; shape_advisory.py:169, :207-215), workBenches 9fbe609c and d86ba59 (setup-openspeckit lines 103, 127/131, 2204-2209, 2273, 2288, 2320-2356, 4059-4078, warning: at 2370), the packet at e6380965 (spec.md:58-59, :90, :99-105, :178, :186; design.md:85-87, :372-380), and every project:NNN citation.

Verdict

READY at 11d3e9c. Zero blocking findings; items 1, 2, 3, 5, 6, 7, 8, 9 PASS; item 4 has one non-blocking gap (note 1). The PR can be marked ready for Brett Heap's word; task 1.6 (ratification) stays open.

@brettheap
brettheap marked this pull request as ready for review October 7, 2026 01:56
@sourcery-ai

sourcery-ai Bot commented Oct 7, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 20 hours by commenting @sourcery-ai review. Upgrade to get a review now.

From review 5436721813 (READY, zero blocking findings):

1. design.md D14 and proposal.md test plan: the --workflow tests assert the
   fake setup-openspeckit is what PATH resolves, and the refusal test
   without it asserts None.
2. spec.md creation-advisory requirement: SHALL NOT run adopt-project.py,
   convert an existing repository, or write a manifest or record on the
   advisory's account.
3. design.md D13: lower-case "preferred, not required" in entry 1 so it
   matches the spec scenario.
4. spec.md creation-advisory requirement: "(chosen by flag, or the terminal
   rule does not hold)" now covers the CI case.

Nothing else changed.

Lane: openRepoProject-1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane: openRepoProject-1

The four non-blocking notes from review 5436721813 are folded at the new head 094d2ef (one commit on top of 11d3e9c, fast-forward push):

  1. design.md D14 and proposal.md test plan: the --workflow tests assert shutil.which("setup-openspeckit", path=env["PATH"]) is the fake, and the refusal test without it asserts None.
  2. spec.md creation-advisory requirement: the command SHALL NOT run adopt-project.py, convert an existing repository, or write a manifest or record on the advisory's account.
  3. design.md D13: "Preferred, not required" is now "It is preferred, not required:" so the phrase matches the spec scenario in lower case.
  4. spec.md creation-advisory requirement: "(chosen by flag, or not at a terminal)" is now "(chosen by flag, or the terminal rule does not hold)", covering the CI case.

Nothing else changed. openspec validate prefer-triad-in-project-new --strict and openspec validate --all --strict pass, and python3 -m unittest discover -s tests runs 43 tests OK.

@sonarqubecloud

sonarqubecloud Bot commented Oct 7, 2026

Copy link
Copy Markdown

@brettheap
brettheap merged commit ca4c615 into main Oct 7, 2026
10 checks passed
@brettheap
brettheap deleted the change/prefer-triad-in-project-new branch October 7, 2026 10:02
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane: openRepoProject-1
Landed by squash as ca4c615 on Brett Heap's word "ratify 5" (2026-10-07). READY review 5436721813 stood at head 094d2ef (CI 9 pass, 1 skipping).
Next: one /speckit-specify run creates the single feature for this change; the realization PR will carry "Closes #3".

brettheap added a commit that referenced this pull request Oct 7, 2026
Apply the second review round to the four-document brainstorm packet and
re-align the overview, synthesis and handoff with the feature documents:

- Probe directory rule: worktree-specific probes run in the worktree's own
  index (`git -C <worktree path>`), repository-wide probes at the canonical
  root; without it the hidden-local-state probe read the main index.
- Caps follow the removal floor: a removal child is spawned only with at
  least `removal_floor_seconds` (5 s) of the work deadline left, so the
  batch target cap is 16, not 24 (128 worktree rows unchanged); the
  overview's listing phase is an estimate counted against the deadline,
  with a 5 s bound per root, and the break-even and single-repository
  figures move with it.
- Submodules: any gitlink, or an admin `modules` directory, excludes a
  worktree as `contains-submodule` from the same bounded `ls-files` probe.
- One path-escaping form in both documents (`\xHH` bytes, `\uXXXX` code
  points, lowercase hex); the packet carries no raw bidirectional control
  character.
- Merge target: strip `refs/remotes/origin/` from the `origin/HEAD` symref;
  manifest revalidation compares against the plan-time value.
- Rewritten scenarios whose expectations the documents' own rules made
  impossible ("Repository replaced mid-batch", "Gated merged worktrees");
  explicit error-row fields; `{"error", "code"}` exit-2 JSON; `doctor`'s Git
  refusal; the overview's fifth gate also withholds `--all-safe` above the
  batch row cap.
- Handoff resolution record updated with the round-2 result and the cap
  decision; baseline notes record origin/main at ca4c615 (PR #5, OpenSpec
  only) and the closure of PR #2 (`cleanup`, retained on origin) with the
  follow-ups its decision record names.

Design only: no code, tests, OpenSpec or Speckit artifacts. Packet
validator passes; review rounds 1 and 2 resolved for every high and medium
finding; targeted re-verification recorded in the PR.

Lane: openRepoProject-3
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 7, 2026
…fications.md

Lane: openRepoProject-1
The proposal's status paragraph now states the ratification ("ratify 5", PR #5) and the landing sha instead of the pre-ratification draft status. clarifications.md gains the lane line the other files of the change carry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 8, 2026
…-new) (#8)

* Specify feature 002-triad-first-project-new from the ratified change

Lane: openRepoProject-1

Creates the single Speckit feature that realizes OpenSpec change
prefer-triad-in-project-new (ratified by Brett Heap, "ratify 5",
2026-10-07; PR #5 landed as ca4c615), records the ratification and the
Speckit handoff in the change's tasks.md, and adds the spec quality
checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record the ratification in proposal.md and add the lane line to clarifications.md

Lane: openRepoProject-1
The proposal's status paragraph now states the ratification ("ratify 5", PR #5) and the landing sha instead of the pre-ratification draft status. clarifications.md gains the lane line the other files of the change carry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plan feature 002-triad-first-project-new

Lane: openRepoProject-1
The plan sequences the ratified design (D1 to D14) into nine slices, with research, the data model, the project new command-line contract and a quickstart validation guide. It cites the design rather than restating it and contradicts none of it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add tasks for feature 002-triad-first-project-new

Lane: openRepoProject-1
Seventy-three dependency-ordered tasks in eight phases, tests before implementation in each user-story phase, with every one of the 47 delta scenarios named by at least one test task and a traceability record to be kept in traceability.md. Landing, the workBenches issue and the issue comments stay with the lane lead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Phases 1 and 2: traceability record, test harness and the offer decision

Lane: openRepoProject-1

Adds the D13 fixtures, the D14 environment, fakes, pty and in-process helpers,
the terminal-rule test, person_at_terminal(), the pinned name forms and the
offer value computed once in new(), which nothing reads yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Phase 3 (US1): ask the creation question at the terminal

Lane: openRepoProject-1

A person at a terminal without a choosing flag is asked Triad or single
repository once the name is known; the single-repository answer, retries,
refusals and interrupts work end to end, with T012 to T022 as pty tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Phase 4 (US2): create the Triad from the answer

Lane: openRepoProject-1

A Triad answer asks for the organization and then the visibility, restates
them, and re-enters the unchanged shape branch, so openRepoShape runs without
--yes and keeps its own confirmation; T025 to T033 cover it at the pty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Apply the analyze findings to the feature artifacts and record phases 1 to 4

Lane: openRepoProject-1

Applies the 13 findings of the /speckit-analyze pass (C1, C2, G1 and ten
LOW) to tasks.md, contracts/project-new.md, data-model.md and plan.md, and
ticks T001 to T034, verified by the lead at 4f23450 (65 tests OK on all
four CI jobs).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Phase 5 (US3): name the known Triad obstacles and refuse them on a Triad answer

Lane: openRepoProject-1

The obstacle reader checks the name form, openRepoShape on PATH and the parent directory before the
question, names each under the Triad entry, and refuses a Triad answer that meets one (T035 to T041).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Phase 6 (US4): keep scripted creation unasked and advise it on standard error

Lane: openRepoProject-1

A single repository created without the question gets the two advisory lines through a guarded
descriptor write that cannot reach stdout or change the exit status (T042 to T053).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Phase 7 (US5): workspace names stay unasked and the offer precedes the workflow follow-up

Lane: openRepoProject-1

An asked invocation now meets the --workflow prerequisite refusal before the question, and the tests
pin the -wip silence and the advisory's place ahead of setup-openspeckit (T054 to T061).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Phase 8 (Polish): document the creation question, the advisory and the agent rule

Lane: openRepoProject-1

README "Create a project" and its Configuration table, and an AGENTS.md section, describe the question,
what skips it, the Triad prompts, the stderr advisory, the --workflow order, Ctrl-C and agents (T062 to T064).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Record phases 5 to 8 as verified and fix the T043 seam

Lane: openRepoProject-1

Ticks T035 to T073, verified by the lead at 4e20f61 (94 tests OK on all
four CI jobs; traceability 47 of 47; fixtures 25 of 25), and removes a
stray punctuation seam left by the analyze edit to T043.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Apply the re-verification findings F1 to F3, F6 and F7

Lane: openRepoProject-1

F1 pins the typed organization's case on its way to openRepoShape; F2
states that CI is compared case-insensitively and stripped; F3 corrects a
comment; F6 and F7 are governance bookkeeping (handoff wording, box 2.2,
spec status).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant