halfcycle 0.4.8 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/bin/bin.bundle.mjs +2162 -335
  3. package/commands/agent-teams-orchestration.md +1 -1
  4. package/commands/consistency-check.md +1 -1
  5. package/commands/context-update.md +1 -1
  6. package/commands/halfcycle-design.md +1 -1
  7. package/commands/halfcycle-setup.md +1 -1
  8. package/commands/stack-assembly-worker.md +1 -1
  9. package/commands/task-execution.md +1 -1
  10. package/commands/task-graph.md +1 -1
  11. package/commands/write-spec.md +1 -1
  12. package/dist/bin.d.ts +2 -1
  13. package/dist/bin.d.ts.map +1 -1
  14. package/dist/bin.js +0 -0
  15. package/dist/build-record/obtain.d.ts +14 -7
  16. package/dist/build-record/obtain.d.ts.map +1 -1
  17. package/dist/build-record/render-v2.d.ts +32 -6
  18. package/dist/build-record/render-v2.d.ts.map +1 -1
  19. package/dist/build-record/write.d.ts +2 -2
  20. package/dist/build-record/write.d.ts.map +1 -1
  21. package/dist/cli-contract.d.ts +2 -9
  22. package/dist/cli-contract.d.ts.map +1 -1
  23. package/dist/cli.js +3859 -1946
  24. package/dist/cli.js.map +3 -3
  25. package/dist/close-phase.d.ts +223 -134
  26. package/dist/close-phase.d.ts.map +1 -1
  27. package/dist/draw/index.d.ts +29 -6
  28. package/dist/draw/index.d.ts.map +1 -1
  29. package/dist/identity.d.ts +78 -9
  30. package/dist/identity.d.ts.map +1 -1
  31. package/dist/index.js +1685 -232
  32. package/dist/index.js.map +3 -3
  33. package/dist/install.d.ts +16 -0
  34. package/dist/install.d.ts.map +1 -1
  35. package/dist/open-phase.d.ts +25 -1
  36. package/dist/open-phase.d.ts.map +1 -1
  37. package/dist/plan.d.ts +75 -23
  38. package/dist/plan.d.ts.map +1 -1
  39. package/dist/pre-create-checks.d.ts.map +1 -1
  40. package/dist/review.d.ts +231 -0
  41. package/dist/review.d.ts.map +1 -0
  42. package/dist/shown-record.d.ts +46 -0
  43. package/dist/shown-record.d.ts.map +1 -0
  44. package/dist/system-report.d.ts +28 -4
  45. package/dist/system-report.d.ts.map +1 -1
  46. package/package.json +11 -11
  47. package/dist/project-page.d.ts +0 -24
  48. package/dist/project-page.d.ts.map +0 -1
