arcane-cli 0.16.2 → 0.16.4
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.
|
@@ -33,16 +33,28 @@ Workflow:
|
|
|
33
33
|
- Lessons learned (anything that cost time, surprised the user, or should prevent future mistakes).
|
|
34
34
|
- **Capture tie-in tip:** If this session produced durable analysis worth formalizing (a design, an investigation, a reusable explanation), suggest running `spell-document` before closing so it lands in the right doc. For quick ideas worth keeping but not yet actionable, suggest `spell-save-idea`.
|
|
35
35
|
|
|
36
|
+
1b. **Verify in-flight async work before writing any output artifact (EF-21). Enforcement: structured spell gate (ARC-023) — this workflow requires the observable state of every dispatched item before proceeding to step 2.** "I started it" is not "it worked." Before the journal, TODO.md, or the handoff are written, enumerate every piece of asynchronous work dispatched during this session that could still be running or could have failed after being kicked off — CI/CD runs, deployments, package publishes, long-running background tasks, PR checks. For each, **actively check** its current status through whatever means is available (CI provider API/CLI, deployment dashboard, `gh run view`, `az pipelines runs show`, etc.) — attempt the check before reaching for `unverifiable`; that state exists for genuine access limits (no CLI/API access, no credentials, provider outage), not for skipping a check that was merely inconvenient to run. Then classify into exactly one state:
|
|
37
|
+
|
|
38
|
+
- **succeeded** — confirmed complete and correct. Only this state may be described as "done," "completed," or "shipped" anywhere in the journal, TODO.md, or the handoff.
|
|
39
|
+
- **failed** — confirmed to have failed. Record as a blocker or carry-forward item, never as done.
|
|
40
|
+
- **pending** — still running at last check. Record as in-flight; do not describe as complete.
|
|
41
|
+
- **dispatched** — kicked off, but no result has been observed at all (the letter was handed to the post office — you know it was sent, not whether it arrived).
|
|
42
|
+
- **unverifiable** — only after genuinely attempting the check above and finding this environment cannot query the target system. State the exact command, URL, or action the operator must run to check it themselves — a bare "unverifiable" with no next step is barely better than silence, and reaching for this state without attempting the check first defeats the purpose of this step as surely as skipping it entirely.
|
|
43
|
+
|
|
44
|
+
A commit succeeding, code being pushed, or a build being triggered are all **dispatched**, not **succeeded** — do not write any of them as complete until independently confirmed. Carry every non-succeeded item into the handoff's `Pending Verification` field (step 5b), and into `Last completed step` worded as not-yet-complete (also step 5b).
|
|
45
|
+
|
|
46
|
+
Step 10's existing PR-merge check ("Verify through the detected provider that the PR is merged before changing branches") is this same requirement applied to one specific, already-shipped case — the close-session's own PR. Running step 10 does not substitute for step 1b's sweep of everything else dispatched this session, and step 1b does not substitute for step 10's specific merge check; both apply.
|
|
47
|
+
|
|
36
48
|
2. **Update today's journal file** at `journal/YYYY-MM-DD-topic-slug.md`.
|
|
37
49
|
- If it exists and already has a session section from this chat, **update that section in place** (do not create a duplicate).
|
|
38
50
|
- If it exists but has no section for this session, **append** a new `## Session: <title>` section.
|
|
39
51
|
- If it does not exist, **create** it using the existing journal frontmatter style.
|
|
40
52
|
- Journal entries must include:
|
|
41
53
|
- `### Prompt Context` — original prompt text or a faithful paraphrase of the multi-turn request when the session began from a substantive user ask. Include follow-up scope inputs if they materially changed the direction of the work.
|
|
42
|
-
- `### What Got Done` — numbered list of concrete outcomes with file/ADR references.
|
|
54
|
+
- `### What Got Done` — numbered list of concrete outcomes with file/ADR references. Only list an outcome here if step 1b classified it `succeeded` (or it never involved async work at all, e.g. a local file edit). A `pending`/`dispatched`/`unverifiable` item belongs in Open Items Carried Forward, not here — do not let this section imply completion step 1b couldn't confirm.
|
|
43
55
|
- `### Decisions Made` — table of ADR number, decision, and rationale. If no decisions were made, omit this section.
|
|
44
56
|
- `### Lessons Learned` — one heading per lesson with enough context to prevent the mistake again. Use narrative, not bullet fragments.
|
|
45
|
-
- `### Open Items Carried Forward` — items that remain undone from this session.
|
|
57
|
+
- `### Open Items Carried Forward` — items that remain undone from this session, including every `pending`/`dispatched`/`failed`/`unverifiable` item from step 1b.
|
|
46
58
|
|
|
47
59
|
3. **Update [DECISIONS.md](../../DECISIONS.md).**
|
|
48
60
|
- Add ADRs for any decisions made during the session (naming choices, tool preferences, workflow standards, security policies, etc.).
|
|
@@ -50,7 +62,7 @@ Workflow:
|
|
|
50
62
|
- Follow the existing format: Date, Status, Context, Decision, Reasoning, Rejected alternatives.
|
|
51
63
|
|
|
52
64
|
4. **Update [TODO.md](../../TODO.md).**
|
|
53
|
-
- Mark
|
|
65
|
+
- Mark an item done with date only if step 1b classified its outcome `succeeded` (or it required no async verification at all). A `pending`, `dispatched`, `failed`, or `unverifiable` item stays open, regardless of how confident the session felt about it.
|
|
54
66
|
- Keep unresolved items open.
|
|
55
67
|
- Remove completed checklist items only when completion is documented in the correct source-of-truth file.
|
|
56
68
|
|
|
@@ -69,11 +81,12 @@ In `ai-context/system-prompt-context.md`, replace the existing `## Next Session
|
|
|
69
81
|
> Generated: YYYY-MM-DD
|
|
70
82
|
|
|
71
83
|
- **Active task:** One-line description of the task that was in progress when this session closed.
|
|
72
|
-
- **Last completed step:** The final concrete action taken — include file path or command text.
|
|
84
|
+
- **Last completed step:** The final concrete action taken — include file path or command text. If that action was async work step 1b classified as anything other than `succeeded`, do not describe it as done here — name the action in `dispatched`/in-progress terms (e.g. "Pushed the fix and opened PR #NN" is fine; "Merged PR #NN" is not, unless step 1b confirmed the merge) and let `Pending Verification` carry the actual state.
|
|
73
85
|
- **Next concrete action:** The exact first thing to do at next session start. Be specific: file, command, or decision. Never write "continue work."
|
|
74
86
|
- **Active files:** Files with uncommitted changes or the focus of the last edit (comma-separated).
|
|
75
87
|
- **Branch:** Output of `git branch --show-current`.
|
|
76
88
|
- **Blockers:** Known unresolved blockers or dependencies. Write "None" if clear.
|
|
89
|
+
- **Pending Verification:** One line per non-`succeeded` item from step 1b: `<what> — <state> — <verification action>`. States: `dispatched`, `pending`, `failed`, `unverifiable` (never list a `succeeded` item here — it belongs in `Last completed step` / TODO.md / the journal instead, worded per the gating above). The verification action is required for `dispatched`, `pending`, and `unverifiable` — "N/A" is only valid for `failed`, where the state is already resolved and there is nothing left to check. Write "None" only if step 1b found every dispatched item resolved to `succeeded` or `failed` before this handoff was written.
|
|
77
90
|
- **Notes:** Anything time-sensitive, fragile, or contextual that would be lost if not stated explicitly.
|
|
78
91
|
```
|
|
79
92
|
|
|
@@ -101,7 +114,8 @@ Rules:
|
|
|
101
114
|
|
|
102
115
|
9. **Stage and commit session-close docs.**
|
|
103
116
|
|
|
104
|
-
**Remote-capability check:** classify the state created by `spell-open-session` before branching, staging, push, or PR operations.
|
|
117
|
+
**Remote-capability check:** classify the state created by `spell-open-session` before branching, staging, push, or PR operations. Check for **not a repository at all** first — every other classification assumes a `.git` directory exists.
|
|
118
|
+
- **Not a Git repository (EF-05):** `git rev-parse --is-inside-work-tree` fails. Do not attempt any git operation (branch, add, commit, push). Fail closed with: `No Git repository detected. Run \`git init -b main\` first (explicit -b avoids landing on "master" on systems where that's the init.defaultBranch default), then re-run spell-close-session.` This mirrors `spell init`'s own next-steps guidance for the same state.
|
|
105
119
|
- **Usable supported remote/merge path:** requires an authenticated supported provider (`github.com`, `dev.azure.com`, or `visualstudio.com`). Keep the current compliant session/PR/worktree branch, resolve the actual remote name and integration branch, and use the provider-neutral PR path below.
|
|
106
120
|
- **No usable remote/merge path:** remain on the current trunk. Do not create a dead session branch, push, pull, or open a PR. Apply the EF-28 interactive commit approval gate, commit locally on trunk after approval, report `Local-only close: committed on <trunk>; no remote PR/pull performed`, and skip steps 9a, 9c, and 10's remote operations.
|
|
107
121
|
- **Read-only session:** if no repository mutation occurred, do not create a branch or commit.
|
|
@@ -196,7 +210,7 @@ Route the user to the right next spell based on the end state:
|
|
|
196
210
|
|
|
197
211
|
Rules:
|
|
198
212
|
|
|
199
|
-
- Do not invent completions — only mark items done if verifiable from context.
|
|
213
|
+
- Do not invent completions — only mark items done if verifiable from context. "Verifiable" means classified `succeeded` under step 1b's five-state vocabulary (`dispatched`/`pending`/`succeeded`/`failed`/`unverifiable`), not merely "I can see it was started" (EF-21).
|
|
200
214
|
- Keep frontmatter date values in `YYYY-MM-DD`.
|
|
201
215
|
- Preserve existing checklist and journal style.
|
|
202
216
|
- Keep edits minimal and factual.
|
|
@@ -35,6 +35,7 @@ Use these files first:
|
|
|
35
35
|
Check `ai-context/system-prompt-context.md` for a `## Next Session Handoff` section.
|
|
36
36
|
|
|
37
37
|
- If present and **not yet marked consumed** (no `> ✓ Consumed:` line): surface it immediately as `## Picking Up From Last Session` at the very top of your output, before all other sections. Use the `Next concrete action` field as the first item in `## Next Session Plan`. Use the `Active task`, `Active files`, and `Branch` fields to pre-populate the State Snapshot.
|
|
38
|
+
- **If the handoff's `Pending Verification` field lists anything other than "None" (EF-21):** surface each listed item explicitly, then actively re-check its current status (per the item's stated verification action) rather than treating the state recorded at close-session time as still current — a `dispatched`/`pending` item may have since succeeded, failed, or still be running, and carrying it forward as inert text would repeat the exact failure mode this field exists to prevent. Report what you found (resolved succeeded / resolved failed / still pending / still unverifiable) before relying on anything downstream that depended on it.
|
|
38
39
|
- After surfacing it, apply the Mutation Guard below before appending `> ✓ Consumed: YYYY-MM-DD` (today's date). The marker write is a repository mutation.
|
|
39
40
|
- If the section is absent or already marked consumed: skip this step and proceed with the normal workspace scan.
|
|
40
41
|
|
package/dist/index.js
CHANGED
|
@@ -1751,13 +1751,50 @@ async function inspectGitRepository(cwd) {
|
|
|
1751
1751
|
uncommittedChanges: lines.length === 0 ? 0 : lines.split("\n").length
|
|
1752
1752
|
};
|
|
1753
1753
|
}
|
|
1754
|
-
async function
|
|
1754
|
+
async function correctUnbornMasterDefault(cwd) {
|
|
1755
|
+
let current;
|
|
1755
1756
|
try {
|
|
1756
|
-
const { stdout } = await runGit(cwd, ["
|
|
1757
|
-
|
|
1758
|
-
return lines.length === 0 ? 0 : lines.split("\n").length;
|
|
1757
|
+
const { stdout } = await runGit(cwd, ["symbolic-ref", "--short", "HEAD"]);
|
|
1758
|
+
current = stdout.trim();
|
|
1759
1759
|
} catch {
|
|
1760
|
-
return
|
|
1760
|
+
return { corrected: false, to: "main" };
|
|
1761
|
+
}
|
|
1762
|
+
if (current !== "master") {
|
|
1763
|
+
return { corrected: false, to: "main" };
|
|
1764
|
+
}
|
|
1765
|
+
try {
|
|
1766
|
+
await runGit(cwd, ["rev-parse", "--verify", "HEAD"]);
|
|
1767
|
+
return { corrected: false, to: "main" };
|
|
1768
|
+
} catch {
|
|
1769
|
+
}
|
|
1770
|
+
try {
|
|
1771
|
+
await runGit(cwd, ["rev-parse", "--verify", "refs/heads/main"]);
|
|
1772
|
+
return { corrected: false, to: "main" };
|
|
1773
|
+
} catch {
|
|
1774
|
+
}
|
|
1775
|
+
await runGit(cwd, ["symbolic-ref", "HEAD", "refs/heads/main"]);
|
|
1776
|
+
return { corrected: true, from: current, to: "main" };
|
|
1777
|
+
}
|
|
1778
|
+
async function ensureLocalPullRebase(cwd) {
|
|
1779
|
+
let rawLocalValue;
|
|
1780
|
+
try {
|
|
1781
|
+
const { stdout } = await runGit(cwd, ["config", "--local", "--get", "pull.rebase"]);
|
|
1782
|
+
rawLocalValue = stdout.trim();
|
|
1783
|
+
} catch {
|
|
1784
|
+
rawLocalValue = void 0;
|
|
1785
|
+
}
|
|
1786
|
+
if (rawLocalValue === void 0) {
|
|
1787
|
+
await runGit(cwd, ["config", "--local", "pull.rebase", "true"]);
|
|
1788
|
+
return { action: "set" };
|
|
1789
|
+
}
|
|
1790
|
+
try {
|
|
1791
|
+
const { stdout } = await runGit(cwd, ["config", "--local", "--type=bool", "--get", "pull.rebase"]);
|
|
1792
|
+
if (stdout.trim() === "false") {
|
|
1793
|
+
return { action: "explicit-false-preserved" };
|
|
1794
|
+
}
|
|
1795
|
+
return { action: "already-set" };
|
|
1796
|
+
} catch {
|
|
1797
|
+
return { action: "already-set" };
|
|
1761
1798
|
}
|
|
1762
1799
|
}
|
|
1763
1800
|
|
|
@@ -1837,21 +1874,42 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
1837
1874
|
console.log(chalk2.hex("#a855f7")(`
|
|
1838
1875
|
\u2726 Arcane v${packageVersion}
|
|
1839
1876
|
`));
|
|
1877
|
+
let repoNotFound = false;
|
|
1840
1878
|
if (!options.dryRun) {
|
|
1841
|
-
const
|
|
1842
|
-
if (
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1879
|
+
const gitState = await inspectGitRepository(targetDir);
|
|
1880
|
+
if (gitState.status === "not-repository") {
|
|
1881
|
+
repoNotFound = true;
|
|
1882
|
+
} else {
|
|
1883
|
+
if (gitState.status === "no-commits") {
|
|
1884
|
+
const correction = await correctUnbornMasterDefault(targetDir);
|
|
1885
|
+
if (correction.corrected) {
|
|
1886
|
+
printInfo(
|
|
1887
|
+
`Repointed the unborn branch from "${correction.from}" to "${correction.to}" (avoids the Git for Windows default-branch trap).`
|
|
1888
|
+
);
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (gitState.status === "ready" && gitState.uncommittedChanges > 0) {
|
|
1892
|
+
printWarning(
|
|
1893
|
+
`This repo has ${gitState.uncommittedChanges} uncommitted change${gitState.uncommittedChanges === 1 ? "" : "s"}.`
|
|
1894
|
+
);
|
|
1895
|
+
printInfo("Running spell init will add new files to your working tree.");
|
|
1896
|
+
console.log();
|
|
1897
|
+
const continueAnyway = await confirm({
|
|
1898
|
+
message: "Continue anyway?",
|
|
1899
|
+
default: true
|
|
1900
|
+
});
|
|
1901
|
+
if (!continueAnyway) {
|
|
1902
|
+
console.log("\nCancelled. Commit or stash your changes first.");
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1905
|
+
console.log();
|
|
1906
|
+
}
|
|
1907
|
+
const rebaseResult = await ensureLocalPullRebase(targetDir);
|
|
1908
|
+
if (rebaseResult.action === "explicit-false-preserved") {
|
|
1909
|
+
printWarning(
|
|
1910
|
+
"This repo's pull.rebase is explicitly set to false, but Arcane's governance mandates rebase-and-fast-forward (see .arcane/governance/git-conventions.md). Left as-is since you set it explicitly."
|
|
1911
|
+
);
|
|
1853
1912
|
}
|
|
1854
|
-
console.log();
|
|
1855
1913
|
}
|
|
1856
1914
|
}
|
|
1857
1915
|
let profile = options.profile;
|
|
@@ -2000,6 +2058,12 @@ async function runInit(options, targetDir, assetsDir, packageVersion) {
|
|
|
2000
2058
|
"Review .arcane/governance/ docs and customize for your project"
|
|
2001
2059
|
);
|
|
2002
2060
|
}
|
|
2061
|
+
if (repoNotFound) {
|
|
2062
|
+
printNextStep(
|
|
2063
|
+
step++,
|
|
2064
|
+
'Initialize Git for this project: `git init -b main` (avoids defaulting to "master" on systems where that\'s the system default)'
|
|
2065
|
+
);
|
|
2066
|
+
}
|
|
2003
2067
|
printNextStep(
|
|
2004
2068
|
step++,
|
|
2005
2069
|
"Commit the new files: git add .arcane .github .claude AGENTS.md CLAUDE.md .arcane.json"
|
|
@@ -2680,6 +2744,29 @@ async function checkArcaneManifest(targetDir) {
|
|
|
2680
2744
|
};
|
|
2681
2745
|
}
|
|
2682
2746
|
}
|
|
2747
|
+
async function checkPullRebase(targetDir) {
|
|
2748
|
+
const name = "Git pull.rebase (rebase-and-fast-forward mandate)";
|
|
2749
|
+
try {
|
|
2750
|
+
const { stdout } = await runGit(targetDir, ["config", "--type=bool", "--get", "pull.rebase"]);
|
|
2751
|
+
const value = stdout.trim();
|
|
2752
|
+
if (value === "true") {
|
|
2753
|
+
return { name, passed: true, message: "pull.rebase=true" };
|
|
2754
|
+
}
|
|
2755
|
+
return {
|
|
2756
|
+
name,
|
|
2757
|
+
passed: false,
|
|
2758
|
+
message: `pull.rebase resolves to "${value}", not "true" -- a bare \`git pull\` will create merge commits, contradicting git-conventions.md's rebase-and-fast-forward mandate. Run: git config --local pull.rebase true`,
|
|
2759
|
+
blocking: false
|
|
2760
|
+
};
|
|
2761
|
+
} catch {
|
|
2762
|
+
return {
|
|
2763
|
+
name,
|
|
2764
|
+
passed: false,
|
|
2765
|
+
message: "pull.rebase is unset, not a recognized boolean, or this isn't a Git repository -- effective behavior may not be rebase-and-fast-forward, and Git for Windows defaults this to false when unset. Run: git config --local pull.rebase true",
|
|
2766
|
+
blocking: false
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2769
|
+
}
|
|
2683
2770
|
function checkIncidentReleaseGate() {
|
|
2684
2771
|
const result = evaluateIncidentGate(INCIDENT_QUEUE);
|
|
2685
2772
|
if (result.blocked) {
|
|
@@ -2742,7 +2829,8 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
2742
2829
|
checkNodeVersion(),
|
|
2743
2830
|
checkVSCodeExtension("GitHub.copilot-chat", "GitHub Copilot (Chat)"),
|
|
2744
2831
|
checkArcaneManifest(targetDir),
|
|
2745
|
-
Promise.resolve(checkIncidentReleaseGate())
|
|
2832
|
+
Promise.resolve(checkIncidentReleaseGate()),
|
|
2833
|
+
checkPullRebase(targetDir)
|
|
2746
2834
|
]);
|
|
2747
2835
|
const sessionResults = await checkSessionContinuity(targetDir);
|
|
2748
2836
|
results.push(...sessionResults);
|