arcane-cli 0.25.0 → 0.26.1
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/git-conventions.md +11 -0
- package/dist/assets/.claude/commands/spell-scry.md +13 -0
- package/dist/assets/.github/instructions/agent-output.instructions.md +7 -0
- package/dist/assets/.github/prompts/spell-commit-work.prompt.md +2 -0
- package/dist/assets/.github/prompts/spell-scry.prompt.md +123 -0
- package/dist/assets/.mcp.json +10 -0
- package/dist/index.js +55 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -478,6 +478,17 @@ git branch -d <branch> # delete local branch (skip if branch
|
|
|
478
478
|
git push origin --delete <branch> # delete remote branch (safety net — --delete-source-branch true usually handles this; ignore 'does not exist' errors)
|
|
479
479
|
```
|
|
480
480
|
|
|
481
|
+
**MCP fail-fast / fallback rule:** one abnormal MCP tool failure — a hang, an idle-timeout abort, a
|
|
482
|
+
transport error, or an empty response where data is clearly expected — marks that MCP server **down
|
|
483
|
+
for the rest of the session**. Do not retry it blindly. All subsequent operations that would have used
|
|
484
|
+
it fall back to the documented CLI (`az repos pr` / `gh` / a direct REST call), and the downgrade is
|
|
485
|
+
reported in output so a human sees it happened. The known-issues rows below (`MCP vote_pull_request`,
|
|
486
|
+
`MCP create_pull_request`) are concrete instances of this general rule, not special cases of their own.
|
|
487
|
+
**Consumer hardening:** set a per-server `"timeout"` (milliseconds) in `.mcp.json` so a hang aborts on a
|
|
488
|
+
predictable schedule instead of running to the client's own default idle limit (as long as 30 minutes).
|
|
489
|
+
Origin: a real ops session lost roughly an hour to two consecutive 30-minute MCP hangs before the
|
|
490
|
+
documented `az` fallback was used (2026-08-14).
|
|
491
|
+
|
|
481
492
|
**Known issues:**
|
|
482
493
|
|
|
483
494
|
| Issue | Symptom | Fix |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Use PROACTIVELY when naming anything new (a spell, a field, a product, an internal concept) before committing to it — clears a candidate name against both the outside world and this repository itself.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Scry
|
|
6
|
+
|
|
7
|
+
Invoke the Arcane `spell-scry` spell workflow.
|
|
8
|
+
|
|
9
|
+
See the full prompt at `.github/prompts/spell-scry.prompt.md` for the complete workflow definition.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
@.github/prompts/spell-scry.prompt.md
|
|
@@ -81,6 +81,13 @@ When completing a PR via `az repos pr update`, do **not** pass `--squash true`.
|
|
|
81
81
|
az repos pr update --id <PR_ID> --org https://dev.azure.com/<org> --status completed --delete-source-branch true
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
+
## MCP Fail-Fast / Fallback
|
|
85
|
+
|
|
86
|
+
One abnormal MCP tool failure (hang, idle-timeout abort, transport error, empty response where data is
|
|
87
|
+
expected) marks that server down for the rest of the session — fall back to the documented CLI (`az
|
|
88
|
+
repos pr` / `gh` / REST), no blind retries, and report the downgrade in output. Full rule and origin
|
|
89
|
+
incident: `.arcane/governance/git-conventions.md` → Known issues.
|
|
90
|
+
|
|
84
91
|
## Branch Naming (Interactive Sessions)
|
|
85
92
|
|
|
86
93
|
Interactive tool session branches use:
|
|
@@ -152,6 +152,8 @@ Workflow:
|
|
|
152
152
|
|
|
153
153
|
a. Run `git push origin <branch>`.
|
|
154
154
|
|
|
155
|
+
a1. **MCP fail-fast / fallback.** If an MCP tool used anywhere in this step (e.g. `create_pull_request`) fails abnormally once — a hang, an idle-timeout abort, a transport error, or an empty response where data is clearly expected — treat that server as down for the rest of this session. Do not retry it blindly; fall back to the raw CLI paths below (`gh pr create` / `az repos pr create`) and report the downgrade. Full rule: `.arcane/governance/git-conventions.md` → Known issues.
|
|
156
|
+
|
|
155
157
|
b. **🛑 Mandatory pre-PR rebase (governance guard, applies to every path below).** Before invoking any PR-creation command — whether via `spell-create-pull-request`, raw `gh pr create`, raw `az repos pr create`, or an MCP `create_pull_request` tool — you MUST:
|
|
156
158
|
|
|
157
159
|
```bash
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Spell — Scry
|
|
3
|
+
description: Clear a candidate name before it ships — four outward checks (who coined it, estate still trading, same-audience giants, first-association salience) plus a mandatory inward repo-local collision pass. Use when naming anything new (a spell, a field, a product, an internal concept) before committing to it.
|
|
4
|
+
argument-hint: '<candidate name or term>'
|
|
5
|
+
agent: agent
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Executive Summary
|
|
9
|
+
|
|
10
|
+
- This spell clears a candidate name before it ships, checking both the outside world and this
|
|
11
|
+
repository itself.
|
|
12
|
+
- The inward pass runs first and is mandatory, not optional — a real naming decision (ARC-028's
|
|
13
|
+
`workspace`) was nearly settled on a clean external read alone, until a single `grep -ri` found the
|
|
14
|
+
word already load-bearing inside the repository in two other senses.
|
|
15
|
+
- Both passes classify hits with the same taxonomy (same-space / adjacent / out-of-space) and combine
|
|
16
|
+
into one verdict: pass / pass-with-disclosure / kill.
|
|
17
|
+
- `spell ward` is the opposite direction of this same concern — it finds what leaked **in**; this spell
|
|
18
|
+
clears what's about to go **out**. They do not call each other; ward's denylist scanning and scry's
|
|
19
|
+
single-candidate classification are different operations on the same underlying grep primitive.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
Clear the candidate name given in the prompt argument.
|
|
24
|
+
|
|
25
|
+
## Step 1 — Resolve the candidate
|
|
26
|
+
|
|
27
|
+
Take the term verbatim from the argument. If none was given, ask for it — do not guess a name to check.
|
|
28
|
+
|
|
29
|
+
## Step 2 — Inward pass (mandatory, run first)
|
|
30
|
+
|
|
31
|
+
Grep this repository for the candidate term before doing any outward research:
|
|
32
|
+
|
|
33
|
+
- **Prose**: markdown, comments, documentation.
|
|
34
|
+
- **Identifiers**: variable, function, type, and class names — weight these **above** prose, since
|
|
35
|
+
consumers already depend on code identifiers in a way they never depend on a sentence mentioning a
|
|
36
|
+
word.
|
|
37
|
+
- **Config keys and schema fields**: `.arcane.json` fields, YAML/JSON config keys, any validated schema.
|
|
38
|
+
|
|
39
|
+
Use word-boundary matching (the same principle `spell ward`'s denylist scan uses, expressed here as a
|
|
40
|
+
`grep -w` / IDE-search equivalent over the codebase) — a bare substring match on a short candidate term
|
|
41
|
+
will drown in false positives from unrelated words that merely contain it.
|
|
42
|
+
|
|
43
|
+
**Classify each internal hit with the same taxonomy the outward checks use below:**
|
|
44
|
+
|
|
45
|
+
- **Same-space**: the term is already load-bearing for a closely related concept (a config field, a
|
|
46
|
+
directory name, a class doing adjacent work) — a genuine internal collision.
|
|
47
|
+
- **Adjacent**: the term appears near the concept but for something distinguishable enough that a
|
|
48
|
+
reader would not confuse the two once told them apart.
|
|
49
|
+
- **Out-of-space**: incidental — a comment, an unrelated variable, coincidental substring overlap.
|
|
50
|
+
|
|
51
|
+
**Why this runs first:** a same-space internal collision is often cheaper to find than the outward
|
|
52
|
+
research below, and finding one early can save the effort of a full outward pass on a name that was
|
|
53
|
+
already going to be killed. This is not a formality — the ARC-028 `workspace` incident is the concrete
|
|
54
|
+
case: a clean external read had nearly settled the decision before a single `grep -ri` found the word
|
|
55
|
+
already meaning something else in two other places inside this exact repository.
|
|
56
|
+
|
|
57
|
+
## Step 3 — Outward pass: the four checks
|
|
58
|
+
|
|
59
|
+
Research the candidate term against the outside world:
|
|
60
|
+
|
|
61
|
+
1. **Who coined it** — search for the term's origin and earliest known usage in the relevant space
|
|
62
|
+
(software, apps, AI, games — whichever applies to this candidate).
|
|
63
|
+
2. **Is an estate/trademark holder still trading** — distinguish an actively-used, enforced brand from
|
|
64
|
+
an abandoned or long-defunct one. A defunct mark carries much less collision risk than a live one.
|
|
65
|
+
3. **Same-audience giants** — do major, well-known players who share this candidate's actual target
|
|
66
|
+
audience already use this name saliently? A collision with a giant outside the target audience
|
|
67
|
+
matters far less than one inside it.
|
|
68
|
+
4. **First-association salience per market** — for someone in the target market, what does this term
|
|
69
|
+
bring to mind *first*? If the first association is something else entirely, that is a real cost even
|
|
70
|
+
without a formal trademark conflict.
|
|
71
|
+
|
|
72
|
+
Classify each outward finding with the **same** same-space / adjacent / out-of-space taxonomy as the
|
|
73
|
+
inward pass — never a separate scheme. Cite sources for every outward claim; do not assert a name's
|
|
74
|
+
history or salience from unverified general knowledge alone.
|
|
75
|
+
|
|
76
|
+
## Step 4 — Combine into one verdict
|
|
77
|
+
|
|
78
|
+
A name that clears outward but collides inward is not a clean pass, and the reverse is equally true —
|
|
79
|
+
combine both passes into a single verdict, not two separate reports:
|
|
80
|
+
|
|
81
|
+
- **pass** — no same-space finding, inward or outward; adjacent/out-of-space findings, if any, are
|
|
82
|
+
noted but do not block.
|
|
83
|
+
- **pass-with-disclosure** — a same-space finding exists but is distant, low-traffic, or otherwise minor
|
|
84
|
+
enough that proceeding is reasonable with the collision stated explicitly, not discovered later.
|
|
85
|
+
- **kill** — a genuine, blocking same-space collision, inward or outward. Do not soften this to
|
|
86
|
+
pass-with-disclosure merely because the outward research took effort to produce.
|
|
87
|
+
|
|
88
|
+
## Step 5 — Report
|
|
89
|
+
|
|
90
|
+
```markdown
|
|
91
|
+
## Scry verdict: <candidate> — PASS | PASS-WITH-DISCLOSURE | KILL
|
|
92
|
+
|
|
93
|
+
### Inward pass
|
|
94
|
+
| Hit | Location | Classification |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| <term usage> | <file:line> | same-space / adjacent / out-of-space |
|
|
97
|
+
|
|
98
|
+
### Outward pass
|
|
99
|
+
| Check | Finding | Classification | Source |
|
|
100
|
+
|---|---|---|---|
|
|
101
|
+
| Who coined it | ... | ... | <url or citation> |
|
|
102
|
+
| Estate still trading | ... | ... | <url or citation> |
|
|
103
|
+
| Same-audience giants | ... | ... | <url or citation> |
|
|
104
|
+
| First-association salience | ... | ... | <url or citation> |
|
|
105
|
+
|
|
106
|
+
### Verdict rationale
|
|
107
|
+
<why this combination of inward + outward findings produced this verdict>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Related Spells
|
|
111
|
+
|
|
112
|
+
- `spell ward` — the opposite direction: finds third-party identifiers that already leaked into the
|
|
113
|
+
repo, rather than clearing a candidate before it ships out.
|
|
114
|
+
|
|
115
|
+
## Rules
|
|
116
|
+
|
|
117
|
+
- **Never skip the inward pass.** It is not optional, and it runs before the outward pass, not after.
|
|
118
|
+
- **Never assert an outward finding without a source.** "Based on general knowledge" is not a citation.
|
|
119
|
+
- **Never use a separate classification scheme for inward vs. outward findings.** Same-space / adjacent
|
|
120
|
+
/ out-of-space applies to both, so a reader can compare them directly.
|
|
121
|
+
- **Never round pass-with-disclosure up to pass, or down to kill, to avoid stating a nuanced verdict.**
|
|
122
|
+
- This spell classifies and reports. It does not rename, alias, or otherwise resolve a collision it
|
|
123
|
+
finds — that decision belongs to whoever asked for the name cleared.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Example MCP server config. Replace or remove the example server below with your own. Set a per-server \"timeout\" (milliseconds) so a hung server aborts on a predictable schedule instead of running to the client's own default idle limit (as long as 30 minutes) -- see .arcane/governance/git-conventions.md's MCP fail-fast / fallback rule.",
|
|
3
|
+
"mcpServers": {
|
|
4
|
+
"example-server": {
|
|
5
|
+
"command": "npx",
|
|
6
|
+
"args": ["-y", "@example/mcp-server"],
|
|
7
|
+
"timeout": 30000
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -428,6 +428,15 @@ var COMPONENTS = [
|
|
|
428
428
|
skipExisting: true,
|
|
429
429
|
initOnly: true
|
|
430
430
|
},
|
|
431
|
+
// Optional MCP config scaffold (BC-22/I12). skipExisting: a repo's own
|
|
432
|
+
// .mcp.json is user-owned server config Arcane must never overwrite.
|
|
433
|
+
// Not initOnly -- useful to retrofit via `spell add` on an existing repo too.
|
|
434
|
+
{
|
|
435
|
+
name: "mcp-config-template",
|
|
436
|
+
description: "Example .mcp.json with a per-server timeout, companion to the MCP fail-fast rule",
|
|
437
|
+
files: [".mcp.json"],
|
|
438
|
+
skipExisting: true
|
|
439
|
+
},
|
|
431
440
|
{
|
|
432
441
|
name: "records-conventions",
|
|
433
442
|
description: "Supersession, tombstone, and retention conventions for records repositories",
|
|
@@ -551,7 +560,9 @@ var COMPONENTS = [
|
|
|
551
560
|
".github/prompts/spell-generate-bot-icons.prompt.md",
|
|
552
561
|
".claude/commands/spell-generate-bot-icons.md",
|
|
553
562
|
".github/prompts/spell-make-discoverable.prompt.md",
|
|
554
|
-
".claude/commands/spell-make-discoverable.md"
|
|
563
|
+
".claude/commands/spell-make-discoverable.md",
|
|
564
|
+
".github/prompts/spell-scry.prompt.md",
|
|
565
|
+
".claude/commands/spell-scry.md"
|
|
555
566
|
]
|
|
556
567
|
},
|
|
557
568
|
{
|
|
@@ -4065,6 +4076,47 @@ async function checkPushPolicy(targetDir) {
|
|
|
4065
4076
|
message: `declared "blocked" but not enforced \u2014 ${missing.join("; ")}. The manifest claims a protection this repository does not have.`
|
|
4066
4077
|
};
|
|
4067
4078
|
}
|
|
4079
|
+
async function checkMcpConfig(targetDir) {
|
|
4080
|
+
const name = "MCP config (per-server timeout, I12)";
|
|
4081
|
+
const filePath = join11(targetDir, ".mcp.json");
|
|
4082
|
+
let raw;
|
|
4083
|
+
try {
|
|
4084
|
+
raw = await readFile6(filePath, "utf8");
|
|
4085
|
+
} catch {
|
|
4086
|
+
return { name, passed: true, blocking: false, message: "no .mcp.json \u2014 nothing to check" };
|
|
4087
|
+
}
|
|
4088
|
+
let parsed;
|
|
4089
|
+
try {
|
|
4090
|
+
parsed = JSON.parse(raw);
|
|
4091
|
+
} catch {
|
|
4092
|
+
return {
|
|
4093
|
+
name,
|
|
4094
|
+
passed: false,
|
|
4095
|
+
blocking: false,
|
|
4096
|
+
message: ".mcp.json exists but contains invalid JSON"
|
|
4097
|
+
};
|
|
4098
|
+
}
|
|
4099
|
+
const servers = parsed.mcpServers ?? {};
|
|
4100
|
+
const serverNames = Object.keys(servers);
|
|
4101
|
+
if (serverNames.length === 0) {
|
|
4102
|
+
return { name, passed: true, blocking: false, message: "no MCP servers configured" };
|
|
4103
|
+
}
|
|
4104
|
+
const missingTimeout = serverNames.filter((n) => typeof servers[n]?.timeout !== "number");
|
|
4105
|
+
if (missingTimeout.length > 0) {
|
|
4106
|
+
return {
|
|
4107
|
+
name,
|
|
4108
|
+
passed: false,
|
|
4109
|
+
blocking: false,
|
|
4110
|
+
message: `server(s) missing a "timeout": ${missingTimeout.join(", ")} \u2014 a hang runs to the client's own default idle limit (up to 30 min) instead of a predictable one`
|
|
4111
|
+
};
|
|
4112
|
+
}
|
|
4113
|
+
return {
|
|
4114
|
+
name,
|
|
4115
|
+
passed: true,
|
|
4116
|
+
blocking: false,
|
|
4117
|
+
message: `${serverNames.length} server(s) configured, all with a timeout set`
|
|
4118
|
+
};
|
|
4119
|
+
}
|
|
4068
4120
|
async function checkDelegations(targetDir) {
|
|
4069
4121
|
const name = "Standing delegations (T13)";
|
|
4070
4122
|
const filePath = join11(targetDir, ".arcane", "delegations.json");
|
|
@@ -4150,7 +4202,8 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
4150
4202
|
checkPullRebase(targetDir),
|
|
4151
4203
|
checkPushPolicy(targetDir),
|
|
4152
4204
|
checkPlatformBranchPolicy(targetDir),
|
|
4153
|
-
checkDelegations(targetDir)
|
|
4205
|
+
checkDelegations(targetDir),
|
|
4206
|
+
checkMcpConfig(targetDir)
|
|
4154
4207
|
]);
|
|
4155
4208
|
const sessionResults = await checkSessionContinuity(targetDir);
|
|
4156
4209
|
results.push(...sessionResults);
|