@@ -1,45 +1,52 @@
1
1
  /**
2
- * Close-phase wiring — the CLI's call to `POST {baseUrl}/engagements/:id/accept`
3
- * (walk 6 F-1, INV-022's sixth occurrence).
2
+ * Close-phase wiring — the CLI's half of the close review (feature
3
+ * `close-review-and-carry-drop` AC1, decision 2; System view · E3).
4
4
  *
5
- * WHY THIS EXISTS AT ALL, WHICH IS THE WHOLE POINT OF THE FINDING. Closing a phase
6
- * has had a reachable route since PM-E6-T-18 and a working transition from `active`
7
- * since W4-1 — but the only CLI that drove it is `phase-close-admin`, which ships
8
- * inside the control plane and is therefore hosted-only (INV-001). A Connect client runs
9
- * `npx halfcycle` in their own repo and can never run it. So the clean close was
10
- * reachable by curl and by nothing the product ships, and every acceptance walk was
11
- * pushed onto `open-phase --override` instead.
5
+ * WHAT THIS VERB DOES NOW. It closes nothing. A phase closes only on its owner's last
6
+ * review decision, which the control service makes inside that decision's own
7
+ * transaction. This verb is the coordinator's act: it carries what the coordinator
8
+ * knows — the walk's verdict and findings (INV-009: sourced from the acceptance walk,
9
+ * never inferred), who ran the close step, and the client's statements — and asks the
10
+ * service to open the review. It writes no Build Record and clears no phase stamp:
11
+ * the phase is still open. `build-record <phase>`, run once the owner's last decision
12
+ * has closed it, writes the record and clears the stamp (`build-record/obtain.ts`).
12
13
  *
13
- * `open-phase` and `close-phase` are the two halves of one cycle and now sit beside
14
- * each other in one package, with one credential and one shape. That is the fix:
15
- * not a new precondition, not a way to make a Studio gate satisfiable — the gate was
16
- * never the problem — but the missing client half of a path that already worked.
14
+ * THE ORDER, AND WHY.
15
+ * 1. The whole request is checked against the route's own schema
16
+ * (`openReviewRequestSchema`, the one home of its shape, INV-003) BEFORE anything
17
+ * is sent — a request this machine refuses sends nothing, not even the counts.
18
+ * 2. The commit HEAD points at is read (`readHeadCommit`), so the statements carry
19
+ * the commit the review starts from.
20
+ * 3. A fresh count of what changed under each part is sent through the runner's
21
+ * `observe` verb (`what-halfcycle-saw` AC3, the unthrottled sender), so the review
22
+ * starts from the commit the walk saw. The runner is SPAWNED as a child process,
23
+ * never imported: this package may import only the two shared packages
24
+ * (INV-001), and counting keeps one home, the runner's. The project credential
25
+ * this verb already read travels through the child's environment under the names
26
+ * the runner's own configuration reader reads. Whatever the child answers, the
27
+ * open goes ahead: a count that could not be sent is said once by the open
28
+ * answer's `counted`, never by a line of this verb's own.
29
+ * 4. The open request is posted, and the answer is parsed through
30
+ * `openReviewAnswerSchema`. Each outcome prints its own sentence (CL1–CL6, CL12,
31
+ * and the typed review's H1), written out below exactly as the spec words them.
17
32
  *
18
- * **This verb names no capability only Studio has.** No GitHub installation, no
19
- * portal session, no operator picking anything up, and it never touches
20
- * `delivering`. It is the same call in both topologies (`acceptPhase` is legal from
21
- * `active` AND from `delivering`), which is what makes it safe to print in a refusal
22
- * that both topologies can read — see `PhaseEntryConditionError`.
33
+ * `override` IS GONE FROM THE CLOSE (W1-3). There is no way through an unfinished step:
34
+ * the remedy is to finish it, decline it or pass its review again. The method version
35
+ * does not move at the close either (W1-5); only `open-phase` moves it, so this verb
36
+ * never touches the committed pin.
23
37
  *
24
- * WIRING, NOT MACHINERY — the same posture as `open-phase.ts`. The verdict is the
25
- * caller's (INV-009: SOURCED from the acceptance walk, never inferred from "no guard
26
- * fired"), the findings/detail consistency rule is the control plane's, and the
27
- * derive-then-transition ordering is the orchestrator's. This module builds the
28
- * request, sends it, and reports what came back.
38
+ * TOPOLOGY. This verb names no capability only Studio has: the open route takes the
39
+ * engagement credential alone, needs no GitHub installation, no portal session and no
40
+ * `delivering` status (INV-022).
29
41
  *
30
- * INV-001: this file imports the global `fetch` (Node 20), Node builtins, and
31
- * `@halfcycle/events` — one of the exactly two workspace packages a shippable may
32
- * import. `acceptPhaseRequestSchema` is the ONE home for the request's shape
33
- * (INV-003), so the CLI validates the whole body it builds against the same schema
34
- * the route parses with. It never imports the control service.
42
+ * INV-001: this file imports the global `fetch`, Node builtins, `@halfcycle/events`
43
+ * and `@halfcycle/core`. It never imports the control service or the runner.
35
44
  *
36
- * SINCE CONNECT PHASE 4 (`close-produces-the-record`) the close carries the
37
- * client's statements and the full commit it closed at, and writes no local close
38
- * note. The service freezes both into the phase's Build Record; the caller fetches
39
- * that record afterwards (`build-record/obtain.ts`).
45
+ * SHIPS TO CLIENT DISK inside the CLI: every string here is a sentence a person reads,
46
+ * and no comment sits beside an object member.
40
47
  */
41
- import { type AcceptanceOutcome, type MethodUpdate, type PhaseCloseDecision } from '@halfcycle/events';
42
- import { type PhaseStampFailure, type RepoCredential } from './open-phase.js';
48
+ import { unfinishedStepLine, type AcceptanceOutcome, type ObserveRunResult, type OpenReviewAnswer } from '@halfcycle/events';
49
+ import type { RepoCredential } from './open-phase.js';
43
50
  /**
44
51
  * Whether the commit this phase closed at could be read, and if so the full hash
45
52
  * that travelled with the close. When it could not, `reason` says why in words a
@@ -63,121 +70,203 @@ export interface StatedStatements {
63
70
  }
64
71
  /** Nothing stated — what a close sends when no statement flag was given. */
65
72
  export declare const NO_STATEMENTS: StatedStatements;
