opencode-goal-plugin 0.6.7 → 0.7.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 +14 -0
- package/README.md +31 -8
- package/docs/compatibility.md +29 -1
- package/index.d.ts +17 -0
- package/package.json +1 -1
- package/scripts/verify.mjs +9 -1
- package/src/goal-plugin.js +1263 -179
- package/src/persistence-lease.js +594 -59
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.7.0 — 2026-08-02
|
|
6
|
+
|
|
7
|
+
- Make `/goal status` add explicit `State:` and `Completion audit:` lines without changing its existing `Active goal:` header; make `/goal list` report `active`, `paused`, or `blocked` and preserve the reason for stopped focused goals. Completion audit reporting distinguishes the evidence gate, built-in independent verifier, and custom completion auditor.
|
|
8
|
+
- Add bounded, transition-only lifecycle notices through OpenCode's structured log and TUI toast, with independent `lifecycleMessages` and `lifecycleMessenger` controls. Delivery is advisory, does not create model turns, and does not announce routine idle/checkpoint activity. Completion/block uses the audit-result message when `auditMessages` is enabled and one lifecycle fallback only when it is disabled.
|
|
9
|
+
- Harden lifecycle persistence around the new feedback path: failed completion writes cannot resurrect an older goal over newer session state, blocked ledger events repair a lagging snapshot with their concrete reason, and clear operations disclose when neither snapshot nor ledger recorded the deletion durably.
|
|
10
|
+
|
|
11
|
+
## 0.6.8 — 2026-08-02
|
|
12
|
+
|
|
13
|
+
- Serialize fresh-namespace migration-marker publication across concurrent processes so Windows does not reject competing first-start renames with `EPERM`.
|
|
14
|
+
- Keep a second OpenCode process usable when it opens a session whose goal-state shard is already leased: ordinary chat and unrelated tools remain available, while goal commands and tools fail safely without reading, changing, or prompting from that session's goal state. After the owner exits, the next explicit goal command or tool retries ownership and reloads any active goal paused. Lease contention is now typed and owner metadata is sanitized; unrelated filesystem failures still fail closed.
|
|
15
|
+
- Harden the single-writer lease with immutable per-owner claims so delayed publication, simultaneous stale reclaim, and concurrent release cannot remove a replacement owner's lease. A complete compatibility guard is published atomically with no replacement, preventing old/current startup races; legacy, incomplete, tampered, or unsupported lease layouts fail closed for explicit manual recovery. Owner reads remain bounded and symlink-safe, slow passive command guards keep blocking tools until an authenticated new boundary, delayed control errors cannot pause a newer goal run, takeover retains Plan/model execution context, and advisory host logging cannot stall loading or disposal.
|
|
16
|
+
|
|
3
17
|
## 0.6.7 — 2026-08-01
|
|
4
18
|
|
|
5
19
|
- Fix `/goal` routing on OpenCode 1.17.15 and 1.18.10 by replacing the host-retained prompt parts in place, authenticating each resolved command turn (including host-expanded file attachments), framing control results with direct reporting instructions, blocking tools during those reporting turns, and excluding control responses from goal completion and progress analysis. Unreadable command attachments now become read-only error reports and pause safely without losing command provenance.
|
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Compatibility: this plugin relies on experimental OpenCode hooks. Re-test agains
|
|
|
18
18
|
- Guarded auto-continuation with turn, duration, token, no-progress, and no-tool-call limits.
|
|
19
19
|
- Project-local restart recovery backed by persisted state and a bounded lifecycle ledger.
|
|
20
20
|
- Evidence-gated completion with an optional independent, fail-closed verifier.
|
|
21
|
+
- Explicit `active`, `paused`, and `blocked` status plus transition-only lifecycle notices.
|
|
21
22
|
- Canonical agent tools, collision-safe goal/verifier agents, multiple goals, and ordered goal sequences.
|
|
22
23
|
|
|
23
24
|
This project is independently implemented for OpenCode. Product names used elsewhere identify their respective owners; no feature-parity or endorsement claim is implied.
|
|
@@ -38,7 +39,7 @@ surface and versioning expectations.
|
|
|
38
39
|
|
|
39
40
|
Tested against real OpenCode 1.17.15 processes with live provider credentials and no mocked plugin hooks. State, ledger entries, and workspace files were checked independently of terminal or model prose:
|
|
40
41
|
|
|
41
|
-
| OpenCode Version | Provider Tested | `/goal status` | Auto-continue | Evidence-gated completion | v0.6.6
|
|
42
|
+
| OpenCode Version | Provider Tested | `/goal status` | Auto-continue | Evidence-gated completion | Historical custom-command presentation (v0.6.6) |
|
|
42
43
|
|---|---|---|---|---|---|
|
|
43
44
|
| 1.17.15 | opencode (`deepseek-v4-flash-free`) | ✅ Canonical tool | ✅ Checkpoint + idle continuation | ✅ Structured `goal_complete` claim | ⚠️ Command text routed to model; mutation guard verified |
|
|
44
45
|
| 1.17.15 | opencode-go (`qwen3.7-plus`) | ✅ | ✅ | ✅ Self-corrected after one rejection (bare `[goal:complete]` with no evidence), then completed cleanly | ⚠️ Not displayed |
|
|
@@ -49,6 +50,8 @@ Tested against real OpenCode 1.17.15 processes with live provider credentials an
|
|
|
49
50
|
|
|
50
51
|
**Note:** The table records the v0.6.6 live-provider matrix. In that release, OpenCode 1.17.15 retained the original command-parts array, so assigning a new `output.parts` array did not replace the raw command argument sent to the model. The current implementation mutates that retained array in place, making the plugin-generated command result the prompt for the turn. OpenCode custom commands still run through the model rather than rendering hook output directly, so the visible response may summarize or paraphrase the result (see [Limitations](#limitations)). Re-test against the exact OpenCode build and provider/backend stack you rely on for unattended work, and see [`docs/providers.md`](docs/providers.md) for the full historical model matrix.
|
|
51
52
|
|
|
53
|
+
Separately, the lifecycle-feedback implementation included in v0.7.0 passed a real OpenCode 1.18.11 host canary covering create, status, pause, resume, edit, and default lifecycle logging with a deterministic localhost provider. That canary validates host integration, not another live-provider compatibility row.
|
|
54
|
+
|
|
52
55
|
## Install
|
|
53
56
|
|
|
54
57
|
```sh
|
|
@@ -100,6 +103,8 @@ Check status:
|
|
|
100
103
|
/goal status
|
|
101
104
|
```
|
|
102
105
|
|
|
106
|
+
`/goal status` keeps its existing `Active goal:` heading and adds an explicit `State:` line: `active` while the goal can continue, `blocked` when the assistant recorded a concrete blocker, and `paused` for other retained stops such as user intervention, a safety limit, or an audit rejection. A `Completion audit:` line distinguishes the always-on evidence gate from an optional built-in independent verifier or custom completion auditor.
|
|
107
|
+
|
|
103
108
|
View lifecycle history and the latest checkpoint:
|
|
104
109
|
|
|
105
110
|
```
|
|
@@ -156,7 +161,7 @@ A session can hold more than one goal. `/goal <condition>` replaces the focused
|
|
|
156
161
|
/goal focus 1
|
|
157
162
|
```
|
|
158
163
|
|
|
159
|
-
`/goal list` shows numbered live goals (focused and backgrounded) plus achieved goals retained in the per-session archive. `/goal clear` intentionally removes live goals and saved status from these views; its terminal ledger entries remain available for crash-safe recovery decisions. `/goal focus <number>` switches the active goal, backgrounding the previous one. Focus is tracked per session and survives a restart.
|
|
164
|
+
`/goal list` shows numbered live goals (focused and backgrounded) plus achieved goals retained in the per-session archive. Each live entry includes its explicit `active`, `paused`, or `blocked` state; a stopped focused goal keeps its bounded stop or blocker reason visible. `/goal clear` intentionally removes live goals and saved status from these views; its terminal ledger entries remain available for crash-safe recovery decisions. `/goal focus <number>` switches the active goal, backgrounding the previous one. Focus is tracked per session and survives a restart.
|
|
160
165
|
|
|
161
166
|
#### Ordered sequences
|
|
162
167
|
|
|
@@ -255,6 +260,10 @@ Alongside each session shard the plugin keeps an **append-only lifecycle ledger*
|
|
|
255
260
|
|
|
256
261
|
Recovered active goals are loaded in a **paused** state with a recovery note, so unattended auto-continue does not resume blindly after a restart. Set `"persistState": false` to keep purely in-memory behavior (this also disables the ledger).
|
|
257
262
|
|
|
263
|
+
Only one OpenCode process may own a given session shard at a time. If the same session is opened in a second process, that process enters **passive goal mode** instead of failing the whole session: ordinary chat and unrelated tools continue to work, but `/goal` commands and goal tools report that another process owns the workflow. Canonical goal tools return the stable envelope code `error: "session_owned_elsewhere"`. The passive process does not read, mutate, persist, or auto-continue that session's goal state. After the owner exits, retry an explicit goal command or goal tool; the process will acquire the shard and load any recovered active goal paused. To work concurrently without waiting, create a new session with `opencode --continue --fork` (or `opencode --session <id> --fork`).
|
|
264
|
+
|
|
265
|
+
Lease ownership uses immutable per-process claim files so a delayed stale-lock cleanup or duplicate release cannot delete a newer owner's lease. The plugin publishes a complete regular-file compatibility guard atomically at `<shard>/state.json.lock`, then elects the current owner from claims in the sibling `<shard>/state.json.lock.claims-v2/` directory. That no-replace publication makes startup safe against older releases: either the older lock directory wins and the current plugin stays passive, or the guard file wins and the older release cannot reclaim it. Automatic ownership handoff requires the current release. Legacy, incomplete, tampered, or unsupported lease layouts fail closed instead of being rewritten online; filesystems must support regular-file hard links and preserve the guard's future timestamp. After confirming that every process using the session is closed and upgraded, either fork or remove only the affected shard's adjacent `.lock` file or legacy directory **and** `.lock.claims-v2` directory; keep its state and ledger.
|
|
266
|
+
|
|
258
267
|
`/goal resume` continues the same objective with a fresh local budget window. This lets you continue after pause, blocker, no-progress pause, rate-limit failures, or a limit stop without retyping the objective.
|
|
259
268
|
|
|
260
269
|
### Per-goal flags
|
|
@@ -332,6 +341,8 @@ Additional plugin-level options:
|
|
|
332
341
|
- `ledgerMaxBytes` / `ledgerRetentionFiles` — bound the lifecycle ledger to 2 MiB per generation and three rotated generations by default. Set retention to `0` to discard the active ledger when it reaches the size ceiling.
|
|
333
342
|
- `resultRetentionMs` — how long a completed goal summary remains available through `/goal status` after the goal leaves active memory.
|
|
334
343
|
- `maxStoredResults` — maximum number of completed-goal summaries retained in process memory before the oldest ones are evicted.
|
|
344
|
+
- `lifecycleMessages` — announce applied goal-state transitions (default `true`). Set to `false` to disable lifecycle notices without disabling audit messages or persistence.
|
|
345
|
+
- `lifecycleMessenger(sessionID, text)` — route lifecycle notices to a custom sink instead of the default structured-log/TUI-toast path.
|
|
335
346
|
|
|
336
347
|
## Agent tools
|
|
337
348
|
|
|
@@ -348,12 +359,23 @@ These operate on the same per-session multi-goal state as the command path: a to
|
|
|
348
359
|
|
|
349
360
|
> Integration note: the tool execute-context shape (`ctx.sessionID`) and Zod argument definitions follow the OpenCode plugin docs. The tool **logic** is unit-tested independently, but live registration should still be confirmed against the exact OpenCode host used in production (see the smoke-test checklist).
|
|
350
361
|
|
|
362
|
+
## Lifecycle messages
|
|
363
|
+
|
|
364
|
+
The plugin announces meaningful, applied state transitions such as goal creation, focus changes, pause/resume, recovery, ordered-goal promotion, and clearing. It does not emit a notice for every idle event, checkpoint, or continuation attempt. Messages are bounded and avoid dumping the full objective, evidence, or filesystem paths.
|
|
365
|
+
|
|
366
|
+
By default, lifecycle notices go to OpenCode's structured log and to a TUI toast when that host capability is available. Provide a `lifecycleMessenger(sessionID, text)` plugin option to route them elsewhere, or set `lifecycleMessages: false` to disable them. Delivery is advisory: notices do not start an assistant turn or make any extra model call, and a log, toast, or custom-messenger failure does not undo the recorded state transition.
|
|
367
|
+
|
|
368
|
+
Lifecycle notices and audit messages are separate controls. Lifecycle notices describe applied goal state; audit messages describe completion/block validation. When `auditMessages` is `true`, its audit-result message is the sole completion/block announcement. When `auditMessages` is `false` and `lifecycleMessages` is `true`, the lifecycle channel emits one terminal fallback instead. Other transitions follow `lifecycleMessages`; disabling one control does not disable the other.
|
|
369
|
+
|
|
351
370
|
## Audit messages
|
|
352
371
|
|
|
353
|
-
When the assistant marks a goal complete or blocked, the plugin announces the audit instead of doing it silently: an audit-start message ("Auditing goal completion…") and an audit-result message ("completion accepted — goal archived" / "paused as blocked — …"). By default these are written to OpenCode's structured log and shown as a TUI toast when that client capability is available. Provide an `auditMessenger(sessionID, text)` plugin option to route them elsewhere, or set `auditMessages: false` to disable them.
|
|
372
|
+
When the assistant marks a goal complete or blocked, the plugin announces the audit instead of doing it silently: an audit-start message ("Auditing goal completion…") and an audit-result message ("completion accepted — goal archived" / "paused as blocked — …"). By default these are written to OpenCode's structured log and shown as a TUI toast when that client capability is available. Provide an `auditMessenger(sessionID, text)` plugin option to route them elsewhere, or set `auditMessages: false` to disable them. The audit-result message owns the terminal completion/block announcement while `auditMessages` is enabled, so the lifecycle channel does not duplicate it.
|
|
373
|
+
|
|
374
|
+
Audit messages are visibility only; enabling them does not turn on the independent completion auditor. The evidence gate always applies. Independent verification is enabled only with `completionAudit: true` or a custom `auditor`.
|
|
375
|
+
|
|
354
376
|
## Completion auditor (optional)
|
|
355
377
|
|
|
356
|
-
|
|
378
|
+
Every `[goal:complete]` claim must first pass the local evidence gate described above. By default, that evidence gate is the only verifier. You can additionally require an independent audit before a goal is archived:
|
|
357
379
|
|
|
358
380
|
- `completionAudit: true` — the plugin spawns an independent OpenCode child session to verify the completion against the goal and workspace. The auditor replies with `[audit:approved]` or `[audit:rejected]` (with a reason).
|
|
359
381
|
- `auditor: async ({ goal, sessionID, latestText }) => ({ approved, reason })` — supply your own auditor function (takes precedence over `completionAudit`).
|
|
@@ -393,7 +415,7 @@ For `/goal status`, `/goal history`, `/goal list`, `/goal pause`, and `/goal cle
|
|
|
393
415
|
|
|
394
416
|
The plugin still registers `experimental.chat.system.transform` as defense in depth for hosts that invoke it. Real OpenCode 1.17.15 and 1.18.10 do not call that hook, so the command-control protections above are deliberately self-contained. Other OpenCode plugin hooks may change between versions.
|
|
395
417
|
|
|
396
|
-
Distinct OpenCode sessions may own shards under the same `stateFilePath` concurrently. A second process using the same session shard
|
|
418
|
+
Distinct OpenCode sessions may own shards under the same `stateFilePath` concurrently. A second process using the same session shard remains usable in passive goal mode, but goal commands and tools are denied until it can acquire that shard. The passive process never falls back to an unpersisted copy of the same goal workflow, which avoids divergent state and last-writer-wins data loss. Use the owner, wait and retry an explicit goal control after it exits, or fork to a new session. A no-replace compatibility guard prevents an older release and the current release from both acquiring during startup; current immutable claims protect takeover and release among upgraded processes. Older processes cannot take over a guarded shard, so all processes participating in automatic same-session handoff must run the current release.
|
|
397
419
|
|
|
398
420
|
## Diagnostics and recovery
|
|
399
421
|
|
|
@@ -403,9 +425,10 @@ If a goal does not continue:
|
|
|
403
425
|
|
|
404
426
|
1. Check for a deliberate pause: user intervention, a hard limit, repeated tool-free/no-progress turns, prompt failures, or a rejected completion audit all stop unattended work by design.
|
|
405
427
|
2. Run `/goal resume` only after resolving the reported reason. Resume creates a fresh local budget window; it does not erase the objective or history.
|
|
406
|
-
3.
|
|
407
|
-
4.
|
|
408
|
-
5.
|
|
428
|
+
3. If a goal control reports that another process owns the session, close that owner and retry the control, or fork to a new session. If it instead reports an older, incomplete, tampered, or unsupported lease, close every process that could own the session and upgrade them first; if the report persists, remove only the affected shard's adjacent `.lock` file or legacy directory and `.lock.claims-v2` directory, or fork. Keep the state and ledger. Do not point two copies of the same session at different state paths: that creates divergent goal histories.
|
|
429
|
+
4. Check OpenCode's structured logs for persistence, SDK-shape, prompt, or auditor errors.
|
|
430
|
+
5. Confirm the configured project directory and state-path precedence described under [Safety limits](#safety-limits). A daemon started elsewhere can otherwise make a manually configured relative path surprising.
|
|
431
|
+
6. Run `npm run verify`, `npm run smoke`, and `npm run smoke:packed-host` against the installed source when diagnosing registration or packaging problems. Maintainers can run `npm run release:check` for the complete artifact and quality gate. `npm run benchmark:behavior` exercises completion, false-completion, loop, interruption, compaction, and restart behavior without a provider call.
|
|
409
432
|
|
|
410
433
|
Do not paste `state.json`, its ledger, or verbose logs into a public issue without reviewing them first: they can contain goal text, assistant checkpoints, blockers, local paths, and command evidence. Prefer the bounded status/history output and redact project-specific content. There is intentionally no broad "dump diagnostics" tool: exposing process-wide session state or persistence paths to the model would add more privacy risk than troubleshooting value.
|
|
411
434
|
|
package/docs/compatibility.md
CHANGED
|
@@ -11,7 +11,8 @@ The latest published release is the supported line. Public compatibility covers:
|
|
|
11
11
|
- the six canonical goal tools and five legacy tool aliases
|
|
12
12
|
- persisted-state recovery from versions documented in the changelog
|
|
13
13
|
- concurrent persistence for distinct OpenCode sessions in one project, with
|
|
14
|
-
single-writer protection retained per session
|
|
14
|
+
single-writer protection retained per session and passive goal behavior for
|
|
15
|
+
a same-session process that does not own the lease
|
|
15
16
|
|
|
16
17
|
The package requires Node.js 18 or newer and OpenCode 1.17.15 through the latest
|
|
17
18
|
compatible 1.x release. CI runs the complete unit suite on Node 18, 20, 22, and
|
|
@@ -25,6 +26,33 @@ them; the plugin does not claim that Windows provides equivalent POSIX semantics
|
|
|
25
26
|
The Windows job also runs the installed-package type, host, and tool contracts
|
|
26
27
|
so their portable npm launcher path is exercised in CI.
|
|
27
28
|
|
|
29
|
+
When two processes open the same OpenCode session, only the lease owner may read
|
|
30
|
+
or change that session's goal workflow. The contender keeps ordinary chat and
|
|
31
|
+
unrelated tools available, but goal controls are denied and ambient hooks do not
|
|
32
|
+
attempt a takeover. Canonical goal tools return the stable envelope code
|
|
33
|
+
`session_owned_elsewhere`; a `/goal` slash command instead produces a
|
|
34
|
+
human-readable denial through its normal model-rendered command turn. Once the
|
|
35
|
+
owner exits, an explicit goal command or tool may acquire the shard; recovered
|
|
36
|
+
active goals load paused and require an explicit resume. Forking creates a
|
|
37
|
+
distinct session shard and remains the supported way to work concurrently from
|
|
38
|
+
the same conversation.
|
|
39
|
+
|
|
40
|
+
The immutable-claim lease protocol atomically hard-links a complete regular-file
|
|
41
|
+
compatibility guard at `<shard>/state.json.lock`; active owners publish unique
|
|
42
|
+
claims in the sibling `<shard>/state.json.lock.claims-v2/` directory. Publication
|
|
43
|
+
is no-replace: an older lock directory and the current guard cannot both win the
|
|
44
|
+
same startup race. Older releases treat the future-dated guard as non-reclaimable,
|
|
45
|
+
while current releases determine ownership only from immutable claims. Automatic
|
|
46
|
+
takeover requires all participating processes to run the current release.
|
|
47
|
+
Legacy, incomplete, tampered, or unsupported lease layouts fail closed rather
|
|
48
|
+
than being rewritten online. If that condition persists, first close every
|
|
49
|
+
OpenCode process that could own the session and upgrade them; then either fork
|
|
50
|
+
the session or manually remove only the affected shard's adjacent `.lock` file
|
|
51
|
+
or legacy directory and `.lock.claims-v2` directory. Do not remove its state or
|
|
52
|
+
lifecycle ledger. The local filesystem must support regular-file hard links
|
|
53
|
+
and preserve the guard's future timestamp; the plugin does not fall back to a
|
|
54
|
+
weaker publication protocol.
|
|
55
|
+
|
|
28
56
|
## OpenCode host compatibility
|
|
29
57
|
|
|
30
58
|
OpenCode's experimental hooks and SDK request shapes may change within the 1.x
|
package/index.d.ts
CHANGED
|
@@ -324,6 +324,23 @@ export interface GoalPluginOptions {
|
|
|
324
324
|
*/
|
|
325
325
|
auditorOptions?: CompletionAuditorOptions
|
|
326
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Whether the plugin announces applied goal-state transitions such as
|
|
329
|
+
* creation, pause/resume, recovery, promotion, and clearing. Routine
|
|
330
|
+
* idle/checkpoint activity is not announced. Completion/block uses the
|
|
331
|
+
* audit-result channel when enabled and this lifecycle channel only as its
|
|
332
|
+
* disabled fallback.
|
|
333
|
+
* @default true
|
|
334
|
+
*/
|
|
335
|
+
lifecycleMessages?: boolean
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Custom sink for bounded lifecycle notices. Defaults to routing through
|
|
339
|
+
* OpenCode's structured log (`client.app.log`) and TUI toast when those host
|
|
340
|
+
* APIs are available. Delivery is advisory and does not make model calls.
|
|
341
|
+
*/
|
|
342
|
+
lifecycleMessenger?: (sessionID: string, text: string) => Promise<void> | void
|
|
343
|
+
|
|
327
344
|
/**
|
|
328
345
|
* Whether the plugin announces completion/blocked audits (an
|
|
329
346
|
* audit-start and an audit-result message) instead of running silently.
|
package/package.json
CHANGED
package/scripts/verify.mjs
CHANGED
|
@@ -146,7 +146,8 @@ await check("/goal set works", async () => {
|
|
|
146
146
|
assert.match(text, /New active goal: verify the installation/)
|
|
147
147
|
const statusText = await runGoalCommand("status")
|
|
148
148
|
assert.match(statusText, /Active goal: verify the installation/)
|
|
149
|
-
assert.
|
|
149
|
+
assert.match(statusText, /State: active/)
|
|
150
|
+
assert.match(statusText, /Completion audit: evidence gate only \(independent verifier off\)/)
|
|
150
151
|
})
|
|
151
152
|
|
|
152
153
|
await check("no model calls were made during verification", () => {
|
|
@@ -156,6 +157,13 @@ await check("no model calls were made during verification", () => {
|
|
|
156
157
|
// Clean up the goal created above so this script has no side effects.
|
|
157
158
|
await runGoalCommand("clear")
|
|
158
159
|
|
|
160
|
+
await check("lifecycle transitions are visible without leaking objective text", () => {
|
|
161
|
+
assert.deepEqual(logCalls.map((entry) => entry.body.extra.kind), ["goal-lifecycle", "goal-lifecycle"])
|
|
162
|
+
assert.match(logCalls[0].body.message, /Goal (?:active|started)/i)
|
|
163
|
+
assert.match(logCalls[1].body.message, /Goal cleared/i)
|
|
164
|
+
assert.ok(logCalls.every((entry) => !entry.body.message.includes("verify the installation")))
|
|
165
|
+
})
|
|
166
|
+
|
|
159
167
|
console.log()
|
|
160
168
|
|
|
161
169
|
const failed = results.filter((r) => !r.ok)
|