arcane-cli 1.20.1 → 1.21.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/README.md +10 -0
- package/dist/assets/.arcane/agents/agent-policies-template.md +8 -4
- package/dist/assets/.arcane/governance/agent-policies.md +95 -6
- package/dist/assets/.arcane/governance/git-conventions.md +5 -1
- package/dist/assets/.arcane/governance/naming-conventions.md +1 -1
- package/dist/assets/.arcane/spells/_fragments/run-grant-authority.md +46 -0
- package/dist/assets/.arcane/spells/_fragments/unattended-run-preflight.md +51 -0
- package/dist/assets/.arcane/spells/spell-close-session.md +64 -2
- package/dist/assets/.arcane/spells/spell-commit-work.md +59 -8
- package/dist/assets/.arcane/spells/spell-create-pull-request.md +51 -0
- package/dist/assets/.arcane/spells/spell-full-cycle.md +109 -4
- package/dist/assets/.arcane/spells/spell-implement.md +54 -0
- package/dist/assets/.arcane/spells/spell-parallel-workers.md +58 -2
- package/dist/assets/.github/prompts/spell-full-cycle.prompt.md +1 -1
- package/dist/index.js +312 -208
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const require = __createRequire(import.meta.url);
|
|
|
5
5
|
// src/index.ts
|
|
6
6
|
import { Command, Option } from "commander";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
import { dirname as dirname15, join as
|
|
8
|
+
import { dirname as dirname15, join as join32 } from "path";
|
|
9
9
|
|
|
10
10
|
// src/package-info.ts
|
|
11
11
|
import { createRequire } from "module";
|
|
@@ -1695,14 +1695,14 @@ async function syncUserTierFanout(options) {
|
|
|
1695
1695
|
const ownsClient = (client2) => owned === void 0 || client2 !== void 0 && owned.includes(client2);
|
|
1696
1696
|
const previous = options.previous ?? {};
|
|
1697
1697
|
const dryRun = Boolean(options.dryRun);
|
|
1698
|
-
const
|
|
1698
|
+
const record2 = {};
|
|
1699
1699
|
const outcomes = [];
|
|
1700
1700
|
const desiredPaths = new Set(desired.map((f) => f.relativePath));
|
|
1701
1701
|
for (const id of rendered.missing) {
|
|
1702
1702
|
for (const client2 of SPELL_FANOUT_CLIENTS) {
|
|
1703
1703
|
const relativePath = USER_FANOUT_PATHS[client2](id);
|
|
1704
1704
|
desiredPaths.add(relativePath);
|
|
1705
|
-
if (previous[relativePath] !== void 0)
|
|
1705
|
+
if (previous[relativePath] !== void 0) record2[relativePath] = previous[relativePath];
|
|
1706
1706
|
outcomes.push({ relativePath, status: "unrenderable" });
|
|
1707
1707
|
}
|
|
1708
1708
|
}
|
|
@@ -1717,7 +1717,7 @@ async function syncUserTierFanout(options) {
|
|
|
1717
1717
|
await mkdir2(dirname2(target), { recursive: true });
|
|
1718
1718
|
await writeFile3(target, file.content, "utf-8");
|
|
1719
1719
|
}
|
|
1720
|
-
|
|
1720
|
+
record2[file.relativePath] = newHash;
|
|
1721
1721
|
outcomes.push({ relativePath: file.relativePath, status: "written" });
|
|
1722
1722
|
continue;
|
|
1723
1723
|
}
|
|
@@ -1726,24 +1726,24 @@ async function syncUserTierFanout(options) {
|
|
|
1726
1726
|
continue;
|
|
1727
1727
|
}
|
|
1728
1728
|
if (!await fileMatchesHash(target, recorded)) {
|
|
1729
|
-
|
|
1729
|
+
record2[file.relativePath] = recorded;
|
|
1730
1730
|
outcomes.push({ relativePath: file.relativePath, status: "customized" });
|
|
1731
1731
|
continue;
|
|
1732
1732
|
}
|
|
1733
1733
|
const currentHash = await hashFile(target);
|
|
1734
1734
|
if (currentHash === newHash) {
|
|
1735
|
-
|
|
1735
|
+
record2[file.relativePath] = newHash;
|
|
1736
1736
|
outcomes.push({ relativePath: file.relativePath, status: "unchanged" });
|
|
1737
1737
|
continue;
|
|
1738
1738
|
}
|
|
1739
1739
|
if (!dryRun) await writeFile3(target, file.content, "utf-8");
|
|
1740
|
-
|
|
1740
|
+
record2[file.relativePath] = newHash;
|
|
1741
1741
|
outcomes.push({ relativePath: file.relativePath, status: "written" });
|
|
1742
1742
|
}
|
|
1743
1743
|
for (const [relativePath, recorded] of Object.entries(previous)) {
|
|
1744
1744
|
if (desiredPaths.has(relativePath)) continue;
|
|
1745
1745
|
if (!ownsClient(clientOfFanoutPath(relativePath))) {
|
|
1746
|
-
|
|
1746
|
+
record2[relativePath] = recorded;
|
|
1747
1747
|
continue;
|
|
1748
1748
|
}
|
|
1749
1749
|
validateTargetPath(options.homeDir, relativePath);
|
|
@@ -1754,7 +1754,7 @@ async function syncUserTierFanout(options) {
|
|
|
1754
1754
|
continue;
|
|
1755
1755
|
}
|
|
1756
1756
|
if (state === "other" || !await fileMatchesHash(target, recorded)) {
|
|
1757
|
-
|
|
1757
|
+
record2[relativePath] = recorded;
|
|
1758
1758
|
outcomes.push({ relativePath, status: "kept-edited" });
|
|
1759
1759
|
continue;
|
|
1760
1760
|
}
|
|
@@ -1765,7 +1765,7 @@ async function syncUserTierFanout(options) {
|
|
|
1765
1765
|
}
|
|
1766
1766
|
outcomes.push({ relativePath, status: "pruned" });
|
|
1767
1767
|
}
|
|
1768
|
-
return { record, outcomes };
|
|
1768
|
+
return { record: record2, outcomes };
|
|
1769
1769
|
}
|
|
1770
1770
|
function summarizeFanout(outcomes) {
|
|
1771
1771
|
const counts = {
|
|
@@ -1834,14 +1834,14 @@ ${paths("kept-edited")}`
|
|
|
1834
1834
|
}
|
|
1835
1835
|
return lines;
|
|
1836
1836
|
}
|
|
1837
|
-
async function inspectUserTierFanout(homeDir,
|
|
1837
|
+
async function inspectUserTierFanout(homeDir, record2) {
|
|
1838
1838
|
const health = {
|
|
1839
1839
|
total: 0,
|
|
1840
1840
|
byClient: { codex: 0, claude: 0, "copilot-agents": 0 },
|
|
1841
1841
|
missing: [],
|
|
1842
1842
|
customized: []
|
|
1843
1843
|
};
|
|
1844
|
-
for (const [relativePath, recorded] of Object.entries(
|
|
1844
|
+
for (const [relativePath, recorded] of Object.entries(record2 ?? {})) {
|
|
1845
1845
|
health.total++;
|
|
1846
1846
|
const client2 = clientOfFanoutPath(relativePath);
|
|
1847
1847
|
if (client2 !== void 0) health.byClient[client2]++;
|
|
@@ -4086,8 +4086,8 @@ function hookInForce(outcome) {
|
|
|
4086
4086
|
}
|
|
4087
4087
|
async function applyBlockedPushControls(cwd, options = {}) {
|
|
4088
4088
|
const messages = [];
|
|
4089
|
-
const say = (level,
|
|
4090
|
-
const message = { level, text:
|
|
4089
|
+
const say = (level, text3) => {
|
|
4090
|
+
const message = { level, text: text3 };
|
|
4091
4091
|
messages.push(message);
|
|
4092
4092
|
options.emit?.(message);
|
|
4093
4093
|
};
|
|
@@ -5052,8 +5052,8 @@ function arraysEqual(a, b) {
|
|
|
5052
5052
|
return a.every((line2, idx) => line2 === b[idx]);
|
|
5053
5053
|
}
|
|
5054
5054
|
var MAX_LINES_FOR_LCS = 4e3;
|
|
5055
|
-
function splitLines(
|
|
5056
|
-
return
|
|
5055
|
+
function splitLines(text3) {
|
|
5056
|
+
return text3.split("\n");
|
|
5057
5057
|
}
|
|
5058
5058
|
function merge3(base, ours, theirs) {
|
|
5059
5059
|
const baseLines = splitLines(base);
|
|
@@ -6457,18 +6457,18 @@ function titleAfterId(rest) {
|
|
|
6457
6457
|
return rest.replace(/^\s*[—–:-]+\s*/u, "").trim();
|
|
6458
6458
|
}
|
|
6459
6459
|
function parseDecisions(content) {
|
|
6460
|
-
const
|
|
6460
|
+
const text3 = content.replace(/\r\n?/g, "\n");
|
|
6461
6461
|
const positions = [];
|
|
6462
6462
|
let match2;
|
|
6463
6463
|
HEADING.lastIndex = 0;
|
|
6464
|
-
while ((match2 = HEADING.exec(
|
|
6464
|
+
while ((match2 = HEADING.exec(text3)) !== null) {
|
|
6465
6465
|
positions.push({ id: `${match2[1]}-${match2[2]}`, heading: `${match2[1]}-${match2[2]}${match2[3]}`, index: match2.index });
|
|
6466
6466
|
}
|
|
6467
6467
|
const entries = [];
|
|
6468
6468
|
const warnings = [];
|
|
6469
6469
|
positions.forEach(({ id, heading, index }, i) => {
|
|
6470
|
-
const end = i + 1 < positions.length ? positions[i + 1].index :
|
|
6471
|
-
const section =
|
|
6470
|
+
const end = i + 1 < positions.length ? positions[i + 1].index : text3.length;
|
|
6471
|
+
const section = text3.slice(index, end);
|
|
6472
6472
|
const dateMatch = /^\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})/m.exec(section);
|
|
6473
6473
|
const statusMatch = /^\*\*Status:\*\*\s*(\S+)/m.exec(section);
|
|
6474
6474
|
if (!dateMatch && !statusMatch) {
|
|
@@ -6515,12 +6515,12 @@ async function collectDecisions(root) {
|
|
|
6515
6515
|
var ITEM = /^\s*- \[( |x|X)\] (.*)$/;
|
|
6516
6516
|
var HEADING2 = /^#{1,6}\s+(.+?)\s*#*\s*$/;
|
|
6517
6517
|
var TEXT_LIMIT = 200;
|
|
6518
|
-
function normalizeItemText(
|
|
6519
|
-
return
|
|
6518
|
+
function normalizeItemText(text3) {
|
|
6519
|
+
return text3.replace(/\s+/g, " ").trim();
|
|
6520
6520
|
}
|
|
6521
|
-
function itemLocator(section,
|
|
6521
|
+
function itemLocator(section, text3) {
|
|
6522
6522
|
const slug = section === null ? "" : headingAnchor(section);
|
|
6523
|
-
const hash = createHash3("sha256").update(normalizeItemText(
|
|
6523
|
+
const hash = createHash3("sha256").update(normalizeItemText(text3), "utf8").digest("hex").slice(0, 12);
|
|
6524
6524
|
return `${slug === "" ? "root" : slug}/${hash}`;
|
|
6525
6525
|
}
|
|
6526
6526
|
function parseBook(content) {
|
|
@@ -6537,13 +6537,13 @@ function parseBook(content) {
|
|
|
6537
6537
|
const item = ITEM.exec(line2);
|
|
6538
6538
|
if (!item) continue;
|
|
6539
6539
|
const checked = item[1] !== " ";
|
|
6540
|
-
const
|
|
6540
|
+
const text3 = item[2].trim();
|
|
6541
6541
|
if (checked) done++;
|
|
6542
6542
|
else open++;
|
|
6543
6543
|
items.push({
|
|
6544
|
-
text:
|
|
6544
|
+
text: text3.length > TEXT_LIMIT ? text3.slice(0, TEXT_LIMIT) : text3,
|
|
6545
6545
|
section,
|
|
6546
|
-
locator: itemLocator(section,
|
|
6546
|
+
locator: itemLocator(section, text3),
|
|
6547
6547
|
checked
|
|
6548
6548
|
});
|
|
6549
6549
|
}
|
|
@@ -6608,10 +6608,10 @@ function featureTracking(frontmatter) {
|
|
|
6608
6608
|
};
|
|
6609
6609
|
}
|
|
6610
6610
|
function parsePrd(content, displayPath) {
|
|
6611
|
-
const
|
|
6612
|
-
const heading = H1.exec(
|
|
6611
|
+
const text3 = content.replace(/\r\n?/g, "\n");
|
|
6612
|
+
const heading = H1.exec(text3);
|
|
6613
6613
|
const empty = { title: heading ? heading[1] : null, status: null, implementation_status: null, tracking: null };
|
|
6614
|
-
const match2 = FRONTMATTER.exec(
|
|
6614
|
+
const match2 = FRONTMATTER.exec(text3);
|
|
6615
6615
|
if (!match2) return { fields: empty, warning: `features: ${displayPath} has no frontmatter` };
|
|
6616
6616
|
let parsed;
|
|
6617
6617
|
try {
|
|
@@ -6961,13 +6961,13 @@ import { join as join17 } from "path";
|
|
|
6961
6961
|
var JOURNAL_DIR = "journal";
|
|
6962
6962
|
var ENTRY_NAME = /^(\d{4}-\d{2}-\d{2})-(.+)\.md$/;
|
|
6963
6963
|
function journalTitle(content) {
|
|
6964
|
-
const
|
|
6965
|
-
const frontmatter = /^---\n([\s\S]*?)\n---/.exec(
|
|
6964
|
+
const text3 = content.replace(/\r\n?/g, "\n");
|
|
6965
|
+
const frontmatter = /^---\n([\s\S]*?)\n---/.exec(text3);
|
|
6966
6966
|
if (frontmatter) {
|
|
6967
6967
|
const title = /^title:\s*(.+)$/m.exec(frontmatter[1]);
|
|
6968
6968
|
if (title) return title[1].trim().replace(/^(["'])(.*)\1$/, "$2");
|
|
6969
6969
|
}
|
|
6970
|
-
const heading = /^# (.+?)\s*$/m.exec(
|
|
6970
|
+
const heading = /^# (.+?)\s*$/m.exec(text3);
|
|
6971
6971
|
return heading ? heading[1].trim() : null;
|
|
6972
6972
|
}
|
|
6973
6973
|
async function collectJournal(root) {
|
|
@@ -7405,9 +7405,9 @@ async function collectState(cwd, packageVersion, options) {
|
|
|
7405
7405
|
|
|
7406
7406
|
// src/commands/state.ts
|
|
7407
7407
|
var USAGE_POINTER = "spell state prints one JSON document to stdout: run `spell state --json` (spell status is the human view).";
|
|
7408
|
-
function writeStdout(
|
|
7408
|
+
function writeStdout(text3) {
|
|
7409
7409
|
return new Promise((resolve10, reject) => {
|
|
7410
|
-
process.stdout.write(
|
|
7410
|
+
process.stdout.write(text3, (error2) => error2 ? reject(error2) : resolve10());
|
|
7411
7411
|
});
|
|
7412
7412
|
}
|
|
7413
7413
|
async function runState(targetDir, packageVersion, options = {}, deps = {}) {
|
|
@@ -7417,13 +7417,13 @@ async function runState(targetDir, packageVersion, options = {}, deps = {}) {
|
|
|
7417
7417
|
return;
|
|
7418
7418
|
}
|
|
7419
7419
|
const collect = deps.collect ?? collectState;
|
|
7420
|
-
let
|
|
7420
|
+
let text3;
|
|
7421
7421
|
try {
|
|
7422
7422
|
const doc = await collect(targetDir, packageVersion, {
|
|
7423
7423
|
online: options.online === true,
|
|
7424
7424
|
includeLocalPaths: options.includeLocalPaths === true
|
|
7425
7425
|
});
|
|
7426
|
-
|
|
7426
|
+
text3 = serializeState(doc);
|
|
7427
7427
|
} catch (error2) {
|
|
7428
7428
|
if (error2 instanceof ManifestNotFoundError) {
|
|
7429
7429
|
console.error('Not initialized. Run "spell init" first.');
|
|
@@ -7436,7 +7436,7 @@ async function runState(targetDir, packageVersion, options = {}, deps = {}) {
|
|
|
7436
7436
|
return;
|
|
7437
7437
|
}
|
|
7438
7438
|
try {
|
|
7439
|
-
await (deps.write ?? writeStdout)(
|
|
7439
|
+
await (deps.write ?? writeStdout)(text3);
|
|
7440
7440
|
} catch (error2) {
|
|
7441
7441
|
console.error(`spell state: stdout is not writable (${error2 instanceof Error ? error2.message : String(error2)})`);
|
|
7442
7442
|
process.exitCode = 1;
|
|
@@ -7638,8 +7638,8 @@ async function uninstallUserTier(options, storeRoot, manifest) {
|
|
|
7638
7638
|
}
|
|
7639
7639
|
|
|
7640
7640
|
// src/commands/doctor.ts
|
|
7641
|
-
import { access as access4, readdir as readdir11, readFile as
|
|
7642
|
-
import { join as
|
|
7641
|
+
import { access as access4, readdir as readdir11, readFile as readFile20, mkdir as mkdir7, copyFile as fsCopyFile } from "fs/promises";
|
|
7642
|
+
import { join as join25, dirname as dirname12, resolve as resolve7 } from "path";
|
|
7643
7643
|
|
|
7644
7644
|
// src/config/incidents.ts
|
|
7645
7645
|
var INCIDENT_QUEUE = [
|
|
@@ -7665,37 +7665,37 @@ function evaluateIncidentGate(records) {
|
|
|
7665
7665
|
const blockers = [];
|
|
7666
7666
|
const acceptedRisks = [];
|
|
7667
7667
|
const seen = /* @__PURE__ */ new Set();
|
|
7668
|
-
for (const
|
|
7669
|
-
if (seen.has(
|
|
7670
|
-
blockers.push(`${
|
|
7668
|
+
for (const record2 of records) {
|
|
7669
|
+
if (seen.has(record2.id)) {
|
|
7670
|
+
blockers.push(`${record2.id}: duplicate incident record`);
|
|
7671
7671
|
continue;
|
|
7672
7672
|
}
|
|
7673
|
-
seen.add(
|
|
7674
|
-
if (
|
|
7675
|
-
if (!PROTECTED_IMPACTS.has(
|
|
7676
|
-
if (!
|
|
7677
|
-
blockers.push(`${
|
|
7673
|
+
seen.add(record2.id);
|
|
7674
|
+
if (record2.confidence === "research") continue;
|
|
7675
|
+
if (!PROTECTED_IMPACTS.has(record2.impact)) continue;
|
|
7676
|
+
if (!record2.severity) {
|
|
7677
|
+
blockers.push(`${record2.id}: confirmed ${record2.impact} incident is missing severity`);
|
|
7678
7678
|
continue;
|
|
7679
7679
|
}
|
|
7680
|
-
if (!BLOCKING_SEVERITIES.has(
|
|
7681
|
-
if (
|
|
7682
|
-
if (!
|
|
7683
|
-
blockers.push(`${
|
|
7680
|
+
if (!BLOCKING_SEVERITIES.has(record2.severity)) continue;
|
|
7681
|
+
if (record2.state === "resolved") {
|
|
7682
|
+
if (!record2.resolution?.trim()) {
|
|
7683
|
+
blockers.push(`${record2.id}: resolved incident is missing resolution evidence`);
|
|
7684
7684
|
}
|
|
7685
7685
|
continue;
|
|
7686
7686
|
}
|
|
7687
|
-
if (
|
|
7688
|
-
if (!
|
|
7687
|
+
if (record2.state === "deferred") {
|
|
7688
|
+
if (!record2.deferral || !DEFERRAL_PATTERN.test(record2.deferral)) {
|
|
7689
7689
|
blockers.push(
|
|
7690
|
-
`${
|
|
7690
|
+
`${record2.id}: deferral must be exactly "Deferred <date> \u2014 known open, accepting the risk."`
|
|
7691
7691
|
);
|
|
7692
7692
|
} else {
|
|
7693
|
-
acceptedRisks.push(`${
|
|
7693
|
+
acceptedRisks.push(`${record2.id}: ${record2.deferral}`);
|
|
7694
7694
|
}
|
|
7695
7695
|
continue;
|
|
7696
7696
|
}
|
|
7697
7697
|
blockers.push(
|
|
7698
|
-
`${
|
|
7698
|
+
`${record2.id}: unresolved ${record2.severity} ${record2.impact} incident blocks release`
|
|
7699
7699
|
);
|
|
7700
7700
|
}
|
|
7701
7701
|
return {
|
|
@@ -7977,10 +7977,10 @@ function stripFrontmatter(content) {
|
|
|
7977
7977
|
const block = FRONTMATTER_PATTERN2.exec(content);
|
|
7978
7978
|
return block ? content.slice(block[0].length) : content;
|
|
7979
7979
|
}
|
|
7980
|
-
function stripFencedCode(
|
|
7980
|
+
function stripFencedCode(text3) {
|
|
7981
7981
|
const kept = [];
|
|
7982
7982
|
let fence = null;
|
|
7983
|
-
for (const line2 of
|
|
7983
|
+
for (const line2 of text3.split(/\r?\n/)) {
|
|
7984
7984
|
if (fence) {
|
|
7985
7985
|
const close = /^ {0,3}(`{3,}|~{3,})[ \t]*$/.exec(line2);
|
|
7986
7986
|
if (close && close[1][0] === fence.char && close[1].length >= fence.length) fence = null;
|
|
@@ -7997,8 +7997,8 @@ function stripFencedCode(text2) {
|
|
|
7997
7997
|
}
|
|
7998
7998
|
return kept.join("\n");
|
|
7999
7999
|
}
|
|
8000
|
-
function stripInlineCode(
|
|
8001
|
-
return
|
|
8000
|
+
function stripInlineCode(text3) {
|
|
8001
|
+
return text3.split(/\n[ \t]*\n/).map((paragraph) => {
|
|
8002
8002
|
let out = "";
|
|
8003
8003
|
let i = 0;
|
|
8004
8004
|
while (i < paragraph.length) {
|
|
@@ -8356,9 +8356,9 @@ function createSessionStorage(options = {}) {
|
|
|
8356
8356
|
const interpret = (result, source) => {
|
|
8357
8357
|
if (result.state === "signed-out") return null;
|
|
8358
8358
|
if (result.state === "corrupt") return { state: "corrupt", source, reason: result.reason };
|
|
8359
|
-
const
|
|
8360
|
-
if (
|
|
8361
|
-
return { state: "ok", source, record };
|
|
8359
|
+
const record2 = parseSessionRecord(result.secret);
|
|
8360
|
+
if (record2 === null) return { state: "corrupt", source, reason: "the stored session is not valid" };
|
|
8361
|
+
return { state: "ok", source, record: record2 };
|
|
8362
8362
|
};
|
|
8363
8363
|
return {
|
|
8364
8364
|
load() {
|
|
@@ -8376,8 +8376,8 @@ function createSessionStorage(options = {}) {
|
|
|
8376
8376
|
}
|
|
8377
8377
|
return keychainError ? { state: "signed-out", keychainError } : { state: "signed-out" };
|
|
8378
8378
|
},
|
|
8379
|
-
save(
|
|
8380
|
-
const serialized = JSON.stringify(
|
|
8379
|
+
save(record2, { insecure }) {
|
|
8380
|
+
const serialized = JSON.stringify(record2);
|
|
8381
8381
|
if (insecure) {
|
|
8382
8382
|
file.save(serialized);
|
|
8383
8383
|
try {
|
|
@@ -8606,10 +8606,10 @@ function send(port, token, method, path5, body, options = {}) {
|
|
|
8606
8606
|
res.on("error", () => done({ kind: "unreachable" }));
|
|
8607
8607
|
res.on("end", () => {
|
|
8608
8608
|
const status = res.statusCode ?? 0;
|
|
8609
|
-
const
|
|
8609
|
+
const text3 = Buffer.concat(chunks).toString("utf8");
|
|
8610
8610
|
let parsed = null;
|
|
8611
8611
|
try {
|
|
8612
|
-
parsed =
|
|
8612
|
+
parsed = text3.length > 0 ? JSON.parse(text3) : null;
|
|
8613
8613
|
} catch {
|
|
8614
8614
|
parsed = null;
|
|
8615
8615
|
}
|
|
@@ -8727,6 +8727,77 @@ function openEventFeed(callbacks, options = {}) {
|
|
|
8727
8727
|
|
|
8728
8728
|
// src/commands/doctor.ts
|
|
8729
8729
|
import { statSync } from "fs";
|
|
8730
|
+
|
|
8731
|
+
// src/modules/run-grants.ts
|
|
8732
|
+
import { readFile as readFile19 } from "fs/promises";
|
|
8733
|
+
import { join as join24 } from "path";
|
|
8734
|
+
var RUN_ACTIONS = [
|
|
8735
|
+
"commit",
|
|
8736
|
+
"push",
|
|
8737
|
+
"create-pr",
|
|
8738
|
+
"assign-pr",
|
|
8739
|
+
"approve-pr",
|
|
8740
|
+
"complete-pr",
|
|
8741
|
+
"delete-remote-branch",
|
|
8742
|
+
"run-dev-pipeline"
|
|
8743
|
+
];
|
|
8744
|
+
var record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
8745
|
+
var text = (v) => typeof v === "string" && v.trim().length > 0;
|
|
8746
|
+
var strings = (v) => Array.isArray(v) && v.every(text);
|
|
8747
|
+
var actions = (v) => strings(v) && v.every((a) => RUN_ACTIONS.some((known) => known === a));
|
|
8748
|
+
function timestamp(v) {
|
|
8749
|
+
if (typeof v !== "string" || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(v)) return false;
|
|
8750
|
+
const n = Date.parse(v);
|
|
8751
|
+
return Number.isFinite(n) && new Date(n).toISOString() === v;
|
|
8752
|
+
}
|
|
8753
|
+
function isRunGrant(v) {
|
|
8754
|
+
if (!record(v) || !record(v.scope)) return false;
|
|
8755
|
+
return [v.id, v.grantedBy, v.operatorWords, v.revocation].every(text) && timestamp(v.grantedAt) && timestamp(v.expiresAt) && Date.parse(v.expiresAt) > Date.parse(v.grantedAt) && (v.status === "active" || v.status === "revoked") && [v.scope.repository, v.scope.task, v.scope.branchPrefix].every(text) && actions(v.permittedActions) && v.permittedActions.length > 0 && strings(v.excludedActions) && strings(v.requiredChecks) && (!v.permittedActions.includes("complete-pr") || v.requiredChecks.length > 0);
|
|
8756
|
+
}
|
|
8757
|
+
function parseRunGrants(value) {
|
|
8758
|
+
if (!record(value)) throw new Error("expected a delegations object");
|
|
8759
|
+
if (value.runGrants === void 0) return [];
|
|
8760
|
+
if (!Array.isArray(value.runGrants) || !value.runGrants.every(isRunGrant)) {
|
|
8761
|
+
throw new Error("runGrants must be an array of well-formed, expiring run grants");
|
|
8762
|
+
}
|
|
8763
|
+
const grants = value.runGrants;
|
|
8764
|
+
if (new Set(grants.map((g) => g.id)).size !== grants.length) {
|
|
8765
|
+
throw new Error("duplicate run grant ID");
|
|
8766
|
+
}
|
|
8767
|
+
return grants;
|
|
8768
|
+
}
|
|
8769
|
+
async function readRunGrants(targetDir) {
|
|
8770
|
+
let raw;
|
|
8771
|
+
try {
|
|
8772
|
+
raw = await readFile19(join24(targetDir, ".arcane", "delegations.json"), "utf8");
|
|
8773
|
+
} catch (error2) {
|
|
8774
|
+
if (error2.code === "ENOENT") return [];
|
|
8775
|
+
throw error2;
|
|
8776
|
+
}
|
|
8777
|
+
return parseRunGrants(JSON.parse(raw));
|
|
8778
|
+
}
|
|
8779
|
+
function evaluateRunGrant(grants, request2) {
|
|
8780
|
+
const deny = (reason) => ({ matches: false, reason });
|
|
8781
|
+
const candidates = grants.filter((g) => g.id === request2.id);
|
|
8782
|
+
if (candidates.length !== 1) return deny("missing or duplicate grant");
|
|
8783
|
+
const grant = candidates[0];
|
|
8784
|
+
if (!isRunGrant(grant)) return deny("invalid grant");
|
|
8785
|
+
if (!Number.isFinite(request2.now.getTime())) return deny("invalid clock");
|
|
8786
|
+
if (grant.status !== "active") return deny("revoked grant");
|
|
8787
|
+
if (request2.now.getTime() < Date.parse(grant.grantedAt)) return deny("grant not yet active");
|
|
8788
|
+
if (request2.now.getTime() >= Date.parse(grant.expiresAt)) return deny("expired grant");
|
|
8789
|
+
if (grant.scope.repository !== request2.repository || grant.scope.task !== request2.task) {
|
|
8790
|
+
return deny("repository or task outside scope");
|
|
8791
|
+
}
|
|
8792
|
+
const prefix = grant.scope.branchPrefix;
|
|
8793
|
+
const boundary = prefix.endsWith("/") || prefix.endsWith("-");
|
|
8794
|
+
if (!(request2.branch === prefix || request2.branch.startsWith(boundary ? prefix : `${prefix}-`) || !boundary && request2.branch.startsWith(`${prefix}/`))) return deny("branch outside scope");
|
|
8795
|
+
if (grant.excludedActions.some((a) => a === request2.action)) return deny("excluded action");
|
|
8796
|
+
if (!grant.permittedActions.some((a) => a === request2.action)) return deny("action not permitted");
|
|
8797
|
+
return { matches: true, reason: "record matches; verify operator consent and execution/platform gates", grant };
|
|
8798
|
+
}
|
|
8799
|
+
|
|
8800
|
+
// src/commands/doctor.ts
|
|
8730
8801
|
function isRecord7(value) {
|
|
8731
8802
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8732
8803
|
}
|
|
@@ -8745,13 +8816,13 @@ async function checkNodeVersion() {
|
|
|
8745
8816
|
}
|
|
8746
8817
|
async function checkArcaneManifest(targetDir) {
|
|
8747
8818
|
const name = "Arcane manifest (.arcane.json)";
|
|
8748
|
-
const manifestPath2 =
|
|
8819
|
+
const manifestPath2 = join25(targetDir, ".arcane.json");
|
|
8749
8820
|
try {
|
|
8750
8821
|
await access4(manifestPath2);
|
|
8751
8822
|
} catch {
|
|
8752
|
-
const selfHostedManifestPath =
|
|
8823
|
+
const selfHostedManifestPath = join25(targetDir, "src", "assets", ".arcane.json");
|
|
8753
8824
|
try {
|
|
8754
|
-
const raw = await
|
|
8825
|
+
const raw = await readFile20(selfHostedManifestPath, "utf8");
|
|
8755
8826
|
const manifest = JSON.parse(raw);
|
|
8756
8827
|
if (isRecord7(manifest) && manifest["selfHosted"] === true && manifest["tracking_mode"] === "internal") {
|
|
8757
8828
|
return {
|
|
@@ -8770,7 +8841,7 @@ async function checkArcaneManifest(targetDir) {
|
|
|
8770
8841
|
};
|
|
8771
8842
|
}
|
|
8772
8843
|
try {
|
|
8773
|
-
const raw = await
|
|
8844
|
+
const raw = await readFile20(manifestPath2, "utf8");
|
|
8774
8845
|
const manifest = JSON.parse(raw);
|
|
8775
8846
|
if (!isRecord7(manifest) || typeof manifest["version"] !== "string" || !manifest["version"] || !Array.isArray(manifest["components"])) {
|
|
8776
8847
|
return {
|
|
@@ -8855,7 +8926,7 @@ async function checkSessionContinuity(targetDir) {
|
|
|
8855
8926
|
const results = [];
|
|
8856
8927
|
for (const file of SESSION_CONTINUITY_FILES) {
|
|
8857
8928
|
const name = `Session file: ${file}`;
|
|
8858
|
-
const filePath =
|
|
8929
|
+
const filePath = join25(targetDir, file);
|
|
8859
8930
|
try {
|
|
8860
8931
|
await access4(filePath);
|
|
8861
8932
|
results.push({ name, passed: true, message: "present" });
|
|
@@ -8873,7 +8944,7 @@ async function checkSessionContinuity(targetDir) {
|
|
|
8873
8944
|
async function fixSessionContinuity(targetDir, assetsDir) {
|
|
8874
8945
|
const created = [];
|
|
8875
8946
|
for (const file of SESSION_CONTINUITY_FILES) {
|
|
8876
|
-
const destPath =
|
|
8947
|
+
const destPath = join25(targetDir, file);
|
|
8877
8948
|
try {
|
|
8878
8949
|
await access4(destPath);
|
|
8879
8950
|
} catch {
|
|
@@ -8891,7 +8962,7 @@ async function fixSessionContinuity(targetDir, assetsDir) {
|
|
|
8891
8962
|
);
|
|
8892
8963
|
continue;
|
|
8893
8964
|
}
|
|
8894
|
-
const srcPath =
|
|
8965
|
+
const srcPath = join25(assetsDir, file);
|
|
8895
8966
|
await mkdir7(dirname12(destPath), { recursive: true });
|
|
8896
8967
|
await fsCopyFile(srcPath, destPath);
|
|
8897
8968
|
created.push(file);
|
|
@@ -9070,7 +9141,7 @@ async function checkGovernancePlaceholders(targetDir) {
|
|
|
9070
9141
|
const name = "Governance placeholders (ARC-051)";
|
|
9071
9142
|
let scan;
|
|
9072
9143
|
try {
|
|
9073
|
-
scan = await scanGovernancePlaceholders(
|
|
9144
|
+
scan = await scanGovernancePlaceholders(join25(targetDir, ".arcane", "governance"));
|
|
9074
9145
|
} catch (err) {
|
|
9075
9146
|
return {
|
|
9076
9147
|
name,
|
|
@@ -9157,10 +9228,10 @@ async function checkNewlyAvailableComponents(targetDir) {
|
|
|
9157
9228
|
}
|
|
9158
9229
|
async function checkMcpConfig(targetDir) {
|
|
9159
9230
|
const name = "MCP config (per-server timeout, I12)";
|
|
9160
|
-
const filePath =
|
|
9231
|
+
const filePath = join25(targetDir, ".mcp.json");
|
|
9161
9232
|
let raw;
|
|
9162
9233
|
try {
|
|
9163
|
-
raw = await
|
|
9234
|
+
raw = await readFile20(filePath, "utf8");
|
|
9164
9235
|
} catch {
|
|
9165
9236
|
return { name, passed: true, blocking: false, message: "no .mcp.json \u2014 nothing to check" };
|
|
9166
9237
|
}
|
|
@@ -9240,10 +9311,10 @@ function isDelegationsFile(value) {
|
|
|
9240
9311
|
}
|
|
9241
9312
|
async function checkDelegations(targetDir) {
|
|
9242
9313
|
const name = "Standing delegations (T13)";
|
|
9243
|
-
const filePath =
|
|
9314
|
+
const filePath = join25(targetDir, ".arcane", "delegations.json");
|
|
9244
9315
|
let raw;
|
|
9245
9316
|
try {
|
|
9246
|
-
raw = await
|
|
9317
|
+
raw = await readFile20(filePath, "utf8");
|
|
9247
9318
|
} catch {
|
|
9248
9319
|
return { name, passed: true, blocking: false, message: "no delegations recorded" };
|
|
9249
9320
|
}
|
|
@@ -9279,6 +9350,23 @@ async function checkDelegations(targetDir) {
|
|
|
9279
9350
|
message: `${active.length} active: ${summary}`
|
|
9280
9351
|
};
|
|
9281
9352
|
}
|
|
9353
|
+
async function checkRunGrants(targetDir, now = /* @__PURE__ */ new Date()) {
|
|
9354
|
+
const name = "Run grants (ARC-061)";
|
|
9355
|
+
try {
|
|
9356
|
+
const grants = await readRunGrants(targetDir);
|
|
9357
|
+
const active = grants.filter((g) => g.status === "active" && Date.parse(g.grantedAt) <= now.getTime() && now.getTime() < Date.parse(g.expiresAt));
|
|
9358
|
+
const inactive = grants.length - active.length;
|
|
9359
|
+
return {
|
|
9360
|
+
name,
|
|
9361
|
+
passed: true,
|
|
9362
|
+
blocking: false,
|
|
9363
|
+
showDetails: grants.length > 0,
|
|
9364
|
+
message: grants.length === 0 ? "no run grants recorded" : `${active.length} active run grant(s): ${active.map((g) => `${g.id} (expires ${g.expiresAt}; task: ${g.scope.task}; branch: ${g.scope.branchPrefix})`).join("; ")}; ${inactive} expired, future or revoked`
|
|
9365
|
+
};
|
|
9366
|
+
} catch {
|
|
9367
|
+
return { name, passed: false, blocking: false, message: "run grant file unreadable or invalid; no run authority can be inferred" };
|
|
9368
|
+
}
|
|
9369
|
+
}
|
|
9282
9370
|
async function checkPlatformBranchPolicy(targetDir) {
|
|
9283
9371
|
const name = "Platform branch/merge policy (T11)";
|
|
9284
9372
|
const targets = await pushTargets(targetDir);
|
|
@@ -9343,7 +9431,7 @@ async function checkManagedFileModes(targetDir) {
|
|
|
9343
9431
|
if (!isClientShimPath(file) || asLinks.includes(file)) continue;
|
|
9344
9432
|
let body;
|
|
9345
9433
|
try {
|
|
9346
|
-
body = await
|
|
9434
|
+
body = await readFile20(join25(targetDir, file), "utf-8");
|
|
9347
9435
|
} catch {
|
|
9348
9436
|
continue;
|
|
9349
9437
|
}
|
|
@@ -9396,7 +9484,7 @@ async function checkSpellScope(targetDir, homeDir = resolveHomeDir()) {
|
|
|
9396
9484
|
let probed = false;
|
|
9397
9485
|
const probe2 = "spell-status.md";
|
|
9398
9486
|
try {
|
|
9399
|
-
const entries = await readdir11(
|
|
9487
|
+
const entries = await readdir11(join25(storeRoot, USER_TIER_SPELLS_DIR));
|
|
9400
9488
|
const spells = entries.filter((e) => e.startsWith("spell-") && e.endsWith(".md"));
|
|
9401
9489
|
spellCount = spells.length;
|
|
9402
9490
|
probed = spells.includes(probe2);
|
|
@@ -9456,6 +9544,7 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
9456
9544
|
checkPushPolicy(targetDir),
|
|
9457
9545
|
checkPlatformBranchPolicy(targetDir),
|
|
9458
9546
|
checkDelegations(targetDir),
|
|
9547
|
+
checkRunGrants(targetDir),
|
|
9459
9548
|
checkMcpConfig(targetDir),
|
|
9460
9549
|
checkUserTier(resolveHomeDir(), options.packageVersion),
|
|
9461
9550
|
checkSpellScope(targetDir),
|
|
@@ -9473,7 +9562,7 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
9473
9562
|
const icon = skipped ? "\u2013" : result.passed ? "\u2713" : result.blocking === false ? "\u26A0" : "\u2717";
|
|
9474
9563
|
const label2 = skipped ? "skip" : result.passed ? "pass" : result.blocking === false ? "warn" : "FAIL";
|
|
9475
9564
|
console.log(` ${icon} [${label2}] ${result.name}`);
|
|
9476
|
-
if (!result.passed || skipped) {
|
|
9565
|
+
if (!result.passed || skipped || result.showDetails === true) {
|
|
9477
9566
|
console.log(` ${result.message}`);
|
|
9478
9567
|
}
|
|
9479
9568
|
if (!result.passed && result.blocking !== false) {
|
|
@@ -9518,17 +9607,31 @@ async function runDoctor(targetDir, options = {}, assetsDir) {
|
|
|
9518
9607
|
}
|
|
9519
9608
|
}
|
|
9520
9609
|
|
|
9610
|
+
// src/commands/grant.ts
|
|
9611
|
+
async function checkRunGrant(targetDir, request2) {
|
|
9612
|
+
try {
|
|
9613
|
+
return evaluateRunGrant(await readRunGrants(targetDir), request2);
|
|
9614
|
+
} catch {
|
|
9615
|
+
return { matches: false, reason: "grant file unreadable or invalid" };
|
|
9616
|
+
}
|
|
9617
|
+
}
|
|
9618
|
+
async function runGrantCheck(options) {
|
|
9619
|
+
const result = await checkRunGrant(process.cwd(), { ...options, now: /* @__PURE__ */ new Date() });
|
|
9620
|
+
console.log(JSON.stringify({ matches: result.matches, reason: result.reason }));
|
|
9621
|
+
if (!result.matches) process.exitCode = 1;
|
|
9622
|
+
}
|
|
9623
|
+
|
|
9521
9624
|
// src/commands/ward.ts
|
|
9522
|
-
import { readFile as
|
|
9625
|
+
import { readFile as readFile22 } from "fs/promises";
|
|
9523
9626
|
|
|
9524
9627
|
// src/modules/ward.ts
|
|
9525
|
-
import { readdir as readdir12, readFile as
|
|
9526
|
-
import { join as
|
|
9628
|
+
import { readdir as readdir12, readFile as readFile21 } from "fs/promises";
|
|
9629
|
+
import { join as join26, relative as relative3 } from "path";
|
|
9527
9630
|
async function scanFileAllRules(absolutePath, displayPath, rules) {
|
|
9528
9631
|
const findings = [];
|
|
9529
9632
|
let content;
|
|
9530
9633
|
try {
|
|
9531
|
-
content = await
|
|
9634
|
+
content = await readFile21(absolutePath, "utf8");
|
|
9532
9635
|
} catch {
|
|
9533
9636
|
return findings;
|
|
9534
9637
|
}
|
|
@@ -9586,7 +9689,7 @@ async function scanFilenames(root, rules, repoRoot = root) {
|
|
|
9586
9689
|
return findings;
|
|
9587
9690
|
}
|
|
9588
9691
|
for (const entry of entries) {
|
|
9589
|
-
const absolutePath =
|
|
9692
|
+
const absolutePath = join26(root, entry.name);
|
|
9590
9693
|
if (entry.isDirectory()) {
|
|
9591
9694
|
if (SKIP_DIRECTORIES.has(entry.name)) continue;
|
|
9592
9695
|
findings.push(...await scanFilenames(absolutePath, rules, repoRoot));
|
|
@@ -9623,7 +9726,7 @@ async function runWard(repoRoot, options = {}) {
|
|
|
9623
9726
|
const findings2 = await scanFileAllRules(absolutePath, displayPath, allRules);
|
|
9624
9727
|
if (findings2.length > 0) {
|
|
9625
9728
|
try {
|
|
9626
|
-
fileLines.set(displayPath, (await
|
|
9729
|
+
fileLines.set(displayPath, (await readFile21(absolutePath, "utf8")).split("\n"));
|
|
9627
9730
|
} catch {
|
|
9628
9731
|
}
|
|
9629
9732
|
}
|
|
@@ -9651,7 +9754,7 @@ function parseTermsFileContent(raw) {
|
|
|
9651
9754
|
}
|
|
9652
9755
|
async function readTermsFile(filePath) {
|
|
9653
9756
|
try {
|
|
9654
|
-
return parseTermsFileContent(await
|
|
9757
|
+
return parseTermsFileContent(await readFile22(filePath, "utf8"));
|
|
9655
9758
|
} catch (error2) {
|
|
9656
9759
|
console.warn(
|
|
9657
9760
|
`\u26A0 arcane: could not read --terms-file "${filePath}": ${error2.message}`
|
|
@@ -9906,16 +10009,16 @@ import * as client from "openid-client";
|
|
|
9906
10009
|
|
|
9907
10010
|
// src/modules/result-pages.ts
|
|
9908
10011
|
import { createHash as createHash5 } from "crypto";
|
|
9909
|
-
import { readFile as
|
|
9910
|
-
import { join as
|
|
10012
|
+
import { readFile as readFile23 } from "fs/promises";
|
|
10013
|
+
import { join as join27 } from "path";
|
|
9911
10014
|
var RESULT_KINDS = ["success", "denied", "failure"];
|
|
9912
10015
|
function resultPagePath(kind) {
|
|
9913
|
-
return
|
|
10016
|
+
return join27("sign-in", `sign-in-callback-${kind}.html`);
|
|
9914
10017
|
}
|
|
9915
10018
|
function styleHashes(html) {
|
|
9916
10019
|
return [...html.matchAll(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/gi)].map((match2) => {
|
|
9917
|
-
const
|
|
9918
|
-
return `'sha256-${createHash5("sha256").update(
|
|
10020
|
+
const text3 = match2[1].replace(/\r\n?/g, "\n");
|
|
10021
|
+
return `'sha256-${createHash5("sha256").update(text3, "utf8").digest("base64")}'`;
|
|
9919
10022
|
});
|
|
9920
10023
|
}
|
|
9921
10024
|
function resultPageHeaders(html) {
|
|
@@ -9958,7 +10061,7 @@ var PLAIN_RESULT_PAGES = {
|
|
|
9958
10061
|
async function loadResultPages(assetsDir) {
|
|
9959
10062
|
try {
|
|
9960
10063
|
const [success, denied, failure] = await Promise.all(
|
|
9961
|
-
RESULT_KINDS.map(async (kind) => resultPage(await
|
|
10064
|
+
RESULT_KINDS.map(async (kind) => resultPage(await readFile23(join27(assetsDir, resultPagePath(kind)), "utf8")))
|
|
9962
10065
|
);
|
|
9963
10066
|
return { success, denied, failure };
|
|
9964
10067
|
} catch {
|
|
@@ -10196,9 +10299,9 @@ function toSessionRecord(result, environment) {
|
|
|
10196
10299
|
expiresAt: result.expiresAt.toISOString()
|
|
10197
10300
|
};
|
|
10198
10301
|
}
|
|
10199
|
-
function describeAccount(
|
|
10200
|
-
const who =
|
|
10201
|
-
return
|
|
10302
|
+
function describeAccount(record2) {
|
|
10303
|
+
const who = record2.email ? `${record2.email} (${record2.sub})` : record2.sub;
|
|
10304
|
+
return record2.environment === "development" ? `${who} [development]` : who;
|
|
10202
10305
|
}
|
|
10203
10306
|
async function runLogin(options, deps) {
|
|
10204
10307
|
let environment;
|
|
@@ -10229,11 +10332,11 @@ async function runLogin(options, deps) {
|
|
|
10229
10332
|
process.exit(1);
|
|
10230
10333
|
return;
|
|
10231
10334
|
}
|
|
10232
|
-
let
|
|
10335
|
+
let record2;
|
|
10233
10336
|
const saveValidatedSession = (result) => {
|
|
10234
10337
|
const next = toSessionRecord(result, environment);
|
|
10235
10338
|
deps.storage.save(next, { insecure: Boolean(options.insecureStorage) });
|
|
10236
|
-
|
|
10339
|
+
record2 = next;
|
|
10237
10340
|
};
|
|
10238
10341
|
try {
|
|
10239
10342
|
if (options.deviceCode) {
|
|
@@ -10258,7 +10361,7 @@ async function runLogin(options, deps) {
|
|
|
10258
10361
|
printWarning("No browser could be opened. Run `spell login --device-code` to sign in on another device.");
|
|
10259
10362
|
}
|
|
10260
10363
|
});
|
|
10261
|
-
if (!
|
|
10364
|
+
if (!record2) throw new Error("The browser sign-in ended without storing a session.");
|
|
10262
10365
|
}
|
|
10263
10366
|
} catch (error2) {
|
|
10264
10367
|
if (error2 instanceof KeychainUnavailableError) {
|
|
@@ -10273,8 +10376,8 @@ async function runLogin(options, deps) {
|
|
|
10273
10376
|
process.exit(1);
|
|
10274
10377
|
return;
|
|
10275
10378
|
}
|
|
10276
|
-
if (!
|
|
10277
|
-
printSuccess(`Signed in as ${describeAccount(
|
|
10379
|
+
if (!record2) throw new Error("The sign-in completed without a stored session.");
|
|
10380
|
+
printSuccess(`Signed in as ${describeAccount(record2)}.`);
|
|
10278
10381
|
if (options.insecureStorage) printWarning(INSECURE_STORAGE_WARNING);
|
|
10279
10382
|
}
|
|
10280
10383
|
|
|
@@ -10342,21 +10445,21 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
|
|
|
10342
10445
|
process.exit(1);
|
|
10343
10446
|
return;
|
|
10344
10447
|
}
|
|
10345
|
-
const { record, source } = loaded;
|
|
10346
|
-
printSuccess(`Signed in as ${describeAccount(
|
|
10347
|
-
const expires = new Date(
|
|
10448
|
+
const { record: record2, source } = loaded;
|
|
10449
|
+
printSuccess(`Signed in as ${describeAccount(record2)}.`);
|
|
10450
|
+
const expires = new Date(record2.expiresAt);
|
|
10348
10451
|
const expired = expires.getTime() <= deps.now().getTime();
|
|
10349
|
-
printInfo(`Signed in on ${new Date(
|
|
10452
|
+
printInfo(`Signed in on ${new Date(record2.obtainedAt).toLocaleString()}.`);
|
|
10350
10453
|
printInfo(
|
|
10351
10454
|
expired ? `The last token expired on ${expires.toLocaleString()}; the session refreshes on next use.` : `The current token is valid until ${expires.toLocaleString()}.`
|
|
10352
10455
|
);
|
|
10353
10456
|
printInfo(`Stored in ${source === "keychain" ? "the OS keychain" : "the insecure session file"}.`);
|
|
10354
10457
|
if (source === "file") printWarning(INSECURE_STORAGE_WARNING);
|
|
10355
10458
|
if (!options.verify) return;
|
|
10356
|
-
const environment = IDENTITY_ENVIRONMENTS[
|
|
10459
|
+
const environment = IDENTITY_ENVIRONMENTS[record2.environment];
|
|
10357
10460
|
try {
|
|
10358
10461
|
const config = await deps.loadConfiguration(environment);
|
|
10359
|
-
const fresh = await deps.refreshSession(config,
|
|
10462
|
+
const fresh = await deps.refreshSession(config, record2.refreshToken, SIGN_IN_SCOPES);
|
|
10360
10463
|
deps.storage.save(toSessionRecord(fresh, environment), { insecure: source === "file" });
|
|
10361
10464
|
printSuccess(`Verified with the identity service at ${environment.signInHost}; the session is active.`);
|
|
10362
10465
|
} catch (error2) {
|
|
@@ -10369,15 +10472,15 @@ async function runWhoami(options, deps = defaultWhoamiDeps()) {
|
|
|
10369
10472
|
|
|
10370
10473
|
// src/commands/report.ts
|
|
10371
10474
|
import { access as access5 } from "fs/promises";
|
|
10372
|
-
import { join as
|
|
10475
|
+
import { join as join30 } from "path";
|
|
10373
10476
|
|
|
10374
10477
|
// src/modules/show-report/generate.ts
|
|
10375
|
-
import { mkdir as mkdir8, readdir as readdir13, readFile as
|
|
10376
|
-
import { basename as basename4, dirname as dirname13, isAbsolute, join as
|
|
10478
|
+
import { mkdir as mkdir8, readdir as readdir13, readFile as readFile25, writeFile as writeFile11 } from "fs/promises";
|
|
10479
|
+
import { basename as basename4, dirname as dirname13, isAbsolute, join as join29, relative as relative4, resolve as resolve9 } from "path";
|
|
10377
10480
|
|
|
10378
10481
|
// src/modules/show-report/model.ts
|
|
10379
|
-
import { readFile as
|
|
10380
|
-
import { join as
|
|
10482
|
+
import { readFile as readFile24 } from "fs/promises";
|
|
10483
|
+
import { join as join28 } from "path";
|
|
10381
10484
|
|
|
10382
10485
|
// src/modules/show-report/plan-parser.ts
|
|
10383
10486
|
function parseFrontmatter(content) {
|
|
@@ -10512,8 +10615,8 @@ function computeCoverageOutcome(content) {
|
|
|
10512
10615
|
const dispositioned = total - parkedCount;
|
|
10513
10616
|
return `${total} of ${total} items dispositioned \u2014 ${dispositioned} routed to an epic, ${parkedCount} parked`;
|
|
10514
10617
|
}
|
|
10515
|
-
function stripMarkdown(
|
|
10516
|
-
return
|
|
10618
|
+
function stripMarkdown(text3) {
|
|
10619
|
+
return text3.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*/g, "").replace(/`/g, "").trim();
|
|
10517
10620
|
}
|
|
10518
10621
|
|
|
10519
10622
|
// src/modules/show-report/queue-parser.ts
|
|
@@ -10559,8 +10662,8 @@ function parseVerificationLedgerSection(content, programNameWords) {
|
|
|
10559
10662
|
}
|
|
10560
10663
|
return rows;
|
|
10561
10664
|
}
|
|
10562
|
-
function stripMarkdown2(
|
|
10563
|
-
return
|
|
10665
|
+
function stripMarkdown2(text3) {
|
|
10666
|
+
return text3.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/\*\*/g, "").replace(/`/g, "").trim();
|
|
10564
10667
|
}
|
|
10565
10668
|
|
|
10566
10669
|
// src/modules/show-report/sources.ts
|
|
@@ -10628,7 +10731,7 @@ async function getCast(cwd, fromRef, toRef, generatedOutputs = []) {
|
|
|
10628
10731
|
// src/modules/show-report/model.ts
|
|
10629
10732
|
async function readOptional(path5) {
|
|
10630
10733
|
try {
|
|
10631
|
-
return await
|
|
10734
|
+
return await readFile24(path5, "utf8");
|
|
10632
10735
|
} catch {
|
|
10633
10736
|
return null;
|
|
10634
10737
|
}
|
|
@@ -10696,9 +10799,9 @@ function buildNeedsYou(queueEntries) {
|
|
|
10696
10799
|
return entry;
|
|
10697
10800
|
});
|
|
10698
10801
|
}
|
|
10699
|
-
function truncateAtWord(
|
|
10700
|
-
if (
|
|
10701
|
-
const window =
|
|
10802
|
+
function truncateAtWord(text3, limit) {
|
|
10803
|
+
if (text3.length <= limit) return text3;
|
|
10804
|
+
const window = text3.slice(0, limit - 1);
|
|
10702
10805
|
const lastSpace = window.lastIndexOf(" ");
|
|
10703
10806
|
const head = lastSpace > 0 ? window.slice(0, lastSpace) : window;
|
|
10704
10807
|
return `${head.replace(/[\s,;:.—-]+$/, "")}\u2026`;
|
|
@@ -10724,13 +10827,13 @@ function buildCorrections(ledgerRows, programName) {
|
|
|
10724
10827
|
};
|
|
10725
10828
|
}
|
|
10726
10829
|
async function buildShowReportModel(options) {
|
|
10727
|
-
const planAbsPath =
|
|
10728
|
-
const queueAbsPath =
|
|
10830
|
+
const planAbsPath = join28(options.rootDir, options.planRelPath);
|
|
10831
|
+
const queueAbsPath = join28(options.rootDir, options.queueRelPath);
|
|
10729
10832
|
const ledgerRelPath = "docs/verification-ledger.md";
|
|
10730
|
-
const ledgerAbsPath =
|
|
10731
|
-
const decisionsAbsPath =
|
|
10833
|
+
const ledgerAbsPath = join28(options.rootDir, ledgerRelPath);
|
|
10834
|
+
const decisionsAbsPath = join28(options.rootDir, "DECISIONS.md");
|
|
10732
10835
|
const [planContent, queueContent, ledgerContent, decisionsContent] = await Promise.all([
|
|
10733
|
-
|
|
10836
|
+
readFile24(planAbsPath, "utf8"),
|
|
10734
10837
|
readOptional(queueAbsPath),
|
|
10735
10838
|
readOptional(ledgerAbsPath),
|
|
10736
10839
|
readOptional(decisionsAbsPath)
|
|
@@ -10824,11 +10927,11 @@ var CATEGORY_ORDER = [
|
|
|
10824
10927
|
"docs",
|
|
10825
10928
|
"platform"
|
|
10826
10929
|
];
|
|
10827
|
-
function escapeHtml(
|
|
10828
|
-
return
|
|
10930
|
+
function escapeHtml(text3) {
|
|
10931
|
+
return text3.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
10829
10932
|
}
|
|
10830
|
-
function inlineMarkupToHtml(
|
|
10831
|
-
const escaped = escapeHtml(
|
|
10933
|
+
function inlineMarkupToHtml(text3) {
|
|
10934
|
+
const escaped = escapeHtml(text3);
|
|
10832
10935
|
const withLinks = escaped.replace(
|
|
10833
10936
|
/\[([^\]]+)\]\((https?:\/\/[^)\s]+)\)/g,
|
|
10834
10937
|
'<a href="$2">$1</a>'
|
|
@@ -10926,7 +11029,7 @@ function toPosix(path5) {
|
|
|
10926
11029
|
async function programFromPlan(rootDir, slug, planRelPath) {
|
|
10927
11030
|
let planContent;
|
|
10928
11031
|
try {
|
|
10929
|
-
planContent = await
|
|
11032
|
+
planContent = await readFile25(join29(rootDir, planRelPath), "utf8");
|
|
10930
11033
|
} catch {
|
|
10931
11034
|
return { skipped: { slug, reason: `no PLAN.md at ${planRelPath}` } };
|
|
10932
11035
|
}
|
|
@@ -10938,13 +11041,13 @@ async function programFromPlan(rootDir, slug, planRelPath) {
|
|
|
10938
11041
|
program: {
|
|
10939
11042
|
slug,
|
|
10940
11043
|
planRelPath,
|
|
10941
|
-
queueRelPath: toPosix(
|
|
11044
|
+
queueRelPath: toPosix(join29(dirname13(planRelPath), "OPERATOR-QUEUE.md")),
|
|
10942
11045
|
ledgerProgramName: programNameFromTitle(frontmatter.title ?? slug)
|
|
10943
11046
|
}
|
|
10944
11047
|
};
|
|
10945
11048
|
}
|
|
10946
11049
|
async function discoverPrograms(rootDir) {
|
|
10947
|
-
const plansDir =
|
|
11050
|
+
const plansDir = join29(rootDir, "docs", "plans");
|
|
10948
11051
|
let entries;
|
|
10949
11052
|
try {
|
|
10950
11053
|
entries = (await readdir13(plansDir, { withFileTypes: true })).filter((e) => e.isDirectory());
|
|
@@ -10971,14 +11074,14 @@ function normalizeLineEndings2(content) {
|
|
|
10971
11074
|
}
|
|
10972
11075
|
async function readOptionalFile(path5) {
|
|
10973
11076
|
try {
|
|
10974
|
-
return await
|
|
11077
|
+
return await readFile25(path5, "utf8");
|
|
10975
11078
|
} catch (error2) {
|
|
10976
11079
|
if (error2.code === "ENOENT") return null;
|
|
10977
11080
|
throw error2;
|
|
10978
11081
|
}
|
|
10979
11082
|
}
|
|
10980
11083
|
async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
|
|
10981
|
-
const planContent = await
|
|
11084
|
+
const planContent = await readFile25(join29(rootDir, program2.planRelPath), "utf8");
|
|
10982
11085
|
const frontmatter = parseFrontmatter(planContent);
|
|
10983
11086
|
const model = await buildShowReportModel({
|
|
10984
11087
|
rootDir,
|
|
@@ -10998,7 +11101,7 @@ async function generateReport(rootDir, program2, templateHtml, theme = "auto") {
|
|
|
10998
11101
|
};
|
|
10999
11102
|
}
|
|
11000
11103
|
async function runReportGeneration(options) {
|
|
11001
|
-
const templateHtml = await
|
|
11104
|
+
const templateHtml = await readFile25(options.templatePath, "utf8");
|
|
11002
11105
|
const theme = options.theme ?? "auto";
|
|
11003
11106
|
const drifted = [];
|
|
11004
11107
|
const repaired = [];
|
|
@@ -11016,12 +11119,12 @@ async function runReportGeneration(options) {
|
|
|
11016
11119
|
[`${outRel}/show-report.html`, generated.html]
|
|
11017
11120
|
];
|
|
11018
11121
|
for (const [relPath, expected] of outputs) {
|
|
11019
|
-
const actual = await readOptionalFile(
|
|
11122
|
+
const actual = await readOptionalFile(join29(options.rootDir, relPath));
|
|
11020
11123
|
if (actual === null || normalizeLineEndings2(actual) !== normalizeLineEndings2(expected)) {
|
|
11021
11124
|
drifted.push(`${relPath} does not match a regeneration from its sources.`);
|
|
11022
11125
|
if (options.mode === "write") {
|
|
11023
11126
|
await mkdir8(outAbs, { recursive: true });
|
|
11024
|
-
await writeFile11(
|
|
11127
|
+
await writeFile11(join29(options.rootDir, relPath), expected, "utf8");
|
|
11025
11128
|
repaired.push(relPath);
|
|
11026
11129
|
}
|
|
11027
11130
|
}
|
|
@@ -11032,7 +11135,7 @@ async function runReportGeneration(options) {
|
|
|
11032
11135
|
|
|
11033
11136
|
// src/commands/report.ts
|
|
11034
11137
|
async function runReport(targetDir, options = {}, assetsDir) {
|
|
11035
|
-
const templatePath =
|
|
11138
|
+
const templatePath = join30(assetsDir, "report", "show-report.template.html");
|
|
11036
11139
|
try {
|
|
11037
11140
|
await access5(templatePath);
|
|
11038
11141
|
} catch {
|
|
@@ -11156,12 +11259,12 @@ function mintRef(type, generate = uuidv7) {
|
|
|
11156
11259
|
function mintSessionRef() {
|
|
11157
11260
|
return mintRef("session");
|
|
11158
11261
|
}
|
|
11159
|
-
function parseRef(
|
|
11160
|
-
if (typeof
|
|
11161
|
-
if (
|
|
11162
|
-
if (
|
|
11163
|
-
if (
|
|
11164
|
-
const parts =
|
|
11262
|
+
function parseRef(text3, expected) {
|
|
11263
|
+
if (typeof text3 !== "string") return { ok: false, reason: "a reference must be a string" };
|
|
11264
|
+
if (text3.length === 0) return { ok: false, reason: "a reference must not be empty" };
|
|
11265
|
+
if (text3 !== text3.trim()) return { ok: false, reason: "a reference must not carry surrounding whitespace" };
|
|
11266
|
+
if (text3 !== text3.toLowerCase()) return { ok: false, reason: "a reference must be lowercase" };
|
|
11267
|
+
const parts = text3.split(":");
|
|
11165
11268
|
if (parts.length !== 3 || parts[0] !== "arcane") {
|
|
11166
11269
|
return { ok: false, reason: 'a reference must have the form "arcane:<type>:<uuid>"' };
|
|
11167
11270
|
}
|
|
@@ -11398,7 +11501,7 @@ var RpcError = class extends Error {
|
|
|
11398
11501
|
function isObject(value) {
|
|
11399
11502
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11400
11503
|
}
|
|
11401
|
-
function
|
|
11504
|
+
function text2(value, isError = false) {
|
|
11402
11505
|
return { content: [{ type: "text", text: value }], isError };
|
|
11403
11506
|
}
|
|
11404
11507
|
function stringArg(args, key) {
|
|
@@ -11420,10 +11523,10 @@ function serveMcp(options) {
|
|
|
11420
11523
|
const fail = (id, error2) => write({ jsonrpc: "2.0", id, error: error2 });
|
|
11421
11524
|
const report = async (args) => {
|
|
11422
11525
|
const status = stringArg(args, "status");
|
|
11423
|
-
if (status === void 0) return
|
|
11526
|
+
if (status === void 0) return text2("status is required: one of working, waiting, blocked, idle.", true);
|
|
11424
11527
|
const needs = args["required_actions"];
|
|
11425
11528
|
if (needs !== void 0 && (!Array.isArray(needs) || needs.some((n) => typeof n !== "string"))) {
|
|
11426
|
-
return
|
|
11529
|
+
return text2("required_actions must be a list of strings.", true);
|
|
11427
11530
|
}
|
|
11428
11531
|
const flags = { status };
|
|
11429
11532
|
for (const key of ["task", "spell", "message", "agent", "harness"]) {
|
|
@@ -11431,21 +11534,21 @@ function serveMcp(options) {
|
|
|
11431
11534
|
if (v !== void 0) flags[key] = v;
|
|
11432
11535
|
}
|
|
11433
11536
|
if (Array.isArray(needs)) flags.requiredActions = needs;
|
|
11434
|
-
if (resolveEndpoint(deps) === null) return
|
|
11537
|
+
if (resolveEndpoint(deps) === null) return text2(NOT_CONFIGURED_TEXT);
|
|
11435
11538
|
const where = stringArg(args, "cwd") ?? cwd;
|
|
11436
11539
|
const outcome = await sendReport(where, flags, deps);
|
|
11437
|
-
if (outcome.kind === "refused") return
|
|
11438
|
-
if (outcome.kind === "silent") return
|
|
11439
|
-
const
|
|
11440
|
-
return { ...
|
|
11540
|
+
if (outcome.kind === "refused") return text2(outcome.line, true);
|
|
11541
|
+
if (outcome.kind === "silent") return text2(`Nothing was reported: ${where} is not inside an Arcane repository, or the service did not answer.`);
|
|
11542
|
+
const record2 = outcome.record;
|
|
11543
|
+
return { ...text2(`Reported ${record2.status} for ${record2.session}.`), structuredContent: record2 };
|
|
11441
11544
|
};
|
|
11442
11545
|
const end = async (args) => {
|
|
11443
|
-
if (resolveEndpoint(deps) === null) return
|
|
11546
|
+
if (resolveEndpoint(deps) === null) return text2(NOT_CONFIGURED_TEXT);
|
|
11444
11547
|
const where = stringArg(args, "cwd") ?? cwd;
|
|
11445
11548
|
const outcome = await sendEnd(where, deps);
|
|
11446
|
-
if (outcome.kind === "refused") return
|
|
11447
|
-
if (outcome.kind === "silent") return
|
|
11448
|
-
return { ...
|
|
11549
|
+
if (outcome.kind === "refused") return text2(outcome.line, true);
|
|
11550
|
+
if (outcome.kind === "silent") return text2(`No session was open for ${where}.`);
|
|
11551
|
+
return { ...text2(`Ended ${outcome.record.session}.`), structuredContent: outcome.record };
|
|
11449
11552
|
};
|
|
11450
11553
|
const findResource = (uri) => {
|
|
11451
11554
|
const resource = PRESENCE_RESOURCES.find((r) => r.uri === uri);
|
|
@@ -11591,7 +11694,7 @@ function serveMcp(options) {
|
|
|
11591
11694
|
if (name === "presence_report") return await report(args);
|
|
11592
11695
|
if (name === "presence_end") return await end(args);
|
|
11593
11696
|
} catch (error2) {
|
|
11594
|
-
if (error2 instanceof RpcError && error2.code === -32602) return
|
|
11697
|
+
if (error2 instanceof RpcError && error2.code === -32602) return text2(error2.message, true);
|
|
11595
11698
|
throw error2;
|
|
11596
11699
|
}
|
|
11597
11700
|
throw new RpcError(-32602, `Unknown tool: ${String(name)}`);
|
|
@@ -11742,14 +11845,14 @@ function closed(value, field, required, optional = []) {
|
|
|
11742
11845
|
if (!isObject2(value)) throw new Refusal("invalid_field", field, `${field ?? "the body"} must be an object`);
|
|
11743
11846
|
const allowed = /* @__PURE__ */ new Set([...required, ...optional]);
|
|
11744
11847
|
for (const key of Object.keys(value)) {
|
|
11745
|
-
if (!allowed.has(key)) throw new Refusal("unknown_field",
|
|
11848
|
+
if (!allowed.has(key)) throw new Refusal("unknown_field", join31(field, key), `${join31(field, key)} is not a field of this contract`);
|
|
11746
11849
|
}
|
|
11747
11850
|
for (const key of required) {
|
|
11748
|
-
if (!(key in value)) throw new Refusal("missing_field",
|
|
11851
|
+
if (!(key in value)) throw new Refusal("missing_field", join31(field, key), `${join31(field, key)} is required`);
|
|
11749
11852
|
}
|
|
11750
11853
|
return value;
|
|
11751
11854
|
}
|
|
11752
|
-
function
|
|
11855
|
+
function join31(parent, key) {
|
|
11753
11856
|
return parent === void 0 ? key : `${parent}.${key}`;
|
|
11754
11857
|
}
|
|
11755
11858
|
function nonEmpty(value, field) {
|
|
@@ -11885,10 +11988,10 @@ var JSON_HEADERS = {
|
|
|
11885
11988
|
"X-Content-Type-Options": "nosniff"
|
|
11886
11989
|
};
|
|
11887
11990
|
function send2(res, status, body, extra = {}) {
|
|
11888
|
-
const
|
|
11991
|
+
const text3 = `${JSON.stringify(body)}
|
|
11889
11992
|
`;
|
|
11890
|
-
res.writeHead(status, { ...JSON_HEADERS, "Content-Length": Buffer.byteLength(
|
|
11891
|
-
res.end(
|
|
11993
|
+
res.writeHead(status, { ...JSON_HEADERS, "Content-Length": Buffer.byteLength(text3), ...extra });
|
|
11994
|
+
res.end(text3);
|
|
11892
11995
|
}
|
|
11893
11996
|
function error(res, status, code, message, field, extra = {}) {
|
|
11894
11997
|
const body = { error: field === void 0 ? { code, message } : { code, field, message } };
|
|
@@ -11935,9 +12038,9 @@ function readBody(req, res) {
|
|
|
11935
12038
|
});
|
|
11936
12039
|
});
|
|
11937
12040
|
}
|
|
11938
|
-
function parseJson(
|
|
12041
|
+
function parseJson(text3) {
|
|
11939
12042
|
try {
|
|
11940
|
-
return { ok: true, value: JSON.parse(
|
|
12043
|
+
return { ok: true, value: JSON.parse(text3) };
|
|
11941
12044
|
} catch {
|
|
11942
12045
|
return { ok: false };
|
|
11943
12046
|
}
|
|
@@ -12051,13 +12154,13 @@ data: ${JSON.stringify(hello)}
|
|
|
12051
12154
|
if (path5 === "/v0/events") return events(req, res);
|
|
12052
12155
|
if (path5 === "/v0/sessions/{session}") {
|
|
12053
12156
|
const parsed = parseRef(found.param, "session");
|
|
12054
|
-
const
|
|
12055
|
-
if (
|
|
12056
|
-
return send2(res, 200,
|
|
12157
|
+
const record2 = parsed.ok ? table.get(found.param ?? "") : null;
|
|
12158
|
+
if (record2 === null) return error(res, 404, "not_found", "no such session");
|
|
12159
|
+
return send2(res, 200, record2);
|
|
12057
12160
|
}
|
|
12058
|
-
const
|
|
12059
|
-
if (
|
|
12060
|
-
const json = parseJson(
|
|
12161
|
+
const text3 = await readBody(req, res);
|
|
12162
|
+
if (text3 === null) return;
|
|
12163
|
+
const json = parseJson(text3);
|
|
12061
12164
|
if (!json.ok) return error(res, 400, "invalid_json", "the body is not valid JSON");
|
|
12062
12165
|
if (path5 === "/v0/sessions/report") {
|
|
12063
12166
|
const result2 = validateReport(json.value);
|
|
@@ -12066,8 +12169,8 @@ data: ${JSON.stringify(hello)}
|
|
|
12066
12169
|
if (isNew && table.count() >= MAX_SESSIONS) {
|
|
12067
12170
|
return error(res, 503, "too_many_sessions", `at most ${MAX_SESSIONS} sessions are held at once`, void 0, { "Retry-After": "60" });
|
|
12068
12171
|
}
|
|
12069
|
-
const { record, created } = table.report(result2.value);
|
|
12070
|
-
return send2(res, created ? 201 : 200,
|
|
12172
|
+
const { record: record2, created } = table.report(result2.value);
|
|
12173
|
+
return send2(res, created ? 201 : 200, record2);
|
|
12071
12174
|
}
|
|
12072
12175
|
const result = validateEnd(json.value);
|
|
12073
12176
|
if (!result.ok) return error(res, 400, result.code, result.message, result.field);
|
|
@@ -12097,8 +12200,8 @@ data: ${JSON.stringify(hello)}
|
|
|
12097
12200
|
}
|
|
12098
12201
|
|
|
12099
12202
|
// src/modules/presence/sessions.ts
|
|
12100
|
-
function copy(
|
|
12101
|
-
return structuredClone(
|
|
12203
|
+
function copy(record2) {
|
|
12204
|
+
return structuredClone(record2);
|
|
12102
12205
|
}
|
|
12103
12206
|
function sameRequiredActions(a, b) {
|
|
12104
12207
|
if (a.length !== b.length) return false;
|
|
@@ -12115,9 +12218,9 @@ function createSessionTable(options) {
|
|
|
12115
12218
|
const expireAfterMs = options.expireAfterMs ?? EXPIRE_AFTER_MS;
|
|
12116
12219
|
const entries = /* @__PURE__ */ new Map();
|
|
12117
12220
|
const listeners = /* @__PURE__ */ new Set();
|
|
12118
|
-
const emit = (name,
|
|
12119
|
-
const event = { event: name, data: copy(
|
|
12120
|
-
for (const listener of listeners) listener({ event: name, data: copy(
|
|
12221
|
+
const emit = (name, record2) => {
|
|
12222
|
+
const event = { event: name, data: copy(record2) };
|
|
12223
|
+
for (const listener of listeners) listener({ event: name, data: copy(record2) });
|
|
12121
12224
|
return event;
|
|
12122
12225
|
};
|
|
12123
12226
|
return {
|
|
@@ -12128,7 +12231,7 @@ function createSessionTable(options) {
|
|
|
12128
12231
|
const requiredActions2 = (body.required_actions ?? []).map((n) => ({ action: n.action, reason: n.reason }));
|
|
12129
12232
|
const existing = entries.get(key);
|
|
12130
12233
|
if (existing === void 0) {
|
|
12131
|
-
const
|
|
12234
|
+
const record3 = {
|
|
12132
12235
|
session: options.mint(),
|
|
12133
12236
|
agent: structuredClone(body.agent),
|
|
12134
12237
|
harness: body.harness,
|
|
@@ -12144,41 +12247,41 @@ function createSessionTable(options) {
|
|
|
12144
12247
|
ended_at: null,
|
|
12145
12248
|
stale: false
|
|
12146
12249
|
};
|
|
12147
|
-
entries.set(key, { record:
|
|
12148
|
-
emit("session.started",
|
|
12149
|
-
return { record: copy(
|
|
12250
|
+
entries.set(key, { record: record3, reportedMs: ms });
|
|
12251
|
+
emit("session.started", record3);
|
|
12252
|
+
return { record: copy(record3), created: true, event: "session.started" };
|
|
12150
12253
|
}
|
|
12151
|
-
const
|
|
12152
|
-
const wasStale =
|
|
12153
|
-
const needsChanged = !sameRequiredActions(
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
|
|
12157
|
-
|
|
12158
|
-
if (body.spell !== void 0)
|
|
12159
|
-
if (body.task !== void 0)
|
|
12160
|
-
if (body.message !== void 0)
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
|
|
12254
|
+
const record2 = existing.record;
|
|
12255
|
+
const wasStale = record2.stale;
|
|
12256
|
+
const needsChanged = !sameRequiredActions(record2.required_actions, requiredActions2);
|
|
12257
|
+
record2.agent = structuredClone(body.agent);
|
|
12258
|
+
record2.harness = body.harness;
|
|
12259
|
+
record2.repository = structuredClone(body.repository);
|
|
12260
|
+
record2.checkout = structuredClone(body.checkout);
|
|
12261
|
+
if (body.spell !== void 0) record2.spell = body.spell;
|
|
12262
|
+
if (body.task !== void 0) record2.task = body.task;
|
|
12263
|
+
if (body.message !== void 0) record2.message = body.message;
|
|
12264
|
+
record2.status = body.status;
|
|
12265
|
+
record2.required_actions = requiredActions2;
|
|
12266
|
+
record2.reported_at = at;
|
|
12267
|
+
record2.stale = false;
|
|
12165
12268
|
existing.reportedMs = ms;
|
|
12166
12269
|
const event = wasStale ? "session.resumed" : needsChanged ? "session.action_required" : "session.updated";
|
|
12167
|
-
emit(event,
|
|
12168
|
-
return { record: copy(
|
|
12270
|
+
emit(event, record2);
|
|
12271
|
+
return { record: copy(record2), created: false, event };
|
|
12169
12272
|
},
|
|
12170
12273
|
end(checkoutPath) {
|
|
12171
12274
|
const existing = entries.get(checkoutPath);
|
|
12172
12275
|
if (existing === void 0) return null;
|
|
12173
12276
|
entries.delete(checkoutPath);
|
|
12174
|
-
const
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
emit("session.ended",
|
|
12178
|
-
return copy(
|
|
12277
|
+
const record2 = existing.record;
|
|
12278
|
+
record2.status = "done";
|
|
12279
|
+
record2.ended_at = new Date(now()).toISOString();
|
|
12280
|
+
emit("session.ended", record2);
|
|
12281
|
+
return copy(record2);
|
|
12179
12282
|
},
|
|
12180
12283
|
get(session) {
|
|
12181
|
-
for (const { record } of entries.values()) if (
|
|
12284
|
+
for (const { record: record2 } of entries.values()) if (record2.session === session) return copy(record2);
|
|
12182
12285
|
return null;
|
|
12183
12286
|
},
|
|
12184
12287
|
snapshot() {
|
|
@@ -12474,7 +12577,7 @@ async function runPresenceMcp() {
|
|
|
12474
12577
|
|
|
12475
12578
|
// src/index.ts
|
|
12476
12579
|
import { realpathSync as realpathSync2 } from "fs";
|
|
12477
|
-
var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ??
|
|
12580
|
+
var ASSETS_DIR = process.env["ARCANE_ASSETS_DIR"] ?? join32(dirname15(fileURLToPath(import.meta.url)), "assets");
|
|
12478
12581
|
var program = new Command();
|
|
12479
12582
|
var USER_FLAG_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 spells shared by every repository on this machine, fanned out to ~/.agents/skills (Codex, Copilot) and ~/.claude/commands (Claude Code)";
|
|
12480
12583
|
var USER_TIER_AGENTS_DESCRIPTION = "Operate on the per-user tier at ~/.arcane \u2014 one agent roster shared by every repository on this machine, rendered to ~/.copilot/agents (VS Code agent modes)";
|
|
@@ -12693,6 +12796,7 @@ agentsCommand.command("list").description("Show the current agent roster with as
|
|
|
12693
12796
|
const scope = opts.user ? "user" : "repo";
|
|
12694
12797
|
await runAgentsList(agentsTargetDir(scope, process.cwd()), scope);
|
|
12695
12798
|
});
|
|
12799
|
+
program.command("grant").description("Read-only run-grant record checks; operator consent and execution/platform gates remain").command("check").requiredOption("--id <id>", "Run grant ID").requiredOption("--repository <repository>", "Exact repository identity recorded in scope").requiredOption("--task <task>", "Exact approved task ID").requiredOption("--branch <branch>", "Current source branch").requiredOption("--action <action>", "Action to check (for example commit or complete-pr)").action(runGrantCheck);
|
|
12696
12800
|
var presenceCommand = program.command("presence").description("The local presence channel: which session works on what, right now (ARC-060). Loopback only; nothing runs until `serve`");
|
|
12697
12801
|
presenceCommand.command("serve").description("Run the presence service in the foreground on 127.0.0.1 until Ctrl+C or `spell presence stop`").option("--port <port>", "Port on 127.0.0.1 (default 27226, or ARCANE_PRESENCE_PORT); 0 picks a free one").option("--rotate", "Replace the token in ~/.arcane/presence/token; every subscriber must re-read it").action(async (opts) => {
|
|
12698
12802
|
await runPresenceServe(opts);
|