66
- /** What `POST /engagements/:id/accept` returns on 200 (the fields the CLI reports). */
67
- export interface ClosedPhase {
68
- engagementId: string;
69
- /** The lifecycle status after the close — `closed`. */
70
- status: string;
71
- /**
72
- * Set when the phase closed but this machine's record of it could not be
73
- * cleared. `null` on every ordinary success. Until it is cleared, work on this
74
- * machine is still filed under a phase that is no longer open.
75
- */
76
- stampFailure: PhaseStampFailure | null;
77
- /** The commit sent with the close, or why none could be read. */
78
- closeCommit: CloseCommitOutcome;
79
- /**
80
- * The method version this close moved the project to (`existing-projects-carry-on`
81
- * AC2), or `null` when it did not move. An older control's answer carries no
82
- * field at all, which reads the same as `null` here — this CLI then prints
83
- * and writes nothing about a move, exactly as it did before this field existed.
84
- */
85
- methodUpdated: MethodUpdate | null;
86
- }
87
73
  /**
88
- * Read the full HEAD commit of the NAMED repository, before the close is sent.
74
+ * Why the full HEAD commit of a named repository could not be read, in one place
75
+ * both `open-phase` and `close-phase` read (`what-halfcycle-saw` AC1):
76
+ * - `no-repository` — no folder was named for the read, or the named folder is not
77
+ * a git repository at all.
78
+ * - `no-commit` — the folder IS a repository, with no commit on it yet.
79
+ * - `unreadable` — git could not be run at all, or its answer was not a full
80
+ * commit hash. `missingGit` tells those two apart: `close-phase` has always
81
+ * printed them as two separate sentences (`readCloseCommit`, below), so the
82
+ * detail travels on the cause rather than being thrown away here.
83
+ */
84
+ export type HeadCommitCause = 'no-repository' | 'no-commit' | 'unreadable';
85
+ /** Whether the full HEAD commit of a named repository could be read, and why not. */
86
+ export type HeadCommitOutcome = {
87
+ read: true;
88
+ commit: string;
89
+ } | {
90
+ read: false;
91
+ cause: 'no-repository' | 'no-commit';
92
+ } | {
93
+ read: false;
94
+ cause: 'unreadable';
95
+ missingGit: boolean;
96
+ };
97
+ /**
98
+ * Read the full HEAD commit of the NAMED repository.
99
+ *
100
+ * ONE READER FOR BOTH VERBS. `close-phase` reads HEAD to send where a phase ends
101
+ * (`readCloseCommit`, below, is this function's body under the name that verb has
102
+ * always used); `open-phase` reads it to send where a phase starts
103
+ * (`what-halfcycle-saw` AC1). One way to read a commit off the person's disk, not two
104
+ * that could disagree about what "unreadable" means.
89
105
  *
90
106
  * `gitEnvForNamedRepo` strips the variables that tell git which repository to use,
91
107
  * so an absolute `GIT_DIR` inherited from a hook cannot substitute another
92
- * repository's HEAD for this one's. git's own stderr is discarded: a failure here
93
- * is expected, the close still goes ahead, and the caller says why in plain words.
94
- * The hash is checked by the contract's own `closeCommitSchema`, so a short or
95
- * malformed answer is never sent.
108
+ * repository's HEAD for this one's. git's own stderr is discarded throughout: a
109
+ * failure here is expected, the caller still proceeds, and it says why in this
110
+ * product's own words, never git's. The hash is checked by the contract's own
111
+ * `closeCommitSchema`, so a short or malformed answer is never treated as read.
112
+ *
113
+ * `no-repository` and `no-commit` are told apart by a second call
114
+ * (`rev-parse --git-dir`), made only when the first failed for a reason other than
115
+ * git being missing: if the folder IS a repository, that first failure can only mean
116
+ * it has no commit yet.
96
117
  */
