hub-launch 1.16.0 → 1.17.0
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.
- package/CHANGELOG.md +6 -0
- package/README.md +101 -2
- package/dist/commands/init.d.ts +55 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +93 -103
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/instructions.d.ts +17 -0
- package/dist/commands/instructions.d.ts.map +1 -0
- package/dist/commands/instructions.js +51 -0
- package/dist/commands/instructions.js.map +1 -0
- package/dist/commands/launch.d.ts +21 -0
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +61 -0
- package/dist/commands/launch.js.map +1 -1
- package/dist/commands/script.d.ts +28 -0
- package/dist/commands/script.d.ts.map +1 -0
- package/dist/commands/script.js +69 -0
- package/dist/commands/script.js.map +1 -0
- package/dist/commands/session-hook.d.ts +26 -0
- package/dist/commands/session-hook.d.ts.map +1 -0
- package/dist/commands/session-hook.js +146 -0
- package/dist/commands/session-hook.js.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/scripts/fix-commit.d.ts +2 -0
- package/dist/scripts/fix-commit.d.ts.map +1 -0
- package/dist/scripts/fix-commit.js +125 -0
- package/dist/scripts/fix-commit.js.map +1 -0
- package/dist/scripts/fix-setup.d.ts +2 -0
- package/dist/scripts/fix-setup.d.ts.map +1 -0
- package/dist/scripts/fix-setup.js +161 -0
- package/dist/scripts/fix-setup.js.map +1 -0
- package/dist/scripts/launch-run.d.ts +14 -0
- package/dist/scripts/launch-run.d.ts.map +1 -0
- package/dist/scripts/launch-run.js +117 -0
- package/dist/scripts/launch-run.js.map +1 -0
- package/dist/scripts/lib/exec.d.ts +48 -0
- package/dist/scripts/lib/exec.d.ts.map +1 -0
- package/dist/scripts/lib/exec.js +61 -0
- package/dist/scripts/lib/exec.js.map +1 -0
- package/dist/scripts/lib/io.d.ts +48 -0
- package/dist/scripts/lib/io.d.ts.map +1 -0
- package/dist/scripts/lib/io.js +72 -0
- package/dist/scripts/lib/io.js.map +1 -0
- package/dist/scripts/lib/read-config.d.ts +26 -0
- package/dist/scripts/lib/read-config.d.ts.map +1 -0
- package/dist/scripts/lib/read-config.js +65 -0
- package/dist/scripts/lib/read-config.js.map +1 -0
- package/dist/scripts/merge-local.d.ts +2 -0
- package/dist/scripts/merge-local.d.ts.map +1 -0
- package/dist/scripts/merge-local.js +146 -0
- package/dist/scripts/merge-local.js.map +1 -0
- package/dist/scripts/merge-remote.d.ts +12 -0
- package/dist/scripts/merge-remote.d.ts.map +1 -0
- package/dist/scripts/merge-remote.js +71 -0
- package/dist/scripts/merge-remote.js.map +1 -0
- package/dist/scripts/schedule-manage.d.ts +2 -0
- package/dist/scripts/schedule-manage.d.ts.map +1 -0
- package/dist/scripts/schedule-manage.js +151 -0
- package/dist/scripts/schedule-manage.js.map +1 -0
- package/dist/scripts/schedule-run.d.ts +2 -0
- package/dist/scripts/schedule-run.d.ts.map +1 -0
- package/dist/scripts/schedule-run.js +146 -0
- package/dist/scripts/schedule-run.js.map +1 -0
- package/dist/scripts/verify-gather.d.ts +2 -0
- package/dist/scripts/verify-gather.d.ts.map +1 -0
- package/dist/scripts/verify-gather.js +210 -0
- package/dist/scripts/verify-gather.js.map +1 -0
- package/dist/scripts/verify-post.d.ts +12 -0
- package/dist/scripts/verify-post.d.ts.map +1 -0
- package/dist/scripts/verify-post.js +72 -0
- package/dist/scripts/verify-post.js.map +1 -0
- package/dist/templates/planning-instructions.md +7 -7
- package/dist/templates/skill-creation-instructions.md +2 -2
- package/dist/templates/skills/hula-confirm/SKILL.md +3 -3
- package/dist/templates/skills/hula-fix/SKILL.md +3 -3
- package/dist/templates/skills/hula-launch/SKILL.md +14 -8
- package/dist/templates/skills/hula-merge/SKILL.md +2 -2
- package/dist/templates/skills/hula-plan/SKILL.md +5 -5
- package/dist/templates/skills/hula-schedule/SKILL.md +16 -16
- package/dist/templates/skills/hula-verify/SKILL.md +2 -2
- package/dist/types/config.schema.d.ts +235 -0
- package/dist/types/config.schema.d.ts.map +1 -1
- package/dist/types/config.schema.js +66 -0
- package/dist/types/config.schema.js.map +1 -1
- package/dist/utils/client-session.d.ts +2 -2
- package/dist/utils/client-session.js +2 -2
- package/package.json +6 -5
- package/scripts/postinstall.mjs +101 -0
- package/dist/templates/scripts/hula-fix-commit.sh +0 -114
- package/dist/templates/scripts/hula-fix-setup.sh +0 -152
- package/dist/templates/scripts/hula-launch-run.sh +0 -140
- package/dist/templates/scripts/hula-merge-local.sh +0 -138
- package/dist/templates/scripts/hula-merge-remote.sh +0 -94
- package/dist/templates/scripts/hula-read-config.sh +0 -38
- package/dist/templates/scripts/hula-schedule-manage.sh +0 -159
- package/dist/templates/scripts/hula-schedule-run.sh +0 -148
- package/dist/templates/scripts/hula-session-hook.sh +0 -68
- package/dist/templates/scripts/hula-verify-gather.sh +0 -135
- package/dist/templates/scripts/hula-verify-post.sh +0 -84
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.17.0] - 2026-07-23
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `hula launch` now supports per-pipeline-step model and iteration-cap overrides via a `steps` block in `.hublaunch/hublaunch.config.js` (hula-server PR #442), plus a `--skip-regression` flag to force-skip regression tests for a single launch. Conflicting `bugfix.model`/`mergeConflict.model` values, or `regression.skip` combined with the legacy `--regression` flag, are rejected locally before any network call.
|
|
15
|
+
|
|
10
16
|
## [1.16.0] - 2026-07-17
|
|
11
17
|
|
|
12
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -82,8 +82,9 @@ After upgrading the CLI:
|
|
|
82
82
|
npm install -g hub-launch@latest # or: pnpm add -g hub-launch
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
re-run `hula init` inside each project to refresh the bundled Agent Skills
|
|
86
|
-
|
|
85
|
+
re-run `hula init` inside each project to refresh the bundled Agent Skills and
|
|
86
|
+
config scaffolding (workflow scripts and instruction docs now ship with the
|
|
87
|
+
package, so they update automatically with the CLI):
|
|
87
88
|
|
|
88
89
|
```bash
|
|
89
90
|
cd <your-project>
|
|
@@ -149,9 +150,41 @@ Top-level commands:
|
|
|
149
150
|
| `hula merge` | — | Merge PR and clean up |
|
|
150
151
|
| `hula launch` | — | Trigger AI coding session on hula-project server |
|
|
151
152
|
| `hula schedule` | — | Trigger or schedule an execute-action (e.g. `--built-in harden`) |
|
|
153
|
+
| `hula script <name>` | — | Run a bundled cross-platform workflow script (used by the Agent Skills) |
|
|
154
|
+
| `hula instructions <name>` | — | Print a bundled instruction doc (`planning`, `proceed`, `skill-creation`) |
|
|
155
|
+
| `hula session-hook` | — | Claude Code PreToolUse hook that captures launch-session provenance |
|
|
152
156
|
|
|
153
157
|
Run `hula <command> --help` for details, or see the full [Commands Reference](./docs/commands.md).
|
|
154
158
|
|
|
159
|
+
### Cross-platform, low-footprint tooling
|
|
160
|
+
|
|
161
|
+
The workflow scripts and instruction documents that power the Agent Skills ship
|
|
162
|
+
**inside the `hula` package** as cross-platform Node — they are no longer copied
|
|
163
|
+
into your repo, and they need **no `bash` and no `jq`**, so they run identically
|
|
164
|
+
on macOS, Linux, and Windows. `hula init` therefore does not write
|
|
165
|
+
`.github/scripts/*.sh` or `.hublaunch/*-instructions.md`.
|
|
166
|
+
|
|
167
|
+
Skills invoke them via the global bin:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
# Run a bundled workflow script (cross-platform, no bash/jq)
|
|
171
|
+
hula script merge-local -- 42 ".hula-worktrees/issue-42" "fix(#42): message"
|
|
172
|
+
|
|
173
|
+
# Print a bundled instruction document
|
|
174
|
+
hula instructions planning
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Migration:** upgrading from an older version? Existing
|
|
178
|
+
`.github/scripts/hula-*.sh` and `.hublaunch/*-instructions.md` keep working, but
|
|
179
|
+
are now obsolete and safe to delete — `hula init` prints a reminder listing them
|
|
180
|
+
(it never deletes anything).
|
|
181
|
+
|
|
182
|
+
**Claude Code commands:** the `/hula-*` commands are available as a Claude Code
|
|
183
|
+
plugin so they load globally, so `hula init` no longer writes `.claude/commands/*`
|
|
184
|
+
symlinks by default. Prefer repo-local command files instead? Run
|
|
185
|
+
`hula init --with-claude-commands`. The committed `.agents/skills/` directory
|
|
186
|
+
(read by GitHub Copilot, Cursor, Codex, and 30+ tools) is unchanged.
|
|
187
|
+
|
|
155
188
|
## `hula launch` and Resume
|
|
156
189
|
|
|
157
190
|
`hula launch` submits a job to the hula-project server, which runs an AI coding agent (Claude Code) through a fixed 9-step pipeline:
|
|
@@ -281,6 +314,72 @@ variables, and includes them in the request to the server. Notes:
|
|
|
281
314
|
- Only forward variables your tests actually need; treat anything you list as
|
|
282
315
|
leaving your machine.
|
|
283
316
|
|
|
317
|
+
### Configuring per-step model & iteration overrides
|
|
318
|
+
|
|
319
|
+
`hula launch` runs your plan through a fixed 9-step server-side pipeline. By
|
|
320
|
+
default every step uses the server's built-in model, loop-iteration cap, and
|
|
321
|
+
skip behavior. To override those per step for your project, add a `steps` block
|
|
322
|
+
to `.hublaunch/hublaunch.config.js` (config-file only — this is a persistent
|
|
323
|
+
project setting, not a per-launch flag):
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
export const config = {
|
|
327
|
+
// ...
|
|
328
|
+
steps: {
|
|
329
|
+
implementation: { model: "opus" },
|
|
330
|
+
lintfix: { model: "haiku", maxIterations: 2 },
|
|
331
|
+
regression: { skip: true },
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Each of the 9 keys maps to a pipeline step. All fields are optional; omit a step
|
|
337
|
+
(or the whole `steps` block) to keep the server default:
|
|
338
|
+
|
|
339
|
+
| Step key | `model` | `maxIterations` | `skip` | Other |
|
|
340
|
+
| ---------------- | :-----: | :-------------: | :----: | ----- |
|
|
341
|
+
| `implementation` | ✅ | | | |
|
|
342
|
+
| `findBugs` | ✅ | ✅ (1–20) | | `diffMaxLines` (≥1), `excludeRegex` (string) |
|
|
343
|
+
| `bugfix` | ✅ | | | |
|
|
344
|
+
| `lintfix` | ✅ | ✅ (1–20) | | |
|
|
345
|
+
| `build` | ✅ | ✅ (1–20) | | |
|
|
346
|
+
| `regression` | | | ✅ | |
|
|
347
|
+
| `mergeConflict` | ✅ | | | |
|
|
348
|
+
| `summary` | ✅ | ✅ (1–20) | | |
|
|
349
|
+
| `verify` | ✅ | ✅ (1–20) | ✅ | |
|
|
350
|
+
|
|
351
|
+
`model` values are free-form strings passed straight through to the server
|
|
352
|
+
(`claude --model <value>`); no allowlist is enforced. Values are validated at
|
|
353
|
+
config-load time — an out-of-range `maxIterations`, a non-boolean `skip`, or an
|
|
354
|
+
empty `model` string fails immediately with a clear error.
|
|
355
|
+
|
|
356
|
+
#### `--skip-regression`
|
|
357
|
+
|
|
358
|
+
As a per-launch counterpart to the config `steps.regression.skip`, `hula launch`
|
|
359
|
+
accepts a `--skip-regression` flag that force-skips the regression-tests step for
|
|
360
|
+
that one invocation (it wins over whatever `steps.regression.skip` is set to in
|
|
361
|
+
config):
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
# Skip regression tests for a single launch
|
|
365
|
+
hula launch feature-auth .hublaunch/plans/my-plan.md --skip-regression
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### Conflict rules (validated locally before any network call)
|
|
369
|
+
|
|
370
|
+
Two combinations are rejected client-side — `hula launch` exits `1` immediately
|
|
371
|
+
with the same message the server would return, so misconfigurations fail fast:
|
|
372
|
+
|
|
373
|
+
- **`bugfix.model` ≠ `mergeConflict.model`** — both map to the same
|
|
374
|
+
`RALPH_BUGFIX_MODEL` env var on the server, so they cannot be set to different
|
|
375
|
+
values:
|
|
376
|
+
`bugfix.model and mergeConflict.model both map to RALPH_BUGFIX_MODEL and cannot conflict`
|
|
377
|
+
- **`regression.skip: true` combined with the legacy `--regression` flag** — one
|
|
378
|
+
forces the step off, the other forces it on. This also covers passing both
|
|
379
|
+
`--skip-regression` and `--regression`, and a config `steps.regression.skip:
|
|
380
|
+
true` combined with a one-off `--regression`:
|
|
381
|
+
`regression.skip and the legacy regression flag are contradictory`
|
|
382
|
+
|
|
284
383
|
## `hula schedule`
|
|
285
384
|
|
|
286
385
|
`hula schedule` triggers a built-in or custom execute-action (e.g. the `harden` security audit) on the hula-project server, which provisions a sandbox to run it and opens a PR / plan / feedback as the outcome.
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -40,7 +40,62 @@ export declare function initCommand(program: Command, _config: Config): void;
|
|
|
40
40
|
* Generate hook templates based on authentication provider
|
|
41
41
|
*/
|
|
42
42
|
export declare function generateHookTemplates(projectRoot: string, authProvider: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Ensure the `.hublaunch/skills/` action-file directory exists.
|
|
45
|
+
*
|
|
46
|
+
* The planning/proceed/skill-creation instruction documents are no longer
|
|
47
|
+
* copied into the consumer repo — they ship inside the `hula` package and are
|
|
48
|
+
* read at runtime via `hula instructions <planning|proceed|skill-creation>`.
|
|
49
|
+
* This keeps the project footprint to genuinely project-specific files.
|
|
50
|
+
*
|
|
51
|
+
* Only the `.hublaunch/skills/` directory (user-authored /hula-schedule action
|
|
52
|
+
* files) is created here; files committed there persist (it is not gitignored).
|
|
53
|
+
*/
|
|
54
|
+
export declare function initializeSkillsActionDir(projectRoot: string): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Initialize Agent Skills for AI-assisted hula workflows.
|
|
57
|
+
*
|
|
58
|
+
* Single source of truth: .agents/skills/<name>/SKILL.md (Agent Skills spec).
|
|
59
|
+
*
|
|
60
|
+
* Each harness gets a reference to that canonical file:
|
|
61
|
+
* - GitHub Copilot, Cursor, Codex, and other Agent Skills-compatible tools
|
|
62
|
+
* read directly from .agents/skills/<name>/SKILL.md.
|
|
63
|
+
* - Claude Code reads from .claude/commands/<name>.md, which is created as
|
|
64
|
+
* a symlink pointing into .agents/skills/ (relative path so it works after
|
|
65
|
+
* cloning on any machine). No content is duplicated.
|
|
66
|
+
*
|
|
67
|
+
* Because the symlinks are named <name>.md (not SKILL.md) and live under
|
|
68
|
+
* .claude/commands/ (not .agents/skills/), GitHub Copilot's Agent Skills
|
|
69
|
+
* scanner does not pick them up, so no duplicate commands appear.
|
|
70
|
+
*
|
|
71
|
+
* Any existing .claude/skills/ directory is removed on init to clean up
|
|
72
|
+
* legacy installations that previously wrote full files there.
|
|
73
|
+
*
|
|
74
|
+
* Agent Skills standard: https://agentskills.io/specification
|
|
75
|
+
*/
|
|
76
|
+
export declare function initializeAgentSkills(projectRoot: string, withClaudeCommands: boolean): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Detect tool-owned files from earlier hula versions that are now obsolete and
|
|
79
|
+
* print a non-destructive notice. `hula init` no longer generates
|
|
80
|
+
* `.github/scripts/hula-*.sh` or `.hublaunch/*-instructions.md` — those scripts
|
|
81
|
+
* and instruction docs ship inside the `hula` package now (run via
|
|
82
|
+
* `hula script <name>` / `hula instructions <name>`). Pre-existing copies are
|
|
83
|
+
* left untouched (they still work) but can be safely deleted.
|
|
84
|
+
*/
|
|
85
|
+
export declare function noticeObsoleteFiles(projectRoot: string): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Register the hula session hook in the consumer repo's .claude/settings.json.
|
|
88
|
+
*
|
|
89
|
+
* Adds a PreToolUse/Bash hook entry that captures the Claude Code session id
|
|
90
|
+
* of the chat session running /hula-launch (clientSessionId provenance —
|
|
91
|
+
* hula-server PR #419). Unlike initVsCodeAutoApproval's single-key overwrite,
|
|
92
|
+
* hooks live in a NESTED ARRAY (`hooks.PreToolUse[]`) that users may already
|
|
93
|
+
* populate, so this merges: it preserves every existing hook and appends ours
|
|
94
|
+
* only when absent (idempotent by the command string).
|
|
95
|
+
*/
|
|
96
|
+
export declare function initClaudeSessionHook(projectRoot: string): Promise<void>;
|
|
43
97
|
export declare function executeInit(options?: {
|
|
44
98
|
force?: boolean;
|
|
99
|
+
withClaudeCommands?: boolean;
|
|
45
100
|
}): Promise<void>;
|
|
46
101
|
//# sourceMappingURL=init.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAahD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAczC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAahD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,aAmBhC,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAe9E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAuBnE;AA0bD;;GAEG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CA+Bf;AAwED;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQlF;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,qBAAqB,CACzC,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,OAAO,GAC1B,OAAO,CAAC,IAAI,CAAC,CAmEf;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC5E;AAwED;;;;;;;;;GASG;AACH,wBAAsB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD9E;AAsCD,wBAAsB,WAAW,CAAC,OAAO,CAAC,EAAE;IAC1C,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,GAAG,OAAO,CAAC,IAAI,CAAC,CAohBhB"}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFile, readFile, mkdir, copyFile,
|
|
1
|
+
import { writeFile, readFile, readdir, mkdir, copyFile, rm, symlink, } from 'fs/promises';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
3
|
import { join, relative, isAbsolute } from 'path';
|
|
4
4
|
import { logger } from '../utils/logger.js';
|
|
@@ -45,6 +45,7 @@ export const TEMPLATE_EMITTED_KEYS = new Set([
|
|
|
45
45
|
'hooks',
|
|
46
46
|
'worktreeBasePath',
|
|
47
47
|
'envVars',
|
|
48
|
+
'steps',
|
|
48
49
|
]);
|
|
49
50
|
/**
|
|
50
51
|
* Build the `// Preserved custom settings` block for any keys present in the
|
|
@@ -75,6 +76,7 @@ export function initCommand(program, _config) {
|
|
|
75
76
|
.command('init')
|
|
76
77
|
.description('Initialize HubLaunch configuration (interactive)')
|
|
77
78
|
.option('--force', 'Overwrite existing configuration')
|
|
79
|
+
.option('--with-claude-commands', 'Also write repo-local .claude/commands/* symlinks (default: use the global hula Claude Code plugin instead)')
|
|
78
80
|
.action(async (options) => {
|
|
79
81
|
try {
|
|
80
82
|
await executeInit(options);
|
|
@@ -607,41 +609,17 @@ const AGENT_SKILLS = [
|
|
|
607
609
|
{ templateDir: 'hula-info', skillName: 'hula-info' },
|
|
608
610
|
];
|
|
609
611
|
/**
|
|
610
|
-
*
|
|
611
|
-
*
|
|
612
|
+
* Ensure the `.hublaunch/skills/` action-file directory exists.
|
|
613
|
+
*
|
|
614
|
+
* The planning/proceed/skill-creation instruction documents are no longer
|
|
615
|
+
* copied into the consumer repo — they ship inside the `hula` package and are
|
|
616
|
+
* read at runtime via `hula instructions <planning|proceed|skill-creation>`.
|
|
617
|
+
* This keeps the project footprint to genuinely project-specific files.
|
|
618
|
+
*
|
|
619
|
+
* Only the `.hublaunch/skills/` directory (user-authored /hula-schedule action
|
|
620
|
+
* files) is created here; files committed there persist (it is not gitignored).
|
|
612
621
|
*/
|
|
613
|
-
async function
|
|
614
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
615
|
-
const __dirname = dirname(__filename);
|
|
616
|
-
const templatesSourceDir = join(__dirname, '..', 'templates');
|
|
617
|
-
const instructionsTemplatePath = join(templatesSourceDir, 'planning-instructions.md');
|
|
618
|
-
const instructionsDestPath = join(projectRoot, '.hublaunch', 'planning-instructions.md');
|
|
619
|
-
if (!existsSync(instructionsDestPath) || overridePlanningInstructions) {
|
|
620
|
-
await copyFile(instructionsTemplatePath, instructionsDestPath);
|
|
621
|
-
logger.success('Updated: .hublaunch/planning-instructions.md');
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
logger.info('Keeping existing planning instructions');
|
|
625
|
-
}
|
|
626
|
-
const proceedInstructionsTemplatePath = join(templatesSourceDir, 'proceed-instructions.md');
|
|
627
|
-
const proceedInstructionsDestPath = join(projectRoot, '.hublaunch', 'proceed-instructions.md');
|
|
628
|
-
if (!existsSync(proceedInstructionsDestPath) || overrideProceedInstructions) {
|
|
629
|
-
await copyFile(proceedInstructionsTemplatePath, proceedInstructionsDestPath);
|
|
630
|
-
logger.success('Updated: .hublaunch/proceed-instructions.md');
|
|
631
|
-
}
|
|
632
|
-
else {
|
|
633
|
-
logger.info('Keeping existing proceed instructions');
|
|
634
|
-
}
|
|
635
|
-
// Skill-creation instructions are read by the /hula-schedule skill at runtime
|
|
636
|
-
// (parallel to how /hula-plan reads planning-instructions.md). They are
|
|
637
|
-
// deterministic tooling, so always refresh them with the latest template.
|
|
638
|
-
const skillCreationTemplatePath = join(templatesSourceDir, 'skill-creation-instructions.md');
|
|
639
|
-
const skillCreationDestPath = join(projectRoot, '.hublaunch', 'skill-creation-instructions.md');
|
|
640
|
-
await copyFile(skillCreationTemplatePath, skillCreationDestPath);
|
|
641
|
-
logger.success('Updated: .hublaunch/skill-creation-instructions.md');
|
|
642
|
-
// Ensure the action-file directory used by the /hula-schedule skill exists.
|
|
643
|
-
// Files committed here persist (it is not gitignored). Seed a .gitkeep so the
|
|
644
|
-
// empty directory survives in git until the first action file is created.
|
|
622
|
+
export async function initializeSkillsActionDir(projectRoot) {
|
|
645
623
|
const skillsDir = join(projectRoot, '.hublaunch', 'skills');
|
|
646
624
|
await mkdir(skillsDir, { recursive: true });
|
|
647
625
|
const gitkeepPath = join(skillsDir, '.gitkeep');
|
|
@@ -671,16 +649,20 @@ async function initializePlanningInstructions(projectRoot, overridePlanningInstr
|
|
|
671
649
|
*
|
|
672
650
|
* Agent Skills standard: https://agentskills.io/specification
|
|
673
651
|
*/
|
|
674
|
-
async function initializeAgentSkills(projectRoot) {
|
|
652
|
+
export async function initializeAgentSkills(projectRoot, withClaudeCommands) {
|
|
675
653
|
const __filename = fileURLToPath(import.meta.url);
|
|
676
654
|
const __dirname = dirname(__filename);
|
|
677
655
|
const skillsSourceDir = join(__dirname, '..', 'templates', 'skills');
|
|
678
656
|
// Canonical location — single source of truth.
|
|
679
657
|
const agentsSkillsDir = join(projectRoot, '.agents', 'skills');
|
|
680
658
|
await mkdir(agentsSkillsDir, { recursive: true });
|
|
681
|
-
// Claude Code reads from .claude/commands
|
|
659
|
+
// Claude Code reads from .claude/commands/ — only when the opt-in flag asks
|
|
660
|
+
// for repo-local commands. By default the /hula-* commands come from the hula
|
|
661
|
+
// Claude Code plugin (installed globally), so no symlinks are written.
|
|
682
662
|
const claudeCommandsDir = join(projectRoot, '.claude', 'commands');
|
|
683
|
-
|
|
663
|
+
if (withClaudeCommands) {
|
|
664
|
+
await mkdir(claudeCommandsDir, { recursive: true });
|
|
665
|
+
}
|
|
684
666
|
// Remove .claude/skills/ if it exists — legacy path that caused duplicates.
|
|
685
667
|
const claudeSkillsDir = join(projectRoot, '.claude', 'skills');
|
|
686
668
|
try {
|
|
@@ -695,63 +677,66 @@ async function initializeAgentSkills(projectRoot) {
|
|
|
695
677
|
const agentsDestDir = join(agentsSkillsDir, skillName);
|
|
696
678
|
await mkdir(agentsDestDir, { recursive: true });
|
|
697
679
|
await copyFile(srcSkillMd, join(agentsDestDir, 'SKILL.md'));
|
|
698
|
-
//
|
|
699
|
-
// Relative path so
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
680
|
+
// Optionally create a symlink in .claude/commands/ pointing back to the
|
|
681
|
+
// canonical file. Relative path so it works after cloning on any machine.
|
|
682
|
+
if (withClaudeCommands) {
|
|
683
|
+
const symlinkPath = join(claudeCommandsDir, `${skillName}.md`);
|
|
684
|
+
const symlinkTarget = join('..', '..', '.agents', 'skills', skillName, 'SKILL.md');
|
|
685
|
+
// Remove any existing file/symlink before (re-)creating.
|
|
686
|
+
await rm(symlinkPath, { force: true });
|
|
687
|
+
await symlink(symlinkTarget, symlinkPath);
|
|
688
|
+
}
|
|
705
689
|
logger.success(`Updated skill: ${skillName}`);
|
|
706
690
|
}
|
|
707
691
|
logger.blank();
|
|
708
692
|
logger.info('🎉 Agent Skills installed — works with Claude Code, GitHub Copilot, Codex, and 30+ AI tools');
|
|
709
693
|
logger.info('💡 Invoke with /hula-plan, /hula-fix, /hula-create, etc. (note: hyphens, not dots)');
|
|
694
|
+
if (withClaudeCommands) {
|
|
695
|
+
logger.info('🔗 Repo-local Claude Code commands written to .claude/commands/ (--with-claude-commands).');
|
|
696
|
+
}
|
|
697
|
+
else {
|
|
698
|
+
logger.info('📦 For Claude Code, /hula-* commands are installed globally to ~/.claude/commands/ when hula is installed/built — no per-project files needed.');
|
|
699
|
+
logger.info(' If they are missing, reinstall hula (or run its postinstall), or use `hula init --with-claude-commands` for repo-local command files.');
|
|
700
|
+
}
|
|
710
701
|
}
|
|
711
702
|
/**
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
703
|
+
* Detect tool-owned files from earlier hula versions that are now obsolete and
|
|
704
|
+
* print a non-destructive notice. `hula init` no longer generates
|
|
705
|
+
* `.github/scripts/hula-*.sh` or `.hublaunch/*-instructions.md` — those scripts
|
|
706
|
+
* and instruction docs ship inside the `hula` package now (run via
|
|
707
|
+
* `hula script <name>` / `hula instructions <name>`). Pre-existing copies are
|
|
708
|
+
* left untouched (they still work) but can be safely deleted.
|
|
715
709
|
*/
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
'
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
]
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
// Create .github/scripts directory
|
|
747
|
-
await mkdir(scriptsDestDir, { recursive: true });
|
|
748
|
-
for (const scriptName of BUNDLED_SCRIPTS) {
|
|
749
|
-
const srcPath = join(scriptsSourceDir, scriptName);
|
|
750
|
-
const destPath = join(scriptsDestDir, scriptName);
|
|
751
|
-
// Always overwrite scripts with latest version (they are deterministic tooling, not user-customized)
|
|
752
|
-
await copyFile(srcPath, destPath);
|
|
753
|
-
await chmod(destPath, 0o755);
|
|
754
|
-
logger.success(`Updated script: .github/scripts/${scriptName}`);
|
|
710
|
+
export async function noticeObsoleteFiles(projectRoot) {
|
|
711
|
+
const obsolete = [];
|
|
712
|
+
const scriptsDir = join(projectRoot, '.github', 'scripts');
|
|
713
|
+
if (existsSync(scriptsDir)) {
|
|
714
|
+
try {
|
|
715
|
+
const entries = await readdir(scriptsDir);
|
|
716
|
+
if (entries.some((f) => f.startsWith('hula-') && f.endsWith('.sh'))) {
|
|
717
|
+
obsolete.push('.github/scripts/hula-*.sh');
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch {
|
|
721
|
+
// Unreadable dir — skip the notice rather than fail init.
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
for (const name of [
|
|
725
|
+
'planning-instructions.md',
|
|
726
|
+
'proceed-instructions.md',
|
|
727
|
+
'skill-creation-instructions.md',
|
|
728
|
+
]) {
|
|
729
|
+
if (existsSync(join(projectRoot, '.hublaunch', name))) {
|
|
730
|
+
obsolete.push(`.hublaunch/${name}`);
|
|
731
|
+
break;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (obsolete.length > 0) {
|
|
735
|
+
logger.blank();
|
|
736
|
+
logger.info('ℹ️ These hula files are now obsolete and safe to delete (scripts & instructions ship with the hula package):');
|
|
737
|
+
for (const f of obsolete) {
|
|
738
|
+
logger.listItem(f, 1);
|
|
739
|
+
}
|
|
755
740
|
}
|
|
756
741
|
}
|
|
757
742
|
/**
|
|
@@ -770,8 +755,6 @@ const VSCODE_AUTO_APPROVE_SETTINGS = {
|
|
|
770
755
|
'/^gh (issue view|pr list|pr view|pr diff|repo view)\\b/': true,
|
|
771
756
|
// GitHub CLI write commands used by hula workflows
|
|
772
757
|
'/^gh pr (comment|merge)\\b/': true,
|
|
773
|
-
// Hula bash scripts deployed to .github/scripts/
|
|
774
|
-
'/^bash \\.github\\/scripts\\/hula-/': true,
|
|
775
758
|
// Common safe commands
|
|
776
759
|
'/^(ls|cat|head|tail|wc|echo|mkdir|rm -f|cd|pwd)\\b/': true,
|
|
777
760
|
};
|
|
@@ -807,10 +790,10 @@ async function initVsCodeAutoApproval(projectRoot) {
|
|
|
807
790
|
await writeFile(settingsPath, content, 'utf-8');
|
|
808
791
|
logger.success('VS Code auto-approval configured: .vscode/settings.json');
|
|
809
792
|
}
|
|
810
|
-
/** The hook command registered in .claude/settings.json
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
const CLAUDE_SESSION_HOOK_COMMAND = '
|
|
793
|
+
/** The hook command registered in .claude/settings.json. Runs the global
|
|
794
|
+
* `hula` bin (cross-platform, no bash/jq) instead of a project-local shell
|
|
795
|
+
* script, so no file is written into the consumer repo. */
|
|
796
|
+
const CLAUDE_SESSION_HOOK_COMMAND = 'hula session-hook';
|
|
814
797
|
/**
|
|
815
798
|
* Register the hula session hook in the consumer repo's .claude/settings.json.
|
|
816
799
|
*
|
|
@@ -821,7 +804,7 @@ const CLAUDE_SESSION_HOOK_COMMAND = 'bash .github/scripts/hula-session-hook.sh';
|
|
|
821
804
|
* populate, so this merges: it preserves every existing hook and appends ours
|
|
822
805
|
* only when absent (idempotent by the command string).
|
|
823
806
|
*/
|
|
824
|
-
async function initClaudeSessionHook(projectRoot) {
|
|
807
|
+
export async function initClaudeSessionHook(projectRoot) {
|
|
825
808
|
const claudeDir = join(projectRoot, '.claude');
|
|
826
809
|
const settingsPath = join(claudeDir, 'settings.json');
|
|
827
810
|
await mkdir(claudeDir, { recursive: true });
|
|
@@ -1118,6 +1101,12 @@ export async function executeInit(options) {
|
|
|
1118
1101
|
envVarsLine =
|
|
1119
1102
|
'\n // envVars: ["TEST_USER_EMAIL", "API_KEY"], // Environment variables to forward to container (read from .env)';
|
|
1120
1103
|
}
|
|
1104
|
+
// Per-pipeline-step model/iteration overrides (hula-server PR #442). Rendered
|
|
1105
|
+
// as a real value when preserved across re-init, otherwise a commented example.
|
|
1106
|
+
const stepsDocsUrl = 'https://github.com/NoStackApp/hub-launch#configuring-per-step-model--iteration-overrides';
|
|
1107
|
+
const stepsLine = finalConfig.steps
|
|
1108
|
+
? `\n steps: ${JSON.stringify(finalConfig.steps)}, // Per-pipeline-step model/iteration overrides. Keys/fields/ranges: ${stepsDocsUrl}`
|
|
1109
|
+
: `\n // steps: { implementation: { model: "opus" }, regression: { skip: true } }, // Per-pipeline-step model/iteration overrides. Keys/fields/ranges: ${stepsDocsUrl}`;
|
|
1121
1110
|
const configContent = `export const config = {${versionLine}
|
|
1122
1111
|
// API Configuration${apiKeyLine}${anthropicKeyLine}${daytonaKeyLine}${daytonaTierLine}${containerResourcesLine}${updateNotificationUrlLine}${updateNotificationNameTagLine}${usageTierLine}
|
|
1123
1112
|
|
|
@@ -1132,7 +1121,7 @@ export async function executeInit(options) {
|
|
|
1132
1121
|
|
|
1133
1122
|
// Worktree base path (used by /hula-fix, /hula-merge, /hula-upload)
|
|
1134
1123
|
worktreeBasePath: "${(finalConfig.worktreeBasePath || '.hula-worktrees').replace(/\\/g, '\\\\').replace(/"/g, '\\"')}",
|
|
1135
|
-
${envVarsLine}${buildPreservedSection(finalConfig)}
|
|
1124
|
+
${envVarsLine}${stepsLine}${buildPreservedSection(finalConfig)}
|
|
1136
1125
|
};
|
|
1137
1126
|
`;
|
|
1138
1127
|
// Create directory if it doesn't exist
|
|
@@ -1232,18 +1221,16 @@ ${envVarsLine}${buildPreservedSection(finalConfig)}
|
|
|
1232
1221
|
logger.blank();
|
|
1233
1222
|
logger.info('Generating templates directory structure...');
|
|
1234
1223
|
await generateTemplatesStructure(repoRoot);
|
|
1235
|
-
//
|
|
1224
|
+
// Ensure the .hublaunch/skills/ action-file directory exists. Instruction
|
|
1225
|
+
// documents are no longer copied here — skills read them from the package via
|
|
1226
|
+
// `hula instructions <name>`.
|
|
1236
1227
|
logger.blank();
|
|
1237
|
-
logger.info('
|
|
1238
|
-
await
|
|
1228
|
+
logger.info('Ensuring .hublaunch/skills/ action directory...');
|
|
1229
|
+
await initializeSkillsActionDir(repoRoot);
|
|
1239
1230
|
// Initialize Agent Skills for AI-assisted planning (works with Copilot, Claude Code, and others)
|
|
1240
1231
|
logger.blank();
|
|
1241
1232
|
logger.info('Setting up Agent Skills for AI-assisted planning...');
|
|
1242
|
-
await initializeAgentSkills(repoRoot);
|
|
1243
|
-
// Initialize bundled shell scripts for chat participant workflows
|
|
1244
|
-
logger.blank();
|
|
1245
|
-
logger.info('Setting up bundled shell scripts for chat workflows...');
|
|
1246
|
-
await initializeScriptFiles(repoRoot);
|
|
1233
|
+
await initializeAgentSkills(repoRoot, options?.withClaudeCommands ?? false);
|
|
1247
1234
|
// Register the Claude Code session hook (clientSessionId provenance)
|
|
1248
1235
|
logger.blank();
|
|
1249
1236
|
logger.info('Registering Claude Code session hook...');
|
|
@@ -1254,6 +1241,8 @@ ${envVarsLine}${buildPreservedSection(finalConfig)}
|
|
|
1254
1241
|
logger.info('Configuring VS Code auto-approval for hula agent commands...');
|
|
1255
1242
|
await initVsCodeAutoApproval(repoRoot);
|
|
1256
1243
|
}
|
|
1244
|
+
// Non-destructive migration notice for repos initialized by an older hula.
|
|
1245
|
+
await noticeObsoleteFiles(repoRoot);
|
|
1257
1246
|
logger.blank();
|
|
1258
1247
|
logger.blank();
|
|
1259
1248
|
logger.info('Next steps:');
|
|
@@ -1263,6 +1252,7 @@ ${envVarsLine}${buildPreservedSection(finalConfig)}
|
|
|
1263
1252
|
logger.listItem('📝 Edit .hublaunch/hublaunch.config.js to customize (each field is documented inline):', 1);
|
|
1264
1253
|
logger.listItem('daytonaTier, containerResources — sandbox sizing', 2);
|
|
1265
1254
|
logger.listItem('teamMembers, envVars — project specifics', 2);
|
|
1255
|
+
logger.listItem('steps — per-pipeline-step model & iteration overrides', 2);
|
|
1266
1256
|
logger.listItem('updateNotificationUrl, updateNotificationNameTag — Slack notifications', 2);
|
|
1267
1257
|
// Show credential setup instructions based on selected services
|
|
1268
1258
|
if (authProvider !== 'none') {
|