arcane-cli 0.16.4 → 0.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/dist/assets/.arcane/governance/agent-policies.md +1 -0
- package/dist/assets/.arcane/governance/cicd-standards.md +103 -3
- package/dist/assets/.arcane/governance/git-conventions.md +15 -0
- package/dist/assets/.github/prompts/spell-check-drift.prompt.md +6 -6
- package/dist/assets/.github/prompts/spell-close-session.prompt.md +1 -1
- package/dist/assets/.github/prompts/spell-commit-work.prompt.md +2 -2
- package/dist/assets/.github/prompts/spell-open-session.prompt.md +5 -5
- package/dist/assets/.github/prompts/spell-plan.prompt.md +7 -1
- package/dist/assets/.github/prompts/spell-save-idea.prompt.md +4 -2
- package/dist/assets/.github/prompts/spell-ship.prompt.md +1 -1
- package/dist/assets/.github/prompts/spell-summon-venture.prompt.md +4 -4
- package/dist/assets/.github/prompts/spell-todo.prompt.md +7 -5
- package/dist/index.js +83 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -306,6 +306,7 @@ When more than one agent may work in the same repository simultaneously:
|
|
|
306
306
|
5. **Story ownership.** Only the agent assigned to a feature modifies that feature's `stories.json`. If the assigned agent does not match your slug, halt and report.
|
|
307
307
|
6. **No cross-repo mutations without coordination.** If a story requires changes in two repos, complete and push one before starting the second. Never hold uncommitted changes across two repos simultaneously.
|
|
308
308
|
7. **Merge window.** When two agents have PRs open for the same repo, the first to merge wins; the second must rebase before merging. The operator coordinates merge order if there are conflicts.
|
|
309
|
+
8. **Working-tree vantage point (EF-33 / ARC-028 R7) — applies even to a solo agent, not only when multiple agents are active.** Listed here because it's the working-tree/multi-primitive dimension ARC-028 R7 defines, not because the hazard requires a second agent: a lone agent working through a bridged/remote mount hits the identical near-miss. All linked worktrees share one physical `.git` and `.git/config` — treat config-mutating operations as fleet-wide, not scoped to the worktree that ran them. Before any `git worktree prune`, `worktree remove`, `gc`, or branch deletion, confirm the target path from the *current process's own filesystem* (not just Git's own report) — a repository or its linked worktrees reached through a bridged/remote mount can report a live, healthy worktree as safe to prune with no error distinguishing "confirmed absent" from "not resolvable from here." See [governance/git-conventions.md](git-conventions.md) Same-Vantage-Point Check section for the full procedure, including how to establish that your own vantage point is trustworthy before relying on it.
|
|
309
310
|
|
|
310
311
|
---
|
|
311
312
|
|
|
@@ -57,6 +57,17 @@ For docs-only repos (Arcane):
|
|
|
57
57
|
|
|
58
58
|
### .NET Backend Pipeline
|
|
59
59
|
|
|
60
|
+
Fail-safe path filter (ARC-022): **exclude** known-inert doc paths rather than **include** a
|
|
61
|
+
named code directory — a new code directory added outside `src/**` would silently never trigger
|
|
62
|
+
this pipeline under an include filter. The exclude entry is a **filetype** glob, not a directory
|
|
63
|
+
glob, and deliberately just one entry: `docs/**`/`journal/**`/`.arcane/governance/**`-style
|
|
64
|
+
directory-wide excludes would ALSO exclude any non-Markdown file a consumer happens to place in
|
|
65
|
+
those directories (a build script, a manifest, tooling config) — silently violating ARC-022's own
|
|
66
|
+
unconditional requirement that scripts, manifests, lockfiles, migrations, containers, and
|
|
67
|
+
infrastructure always remain triggering inputs, regardless of which directory they live in. Filetype-scoping to
|
|
68
|
+
`**/*.md` closes that gap: only actual Markdown files are excluded, anywhere in the tree; anything
|
|
69
|
+
else — including a `.md`-adjacent script — still triggers CI by default.
|
|
70
|
+
|
|
60
71
|
```yaml
|
|
61
72
|
# azure-pipelines.yml
|
|
62
73
|
trigger:
|
|
@@ -64,8 +75,8 @@ trigger:
|
|
|
64
75
|
include:
|
|
65
76
|
- main
|
|
66
77
|
paths:
|
|
67
|
-
|
|
68
|
-
-
|
|
78
|
+
exclude:
|
|
79
|
+
- '**/*.md'
|
|
69
80
|
|
|
70
81
|
pr:
|
|
71
82
|
branches:
|
|
@@ -97,11 +108,19 @@ steps:
|
|
|
97
108
|
|
|
98
109
|
### Node.js Pipeline
|
|
99
110
|
|
|
111
|
+
Previously had no path filter at all — fail-safe (never misses a code change) but wasteful (a
|
|
112
|
+
pure docs commit still burns a full pipeline run). Fail-safe path filter (ARC-022): the same
|
|
113
|
+
filetype-scoped `**/*.md` **exclude** entry as the .NET template above — never a directory-wide
|
|
114
|
+
exclude, and never an include list.
|
|
115
|
+
|
|
100
116
|
```yaml
|
|
101
117
|
trigger:
|
|
102
118
|
branches:
|
|
103
119
|
include:
|
|
104
120
|
- main
|
|
121
|
+
paths:
|
|
122
|
+
exclude:
|
|
123
|
+
- '**/*.md'
|
|
105
124
|
|
|
106
125
|
pr:
|
|
107
126
|
branches:
|
|
@@ -129,7 +148,27 @@ steps:
|
|
|
129
148
|
|
|
130
149
|
### Terraform Pipeline
|
|
131
150
|
|
|
151
|
+
An **include** filter is correct here, not the ARC-022 anti-pattern — but only if it's a
|
|
152
|
+
**filetype** glob, not a directory-prefix one. An earlier version of this template included only
|
|
153
|
+
`infrastructure/terraform/**`: a directory-prefix pattern that silently misses any `.tf` file
|
|
154
|
+
added *outside* that one named directory (a new `modules/**` root, a renamed infra folder) —
|
|
155
|
+
exactly the "new code locations fail open" failure ARC-022 rejects, and exactly what EF-22's own
|
|
156
|
+
report flagged for this template specifically, grouped with .NET's `src/**` problem, not treated
|
|
157
|
+
as a different category. `**/*.tf`/`**/*.tfvars` has the same closure property `**/*.md` has for
|
|
158
|
+
the Markdown-lint template below: Terraform files trigger this pipeline from *anywhere* in the
|
|
159
|
+
tree, while a Node.js/`.NET` source change still correctly does not. Widened to also cover this
|
|
160
|
+
pipeline's own definition file, so an edit to the pipeline itself is validated rather than
|
|
161
|
+
silently unreviewed.
|
|
162
|
+
|
|
163
|
+
Widened further to cover Terraform's JSON-syntax file variants (`.tf.json`/`.tfvars.json`,
|
|
164
|
+
HashiCorp's own first-class alternative syntax to `.tf`/`.tfvars`) and its dependency lock file
|
|
165
|
+
(`.terraform.lock.hcl`, one per root module directory). ARC-022 names lockfiles as a non-negotiable
|
|
166
|
+
always-trigger category in their own right, independent of the general "Terraform files trigger
|
|
167
|
+
this pipeline" argument above — a lock file's own name doesn't end in `.tf`/`.tfvars`, so it would
|
|
168
|
+
have silently fallen through the filetype glob above without a named entry of its own.
|
|
169
|
+
|
|
132
170
|
```yaml
|
|
171
|
+
# azure-pipelines.terraform.yml
|
|
133
172
|
trigger: none # Manual only for apply
|
|
134
173
|
|
|
135
174
|
pr:
|
|
@@ -138,7 +177,12 @@ pr:
|
|
|
138
177
|
- main
|
|
139
178
|
paths:
|
|
140
179
|
include:
|
|
141
|
-
-
|
|
180
|
+
- '**/*.tf'
|
|
181
|
+
- '**/*.tfvars'
|
|
182
|
+
- '**/*.tf.json'
|
|
183
|
+
- '**/*.tfvars.json'
|
|
184
|
+
- '**/.terraform.lock.hcl'
|
|
185
|
+
- azure-pipelines.terraform.yml
|
|
142
186
|
|
|
143
187
|
pool:
|
|
144
188
|
vmImage: 'ubuntu-latest'
|
|
@@ -160,7 +204,14 @@ steps:
|
|
|
160
204
|
|
|
161
205
|
### Markdown Lint Pipeline
|
|
162
206
|
|
|
207
|
+
Also correctly include-scoped, for the same reason as the Terraform pipeline: this pipeline
|
|
208
|
+
exists specifically to lint markdown, not to validate code generally, and `**.md` already has the
|
|
209
|
+
same anywhere-in-the-tree closure property `**/*.tf` gives the Terraform template above (unlike a
|
|
210
|
+
directory-prefix pattern, it was never the gap this fix needed to close). Widened to cover its
|
|
211
|
+
own definition file too.
|
|
212
|
+
|
|
163
213
|
```yaml
|
|
214
|
+
# azure-pipelines.markdown.yml
|
|
164
215
|
trigger:
|
|
165
216
|
branches:
|
|
166
217
|
include:
|
|
@@ -168,6 +219,7 @@ trigger:
|
|
|
168
219
|
paths:
|
|
169
220
|
include:
|
|
170
221
|
- '**.md'
|
|
222
|
+
- azure-pipelines.markdown.yml
|
|
171
223
|
|
|
172
224
|
pool:
|
|
173
225
|
vmImage: 'ubuntu-latest'
|
|
@@ -187,6 +239,54 @@ steps:
|
|
|
187
239
|
|
|
188
240
|
---
|
|
189
241
|
|
|
242
|
+
## Never Trust Commit Metadata for CI Skipping (ARC-022)
|
|
243
|
+
|
|
244
|
+
CI skip/run decisions are based **only on changed paths** — never on commit message, author
|
|
245
|
+
identity, or branch name. Do not add a *new* mechanism (a custom `docs(...)`-style commit-prefix
|
|
246
|
+
convention, a bot rule, a pipeline-level check) that lets a commit opt itself out of validation by
|
|
247
|
+
what it *says* rather than what it *changed*.
|
|
248
|
+
|
|
249
|
+
Commit messages, authorship, and branch names are attacker-controlled metadata — anyone who can
|
|
250
|
+
push a commit controls all three. A CI-skip mechanism keyed on any of them is a bypass primitive:
|
|
251
|
+
it lets a malicious or careless commit self-certify as "safe to skip" regardless of what it
|
|
252
|
+
actually touched. Path-based filtering (above) is the only trust signal this repository
|
|
253
|
+
recognizes, because the pipeline itself independently observes the changed paths rather than
|
|
254
|
+
trusting a claim embedded in the commit.
|
|
255
|
+
|
|
256
|
+
**Know the platform default, don't assume this rule alone controls it.** Azure Pipelines honors
|
|
257
|
+
`[skip ci]`, `[ci skip]`, `skip-checks: true`, `[skip azurepipelines]`, `[skip azpipelines]`,
|
|
258
|
+
`[skip azp]`, and `***NO_CI***` in a pushed commit message **by default**, on `trigger:`-driven
|
|
259
|
+
runs — this is platform behavior, not something a consumer opts into, and this governance rule
|
|
260
|
+
cannot disable it from the YAML alone. The required-before-merge gate this repo's own Branch
|
|
261
|
+
Policies table relies on (**build validation on the PR's merge commit**) is documented to run
|
|
262
|
+
*regardless* of `[skip ci]` and its variants, so the merge gate itself stays sound. What is **not**
|
|
263
|
+
protected: any other trigger-based run this repo depends on for signal outside that one gate —
|
|
264
|
+
direct pushes to a non-PR branch, a secondary environment build, a dashboard keyed off pipeline
|
|
265
|
+
status. Treat those as a residual, platform-level exposure this document can flag but not close;
|
|
266
|
+
if a trigger-based run must be tamper-resistant, that requires an organization-level policy
|
|
267
|
+
outside this repo's own YAML, not a governance sentence.
|
|
268
|
+
|
|
269
|
+
## Branch-Policy Path-Filter Alignment (ARC-022)
|
|
270
|
+
|
|
271
|
+
A YAML pipeline's own `trigger`/`pr` path filter and an Azure DevOps branch policy's **build
|
|
272
|
+
validation path filter** are two independent mechanisms, and only one of them controls whether a
|
|
273
|
+
passing build is actually *required* before a PR can merge. If the branch policy's path filter is
|
|
274
|
+
narrower than the pipeline's own trigger scope, a change that falls outside the policy's filter
|
|
275
|
+
can merge with **no build check required at all** — even though the pipeline itself is correctly
|
|
276
|
+
scoped and would have run and caught a problem, because nothing forced it to be a merge
|
|
277
|
+
requirement for that specific change.
|
|
278
|
+
|
|
279
|
+
**Concrete failure mode:** the .NET pipeline above triggers on everything except docs (exclude
|
|
280
|
+
filter, fail-safe). If the repo's branch policy still requires the build check only for changes
|
|
281
|
+
under `paths: src/**` (an older include-style policy filter, or one configured before this repo
|
|
282
|
+
adopted exclude-based YAML triggers), a change to a new top-level code directory outside `src/`
|
|
283
|
+
triggers the pipeline (YAML is fail-safe) but does **not** require it to pass before merge (branch
|
|
284
|
+
policy is not) — the build can run, fail, and be ignored, or the PR can merge before it finishes.
|
|
285
|
+
Whenever a pipeline's YAML trigger scope changes, the branch policy's own path filter (if it has
|
|
286
|
+
one) must be reviewed and updated to match or be a strict superset — never left narrower.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
190
290
|
## Deployment Gates
|
|
191
291
|
|
|
192
292
|
| Environment | Gate | Who Approves |
|
|
@@ -359,6 +359,21 @@ git push origin --delete type/old-topic # delete remote branch (safety net; ign
|
|
|
359
359
|
|
|
360
360
|
This applies to all actors — humans, interactive tools (Copilot, Claude), and autonomous agents. The `spell-commit-work` and `spell-close-session` prompts enforce this check.
|
|
361
361
|
|
|
362
|
+
### Same-Vantage-Point Check (EF-33 / ARC-028 R7)
|
|
363
|
+
|
|
364
|
+
`git worktree list` can **truthfully** report a live, healthy linked worktree as `prunable` when read through a cross-filesystem bridge — a Linux-side mount of a Windows host, a remote-mounted volume, a container bind-mount, any path-translation layer between the reading process and the repository's actual owning environment. The registered absolute path simply doesn't resolve from that vantage point, even though the directory exists and is valid from the machine that owns it. Nothing in Git's own output distinguishes "confirmed absent" from "not resolvable from this process's filesystem view" — both render identically as `prunable`. (A separate, unconditional hazard applies to `git branch --merged` regardless of vantage point — see the note at the end of this section; do not assume that one only matters when a bridge is in play.)
|
|
365
|
+
|
|
366
|
+
**Before running any Git command with irreversible effects on worktree, branch, or ref state** — `git worktree prune`, `git worktree remove`, `git gc --prune=now`, `git branch -d`/`-D`, or manual deletion of `.git/worktrees/<name>` metadata — **when the repository or its linked worktrees might be reached through more than one filesystem view**, perform the same-vantage-point check first:
|
|
367
|
+
|
|
368
|
+
1. Resolve the registered path exactly as Git sees it (`git worktree list --porcelain`'s `worktree` field).
|
|
369
|
+
2. **First confirm your OWN vantage point, before trusting anything else.** Checking `Test-Path`/`[ -e "$path" ]`/`os.path.exists()` against the registered path is only meaningful if the process running that check is itself in the same environment that registered the worktree — running the "independent" check from the *same* bridged/mounted process that produced the `git worktree list` read reproduces the exact failure this section exists to prevent, since both signals are corrupted by the identical wrong vantage point. A concrete, checkable signal for this: does the registered path's own syntax match the current process's native OS conventions (drive letter + backslashes for Windows; POSIX root + forward-slashes for Linux/macOS)? A registered path in a **foreign** syntax (e.g. a `C:\...` path seen from a POSIX shell, or vice versa) is itself proof you are not viewing this repository from the environment that registered it — treat every entry as unconfirmed on sight, skip step 3 entirely, and go straight to step 4. Matching syntax alone is not proof of a shared vantage point (two different containers can both use POSIX paths) — it only rules out the specific cross-OS case; it does not establish confidence on its own.
|
|
370
|
+
3. Only if step 2 gives you a genuine reason to trust your own vantage point (matching path syntax, plus independent knowledge that this is the same machine/container/session that registered the worktree — not merely "the check ran without error"): confirm the path from the current process's own filesystem (`Test-Path` / `[ -e "$path" ]` / `os.path.exists()` / equivalent). Never treat Git's own `prunable` annotation as sufficient evidence on its own, and never treat "the existence check returned a clean answer" as sufficient either — a clean answer from the wrong vantage point is not a safe answer.
|
|
371
|
+
4. If step 2 raised any doubt, if the path syntax doesn't match, or if there is any uncertainty about which environment actually owns the repository, **stop and ask** rather than trusting the more convenient answer. This is the default outcome whenever step 2's confidence isn't genuinely established — not a rare escape hatch reserved for exceptional cases.
|
|
372
|
+
|
|
373
|
+
This is documented as a standing operational caution, not a code-level gate: EF-33's own intake report frames the underlying defect as cross-machine/cross-mount filesystem visibility, which no single CI runner can reproduce — the confirmation step above must be followed manually by whichever human or agent is about to run the destructive command. Confirmed as a live near-miss on 2026-08-03: a Linux-side read reported eight healthy linked worktrees `prunable`; the same repository read from Windows (their actual owning environment) showed zero prunable entries, and `git worktree prune -v` there removed nothing.
|
|
374
|
+
|
|
375
|
+
**Unconditional, vantage-point-independent note on branch deletion:** `git branch --merged` can also report a fully-landed branch as unmerged — or the reverse confusion, a branch never actually reviewed but ancestry-visible — because Git's rebase-and-fast-forward and squash merge strategies (both sanctioned by this repo's own merge-strategy table above) rewrite commit SHAs, defeating ancestry-based detection. This has nothing to do with filesystem bridges — it fires identically on a single machine with no mount involved. See the ancestry-vs-content-verification finding in `TODO.md`'s PR Workflow section for the full mechanism; verify branch content before deleting regardless of what vantage-point confidence steps 1-4 above establish.
|
|
376
|
+
|
|
362
377
|
### ADO PR Lifecycle — Complete Command Reference
|
|
363
378
|
|
|
364
379
|
When working in an Azure DevOps repo with branch protection (direct push to `main` rejected), use this exact sequence. Several shortcut tools have known reliability issues on Windows — use the commands below instead.
|
|
@@ -27,8 +27,8 @@ Use these files first:
|
|
|
27
27
|
- [ai-context/system-prompt-context.md](../../ai-context/system-prompt-context.md)
|
|
28
28
|
- [agents/agent-policies.md](../../.arcane/governance/agent-policies.md)
|
|
29
29
|
- Most recent journal file(s) in [journal/](../../journal/)
|
|
30
|
-
- Relevant business overviews under
|
|
31
|
-
- `
|
|
30
|
+
- Relevant business overviews under `{BUSINESS_ROOT}/` (resolve from `.arcane.json`'s `business_root` field, default `ventures/` if unset)
|
|
31
|
+
- `{BUSINESS_ROOT}/registry.json`, if present (hub-role and registry-consistency detectors)
|
|
32
32
|
|
|
33
33
|
Scope selection (`--scope <value>`, default `all`):
|
|
34
34
|
|
|
@@ -49,12 +49,12 @@ Concrete detectors to run (in addition to general contradiction/stale-date check
|
|
|
49
49
|
- If normalized content differs, report **real content drift** with normal severity grading.
|
|
50
50
|
- If normalized content matches, report **line-ending-only drift** separately. Do not count these paths as real content findings and do not include them in severity totals or the Go / No-Go decision.
|
|
51
51
|
- Report exact path counts for both classes. Never summarize all byte-different files as content drift.
|
|
52
|
-
- **Hub-artifact leak (non-hub repos)** — if `.arcane.json` does not have `role: "hub"`, the existence of any `
|
|
52
|
+
- **Hub-artifact leak (non-hub repos)** — if `.arcane.json` does not have `role: "hub"`, the existence of any `{BUSINESS_ROOT}/*/IDEAS.md`, `{BUSINESS_ROOT}/*/TODO.md`, or `{BUSINESS_ROOT}/registry.json` (resolve `{BUSINESS_ROOT}` the same way as above -- a consumer repo can still have a configured `business_root` left over from a prior role change, so this must not skip resolution just because the repo isn't currently a hub) is **Critical**: hub-only artifacts (potentially including sibling-venture and portfolio data) do not belong in a repo that isn't a declared hub. This check is deliberately structural (file existence only) — a non-hub repo cannot safely hold the sibling-name list a content-level check would need, so that asymmetry is by design, not a gap.
|
|
53
53
|
- **Hub role/registry consistency (hub repos only)** — when `.arcane.json` has `role: "hub"`:
|
|
54
|
-
- `role: "hub"` with no `{
|
|
55
|
-
- `
|
|
54
|
+
- `role: "hub"` with no `{BUSINESS_ROOT}` directory (default `ventures/`) present — **High**: the manifest claims a role the repo can't actually fulfill.
|
|
55
|
+
- `{BUSINESS_ROOT}/registry.json` slugs that don't match an existing `{BUSINESS_ROOT}/<slug>/` folder, or venture folders with no registry entry — **Medium**, both directions.
|
|
56
56
|
- Malformed `status: promoted → …` / `status: dropped …` markers in any book (unparseable destination, missing date) — **Low**.
|
|
57
|
-
- A `
|
|
57
|
+
- A `{BUSINESS_ROOT}/<slug>/` folder with a `role`-shaped `.arcane.json`-like marker of its own (i.e., it looks like it's trying to be a hub itself) — **Medium**: flag for operator review; nested hubs are unsupported.
|
|
58
58
|
|
|
59
59
|
For each drift finding include:
|
|
60
60
|
|
|
@@ -148,7 +148,7 @@ Rules:
|
|
|
148
148
|
- Resolve `<remote>` and `<trunk>` from observed Git/provider state: use the usable authenticated remote selected in step 9 and the merged PR's target branch (falling back to that remote's default branch). Never assume `origin` or `main`.
|
|
149
149
|
- Verify through the detected provider that the PR is merged before changing branches.
|
|
150
150
|
- Run `git switch <trunk>` followed by `git pull --ff-only <remote> <trunk>`. Do not end a remote-backed session on a topic branch.
|
|
151
|
-
- Delete the local topic branch: `git branch -d <branch
|
|
151
|
+
- Delete the local topic branch: `git branch -d <branch>` — if the repository or its linked worktrees might be reached through more than one filesystem view, run the same-vantage-point check first (EF-33 / ARC-028 R7, [governance/git-conventions.md](../../.arcane/governance/git-conventions.md) Same-Vantage-Point Check section).
|
|
152
152
|
- Verify `git log --oneline -3` shows the merged change at HEAD.
|
|
153
153
|
- If stale local branches exist (merged or older than 7 days), list them and suggest cleanup.
|
|
154
154
|
- See [governance/git-conventions.md](../../.arcane/governance/git-conventions.md) Post-Merge Cleanup section.
|
|
@@ -101,7 +101,7 @@ Workflow:
|
|
|
101
101
|
- `infrastructure` — infrastructure/ directory, hardware, OS setup
|
|
102
102
|
- `decisions` — DECISIONS.md updates
|
|
103
103
|
- `journal` — journal/ entries
|
|
104
|
-
- `business` — ventures
|
|
104
|
+
- `business` — `{BUSINESS_ROOT}` directory (resolve from `.arcane.json`'s `business_root` field, default `ventures/` if unset)
|
|
105
105
|
- `playbooks` — playbooks/ directory
|
|
106
106
|
- `governance` — governance/ directory
|
|
107
107
|
- Multiple scopes? Pick the primary one or use a broader scope like `docs` or `repo`.
|
|
@@ -206,7 +206,7 @@ Workflow:
|
|
|
206
206
|
- `git push origin --delete <branch>` (if already deleted, treat as non-fatal)
|
|
207
207
|
- `git fetch --prune origin`
|
|
208
208
|
- If `<branch>` is attached to an active worktree, skip local branch deletion.
|
|
209
|
-
- If branch is not attached to any active worktree, run `git branch -d <branch
|
|
209
|
+
- If branch is not attached to any active worktree, run `git branch -d <branch>` — but if the repository or its linked worktrees might be reached through more than one filesystem view, run the same-vantage-point check first (EF-33 / ARC-028 R7, [governance/git-conventions.md](../../.arcane/governance/git-conventions.md) Same-Vantage-Point Check section) before trusting a "safe to delete" read.
|
|
210
210
|
- Return to `main` only when appropriate for the active session/worktree context.
|
|
211
211
|
- If other stale local branches exist (merged or older than 7 days), list them and suggest cleanup.
|
|
212
212
|
- See [governance/git-conventions.md](../../.arcane/governance/git-conventions.md) Post-Merge Cleanup section.
|
|
@@ -28,7 +28,7 @@ Use these files first:
|
|
|
28
28
|
- [ai-context/system-prompt-context.md](../../ai-context/system-prompt-context.md)
|
|
29
29
|
- [agents/agent-policies.md](../../.arcane/governance/agent-policies.md)
|
|
30
30
|
- Most recent journal file(s) in [journal/](../../journal/)
|
|
31
|
-
- Relevant business overview(s) under
|
|
31
|
+
- Relevant business overview(s) under `{BUSINESS_ROOT}/` (resolve from `.arcane.json`'s `business_root` field, default `ventures/` if unset)
|
|
32
32
|
|
|
33
33
|
**Handoff Detection (run before anything else):**
|
|
34
34
|
|
|
@@ -70,17 +70,17 @@ Then produce output in this exact structure:
|
|
|
70
70
|
|
|
71
71
|
Before anything else, check:
|
|
72
72
|
|
|
73
|
-
- **Current branch:** Run `git branch --show-current` and `git worktree list`. Report the current state; do not switch during this read-only check. Branch creation/switching is owned by the lazy Mutation Guard.
|
|
73
|
+
- **Current branch:** Run `git branch --show-current` and `git worktree list`. Report the current state; do not switch during this read-only check. Branch creation/switching is owned by the lazy Mutation Guard. **Caveat (EF-33):** if this repository or its linked worktrees might be reached through more than one filesystem view (a bridged/remote mount, a container bind-mount), `git worktree list` can truthfully report a live, healthy worktree as `prunable` when read from a vantage point that can't resolve its registered path — this is a read-only report, not a reason to act; never run a destructive worktree/branch command on its output alone (see git-conventions.md's Same-Vantage-Point Check section before any such command elsewhere in this or later sessions).
|
|
74
74
|
- **Session branch naming compliance:** Branch names must be human-readable and policy-compliant:
|
|
75
75
|
- Interactive session default: `sessions/YYYY-MM-DD-<topic-slug>` (kebab-case topic from current task/session objective).
|
|
76
76
|
- Disallow random adjective-noun generator names (example: `ideal-disco`) as session defaults.
|
|
77
77
|
- If current branch is non-compliant and no active PR depends on it, record the required rename for the Mutation Guard. Do not rename during a read-only session.
|
|
78
78
|
- If an active PR uses the old branch name, do not force-delete the old remote branch; flag it and continue with a follow-up rename plan.
|
|
79
|
-
- **Stale local branches:** Run `git branch --merged main` to list branches already merged that should be deleted.
|
|
80
|
-
- **Tracker configuration check (early):** resolve active tracking settings before planning
|
|
79
|
+
- **Stale local branches:** Run `git branch --merged main` to list branches already merged that should be deleted. This lists candidates only — before deleting any, apply the same-vantage-point check (EF-33) and, separately, verify by content rather than trusting ancestry alone (rebase-and-fast-forward merges are invisible to `--merged`; see the merged-branch cleanup item in TODO.md).
|
|
80
|
+
- **Tracker configuration check (early):** resolve active tracking settings before planning, in this order: root `.arcane.json` (if present) -> the committed self-hosted source manifest (`src/assets/.arcane.json`, read only when it declares `selfHosted: true` -- this is EF-14's recorded self-hosting resolution tier, distinct from treating the mere presence of a self-host marker as license to infer other, unrelated config) -> the current feature PRD frontmatter -> ask. Persisted once at `spell init`/`spell update` (EF-14); do not re-ask when any of the first three sources already sets it.
|
|
81
81
|
- `tracking_mode: internal | external`
|
|
82
82
|
- `external_provider: ado | jira | other`
|
|
83
|
-
- If
|
|
83
|
+
- If all three sources are absent, ask: "Track work in this repo (TODO.md / PRDs)" [internal] vs "Track work in an external tracker (Azure DevOps / Jira / other)" [external]. Default to `external` + `ado` only when existing ADO context already exists (backward compatibility).
|
|
84
84
|
- **Open PRs (external/ado mode only):** if tracking mode is `external` with provider `ado` and ADO MCP is available, list all open PRs across all repos in the configured repo list (resolve from `.arcane.json`; if unset, ask the operator which repos to scan). Flag PRs older than 3 days as stale, older than 7 days as overdue. Format each as a clickable markdown link: `[PR #{id} — {title}](https://dev.azure.com/{ADO_ORG}/{ADO_PROJECT}/_git/{repo}/pullrequest/{id})` — resolve `{ADO_ORG}` and `{ADO_PROJECT}` from `.arcane.json` / PRD frontmatter; ask if unset. Never list a bare `PR #NNN`.
|
|
85
85
|
- **Uncommitted changes:** Run `git status` and report any uncommitted files.
|
|
86
86
|
- **Arcane version check (two-axis):** If `.arcane.json` exists in the repo root, check both whether the repo's managed files are behind the installed CLI _and_ whether the installed CLI is behind the latest published version of `arcane-cli`:
|
|
@@ -31,8 +31,14 @@ Workflow:
|
|
|
31
31
|
external_provider: ado | jira | other
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
Resolve in this order, matching spell-open-session's own resolution chain (EF-14): root
|
|
35
|
+
`.arcane.json` (if present) → the committed self-hosted source manifest (`src/assets/.arcane.json`,
|
|
36
|
+
read only when it declares `selfHosted: true`) → the current feature PRD frontmatter → ask.
|
|
37
|
+
Do not re-ask when any of the first three sources already sets it.
|
|
38
|
+
|
|
34
39
|
Rules:
|
|
35
40
|
- If not explicitly set and ADO context is already present (work item ID + org/project), default to `tracking_mode: external`, `external_provider: ado` for backward compatibility.
|
|
41
|
+
- If none of the three sources above set it and no ADO context exists, ask: "Track work in this repo (TODO.md / PRDs)" [internal] vs "Track work in an external tracker (Azure DevOps / Jira / other)" [external].
|
|
36
42
|
- If `tracking_mode=internal`, do not require an external tracker ID.
|
|
37
43
|
- If `tracking_mode=external` and `external_provider=ado`, collect org/project and resolve process template + available work item types before planning:
|
|
38
44
|
```bash
|
|
@@ -64,7 +70,7 @@ Workflow:
|
|
|
64
70
|
- Are there existing ADRs or constraints?
|
|
65
71
|
- **If `tracking_mode=external` and `external_provider=ado`: What is the ADO work item ID?** — required before proceeding in ADO mode. Store it as `adoWorkItemId` in PRD frontmatter so it flows to `spell-architect` and `stories.json`.
|
|
66
72
|
|
|
67
|
-
3. **Research context** — check the relevant business docs
|
|
73
|
+
3. **Research context** — check the relevant business docs under `{BUSINESS_ROOT}/` (resolve from `.arcane.json`'s `business_root` field, default `ventures/` if unset) and any existing code repos.
|
|
68
74
|
|
|
69
75
|
4. **Generate PRD.md** with these sections:
|
|
70
76
|
```markdown
|
|
@@ -21,10 +21,12 @@ The user's input IS the idea. If no idea text was provided, ask one short questi
|
|
|
21
21
|
|
|
22
22
|
## Step 0 — Venture Targeting (Hub Only)
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Resolve `{BUSINESS_ROOT}` from `.arcane.json`'s `business_root` field (default `ventures/` if unset) before anything below.
|
|
25
|
+
|
|
26
|
+
If the input names a venture ("save this idea for ordo", `--venture <slug>`), resolve it through `{BUSINESS_ROOT}/registry.json`'s aliases first (exact slug → alias → closest match offered — never guessed). Unknown slug:
|
|
25
27
|
|
|
26
28
|
```
|
|
27
|
-
No venture "<slug>" under
|
|
29
|
+
No venture "<slug>" under {BUSINESS_ROOT}/ (closest: ordovica, tidewright).
|
|
28
30
|
1) use <closest> 2) save to hub root IDEAS.md 3) cancel (create the venture first: spell-summon-venture)
|
|
29
31
|
```
|
|
30
32
|
|
|
@@ -93,7 +93,7 @@ Workflow:
|
|
|
93
93
|
9. **Post-merge branch cleanup** (after the PR merges) — the merge was the human gate, so cleanup is automatic:
|
|
94
94
|
- Switch to the main branch (e.g. `git checkout main`).
|
|
95
95
|
- Pull the latest (e.g. `git pull`).
|
|
96
|
-
- Delete the merged topic branch locally (e.g. `git branch -d <branch>`).
|
|
96
|
+
- Delete the merged topic branch locally (e.g. `git branch -d <branch>`) — if the repository or its linked worktrees might be reached through more than one filesystem view, run the same-vantage-point check first (EF-33 / ARC-028 R7, [governance/git-conventions.md](../../.arcane/governance/git-conventions.md) Same-Vantage-Point Check section).
|
|
97
97
|
- Delete the merged topic branch on the remote (e.g. `git push origin --delete <branch>`); tolerate an already-deleted remote branch.
|
|
98
98
|
- Prune stale remote-tracking refs (e.g. `git fetch --prune` or `git remote prune origin`).
|
|
99
99
|
|
|
@@ -7,7 +7,7 @@ agent: agent
|
|
|
7
7
|
|
|
8
8
|
## Executive Summary
|
|
9
9
|
|
|
10
|
-
- This prompt creates a new venture in a **hub** repo: the venture folder, its starter docs, its two idea/todo books, and its
|
|
10
|
+
- This prompt creates a new venture in a **hub** repo: the venture folder, its starter docs, its two idea/todo books, and its registry entry — one operation instead of several manual steps.
|
|
11
11
|
- **Hub-gated:** requires `role: "hub"` in `.arcane.json`. Refuses cleanly in a consumer repo, pointing at the right place to capture the idea instead.
|
|
12
12
|
- Use this when starting a new venture (business, product, or personal project) that the hub should track.
|
|
13
13
|
- Input format is: `Venture Name | Venture Type | Description` — prompt will ask for missing fields.
|
|
@@ -36,7 +36,7 @@ Use these files first (each is optional — if missing, proceed as noted):
|
|
|
36
36
|
|
|
37
37
|
- [playbooks/new-business-setup.md](../../playbooks/new-business-setup.md) — setup playbook. If `playbooks/` doesn't exist, skip and derive steps from the venture-type checklist below.
|
|
38
38
|
- [naming-conventions.md](../../.arcane/governance/naming-conventions.md) — slug/file naming rules. If missing, default to lowercase-with-dashes slugs.
|
|
39
|
-
-
|
|
39
|
+
- `{BUSINESS_ROOT}/_template/overview.md` — overview template. If missing, generate a minimal overview from the fields below.
|
|
40
40
|
- [.arcane/templates/venture-template/IDEAS.md](../../.arcane/templates/venture-template/IDEAS.md) and [.arcane/templates/venture-template/TODO.md](../../.arcane/templates/venture-template/TODO.md) — book stubs. If missing, use the header format from [[spell-save-idea|spell-save-idea]] / [[spell-todo|spell-todo]].
|
|
41
41
|
- [README.md](../../README.md) — repo navigation. If missing, skip cross-linking.
|
|
42
42
|
- [project.md](../../project.md) — project context. If missing, skip.
|
|
@@ -54,7 +54,7 @@ Use these files first (each is optional — if missing, proceed as noted):
|
|
|
54
54
|
|
|
55
55
|
## Step 3 — Register the Venture
|
|
56
56
|
|
|
57
|
-
Open (or create, with the standard header — see `
|
|
57
|
+
Open (or create, with the standard header — see `{BUSINESS_ROOT}/registry.json`'s own `_comment` field for the expected shape) `{BUSINESS_ROOT}/registry.json` and append an entry keyed by the slug:
|
|
58
58
|
|
|
59
59
|
```json
|
|
60
60
|
"<slug>": {
|
|
@@ -83,7 +83,7 @@ Output format:
|
|
|
83
83
|
|
|
84
84
|
## Created
|
|
85
85
|
|
|
86
|
-
- File and folder paths created, including the `
|
|
86
|
+
- File and folder paths created, including the `{BUSINESS_ROOT}/registry.json` entry.
|
|
87
87
|
|
|
88
88
|
## Defaults Applied
|
|
89
89
|
|
|
@@ -15,11 +15,13 @@ agent: agent
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
+
Resolve `{BUSINESS_ROOT}` from `.arcane.json`'s `business_root` field (default `ventures/` if unset) before either mode below.
|
|
19
|
+
|
|
18
20
|
## Sweep Mode (`--sweep`)
|
|
19
21
|
|
|
20
22
|
If the argument is (or starts with) `--sweep`, skip every other step in this spell entirely and run this instead:
|
|
21
23
|
|
|
22
|
-
- **In a hub repo:** report `status: new` counts and oldest-entry age for the hub root `TODO.md` plus every `
|
|
24
|
+
- **In a hub repo:** report `status: new` counts and oldest-entry age for the hub root `TODO.md` plus every `{BUSINESS_ROOT}/<slug>/TODO.md` that exists. Group by book, point at `spell-manifest` for entries ready to promote. Report-only — no edits.
|
|
23
25
|
- **In a consumer repo:** report the same, for the repo-root `TODO.md` only (no venture books exist there).
|
|
24
26
|
|
|
25
27
|
Output format:
|
|
@@ -33,10 +35,10 @@ ventures/ordovica/TODO.md 3 open, oldest 2026-08-10
|
|
|
33
35
|
|
|
34
36
|
## Step 0 — Venture Targeting (Hub Only)
|
|
35
37
|
|
|
36
|
-
If the input names a venture ("add a todo for ordo", `--venture <slug>`), resolve it through `
|
|
38
|
+
If the input names a venture ("add a todo for ordo", `--venture <slug>`), resolve it through `{BUSINESS_ROOT}/registry.json`'s aliases first (exact slug → alias → closest match offered — never guessed). Unknown slug:
|
|
37
39
|
|
|
38
40
|
```
|
|
39
|
-
No venture "<slug>" under
|
|
41
|
+
No venture "<slug>" under {BUSINESS_ROOT}/ (closest: ordovica, tidewright).
|
|
40
42
|
1) use <closest> 2) add to hub root TODO.md 3) cancel (create the venture first: spell-summon-venture)
|
|
41
43
|
```
|
|
42
44
|
|
|
@@ -81,7 +83,7 @@ Classify the idea along two axes:
|
|
|
81
83
|
- `infrastructure` — hardware, OS, cloud, networking, provisioning
|
|
82
84
|
- `governance` — git conventions, agent autonomy, spending controls, work management
|
|
83
85
|
- `ci-cd` — repos, pipelines, build/release automation
|
|
84
|
-
- `business` — a specific business under `
|
|
86
|
+
- `business` — a specific business under `{BUSINESS_ROOT}/` — name which one
|
|
85
87
|
- `product-ideas` — new product concepts or packaging ideas
|
|
86
88
|
- `legal` — LLC, ToS, contracts
|
|
87
89
|
- `playbooks` — runbooks, setup guides, automation scripts
|
|
@@ -116,7 +118,7 @@ Determine the target file(s) for each item:
|
|
|
116
118
|
| Speculative idea or future-facing concept | `TODO.md` under `## Product Ideas` or the best matching section |
|
|
117
119
|
| ADR candidate | `DECISIONS.md` — flag it as pending, do not write the full ADR yet |
|
|
118
120
|
| Journal-worthy context | `journal/` — propose a filename with today's date slug, do not create the file yet |
|
|
119
|
-
| Business-specific execution item | Both `TODO.md` AND the relevant `
|
|
121
|
+
| Business-specific execution item | Both `TODO.md` AND the relevant `{BUSINESS_ROOT}/<name>/overview.md` reference section |
|
|
120
122
|
|
|
121
123
|
## Step 4 — Show Proposal
|
|
122
124
|
|
package/dist/index.js
CHANGED
|
@@ -87,10 +87,28 @@ var ManifestCorruptError = class extends Error {
|
|
|
87
87
|
this.name = "ManifestCorruptError";
|
|
88
88
|
}
|
|
89
89
|
};
|
|
90
|
+
var ManifestInvalidFieldError = class extends Error {
|
|
91
|
+
constructor(manifestPath2, field, value) {
|
|
92
|
+
super(
|
|
93
|
+
`Manifest at "${manifestPath2}" has an unsupported value for "${field}": ${JSON.stringify(value)}.`
|
|
94
|
+
);
|
|
95
|
+
this.name = "ManifestInvalidFieldError";
|
|
96
|
+
}
|
|
97
|
+
};
|
|
90
98
|
var MANIFEST_FILE = ".arcane.json";
|
|
99
|
+
var VALID_TRACKING_MODES = ["internal", "external"];
|
|
100
|
+
var VALID_EXTERNAL_PROVIDERS = ["ado", "jira", "other"];
|
|
91
101
|
function manifestPath(targetDir) {
|
|
92
102
|
return path2.join(targetDir, MANIFEST_FILE);
|
|
93
103
|
}
|
|
104
|
+
function validateTrackingFields(manifest, filePath) {
|
|
105
|
+
if (manifest.tracking_mode !== void 0 && !VALID_TRACKING_MODES.includes(manifest.tracking_mode)) {
|
|
106
|
+
throw new ManifestInvalidFieldError(filePath, "tracking_mode", manifest.tracking_mode);
|
|
107
|
+
}
|
|
108
|
+
if (manifest.external_provider !== void 0 && manifest.external_provider !== null && !VALID_EXTERNAL_PROVIDERS.includes(manifest.external_provider)) {
|
|
109
|
+
throw new ManifestInvalidFieldError(filePath, "external_provider", manifest.external_provider);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
94
112
|
async function readManifest(targetDir) {
|
|
95
113
|
const filePath = manifestPath(targetDir);
|
|
96
114
|
let content;
|
|
@@ -102,11 +120,14 @@ async function readManifest(targetDir) {
|
|
|
102
120
|
}
|
|
103
121
|
throw err;
|
|
104
122
|
}
|
|
123
|
+
let manifest;
|
|
105
124
|
try {
|
|
106
|
-
|
|
125
|
+
manifest = JSON.parse(content);
|
|
107
126
|
} catch {
|
|
108
127
|
throw new ManifestCorruptError(filePath);
|
|
109
128
|
}
|
|
129
|
+
validateTrackingFields(manifest, filePath);
|
|
130
|
+
return manifest;
|
|
110
131
|
}
|
|
111
132
|
async function writeManifest(targetDir, manifest) {
|
|
112
133
|
const filePath = manifestPath(targetDir);
|
|
@@ -2018,12 +2039,40 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2018
2039
|
});
|
|
2019
2040
|
role = isHub ? "hub" : "consumer";
|
|
2020
2041
|
}
|
|
2042
|
+
let tracking_mode;
|
|
2043
|
+
let external_provider;
|
|
2044
|
+
if (profile === "governance-only" || profile === "methodology") {
|
|
2045
|
+
tracking_mode = "internal";
|
|
2046
|
+
external_provider = null;
|
|
2047
|
+
} else if (!options.profile) {
|
|
2048
|
+
console.log();
|
|
2049
|
+
tracking_mode = await select2({
|
|
2050
|
+
message: "How will work be tracked in this repo?",
|
|
2051
|
+
choices: [
|
|
2052
|
+
{ value: "internal", name: "Track work in this repo (TODO.md / PRDs)" },
|
|
2053
|
+
{ value: "external", name: "Track work in an external tracker (Azure DevOps / Jira / other)" }
|
|
2054
|
+
]
|
|
2055
|
+
});
|
|
2056
|
+
if (tracking_mode === "external") {
|
|
2057
|
+
external_provider = await select2({
|
|
2058
|
+
message: "Which external tracker?",
|
|
2059
|
+
choices: [
|
|
2060
|
+
{ value: "ado", name: "Azure DevOps" },
|
|
2061
|
+
{ value: "jira", name: "Jira" },
|
|
2062
|
+
{ value: "other", name: "Other" }
|
|
2063
|
+
]
|
|
2064
|
+
});
|
|
2065
|
+
} else {
|
|
2066
|
+
external_provider = null;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2021
2069
|
const manifest = {
|
|
2022
2070
|
version: packageVersion,
|
|
2023
2071
|
profile,
|
|
2024
2072
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2025
2073
|
components: installedComponents,
|
|
2026
|
-
...role ? { role } : {}
|
|
2074
|
+
...role ? { role } : {},
|
|
2075
|
+
...tracking_mode ? { tracking_mode, external_provider } : {}
|
|
2027
2076
|
};
|
|
2028
2077
|
await writeManifest(targetDir, manifest);
|
|
2029
2078
|
printSuccess(`Initialized with profile "${profile}" \u2014 ${fileCount} files installed`);
|
|
@@ -2150,7 +2199,7 @@ async function runAdd(name, options, targetDir, assetsDir, packageVersion) {
|
|
|
2150
2199
|
import { join as join7 } from "path";
|
|
2151
2200
|
|
|
2152
2201
|
// src/modules/hub.ts
|
|
2153
|
-
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
2202
|
+
import { confirm as confirm2, select as select3 } from "@inquirer/prompts";
|
|
2154
2203
|
import { readdir as readdir3, writeFile as writeFile5 } from "fs/promises";
|
|
2155
2204
|
import { join as join6 } from "path";
|
|
2156
2205
|
var MANIFEST_RETROFITS = [
|
|
@@ -2164,6 +2213,36 @@ var MANIFEST_RETROFITS = [
|
|
|
2164
2213
|
});
|
|
2165
2214
|
return { role: isHub ? "hub" : "consumer" };
|
|
2166
2215
|
}
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
field: "tracking_mode",
|
|
2219
|
+
needsRetrofit: (m) => m.tracking_mode === void 0,
|
|
2220
|
+
// Mirrors init.ts's Step 5b branching exactly (EF-14 D5): docs-only
|
|
2221
|
+
// profiles get a silent default, full/lite get asked.
|
|
2222
|
+
ask: async (manifest) => {
|
|
2223
|
+
if (manifest.profile === "governance-only" || manifest.profile === "methodology") {
|
|
2224
|
+
return { tracking_mode: "internal", external_provider: null };
|
|
2225
|
+
}
|
|
2226
|
+
const tracking_mode = await select3({
|
|
2227
|
+
message: "How will work be tracked in this repo?",
|
|
2228
|
+
choices: [
|
|
2229
|
+
{ value: "internal", name: "Track work in this repo (TODO.md / PRDs)" },
|
|
2230
|
+
{ value: "external", name: "Track work in an external tracker (Azure DevOps / Jira / other)" }
|
|
2231
|
+
]
|
|
2232
|
+
});
|
|
2233
|
+
if (tracking_mode === "external") {
|
|
2234
|
+
const external_provider = await select3({
|
|
2235
|
+
message: "Which external tracker?",
|
|
2236
|
+
choices: [
|
|
2237
|
+
{ value: "ado", name: "Azure DevOps" },
|
|
2238
|
+
{ value: "jira", name: "Jira" },
|
|
2239
|
+
{ value: "other", name: "Other" }
|
|
2240
|
+
]
|
|
2241
|
+
});
|
|
2242
|
+
return { tracking_mode, external_provider };
|
|
2243
|
+
}
|
|
2244
|
+
return { tracking_mode, external_provider: null };
|
|
2245
|
+
}
|
|
2167
2246
|
}
|
|
2168
2247
|
];
|
|
2169
2248
|
async function runManifestRetrofits(manifest) {
|
|
@@ -2175,7 +2254,7 @@ async function runManifestRetrofits(manifest) {
|
|
|
2175
2254
|
);
|
|
2176
2255
|
let patch = {};
|
|
2177
2256
|
for (const retrofit of applicable) {
|
|
2178
|
-
const answer = await retrofit.ask();
|
|
2257
|
+
const answer = await retrofit.ask({ ...manifest, ...patch });
|
|
2179
2258
|
patch = { ...patch, ...answer };
|
|
2180
2259
|
}
|
|
2181
2260
|
return patch;
|