97
- export declare function readCloseCommit(repoRoot: string | undefined): CloseCommitOutcome;
118
+ export declare function readHeadCommit(repoRoot: string | undefined): HeadCommitOutcome;
98
119
  /**
99
- * A refusal from the control plane, carried as an error the CLI can print whole.
120
+ * Read the full HEAD commit of the NAMED repository, before the close is sent.
100
121
  *
101
- * The two that matter and what they mean, because a status code alone sends people
102
- * to the wrong fix:
103
- * - `409` — a refusal. THREE different ones now share this code (already closed;
104
- * outstanding work; the phase is not the one open), which is why `code` below
105
- * exists: the status alone stopped being enough to say what happened.
106
- * - `422` — the outcome is not a valid INV-009 outcome (most often the findings
107
- * count disagreeing with the number of `--finding` summaries). Nothing was
108
- * written: the derivation validates before any write.
109
- */
110
- export declare class ClosePhaseRefused extends Error {
111
- readonly status: number;
112
- /**
113
- * What the control plane's own store found outstanding in this phase, when the
114
- * refusal was a `PhaseCloseConditionUnmet` (walk 7 `F-4`). Absent on every other
115
- * refusal. Carried separately from `message` so the CLI can print the finding on
116
- * its own line, the way `open-phase` prints a crossed entry finding.
117
- */
118
- readonly finding?: string | undefined;
119
- /**
120
- * The plane's own name for the refusal (`error` on the body) — e.g.
121
- * `PhaseAlreadyClosed`, `PhaseCloseConditionUnmet`, `PhaseNotOpen`.
122
- *
123
- * WHY IT IS CARRIED (walk 8 `F-1`). The CLI used to read a bare `409` as "this
124
- * phase is already closed" and print that as advice. Once a wrong phase NAME
125
- * became its own 409, that line printed underneath a refusal saying the opposite
126
- * — the plane said *phase 1 is open, close that one* and the CLI said *it is
127
- * already closed*. Guessing a cause from a status code is how a remedy string
128
- * comes to contradict the refusal above it; the code is read, not inferred.
129
- */
130
- readonly code?: string | undefined;
131
- constructor(status: number, message: string,
132
- /**
133
- * What the control plane's own store found outstanding in this phase, when the
134
- * refusal was a `PhaseCloseConditionUnmet` (walk 7 `F-4`). Absent on every other
135
- * refusal. Carried separately from `message` so the CLI can print the finding on
136
- * its own line, the way `open-phase` prints a crossed entry finding.
137
- */
138
- finding?: string | undefined,
139
- /**
140
- * The plane's own name for the refusal (`error` on the body) — e.g.
141
- * `PhaseAlreadyClosed`, `PhaseCloseConditionUnmet`, `PhaseNotOpen`.
142
- *
143
- * WHY IT IS CARRIED (walk 8 `F-1`). The CLI used to read a bare `409` as "this
144
- * phase is already closed" and print that as advice. Once a wrong phase NAME
145
- * became its own 409, that line printed underneath a refusal saying the opposite
146
- * — the plane said *phase 1 is open, close that one* and the CLI said *it is
147
- * already closed*. Guessing a cause from a status code is how a remedy string
148
- * comes to contradict the refusal above it; the code is read, not inferred.
149
- */
150
- code?: string | undefined);
151
- }
122
+ * `readHeadCommit`'s body, mapped to the two sentences this verb has always
123
+ * printed for `unreadable` — git being missing on this machine, and git running
124
+ * but not answering with a full hash, read differently by whoever hits them, so
125
+ * they stay two sentences rather than merging into one that explains neither.
126
+ */
127
+ export declare function readCloseCommit(repoRoot: string | undefined): CloseCommitOutcome;
128
+ /**
129
+ * The longest the `observe` child may run before it is stopped and the open goes ahead
130
+ * without it. The child makes two requests with a 1.5 s deadline each and a handful of
131
+ * git calls with their own caps; this is a backstop for a child that hangs, not a
132
+ * budget it is expected to use.
133
+ */
134
+ export declare const OBSERVE_CHILD_DEADLINE_MS = 30000;
135
+ /**
136
+ * The environment names the runner's configuration reader takes the project
137
+ * credential from: the token, the project id, and the service address. Named once,
138
+ * here, because the runner cannot be imported to read them (INV-001); the spawn test
139
+ * proves the child reads exactly these.
140
+ */
141
+ export declare const OBSERVE_CHILD_CREDENTIAL_ENV: {
142
+ readonly token: "GUARD_SERVICE_TOKEN";
143
+ readonly engagementId: "GUARD_ENGAGEMENT_ID";
144
+ readonly serviceUrl: "HALFCYCLE_SERVICE_URL";
145
+ };
146
+ /**
147
+ * The child's environment: this process's, without the variables that tell git which
148
+ * repository to use (an absolute `GIT_DIR` inherited from a hook would otherwise count
149
+ * another repository), and with the project credential this verb already read set
150
+ * under the runner's own names — so the child counts for the same project, against the
151
+ * same service, that the open is sent to.
152
+ */
153
+ export declare function observeChildEnv(credential: RepoCredential): NodeJS.ProcessEnv;
154
+ /**
155
+ * The one line the `observe` child printed, read through `observeRunResultSchema`, or
156
+ * `unreachable` when stdout holds no such line.
157
+ */
158
+ export declare function readObserveLine(stdout: string): ObserveRunResult;
159
+ /**
160
+ * Send a fresh count by running the runner binary this package ships, as a child
161
+ * process, in the named repository: `node <binary> observe`. Never throws, never
162
+ * prints: a binary that cannot be found (`binary` is the error that said so), a child
163
+ * that cannot start, runs past `OBSERVE_CHILD_DEADLINE_MS` or prints no result line
164
+ * all read `unreachable`, and the open goes ahead.
165
+ */
166
+ export declare function runObserveChild(binary: string | Error, credential: RepoCredential, repoRoot: string): Promise<ObserveRunResult>;
152
167
  /**
153
- * A close refused on this machine, before anything was sent: the request failed the
154
- * shared request schema. The CLI prints the message and exits as a usage error.
168
+ * A close step refused on this machine, before anything was sent: the request failed
169
+ * the shared request schema. The CLI prints the message and exits as a usage error.
155
170
  */
156
171
  export declare class CloseRequestInvalid extends Error {
157
172
  constructor(message: string);
158
173
  }
