@entelligentsia/forgecli 0.2.0 → 0.3.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 +86 -0
- package/README.md +132 -5
- package/dist/bin/argv.js +5 -0
- package/dist/bin/argv.js.map +1 -1
- package/dist/bin/forge.js +1 -0
- package/dist/bin/forge.js.map +1 -1
- package/dist/extensions/forgecli/ask-user-tool.d.ts +17 -0
- package/dist/extensions/forgecli/ask-user-tool.js +139 -0
- package/dist/extensions/forgecli/ask-user-tool.js.map +1 -0
- package/dist/extensions/forgecli/forge-init.js +48 -16
- package/dist/extensions/forgecli/forge-init.js.map +1 -1
- package/dist/extensions/forgecli/hook-dispatcher.d.ts +34 -1
- package/dist/extensions/forgecli/hook-dispatcher.js +237 -3
- package/dist/extensions/forgecli/hook-dispatcher.js.map +1 -1
- package/dist/extensions/forgecli/index.js +6 -2
- package/dist/extensions/forgecli/index.js.map +1 -1
- package/dist/extensions/forgecli/store-validator.d.ts +13 -0
- package/dist/extensions/forgecli/store-validator.js +35 -0
- package/dist/extensions/forgecli/store-validator.js.map +1 -0
- package/dist/extensions/forgecli/transition-guard.d.ts +20 -0
- package/dist/extensions/forgecli/transition-guard.js +125 -0
- package/dist/extensions/forgecli/transition-guard.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,89 @@ All notable changes to `@entelligentsia/forgecli` are documented here.
|
|
|
5
5
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## [0.3.0] — 2026-05-09
|
|
13
|
+
|
|
14
|
+
Headline: Pi-runtime parity adapters — interactive UX + hook safety net.
|
|
15
|
+
`forge:ask_user` delivers real TUI prompts for all `/forge:init` gate sites;
|
|
16
|
+
the hook safety net enforces store-cli write validity and legal status
|
|
17
|
+
transitions at runtime. Fixes BUG-026 (non-blocking Y/N prompts) and BUG-027
|
|
18
|
+
(unguarded store-cli writes under pi runtime).
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`forge:ask_user` custom tool** (FORGE-S18-T04).
|
|
23
|
+
Registers `forge_ask_user` via `pi.registerTool`. Accepts `{question, type, options?, default?}`
|
|
24
|
+
where `type` is `confirm` (Y/N), `choice` (select from list), or `text` (free-form input).
|
|
25
|
+
Uses `ctx.ui.confirm / select / input` from the pi `ExtensionContext` — no raw pi-tui
|
|
26
|
+
component wiring needed. Blocks the model loop until the user responds. Non-interactive
|
|
27
|
+
bypass: `FORGE_YES=1`, `--non-interactive`, or headless mode returns the default
|
|
28
|
+
immediately. Cancellation surfaces as `isError: true`. 14 Vitest tests.
|
|
29
|
+
|
|
30
|
+
- **`registerHookDispatcher` wired to `tool_call` / `tool_result`** (FORGE-S18-T02).
|
|
31
|
+
Replaces the 7-line empty shim with a real implementation. Subscribes both pi events
|
|
32
|
+
(wired to tool_call/tool_result; enforcement layer added by T03).
|
|
33
|
+
Exports `parseStoreCLIInvocation()` + `StoreCLICall` interface for T03 to layer
|
|
34
|
+
validation on top. Hook inventory document produced at
|
|
35
|
+
`engineering/sprints/FORGE-S18/FORGE-S18-T02/HOOK_INVENTORY.md`.
|
|
36
|
+
|
|
37
|
+
- **Store-cli pushback correction loop + audit-log mode** (FORGE-S18-T03).
|
|
38
|
+
Extends the T02 hook-dispatcher scaffold from audit-only to enforcement.
|
|
39
|
+
`store-validator.ts` spawns `store-cli validate` synchronously on every write call;
|
|
40
|
+
`transition-guard.ts` enforces the legal status-transition table for task/sprint/bug
|
|
41
|
+
records. Returns `{ block: true, reason }` on violation. `FORGE_HOOK_AUDIT=1` logs
|
|
42
|
+
every decision (would-block/would-allow) with timestamp, entity, reason — returns
|
|
43
|
+
`undefined` (allow-through) regardless, enabling observation without disruption.
|
|
44
|
+
Closes FORGE-BUG-027.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- **Gate sites in `/forge:init` now use `forge:ask_user`** (FORGE-S18-T05). G2
|
|
49
|
+
(pre-flight phase selector) and G3 (KB folder prompt) replaced from
|
|
50
|
+
`sendToAgent+waitForIdle` to `ctx.ui.confirm / ctx.ui.input`. Operator receives a
|
|
51
|
+
real TUI prompt instead of model-generated text. Gate audit captured in
|
|
52
|
+
`GATE_AUDIT.md`.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **BUG-026** — Pi runtime: TUI Y/N prompts in command bodies don't wait for user
|
|
57
|
+
input. Fixed by `forge:ask_user` tool (T04) + gate-site retrofit (T05). `/forge:init`
|
|
58
|
+
gate sites G2 and G3 now use `ctx.ui.confirm/input` and block until the operator
|
|
59
|
+
responds.
|
|
60
|
+
- **BUG-027** — Pi runtime: validation hooks not wired — store-cli writes unguarded,
|
|
61
|
+
no pushback correction loop. Fixed by hook adapter (T02) + enforcement layer (T03).
|
|
62
|
+
`registerHookDispatcher` intercepts `store-cli write` and `store-cli update-status`
|
|
63
|
+
calls, validates against schema and transition table, returns `{ block: true, reason }`
|
|
64
|
+
on violation.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## [0.2.1] — 2026-05-09
|
|
69
|
+
|
|
70
|
+
Headline: Non-interactive mode for CI and scripted use. `FORGE_YES=1` and
|
|
71
|
+
`forge --non-interactive` both bypass every Y/N gate in `/forge:init`, resolving
|
|
72
|
+
each to its documented default. Unblocks scripted adoption immediately (BUG-026
|
|
73
|
+
short-circuit; T04/T05 deliver the real interactive TUI path).
|
|
74
|
+
|
|
75
|
+
### Added
|
|
76
|
+
|
|
77
|
+
- **`--non-interactive` CLI flag** (FORGE-S18-T01). Parsed by `argv.ts`,
|
|
78
|
+
sets `FORGE_NON_INTERACTIVE=1`. Documented in `--help` output.
|
|
79
|
+
- **`FORGE_YES=1` environment variable** (FORGE-S18-T01). Ergonomic shorthand
|
|
80
|
+
for scripts (`FORGE_YES=1 forge`). Checked alongside `FORGE_NON_INTERACTIVE`.
|
|
81
|
+
- **`isNonInteractive()` helper** in `forge-init.ts`. Bypasses G1 (resume
|
|
82
|
+
confirm), G2 (pre-flight phase selector), G3 (KB folder name), G4 (CLAUDE.md
|
|
83
|
+
create confirm) when active.
|
|
84
|
+
- **README non-interactive mode section** — flag, env var, and default-resolution
|
|
85
|
+
table for all four gate sites.
|
|
86
|
+
- **Vitest gate coverage** — 12 new test cases covering each gate under
|
|
87
|
+
interactive, `FORGE_NON_INTERACTIVE=1`, and `FORGE_YES=1` arms.
|
|
88
|
+
- **E2E smoke gates E2E-04/05/06** — auth-free checks that flag and env vars
|
|
89
|
+
are accepted without errors.
|
|
90
|
+
|
|
8
91
|
## [0.2.0] — 2026-05-09
|
|
9
92
|
|
|
10
93
|
Headline: `/forge:init` is now a real implementation. The 0.1.0 stub at
|
|
@@ -109,4 +192,7 @@ ported onto `@earendil-works/pi-coding-agent`.
|
|
|
109
192
|
- `claude-agent-sdk` plan-limit support (deferred to S17+).
|
|
110
193
|
- Cost telemetry surfacing in `/forge:*` (waived for S16).
|
|
111
194
|
|
|
195
|
+
[0.3.0]: https://github.com/Entelligentsia/forge-cli/releases/tag/v0.3.0
|
|
196
|
+
[0.2.1]: https://github.com/Entelligentsia/forge-cli/releases/tag/v0.2.1
|
|
197
|
+
[0.2.0]: https://github.com/Entelligentsia/forge-cli/releases/tag/v0.2.0
|
|
112
198
|
[0.1.0]: https://github.com/Entelligentsia/forge-cli/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -35,19 +35,146 @@ Outputs land in `.forge/{personas,skills,workflows,templates,config.json,project
|
|
|
35
35
|
## CLI flags
|
|
36
36
|
|
|
37
37
|
```
|
|
38
|
-
forge --version
|
|
39
|
-
forge --help
|
|
40
|
-
forge --no-update-check
|
|
41
|
-
forge --
|
|
38
|
+
forge --version Print version triplet (forgecli, forge, pi)
|
|
39
|
+
forge --help Show forge + pi help
|
|
40
|
+
forge --no-update-check Skip update check
|
|
41
|
+
forge --non-interactive Bypass all Y/N gates with defaults (CI/scripted use)
|
|
42
|
+
forge --registry <path> Override model registry
|
|
42
43
|
```
|
|
43
44
|
|
|
44
45
|
Pi flags (`-p`, `--cwd`, `--session`, `--model`, `--tools`, `--thinking`, …) are forwarded verbatim. Run `forge --help` for the full list.
|
|
45
46
|
|
|
47
|
+
## Non-interactive mode
|
|
48
|
+
|
|
49
|
+
For CI, scripts, or any context where the model cannot answer Y/N prompts:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
# Using the flag
|
|
53
|
+
forge --non-interactive
|
|
54
|
+
|
|
55
|
+
# Using the environment variable
|
|
56
|
+
FORGE_YES=1 forge
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Both activate the same bypass. When active, every Y/N gate in `/forge:init` resolves to its documented default:
|
|
60
|
+
|
|
61
|
+
| Gate | Default resolution |
|
|
62
|
+
|------|--------------------|
|
|
63
|
+
| Resume previous init? | No — delete checkpoint, start fresh |
|
|
64
|
+
| Pre-flight phase selector (Phase 1–4 prompt) | Skip prompt — proceed from Phase 1 |
|
|
65
|
+
| Knowledge base folder name | Use default `engineering/` |
|
|
66
|
+
| Create CLAUDE.md? | Yes — create with KB links |
|
|
67
|
+
|
|
68
|
+
## Hook safety net
|
|
69
|
+
|
|
70
|
+
forge-cli intercepts `store-cli write` and `store-cli update-status` bash calls and validates them before they reach the store. This prevents malformed payloads and illegal status transitions from corrupting the project's engineering knowledge base.
|
|
71
|
+
|
|
72
|
+
### Default-on enforcement
|
|
73
|
+
|
|
74
|
+
When enforcement is active (default), the hook dispatcher:
|
|
75
|
+
|
|
76
|
+
1. **Schema validation** — every `store-cli write <entity> '<json>'` invocation is validated against the entity schema. If the payload is invalid, the model receives a structured error via `{ block: true, reason: <error> }` and is expected to self-correct on the next attempt.
|
|
77
|
+
|
|
78
|
+
2. **Transition guard** — every `store-cli update-status <entity> <id> status <value>` invocation is checked against the legal transition table for the entity. Illegal transitions (e.g. `draft → committed`, skipping required intermediate states) are blocked with an explanatory message naming both `from` and `to` states and the legal next states.
|
|
79
|
+
|
|
80
|
+
Both checks are enforced by default. The hooks fire synchronously before the tool executes, so the model sees the error as the tool result and retries with a corrected payload.
|
|
81
|
+
|
|
82
|
+
### `--force` scope
|
|
83
|
+
|
|
84
|
+
When `--force` is present in the `store-cli` argv:
|
|
85
|
+
|
|
86
|
+
- **Transition guard** is bypassed — `--force` is an explicit operator override for status transitions.
|
|
87
|
+
- **Schema validation still runs** — a malformed payload is always invalid regardless of intent.
|
|
88
|
+
|
|
89
|
+
### `FORGE_HOOK_AUDIT=1` — audit-only mode
|
|
90
|
+
|
|
91
|
+
Set `FORGE_HOOK_AUDIT=1` to observe hook decisions without taking action. In audit mode:
|
|
92
|
+
|
|
93
|
+
- Every decision (would-block, would-allow, lookup-failed) is logged to `.forge/logs/hooks.log`.
|
|
94
|
+
- Nothing is blocked — all calls proceed regardless of validation outcome.
|
|
95
|
+
- Useful for calibrating the false-positive rate before enabling enforcement in a new project.
|
|
96
|
+
|
|
97
|
+
Log format (one entry per line):
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
[store-cli-intercept] subcmd=write entity=task payload={"taskId":"..."}
|
|
101
|
+
[store-cli-intercept] decision=would-block reason=missing required field: taskId
|
|
102
|
+
[store-cli-intercept] decision=would-allow
|
|
103
|
+
[store-cli-intercept] decision=lookup-failed entity=task entityId=FORGE-S18-T03
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Interpreting block messages
|
|
107
|
+
|
|
108
|
+
When the model emits a malformed `store-cli` call and the hook blocks it, the tool result will contain:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
block: true
|
|
112
|
+
reason: <error text>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The model should read the `reason` field and self-correct the payload or transition before retrying. Common block reasons:
|
|
116
|
+
|
|
117
|
+
| Reason pattern | Cause | Fix |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `missing required field: <field>` | Schema validation — required field absent | Add the missing field to the payload |
|
|
120
|
+
| `<from> → <to> is not a legal transition...` | Transition guard — illegal status jump | Use the listed legal next states |
|
|
121
|
+
| `store-cli validate exited with code 1` | Schema validation — malformed JSON or unknown entity | Fix the JSON payload |
|
|
122
|
+
|
|
123
|
+
## Custom tools
|
|
124
|
+
|
|
125
|
+
### `forge:ask_user` — interactive prompt
|
|
126
|
+
|
|
127
|
+
The `forge_ask_user` custom tool allows Forge workflows to request user input
|
|
128
|
+
during model execution. It presents the appropriate TUI prompt and blocks the
|
|
129
|
+
model loop until the user responds.
|
|
130
|
+
|
|
131
|
+
**Schema:**
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
{
|
|
135
|
+
question: string; // The prompt shown to the user
|
|
136
|
+
type: "confirm" // Y/N boolean confirmation
|
|
137
|
+
| "choice" // Select from a list
|
|
138
|
+
| "text"; // Free-form single-line input
|
|
139
|
+
options?: string[]; // Required when type === "choice"
|
|
140
|
+
default?: string; // Returned in non-interactive mode
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Returns:** A string — `"Y"` or `"N"` for `confirm`, the selected option for
|
|
145
|
+
`choice`, or the entered text for `text`. On cancellation (user dismisses the
|
|
146
|
+
dialog), the tool returns `isError: true` with a structured message.
|
|
147
|
+
|
|
148
|
+
**Examples:**
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
// Confirm
|
|
152
|
+
forge_ask_user({ question: "Overwrite existing files?", type: "confirm" })
|
|
153
|
+
// → "Y" or "N"
|
|
154
|
+
|
|
155
|
+
// Choice
|
|
156
|
+
forge_ask_user({
|
|
157
|
+
question: "Select environment:",
|
|
158
|
+
type: "choice",
|
|
159
|
+
options: ["development", "staging", "production"]
|
|
160
|
+
})
|
|
161
|
+
// → "development" | "staging" | "production"
|
|
162
|
+
|
|
163
|
+
// Text
|
|
164
|
+
forge_ask_user({ question: "Enter project name:", type: "text", default: "myproject" })
|
|
165
|
+
// → user-entered string (or "myproject" in non-interactive mode)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Non-interactive behaviour:** When `FORGE_YES=1`, `--non-interactive` is set,
|
|
169
|
+
or pi is running in headless/RPC mode, the tool returns the `default` immediately
|
|
170
|
+
without rendering any TUI. Fallback defaults when no explicit `default` is
|
|
171
|
+
provided: `confirm` → `"Y"`, `choice` → `options[0]`, `text` → `""`.
|
|
172
|
+
|
|
46
173
|
## Roadmap
|
|
47
174
|
|
|
48
175
|
| Command | Status |
|
|
49
176
|
|---------------------------|---------------------|
|
|
50
|
-
| `/forge:init` | Shipped (0.
|
|
177
|
+
| `/forge:init` | Shipped (0.3.0) |
|
|
51
178
|
| Other `/forge:*` commands | Roadmap |
|
|
52
179
|
|
|
53
180
|
Track via [issues](https://github.com/Entelligentsia/forge-cli/issues).
|
package/dist/bin/argv.js
CHANGED
|
@@ -71,6 +71,11 @@ export function parseForgeArgv(argv) {
|
|
|
71
71
|
i++;
|
|
72
72
|
continue;
|
|
73
73
|
}
|
|
74
|
+
if (token === "--non-interactive") {
|
|
75
|
+
env.FORGE_NON_INTERACTIVE = "1";
|
|
76
|
+
i++;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
74
79
|
if (token === "--registry") {
|
|
75
80
|
if (i + 1 >= argv.length) {
|
|
76
81
|
return { error: "forge: --registry requires a path argument. Run `forge --help` for usage." };
|
package/dist/bin/argv.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"argv.js","sourceRoot":"","sources":["../../src/bin/argv.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"argv.js","sourceRoot":"","sources":["../../src/bin/argv.ts"],"names":[],"mappings":"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"}
|
package/dist/bin/forge.js
CHANGED
|
@@ -56,6 +56,7 @@ Forge-owned options:
|
|
|
56
56
|
--version Print version triplet and exit
|
|
57
57
|
--help, -h Print this help message
|
|
58
58
|
--no-update-check Skip forge update check (sets FORGE_NO_UPDATE_CHECK=1)
|
|
59
|
+
--non-interactive Bypass all Y/N gates with defaults, e.g. for CI (sets FORGE_NON_INTERACTIVE=1)
|
|
59
60
|
--registry <path> Override model registry path (sets FORGE_MODEL_REGISTRY=path)
|
|
60
61
|
|
|
61
62
|
Pi options (forwarded verbatim):
|
package/dist/bin/forge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"forge.js","sourceRoot":"","sources":["../../src/bin/forge.ts"],"names":[],"mappings":";AAEA,+CAA+C;AAC/C,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAE/E,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,iCAAiC,CAAC;AACvD,OAAO,QAAQ,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEzD,8EAA8E;AAC9E,oEAAoE;AACpE,8EAA8E;AAE9E,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAS3C,SAAS,eAAe;IACvB,IAAI,CAAC;QACJ,0DAA0D;QAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QACpE,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAa,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAC/B,CAAC;AACF,CAAC;AAED,KAAK,UAAU,aAAa;IAC3B,IAAI,CAAC;QACJ,iFAAiF;QACjF,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,EAAE,cAAc,CAAC,CAAC;QAC1D,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAyB,CAAC;QACpD,OAAO,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;AACF,CAAC;AAED,KAAK,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,eAAe,EAAE,CAAC;IAC9B,MAAM,eAAe,GAAG,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;IACjD,MAAM,cAAc,GAAG,GAAG,CAAC,KAAK,EAAE,cAAc,IAAI,SAAS,CAAC;IAC9D,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC;IACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB,4BAA4B,eAAe,kBAAkB,cAAc,QAAQ,SAAS,KAAK,CACjG,CAAC;AACH,CAAC;AAED,SAAS,SAAS;IACjB,OAAO,CAAC,MAAM,CAAC,KAAK,CACnB
|
|
1
|
+
{"version":3,"file":"forge.js","sourceRoot":"","sources":["../../src/bin/forge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
export declare const AskUserParams: Type.TObject<{
|
|
4
|
+
question: Type.TString;
|
|
5
|
+
type: Type.TUnion<[Type.TLiteral<"confirm">, Type.TLiteral<"choice">, Type.TLiteral<"text">]>;
|
|
6
|
+
options: Type.TOptional<Type.TArray<Type.TString>>;
|
|
7
|
+
default: Type.TOptional<Type.TString>;
|
|
8
|
+
}>;
|
|
9
|
+
/**
|
|
10
|
+
* Register the forge_ask_user tool with the pi ExtensionAPI.
|
|
11
|
+
*
|
|
12
|
+
* The tool is named `forge_ask_user` (snake_case per pi convention); the
|
|
13
|
+
* human/LLM-facing name `forge:ask_user` appears in description and promptSnippet.
|
|
14
|
+
*
|
|
15
|
+
* @param pi The pi ExtensionAPI instance.
|
|
16
|
+
*/
|
|
17
|
+
export declare function registerAskUserTool(pi: ExtensionAPI): void;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// forge:ask_user custom tool — FORGE-S18-T04
|
|
2
|
+
//
|
|
3
|
+
// Registers forge_ask_user via pi.registerTool. The tool accepts a question and
|
|
4
|
+
// an input type (confirm | choice | text), presents the appropriate TUI prompt
|
|
5
|
+
// via ctx.ui.confirm / ctx.ui.select / ctx.ui.input, and returns the user's
|
|
6
|
+
// answer as a string.
|
|
7
|
+
//
|
|
8
|
+
// Non-interactive bypass:
|
|
9
|
+
// When FORGE_YES=1 or FORGE_NON_INTERACTIVE=1 (set by `forge --non-interactive`),
|
|
10
|
+
// or when ctx.hasUI is false (headless / RPC mode), the tool returns the supplied
|
|
11
|
+
// default without rendering any TUI. Fallback defaults when no explicit default:
|
|
12
|
+
// confirm → "Y"
|
|
13
|
+
// choice → options[0] (or "" if empty)
|
|
14
|
+
// text → ""
|
|
15
|
+
//
|
|
16
|
+
// Cancellation:
|
|
17
|
+
// ctx.ui.* returns undefined when the user cancels. The tool surfaces this as
|
|
18
|
+
// isError: true with a structured message — never silently defaults.
|
|
19
|
+
//
|
|
20
|
+
// Iron Law 6 compliance: no shell-string interpolation. No subprocess spawning.
|
|
21
|
+
import { Type } from "typebox";
|
|
22
|
+
// ── Schema ────────────────────────────────────────────────────────────────────
|
|
23
|
+
export const AskUserParams = Type.Object({
|
|
24
|
+
question: Type.String({
|
|
25
|
+
description: "The question or prompt to display to the user.",
|
|
26
|
+
}),
|
|
27
|
+
type: Type.Union([Type.Literal("confirm"), Type.Literal("choice"), Type.Literal("text")], {
|
|
28
|
+
description: "Input modality: confirm (Y/N boolean), choice (select from list), or text (free-form single-line input).",
|
|
29
|
+
}),
|
|
30
|
+
options: Type.Optional(Type.Array(Type.String(), {
|
|
31
|
+
description: "Required when type === 'choice'. The list of options to present to the user.",
|
|
32
|
+
})),
|
|
33
|
+
default: Type.Optional(Type.String({
|
|
34
|
+
description: "Default value returned in non-interactive mode or when no default is needed. " +
|
|
35
|
+
"If absent, the fallback is: confirm → 'Y', choice → options[0], text → ''.",
|
|
36
|
+
})),
|
|
37
|
+
});
|
|
38
|
+
// ── Non-interactive helper ────────────────────────────────────────────────────
|
|
39
|
+
/**
|
|
40
|
+
* Returns true when running in non-interactive / CI mode.
|
|
41
|
+
*
|
|
42
|
+
* Inlined here (not imported from forge-init.ts) to keep the module boundary
|
|
43
|
+
* clean and avoid any risk of circular imports.
|
|
44
|
+
*
|
|
45
|
+
* Activated by:
|
|
46
|
+
* - `FORGE_YES=1` — ergonomic shell shorthand (FORGE-S18-T01)
|
|
47
|
+
* - `FORGE_NON_INTERACTIVE=1` — set by `forge --non-interactive` flag
|
|
48
|
+
*/
|
|
49
|
+
function isNonInteractive() {
|
|
50
|
+
return process.env.FORGE_YES === "1" || process.env.FORGE_NON_INTERACTIVE === "1";
|
|
51
|
+
}
|
|
52
|
+
// ── Result helpers ────────────────────────────────────────────────────────────
|
|
53
|
+
function okResult(text) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: text || "" }],
|
|
56
|
+
details: {},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function errResult(text) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text }],
|
|
62
|
+
details: {},
|
|
63
|
+
isError: true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// ── Fallback computation ──────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Compute the non-interactive fallback value.
|
|
69
|
+
*
|
|
70
|
+
* Priority: explicit `default` field → type-specific hardcoded fallback.
|
|
71
|
+
*/
|
|
72
|
+
function computeFallback(params) {
|
|
73
|
+
if (params.default !== undefined)
|
|
74
|
+
return params.default;
|
|
75
|
+
if (params.type === "confirm")
|
|
76
|
+
return "Y";
|
|
77
|
+
if (params.type === "choice")
|
|
78
|
+
return params.options?.[0] ?? "";
|
|
79
|
+
return ""; // text
|
|
80
|
+
}
|
|
81
|
+
// ── Public registration ───────────────────────────────────────────────────────
|
|
82
|
+
/**
|
|
83
|
+
* Register the forge_ask_user tool with the pi ExtensionAPI.
|
|
84
|
+
*
|
|
85
|
+
* The tool is named `forge_ask_user` (snake_case per pi convention); the
|
|
86
|
+
* human/LLM-facing name `forge:ask_user` appears in description and promptSnippet.
|
|
87
|
+
*
|
|
88
|
+
* @param pi The pi ExtensionAPI instance.
|
|
89
|
+
*/
|
|
90
|
+
export function registerAskUserTool(pi) {
|
|
91
|
+
pi.registerTool({
|
|
92
|
+
name: "forge_ask_user",
|
|
93
|
+
label: "Forge Ask User",
|
|
94
|
+
description: "forge:ask_user — Present an interactive prompt to the user and return their answer. " +
|
|
95
|
+
"Accepts three input types: 'confirm' (Y/N), 'choice' (select from a list), or 'text' " +
|
|
96
|
+
"(free-form single-line input). Blocks the model loop until the user responds. " +
|
|
97
|
+
"In non-interactive mode (FORGE_YES=1 or --non-interactive), returns the default immediately.",
|
|
98
|
+
promptSnippet: "Use forge_ask_user when a Forge workflow needs synchronous user input — confirm (Y/N), choice from a list, or free-form text.",
|
|
99
|
+
parameters: AskUserParams,
|
|
100
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
101
|
+
// Non-interactive bypass: applies when env flag is set OR when running
|
|
102
|
+
// headless (ctx.hasUI=false, e.g. RPC mode / print mode).
|
|
103
|
+
if (isNonInteractive() || !ctx.hasUI) {
|
|
104
|
+
const fallback = computeFallback(params);
|
|
105
|
+
// Emit a one-line audit entry to stderr (not a file) so CI logs capture it.
|
|
106
|
+
process.stderr.write(`[forge:ask_user] non-interactive fallback — type=${params.type} question="${params.question}" default="${fallback}"\n`);
|
|
107
|
+
return okResult(fallback);
|
|
108
|
+
}
|
|
109
|
+
const opts = signal !== undefined ? { signal } : {};
|
|
110
|
+
if (params.type === "confirm") {
|
|
111
|
+
// ctx.ui.confirm returns true/false or undefined (cancel).
|
|
112
|
+
const answer = await ctx.ui.confirm("forge:ask_user", params.question, opts);
|
|
113
|
+
if (answer === undefined) {
|
|
114
|
+
return errResult(`forge:ask_user cancelled — user dismissed the prompt. question: "${params.question}"`);
|
|
115
|
+
}
|
|
116
|
+
return okResult(answer ? "Y" : "N");
|
|
117
|
+
}
|
|
118
|
+
if (params.type === "choice") {
|
|
119
|
+
if (!params.options || params.options.length === 0) {
|
|
120
|
+
return errResult("forge:ask_user error — type 'choice' requires a non-empty 'options' array.");
|
|
121
|
+
}
|
|
122
|
+
// ctx.ui.select returns the selected string or undefined (cancel).
|
|
123
|
+
const answer = await ctx.ui.select("forge:ask_user", params.options, opts);
|
|
124
|
+
if (answer === undefined) {
|
|
125
|
+
return errResult(`forge:ask_user cancelled — user dismissed the prompt. question: "${params.question}"`);
|
|
126
|
+
}
|
|
127
|
+
return okResult(answer);
|
|
128
|
+
}
|
|
129
|
+
// type === "text"
|
|
130
|
+
// ctx.ui.input returns the entered string or undefined (cancel/ESC).
|
|
131
|
+
const answer = await ctx.ui.input("forge:ask_user", params.question, opts);
|
|
132
|
+
if (answer === undefined) {
|
|
133
|
+
return errResult(`forge:ask_user cancelled — user dismissed the prompt. question: "${params.question}"`);
|
|
134
|
+
}
|
|
135
|
+
return okResult(answer);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=ask-user-tool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ask-user-tool.js","sourceRoot":"","sources":["../../../src/extensions/forgecli/ask-user-tool.ts"],"names":[],"mappings":"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"}
|
|
@@ -90,6 +90,20 @@ function getBundledForgeVersion() {
|
|
|
90
90
|
// ── Session-scoped banner state ────────────────────────────────────────────
|
|
91
91
|
// Prevents re-rendering the hero banner on resume within the same session.
|
|
92
92
|
let heroBannerShown = false;
|
|
93
|
+
// ── Non-interactive mode ───────────────────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* Returns true when running in non-interactive / CI mode.
|
|
96
|
+
*
|
|
97
|
+
* Activated by either:
|
|
98
|
+
* - `FORGE_YES=1` — ergonomic shell shorthand (FORGE-S18-T01)
|
|
99
|
+
* - `FORGE_NON_INTERACTIVE=1` — set by `forge --non-interactive` flag
|
|
100
|
+
*
|
|
101
|
+
* When active, every Y/N gate resolves to its documented default without
|
|
102
|
+
* emitting a model-text prompt.
|
|
103
|
+
*/
|
|
104
|
+
function isNonInteractive() {
|
|
105
|
+
return process.env.FORGE_YES === "1" || process.env.FORGE_NON_INTERACTIVE === "1";
|
|
106
|
+
}
|
|
93
107
|
function parseInitFlags(args) {
|
|
94
108
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
95
109
|
const hasFast = parts.includes("--fast");
|
|
@@ -255,8 +269,10 @@ async function linkAgentInstructionFile(cwd, kbPath, projectName, ctx) {
|
|
|
255
269
|
// Already exists — do NOT modify (per spec step 4-11: avoid KB-link bloat)
|
|
256
270
|
return;
|
|
257
271
|
}
|
|
258
|
-
// None exist — prompt to create minimal CLAUDE.md
|
|
259
|
-
const ok =
|
|
272
|
+
// None exist — prompt to create minimal CLAUDE.md (G4: bypassed in non-interactive mode)
|
|
273
|
+
const ok = isNonInteractive()
|
|
274
|
+
? true
|
|
275
|
+
: await ctx.ui.confirm("Create CLAUDE.md?", `No agent instruction file found at project root.\nCreate a minimal CLAUDE.md with links to the Forge knowledge base? [Y/n]`);
|
|
260
276
|
if (!ok) {
|
|
261
277
|
ctx.ui.notify("〇 KB not linked — run /forge:refresh-kb-links after creating CLAUDE.md.", "info");
|
|
262
278
|
return;
|
|
@@ -343,7 +359,10 @@ export function registerForgeInit(pi) {
|
|
|
343
359
|
const nextPhase = Math.min(lastPhase + 1, 4);
|
|
344
360
|
const resumeBanner = `〇 Previous init detected — last completed phase: ${lastPhase} of 4\n` +
|
|
345
361
|
`Resume from Phase ${nextPhase}?`;
|
|
346
|
-
|
|
362
|
+
// G1: in non-interactive mode, default to not resuming (start fresh)
|
|
363
|
+
const shouldResume = isNonInteractive()
|
|
364
|
+
? false
|
|
365
|
+
: await ctx.ui.confirm("Resume /forge:init?", resumeBanner);
|
|
347
366
|
if (shouldResume) {
|
|
348
367
|
startPhase = nextPhase;
|
|
349
368
|
// Skip hero banner on resume (session-scoped gate)
|
|
@@ -386,7 +405,7 @@ export function registerForgeInit(pi) {
|
|
|
386
405
|
// ── 5. Pre-flight plan (unless jumping to a specific phase) ───────
|
|
387
406
|
const projectName = discoverProjectName(cwd);
|
|
388
407
|
if (flags.startPhase === null || flags.invalidPhase) {
|
|
389
|
-
const
|
|
408
|
+
const preflightSummary = `Forge Init — ${projectName}\n\n` +
|
|
390
409
|
`4 phases will run in this session (~45 seconds non-interactive):\n\n` +
|
|
391
410
|
` 1 Collect — 5 parallel discovery scans → config.json\n` +
|
|
392
411
|
` KB folder prompt (interactive)\n` +
|
|
@@ -394,10 +413,15 @@ export function registerForgeInit(pi) {
|
|
|
394
413
|
` 3 Materialize — substitute-placeholders.cjs → fully functional workflows\n` +
|
|
395
414
|
` 4 Register — versioning, manifest, cache, store entries, Tomoshibi\n\n` +
|
|
396
415
|
`Phase 1 is interactive (KB folder name prompt). Phases 2–4 are non-interactive\n` +
|
|
397
|
-
`and complete in under 45 seconds
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
416
|
+
`and complete in under 45 seconds.`;
|
|
417
|
+
// G2: skip pre-flight confirm in non-interactive mode (proceed directly to Phase 1)
|
|
418
|
+
if (!isNonInteractive()) {
|
|
419
|
+
const proceed = await ctx.ui.confirm("Start /forge:init?", preflightSummary);
|
|
420
|
+
if (!proceed) {
|
|
421
|
+
ctx.ui.notify("〇 /forge:init cancelled.", "info");
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
401
425
|
}
|
|
402
426
|
// ── Phase 1 — Collect ─────────────────────────────────────────────
|
|
403
427
|
if (startPhase <= 1) {
|
|
@@ -417,14 +441,22 @@ export function registerForgeInit(pi) {
|
|
|
417
441
|
const phase1Prompt = buildPhase1PromptText(bundleRoot, projectName);
|
|
418
442
|
sendToAgent(phase1Prompt);
|
|
419
443
|
await ctx.waitForIdle();
|
|
420
|
-
// KB folder prompt (spec §7, F2)
|
|
421
|
-
|
|
422
|
-
`Forge will create a folder for architecture docs, sprints, bugs, and features.\n` +
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
444
|
+
// KB folder prompt (spec §7, F2) — G3: skipped in non-interactive mode (default: "engineering")
|
|
445
|
+
if (!isNonInteractive()) {
|
|
446
|
+
const kbDescription = `Forge will create a folder for architecture docs, sprints, bugs, and features.\n` +
|
|
447
|
+
`Default name: engineering/\n\n` +
|
|
448
|
+
`Does "engineering" conflict with an existing folder in this project?`;
|
|
449
|
+
const hasConflict = await ctx.ui.confirm("Engineering folder name?", kbDescription);
|
|
450
|
+
if (hasConflict) {
|
|
451
|
+
const customName = await ctx.ui.input("Engineering folder name?", "Enter preferred folder name (e.g. ai-docs, .forge-kb, docs/ai): ");
|
|
452
|
+
if (customName && customName.trim()) {
|
|
453
|
+
const manageConfigToolEarly = path.join(toolsRoot, "manage-config.cjs");
|
|
454
|
+
if (fs.existsSync(manageConfigToolEarly)) {
|
|
455
|
+
await runToolAdvisory(manageConfigToolEarly, ["set", "paths.engineering", customName.trim()], cwd, ctx, "manage-config paths.engineering");
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
428
460
|
// Marketplace skills advisory (sub-decision #1)
|
|
429
461
|
ctx.ui.notify("〇 Marketplace skills auto-recommendation is Claude-Code-only. " +
|
|
430
462
|
"Pi users install extensions manually. Writing installedSkills: []", "info");
|