Skip to content

Commit c56fdeb

Browse files
committed
fix(agent): carry the new codes into improve-rule, and correct four contract gaps
From review of #190. All five findings verified against the code before acting; all five were real, and one was my own error. `improve-rule.txt` was left on the old contract. `rule improve` goes through the same service path as `rule create` and raises the same three codes — the new `error-envelope` cases already assert that — but its recipe still listed "a GitHub origin remote" as a precondition and documented only `NO_GITHUB_REMOTE`. An agent following it in any no-remote population got an undocumented code and no guidance, which is the exact failure this change set out to remove from `rule create`. It now carries the same precondition check, the same three codes, and the same note that `auth login` does not fix it. The test that should have caught it named one recipe, so it could not. It is now `it.each` over both, which is what makes the omission impossible to repeat. `cli-rule-routing`'s spec said `route` reads the repository URL from `auth`. It reads `ghOwner` from `info`; the `auth` wording predates moving those fields and was never updated, so one scenario asserted the opposite of what was built and of what every other spec in the change says. Corrected in the live spec and in the archived delta. Both touched recipes skipped their topic version bump, which `cli-agent`'s spec requires on a meaningful change. `route` gained a whole step and a renumbering; `create-remote-rule` gained a precondition section and three error rows. Bumped to v3 and v2, and `improve-rule` with them. `create-remote-rule` claimed four distinguishable causes. The throwing path distinguishes three: the probe that would separate "git not installed" from "not a git repository" is itself a `git` call, so a missing binary reports `NOT_A_GIT_REPOSITORY`. Rather than claim a distinction the code cannot make, the recipe now says so and tells the reader to check `PATH` when the user insists the directory is a repository. `resolveRepositoryContext` is unaffected: it never throws and reports `[unknown]` whatever the cause. `resolveIdentity`'s docstring still named `NO_GITHUB_REMOTE` as the failure it raises. Refs #179
1 parent 0319291 commit c56fdeb

7 files changed

Lines changed: 56 additions & 25 deletions

File tree

‎openspec/changes/archive/2026-08-26-remote-tier-capability-boundary/specs/cli-rule-routing/spec.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@
1818
#### Scenario: The availability test has one source
1919

2020
- **WHEN** `route` determines whether remote generation is available
21-
- **THEN** it SHALL rely on the repository URL reported by `auth` rather than re-deriving the remote independently
21+
- **THEN** it SHALL rely on the `ghOwner` reported by `info` rather than re-deriving the remote independently

‎openspec/specs/cli-rule-routing/spec.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,4 +331,4 @@ unusable where the engine's platform binary did not resolve.
331331
#### Scenario: The availability test has one source
332332

333333
- **WHEN** `route` determines whether remote generation is available
334-
- **THEN** it SHALL rely on the repository URL reported by `auth` rather than re-deriving the remote independently
334+
- **THEN** it SHALL rely on the `ghOwner` reported by `info` rather than re-deriving the remote independently

‎packages/cli/src/agent/create-remote-rule.txt‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Topic: create-remote-rule (CLI v%(CLI_VERSION)s / topic v1)
1+
# Topic: create-remote-rule (CLI v%(CLI_VERSION)s / topic v2)
22

33
## You are here
44
This is `create-remote-rule`. It helps you have the Taskless service
@@ -48,8 +48,13 @@ is advisory; this is the point of use.
4848

4949
**`auth login` does not fix it.** No GitHub owner is a property of the
5050
project, not the session, so do not send the user to log in for it. The
51-
four causes are: not a git repository, no `origin` remote, an `origin`
52-
that is not GitHub, and no git installed.
51+
causes are: not a git repository, no `origin` remote, an `origin` that is
52+
not GitHub, and git not being installed at all. The first three each have
53+
their own error code; a missing `git` binary cannot be told apart from
54+
the first, because the probe that would distinguish them is itself a
55+
`git` call, so it reports `NOT_A_GIT_REPOSITORY`. If the user says the
56+
directory *is* a repository, check whether `git` is on their `PATH`
57+
before believing the code.
5358

5459
## Cost, and when this path is right
5560

‎packages/cli/src/agent/improve-rule.txt‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Topic: improve-rule (CLI v%(CLI_VERSION)s / topic v3)
1+
# Topic: improve-rule (CLI v%(CLI_VERSION)s / topic v4)
22

33
## Goal
44
Iterate on an existing Taskless rule. The CLI submits the user's
@@ -12,7 +12,12 @@ If the user wants the local-only flow (no API call), fetch
1212