159
174
  /**
160
- * Record a phase's acceptance and close it.
175
+ * What came back from the open route:
176
+ * - `answered`: a body the open route's own answer schema accepts — one of its
177
+ * outcomes, or the not-open body;
178
+ * - `refused`: any other answer that was not a success, with its status and the
179
+ * service's own sentence when it gave one;
180
+ * - `unreadable`: a success whose body is not an answer this CLI can read;
181
+ * - `unreachable`: the service could not be reached at all.
182
+ */
183
+ export type ReviewOpenAnswer = {
184
+ kind: 'answered';
185
+ answer: OpenReviewAnswer;
186
+ } | {
187
+ kind: 'refused';
188
+ status: number;
189
+ message: string | null;
190
+ } | {
191
+ kind: 'unreadable';
192
+ status: number;
193
+ } | {
194
+ kind: 'unreachable';
195
+ why: string;
196
+ };
197
+ /** What one run of the close step did: the count it sent, the commit it read, the answer. */
198
+ export interface ReviewOpenAttempt {
199
+ observe: ObserveRunResult;
200
+ closeCommit: CloseCommitOutcome;
201
+ open: ReviewOpenAnswer;
202
+ }
203
+ /** What the close step sends: who ran it, the walk's outcome, and the client's statements. */
204
+ export interface CloseStepRequest {
205
+ actor: string;
206
+ acceptanceOutcome: AcceptanceOutcome;
207
+ stated: StatedStatements;
208
+ }
209
+ /** The address of a phase's open route: the phase is one path segment, encoded once. */
210
+ export declare function reviewOpenUrl(credential: RepoCredential, phase: string): string;
211
+ /**
212
+ * Run the close step: check the request, read HEAD, send a fresh count through
213
+ * `observe`, then ask the service to open the review of `phase`.
161
214
  *
162
- * @param phase the phase being closed, named explicitly — the lifecycle row has no
163
- * phase column (PM-E4-DEC-7), so the caller names what closed.
164
- * @param outcome the SOURCED INV-009 verdict + findings.
165
- * @param close WHO is closing it and on what basis (walk 7 `F-4`). Required here
166
- * for the same reason `openPhase` requires its entry decision: there
167
- * is no argument-less way to move the phase axis, so an unattributed
168
- * close is unreachable from any caller that compiles.
169
- * @param repoRoot the repository whose HEAD this close is sent with.
170
- * Optional so that a caller with no repository in hand — a test
171
- * harness, a future non-repo surface — is not forced to invent one;
172
- * omitting it sends no commit rather than silently resolving the
215
+ * @param observe the fresh count, run after the request is checked and before the open
216
+ * is sent. The CLI passes `runObserveChild` over the shipped runner
217
+ * binary; it is a parameter so this function holds no path to a binary
218
+ * of its own.
219
+ * @param repoRoot the repository whose HEAD the statements carry. Optional so a caller
220
+ * with no repository in hand sends no commit rather than resolving the
173
221
  * process's working directory, which would send a stranger's HEAD.
174
- * @param stated the client's own statements; each absent one is sent as not
175
- * stated (`null`, or no deviations).
176
222
  *
177
- * @throws ClosePhaseRefused on a 4xx the plane explained, so the CLI prints the
178
- * plane's own sentence and exits non-zero.
179
- * @throws Error before anything is sent when the request fails the shared schema;
180
- * on transport failure; or on a body that is not the expected shape.
223
+ * @throws CloseRequestInvalid before anything is sent — no count and no open — when the
224
+ * request fails the shared schema or the phase is not a phase name (the one
225
+ * rule, `phaseNameSchema`); nothing else is thrown — every answer is an outcome.
226
+ */
227
+ export declare function openPhaseReview(credential: RepoCredential, phase: string, request: CloseStepRequest, observe: () => Promise<ObserveRunResult>, repoRoot?: string): Promise<ReviewOpenAttempt>;
228
+ /** CL1 — the review is open. */
229
+ export declare function reviewOpenedLine(phase: string): string;
230
+ /** CL2 — Halfcycle could not count what the phase changed. */
231
+ export declare const COULD_NOT_COUNT_LINE = "[halfcycle] Halfcycle could not count what this phase changed, so the owner judges each part on what the agents reported alone.";
232
+ /** H1 (`typed-close-review`) — how the owner reviews, or that typing is switched off. */
233
+ export declare function typedReviewHintLine(offered: boolean): string;
234
+ /**
235
+ * After H1 (`system-view-page` AC7): the console's way to review, ending with the
236
+ * phase's System view address on `engagementId`. The second way while typing is
237
+ * offered, the way while it is switched off. `undefined` when there is no address (an
238
+ * id that is not an engagement id), and then no line is printed.
239
+ */
240
+ export declare function systemViewReviewLine(offered: boolean, engagementId: string, phase: string): string | undefined;
241
+ /** CL3 — tasks of the phase are not finished, named by title, at most ten. */
242
+ export declare function tasksOpenLine(phase: string, open: readonly string[], more: number): string;
243
+ /** CL4 — no build of the phase has been reported. */
244
+ export declare function noBuildLine(phase: string): string;
245
+ /** CL5 — a round is already open and waiting for the owner. */
246
+ export declare function reviewAlreadyOpenLine(phase: string): string;
247
+ /**
248
+ * CL6 — steps of the phase are unfinished: the first line names each with its state,
249
+ * then one line per step from `unfinishedStepLine` (the one composer the typed review
250
+ * prints too), then what to run after.
251
+ */
252
+ export declare function unfinishedStepsLines(phase: string, steps: Parameters<typeof unfinishedStepLine>[0][]): string[];
253
+ /** CL12 — a clean verdict over defects recorded against the phase. */
254
+ export declare function cleanOverFindingsLine(phase: string, findings: number): string;
255
+ /** What the close step prints for one attempt, and the exit code it ends with. */
256
+ export interface CloseStepReport {
257
+ stdout: string[];
258
+ stderr: string[];
259
+ exitCode: 0 | 1;
260
+ }
261
+ /**
262
+ * Every line the close step prints for one attempt. An open prints CL1, then CL2 when
263
+ * the answer says Halfcycle could not count, then H1, then the System view line on
264
+ * `engagementId` (`systemViewReviewLine`), and closes nothing; a commit
265
+ * that could not be read is then said on stderr, with the reasons this verb has always
266
+ * given (git missing and git's bad answer stay two), because the statements carry the
267
+ * commit to the record. A refusal from the service names each field by its flag, as a
268
+ * refusal on this machine does (`describeIssuePath`). A refusal prints
269
+ * its own sentence and exits 1.
181
270
  */
