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