1313
## Preconditions
1414
- User is logged in.
15-
- Repository has a GitHub origin remote.
15+
- The project has a GitHub owner. Improvement runs through the same
16+
service path as generation, so it carries the same constraint. Check
17+
`ghOwner` from `%(TASKLESS_CLI)s info --json`: if it is the literal
18+
`[unknown]`, stop and say the tier is unavailable rather than
19+
submitting. `auth login` does not fix it — no GitHub owner is a
20+
property of the project, not the session.
1621
- The target rule exists at `.taskless/rules/sg/<id>/<id>.yml`.
1722
- The rule has metadata at `.taskless/rule-metadata/<id>.yml` (the
1823
`ticketId` is required by the iterate endpoint). If metadata is
@@ -110,7 +115,10 @@ When `--json` is set, failures emit `{ ok: false, code, message }`:
110115
| code | meaning | fix |
111116
|--------------------------|-------------------------------------|-----------------------------------------------|
112117
| `AUTH_REQUIRED` | not logged in | fetch `%(TASKLESS_CLI)s agent auth` |
113-
| `NO_GITHUB_REMOTE` | no GitHub origin remote | tell the user; we cannot proceed |
118+
| `NO_GITHUB_REMOTE` | no GitHub origin remote | retained for compatibility; treat as below |
119+
| `NOT_A_GIT_REPOSITORY` | not a git repository | tell the user; `auth login` cannot fix it |
120+
| `NO_ORIGIN_REMOTE` | git repository, no `origin` | tell the user; `auth login` cannot fix it |
121+
| `UNSUPPORTED_REMOTE_HOST`| `origin` is not GitHub | tell the user; `auth login` cannot fix it |
114122
| `INVALID_INPUT` | `--from` JSON failed validation | re-read input schema, fix, retry |
115123
| `RULE_NOT_FOUND` | metadata missing for the given rule | use anonymous variant or recreate via create |
116124
| `NETWORK_ERROR` | API submit/poll failed | report and suggest retry |

‎packages/cli/src/agent/route.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Topic: route (CLI v%(CLI_VERSION)s / topic v2)
1+
# Topic: route (CLI v%(CLI_VERSION)s / topic v3)
22

33
## Goal
44
Turn "write me a rule that…" into one command to run. This is the front

‎packages/cli/src/auth/identity.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ export interface Identity {
2323
* - repositoryUrl: inferred from `git remote get-url origin`
2424
*
2525
* Throws a `CLIError` carrying a stable `CLIErrorCode` if auth is missing
26-
* (`AUTH_REQUIRED`) or the repository URL cannot be resolved
27-
* (`NO_GITHUB_REMOTE`). Read the code off the error with
28-
* `identityFailureCode`; never re-derive it from the message.
26+
* (`AUTH_REQUIRED`) or the repository URL cannot be resolved, in which case
27+
* the code names which population the project is in:
28+
* `NOT_A_GIT_REPOSITORY`, `NO_ORIGIN_REMOTE`, or `UNSUPPORTED_REMOTE_HOST`.
29+
* Read the code off the error with `identityFailureCode`; never re-derive it
30+
* from the message.
2931
*/
3032
export async function resolveIdentity(cwd: string): Promise<Identity> {
3133
const token = await getToken(cwd);

‎packages/cli/test/agent-extensions.test.ts‎

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -419,21 +419,37 @@ describe("the GitHub-owner constraint is guarded twice", () => {
419419
expect(result.stdout).toContain("stop here");
420420
});
421421

422-
it("the remote recipe documents every code the populations raise", async () => {
423-
const result = await runCli(["agent", "create-remote-rule", "-d", cwd]);
424-
for (const code of [
425-
"NOT_A_GIT_REPOSITORY",
426-
"NO_ORIGIN_REMOTE",
427-
"UNSUPPORTED_REMOTE_HOST",
428-
// Retained for compatibility, so it must stay documented too.
429-
"NO_GITHUB_REMOTE",
430-
]) {
431-
expect(result.stdout).toContain(code);
432-
}
433-
});
434-
435422
it("the remote recipe does not send the user to auth login for it", async () => {
436423
const result = await runCli(["agent", "create-remote-rule", "-d", cwd]);
437424
expect(result.stdout).toContain("`auth login` does not fix it");
438425
});
426+
427+
// `rule improve` goes through the same service path and raises the same
428+
// three codes, so its recipe carries the same contract. The first version of
429+
// this change updated only `create-remote-rule`, and nothing caught it: the
430+
// test above names one recipe, so it could not.
431+
it.each(["create-remote-rule", "improve-rule"])(
432+
"%s documents every code the populations raise",
433+
async (topic) => {
434+
const result = await runCli(["agent", topic, "-d", cwd]);
435+
expect(result.exitCode).toBe(0);
436+
for (const code of [
437+
"NOT_A_GIT_REPOSITORY",
438+
"NO_ORIGIN_REMOTE",
439+
"UNSUPPORTED_REMOTE_HOST",
440+
// Retained for compatibility, so it must stay documented too.
441+
"NO_GITHUB_REMOTE",
442+
]) {
443+
expect(result.stdout).toContain(code);
444+
}
445+
}
446+
);
447+
448+
it.each(["create-remote-rule", "improve-rule"])(
449+
"%s says auth login does not fix a missing owner",
450+
async (topic) => {
451+
const result = await runCli(["agent", topic, "-d", cwd]);
452+
expect(result.stdout).toMatch(/`auth login` (does not|cannot) fix/);
453+
}
454+
);
439455
});

0 commit comments

Comments
 (0)