182
- export declare function closePhase(credential: RepoCredential, phase: string, outcome: AcceptanceOutcome, close: PhaseCloseDecision, home?: string, repoRoot?: string, stated?: StatedStatements): Promise<ClosedPhase>;
271
+ export declare function closeStepReport(phase: string, attempt: ReviewOpenAttempt, engagementId: string): CloseStepReport;
183
272
  //# sourceMappingURL=close-phase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"close-phase.d.ts","sourceRoot":"","sources":["../src/close-phase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAmB,KAAK,iBAAiB,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAG/F;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElG;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,4EAA4E;AAC5E,eAAO,MAAM,aAAa,EAAE,gBAAkE,CAAC;AAE/F,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,YAAY,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACvC,iEAAiE;IACjE,WAAW,EAAE,kBAAkB,CAAC;IAChC;;;;;OAKG;IACH,aAAa,EAAE,YAAY,GAAG,IAAI,CAAC;CACpC;AAcD;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,kBAAkB,CA4BhF;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAExB,MAAM,EAAE,MAAM;IAE9B;;;;;OAKG;aACa,OAAO,CAAC,EAAE,MAAM;IAChC;;;;;;;;;;OAUG;aACa,IAAI,CAAC,EAAE,MAAM;gBApBb,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM;IACf;;;;;OAKG;IACa,OAAO,CAAC,EAAE,MAAM,YAAA;IAChC;;;;;;;;;;OAUG;IACa,IAAI,CAAC,EAAE,MAAM,YAAA;CAKhC;AAED;;;GAGG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,cAAc,EAC1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,iBAAiB,EAC1B,KAAK,EAAE,kBAAkB,EACzB,IAAI,CAAC,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,GAAE,gBAAgC,GACvC,OAAO,CAAC,WAAW,CAAC,CAiFtB"}
