spacecode 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +6 -4
- package/dist/index.js.map +2 -2
- package/dist/repl.mjs +370 -6
- package/package.json +1 -1
package/dist/repl.mjs
CHANGED
|
@@ -5031,6 +5031,11 @@ function createProfileClient({ baseUrl, apiKey, fetch: doFetch, timeoutMs = 8e3
|
|
|
5031
5031
|
throw err;
|
|
5032
5032
|
}
|
|
5033
5033
|
},
|
|
5034
|
+
/** Asks the server-side classifier to decide keep/on_demand/drop per section (see adapt-plan.ts). */
|
|
5035
|
+
async adapt(sections) {
|
|
5036
|
+
const res = await call("POST", "/adapt", { body: { sections } });
|
|
5037
|
+
return await res.json();
|
|
5038
|
+
},
|
|
5034
5039
|
async put(kind, name, input) {
|
|
5035
5040
|
const res = await call("PUT", `/assets/${enc(kind)}/${enc(name)}`, {
|
|
5036
5041
|
body: { content: input.content, description: input.description ?? "", base_version: input.baseVersion }
|
|
@@ -5174,6 +5179,222 @@ var init_profile_loader = __esm({
|
|
|
5174
5179
|
}
|
|
5175
5180
|
});
|
|
5176
5181
|
|
|
5182
|
+
// ../agent/src/import/instruction-sections.ts
|
|
5183
|
+
function slugify(title, taken) {
|
|
5184
|
+
const base = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "section";
|
|
5185
|
+
let slug = base;
|
|
5186
|
+
for (let n = 2; taken.has(slug); n++) slug = `${base}-${n}`;
|
|
5187
|
+
taken.add(slug);
|
|
5188
|
+
return slug;
|
|
5189
|
+
}
|
|
5190
|
+
function splitSections(text) {
|
|
5191
|
+
const lines2 = text.split("\n");
|
|
5192
|
+
const starts = [];
|
|
5193
|
+
let fence = null;
|
|
5194
|
+
lines2.forEach((line, i) => {
|
|
5195
|
+
const m2 = /^\s*(```+|~~~+)/.exec(line);
|
|
5196
|
+
if (m2) {
|
|
5197
|
+
if (fence === null) fence = m2[1][0];
|
|
5198
|
+
else if (m2[1][0] === fence) fence = null;
|
|
5199
|
+
return;
|
|
5200
|
+
}
|
|
5201
|
+
if (fence === null && /^## \S/.test(line)) starts.push(i);
|
|
5202
|
+
});
|
|
5203
|
+
if (starts.length === 0) return { preamble: text, sections: [] };
|
|
5204
|
+
const taken = /* @__PURE__ */ new Set();
|
|
5205
|
+
const sections = starts.map((start, k2) => {
|
|
5206
|
+
const end = k2 + 1 < starts.length ? starts[k2 + 1] : lines2.length;
|
|
5207
|
+
const chunk = lines2.slice(start, end).join("\n") + (end < lines2.length ? "\n" : "");
|
|
5208
|
+
const title = lines2[start].slice(3).trim();
|
|
5209
|
+
return { title, slug: slugify(title, taken), text: chunk };
|
|
5210
|
+
});
|
|
5211
|
+
const preamble = starts[0] === 0 ? "" : lines2.slice(0, starts[0]).join("\n") + "\n";
|
|
5212
|
+
return { preamble, sections };
|
|
5213
|
+
}
|
|
5214
|
+
var init_instruction_sections = __esm({
|
|
5215
|
+
"../agent/src/import/instruction-sections.ts"() {
|
|
5216
|
+
"use strict";
|
|
5217
|
+
}
|
|
5218
|
+
});
|
|
5219
|
+
|
|
5220
|
+
// ../agent/src/import/tool-names.ts
|
|
5221
|
+
function translateToolNames(text) {
|
|
5222
|
+
const counts = /* @__PURE__ */ new Map();
|
|
5223
|
+
const names = Object.keys(TOOL_NAME_MAP).join("|");
|
|
5224
|
+
const re3 = new RegExp("`(" + names + ")(\\([^`)]*\\))?`", "g");
|
|
5225
|
+
const out = text.replace(re3, (_m, name, args) => {
|
|
5226
|
+
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
5227
|
+
return "`" + TOOL_NAME_MAP[name] + (args ?? "") + "`";
|
|
5228
|
+
});
|
|
5229
|
+
return { text: out, replaced: [...counts].map(([from, count]) => ({ from, to: TOOL_NAME_MAP[from], count })) };
|
|
5230
|
+
}
|
|
5231
|
+
function findUnsupported(text) {
|
|
5232
|
+
return UNSUPPORTED.filter((u3) => u3.re.test(text)).map((u3) => u3.label);
|
|
5233
|
+
}
|
|
5234
|
+
var TOOL_NAME_MAP, UNSUPPORTED;
|
|
5235
|
+
var init_tool_names = __esm({
|
|
5236
|
+
"../agent/src/import/tool-names.ts"() {
|
|
5237
|
+
"use strict";
|
|
5238
|
+
TOOL_NAME_MAP = {
|
|
5239
|
+
Read: "read_file",
|
|
5240
|
+
Write: "write_file",
|
|
5241
|
+
Edit: "edit_file",
|
|
5242
|
+
MultiEdit: "edit_file",
|
|
5243
|
+
Glob: "list_directory",
|
|
5244
|
+
Grep: "search",
|
|
5245
|
+
Bash: "bash",
|
|
5246
|
+
TodoWrite: "todo_write"
|
|
5247
|
+
};
|
|
5248
|
+
UNSUPPORTED = [
|
|
5249
|
+
{ label: "hooks", re: /\bhooks?\b.*\.(sh|json)|\bPreToolUse\b|\bPostToolUse\b|\bSessionStart\b|\bUserPromptSubmit\b/i },
|
|
5250
|
+
{ label: "ToolSearch / deferred tools", re: /\bToolSearch\b|deferred tools?/i },
|
|
5251
|
+
{ label: "Claude Code's Task/subagent tools", re: /\bsubagents?\b|\bAgent tool\b|`Task`|subagent_type/i },
|
|
5252
|
+
{ label: "Claude Code skills / slash commands", re: /\bSkill tool\b|invoke (?:the )?\w+ skill|\/ship\b|\/handoff\b/i },
|
|
5253
|
+
{ label: "MCP tool names (mcp__*)", re: /\bmcp__[A-Za-z0-9_-]+/ },
|
|
5254
|
+
{ label: "WebFetch/WebSearch/NotebookEdit tools", re: /`(?:WebFetch|WebSearch|NotebookEdit)`/ },
|
|
5255
|
+
{ label: "plan mode / worktree tools", re: /\bEnterPlanMode\b|\bExitPlanMode\b|\bEnterWorktree\b/ }
|
|
5256
|
+
];
|
|
5257
|
+
}
|
|
5258
|
+
});
|
|
5259
|
+
|
|
5260
|
+
// ../agent/src/import/coverage.ts
|
|
5261
|
+
function extractRuleLines(text) {
|
|
5262
|
+
const out = [];
|
|
5263
|
+
let fence = false;
|
|
5264
|
+
for (const line of text.split("\n")) {
|
|
5265
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
5266
|
+
fence = !fence;
|
|
5267
|
+
continue;
|
|
5268
|
+
}
|
|
5269
|
+
if (fence) continue;
|
|
5270
|
+
const n = norm(line);
|
|
5271
|
+
if (n !== "" && RULE_RE.test(n)) out.push(n);
|
|
5272
|
+
}
|
|
5273
|
+
return out;
|
|
5274
|
+
}
|
|
5275
|
+
function checkCoverage(original, kept, dropped) {
|
|
5276
|
+
const haystack = norm(kept.join("\n"));
|
|
5277
|
+
const droppedHay = norm(dropped.join("\n"));
|
|
5278
|
+
const missing = [];
|
|
5279
|
+
const droppedRules = [];
|
|
5280
|
+
for (const rule of new Set(extractRuleLines(original))) {
|
|
5281
|
+
if (haystack.includes(rule)) continue;
|
|
5282
|
+
if (droppedHay.includes(rule)) droppedRules.push(rule);
|
|
5283
|
+
else missing.push(rule);
|
|
5284
|
+
}
|
|
5285
|
+
return { ok: missing.length === 0, missing, droppedRules };
|
|
5286
|
+
}
|
|
5287
|
+
var RULE_RE, norm;
|
|
5288
|
+
var init_coverage = __esm({
|
|
5289
|
+
"../agent/src/import/coverage.ts"() {
|
|
5290
|
+
"use strict";
|
|
5291
|
+
RULE_RE = /\b(MUST|NEVER|ALWAYS|IMPORTANT|CRITICAL|MANDATORY|DO NOT)\b|\b(never|must not|do not|don't|mandatory)\b/i;
|
|
5292
|
+
norm = (s) => s.replace(/[*_`]/g, "").replace(/\s+/g, " ").trim();
|
|
5293
|
+
}
|
|
5294
|
+
});
|
|
5295
|
+
|
|
5296
|
+
// ../agent/src/import/adapt-plan.ts
|
|
5297
|
+
function prepareAdaptation(original) {
|
|
5298
|
+
const split = splitSections(original);
|
|
5299
|
+
const replaced = /* @__PURE__ */ new Map();
|
|
5300
|
+
const tr = (t) => {
|
|
5301
|
+
const r = translateToolNames(t);
|
|
5302
|
+
for (const x2 of r.replaced) {
|
|
5303
|
+
const cur = replaced.get(x2.from);
|
|
5304
|
+
replaced.set(x2.from, { from: x2.from, to: x2.to, count: (cur?.count ?? 0) + x2.count });
|
|
5305
|
+
}
|
|
5306
|
+
return r.text;
|
|
5307
|
+
};
|
|
5308
|
+
const preamble = tr(split.preamble);
|
|
5309
|
+
const sections = split.sections.map((s) => {
|
|
5310
|
+
const text = tr(s.text);
|
|
5311
|
+
return { slug: s.slug, title: s.title, chars: text.length, unsupported: findUnsupported(text), text };
|
|
5312
|
+
});
|
|
5313
|
+
return { preamble, sections, replaced: [...replaced.values()] };
|
|
5314
|
+
}
|
|
5315
|
+
function applyPlan(input, decisions, maxCoreChars = MAX_CORE_CHARS) {
|
|
5316
|
+
const bySlug = new Map(input.sections.map((s) => [s.slug, s]));
|
|
5317
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5318
|
+
for (const d of decisions) {
|
|
5319
|
+
if (!bySlug.has(d.slug)) throw new AdaptPlanError(`plan names unknown section "${d.slug}"`);
|
|
5320
|
+
if (seen.has(d.slug)) throw new AdaptPlanError(`plan decides section "${d.slug}" twice`);
|
|
5321
|
+
seen.add(d.slug);
|
|
5322
|
+
if (d.decision === "drop" && !(d.reason && d.reason.trim())) throw new AdaptPlanError(`dropped section "${d.slug}" has no reason`);
|
|
5323
|
+
if (d.decision === "on_demand" && !(d.summary && d.summary.trim())) throw new AdaptPlanError(`on-demand section "${d.slug}" has no summary`);
|
|
5324
|
+
}
|
|
5325
|
+
const missing = input.sections.filter((s) => !seen.has(s.slug)).map((s) => s.slug);
|
|
5326
|
+
if (missing.length > 0) throw new AdaptPlanError(`plan does not decide section(s): ${missing.join(", ")}`);
|
|
5327
|
+
const kept = [];
|
|
5328
|
+
const onDemand = [];
|
|
5329
|
+
const dropped = [];
|
|
5330
|
+
const index = [];
|
|
5331
|
+
for (const d of decisions) {
|
|
5332
|
+
const s = bySlug.get(d.slug);
|
|
5333
|
+
if (d.decision === "keep") kept.push(s.text);
|
|
5334
|
+
else if (d.decision === "on_demand") {
|
|
5335
|
+
onDemand.push({ slug: s.slug, title: s.title, text: s.text });
|
|
5336
|
+
index.push(`- ${s.slug}: ${s.title} \u2014 ${d.summary.trim().replace(/\s+/g, " ")}`);
|
|
5337
|
+
} else dropped.push({ slug: s.slug, title: s.title, reason: d.reason.trim(), unsupported: s.unsupported });
|
|
5338
|
+
}
|
|
5339
|
+
const indexBlock = index.length > 0 ? `
|
|
5340
|
+
## More instructions (on demand)
|
|
5341
|
+
Use the instructions_read tool with a section name to read one before acting in that area:
|
|
5342
|
+
${index.join("\n")}
|
|
5343
|
+
` : "";
|
|
5344
|
+
const core = input.preamble + kept.join("") + indexBlock;
|
|
5345
|
+
if (core.length > maxCoreChars) throw new AdaptPlanError(`core is ${core.length} chars, over the ${maxCoreChars} budget: demote larger sections to on_demand`);
|
|
5346
|
+
const translated = input.preamble + input.sections.map((s) => s.text).join("");
|
|
5347
|
+
const coverage = checkCoverage(translated, [core, ...onDemand.map((o) => o.text)], dropped.map((d) => bySlug.get(d.slug).text));
|
|
5348
|
+
return { core, onDemand, dropped, coverage, coreChars: core.length };
|
|
5349
|
+
}
|
|
5350
|
+
var AdaptPlanError, MAX_CORE_CHARS;
|
|
5351
|
+
var init_adapt_plan = __esm({
|
|
5352
|
+
"../agent/src/import/adapt-plan.ts"() {
|
|
5353
|
+
"use strict";
|
|
5354
|
+
init_instruction_sections();
|
|
5355
|
+
init_tool_names();
|
|
5356
|
+
init_coverage();
|
|
5357
|
+
AdaptPlanError = class extends Error {
|
|
5358
|
+
constructor(message) {
|
|
5359
|
+
super(message);
|
|
5360
|
+
this.name = "AdaptPlanError";
|
|
5361
|
+
}
|
|
5362
|
+
};
|
|
5363
|
+
MAX_CORE_CHARS = 6e3;
|
|
5364
|
+
}
|
|
5365
|
+
});
|
|
5366
|
+
|
|
5367
|
+
// ../agent/src/import/instructions-tool.ts
|
|
5368
|
+
function createInstructionsReadTool(client) {
|
|
5369
|
+
return async (input) => {
|
|
5370
|
+
const name = typeof input.name === "string" ? input.name.trim().toLowerCase() : "";
|
|
5371
|
+
if (!SAFE2.test(name)) return { output: 'instructions_read needs a "name" from the on-demand list, e.g. "cloudflare-auth"', is_error: true };
|
|
5372
|
+
try {
|
|
5373
|
+
const asset = await client.get("instructions", `global.sec.${name}`);
|
|
5374
|
+
if (!asset) return { output: `No instructions section "${name}". Use a name from the on-demand list.`, is_error: true };
|
|
5375
|
+
return { output: asset.content };
|
|
5376
|
+
} catch (err) {
|
|
5377
|
+
return { output: `instructions_read failed: ${err instanceof Error ? err.message : String(err)}`, is_error: true };
|
|
5378
|
+
}
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
var INSTRUCTIONS_READ_TOOL, SAFE2;
|
|
5382
|
+
var init_instructions_tool = __esm({
|
|
5383
|
+
"../agent/src/import/instructions-tool.ts"() {
|
|
5384
|
+
"use strict";
|
|
5385
|
+
INSTRUCTIONS_READ_TOOL = {
|
|
5386
|
+
name: "instructions_read",
|
|
5387
|
+
description: `Read one on-demand section of the user's imported instructions by its name (listed under "More instructions (on demand)" in your instructions). Read the relevant section before working in that area; these are the user's standing rules.`,
|
|
5388
|
+
input_schema: {
|
|
5389
|
+
type: "object",
|
|
5390
|
+
properties: { name: { type: "string", description: 'The section name exactly as listed, e.g. "cloudflare-auth".' } },
|
|
5391
|
+
required: ["name"]
|
|
5392
|
+
}
|
|
5393
|
+
};
|
|
5394
|
+
SAFE2 = /^[a-z0-9-]{1,40}$/;
|
|
5395
|
+
}
|
|
5396
|
+
});
|
|
5397
|
+
|
|
5177
5398
|
// ../agent/src/index.ts
|
|
5178
5399
|
var init_src = __esm({
|
|
5179
5400
|
"../agent/src/index.ts"() {
|
|
@@ -5206,6 +5427,11 @@ var init_src = __esm({
|
|
|
5206
5427
|
init_profile_prompt();
|
|
5207
5428
|
init_memory_tool();
|
|
5208
5429
|
init_profile_loader();
|
|
5430
|
+
init_instruction_sections();
|
|
5431
|
+
init_tool_names();
|
|
5432
|
+
init_coverage();
|
|
5433
|
+
init_adapt_plan();
|
|
5434
|
+
init_instructions_tool();
|
|
5209
5435
|
}
|
|
5210
5436
|
});
|
|
5211
5437
|
|
|
@@ -5296,15 +5522,19 @@ async function prepareProfileUnsafe(deps) {
|
|
|
5296
5522
|
if (!result.profile) return EMPTY([`profile: not loaded (${result.error ?? "unknown error"})`]);
|
|
5297
5523
|
const key = result.profile.projectKey;
|
|
5298
5524
|
const hasMemory = result.profile.memoryIndex !== null && key !== null;
|
|
5525
|
+
const hasSections = result.profile.instructions?.includes("## More instructions (on demand)") ?? false;
|
|
5299
5526
|
const section2 = buildProfileSection({ instructions: result.profile.instructions, memoryIndex: result.profile.memoryIndex, hasMemoryTool: hasMemory });
|
|
5300
5527
|
const lines2 = [
|
|
5301
|
-
`profile: v${result.profile.version} ${result.source === "cache" ? "(STALE: served from cache)" : "(live)"}${result.profile.instructions ? ", instructions" : ""}${hasMemory ? `, memory "${key}"` : ""}${section2.truncated ? ", TRUNCATED to fit the prompt budget" : ""}`
|
|
5528
|
+
`profile: v${result.profile.version} ${result.source === "cache" ? "(STALE: served from cache)" : "(live)"}${result.profile.instructions ? ", instructions" : ""}${hasSections ? ", adapted (on-demand sections)" : ""}${hasMemory ? `, memory "${key}"` : ""}${section2.truncated ? ", TRUNCATED to fit the prompt budget" : ""}`
|
|
5302
5529
|
];
|
|
5303
5530
|
if (result.error) lines2.push(`profile warning: ${result.error}`);
|
|
5304
5531
|
return {
|
|
5305
5532
|
promptSection: section2.text,
|
|
5306
|
-
tools: hasMemory ? [MEMORY_READ_TOOL] : [],
|
|
5307
|
-
handlers:
|
|
5533
|
+
tools: [...hasMemory ? [MEMORY_READ_TOOL] : [], ...hasSections ? [INSTRUCTIONS_READ_TOOL] : []],
|
|
5534
|
+
handlers: {
|
|
5535
|
+
...hasMemory ? { memory_read: createMemoryReadTool(client, key) } : {},
|
|
5536
|
+
...hasSections ? { instructions_read: createInstructionsReadTool(client) } : {}
|
|
5537
|
+
},
|
|
5308
5538
|
statusLines: lines2
|
|
5309
5539
|
};
|
|
5310
5540
|
}
|
|
@@ -5319,6 +5549,98 @@ var init_profile_runtime = __esm({
|
|
|
5319
5549
|
}
|
|
5320
5550
|
});
|
|
5321
5551
|
|
|
5552
|
+
// src/import-adapt.ts
|
|
5553
|
+
async function sha12(text) {
|
|
5554
|
+
const d = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
|
|
5555
|
+
return [...new Uint8Array(d)].slice(0, 6).map((b2) => b2.toString(16).padStart(2, "0")).join("");
|
|
5556
|
+
}
|
|
5557
|
+
function formatAdaptReport(r, before, replaced) {
|
|
5558
|
+
const lines2 = [`Adapted instructions: ${before} chars -> core ${r.coreChars} chars + ${r.onDemand.length} on-demand section(s), ${r.dropped.length} dropped.`];
|
|
5559
|
+
for (const x2 of replaced) lines2.push(` tool name ${x2.from} -> ${x2.to} (${x2.count}x)`);
|
|
5560
|
+
for (const o of r.onDemand) lines2.push(` on demand ${o.slug} (${o.text.length} chars)`);
|
|
5561
|
+
for (const d of r.dropped) lines2.push(` DROPPED ${d.slug}: ${d.reason}${d.unsupported.length ? ` [${d.unsupported.join("; ")}]` : ""}`);
|
|
5562
|
+
if (r.coverage.droppedRules.length > 0) {
|
|
5563
|
+
lines2.push(` ${r.coverage.droppedRules.length} rule line(s) exist only in dropped sections:`);
|
|
5564
|
+
for (const rule of r.coverage.droppedRules.slice(0, 12)) lines2.push(` - ${rule.slice(0, 140)}`);
|
|
5565
|
+
if (r.coverage.droppedRules.length > 12) lines2.push(` ... and ${r.coverage.droppedRules.length - 12} more`);
|
|
5566
|
+
}
|
|
5567
|
+
return lines2;
|
|
5568
|
+
}
|
|
5569
|
+
async function adaptInstructions(deps, original) {
|
|
5570
|
+
const { client, log } = deps;
|
|
5571
|
+
const hash = await sha12(original);
|
|
5572
|
+
const current = await client.get("instructions", "global");
|
|
5573
|
+
const marker = adaptedMarker(hash);
|
|
5574
|
+
const existingOrig = await client.get("instructions", "global.orig");
|
|
5575
|
+
if (current && existingOrig && existingOrig.content === original && current.description === marker) {
|
|
5576
|
+
log(" unchanged instructions/global (already adapted from this source)");
|
|
5577
|
+
return true;
|
|
5578
|
+
}
|
|
5579
|
+
const input = prepareAdaptation(original);
|
|
5580
|
+
if (input.sections.length === 0) {
|
|
5581
|
+
log(' adapt: no "## " sections found, storing the file as is');
|
|
5582
|
+
return false;
|
|
5583
|
+
}
|
|
5584
|
+
let decisions;
|
|
5585
|
+
let model;
|
|
5586
|
+
try {
|
|
5587
|
+
const res = await client.adapt(input.sections.map((s) => ({ slug: s.slug, title: s.title, chars: s.chars, unsupported: s.unsupported, excerpt: s.text.slice(0, EXCERPT_CHARS) })));
|
|
5588
|
+
decisions = res.decisions;
|
|
5589
|
+
model = res.model;
|
|
5590
|
+
} catch (err) {
|
|
5591
|
+
log(` ADAPT FAILED ${err instanceof ProfileApiError || err instanceof Error ? err.message : String(err)}; the unadapted instructions will be imported instead`);
|
|
5592
|
+
return false;
|
|
5593
|
+
}
|
|
5594
|
+
let result;
|
|
5595
|
+
try {
|
|
5596
|
+
result = applyPlan(input, decisions);
|
|
5597
|
+
} catch (err) {
|
|
5598
|
+
if (!(err instanceof AdaptPlanError)) throw err;
|
|
5599
|
+
log(` ADAPT REJECTED ${err.message}; the unadapted instructions will be imported instead`);
|
|
5600
|
+
return false;
|
|
5601
|
+
}
|
|
5602
|
+
if (!result.coverage.ok) {
|
|
5603
|
+
log(` ADAPT REJECTED ${result.coverage.missing.length} rule line(s) would be lost, e.g.:`);
|
|
5604
|
+
for (const m2 of result.coverage.missing.slice(0, 5)) log(` - ${m2.slice(0, 140)}`);
|
|
5605
|
+
log(" The unadapted instructions will be imported instead.");
|
|
5606
|
+
return false;
|
|
5607
|
+
}
|
|
5608
|
+
log(` classifier model: ${model}`);
|
|
5609
|
+
for (const l of formatAdaptReport(result, original.length, input.replaced)) log(l);
|
|
5610
|
+
if (!deps.assumeYes && !await deps.confirm("Store the adapted instructions?")) {
|
|
5611
|
+
log(" Not stored (declined or no terminal; use --yes to skip the question). The unadapted instructions will be imported instead.");
|
|
5612
|
+
return false;
|
|
5613
|
+
}
|
|
5614
|
+
await client.put("instructions", "global.orig", { content: original, description: "Original CLAUDE.md (never loaded into the prompt)", baseVersion: existingOrig?.version ?? 0 });
|
|
5615
|
+
for (const s of result.onDemand) {
|
|
5616
|
+
const existing = await client.get("instructions", `global.sec.${s.slug}`);
|
|
5617
|
+
await client.put("instructions", `global.sec.${s.slug}`, { content: s.text, description: s.title.slice(0, 200), baseVersion: existing?.version ?? 0 });
|
|
5618
|
+
}
|
|
5619
|
+
await client.put("instructions", "global", { content: result.core, description: marker, baseVersion: current?.version ?? 0 });
|
|
5620
|
+
log(` imported instructions/global (adapted: core + ${result.onDemand.length} section(s)); original kept as instructions/global.orig`);
|
|
5621
|
+
return true;
|
|
5622
|
+
}
|
|
5623
|
+
async function restoreOriginal(client, log) {
|
|
5624
|
+
const orig = await client.get("instructions", "global.orig");
|
|
5625
|
+
if (!orig) {
|
|
5626
|
+
log("No stored original to restore (nothing was adapted).");
|
|
5627
|
+
return false;
|
|
5628
|
+
}
|
|
5629
|
+
const current = await client.get("instructions", "global");
|
|
5630
|
+
await client.put("instructions", "global", { content: orig.content, description: "Global CLAUDE.md", baseVersion: current?.version ?? 0 });
|
|
5631
|
+
log("Restored the original instructions as instructions/global.");
|
|
5632
|
+
return true;
|
|
5633
|
+
}
|
|
5634
|
+
var EXCERPT_CHARS, adaptedMarker;
|
|
5635
|
+
var init_import_adapt = __esm({
|
|
5636
|
+
"src/import-adapt.ts"() {
|
|
5637
|
+
"use strict";
|
|
5638
|
+
init_src();
|
|
5639
|
+
EXCERPT_CHARS = 1e3;
|
|
5640
|
+
adaptedMarker = (hash) => `adapted-from:${hash}`;
|
|
5641
|
+
}
|
|
5642
|
+
});
|
|
5643
|
+
|
|
5322
5644
|
// src/import-claude.ts
|
|
5323
5645
|
var import_claude_exports = {};
|
|
5324
5646
|
__export(import_claude_exports, {
|
|
@@ -5326,7 +5648,7 @@ __export(import_claude_exports, {
|
|
|
5326
5648
|
});
|
|
5327
5649
|
async function runImportClaude(deps) {
|
|
5328
5650
|
const flags = deps.argv.filter((a) => a !== "import-claude");
|
|
5329
|
-
const unknown = flags.filter((f) =>
|
|
5651
|
+
const unknown = flags.filter((f) => !["--dry-run", "--overwrite", "--adapt", "--yes", "--restore-original"].includes(f));
|
|
5330
5652
|
if (unknown.length > 0) {
|
|
5331
5653
|
deps.log(`Unknown option: ${unknown.join(" ")}
|
|
5332
5654
|
${USAGE}`);
|
|
@@ -5334,6 +5656,26 @@ ${USAGE}`);
|
|
|
5334
5656
|
}
|
|
5335
5657
|
const dryRun = flags.includes("--dry-run");
|
|
5336
5658
|
const overwrite = flags.includes("--overwrite");
|
|
5659
|
+
const adapt = flags.includes("--adapt");
|
|
5660
|
+
const restore = flags.includes("--restore-original");
|
|
5661
|
+
if (restore && (adapt || dryRun)) {
|
|
5662
|
+
deps.log(`--restore-original cannot be combined with --adapt or --dry-run
|
|
5663
|
+
${USAGE}`);
|
|
5664
|
+
return 2;
|
|
5665
|
+
}
|
|
5666
|
+
if (restore) {
|
|
5667
|
+
const apiKey2 = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
|
|
5668
|
+
if (!apiKey2) {
|
|
5669
|
+
deps.log("Not signed in. Run `spacecode login` first.");
|
|
5670
|
+
return 1;
|
|
5671
|
+
}
|
|
5672
|
+
try {
|
|
5673
|
+
return await restoreOriginal(createProfileClient({ baseUrl: backendUrl(deps.env), apiKey: apiKey2, fetch: deps.fetch }), deps.log) ? 0 : 1;
|
|
5674
|
+
} catch (err) {
|
|
5675
|
+
deps.log(`FAILED ${err instanceof Error ? err.message : String(err)}`);
|
|
5676
|
+
return 1;
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5337
5679
|
const root = await deps.gitRoot();
|
|
5338
5680
|
const projectDirs = root && root !== deps.cwd ? [deps.cwd, root] : [deps.cwd];
|
|
5339
5681
|
const report = await readClaudeAssets({ fs: deps.fs, home: deps.homedir(), projectDirs });
|
|
@@ -5360,7 +5702,18 @@ ${USAGE}`);
|
|
|
5360
5702
|
let skipped = 0;
|
|
5361
5703
|
for (const a of report.assets) {
|
|
5362
5704
|
try {
|
|
5705
|
+
if (adapt && a.kind === "instructions" && a.name === "global") {
|
|
5706
|
+
if (await adaptInstructions({ client, log: deps.log, confirm: deps.confirm, assumeYes: flags.includes("--yes") }, a.content)) {
|
|
5707
|
+
imported++;
|
|
5708
|
+
continue;
|
|
5709
|
+
}
|
|
5710
|
+
}
|
|
5363
5711
|
const existing = await client.get(a.kind, a.name);
|
|
5712
|
+
if (existing && existing.description === adaptedMarker(await sha12(a.content))) {
|
|
5713
|
+
skipped++;
|
|
5714
|
+
deps.log(` unchanged ${a.kind}/${a.name} (adapted from this source)`);
|
|
5715
|
+
continue;
|
|
5716
|
+
}
|
|
5364
5717
|
if (existing && existing.content === a.content) {
|
|
5365
5718
|
skipped++;
|
|
5366
5719
|
deps.log(` unchanged ${a.kind}/${a.name}`);
|
|
@@ -5390,7 +5743,8 @@ var init_import_claude = __esm({
|
|
|
5390
5743
|
init_src();
|
|
5391
5744
|
init_api_key();
|
|
5392
5745
|
init_profile_runtime();
|
|
5393
|
-
|
|
5746
|
+
init_import_adapt();
|
|
5747
|
+
USAGE = "Usage: spacecode import-claude [--dry-run] [--overwrite] [--adapt [--yes]] [--restore-original]";
|
|
5394
5748
|
}
|
|
5395
5749
|
});
|
|
5396
5750
|
|
|
@@ -101630,7 +101984,17 @@ if (process.argv[2] === "import-claude") {
|
|
|
101630
101984
|
cwd: process.cwd(),
|
|
101631
101985
|
gitRoot,
|
|
101632
101986
|
fetch,
|
|
101633
|
-
log: (line) => console.log(line)
|
|
101987
|
+
log: (line) => console.log(line),
|
|
101988
|
+
confirm: async (question) => {
|
|
101989
|
+
if (!process.stdin.isTTY) return false;
|
|
101990
|
+
const { createInterface } = await import("node:readline/promises");
|
|
101991
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
101992
|
+
try {
|
|
101993
|
+
return /^y(es)?$/i.test((await rl.question(`${question} [y/N] `)).trim());
|
|
101994
|
+
} finally {
|
|
101995
|
+
rl.close();
|
|
101996
|
+
}
|
|
101997
|
+
}
|
|
101634
101998
|
});
|
|
101635
101999
|
process.exit(code);
|
|
101636
102000
|
}
|