runcastle 1.4.6 → 1.4.7
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/bin/runcastle.js
CHANGED
|
@@ -4759,6 +4759,64 @@ var init_install_guard = __esm(() => {
|
|
|
4759
4759
|
HEREDOC_OPENER = /(?<!<)<<(?!<)(-?)[ \t]*(?:'([^'\n]+)'|"([^"\n]+)"|\\?([\w.-]+))/g;
|
|
4760
4760
|
});
|
|
4761
4761
|
|
|
4762
|
+
// src/launcher/skills-root.ts
|
|
4763
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4764
|
+
import { dirname as dirname3, join as join7, resolve as resolve6 } from "path";
|
|
4765
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4766
|
+
function resolveSkillsRoot(fromDir) {
|
|
4767
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4768
|
+
if (override) {
|
|
4769
|
+
const root = resolve6(override);
|
|
4770
|
+
if (existsSync7(join7(root, SKILLS_MARKER)))
|
|
4771
|
+
return root;
|
|
4772
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
4773
|
+
}
|
|
4774
|
+
const searched = [];
|
|
4775
|
+
let dir = fromDir;
|
|
4776
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4777
|
+
const candidate = join7(dir, "packages", "skills");
|
|
4778
|
+
searched.push(candidate);
|
|
4779
|
+
if (existsSync7(join7(candidate, SKILLS_MARKER)))
|
|
4780
|
+
return candidate;
|
|
4781
|
+
const parent = dirname3(dir);
|
|
4782
|
+
if (parent === dir)
|
|
4783
|
+
break;
|
|
4784
|
+
dir = parent;
|
|
4785
|
+
}
|
|
4786
|
+
throw new Error(`runcastle skills root not found; searched:
|
|
4787
|
+
${searched.join(`
|
|
4788
|
+
`)}`);
|
|
4789
|
+
}
|
|
4790
|
+
function resolvePluginDir(fromDir = dirname3(fileURLToPath3(import.meta.url))) {
|
|
4791
|
+
const rel = join7("packages", "skills", "packs", "runcastle");
|
|
4792
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4793
|
+
if (override) {
|
|
4794
|
+
const dir2 = join7(resolve6(override), "packs", "runcastle");
|
|
4795
|
+
if (existsSync7(dir2))
|
|
4796
|
+
return dir2;
|
|
4797
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
4798
|
+
}
|
|
4799
|
+
const searched = [];
|
|
4800
|
+
let dir = fromDir;
|
|
4801
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4802
|
+
const candidate = join7(dir, rel);
|
|
4803
|
+
searched.push(candidate);
|
|
4804
|
+
if (existsSync7(candidate))
|
|
4805
|
+
return candidate;
|
|
4806
|
+
const parent = dirname3(dir);
|
|
4807
|
+
if (parent === dir)
|
|
4808
|
+
break;
|
|
4809
|
+
dir = parent;
|
|
4810
|
+
}
|
|
4811
|
+
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
4812
|
+
${searched.join(`
|
|
4813
|
+
`)}`);
|
|
4814
|
+
}
|
|
4815
|
+
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
4816
|
+
var init_skills_root = __esm(() => {
|
|
4817
|
+
SKILLS_MARKER = join7("packs", "runcastle");
|
|
4818
|
+
});
|
|
4819
|
+
|
|
4762
4820
|
// src/launcher/runtimes/skills.ts
|
|
4763
4821
|
function skillRef(runtime, skill) {
|
|
4764
4822
|
return SPELLINGS[runtime](skill);
|
|
@@ -4814,10 +4872,10 @@ var init_skills = __esm(() => {
|
|
|
4814
4872
|
|
|
4815
4873
|
// src/launcher/artifacts.ts
|
|
4816
4874
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4817
|
-
import { fileURLToPath as
|
|
4818
|
-
import { join as
|
|
4875
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4876
|
+
import { join as join8 } from "path";
|
|
4819
4877
|
function hookClientPath() {
|
|
4820
|
-
return resolveAsset(ASSET_ENV.hookClient,
|
|
4878
|
+
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4821
4879
|
}
|
|
4822
4880
|
function serverUrlFor(config2) {
|
|
4823
4881
|
return `http://localhost:${config2.serverPort}`;
|
|
@@ -5261,6 +5319,10 @@ function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
|
5261
5319
|
].join(`
|
|
5262
5320
|
`);
|
|
5263
5321
|
}
|
|
5322
|
+
function skillPackReadRule(packDir) {
|
|
5323
|
+
const posix2 = packDir.replace(/\\/g, "/").replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`).replace(/\/+$/, "");
|
|
5324
|
+
return `Read(/${posix2}/**)`;
|
|
5325
|
+
}
|
|
5264
5326
|
function entrySkillDenyRules(kind) {
|
|
5265
5327
|
const allowed = new Set(entrySkillsFor(kind));
|
|
5266
5328
|
return ENTRY_SKILLS.filter((skill) => !allowed.has(skill)).flatMap((skill) => [
|
|
@@ -5279,7 +5341,7 @@ function sessionBashAllowRules(kind) {
|
|
|
5279
5341
|
}
|
|
5280
5342
|
return SESSION_BASH_ALLOW_RULES;
|
|
5281
5343
|
}
|
|
5282
|
-
function renderSettings(hookClient, kind) {
|
|
5344
|
+
function renderSettings(hookClient, kind, skillPackDir) {
|
|
5283
5345
|
const cmd = (event) => ({
|
|
5284
5346
|
type: "command",
|
|
5285
5347
|
command: `bun run "${hookClient}" ${event}`,
|
|
@@ -5287,7 +5349,11 @@ function renderSettings(hookClient, kind) {
|
|
|
5287
5349
|
});
|
|
5288
5350
|
return {
|
|
5289
5351
|
permissions: {
|
|
5290
|
-
allow: [
|
|
5352
|
+
allow: [
|
|
5353
|
+
...RUNCASTLE_MCP_ALLOW_RULES,
|
|
5354
|
+
...sessionBashAllowRules(kind),
|
|
5355
|
+
...skillPackDir ? [skillPackReadRule(skillPackDir)] : []
|
|
5356
|
+
],
|
|
5291
5357
|
deny: entrySkillDenyRules(kind)
|
|
5292
5358
|
},
|
|
5293
5359
|
hooks: {
|
|
@@ -5348,15 +5414,15 @@ function renderSessionPrompt(input) {
|
|
|
5348
5414
|
return renderProjectPrompt(projectBrief, runtime);
|
|
5349
5415
|
throw new Error(`session ${session.id} has no feature and no project-session brief`);
|
|
5350
5416
|
}
|
|
5351
|
-
async function writeSessionArtifacts(input) {
|
|
5417
|
+
async function writeSessionArtifacts(input, pluginDir = resolvePluginDir()) {
|
|
5352
5418
|
const { session, config: config2 } = input;
|
|
5353
5419
|
const dir = sessionDir(session.id);
|
|
5354
5420
|
mkdirSync3(dir, { recursive: true });
|
|
5355
|
-
const systemPromptPath =
|
|
5356
|
-
const settingsPath =
|
|
5357
|
-
const mcpConfigPath =
|
|
5421
|
+
const systemPromptPath = join8(dir, "system-prompt.md");
|
|
5422
|
+
const settingsPath = join8(dir, "settings.json");
|
|
5423
|
+
const mcpConfigPath = join8(dir, "mcp.json");
|
|
5358
5424
|
writeFileSync2(systemPromptPath, renderSessionPrompt(input), "utf8");
|
|
5359
|
-
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind), null, 2), "utf8");
|
|
5425
|
+
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind, pluginDir), null, 2), "utf8");
|
|
5360
5426
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
5361
5427
|
return { systemPromptPath, settingsPath, mcpConfigPath };
|
|
5362
5428
|
}
|
|
@@ -5368,6 +5434,7 @@ var init_artifacts = __esm(() => {
|
|
|
5368
5434
|
init_asset_paths();
|
|
5369
5435
|
init_edit_guard();
|
|
5370
5436
|
init_install_guard();
|
|
5437
|
+
init_skills_root();
|
|
5371
5438
|
init_skills();
|
|
5372
5439
|
RUNCASTLE_MCP_ALLOW_RULES = [
|
|
5373
5440
|
"mcp__runcastle__get_feature_context",
|
|
@@ -5416,64 +5483,6 @@ var init_artifacts = __esm(() => {
|
|
|
5416
5483
|
SESSION_START_SOURCES = ["startup", "resume", "clear", "compact", "fork"];
|
|
5417
5484
|
});
|
|
5418
5485
|
|
|
5419
|
-
// src/launcher/skills-root.ts
|
|
5420
|
-
import { existsSync as existsSync7 } from "fs";
|
|
5421
|
-
import { dirname as dirname3, join as join8, resolve as resolve6 } from "path";
|
|
5422
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5423
|
-
function resolveSkillsRoot(fromDir) {
|
|
5424
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5425
|
-
if (override) {
|
|
5426
|
-
const root = resolve6(override);
|
|
5427
|
-
if (existsSync7(join8(root, SKILLS_MARKER)))
|
|
5428
|
-
return root;
|
|
5429
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
5430
|
-
}
|
|
5431
|
-
const searched = [];
|
|
5432
|
-
let dir = fromDir;
|
|
5433
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5434
|
-
const candidate = join8(dir, "packages", "skills");
|
|
5435
|
-
searched.push(candidate);
|
|
5436
|
-
if (existsSync7(join8(candidate, SKILLS_MARKER)))
|
|
5437
|
-
return candidate;
|
|
5438
|
-
const parent = dirname3(dir);
|
|
5439
|
-
if (parent === dir)
|
|
5440
|
-
break;
|
|
5441
|
-
dir = parent;
|
|
5442
|
-
}
|
|
5443
|
-
throw new Error(`runcastle skills root not found; searched:
|
|
5444
|
-
${searched.join(`
|
|
5445
|
-
`)}`);
|
|
5446
|
-
}
|
|
5447
|
-
function resolvePluginDir(fromDir = dirname3(fileURLToPath4(import.meta.url))) {
|
|
5448
|
-
const rel = join8("packages", "skills", "packs", "runcastle");
|
|
5449
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5450
|
-
if (override) {
|
|
5451
|
-
const dir2 = join8(resolve6(override), "packs", "runcastle");
|
|
5452
|
-
if (existsSync7(dir2))
|
|
5453
|
-
return dir2;
|
|
5454
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
5455
|
-
}
|
|
5456
|
-
const searched = [];
|
|
5457
|
-
let dir = fromDir;
|
|
5458
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5459
|
-
const candidate = join8(dir, rel);
|
|
5460
|
-
searched.push(candidate);
|
|
5461
|
-
if (existsSync7(candidate))
|
|
5462
|
-
return candidate;
|
|
5463
|
-
const parent = dirname3(dir);
|
|
5464
|
-
if (parent === dir)
|
|
5465
|
-
break;
|
|
5466
|
-
dir = parent;
|
|
5467
|
-
}
|
|
5468
|
-
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
5469
|
-
${searched.join(`
|
|
5470
|
-
`)}`);
|
|
5471
|
-
}
|
|
5472
|
-
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
5473
|
-
var init_skills_root = __esm(() => {
|
|
5474
|
-
SKILLS_MARKER = join8("packs", "runcastle");
|
|
5475
|
-
});
|
|
5476
|
-
|
|
5477
5486
|
// src/launcher/runtimes/types.ts
|
|
5478
5487
|
function assertHeadlessKickoff(headless, line) {
|
|
5479
5488
|
if (headless && !line)
|
|
@@ -5544,11 +5553,12 @@ var init_claude = __esm(() => {
|
|
|
5544
5553
|
};
|
|
5545
5554
|
},
|
|
5546
5555
|
async writeArtifacts(input) {
|
|
5547
|
-
const
|
|
5556
|
+
const pluginDir = resolvePluginDir();
|
|
5557
|
+
const files = await writeSessionArtifacts({ ...input, runtime: this.id }, pluginDir);
|
|
5548
5558
|
return {
|
|
5549
5559
|
files: [files.systemPromptPath, files.settingsPath, files.mcpConfigPath],
|
|
5550
5560
|
argv: buildClaudeArgs({
|
|
5551
|
-
pluginDir
|
|
5561
|
+
pluginDir,
|
|
5552
5562
|
settingsPath: files.settingsPath,
|
|
5553
5563
|
mcpConfigPath: files.mcpConfigPath,
|
|
5554
5564
|
systemPromptPath: files.systemPromptPath,
|
|
@@ -62837,6 +62847,24 @@ async function commitDocsCheckpoint(ctx, session, summary) {
|
|
|
62837
62847
|
}
|
|
62838
62848
|
await landChatCommits(ctx, project3, feature);
|
|
62839
62849
|
}
|
|
62850
|
+
function checkpointDocsAfterReply(ctx, session, summary) {
|
|
62851
|
+
let queue = docsCheckpointQueues.get(session.worktreePath);
|
|
62852
|
+
if (!queue) {
|
|
62853
|
+
queue = createSerialQueue();
|
|
62854
|
+
docsCheckpointQueues.set(session.worktreePath, queue);
|
|
62855
|
+
}
|
|
62856
|
+
queue(() => commitDocsCheckpoint(ctx, session, summary)).catch((e) => {
|
|
62857
|
+
try {
|
|
62858
|
+
emit(ctx, requireFeatureId(session), {
|
|
62859
|
+
type: "git.commit_pending",
|
|
62860
|
+
message: `docs checkpoint failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
62861
|
+
data: { summary }
|
|
62862
|
+
});
|
|
62863
|
+
} catch (err) {
|
|
62864
|
+
console.error(`docs checkpoint failed for session ${session.id}`, e, err);
|
|
62865
|
+
}
|
|
62866
|
+
});
|
|
62867
|
+
}
|
|
62840
62868
|
function requireProject(ctx, session) {
|
|
62841
62869
|
if (!session.projectId) {
|
|
62842
62870
|
throw new GateError(`this tool belongs to a project-scoped session; this is a ${session.kind} session on a ` + "feature. Use get_feature_context and the pipeline tools for feature work.");
|
|
@@ -63577,7 +63605,7 @@ function buildMcpServer(audience) {
|
|
|
63577
63605
|
if (!rs)
|
|
63578
63606
|
return noSession();
|
|
63579
63607
|
const result = toolEmitTickets(rs.ctx, rs.session, args2);
|
|
63580
|
-
|
|
63608
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `tickets emitted (${result.stored})`);
|
|
63581
63609
|
return ok(result);
|
|
63582
63610
|
});
|
|
63583
63611
|
}
|
|
@@ -63599,7 +63627,7 @@ function buildMcpServer(audience) {
|
|
|
63599
63627
|
if (!rs)
|
|
63600
63628
|
return noSession();
|
|
63601
63629
|
const result = toolUpdateTicket(rs.ctx, rs.session, args2);
|
|
63602
|
-
|
|
63630
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} updated`);
|
|
63603
63631
|
return ok(result);
|
|
63604
63632
|
});
|
|
63605
63633
|
}
|
|
@@ -63616,7 +63644,7 @@ function buildMcpServer(audience) {
|
|
|
63616
63644
|
if (!rs)
|
|
63617
63645
|
return noSession();
|
|
63618
63646
|
const result = toolCancelTicket(rs.ctx, rs.session, args2);
|
|
63619
|
-
|
|
63647
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} cancelled`);
|
|
63620
63648
|
return ok(result);
|
|
63621
63649
|
});
|
|
63622
63650
|
}
|
|
@@ -63660,7 +63688,7 @@ function buildMcpServer(audience) {
|
|
|
63660
63688
|
return noSession();
|
|
63661
63689
|
const result = toolCompletePhase(rs.ctx, rs.session, args2);
|
|
63662
63690
|
if (result.ok) {
|
|
63663
|
-
|
|
63691
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `phase '${args2.phase}' complete`);
|
|
63664
63692
|
}
|
|
63665
63693
|
return ok(result);
|
|
63666
63694
|
});
|
|
@@ -63693,7 +63721,7 @@ async function readMcpBody(c) {
|
|
|
63693
63721
|
return;
|
|
63694
63722
|
}
|
|
63695
63723
|
}
|
|
63696
|
-
var DOCS_NOTE, TICKETS_NOTE, REVIEW_TITLE, ResolveFindingShape, ResolveFindingInput, DRAFTING_KINDS, ADRS_NOTE, FEATURE_INDEX_NOTE, FEATURE_INDEX_SHIPPED_CAP = 50, WORK_RECORD_DIGESTS_NOTE, FEATURE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
63724
|
+
var DOCS_NOTE, TICKETS_NOTE, REVIEW_TITLE, ResolveFindingShape, ResolveFindingInput, docsCheckpointQueues, DRAFTING_KINDS, ADRS_NOTE, FEATURE_INDEX_NOTE, FEATURE_INDEX_SHIPPED_CAP = 50, WORK_RECORD_DIGESTS_NOTE, FEATURE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
63697
63725
|
var init_server = __esm(() => {
|
|
63698
63726
|
init_src();
|
|
63699
63727
|
init_paths();
|
|
@@ -63712,6 +63740,7 @@ var init_server = __esm(() => {
|
|
|
63712
63740
|
init_review_findings();
|
|
63713
63741
|
init_git();
|
|
63714
63742
|
init_knowledge();
|
|
63743
|
+
init_landing_queue();
|
|
63715
63744
|
init_planning();
|
|
63716
63745
|
init_project_notes();
|
|
63717
63746
|
init_repo();
|
|
@@ -63736,6 +63765,7 @@ var init_server = __esm(() => {
|
|
|
63736
63765
|
});
|
|
63737
63766
|
}
|
|
63738
63767
|
});
|
|
63768
|
+
docsCheckpointQueues = new Map;
|
|
63739
63769
|
DRAFTING_KINDS = ["chat"];
|
|
63740
63770
|
ADRS_NOTE = "adrs[] is an index of the project\u2019s LIVE decisions (superseded ones are already omitted), not " + "their text. Read the ones your work touches with read_adr({ relPath }) \u2014 they bind you the " + "same either way. They are also plain files in this worktree at docs/adr/.";
|
|
63741
63771
|
FEATURE_INDEX_NOTE = "featureIndex lists every in-flight feature, then every draft, then the most recently shipped, " + "one `slug \u2014 title [state]` line each. A trailing `\xB7 waits on: a, b \u2713` lists the features it " + "must wait to merge (\u2713 = already shipped), and `[draft, waiting]` marks a draft that cannot " + "start until every unmarked one ships. A shipped feature\u2019s docs live at docs/features/<slug>/ " + "in this worktree; an in-flight or draft feature\u2019s docs live on an unmerged branch. Any " + "feature\u2019s brief is read_feature_brief({ slug }); what one actually did is " + "get_work_record({ featureSlug }).";
|
|
@@ -65974,8 +66004,8 @@ var init_transcripts = __esm(() => {
|
|
|
65974
66004
|
|
|
65975
66005
|
// src/services/conversations.ts
|
|
65976
66006
|
function withoutKickoff(turns, kind, runtime5) {
|
|
65977
|
-
const
|
|
65978
|
-
return turns.filter((turn) => !(turn.role === "user" && promptMatchesKickoff(kickoff, turn.text)));
|
|
66007
|
+
const kickoffs = [kickoffLineFor(kind, undefined, runtime5), selfPlanKickoffFor(runtime5)];
|
|
66008
|
+
return turns.filter((turn) => !(turn.role === "user" && kickoffs.some((kickoff) => promptMatchesKickoff(kickoff, turn.text))));
|
|
65979
66009
|
}
|
|
65980
66010
|
function promptMatchesKickoff(line, prompt) {
|
|
65981
66011
|
if (!prompt)
|
|
@@ -66069,6 +66099,7 @@ var TITLE_MAX = 60, MATCH_PREFIX = 40, INTERRUPTED = "[Request interrupted by us
|
|
|
66069
66099
|
var init_conversations = __esm(() => {
|
|
66070
66100
|
init_src();
|
|
66071
66101
|
init_sessions();
|
|
66102
|
+
init_skills();
|
|
66072
66103
|
init_transcripts();
|
|
66073
66104
|
});
|
|
66074
66105
|
|
package/index.js
CHANGED
|
@@ -4758,6 +4758,64 @@ var init_install_guard = __esm(() => {
|
|
|
4758
4758
|
HEREDOC_OPENER = /(?<!<)<<(?!<)(-?)[ \t]*(?:'([^'\n]+)'|"([^"\n]+)"|\\?([\w.-]+))/g;
|
|
4759
4759
|
});
|
|
4760
4760
|
|
|
4761
|
+
// src/launcher/skills-root.ts
|
|
4762
|
+
import { existsSync as existsSync7 } from "fs";
|
|
4763
|
+
import { dirname as dirname3, join as join7, resolve as resolve6 } from "path";
|
|
4764
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4765
|
+
function resolveSkillsRoot(fromDir) {
|
|
4766
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4767
|
+
if (override) {
|
|
4768
|
+
const root = resolve6(override);
|
|
4769
|
+
if (existsSync7(join7(root, SKILLS_MARKER)))
|
|
4770
|
+
return root;
|
|
4771
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
4772
|
+
}
|
|
4773
|
+
const searched = [];
|
|
4774
|
+
let dir = fromDir;
|
|
4775
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4776
|
+
const candidate = join7(dir, "packages", "skills");
|
|
4777
|
+
searched.push(candidate);
|
|
4778
|
+
if (existsSync7(join7(candidate, SKILLS_MARKER)))
|
|
4779
|
+
return candidate;
|
|
4780
|
+
const parent = dirname3(dir);
|
|
4781
|
+
if (parent === dir)
|
|
4782
|
+
break;
|
|
4783
|
+
dir = parent;
|
|
4784
|
+
}
|
|
4785
|
+
throw new Error(`runcastle skills root not found; searched:
|
|
4786
|
+
${searched.join(`
|
|
4787
|
+
`)}`);
|
|
4788
|
+
}
|
|
4789
|
+
function resolvePluginDir(fromDir = dirname3(fileURLToPath3(import.meta.url))) {
|
|
4790
|
+
const rel = join7("packages", "skills", "packs", "runcastle");
|
|
4791
|
+
const override = process.env[SKILLS_DIR_ENV];
|
|
4792
|
+
if (override) {
|
|
4793
|
+
const dir2 = join7(resolve6(override), "packs", "runcastle");
|
|
4794
|
+
if (existsSync7(dir2))
|
|
4795
|
+
return dir2;
|
|
4796
|
+
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
4797
|
+
}
|
|
4798
|
+
const searched = [];
|
|
4799
|
+
let dir = fromDir;
|
|
4800
|
+
for (let i = 0;i < 8; i += 1) {
|
|
4801
|
+
const candidate = join7(dir, rel);
|
|
4802
|
+
searched.push(candidate);
|
|
4803
|
+
if (existsSync7(candidate))
|
|
4804
|
+
return candidate;
|
|
4805
|
+
const parent = dirname3(dir);
|
|
4806
|
+
if (parent === dir)
|
|
4807
|
+
break;
|
|
4808
|
+
dir = parent;
|
|
4809
|
+
}
|
|
4810
|
+
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
4811
|
+
${searched.join(`
|
|
4812
|
+
`)}`);
|
|
4813
|
+
}
|
|
4814
|
+
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
4815
|
+
var init_skills_root = __esm(() => {
|
|
4816
|
+
SKILLS_MARKER = join7("packs", "runcastle");
|
|
4817
|
+
});
|
|
4818
|
+
|
|
4761
4819
|
// src/launcher/runtimes/skills.ts
|
|
4762
4820
|
function skillRef(runtime, skill) {
|
|
4763
4821
|
return SPELLINGS[runtime](skill);
|
|
@@ -4813,10 +4871,10 @@ var init_skills = __esm(() => {
|
|
|
4813
4871
|
|
|
4814
4872
|
// src/launcher/artifacts.ts
|
|
4815
4873
|
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
4816
|
-
import { fileURLToPath as
|
|
4817
|
-
import { join as
|
|
4874
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
4875
|
+
import { join as join8 } from "path";
|
|
4818
4876
|
function hookClientPath() {
|
|
4819
|
-
return resolveAsset(ASSET_ENV.hookClient,
|
|
4877
|
+
return resolveAsset(ASSET_ENV.hookClient, fileURLToPath4(new URL("./hook-client.ts", import.meta.url)));
|
|
4820
4878
|
}
|
|
4821
4879
|
function serverUrlFor(config2) {
|
|
4822
4880
|
return `http://localhost:${config2.serverPort}`;
|
|
@@ -5260,6 +5318,10 @@ function renderProjectPrompt(brief, runtime = DEFAULT_RUNTIME) {
|
|
|
5260
5318
|
].join(`
|
|
5261
5319
|
`);
|
|
5262
5320
|
}
|
|
5321
|
+
function skillPackReadRule(packDir) {
|
|
5322
|
+
const posix2 = packDir.replace(/\\/g, "/").replace(/^([A-Za-z]):/, (_, drive) => `/${drive.toLowerCase()}`).replace(/\/+$/, "");
|
|
5323
|
+
return `Read(/${posix2}/**)`;
|
|
5324
|
+
}
|
|
5263
5325
|
function entrySkillDenyRules(kind) {
|
|
5264
5326
|
const allowed = new Set(entrySkillsFor(kind));
|
|
5265
5327
|
return ENTRY_SKILLS.filter((skill) => !allowed.has(skill)).flatMap((skill) => [
|
|
@@ -5278,7 +5340,7 @@ function sessionBashAllowRules(kind) {
|
|
|
5278
5340
|
}
|
|
5279
5341
|
return SESSION_BASH_ALLOW_RULES;
|
|
5280
5342
|
}
|
|
5281
|
-
function renderSettings(hookClient, kind) {
|
|
5343
|
+
function renderSettings(hookClient, kind, skillPackDir) {
|
|
5282
5344
|
const cmd = (event) => ({
|
|
5283
5345
|
type: "command",
|
|
5284
5346
|
command: `bun run "${hookClient}" ${event}`,
|
|
@@ -5286,7 +5348,11 @@ function renderSettings(hookClient, kind) {
|
|
|
5286
5348
|
});
|
|
5287
5349
|
return {
|
|
5288
5350
|
permissions: {
|
|
5289
|
-
allow: [
|
|
5351
|
+
allow: [
|
|
5352
|
+
...RUNCASTLE_MCP_ALLOW_RULES,
|
|
5353
|
+
...sessionBashAllowRules(kind),
|
|
5354
|
+
...skillPackDir ? [skillPackReadRule(skillPackDir)] : []
|
|
5355
|
+
],
|
|
5290
5356
|
deny: entrySkillDenyRules(kind)
|
|
5291
5357
|
},
|
|
5292
5358
|
hooks: {
|
|
@@ -5347,15 +5413,15 @@ function renderSessionPrompt(input) {
|
|
|
5347
5413
|
return renderProjectPrompt(projectBrief, runtime);
|
|
5348
5414
|
throw new Error(`session ${session.id} has no feature and no project-session brief`);
|
|
5349
5415
|
}
|
|
5350
|
-
async function writeSessionArtifacts(input) {
|
|
5416
|
+
async function writeSessionArtifacts(input, pluginDir = resolvePluginDir()) {
|
|
5351
5417
|
const { session, config: config2 } = input;
|
|
5352
5418
|
const dir = sessionDir(session.id);
|
|
5353
5419
|
mkdirSync3(dir, { recursive: true });
|
|
5354
|
-
const systemPromptPath =
|
|
5355
|
-
const settingsPath =
|
|
5356
|
-
const mcpConfigPath =
|
|
5420
|
+
const systemPromptPath = join8(dir, "system-prompt.md");
|
|
5421
|
+
const settingsPath = join8(dir, "settings.json");
|
|
5422
|
+
const mcpConfigPath = join8(dir, "mcp.json");
|
|
5357
5423
|
writeFileSync2(systemPromptPath, renderSessionPrompt(input), "utf8");
|
|
5358
|
-
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind), null, 2), "utf8");
|
|
5424
|
+
writeFileSync2(settingsPath, JSON.stringify(renderSettings(hookClientPath(), session.kind, pluginDir), null, 2), "utf8");
|
|
5359
5425
|
writeFileSync2(mcpConfigPath, JSON.stringify(renderMcpConfig(session, config2), null, 2), "utf8");
|
|
5360
5426
|
return { systemPromptPath, settingsPath, mcpConfigPath };
|
|
5361
5427
|
}
|
|
@@ -5367,6 +5433,7 @@ var init_artifacts = __esm(() => {
|
|
|
5367
5433
|
init_asset_paths();
|
|
5368
5434
|
init_edit_guard();
|
|
5369
5435
|
init_install_guard();
|
|
5436
|
+
init_skills_root();
|
|
5370
5437
|
init_skills();
|
|
5371
5438
|
RUNCASTLE_MCP_ALLOW_RULES = [
|
|
5372
5439
|
"mcp__runcastle__get_feature_context",
|
|
@@ -5415,64 +5482,6 @@ var init_artifacts = __esm(() => {
|
|
|
5415
5482
|
SESSION_START_SOURCES = ["startup", "resume", "clear", "compact", "fork"];
|
|
5416
5483
|
});
|
|
5417
5484
|
|
|
5418
|
-
// src/launcher/skills-root.ts
|
|
5419
|
-
import { existsSync as existsSync7 } from "fs";
|
|
5420
|
-
import { dirname as dirname3, join as join8, resolve as resolve6 } from "path";
|
|
5421
|
-
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
5422
|
-
function resolveSkillsRoot(fromDir) {
|
|
5423
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5424
|
-
if (override) {
|
|
5425
|
-
const root = resolve6(override);
|
|
5426
|
-
if (existsSync7(join8(root, SKILLS_MARKER)))
|
|
5427
|
-
return root;
|
|
5428
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} is not a runcastle skills root (missing ${SKILLS_MARKER})`);
|
|
5429
|
-
}
|
|
5430
|
-
const searched = [];
|
|
5431
|
-
let dir = fromDir;
|
|
5432
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5433
|
-
const candidate = join8(dir, "packages", "skills");
|
|
5434
|
-
searched.push(candidate);
|
|
5435
|
-
if (existsSync7(join8(candidate, SKILLS_MARKER)))
|
|
5436
|
-
return candidate;
|
|
5437
|
-
const parent = dirname3(dir);
|
|
5438
|
-
if (parent === dir)
|
|
5439
|
-
break;
|
|
5440
|
-
dir = parent;
|
|
5441
|
-
}
|
|
5442
|
-
throw new Error(`runcastle skills root not found; searched:
|
|
5443
|
-
${searched.join(`
|
|
5444
|
-
`)}`);
|
|
5445
|
-
}
|
|
5446
|
-
function resolvePluginDir(fromDir = dirname3(fileURLToPath4(import.meta.url))) {
|
|
5447
|
-
const rel = join8("packages", "skills", "packs", "runcastle");
|
|
5448
|
-
const override = process.env[SKILLS_DIR_ENV];
|
|
5449
|
-
if (override) {
|
|
5450
|
-
const dir2 = join8(resolve6(override), "packs", "runcastle");
|
|
5451
|
-
if (existsSync7(dir2))
|
|
5452
|
-
return dir2;
|
|
5453
|
-
throw new Error(`${SKILLS_DIR_ENV}=${override} has no plugin dir at ${dir2}`);
|
|
5454
|
-
}
|
|
5455
|
-
const searched = [];
|
|
5456
|
-
let dir = fromDir;
|
|
5457
|
-
for (let i = 0;i < 8; i += 1) {
|
|
5458
|
-
const candidate = join8(dir, rel);
|
|
5459
|
-
searched.push(candidate);
|
|
5460
|
-
if (existsSync7(candidate))
|
|
5461
|
-
return candidate;
|
|
5462
|
-
const parent = dirname3(dir);
|
|
5463
|
-
if (parent === dir)
|
|
5464
|
-
break;
|
|
5465
|
-
dir = parent;
|
|
5466
|
-
}
|
|
5467
|
-
throw new Error(`runcastle plugin dir (${rel}) not found; searched:
|
|
5468
|
-
${searched.join(`
|
|
5469
|
-
`)}`);
|
|
5470
|
-
}
|
|
5471
|
-
var SKILLS_DIR_ENV = "RUNCASTLE_SKILLS_DIR", SKILLS_MARKER;
|
|
5472
|
-
var init_skills_root = __esm(() => {
|
|
5473
|
-
SKILLS_MARKER = join8("packs", "runcastle");
|
|
5474
|
-
});
|
|
5475
|
-
|
|
5476
5485
|
// src/launcher/runtimes/types.ts
|
|
5477
5486
|
function assertHeadlessKickoff(headless, line) {
|
|
5478
5487
|
if (headless && !line)
|
|
@@ -5543,11 +5552,12 @@ var init_claude = __esm(() => {
|
|
|
5543
5552
|
};
|
|
5544
5553
|
},
|
|
5545
5554
|
async writeArtifacts(input) {
|
|
5546
|
-
const
|
|
5555
|
+
const pluginDir = resolvePluginDir();
|
|
5556
|
+
const files = await writeSessionArtifacts({ ...input, runtime: this.id }, pluginDir);
|
|
5547
5557
|
return {
|
|
5548
5558
|
files: [files.systemPromptPath, files.settingsPath, files.mcpConfigPath],
|
|
5549
5559
|
argv: buildClaudeArgs({
|
|
5550
|
-
pluginDir
|
|
5560
|
+
pluginDir,
|
|
5551
5561
|
settingsPath: files.settingsPath,
|
|
5552
5562
|
mcpConfigPath: files.mcpConfigPath,
|
|
5553
5563
|
systemPromptPath: files.systemPromptPath,
|
|
@@ -62836,6 +62846,24 @@ async function commitDocsCheckpoint(ctx, session, summary) {
|
|
|
62836
62846
|
}
|
|
62837
62847
|
await landChatCommits(ctx, project3, feature);
|
|
62838
62848
|
}
|
|
62849
|
+
function checkpointDocsAfterReply(ctx, session, summary) {
|
|
62850
|
+
let queue = docsCheckpointQueues.get(session.worktreePath);
|
|
62851
|
+
if (!queue) {
|
|
62852
|
+
queue = createSerialQueue();
|
|
62853
|
+
docsCheckpointQueues.set(session.worktreePath, queue);
|
|
62854
|
+
}
|
|
62855
|
+
queue(() => commitDocsCheckpoint(ctx, session, summary)).catch((e) => {
|
|
62856
|
+
try {
|
|
62857
|
+
emit(ctx, requireFeatureId(session), {
|
|
62858
|
+
type: "git.commit_pending",
|
|
62859
|
+
message: `docs checkpoint failed: ${e instanceof Error ? e.message : String(e)}`,
|
|
62860
|
+
data: { summary }
|
|
62861
|
+
});
|
|
62862
|
+
} catch (err) {
|
|
62863
|
+
console.error(`docs checkpoint failed for session ${session.id}`, e, err);
|
|
62864
|
+
}
|
|
62865
|
+
});
|
|
62866
|
+
}
|
|
62839
62867
|
function requireProject(ctx, session) {
|
|
62840
62868
|
if (!session.projectId) {
|
|
62841
62869
|
throw new GateError(`this tool belongs to a project-scoped session; this is a ${session.kind} session on a ` + "feature. Use get_feature_context and the pipeline tools for feature work.");
|
|
@@ -63576,7 +63604,7 @@ function buildMcpServer(audience) {
|
|
|
63576
63604
|
if (!rs)
|
|
63577
63605
|
return noSession();
|
|
63578
63606
|
const result = toolEmitTickets(rs.ctx, rs.session, args2);
|
|
63579
|
-
|
|
63607
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `tickets emitted (${result.stored})`);
|
|
63580
63608
|
return ok(result);
|
|
63581
63609
|
});
|
|
63582
63610
|
}
|
|
@@ -63598,7 +63626,7 @@ function buildMcpServer(audience) {
|
|
|
63598
63626
|
if (!rs)
|
|
63599
63627
|
return noSession();
|
|
63600
63628
|
const result = toolUpdateTicket(rs.ctx, rs.session, args2);
|
|
63601
|
-
|
|
63629
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} updated`);
|
|
63602
63630
|
return ok(result);
|
|
63603
63631
|
});
|
|
63604
63632
|
}
|
|
@@ -63615,7 +63643,7 @@ function buildMcpServer(audience) {
|
|
|
63615
63643
|
if (!rs)
|
|
63616
63644
|
return noSession();
|
|
63617
63645
|
const result = toolCancelTicket(rs.ctx, rs.session, args2);
|
|
63618
|
-
|
|
63646
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `ticket ${result.ticket.seq} cancelled`);
|
|
63619
63647
|
return ok(result);
|
|
63620
63648
|
});
|
|
63621
63649
|
}
|
|
@@ -63659,7 +63687,7 @@ function buildMcpServer(audience) {
|
|
|
63659
63687
|
return noSession();
|
|
63660
63688
|
const result = toolCompletePhase(rs.ctx, rs.session, args2);
|
|
63661
63689
|
if (result.ok) {
|
|
63662
|
-
|
|
63690
|
+
checkpointDocsAfterReply(rs.ctx, rs.session, `phase '${args2.phase}' complete`);
|
|
63663
63691
|
}
|
|
63664
63692
|
return ok(result);
|
|
63665
63693
|
});
|
|
@@ -63692,7 +63720,7 @@ async function readMcpBody(c) {
|
|
|
63692
63720
|
return;
|
|
63693
63721
|
}
|
|
63694
63722
|
}
|
|
63695
|
-
var DOCS_NOTE, TICKETS_NOTE, REVIEW_TITLE, ResolveFindingShape, ResolveFindingInput, DRAFTING_KINDS, ADRS_NOTE, FEATURE_INDEX_NOTE, FEATURE_INDEX_SHIPPED_CAP = 50, WORK_RECORD_DIGESTS_NOTE, FEATURE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
63723
|
+
var DOCS_NOTE, TICKETS_NOTE, REVIEW_TITLE, ResolveFindingShape, ResolveFindingInput, docsCheckpointQueues, DRAFTING_KINDS, ADRS_NOTE, FEATURE_INDEX_NOTE, FEATURE_INDEX_SHIPPED_CAP = 50, WORK_RECORD_DIGESTS_NOTE, FEATURE_KINDS, PROJECT_KINDS, ALL_AUDIENCES, TOOL_AUDIENCES, CompletablePhase, EventType, mcp, server_default;
|
|
63696
63724
|
var init_server = __esm(() => {
|
|
63697
63725
|
init_src();
|
|
63698
63726
|
init_paths();
|
|
@@ -63711,6 +63739,7 @@ var init_server = __esm(() => {
|
|
|
63711
63739
|
init_review_findings();
|
|
63712
63740
|
init_git();
|
|
63713
63741
|
init_knowledge();
|
|
63742
|
+
init_landing_queue();
|
|
63714
63743
|
init_planning();
|
|
63715
63744
|
init_project_notes();
|
|
63716
63745
|
init_repo();
|
|
@@ -63735,6 +63764,7 @@ var init_server = __esm(() => {
|
|
|
63735
63764
|
});
|
|
63736
63765
|
}
|
|
63737
63766
|
});
|
|
63767
|
+
docsCheckpointQueues = new Map;
|
|
63738
63768
|
DRAFTING_KINDS = ["chat"];
|
|
63739
63769
|
ADRS_NOTE = "adrs[] is an index of the project\u2019s LIVE decisions (superseded ones are already omitted), not " + "their text. Read the ones your work touches with read_adr({ relPath }) \u2014 they bind you the " + "same either way. They are also plain files in this worktree at docs/adr/.";
|
|
63740
63770
|
FEATURE_INDEX_NOTE = "featureIndex lists every in-flight feature, then every draft, then the most recently shipped, " + "one `slug \u2014 title [state]` line each. A trailing `\xB7 waits on: a, b \u2713` lists the features it " + "must wait to merge (\u2713 = already shipped), and `[draft, waiting]` marks a draft that cannot " + "start until every unmarked one ships. A shipped feature\u2019s docs live at docs/features/<slug>/ " + "in this worktree; an in-flight or draft feature\u2019s docs live on an unmerged branch. Any " + "feature\u2019s brief is read_feature_brief({ slug }); what one actually did is " + "get_work_record({ featureSlug }).";
|
|
@@ -65973,8 +66003,8 @@ var init_transcripts = __esm(() => {
|
|
|
65973
66003
|
|
|
65974
66004
|
// src/services/conversations.ts
|
|
65975
66005
|
function withoutKickoff(turns, kind, runtime5) {
|
|
65976
|
-
const
|
|
65977
|
-
return turns.filter((turn) => !(turn.role === "user" && promptMatchesKickoff(kickoff, turn.text)));
|
|
66006
|
+
const kickoffs = [kickoffLineFor(kind, undefined, runtime5), selfPlanKickoffFor(runtime5)];
|
|
66007
|
+
return turns.filter((turn) => !(turn.role === "user" && kickoffs.some((kickoff) => promptMatchesKickoff(kickoff, turn.text))));
|
|
65978
66008
|
}
|
|
65979
66009
|
function promptMatchesKickoff(line, prompt) {
|
|
65980
66010
|
if (!prompt)
|
|
@@ -66068,6 +66098,7 @@ var TITLE_MAX = 60, MATCH_PREFIX = 40, INTERRUPTED = "[Request interrupted by us
|
|
|
66068
66098
|
var init_conversations = __esm(() => {
|
|
66069
66099
|
init_src();
|
|
66070
66100
|
init_sessions();
|
|
66101
|
+
init_skills();
|
|
66071
66102
|
init_transcripts();
|
|
66072
66103
|
});
|
|
66073
66104
|
|
package/package.json
CHANGED
|
@@ -51,8 +51,9 @@ This is the **self-planning path**. It applies only when your briefing says no h
|
|
|
51
51
|
- **Depth rule.** Record a decision only if it changes what the feature is or does for its users, or the human might reasonably overturn it. Anything an engineer would settle while building (wording, internal formats, test coverage) is not a question; it goes to the spec as detail.
|
|
52
52
|
- **Exactly one challenger.** When a round would be empty, run one separate agent: the Agent tool in Claude Code, `spawn_agent` in Codex. Give it only the brief and `decisions.md`. Ask it for (a) design branches never asked, within the depth rule, and (b) decisions it would have made differently, with its reason. Do not tell it what you expect to hear. Ask its branches as one more round and decide them, with whatever they open. A decision it disagrees with is marked challenged with its reason (§2), and your own answer stands. There is no second pass, and no requirement that the pass come back empty. If you have no tool that runs a separate agent, challenge your own plan as a separate pass and write in `decisions.md` that the challenge was not independent.
|
|
53
53
|
- **A refused tool is not a wait.** If a tool is refused, do not retry it and do not wait: decide on what you know, and treat that decision as a candidate to check first.
|
|
54
|
+
- **A timed-out tool is not a refused one.** A refusal is runcastle's answer; a timeout is no answer, and the call may have landed anyway. Retry it once, checking first that it did not land: `complete_phase` is safe to repeat (a step already reported comes back ok with a note), but before repeating `emit_tickets`, `update_ticket` or `cancel_ticket`, read `list_tickets` and send only what is missing. If the retry times out too, carry on and say so in the closing message. Never leave `complete_phase({ phase: "tickets" })` on a timeout: until it lands, Burn is not armed.
|
|
54
55
|
- **Check first.** Pick at most five decisions for the human to check first: guesses about the human's intent that the brief did not settle, decisions that force several others, and decisions the challenger disagreed with. They open the preview's decisions part (`./references/preview.md`).
|
|
55
|
-
- **Straight through.** Then draw the preview and run its self-check (§3, steps 1 and 2), but do not tell the human to look and do not wait: complete ideation (§4), run `/runcastle:spec`, then `/runcastle:tickets` (§5). End with one closing message (§6): the plan was made without the human, how many decisions it holds, and which are to check first.
|
|
56
|
+
- **Straight through.** Then draw the preview and run its self-check (§3, steps 1 and 2), but do not tell the human to look and do not wait: complete ideation (§4), run `/runcastle:spec`, then `/runcastle:tickets` (§5). End with one closing message (§6) that covers the plan only: the plan was made without the human, how many decisions it holds, and which are to check first, plus anything that stopped you doing the job (a refused or failed tool the plan needed). Leave out the state of tools and connectors the plan did not need.
|
|
56
57
|
|
|
57
58
|
## 2. Lock decisions incrementally
|
|
58
59
|
|
|
@@ -122,6 +123,6 @@ Do not open a new session for these. They run here, on top of everything you jus
|
|
|
122
123
|
|
|
123
124
|
> Tickets are in the runcastle UI. Review the ticket cards and click **Burn** to start the AFK agents. That is the next step — I will stop here.
|
|
124
125
|
|
|
125
|
-
On the self-planning path (§1a), the closing message also says that the plan was made without the human, how many decisions it holds, and which are to check first, by number and title. Then end the turn; the run is done.
|
|
126
|
+
On the self-planning path (§1a), the closing message also says that the plan was made without the human, how many decisions it holds, and which are to check first, by number and title. It covers the plan only. Report anything that stopped you doing the job: a tool the plan needed that was refused or failed, and what you decided without it. Do not report the state of tools and connectors the plan did not need, such as MCP servers that failed to connect or connectors waiting to be authorised: the session inherits the human's own, and they are not part of the plan. Then end the turn; the run is done.
|
|
126
127
|
|
|
127
128
|
Do **not** start burning. Burn and Merge are the only two clicks in the whole pipeline, and both belong to the human. (You do not write code here either — that rule is in your injected prompt and enforced by the edit guard, not restated as advice.)
|