1
+ {"version":3,"file":"close-phase.d.ts","sourceRoot":"","sources":["../src/close-phase.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,17 @@
18
18
  * just before writing, so a refusal this command can make locally never costs a
19
19
  * request, and a change made while it waited is still seen.
20
20
  *
21
+ * THE CLOSED ARM COMES FIRST, for a phase (`part-state-and-builds` AC5, #1742's forward
22
+ * close half). After the identity and flight-deck checks, `localRefusal` is asked once
23
+ * and the credential is read (`readCredentialOrLine`, `../system-report.ts`) before
24
+ * anything is sent — no description, no plan. `{ picture: 'closed-phase', phase }`
25
+ * is then sent alone. `drawn` is checked and written exactly as steps (4) and (5) above,
26
+ * and nothing else is sent: a closed phase needs no plan file and no fresh description.
27
+ * `refused` stops the verb with the service's reason. Anything else — `not-closed`, a
28
+ * missing route, or the service unavailable — runs the order above unchanged, from (1):
29
+ * a server that does not yet know this phase as closed, or does not know the arm at
30
+ * all, draws the phase exactly as it did before this spec.
31
+ *
21
32
  * A PHASE'S FILES. The base name is `phaseDiagramBase(phase)`, `renderPhaseSegment`'s
22
33
  * segment. A phase that is not a valid identity is refused naming it, before anything
23
34
  * is read; so is one whose base name ends in the Flight Deck word, in any case, whose
@@ -32,15 +43,20 @@
32
43
  * For a phase, the plan route and the draw route's phase arm ship in one service
33
44
  * release, so a missing plan route is the one signal that this service cannot draw
34
45
  * phase pictures, and the draw route is then never asked. Anything else is reported by
35
- * its status and the service's own sentence.
46
+ * its status and the service's own sentence. The answer-parsing switch is exhaustive
47
+ * over `systemDrawResultSchema`'s four outcomes, with a `never` default (`assertNever`):
48
+ * a fifth outcome added to the wire without a matching case here is a compile error.
36
49
  *
37
50
  * NO BYTE OF THE RESPONSE DECIDES A PATH (INV-023): the response gives contents only;
38
51
  * where they go is `./write.ts`'s constants and the base name this module chose from
39
52
  * the arguments.
40
53
  *
41
- * WHAT LEAVES THE MACHINE. The checked description (through the shared stage); for a
42
- * phase, the checked plan (through `plan`'s request); then only the draw request,
43
- * `{ version, picture: 'architecture' }` or `{ picture: 'phase', phase, planVersion }`.
54
+ * WHAT LEAVES THE MACHINE. For the architecture, the checked description (through the
55
+ * shared stage), then `{ version, picture: 'architecture' }`. For a phase, first
56
+ * `{ picture: 'closed-phase', phase }` alone; only once that answers `not-closed` (or a
57
+ * missing route, or the service is unavailable) does the checked description and the
58
+ * checked plan (through `plan`'s request) go out, then `{ picture: 'phase', phase,
59
+ * planVersion }`.
44
60
  *
45
61
  * INV-001: Node builtins, the global `fetch`, `@halfcycle/events` and `@halfcycle/core`
46
62
  * only. SHIPS TO CLIENT DISK inside the CLI: every string here is a sentence a person
@@ -81,7 +97,9 @@ export declare function parseDrawArgs(argv: readonly string[]): DrawArgs;
81
97
  * What came of asking for the drawing. `drawn`: the Mermaid and every look's picture,
82
98
  * parsed under the strict result schema (so no look is missing and none is extra) but
83
99
  * not yet checked. `refused`: the service's reason. `no-route`: this service cannot draw yet.
84
- * `unavailable`: it could not be drawn, and `why` says so.
100
+ * `unavailable`: it could not be drawn, and `why` says so. `not-closed`: the answer to
101
+ * the closed arm alone (`part-state-and-builds` AC5) — the phase has not closed, or was
102
+ * opened again since, so the caller draws it as it stands now with the `phase` arm.
85
103
  */
86
104
  export type DrawOutcome = {
87
105
  kind: 'drawn';
@@ -94,6 +112,8 @@ export type DrawOutcome = {
94
112
  } | {
95
113
  kind: 'unavailable';
96
114
  why: string;
115
+ } | {
116
+ kind: 'not-closed';
97
117
  };
98
118
  /** The address the drawing is asked of: the engagement is one path segment, encoded once. */
99
119
  export declare function systemDrawUrl(credential: RepoCredential): string;
@@ -123,7 +143,10 @@ export type PasteTarget = {
123
143
  };
124
144
  /** The architecture's paste target. */
125
145
  export declare const ARCHITECTURE_PASTE: PasteTarget;
126
- /** A phase's paste target: `Phase <name>`, in `docs/phases/`. */
146
+ /** A phase's paste target: `Phase "<name>"`, in `docs/phases/`. Quoted
147
+ * (agreed difference #41, FW-03's form), same reason `closedPhraseFor`
148
+ * above is: an unquoted name is indistinguishable from Halfcycle's own
149
+ * plan-order number on a project that names a phase a plain digit. */
127
150
  export declare function phasePaste(phase: string): PasteTarget;
128
151
  /** The Markdown to paste into the document `target` names, and the line before it. */
129
152
  export declare function pasteLines(mermaid: string, target: PasteTarget): string;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/draw/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/draw/index.ts"],"names":[],"mappings":"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"}
@@ -83,6 +83,38 @@ export declare function isGitWorkTree(targetRepoRoot: string): boolean;
83
83
  export declare function gitIsInstalled(): boolean;
84
84
  /** Read the `origin` remote URL. */
85
85
  export declare function readRemote(targetRepoRoot: string): string | null;
86
+ /**
87
+ * Remove a password or token from a git remote, leaving the host and path
88
+ * (#1744: `project.json` is committed, so a remote embedding one — a common
89
+ * setup, our own clones have one — put it into the client's repository history
90
+ * and onto GitHub with the next push). `null` passes through unchanged.
91
+ *
92
+ * Both forms git itself accepts are covered, and they are NOT treated alike,
93
+ * because a bare user name means two different things in them:
94
+ *
95
+ * - A URL (`scheme://[user[:pass]@]host[:port]/path` — this also covers
96
+ * `ssh://`) is handed to the platform's own `URL` parser, which strips
97
+ * BOTH username and password whenever either is present. A bare username
98
+ * with no password is included on purpose: GitHub and most hosts accept a
99
+ * personal access token AS the username on an `https://` remote (`https://
100
+ * <token>@host/...`), which is exactly the shape that needs catching with
101
+ * no password field to key off. **When `URL` refuses to parse the string
102
+ * at all** (review R-34, F2 — a non-numeric port is a real remote `git`
103
+ * accepts and `URL` does not), the userinfo is stripped TEXTUALLY instead
104
+ * of leaving the input, credential and all, untouched.
105
+ * - The scp-like syntax git also accepts, with no scheme
106
+ * (`[user[:pass]@]host:path`, e.g. `git@github.com:org/repo.git`), has no
107
+ * parser to borrow, so it is read directly. There a bare username is not a
108
+ * secret — SSH carries no password in the URL at all, and `git@host:path`
109
+ * (or any fixed service account name) is the ordinary, universal shape of
110
+ * an SSH git remote, not a credential. So only a PASSWORD inside the
111
+ * userinfo (`user:pass@host:path`) is stripped here; a bare `user@` is
112
+ * left alone, or every ordinary SSH remote would be rewritten for nothing
113
+ * it is hiding. Split on the LAST `@` before the host (review R-34, F3): a
114
+ * password containing `@` (`user:p@ss@host:path`) made the first `@` leave
115
+ * a password FRAGMENT (`ss@host:path`) in the stored value.
116
+ */
117
+ export declare function redactRemoteCredential(remote: string | null): string | null;
86
118
  /**
87
119
  * How many commits are reachable from HEAD, or `null` when this is not a git repository
88
120
  * (or is one with no commits yet).
@@ -139,21 +171,58 @@ export declare function readIgnoredPaths(targetRepoRoot: string, paths: readonly
139
171
  * on both the mint and the read path, so an install from before this field
140
172
  * existed picks up the disambiguation text on its next install rather than
141
173
  * staying silent forever.
174
+ *
175
+ * `remote` gets a second exception (#1744, decided G1): a file written before this
176
+ * fix existed may hold a remote with a credential in it. `repairRemoteCredential`
177
+ * below is called first, so by the time this function does its own read the file
178
+ * is already clean on disk when that write could land; `redactRemoteCredential`
179
+ * is then applied to the in-memory value too, so what this function RETURNS is
180
+ * clean even on a folder the repair could not write to.
142
181
  */
143
182
  export declare function mintOrReadIdentity(targetRepoRoot: string): {
144
183
  identity: ProjectIdentity;
145
184
  minted: boolean;
146
185
  };
147
186
  /**
148
- * The ONE serializer for `project.json`, called from exactly one place:
149
- * `createProjectIdentityFile` below, to produce the bytes for a freshly minted
150
- * identity. `install()`'s `writeProjectIdentity` no longer calls this function
151
- * itself — until the onboarding fix wave (T-05, from review R-03) it did, to
152
- * reconcile an existing file's bytes against what it would have written and
153
- * rewrite one that differed. That reconciliation is retired: `writeProjectIdentity`
154
- * now only calls `createProjectIdentityFile`, which skips a file that is already
155
- * there, whatever its bytes (`project-identity-reuse` AC1). A file the pre-write
156
- * created is therefore read back as `skipped`, never compared or rewritten.
187
+ * If `.halfcycle/project.json` exists and its `remote` still carries a credential,
188
+ * rewrite the file with it stripped — ONCE; a file whose remote is already clean
189
+ * is never touched. This is the G1 repair (#1744), and it is the ONE exception to
190
+ * "an existing `project.json` is never rewritten" (`createProjectIdentityFile`,
191
+ * the `note` field on `mintOrReadIdentity` above): every other path in this
192
+ * module leaves a present file alone, whatever its bytes.
193
+ *
194
+ * **CALLED FROM EVERY REAL PATH THAT TOUCHES AN EXISTING `project.json`, NOT
195
+ * ONLY FROM `mintOrReadIdentity` (review R-34, F1).** The first version of this
196
+ * fix lived entirely inside `mintOrReadIdentity`, which no production code
197
+ * calls — `install.ts`'s `writeProjectIdentity` and `pre-create-checks.ts`'s
198
+ * `claimFolderId` both skip a `project.json` that is already there and neither
199
+ * read it back, so a real `npx halfcycle` run left the credential in place.
200
+ * Those two call this function directly; `mintOrReadIdentity` calls it too, for
201
+ * every OTHER caller that reads the committed identity.
202
+ *
203
+ * Silently best-effort throughout: an unreadable file, one that is not valid
204
+ * JSON, or one that parses to something other than an object (`null`, an array,
205
+ * a bare string or number — `project-identity-reuse` AC2's "not an object" case,
206
+ * which this function must leave exactly as it was, same as its sibling
207
+ * refusal), or a folder this process cannot write to, is left exactly as it is
208
+ * for the next run to try again — a repair is not what the person asked this
209
+ * run to do, and must never be what makes it fail, let alone throw. Written to
210
+ * a temporary file beside the target first and renamed over it, so a reader
211
+ * never sees a half-written file.
212
+ */
213
+ export declare function repairRemoteCredential(targetRepoRoot: string): void;
214
+ /**
215
+ * The ONE serializer for `project.json`. `createProjectIdentityFile` below calls
216
+ * it to produce the bytes for a freshly minted identity; `repairRemoteCredential`
217
+ * above calls it for the one other case this module ever writes an identity's
218
+ * bytes, the G1 credential repair (#1744). `install()`'s `writeProjectIdentity`
219
+ * no longer reconciles bytes itself — until the onboarding fix wave (T-05, from
220
+ * review R-03) it did, rewriting any file that differed from what it would have
221
+ * written (an older file without `note`, CRLF, an extra field). That
222
+ * reconciliation is retired: an existing file is skipped, whatever its bytes
223
+ * (`project-identity-reuse` AC1), with exactly one exception now — a credential
224
+ * in `remote`, which `writeProjectIdentity` repairs through the function below
225
+ * before reporting `skipped`.
157
226
  */
158
227
  export declare function serializeProjectIdentity(identity: ProjectIdentity): string;
159
228
  /**