halfcycle 0.3.12 → 0.3.13

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "halfcycle",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "description": "Halfcycle Method bundle — resolution-stub slash commands, remote method-delivery registration, and governance-hook wiring for a Halfcycle engagement repo. It ships NO worker role files: a project's roles are authored from that project's own recorded decisions at orchestration kickoff (FX-2, W3-F-27).",
5
5
  "commands": [
6
6
  {
@@ -0,0 +1,73 @@
1
+ /**
2
+ * WHAT THE BANNER SAYS ABOUT THIS RUN — the impure half of `banner.ts`.
3
+ *
4
+ * The banner's whole claim is that it reads the repository before it says anything: the
5
+ * version, the Claude Code beside it, the project it is standing in, whether that project
6
+ * has ever been installed, and which plane it will talk to. All five are knowable before a
7
+ * byte is written, and every one of them is a probe that can fail.
8
+ *
9
+ * **A FACT THAT CANNOT BE ESTABLISHED IS OMITTED, NEVER GUESSED AND NEVER APOLOGISED FOR.**
10
+ * No `unknown`, no `n/a`, no empty row. A developer without `claude` on their PATH is the
11
+ * ordinary case, not a fault to report in a decorative block, and the fact column simply
12
+ * has one fewer row. That rule is why every probe below is wrapped and why each returns
13
+ * `undefined` rather than a placeholder string.
14
+ *
15
+ * **NOTHING HERE MAY THROW.** It runs first, before argument parsing has decided what the
16
+ * command even is, so an exception raised while composing a logo would take down a
17
+ * `check-drift` that was going to work. `openingBannerContent` is total.
18
+ *
19
+ * INV-001: Node builtins and modules inside this package only.
20
+ */
21
+ import { type BannerContent } from './banner.js';
22
+ /**
23
+ * The address on the banner's last line.
24
+ *
25
+ * This is the BRAND's front door, not the plane this run talks to — those are different
26
+ * questions and the `Plane` fact answers the second one. A developer pointed at a
27
+ * development plane should still be told where Halfcycle is.
28
+ */
29
+ export declare const BRAND_URL = "halfcycle.ai";
30
+ /**
31
+ * The tagline, verbatim (operator, 2026-09-01).
32
+ *
33
+ * The web surfaces deliberately say something else; that divergence is recorded as a
34
+ * decision in the phase document, so anybody who arrives here intending to make the two
35
+ * agree should read it first rather than treating this as drift.
36
+ */
37
+ export declare const TAGLINE: readonly string[];
38
+ /** The same tagline stacked for the narrow cut, where the second line does not fit. */
39
+ export declare const TAGLINE_NARROW: readonly string[];
40
+ /** This package's own version, or `undefined` if the manifest cannot be read. */
41
+ export declare function bundleVersion(): string | undefined;
42
+ /**
43
+ * The locally installed Claude Code version, or `undefined`.
44
+ *
45
+ * A short timeout and a swallowed failure, for the reason the install path's own version
46
+ * check gives: `claude` missing from PATH is the normal case for a desktop-app user, and
47
+ * treating it as a problem would put a warning on a correct install. This is the second
48
+ * place in the CLI that asks; they are deliberately separate calls because that one exists
49
+ * to WARN about an old version and this one exists to state a fact, and collapsing them
50
+ * would make the banner responsible for a compatibility check.
51
+ */
52
+ export declare function claudeCodeVersion(): string | undefined;
53
+ /** Everything the fact collector is allowed to reach outside itself, so a test can hand it
54
+ * a world instead of standing in one. */
55
+ export interface FactProbes {
56
+ readonly bundleVersion: () => string | undefined;
57
+ readonly claudeCodeVersion: () => string | undefined;
58
+ readonly projectName: (dir: string) => string | undefined;
59
+ readonly commitCount: (dir: string) => number | null;
60
+ readonly installedVersion: (dir: string) => string | undefined;
61
+ readonly planeHost: (env: NodeJS.ProcessEnv) => string | undefined;
62
+ }
63
+ /** The real probes. Each one is separately replaceable, which is the point of the seam. */
64
+ export declare const REAL_PROBES: FactProbes;
65
+ /**
66
+ * The opening block's content for a run against `projectDir`.
67
+ *
68
+ * `projectDir` is the directory the command is about to act on — the install target, which
69
+ * is the current directory in the bare form and an argument otherwise. The banner names
70
+ * what the run will touch, not where the shell happens to be.
71
+ */
72
+ export declare function openingBannerContent(projectDir: string, env?: NodeJS.ProcessEnv, probes?: FactProbes): BannerContent;
73
+ //# sourceMappingURL=banner-facts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner-facts.d.ts","sourceRoot":"","sources":["../src/banner-facts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,EAAkB,KAAK,aAAa,EAAmB,MAAM,aAAa,CAAC;AAElF;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,iBAAiB,CAAC;AAExC;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,SAAS,MAAM,EAGpC,CAAC;AAEF,uFAAuF;AACvF,eAAO,MAAM,cAAc,EAAE,SAAS,MAAM,EAI3C,CAAC;AAEF,iFAAiF;AACjF,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAOlD;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,GAAG,SAAS,CAWtD;AAED;0CAC0C;AAC1C,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,aAAa,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACjD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IACrD,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC1D,QAAQ,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACrD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/D,QAAQ,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,KAAK,MAAM,GAAG,SAAS,CAAC;CACpE;AAED,2FAA2F;AAC3F,eAAO,MAAM,WAAW,EAAE,UAqCzB,CAAC;AAQF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,MAAM,GAAE,UAAwB,GAC/B,aAAa,CAmCf"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * THE `npx halfcycle` OPENING BANNER — the mark, the name, the tagline and the URL.
3
+ *
4
+ * This module is PURE: it takes content and an environment description and returns a
5
+ * string. Everything that touches the filesystem, git or a subprocess lives in
6
+ * `banner-facts.ts`, so the layout can be compared against its generator without a
7
+ * repository, a network or a clock being involved.
8
+ *
9
+ * ---------------------------------------------------------------------------
10
+ * WHERE THE ART COMES FROM, AND WHY IT IS NOT DRAWN HERE.
11
+ *
12
+ * `MARK_FULL` and `MARK_NARROW` below are the OUTPUT of `docs/design/cli-banner.mjs`,
13
+ * which supersamples the brand mark's real SVG geometry onto a character density ramp.
14
+ * They were produced by running it and are never edited by hand. `cli-banner-drift.test.ts`
15
+ * re-runs that generator and compares, and it also compares the generator's geometry
16
+ * numbers against the SVG they were read from — so the chain
17
+ * `Mark.tsx -> cli-banner.mjs -> the constants below` has a check at every link, and a
18
+ * change to the mark at either end fails the build rather than quietly shipping a logo
19
+ * that is no longer the logo.
20
+ *
21
+ * Because a copy of a generator's output is exactly the shape that rots, the constants
22
+ * carry NO layout decisions of their own beyond the glyphs: the column widths, the gap,
23
+ * the rule length and the centring below reproduce `render()` in that generator, and the
24
+ * drift test asserts the whole rendered block byte-for-byte, not just the art.
25
+ *
26
+ * ---------------------------------------------------------------------------
27
+ * THE FIVE RULES THIS FILE EXISTS TO HOLD.
28
+ *
29
+ * 1. **No dependency.** Raw ANSI, hand-written. The package ships to a client's disk and
30
+ * its runtime dependency list is one entry long; a colour library is not worth a
31
+ * second. Nothing here imports anything at all.
32
+ * 2. **Never a hard-coded foreground colour.** The mark, the fact values and the first
33
+ * tagline line take the terminal's OWN foreground. A surface in this product has
34
+ * already once been invisible on a light-mode machine because it chose its own ink.
35
+ * Sixteen-colour ANSI and nothing else — no truecolor, no 256-colour cube — so it
36
+ * resolves against whatever theme the developer already picked.
37
+ * 3. **Off when it should be off.** `--quiet`, a stdout that is not a TTY, `NO_COLOR`
38
+ * and `TERM=dumb` each have a defined answer, and none of them is "art anyway".
39
+ * 4. **stdout, never stderr.** A banner on stderr turns a successful install into a red
40
+ * block in half the tools that wrap it.
41
+ * 5. **Printable ASCII, with a fallback for the two glyphs that are not.** Only the rule
42
+ * and the palette strip use box-drawing characters; on a console that cannot render
43
+ * them the whole banner is put through `toAscii` instead of shipping mojibake.
44
+ */
45
+ /**
46
+ * One size of the mark as characters.
47
+ *
48
+ * `discRows` is a row COUNT rather than a per-character map because the generator's
49
+ * output happens to split cleanly: every row is either wholly the solid top disc or
50
+ * wholly the outlined bottom ring, at both sizes. The drift test re-derives it from the
51
+ * generator instead of trusting that sentence, so if a geometry change ever mixes the two
52
+ * within one row the test says so rather than the banner silently mis-colouring.
53
+ */
54
+ export interface MarkArt {
55
+ /** The generator's column count — lines are right-trimmed, so this is not their length. */
56
+ readonly width: number;
57
+ /** How many leading rows are the solid disc; the rest are the ring and the divider. */
58
+ readonly discRows: number;
59
+ readonly lines: readonly string[];
60
+ }
61
+ /** The accepted cut: 32 columns wide, 14 rows. Generated by `cli-banner.mjs`. */
62
+ export declare const MARK_FULL: MarkArt;
63
+ /** The narrow cut: the same geometry at 24 columns, 10 rows. Generated by `cli-banner.mjs`. */
64
+ export declare const MARK_NARROW: MarkArt;
65
+ /**
66
+ * The non-ASCII characters the banner is allowed to contain, and nothing else.
67
+ *
68
+ * The mark itself is printable ASCII by construction — that is what makes it survive a
69
+ * console in a legacy code page. These four are the entire remainder, which is what lets
70
+ * a test assert a closed vocabulary rather than a threshold.
71
+ */
72
+ export interface GlyphSet {
73
+ /** The rule under the header. */
74
+ readonly rule: string;
75
+ /** One half of a palette block; the strip is eight pairs of these. */
76
+ readonly swatch: string;
77
+ /** The separator inside a composed fact value, and inside the one-line cut. */
78
+ readonly dot: string;
79
+ /** The dash in a fact value's aside. */
80
+ readonly dash: string;
81
+ }
82
+ export declare const UNICODE_GLYPHS: GlyphSet;
83
+ /** What each of them becomes when the console cannot render it. */
84
+ export declare const ASCII_GLYPHS: GlyphSet;
85
+ /**
86
+ * Force `text` into printable ASCII.
87
+ *
88
+ * The four named glyphs get a chosen substitute; ANYTHING else outside printable ASCII
89
+ * becomes `?`. That last clause is not defensive dressing — a fact value carries a
90
+ * project directory's name, and a developer whose folder is called `café` on a console in
91
+ * a non-UTF-8 code page should see one question mark rather than two bytes of mojibake in
92
+ * the middle of the mark's alignment. Every substitution is length-preserving, so it
93
+ * cannot break the column arithmetic.
94
+ */
95
+ export declare function toAscii(text: string): string;
96
+ /**
97
+ * One labelled row of the fact column.
98
+ *
99
+ * `narrow` is both the value used in the narrow cut AND the marker that the fact belongs
100
+ * in it: a fact with no `narrow` is dropped there. Two of the five facts are, which is
101
+ * what makes the narrow block fit beside a mark half the width without clipping every
102
+ * value into meaninglessness.
103
+ */
104
+ export interface BannerFact {
105
+ readonly label: string;
106
+ readonly value: string;
107
+ readonly narrow?: string;
108
+ }
109
+ export interface BannerContent {
110
+ /** `halfcycle@my-app` — split on the first `@` for colouring. */
111
+ readonly head: string;
112
+ readonly facts: readonly BannerFact[];
113
+ /** The tagline, one entry per line. The first line is bold, the rest dim. */
114
+ readonly tag: readonly string[];
115
+ /** The tagline re-wrapped for the narrow cut; falls back to `tag`. */
116
+ readonly tagNarrow?: readonly string[];
117
+ readonly url: string;
118
+ /** The version the one-line cut reports. */
119
+ readonly version: string;
120
+ }
121
+ /**
122
+ * `none` prints nothing at all; `line` is the single line a pipe or a CI log gets;
123
+ * `narrow` and `full` are the two art cuts.
124
+ */
125
+ export type BannerCut = 'none' | 'line' | 'narrow' | 'full';
126
+ /** What `process.stdout.columns` is taken to be when it does not report a usable width. */
127
+ export declare const DEFAULT_COLUMNS = 80;
128
+ /** Everything the banner needs to know about the world it is printing into. */
129
+ export interface BannerEnvironment {
130
+ readonly columns?: number | undefined;
131
+ readonly isTTY?: boolean | undefined;
132
+ /** `--quiet` was passed. */
133
+ readonly quiet?: boolean | undefined;
134
+ readonly env?: Readonly<Record<string, string | undefined>> | undefined;
135
+ /** `process.platform`; only the Windows console needs a different unicode answer. */
136
+ readonly platform?: string | undefined;
137
+ }
138
+ /**
139
+ * Which cut this run prints.
140
+ *
141
+ * The order is the order the reasons override each other: an explicit `--quiet` beats
142
+ * everything, then the shape of the output stream, then the width of the terminal.
143
+ */
144
+ export declare function decideCut(environment?: BannerEnvironment): BannerCut;
145
+ /**
146
+ * Is colour emitted?
147
+ *
148
+ * `FORCE_COLOR` is checked first and both ways, because it is the only explicit statement
149
+ * in the set: `FORCE_COLOR=0` turns colour off in a terminal that would otherwise have it,
150
+ * and any other value turns it on. Everything else is inference.
151
+ */
152
+ export declare function decideColour(environment?: BannerEnvironment): boolean;
153
+ /**
154
+ * Can this console render the rule and the palette strip?
155
+ *
156
+ * Everywhere but Windows, the locale answers: an explicitly non-UTF-8 `LANG`/`LC_ALL`
157
+ * means no, and an unset locale — the ordinary case under `npx` in a launcher-started
158
+ * terminal — is taken as yes. On Windows the locale says nothing about the console's code
159
+ * page, so the answer is the presence of a host known to run in UTF-8; the old
160
+ * `cmd.exe`/`powershell.exe` console is assumed to be a legacy code page, which is the
161
+ * conservative direction: the cost of being wrong is a mark drawn with `-` and `#`.
162
+ */
163
+ export declare function decideUnicode(environment?: BannerEnvironment): boolean;
164
+ /**
165
+ * The banner as a string, with no trailing newline. `''` means print nothing — the caller
166
+ * must not turn that into a blank line.
167
+ */
168
+ export declare function renderBanner(content: BannerContent, environment?: BannerEnvironment): string;
169
+ /** Somewhere to write the banner. `process.stdout` satisfies it; so does a test double. */
170
+ export interface BannerSink {
171
+ write(chunk: string): unknown;
172
+ }
173
+ /**
174
+ * Print the banner — to the sink the caller names, which is always stdout.
175
+ *
176
+ * Nothing is written at all when the banner is suppressed, not even the newline: a
177
+ * `--quiet` run and a piped run should differ from an unsuppressed one by the banner, not
178
+ * by a blank line the next tool has to strip.
179
+ */
180
+ export declare function printBanner(sink: BannerSink, content: BannerContent, environment?: BannerEnvironment): void;
181
+ /**
182
+ * The environment as this process actually finds it.
183
+ *
184
+ * The one place `process` is read, so every other function here can be handed a
185
+ * description of a world instead of running in one.
186
+ */
187
+ export declare function currentEnvironment(quiet: boolean): BannerEnvironment;
188
+ //# sourceMappingURL=banner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.d.ts","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"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"}