@fro.bot/systematic 3.9.0 → 3.10.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/HARNESSES.md +1 -1
- package/dist/cli.js +1 -1
- package/dist/{index-hjbt4p5s.js → index-y8nc60tz.js} +297 -299
- package/dist/index.js +205 -610
- package/dist/lib/agent-overlays.d.ts +0 -1
- package/dist/lib/config-handler.d.ts +0 -3
- package/dist/lib/skill-catalog.d.ts +0 -13
- package/dist/lib/skill-tool.d.ts +0 -6
- package/dist/pi.js +173 -202
- package/package.json +3 -3
- package/dist/lib/model-availability.d.ts +0 -83
- package/dist/lib/source-model-defaults.d.ts +0 -117
package/dist/index.js
CHANGED
|
@@ -14,125 +14,18 @@ import {
|
|
|
14
14
|
loadConfig,
|
|
15
15
|
loadConfigWithSources,
|
|
16
16
|
parseFrontmatter
|
|
17
|
-
} from "./index-
|
|
17
|
+
} from "./index-y8nc60tz.js";
|
|
18
18
|
|
|
19
19
|
// src/index.ts
|
|
20
|
-
import { createHash as
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import { fileURLToPath as
|
|
20
|
+
import { createHash as createHash4 } from "crypto";
|
|
21
|
+
import fs10 from "fs";
|
|
22
|
+
import path8 from "path";
|
|
23
|
+
import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "url";
|
|
24
24
|
|
|
25
25
|
// src/lib/bootstrap.ts
|
|
26
|
-
import fs3 from "fs";
|
|
27
|
-
import os from "os";
|
|
28
|
-
import path2 from "path";
|
|
29
|
-
|
|
30
|
-
// src/lib/skill-catalog.ts
|
|
31
|
-
import { pathToFileURL } from "url";
|
|
32
|
-
|
|
33
|
-
// src/lib/skill-loader.ts
|
|
34
26
|
import fs2 from "fs";
|
|
27
|
+
import os from "os";
|
|
35
28
|
import path from "path";
|
|
36
|
-
var SKILL_PREFIX = "systematic:";
|
|
37
|
-
var SKILL_DESCRIPTION_PREFIX = "(Systematic) ";
|
|
38
|
-
function formatSkillCommandName(name2) {
|
|
39
|
-
if (name2.includes(":")) {
|
|
40
|
-
return name2;
|
|
41
|
-
}
|
|
42
|
-
return `${SKILL_PREFIX}${name2}`;
|
|
43
|
-
}
|
|
44
|
-
function formatSkillDescription(description, fallbackName) {
|
|
45
|
-
const desc = description || `${fallbackName} skill`;
|
|
46
|
-
if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
|
|
47
|
-
return desc;
|
|
48
|
-
}
|
|
49
|
-
return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
|
|
50
|
-
}
|
|
51
|
-
function wrapSkillTemplate(skillPath, body2) {
|
|
52
|
-
const skillDir = path.dirname(skillPath);
|
|
53
|
-
return `<skill-instruction>
|
|
54
|
-
Base directory for this skill: ${skillDir}/
|
|
55
|
-
File references (@path) in this skill are relative to this directory.
|
|
56
|
-
|
|
57
|
-
${body2.trim()}
|
|
58
|
-
</skill-instruction>
|
|
59
|
-
|
|
60
|
-
<user-request>
|
|
61
|
-
$ARGUMENTS
|
|
62
|
-
</user-request>`;
|
|
63
|
-
}
|
|
64
|
-
function extractSkillBody(wrappedTemplate) {
|
|
65
|
-
const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
66
|
-
return match ? match[1].trim() : wrappedTemplate;
|
|
67
|
-
}
|
|
68
|
-
function loadSkill(skillInfo) {
|
|
69
|
-
try {
|
|
70
|
-
const content = fs2.readFileSync(skillInfo.skillFile, "utf8");
|
|
71
|
-
const { body: body2 } = parseFrontmatter(content);
|
|
72
|
-
const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body2);
|
|
73
|
-
return {
|
|
74
|
-
name: skillInfo.name,
|
|
75
|
-
prefixedName: formatSkillCommandName(skillInfo.name),
|
|
76
|
-
description: formatSkillDescription(skillInfo.description, skillInfo.name),
|
|
77
|
-
path: skillInfo.path,
|
|
78
|
-
skillFile: skillInfo.skillFile,
|
|
79
|
-
wrappedTemplate,
|
|
80
|
-
disableModelInvocation: skillInfo.disableModelInvocation,
|
|
81
|
-
userInvocable: skillInfo.userInvocable,
|
|
82
|
-
subtask: skillInfo.subtask,
|
|
83
|
-
agent: skillInfo.agent,
|
|
84
|
-
model: skillInfo.model,
|
|
85
|
-
argumentHint: skillInfo.argumentHint
|
|
86
|
-
};
|
|
87
|
-
} catch {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// src/lib/skill-catalog.ts
|
|
93
|
-
function escapeXml(text) {
|
|
94
|
-
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
95
|
-
}
|
|
96
|
-
function buildCatalogEntries(options) {
|
|
97
|
-
const { bundledSkillsDir, disabledSkills } = options;
|
|
98
|
-
return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).filter((s) => s.disableModelInvocation !== true).sort((a, b) => a.name.localeCompare(b.name)).map((s) => ({
|
|
99
|
-
name: s.name,
|
|
100
|
-
prefixedName: formatSkillCommandName(s.name),
|
|
101
|
-
description: s.description,
|
|
102
|
-
path: s.path,
|
|
103
|
-
skillFile: s.skillFile
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
function renderCatalogVerbose(options) {
|
|
107
|
-
const entries = buildCatalogEntries(options);
|
|
108
|
-
if (entries.length === 0)
|
|
109
|
-
return "";
|
|
110
|
-
const skillLines = entries.flatMap((entry) => [
|
|
111
|
-
" <skill>",
|
|
112
|
-
` <name>${escapeXml(entry.prefixedName)}</name>`,
|
|
113
|
-
` <description>${escapeXml(entry.description)}</description>`,
|
|
114
|
-
` <location>${pathToFileURL(entry.path).href}</location>`,
|
|
115
|
-
" </skill>"
|
|
116
|
-
]);
|
|
117
|
-
return ["<available_skills>", ...skillLines, "</available_skills>"].join(`
|
|
118
|
-
`);
|
|
119
|
-
}
|
|
120
|
-
function renderCatalogCompact(options) {
|
|
121
|
-
const entries = buildCatalogEntries(options);
|
|
122
|
-
const heading = "## Available Systematic Skills";
|
|
123
|
-
if (entries.length === 0) {
|
|
124
|
-
return `${heading}
|
|
125
|
-
|
|
126
|
-
No Systematic skills are currently available.`;
|
|
127
|
-
}
|
|
128
|
-
const bullets = entries.map((entry) => `- ${entry.prefixedName}: ${entry.description}`).join(`
|
|
129
|
-
`);
|
|
130
|
-
return `${heading}
|
|
131
|
-
|
|
132
|
-
${bullets}`;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// src/lib/bootstrap.ts
|
|
136
29
|
var INTERNAL_AGENT_SIGNATURES = [
|
|
137
30
|
"You are a title generator",
|
|
138
31
|
"You are a helpful AI assistant tasked with summarizing conversations",
|
|
@@ -190,9 +83,9 @@ var applyBootstrapContent = (output, content) => {
|
|
|
190
83
|
${content}` : content;
|
|
191
84
|
};
|
|
192
85
|
function readHarnessProfile(bundledSkillsDir, name2) {
|
|
193
|
-
const profilePath =
|
|
86
|
+
const profilePath = path.join(bundledSkillsDir, "using-systematic/references", `${name2}-profile.md`);
|
|
194
87
|
try {
|
|
195
|
-
return
|
|
88
|
+
return fs2.readFileSync(profilePath, "utf8");
|
|
196
89
|
} catch (error) {
|
|
197
90
|
console.error(`Failed to read harness profile ${profilePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
198
91
|
return null;
|
|
@@ -216,25 +109,18 @@ function getBootstrapContent(config, deps) {
|
|
|
216
109
|
if (!config.bootstrap.enabled)
|
|
217
110
|
return null;
|
|
218
111
|
if (config.bootstrap.file) {
|
|
219
|
-
const customPath = config.bootstrap.file.startsWith("~/") ?
|
|
220
|
-
if (
|
|
221
|
-
return
|
|
112
|
+
const customPath = config.bootstrap.file.startsWith("~/") ? path.join(os.homedir(), config.bootstrap.file.slice(2)) : config.bootstrap.file;
|
|
113
|
+
if (fs2.existsSync(customPath)) {
|
|
114
|
+
return fs2.readFileSync(customPath, "utf8");
|
|
222
115
|
}
|
|
223
116
|
}
|
|
224
|
-
const usingSystematicPath =
|
|
225
|
-
if (!
|
|
117
|
+
const usingSystematicPath = path.join(bundledSkillsDir, "using-systematic/SKILL.md");
|
|
118
|
+
if (!fs2.existsSync(usingSystematicPath))
|
|
226
119
|
return null;
|
|
227
|
-
const fullContent =
|
|
120
|
+
const fullContent = fs2.readFileSync(usingSystematicPath, "utf8");
|
|
228
121
|
const { body: body2 } = parseFrontmatter(fullContent);
|
|
229
122
|
const content = body2.trim();
|
|
230
123
|
const skillUsage = usageTemplate ?? getSkillUsageTemplate();
|
|
231
|
-
const catalog = renderCatalogVerbose({
|
|
232
|
-
bundledSkillsDir,
|
|
233
|
-
disabledSkills: config.disabled_skills
|
|
234
|
-
});
|
|
235
|
-
const catalogSection = catalog.length > 0 ? `
|
|
236
|
-
|
|
237
|
-
${catalog}` : "";
|
|
238
124
|
const profileSection = profileBlock === undefined ? "" : `
|
|
239
125
|
|
|
240
126
|
${profileBlock}`;
|
|
@@ -245,222 +131,32 @@ You have access to structured engineering workflows via the Systematic plugin.
|
|
|
245
131
|
|
|
246
132
|
${content}
|
|
247
133
|
|
|
248
|
-
${skillUsage}${profileSection}
|
|
134
|
+
${skillUsage}${profileSection}
|
|
249
135
|
</SYSTEMATIC_WORKFLOWS>`;
|
|
250
136
|
}
|
|
251
137
|
|
|
252
138
|
// src/lib/config-handler.ts
|
|
253
|
-
import
|
|
254
|
-
import
|
|
255
|
-
import path6 from "path";
|
|
256
|
-
|
|
257
|
-
// src/lib/agent-overlays.ts
|
|
258
|
-
import fs4 from "fs";
|
|
139
|
+
import fs5 from "fs";
|
|
140
|
+
import os2 from "os";
|
|
259
141
|
import path4 from "path";
|
|
260
142
|
|
|
261
|
-
// src/lib/source-model-defaults.ts
|
|
262
|
-
import * as path3 from "path";
|
|
263
|
-
import { fileURLToPath } from "url";
|
|
264
|
-
var __filename2 = fileURLToPath(import.meta.url);
|
|
265
|
-
var __dirname2 = path3.dirname(__filename2);
|
|
266
|
-
var packageRoot = path3.resolve(__dirname2, "..", "..");
|
|
267
|
-
var bundledAgentsDir = path3.join(packageRoot, "agents");
|
|
268
|
-
var ProviderID = exports_external.union([
|
|
269
|
-
exports_external.literal("vercel"),
|
|
270
|
-
exports_external.literal("opencode"),
|
|
271
|
-
exports_external.literal("github-copilot"),
|
|
272
|
-
exports_external.literal("opencode-go"),
|
|
273
|
-
exports_external.literal("openai"),
|
|
274
|
-
exports_external.literal("anthropic"),
|
|
275
|
-
exports_external.literal("google")
|
|
276
|
-
]);
|
|
277
|
-
var variantSchema = exports_external.string().min(1, "variant must be a non-empty string").max(128, "variant must be at most 128 characters").regex(/^\S+$/, "variant must not contain whitespace");
|
|
278
|
-
var tableCellSchema = exports_external.string().min(1, "must be a non-empty string").regex(/^[^|\r\n]+$/, "must not contain pipe (|) or newline characters \u2014 these break Markdown table generation");
|
|
279
|
-
var ModelEntrySchema = exports_external.object({
|
|
280
|
-
model: exports_external.string().min(1, "model must be a non-empty string"),
|
|
281
|
-
variant: variantSchema.optional()
|
|
282
|
-
}).strict();
|
|
283
|
-
var ProviderEntrySchema = exports_external.object({
|
|
284
|
-
provider: ProviderID,
|
|
285
|
-
models: exports_external.array(ModelEntrySchema).min(1, "models must be non-empty \u2014 every provider entry must list at least one model")
|
|
286
|
-
}).strict().refine((entry) => {
|
|
287
|
-
const seen = new Set;
|
|
288
|
-
for (const m of entry.models) {
|
|
289
|
-
const key = `${m.model}::${m.variant ?? ""}`;
|
|
290
|
-
if (seen.has(key))
|
|
291
|
-
return false;
|
|
292
|
-
seen.add(key);
|
|
293
|
-
}
|
|
294
|
-
return true;
|
|
295
|
-
}, {
|
|
296
|
-
message: "duplicate (model, variant) pair within a provider entry \u2014 each model+variant combination must be unique"
|
|
297
|
-
});
|
|
298
|
-
var CategoryDefaultSchema = exports_external.object({
|
|
299
|
-
rationale: tableCellSchema,
|
|
300
|
-
whenToOverride: tableCellSchema.optional(),
|
|
301
|
-
providers: exports_external.array(ProviderEntrySchema).min(1, "providers must be non-empty \u2014 every category must list at least one provider")
|
|
302
|
-
}).strict().refine((cat) => {
|
|
303
|
-
const seen = new Set;
|
|
304
|
-
for (const p of cat.providers) {
|
|
305
|
-
if (seen.has(p.provider))
|
|
306
|
-
return false;
|
|
307
|
-
seen.add(p.provider);
|
|
308
|
-
}
|
|
309
|
-
return true;
|
|
310
|
-
}, {
|
|
311
|
-
message: "duplicate provider ID within a category \u2014 each provider must appear at most once per category"
|
|
312
|
-
});
|
|
313
|
-
var SourceCategoryDefaultsSchema = exports_external.record(exports_external.string(), CategoryDefaultSchema);
|
|
314
|
-
var SOURCE_CATEGORY_MODEL_DEFAULTS = {
|
|
315
|
-
design: {
|
|
316
|
-
rationale: "High-judgment UX, product, and design work benefits from a strong general reasoning model with broad creative capability.",
|
|
317
|
-
whenToOverride: "Override to a faster/cheaper model when design tasks are primarily templating or low-stakes layout work.",
|
|
318
|
-
providers: [
|
|
319
|
-
{
|
|
320
|
-
provider: "github-copilot",
|
|
321
|
-
models: [{ model: "gemini-3.1-pro-preview" }]
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
provider: "openai",
|
|
325
|
-
models: [{ model: "gpt-5.5", variant: "high" }]
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
provider: "anthropic",
|
|
329
|
-
models: [{ model: "claude-opus-4-7", variant: "max" }]
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
provider: "vercel",
|
|
333
|
-
models: [{ model: "v0-1.5-md" }]
|
|
334
|
-
}
|
|
335
|
-
]
|
|
336
|
-
},
|
|
337
|
-
"document-review": {
|
|
338
|
-
rationale: "Requirements and plan critique benefit from the strongest nuanced reasoning to surface non-obvious gaps and contradictions.",
|
|
339
|
-
providers: [
|
|
340
|
-
{
|
|
341
|
-
provider: "anthropic",
|
|
342
|
-
models: [{ model: "claude-opus-4-7", variant: "max" }]
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
provider: "openai",
|
|
346
|
-
models: [{ model: "gpt-5.5", variant: "high" }]
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
provider: "github-copilot",
|
|
350
|
-
models: [{ model: "gemini-3.1-pro-preview" }]
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
provider: "opencode",
|
|
354
|
-
models: [{ model: "claude-sonnet-4-6" }]
|
|
355
|
-
}
|
|
356
|
-
]
|
|
357
|
-
},
|
|
358
|
-
research: {
|
|
359
|
-
rationale: "Tool-heavy synthesis and source evaluation benefit from a strong general reasoning model with broad knowledge.",
|
|
360
|
-
providers: [
|
|
361
|
-
{
|
|
362
|
-
provider: "openai",
|
|
363
|
-
models: [{ model: "gpt-5.4-mini" }]
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
provider: "anthropic",
|
|
367
|
-
models: [{ model: "claude-sonnet-4-6" }]
|
|
368
|
-
},
|
|
369
|
-
{
|
|
370
|
-
provider: "github-copilot",
|
|
371
|
-
models: [{ model: "gemini-3.1-pro-preview" }]
|
|
372
|
-
},
|
|
373
|
-
{
|
|
374
|
-
provider: "opencode",
|
|
375
|
-
models: [{ model: "claude-sonnet-4-6" }]
|
|
376
|
-
}
|
|
377
|
-
]
|
|
378
|
-
},
|
|
379
|
-
review: {
|
|
380
|
-
rationale: "Code, security, and adversarial review benefits from the strongest reasoning to catch subtle bugs and security issues.",
|
|
381
|
-
whenToOverride: "Override to a faster model when review tasks are primarily style or formatting checks rather than correctness or security analysis.",
|
|
382
|
-
providers: [
|
|
383
|
-
{
|
|
384
|
-
provider: "anthropic",
|
|
385
|
-
models: [{ model: "claude-sonnet-4-6" }]
|
|
386
|
-
},
|
|
387
|
-
{
|
|
388
|
-
provider: "openai",
|
|
389
|
-
models: [{ model: "gpt-5.3-codex" }]
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
provider: "github-copilot",
|
|
393
|
-
models: [{ model: "gemini-3.1-pro-preview" }]
|
|
394
|
-
},
|
|
395
|
-
{
|
|
396
|
-
provider: "opencode",
|
|
397
|
-
models: [{ model: "claude-sonnet-4-6" }]
|
|
398
|
-
}
|
|
399
|
-
]
|
|
400
|
-
},
|
|
401
|
-
workflow: {
|
|
402
|
-
rationale: "Orchestration and bounded implementation tasks should default cheaper and faster; strong reasoning is rarely needed for routing decisions.",
|
|
403
|
-
providers: [
|
|
404
|
-
{
|
|
405
|
-
provider: "openai",
|
|
406
|
-
models: [{ model: "gpt-5.4-mini" }]
|
|
407
|
-
},
|
|
408
|
-
{
|
|
409
|
-
provider: "anthropic",
|
|
410
|
-
models: [{ model: "claude-sonnet-4-6" }]
|
|
411
|
-
},
|
|
412
|
-
{
|
|
413
|
-
provider: "opencode",
|
|
414
|
-
models: [{ model: "claude-haiku-4-5" }]
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
provider: "opencode-go",
|
|
418
|
-
models: [{ model: "claude-haiku-4-5" }]
|
|
419
|
-
}
|
|
420
|
-
]
|
|
421
|
-
}
|
|
422
|
-
};
|
|
423
|
-
function resolveSourceModel(category, availabilitySet) {
|
|
424
|
-
const categoryDefault = SOURCE_CATEGORY_MODEL_DEFAULTS[category];
|
|
425
|
-
if (!categoryDefault) {
|
|
426
|
-
throw new Error(`resolveSourceModel: unknown category "${category}". Valid categories: ${Object.keys(SOURCE_CATEGORY_MODEL_DEFAULTS).join(", ")}`);
|
|
427
|
-
}
|
|
428
|
-
for (const providerEntry of categoryDefault.providers) {
|
|
429
|
-
for (const modelEntry of providerEntry.models) {
|
|
430
|
-
const key = `${providerEntry.provider}/${modelEntry.model}`;
|
|
431
|
-
if (availabilitySet.has(key)) {
|
|
432
|
-
return {
|
|
433
|
-
provider: providerEntry.provider,
|
|
434
|
-
model: modelEntry.model,
|
|
435
|
-
variant: modelEntry.variant
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
const firstProvider = categoryDefault.providers[0];
|
|
441
|
-
const firstModel = firstProvider.models[0];
|
|
442
|
-
return {
|
|
443
|
-
provider: firstProvider.provider,
|
|
444
|
-
model: firstModel.model,
|
|
445
|
-
variant: firstModel.variant
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
|
|
449
143
|
// src/lib/agent-overlays.ts
|
|
144
|
+
import fs3 from "fs";
|
|
145
|
+
import path2 from "path";
|
|
450
146
|
function buildBundledAgentInventory(agentsDir, disabledAgents) {
|
|
451
147
|
const categories = readCategoryDirs(agentsDir);
|
|
452
148
|
const agentsByQualifiedId = {};
|
|
453
149
|
const stemCategories = new Map;
|
|
454
150
|
const disabledSet = new Set(disabledAgents);
|
|
455
151
|
for (const category of categories) {
|
|
456
|
-
for (const fileName of readMarkdownFiles(
|
|
152
|
+
for (const fileName of readMarkdownFiles(path2.join(agentsDir, category))) {
|
|
457
153
|
const key = fileName.replace(/\.md$/, "");
|
|
458
154
|
const id = `${category}/${key}`;
|
|
459
155
|
agentsByQualifiedId[id] = {
|
|
460
156
|
id,
|
|
461
157
|
key,
|
|
462
158
|
category,
|
|
463
|
-
file:
|
|
159
|
+
file: path2.join(agentsDir, category, fileName),
|
|
464
160
|
disabled: disabledSet.has(key) || disabledSet.has(id)
|
|
465
161
|
};
|
|
466
162
|
const existing = stemCategories.get(key) ?? [];
|
|
@@ -497,12 +193,6 @@ function resolveAgentOverlaySet(overlays) {
|
|
|
497
193
|
categoriesByKey: new Map(overlays.categories.map((overlay) => [overlay.key, overlay]))
|
|
498
194
|
};
|
|
499
195
|
}
|
|
500
|
-
function assertSourceCategoryModelCoverage(categories) {
|
|
501
|
-
const missingCategories = categories.filter((category) => !Object.hasOwn(SOURCE_CATEGORY_MODEL_DEFAULTS, category));
|
|
502
|
-
if (missingCategories.length > 0) {
|
|
503
|
-
throw new Error(`Source category model defaults missing intentional coverage for: ${missingCategories.join(", ")}`);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
196
|
function validateExactAgentOverlays(inventory, overlays, nativeAgents, enabledSkills) {
|
|
507
197
|
const result = [];
|
|
508
198
|
const seenTargets = new Map;
|
|
@@ -585,14 +275,14 @@ function validateOverlaySkills(overlay, skills, enabledSkills) {
|
|
|
585
275
|
}
|
|
586
276
|
function readCategoryDirs(agentsDir) {
|
|
587
277
|
try {
|
|
588
|
-
return
|
|
278
|
+
return fs3.readdirSync(agentsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
589
279
|
} catch {
|
|
590
280
|
return [];
|
|
591
281
|
}
|
|
592
282
|
}
|
|
593
283
|
function readMarkdownFiles(categoryDir) {
|
|
594
284
|
try {
|
|
595
|
-
return
|
|
285
|
+
return fs3.readdirSync(categoryDir, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort();
|
|
596
286
|
} catch {
|
|
597
287
|
return [];
|
|
598
288
|
}
|
|
@@ -604,169 +294,63 @@ function throwConfigError(sourcePath, keyPath, message) {
|
|
|
604
294
|
throw new Error(`Invalid Systematic config in ${sourcePath}: ${keyPath} ${message}`);
|
|
605
295
|
}
|
|
606
296
|
|
|
607
|
-
// src/lib/
|
|
608
|
-
import
|
|
609
|
-
import
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
function
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
var MAX_CACHE_FILE_BYTES = 16 * 1024 * 1024;
|
|
616
|
-
var DEFAULT_API_TIMEOUT_MS = 1500;
|
|
617
|
-
var MODELS_JSON_FILENAME = "models.json";
|
|
618
|
-
var availabilityCache = new WeakMap;
|
|
619
|
-
function resolveCacheDir() {
|
|
620
|
-
const xdgCacheHome = process.env.XDG_CACHE_HOME?.trim();
|
|
621
|
-
const cacheBase = xdgCacheHome && path5.isAbsolute(xdgCacheHome) ? xdgCacheHome : path5.join(os2.homedir(), ".cache");
|
|
622
|
-
return path5.join(cacheBase, "opencode");
|
|
623
|
-
}
|
|
624
|
-
function fastHash(input) {
|
|
625
|
-
return createHash("sha1").update(input).digest("hex");
|
|
626
|
-
}
|
|
627
|
-
function isProviderRecord(value) {
|
|
628
|
-
if (!isRecord(value))
|
|
629
|
-
return false;
|
|
630
|
-
for (const entry of Object.values(value)) {
|
|
631
|
-
if (!isRecord(entry))
|
|
632
|
-
return false;
|
|
633
|
-
if (!isRecord(entry.models))
|
|
634
|
-
return false;
|
|
297
|
+
// src/lib/skill-loader.ts
|
|
298
|
+
import fs4 from "fs";
|
|
299
|
+
import path3 from "path";
|
|
300
|
+
var SKILL_PREFIX = "systematic:";
|
|
301
|
+
var SKILL_DESCRIPTION_PREFIX = "(Systematic) ";
|
|
302
|
+
function formatSkillCommandName(name2) {
|
|
303
|
+
if (name2.includes(":")) {
|
|
304
|
+
return name2;
|
|
635
305
|
}
|
|
636
|
-
return
|
|
306
|
+
return `${SKILL_PREFIX}${name2}`;
|
|
637
307
|
}
|
|
638
|
-
function
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
} catch {
|
|
643
|
-
return null;
|
|
644
|
-
}
|
|
645
|
-
let raw;
|
|
646
|
-
try {
|
|
647
|
-
let stat;
|
|
648
|
-
try {
|
|
649
|
-
stat = fs5.fstatSync(fd);
|
|
650
|
-
} catch {
|
|
651
|
-
return null;
|
|
652
|
-
}
|
|
653
|
-
if (!stat.isFile())
|
|
654
|
-
return null;
|
|
655
|
-
if (stat.size === 0)
|
|
656
|
-
return null;
|
|
657
|
-
if (stat.size > MAX_CACHE_FILE_BYTES) {
|
|
658
|
-
console.warn(`[systematic] models.json at ${filePath} is ${stat.size} bytes (>${MAX_CACHE_FILE_BYTES}); treating as cache miss.`);
|
|
659
|
-
return null;
|
|
660
|
-
}
|
|
661
|
-
const buffer = Buffer.alloc(stat.size);
|
|
662
|
-
let bytesRead;
|
|
663
|
-
try {
|
|
664
|
-
bytesRead = fs5.readSync(fd, buffer, 0, stat.size, 0);
|
|
665
|
-
} catch {
|
|
666
|
-
return null;
|
|
667
|
-
}
|
|
668
|
-
if (bytesRead !== stat.size)
|
|
669
|
-
return null;
|
|
670
|
-
raw = buffer.toString("utf8");
|
|
671
|
-
} finally {
|
|
672
|
-
try {
|
|
673
|
-
fs5.closeSync(fd);
|
|
674
|
-
} catch {}
|
|
675
|
-
}
|
|
676
|
-
if (raw.trim().length === 0)
|
|
677
|
-
return null;
|
|
678
|
-
let parsed;
|
|
679
|
-
try {
|
|
680
|
-
parsed = JSON.parse(raw);
|
|
681
|
-
} catch {
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
if (!isProviderRecord(parsed)) {
|
|
685
|
-
console.warn(`[systematic] models.json schema mismatch at ${filePath}; treating as cache miss. Upstream shape may have drifted.`);
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
const result = new Set;
|
|
689
|
-
for (const [providerId, providerData] of Object.entries(parsed)) {
|
|
690
|
-
for (const modelId of Object.keys(providerData.models)) {
|
|
691
|
-
result.add(`${providerId}/${modelId}`);
|
|
692
|
-
}
|
|
308
|
+
function formatSkillDescription(description, fallbackName) {
|
|
309
|
+
const desc = description || `${fallbackName} skill`;
|
|
310
|
+
if (desc.startsWith(SKILL_DESCRIPTION_PREFIX)) {
|
|
311
|
+
return desc;
|
|
693
312
|
}
|
|
694
|
-
return
|
|
313
|
+
return `${SKILL_DESCRIPTION_PREFIX}${desc}`;
|
|
695
314
|
}
|
|
696
|
-
function
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
const defaultResult = readModelsFromCache(defaultPath);
|
|
709
|
-
if (defaultResult !== null && defaultResult.size > 0) {
|
|
710
|
-
return { status: "cache", models: defaultResult };
|
|
711
|
-
}
|
|
712
|
-
return emptyAvailability();
|
|
315
|
+
function wrapSkillTemplate(skillPath, body2) {
|
|
316
|
+
const skillDir = path3.dirname(skillPath);
|
|
317
|
+
return `<skill-instruction>
|
|
318
|
+
Base directory for this skill: ${skillDir}/
|
|
319
|
+
File references (@path) in this skill are relative to this directory.
|
|
320
|
+
|
|
321
|
+
${body2.trim()}
|
|
322
|
+
</skill-instruction>
|
|
323
|
+
|
|
324
|
+
<user-request>
|
|
325
|
+
$ARGUMENTS
|
|
326
|
+
</user-request>`;
|
|
713
327
|
}
|
|
714
|
-
function
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
for (const modelId of Object.keys(provider.models)) {
|
|
718
|
-
result.add(`${provider.id}/${modelId}`);
|
|
719
|
-
}
|
|
720
|
-
}
|
|
721
|
-
return result;
|
|
328
|
+
function extractSkillBody(wrappedTemplate) {
|
|
329
|
+
const match = wrappedTemplate.match(/<skill-instruction>([\s\S]*?)<\/skill-instruction>/);
|
|
330
|
+
return match ? match[1].trim() : wrappedTemplate;
|
|
722
331
|
}
|
|
723
|
-
|
|
724
|
-
const cached = availabilityCache.get(client);
|
|
725
|
-
if (cached !== undefined) {
|
|
726
|
-
return cached;
|
|
727
|
-
}
|
|
728
|
-
const cacheAndReturn = (envelope) => {
|
|
729
|
-
if (envelope.status !== "unknown") {
|
|
730
|
-
availabilityCache.set(client, envelope);
|
|
731
|
-
}
|
|
732
|
-
return envelope;
|
|
733
|
-
};
|
|
734
|
-
const timeoutMs = options.apiTimeoutMs === undefined ? DEFAULT_API_TIMEOUT_MS : options.apiTimeoutMs;
|
|
735
|
-
if (typeof client.config?.providers !== "function") {
|
|
736
|
-
return cacheAndReturn(readFallbackCache());
|
|
737
|
-
}
|
|
738
|
-
const apiCall = client.config.providers();
|
|
739
|
-
let response;
|
|
332
|
+
function loadSkill(skillInfo) {
|
|
740
333
|
try {
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
334
|
+
const content = fs4.readFileSync(skillInfo.skillFile, "utf8");
|
|
335
|
+
const { body: body2 } = parseFrontmatter(content);
|
|
336
|
+
const wrappedTemplate = wrapSkillTemplate(skillInfo.skillFile, body2);
|
|
337
|
+
return {
|
|
338
|
+
name: skillInfo.name,
|
|
339
|
+
prefixedName: formatSkillCommandName(skillInfo.name),
|
|
340
|
+
description: formatSkillDescription(skillInfo.description, skillInfo.name),
|
|
341
|
+
path: skillInfo.path,
|
|
342
|
+
skillFile: skillInfo.skillFile,
|
|
343
|
+
wrappedTemplate,
|
|
344
|
+
disableModelInvocation: skillInfo.disableModelInvocation,
|
|
345
|
+
userInvocable: skillInfo.userInvocable,
|
|
346
|
+
subtask: skillInfo.subtask,
|
|
347
|
+
agent: skillInfo.agent,
|
|
348
|
+
model: skillInfo.model,
|
|
349
|
+
argumentHint: skillInfo.argumentHint
|
|
350
|
+
};
|
|
756
351
|
} catch {
|
|
757
|
-
return
|
|
758
|
-
}
|
|
759
|
-
if (response.error !== undefined || response.data === undefined) {
|
|
760
|
-
return cacheAndReturn(readFallbackCache());
|
|
761
|
-
}
|
|
762
|
-
const models = buildSetFromProviders(response.data.providers);
|
|
763
|
-
if (models.size === 0) {
|
|
764
|
-
return cacheAndReturn(emptyAvailability());
|
|
352
|
+
return null;
|
|
765
353
|
}
|
|
766
|
-
return cacheAndReturn({
|
|
767
|
-
status: "api",
|
|
768
|
-
models
|
|
769
|
-
});
|
|
770
354
|
}
|
|
771
355
|
|
|
772
356
|
// src/lib/config-handler.ts
|
|
@@ -805,7 +389,7 @@ function formatAgentDescription(name2, description) {
|
|
|
805
389
|
}
|
|
806
390
|
function loadAgentAsConfig(agentInfo) {
|
|
807
391
|
try {
|
|
808
|
-
const content =
|
|
392
|
+
const content = fs5.readFileSync(agentInfo.file, "utf8");
|
|
809
393
|
const {
|
|
810
394
|
description,
|
|
811
395
|
prompt,
|
|
@@ -854,7 +438,7 @@ function loadAgentAsConfig(agentInfo) {
|
|
|
854
438
|
}
|
|
855
439
|
function loadCommandAsConfig(commandInfo) {
|
|
856
440
|
try {
|
|
857
|
-
const content =
|
|
441
|
+
const content = fs5.readFileSync(commandInfo.file, "utf8");
|
|
858
442
|
const { name: name2, description, agent, model, subtask } = extractCommandFrontmatter(content);
|
|
859
443
|
const { body: body2 } = parseFrontmatter(content);
|
|
860
444
|
const cleanName = commandInfo.name.replace(/^\//, "");
|
|
@@ -894,7 +478,7 @@ function loadSkillAsCommand(loaded) {
|
|
|
894
478
|
config.subtask = loaded.subtask;
|
|
895
479
|
return config;
|
|
896
480
|
}
|
|
897
|
-
function collectAgents(dir, disabledAgents, nativeAgents, overlays
|
|
481
|
+
function collectAgents(dir, disabledAgents, nativeAgents, overlays) {
|
|
898
482
|
const agents = {};
|
|
899
483
|
const agentList = findAgentsInDir(dir);
|
|
900
484
|
const disabledSet = new Set(disabledAgents);
|
|
@@ -909,12 +493,12 @@ function collectAgents(dir, disabledAgents, nativeAgents, overlays, availability
|
|
|
909
493
|
continue;
|
|
910
494
|
const config = loadAgentAsConfig(agentInfo);
|
|
911
495
|
if (config) {
|
|
912
|
-
agents[agentInfo.name] = applyAgentOverlays(config, agentInfo, overlays
|
|
496
|
+
agents[agentInfo.name] = applyAgentOverlays(config, agentInfo, overlays);
|
|
913
497
|
}
|
|
914
498
|
}
|
|
915
499
|
return agents;
|
|
916
500
|
}
|
|
917
|
-
function applyAgentOverlays(config, agentInfo, overlays
|
|
501
|
+
function applyAgentOverlays(config, agentInfo, overlays) {
|
|
918
502
|
const id = agentInfo.category ? `${agentInfo.category}/${agentInfo.name}` : agentInfo.name;
|
|
919
503
|
const categoryOverlay = agentInfo.category ? overlays.categoriesByKey.get(agentInfo.category) : undefined;
|
|
920
504
|
const exactOverlay = overlays.agentsByTargetId.get(id);
|
|
@@ -924,23 +508,11 @@ function applyAgentOverlays(config, agentInfo, overlays, availabilitySet) {
|
|
|
924
508
|
if (hasPermissionOverlay && isRecord(config.permission)) {
|
|
925
509
|
addPermissionRules(permissionRules, config.permission);
|
|
926
510
|
}
|
|
927
|
-
applySourceModelDefault(result, agentInfo, availabilitySet);
|
|
928
511
|
applyAgentOverlay(result, categoryOverlay?.value, permissionRules);
|
|
929
512
|
applyAgentOverlay(result, exactOverlay?.value, permissionRules);
|
|
930
513
|
applyPermissionOverlay(result, permissionRules, hasPermissionOverlay);
|
|
931
514
|
return result;
|
|
932
515
|
}
|
|
933
|
-
function applySourceModelDefault(target, agentInfo, availabilitySet) {
|
|
934
|
-
if (!agentInfo.category || availabilitySet === undefined)
|
|
935
|
-
return;
|
|
936
|
-
const resolved = resolveSourceModel(agentInfo.category, availabilitySet);
|
|
937
|
-
target.model = `${resolved.provider}/${resolved.model}`;
|
|
938
|
-
if (resolved.variant !== undefined) {
|
|
939
|
-
target.variant = resolved.variant;
|
|
940
|
-
} else {
|
|
941
|
-
delete target.variant;
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
516
|
function applyAgentOverlay(target, overlay, permissionRules) {
|
|
945
517
|
if (overlay === undefined)
|
|
946
518
|
return;
|
|
@@ -1113,9 +685,9 @@ function collectEnabledSkillNames(dir, disabledSkills) {
|
|
|
1113
685
|
return findSkillsInDir(dir).filter((skillInfo) => !disabledSet.has(skillInfo.name)).map((skillInfo) => skillInfo.name);
|
|
1114
686
|
}
|
|
1115
687
|
function createConfigHandler(deps) {
|
|
1116
|
-
const { directory, bundledSkillsDir, bundledAgentsDir
|
|
1117
|
-
const homeDir = deps.homeDir ??
|
|
1118
|
-
const opencodeConfigDir = deps.opencodeConfigDir ??
|
|
688
|
+
const { directory, bundledSkillsDir, bundledAgentsDir, bundledCommandsDir } = deps;
|
|
689
|
+
const homeDir = deps.homeDir ?? os2.homedir();
|
|
690
|
+
const opencodeConfigDir = deps.opencodeConfigDir ?? path4.join(homeDir, ".config/opencode");
|
|
1119
691
|
const opencodeConfigDirOverride = process.env.OPENCODE_CONFIG_DIR?.trim() ? process.env.OPENCODE_CONFIG_DIR : undefined;
|
|
1120
692
|
return async (config) => {
|
|
1121
693
|
const { config: systematicConfig, overlays } = loadConfigWithSources(directory);
|
|
@@ -1124,10 +696,7 @@ function createConfigHandler(deps) {
|
|
|
1124
696
|
const nativeAgents = Object.fromEntries(Object.entries(existingAgents).filter(([, agent]) => !isSystematicAgentConfig(agent)));
|
|
1125
697
|
const bundledSkills = collectSkillsAsCommands(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
1126
698
|
const enabledSkillNames = collectEnabledSkillNames(bundledSkillsDir, systematicConfig.disabled_skills);
|
|
1127
|
-
const inventory = buildBundledAgentInventory(
|
|
1128
|
-
const availability = deps.client ? await getAvailableModels(deps.client) : undefined;
|
|
1129
|
-
const availabilitySet = availability && availability.status !== "unknown" ? availability.models : undefined;
|
|
1130
|
-
assertSourceCategoryModelCoverage(inventory.categories);
|
|
699
|
+
const inventory = buildBundledAgentInventory(bundledAgentsDir, systematicConfig.disabled_agents);
|
|
1131
700
|
const validatedOverlays = validateAgentOverlays({
|
|
1132
701
|
inventory,
|
|
1133
702
|
overlays,
|
|
@@ -1135,7 +704,7 @@ function createConfigHandler(deps) {
|
|
|
1135
704
|
enabledSkills: enabledSkillNames
|
|
1136
705
|
});
|
|
1137
706
|
const resolvedOverlays = resolveAgentOverlaySet(validatedOverlays);
|
|
1138
|
-
const bundledAgents = collectAgents(
|
|
707
|
+
const bundledAgents = collectAgents(bundledAgentsDir, systematicConfig.disabled_agents, nativeAgents, resolvedOverlays);
|
|
1139
708
|
const bundledCommands = collectCommands(bundledCommandsDir, systematicConfig.disabled_commands);
|
|
1140
709
|
const discoveredSkillCommands = systematicConfig.skills_as_commands !== false ? collectDiscoveredSkillsAsCommands(directory, homeDir, opencodeConfigDir, opencodeConfigDirOverride, systematicConfig.disabled_commands) : {};
|
|
1141
710
|
const bundledAgentKeys = new Set(Object.keys(bundledAgents));
|
|
@@ -1161,21 +730,21 @@ function registerSkillsPaths(config, skillsDir) {
|
|
|
1161
730
|
};
|
|
1162
731
|
}
|
|
1163
732
|
function removeSystematicSkillPaths(paths) {
|
|
1164
|
-
return paths.filter((
|
|
733
|
+
return paths.filter((path5) => !isSystematicSkillPath(path5));
|
|
1165
734
|
}
|
|
1166
|
-
function isSystematicSkillPath(
|
|
1167
|
-
const normalizedPath = normalizePath(
|
|
735
|
+
function isSystematicSkillPath(path5) {
|
|
736
|
+
const normalizedPath = normalizePath(path5);
|
|
1168
737
|
return normalizedPath.endsWith("/.config/opencode/systematic/skills") || normalizedPath.endsWith("/.cache/opencode/systematic/skills") || normalizedPath.endsWith("/.local/share/opencode/systematic/skills") || normalizedPath.endsWith("/.opencode/systematic/skills") || /(?:^|\/)\.cache\/opencode\/packages\/@fro\.bot\/systematic@[^/]+\/node_modules\/@fro\.bot\/systematic\/skills(?:$|\/)/u.test(normalizedPath);
|
|
1169
738
|
}
|
|
1170
|
-
function normalizePath(
|
|
1171
|
-
return
|
|
739
|
+
function normalizePath(path5) {
|
|
740
|
+
return path5.replaceAll("\\", "/").replace(/\/+$/u, "");
|
|
1172
741
|
}
|
|
1173
742
|
|
|
1174
743
|
// src/lib/opencode-operation-observer.ts
|
|
1175
744
|
import { spawnSync } from "child_process";
|
|
1176
|
-
import { createHash
|
|
1177
|
-
import
|
|
1178
|
-
import
|
|
745
|
+
import { createHash } from "crypto";
|
|
746
|
+
import fs6 from "fs";
|
|
747
|
+
import path5 from "path";
|
|
1179
748
|
var DEFAULT_LIMITS = {
|
|
1180
749
|
maxCommandOutputBytes: 1048576,
|
|
1181
750
|
maxCommandTimeoutMs: 5000,
|
|
@@ -1196,7 +765,7 @@ function unavailable(result) {
|
|
|
1196
765
|
return { status: "unavailable", reasonCode: result.reasonCode };
|
|
1197
766
|
}
|
|
1198
767
|
function digest(domain, facts) {
|
|
1199
|
-
const hash =
|
|
768
|
+
const hash = createHash("sha256");
|
|
1200
769
|
hash.update(`systematic:opencode-observer:${domain}:v1\x00`);
|
|
1201
770
|
for (const fact of facts) {
|
|
1202
771
|
hash.update(fact);
|
|
@@ -1287,11 +856,11 @@ function parseStageEntries(output, maxPathBytes) {
|
|
|
1287
856
|
return entries.sort((left, right) => left.relativePath.localeCompare(right.relativePath));
|
|
1288
857
|
}
|
|
1289
858
|
function safePath(root, relativePath) {
|
|
1290
|
-
if (
|
|
859
|
+
if (path5.isAbsolute(relativePath))
|
|
1291
860
|
return;
|
|
1292
|
-
const absolutePath =
|
|
1293
|
-
const relative =
|
|
1294
|
-
if (relative === "" || relative === ".." || relative.startsWith(`..${
|
|
861
|
+
const absolutePath = path5.resolve(root, relativePath);
|
|
862
|
+
const relative = path5.relative(root, absolutePath);
|
|
863
|
+
if (relative === "" || relative === ".." || relative.startsWith(`..${path5.sep}`)) {
|
|
1295
864
|
return;
|
|
1296
865
|
}
|
|
1297
866
|
return absolutePath;
|
|
@@ -1314,25 +883,25 @@ function runCommand(runner, args2, cwd, limits) {
|
|
|
1314
883
|
}
|
|
1315
884
|
return { status: "ok", output: { stdout: result.stdout } };
|
|
1316
885
|
}
|
|
1317
|
-
function canonicalPath(filePath, realPath =
|
|
886
|
+
function canonicalPath(filePath, realPath = fs6.realpathSync) {
|
|
1318
887
|
try {
|
|
1319
888
|
return realPath(filePath);
|
|
1320
889
|
} catch {
|
|
1321
890
|
return;
|
|
1322
891
|
}
|
|
1323
892
|
}
|
|
1324
|
-
function requiredGitPath(runner, args2, cwd, limits, realPath =
|
|
893
|
+
function requiredGitPath(runner, args2, cwd, limits, realPath = fs6.realpathSync) {
|
|
1325
894
|
const result = runCommand(runner, args2, cwd, limits);
|
|
1326
895
|
if (result.status === "error")
|
|
1327
896
|
return result;
|
|
1328
897
|
const rawValue = result.output.stdout.trim();
|
|
1329
|
-
if (!
|
|
898
|
+
if (!path5.isAbsolute(rawValue)) {
|
|
1330
899
|
return { status: "error", reasonCode: "target-unavailable" };
|
|
1331
900
|
}
|
|
1332
901
|
const value = canonicalPath(rawValue, realPath);
|
|
1333
902
|
return value === undefined ? { status: "error", reasonCode: "target-unavailable" } : { status: "ok", value };
|
|
1334
903
|
}
|
|
1335
|
-
function captureParentIdentity(targetDirectory, runner, limits, realPath =
|
|
904
|
+
function captureParentIdentity(targetDirectory, runner, limits, realPath = fs6.realpathSync) {
|
|
1336
905
|
const targetRoot = requiredGitPath(runner, ["rev-parse", "--show-toplevel"], targetDirectory, limits, realPath);
|
|
1337
906
|
if (targetRoot.status === "error")
|
|
1338
907
|
return targetRoot;
|
|
@@ -1360,53 +929,53 @@ function captureParentIdentity(targetDirectory, runner, limits, realPath = fs7.r
|
|
|
1360
929
|
}
|
|
1361
930
|
} : { status: "error", reasonCode: "target-unavailable" };
|
|
1362
931
|
}
|
|
1363
|
-
function readGitfileTarget(gitfilePath, realPath =
|
|
932
|
+
function readGitfileTarget(gitfilePath, realPath = fs6.realpathSync) {
|
|
1364
933
|
let contents;
|
|
1365
934
|
try {
|
|
1366
|
-
contents =
|
|
935
|
+
contents = fs6.readFileSync(gitfilePath, "utf8");
|
|
1367
936
|
} catch {
|
|
1368
937
|
return;
|
|
1369
938
|
}
|
|
1370
939
|
const match = /^gitdir:\s*(.+?)\s*$/im.exec(contents);
|
|
1371
940
|
if (!match)
|
|
1372
941
|
return;
|
|
1373
|
-
const target =
|
|
942
|
+
const target = path5.isAbsolute(match[1]) ? match[1] : path5.resolve(path5.dirname(gitfilePath), match[1]);
|
|
1374
943
|
return canonicalPath(target, realPath);
|
|
1375
944
|
}
|
|
1376
945
|
function readGitdirBacklink(backlinkPath, realPath) {
|
|
1377
946
|
let contents;
|
|
1378
947
|
try {
|
|
1379
|
-
contents =
|
|
948
|
+
contents = fs6.readFileSync(backlinkPath, "utf8").trim();
|
|
1380
949
|
} catch {
|
|
1381
950
|
return;
|
|
1382
951
|
}
|
|
1383
952
|
if (contents.length === 0)
|
|
1384
953
|
return;
|
|
1385
|
-
const target =
|
|
954
|
+
const target = path5.isAbsolute(contents) ? contents : path5.resolve(path5.dirname(backlinkPath), contents);
|
|
1386
955
|
return canonicalPath(target, realPath);
|
|
1387
956
|
}
|
|
1388
957
|
function isPathWithin(root, candidate) {
|
|
1389
|
-
const relative =
|
|
1390
|
-
return relative !== "" && relative !== ".." && !relative.startsWith(`..${
|
|
958
|
+
const relative = path5.relative(root, candidate);
|
|
959
|
+
return relative !== "" && relative !== ".." && !relative.startsWith(`..${path5.sep}`) && !path5.isAbsolute(relative);
|
|
1391
960
|
}
|
|
1392
961
|
function validateDotGitLinkage(targetRoot, gitDir, commonDir, realPath) {
|
|
1393
|
-
const dotGit =
|
|
962
|
+
const dotGit = path5.join(targetRoot, ".git");
|
|
1394
963
|
let dotGitStat;
|
|
1395
964
|
try {
|
|
1396
|
-
dotGitStat =
|
|
965
|
+
dotGitStat = fs6.lstatSync(dotGit);
|
|
1397
966
|
} catch {
|
|
1398
967
|
return false;
|
|
1399
968
|
}
|
|
1400
969
|
if (dotGitStat.isDirectory()) {
|
|
1401
970
|
return gitDir === commonDir && canonicalPath(dotGit, realPath) === commonDir;
|
|
1402
971
|
}
|
|
1403
|
-
if (!dotGitStat.isFile() || dotGitStat.isSymbolicLink() || !isPathWithin(
|
|
972
|
+
if (!dotGitStat.isFile() || dotGitStat.isSymbolicLink() || !isPathWithin(path5.join(commonDir, "worktrees"), gitDir) || readGitfileTarget(dotGit, realPath) !== gitDir) {
|
|
1404
973
|
return false;
|
|
1405
974
|
}
|
|
1406
|
-
const backlinkPath =
|
|
975
|
+
const backlinkPath = path5.join(gitDir, "gitdir");
|
|
1407
976
|
let backlinkStat;
|
|
1408
977
|
try {
|
|
1409
|
-
backlinkStat =
|
|
978
|
+
backlinkStat = fs6.lstatSync(backlinkPath);
|
|
1410
979
|
} catch {
|
|
1411
980
|
return false;
|
|
1412
981
|
}
|
|
@@ -1415,7 +984,7 @@ function validateDotGitLinkage(targetRoot, gitDir, commonDir, realPath) {
|
|
|
1415
984
|
function validateRegisteredWorktree(candidateDirectory, parentIdentity, options = {}) {
|
|
1416
985
|
const runner = options.commandRunner ?? defaultCommandRunner;
|
|
1417
986
|
const limits = mergeLimits(options.limits);
|
|
1418
|
-
const realPath = options.realPath ??
|
|
987
|
+
const realPath = options.realPath ?? fs6.realpathSync;
|
|
1419
988
|
const candidateRoot = canonicalPath(candidateDirectory, realPath);
|
|
1420
989
|
if (candidateRoot === undefined) {
|
|
1421
990
|
return { status: "error", reasonCode: "target-unavailable" };
|
|
@@ -1582,7 +1151,7 @@ function readWorktreeRevisionV2(runner, fileReader, symlinkReader, statReader, c
|
|
|
1582
1151
|
if (paths.length > limits.maxFiles) {
|
|
1583
1152
|
return { status: "error", reasonCode: "file-limit" };
|
|
1584
1153
|
}
|
|
1585
|
-
const hash =
|
|
1154
|
+
const hash = createHash("sha256");
|
|
1586
1155
|
hash.update("systematic:opencode-observer:worktree:v2\x00");
|
|
1587
1156
|
let totalBytes = 0;
|
|
1588
1157
|
for (const relativePath of paths) {
|
|
@@ -1800,19 +1369,19 @@ function createOpencodeOperationObserver(options) {
|
|
|
1800
1369
|
const limits = mergeLimits(options.limits);
|
|
1801
1370
|
let targetDirectory;
|
|
1802
1371
|
try {
|
|
1803
|
-
targetDirectory = (options.realPath ??
|
|
1372
|
+
targetDirectory = (options.realPath ?? fs6.realpathSync)(options.targetDirectory);
|
|
1804
1373
|
} catch {
|
|
1805
|
-
targetDirectory =
|
|
1374
|
+
targetDirectory = path5.resolve(options.targetDirectory);
|
|
1806
1375
|
}
|
|
1807
1376
|
const runner = options.commandRunner ?? defaultCommandRunner;
|
|
1808
1377
|
const remoteRunner = options.remoteCommandRunner ?? defaultRemoteCommandRunner;
|
|
1809
|
-
const realPath = options.realPath ??
|
|
1378
|
+
const realPath = options.realPath ?? fs6.realpathSync;
|
|
1810
1379
|
const parentIdentityResult = captureParentIdentity(targetDirectory, runner, limits, realPath);
|
|
1811
1380
|
const targetDigest = digest("target", [targetDirectory]);
|
|
1812
|
-
const fileReader = options.fileReader ??
|
|
1813
|
-
const symlinkReader = options.symlinkReader ??
|
|
1381
|
+
const fileReader = options.fileReader ?? fs6.readFileSync;
|
|
1382
|
+
const symlinkReader = options.symlinkReader ?? fs6.readlinkSync;
|
|
1814
1383
|
const statReader = options.statReader ?? ((filePath) => {
|
|
1815
|
-
const stat =
|
|
1384
|
+
const stat = fs6.lstatSync(filePath);
|
|
1816
1385
|
return {
|
|
1817
1386
|
isFile: stat.isFile(),
|
|
1818
1387
|
isSymbolicLink: stat.isSymbolicLink(),
|
|
@@ -1875,11 +1444,11 @@ function createOpencodeOperationObserver(options) {
|
|
|
1875
1444
|
|
|
1876
1445
|
// src/lib/opencode-workflow-guard.ts
|
|
1877
1446
|
import { randomBytes as randomBytes4 } from "crypto";
|
|
1878
|
-
import
|
|
1879
|
-
import
|
|
1447
|
+
import fs8 from "fs";
|
|
1448
|
+
import path6 from "path";
|
|
1880
1449
|
|
|
1881
1450
|
// src/lib/question-attestation.ts
|
|
1882
|
-
import { createHash as
|
|
1451
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
1883
1452
|
var CANONICAL_QUESTION_WORDING = "Confirm the requested guarded transition.";
|
|
1884
1453
|
var MAX_ID_LENGTH = 128;
|
|
1885
1454
|
var MAX_RESOURCE_LENGTH = 512;
|
|
@@ -1910,7 +1479,7 @@ function isQuestionPurpose(value) {
|
|
|
1910
1479
|
return value === "transition" || value === "session-disablement";
|
|
1911
1480
|
}
|
|
1912
1481
|
function digestResource(resource) {
|
|
1913
|
-
return `sha256:${
|
|
1482
|
+
return `sha256:${createHash2("sha256").update(`systematic-question-attestation:v1:resource:${resource}`, "utf8").digest("hex")}`;
|
|
1914
1483
|
}
|
|
1915
1484
|
function bindingKey(input, resourceDigest) {
|
|
1916
1485
|
return [
|
|
@@ -2396,7 +1965,7 @@ function createQuestionAttestation(options = {}) {
|
|
|
2396
1965
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2397
1966
|
|
|
2398
1967
|
// src/lib/receipt-readback.ts
|
|
2399
|
-
import { createHash as
|
|
1968
|
+
import { createHash as createHash3 } from "crypto";
|
|
2400
1969
|
var RECEIPT_READBACK_SCHEMA_VERSION = 2;
|
|
2401
1970
|
var RECEIPT_READBACK_PROTOCOL_VERSION = 2;
|
|
2402
1971
|
var LEGACY_RECEIPT_READBACK_SCHEMA_VERSION = 1;
|
|
@@ -2567,7 +2136,7 @@ function sessionSaltHex(value) {
|
|
|
2567
2136
|
}
|
|
2568
2137
|
function digestReceiptIdentity(domain, identity, sessionSalt) {
|
|
2569
2138
|
const salt = sessionSaltHex(sessionSalt) ?? "invalid-salt";
|
|
2570
|
-
return
|
|
2139
|
+
return createHash3("sha256").update(`systematic/receipt-ledger/${domain}/v1/${salt}/${identity}`).digest("hex");
|
|
2571
2140
|
}
|
|
2572
2141
|
function decodeSessionSalt(value) {
|
|
2573
2142
|
return Uint8Array.from(Buffer.from(value, "hex"));
|
|
@@ -2730,7 +2299,7 @@ function serializeEnvelope(envelope) {
|
|
|
2730
2299
|
});
|
|
2731
2300
|
}
|
|
2732
2301
|
function hashIntegrity(scope, value) {
|
|
2733
|
-
return
|
|
2302
|
+
return createHash3("sha256").update(`systematic/receipt-readback/${scope}/v1/${value}`).digest("hex");
|
|
2734
2303
|
}
|
|
2735
2304
|
function mintIntegrity(envelope, sessionSalt) {
|
|
2736
2305
|
return hashIntegrity("mint", `${sessionSalt}/${serializeEnvelope(envelope)}`);
|
|
@@ -4416,8 +3985,8 @@ function normalizeCapabilities(input) {
|
|
|
4416
3985
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
4417
3986
|
|
|
4418
3987
|
// src/lib/receipt-classifier.ts
|
|
4419
|
-
import
|
|
4420
|
-
import { fileURLToPath
|
|
3988
|
+
import fs7 from "fs";
|
|
3989
|
+
import { fileURLToPath } from "url";
|
|
4421
3990
|
|
|
4422
3991
|
// node_modules/.bun/web-tree-sitter@0.25.10/node_modules/web-tree-sitter/tree-sitter.js
|
|
4423
3992
|
var __defProp = Object.defineProperty;
|
|
@@ -5853,8 +5422,8 @@ var Module2 = (() => {
|
|
|
5853
5422
|
var moduleRtn;
|
|
5854
5423
|
var Module = moduleArg;
|
|
5855
5424
|
var readyPromiseResolve, readyPromiseReject;
|
|
5856
|
-
var readyPromise = new Promise((
|
|
5857
|
-
readyPromiseResolve =
|
|
5425
|
+
var readyPromise = new Promise((resolve, reject) => {
|
|
5426
|
+
readyPromiseResolve = resolve;
|
|
5858
5427
|
readyPromiseReject = reject;
|
|
5859
5428
|
});
|
|
5860
5429
|
var ENVIRONMENT_IS_WEB = typeof window == "object";
|
|
@@ -5876,11 +5445,11 @@ var Module2 = (() => {
|
|
|
5876
5445
|
throw toThrow;
|
|
5877
5446
|
}, "quit_");
|
|
5878
5447
|
var scriptDirectory = "";
|
|
5879
|
-
function locateFile(
|
|
5448
|
+
function locateFile(path6) {
|
|
5880
5449
|
if (Module["locateFile"]) {
|
|
5881
|
-
return Module["locateFile"](
|
|
5450
|
+
return Module["locateFile"](path6, scriptDirectory);
|
|
5882
5451
|
}
|
|
5883
|
-
return scriptDirectory +
|
|
5452
|
+
return scriptDirectory + path6;
|
|
5884
5453
|
}
|
|
5885
5454
|
__name(locateFile, "locateFile");
|
|
5886
5455
|
var readAsync, readBinary;
|
|
@@ -5934,13 +5503,13 @@ var Module2 = (() => {
|
|
|
5934
5503
|
}
|
|
5935
5504
|
readAsync = /* @__PURE__ */ __name(async (url) => {
|
|
5936
5505
|
if (isFileURI(url)) {
|
|
5937
|
-
return new Promise((
|
|
5506
|
+
return new Promise((resolve, reject) => {
|
|
5938
5507
|
var xhr = new XMLHttpRequest;
|
|
5939
5508
|
xhr.open("GET", url, true);
|
|
5940
5509
|
xhr.responseType = "arraybuffer";
|
|
5941
5510
|
xhr.onload = () => {
|
|
5942
5511
|
if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
|
|
5943
|
-
|
|
5512
|
+
resolve(xhr.response);
|
|
5944
5513
|
return;
|
|
5945
5514
|
}
|
|
5946
5515
|
reject(xhr.status);
|
|
@@ -6160,10 +5729,10 @@ var Module2 = (() => {
|
|
|
6160
5729
|
__name(receiveInstantiationResult, "receiveInstantiationResult");
|
|
6161
5730
|
var info2 = getWasmImports();
|
|
6162
5731
|
if (Module["instantiateWasm"]) {
|
|
6163
|
-
return new Promise((
|
|
5732
|
+
return new Promise((resolve, reject) => {
|
|
6164
5733
|
Module["instantiateWasm"](info2, (mod, inst) => {
|
|
6165
5734
|
receiveInstance(mod, inst);
|
|
6166
|
-
|
|
5735
|
+
resolve(mod.exports);
|
|
6167
5736
|
});
|
|
6168
5737
|
});
|
|
6169
5738
|
}
|
|
@@ -7954,7 +7523,7 @@ function matchesToolIdentity(tool, executable) {
|
|
|
7954
7523
|
async function initializeParserAssets(options) {
|
|
7955
7524
|
const treeSitterWasmPath = options.treeSitterWasmPath ?? resolveAsset("web-tree-sitter/tree-sitter.wasm");
|
|
7956
7525
|
const bashWasmPath = options.bashWasmPath ?? resolveAsset("tree-sitter-bash/tree-sitter-bash.wasm");
|
|
7957
|
-
if (!
|
|
7526
|
+
if (!fs7.existsSync(treeSitterWasmPath) || !fs7.existsSync(bashWasmPath)) {
|
|
7958
7527
|
throw new Error("parser-asset-unavailable");
|
|
7959
7528
|
}
|
|
7960
7529
|
try {
|
|
@@ -7975,7 +7544,7 @@ async function initializeParserAssets(options) {
|
|
|
7975
7544
|
}
|
|
7976
7545
|
function resolveAsset(specifier) {
|
|
7977
7546
|
const resolved = import.meta.resolve(specifier);
|
|
7978
|
-
return resolved.startsWith("file://") ?
|
|
7547
|
+
return resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved;
|
|
7979
7548
|
}
|
|
7980
7549
|
function classifyParserFailure(error) {
|
|
7981
7550
|
if (error instanceof Error && error.message === "grammar-incompatible") {
|
|
@@ -10832,16 +10401,16 @@ function canonicalFileTarget(filePath, realPath) {
|
|
|
10832
10401
|
const existing = canonicalExistingPath(filePath, realPath);
|
|
10833
10402
|
if (existing)
|
|
10834
10403
|
return existing;
|
|
10835
|
-
const parent = canonicalExistingPath(
|
|
10836
|
-
const basename =
|
|
10837
|
-
return parent && basename && basename !== "." && basename !== ".." ?
|
|
10404
|
+
const parent = canonicalExistingPath(path6.dirname(filePath), realPath);
|
|
10405
|
+
const basename = path6.basename(filePath);
|
|
10406
|
+
return parent && basename && basename !== "." && basename !== ".." ? path6.join(parent, basename) : undefined;
|
|
10838
10407
|
}
|
|
10839
10408
|
function pathWithinOrEqual(root, candidate) {
|
|
10840
|
-
const relative =
|
|
10841
|
-
return relative === "" || relative !== ".." && !relative.startsWith(`..${
|
|
10409
|
+
const relative = path6.relative(root, candidate);
|
|
10410
|
+
return relative === "" || relative !== ".." && !relative.startsWith(`..${path6.sep}`) && !path6.isAbsolute(relative);
|
|
10842
10411
|
}
|
|
10843
10412
|
function targetIsGitAdminStorage(targetPath, validation) {
|
|
10844
|
-
return pathWithinOrEqual(
|
|
10413
|
+
return pathWithinOrEqual(path6.join(validation.targetRoot, ".git"), targetPath) || pathWithinOrEqual(validation.gitDir, targetPath) || pathWithinOrEqual(validation.commonDir, targetPath);
|
|
10845
10414
|
}
|
|
10846
10415
|
function targetResultFromValidation(candidatePath, validation) {
|
|
10847
10416
|
if (validation.status === "error")
|
|
@@ -10857,7 +10426,7 @@ function targetResultFromValidation(candidatePath, validation) {
|
|
|
10857
10426
|
function trustedParentTarget(parentTargetRoot, candidatePath) {
|
|
10858
10427
|
if (!pathWithinOrEqual(parentTargetRoot, candidatePath))
|
|
10859
10428
|
return;
|
|
10860
|
-
if (pathWithinOrEqual(
|
|
10429
|
+
if (pathWithinOrEqual(path6.join(parentTargetRoot, ".git"), candidatePath)) {
|
|
10861
10430
|
return unavailableOperationTarget();
|
|
10862
10431
|
}
|
|
10863
10432
|
return { status: "available", targetRoot: parentTargetRoot };
|
|
@@ -10870,14 +10439,14 @@ function validationForCandidate(candidatePath, options, targetPath = candidatePa
|
|
|
10870
10439
|
}
|
|
10871
10440
|
}
|
|
10872
10441
|
function deriveFileTarget(rawPath, baseDirectory, parentTargetRoot, options, realPath, allowParentFastPath = true) {
|
|
10873
|
-
const resolvedPath =
|
|
10442
|
+
const resolvedPath = path6.resolve(baseDirectory, rawPath);
|
|
10874
10443
|
const canonicalPath2 = canonicalFileTarget(resolvedPath, realPath);
|
|
10875
10444
|
if (!canonicalPath2)
|
|
10876
10445
|
return unavailableOperationTarget();
|
|
10877
|
-
const parentResult = allowParentFastPath && !
|
|
10446
|
+
const parentResult = allowParentFastPath && !path6.isAbsolute(rawPath) ? trustedParentTarget(parentTargetRoot, canonicalPath2) : undefined;
|
|
10878
10447
|
if (parentResult)
|
|
10879
10448
|
return parentResult;
|
|
10880
|
-
return validationForCandidate(
|
|
10449
|
+
return validationForCandidate(path6.dirname(canonicalPath2), options, canonicalPath2);
|
|
10881
10450
|
}
|
|
10882
10451
|
function sharedTargetRoot(targets, extraRoots = []) {
|
|
10883
10452
|
const roots = [...extraRoots];
|
|
@@ -10890,10 +10459,10 @@ function sharedTargetRoot(targets, extraRoots = []) {
|
|
|
10890
10459
|
return targetRoot && roots.every((root) => root === targetRoot) ? targetRoot : undefined;
|
|
10891
10460
|
}
|
|
10892
10461
|
function deriveDirectoryTarget(rawPath, baseDirectory, parentTargetRoot, options, realPath) {
|
|
10893
|
-
const resolvedPath = canonicalExistingPath(
|
|
10462
|
+
const resolvedPath = canonicalExistingPath(path6.resolve(baseDirectory, rawPath), realPath);
|
|
10894
10463
|
if (!resolvedPath)
|
|
10895
10464
|
return { status: "unavailable" };
|
|
10896
|
-
const parentResult =
|
|
10465
|
+
const parentResult = path6.isAbsolute(rawPath) ? undefined : trustedParentTarget(parentTargetRoot, resolvedPath);
|
|
10897
10466
|
if (parentResult?.status === "unavailable") {
|
|
10898
10467
|
return { status: "unavailable" };
|
|
10899
10468
|
}
|
|
@@ -11001,14 +10570,14 @@ function deriveBashTarget(args2, sessionLocation, parentTargetRoot, options, rea
|
|
|
11001
10570
|
if (typeof args2.workdir !== "string" || args2.workdir.length === 0) {
|
|
11002
10571
|
return unavailableOperationTarget();
|
|
11003
10572
|
}
|
|
11004
|
-
const candidatePath = canonicalExistingPath(
|
|
10573
|
+
const candidatePath = canonicalExistingPath(path6.resolve(sessionLocation, args2.workdir), realPath);
|
|
11005
10574
|
if (!candidatePath)
|
|
11006
10575
|
return unavailableOperationTarget();
|
|
11007
|
-
const parentResult =
|
|
10576
|
+
const parentResult = path6.isAbsolute(args2.workdir) ? undefined : trustedParentTarget(parentTargetRoot, candidatePath);
|
|
11008
10577
|
return parentResult ?? validationForCandidate(candidatePath, options);
|
|
11009
10578
|
}
|
|
11010
10579
|
function deriveOpencodeOperationTarget(tool, args2, options) {
|
|
11011
|
-
const realPath = options.realPath ??
|
|
10580
|
+
const realPath = options.realPath ?? fs8.realpathSync;
|
|
11012
10581
|
const parentTargetRoot = canonicalExistingPath(options.parentTargetRoot, realPath);
|
|
11013
10582
|
if (!parentTargetRoot)
|
|
11014
10583
|
return unavailableOperationTarget();
|
|
@@ -11085,7 +10654,7 @@ function canonicalPatchText(patchText, sessionLocation) {
|
|
|
11085
10654
|
const filePath = line.slice(prefix.length).trim();
|
|
11086
10655
|
if (!filePath)
|
|
11087
10656
|
return;
|
|
11088
|
-
segments[index] = `${prefix}${
|
|
10657
|
+
segments[index] = `${prefix}${path6.resolve(sessionLocation, filePath)}`;
|
|
11089
10658
|
}
|
|
11090
10659
|
return segments.join("");
|
|
11091
10660
|
}
|
|
@@ -11099,13 +10668,13 @@ function canonicalHunks(value, sessionLocation) {
|
|
|
11099
10668
|
}
|
|
11100
10669
|
const canonical = {
|
|
11101
10670
|
...hunk,
|
|
11102
|
-
path:
|
|
10671
|
+
path: path6.resolve(sessionLocation, hunk.path)
|
|
11103
10672
|
};
|
|
11104
10673
|
if (hunk.move_path !== undefined) {
|
|
11105
10674
|
if (typeof hunk.move_path !== "string" || !hunk.move_path) {
|
|
11106
10675
|
return;
|
|
11107
10676
|
}
|
|
11108
|
-
canonical.move_path =
|
|
10677
|
+
canonical.move_path = path6.resolve(sessionLocation, hunk.move_path);
|
|
11109
10678
|
}
|
|
11110
10679
|
hunks.push(canonical);
|
|
11111
10680
|
}
|
|
@@ -11394,7 +10963,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
|
|
|
11394
10963
|
function parentObserverRegistration() {
|
|
11395
10964
|
if (!options.observer)
|
|
11396
10965
|
return;
|
|
11397
|
-
const targetRoot = canonicalExistingPath(options.targetDirectory ?? process.cwd(),
|
|
10966
|
+
const targetRoot = canonicalExistingPath(options.targetDirectory ?? process.cwd(), fs8.realpathSync);
|
|
11398
10967
|
if (!targetRoot)
|
|
11399
10968
|
return;
|
|
11400
10969
|
let validation;
|
|
@@ -12343,7 +11912,7 @@ function createSessionRuntime(options, operationObservers, recoveredOperationCon
|
|
|
12343
11912
|
markUnavailable();
|
|
12344
11913
|
return;
|
|
12345
11914
|
}
|
|
12346
|
-
const canonicalParentTargetRoot = canonicalExistingPath(parentTargetRoot,
|
|
11915
|
+
const canonicalParentTargetRoot = canonicalExistingPath(parentTargetRoot, fs8.realpathSync);
|
|
12347
11916
|
if (!canonicalParentTargetRoot) {
|
|
12348
11917
|
markUnavailable();
|
|
12349
11918
|
return;
|
|
@@ -13379,9 +12948,35 @@ function createOpencodeWorkflowGuard(options) {
|
|
|
13379
12948
|
}
|
|
13380
12949
|
|
|
13381
12950
|
// src/lib/skill-resolver.ts
|
|
13382
|
-
import
|
|
13383
|
-
import
|
|
13384
|
-
import { pathToFileURL
|
|
12951
|
+
import fs9 from "fs";
|
|
12952
|
+
import path7 from "path";
|
|
12953
|
+
import { pathToFileURL } from "url";
|
|
12954
|
+
|
|
12955
|
+
// src/lib/skill-catalog.ts
|
|
12956
|
+
function buildCatalogEntries(options) {
|
|
12957
|
+
const { bundledSkillsDir, disabledSkills } = options;
|
|
12958
|
+
return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).filter((s) => s.disableModelInvocation !== true).sort((a, b) => a.name.localeCompare(b.name)).map((s) => ({
|
|
12959
|
+
name: s.name,
|
|
12960
|
+
prefixedName: formatSkillCommandName(s.name),
|
|
12961
|
+
description: s.description
|
|
12962
|
+
}));
|
|
12963
|
+
}
|
|
12964
|
+
function renderCatalogCompact(options) {
|
|
12965
|
+
const entries = buildCatalogEntries(options);
|
|
12966
|
+
const heading = "## Available Systematic Skills";
|
|
12967
|
+
if (entries.length === 0) {
|
|
12968
|
+
return `${heading}
|
|
12969
|
+
|
|
12970
|
+
No Systematic skills are currently available.`;
|
|
12971
|
+
}
|
|
12972
|
+
const bullets = entries.map((entry) => `- ${entry.prefixedName}: ${entry.description}`).join(`
|
|
12973
|
+
`);
|
|
12974
|
+
return `${heading}
|
|
12975
|
+
|
|
12976
|
+
${bullets}`;
|
|
12977
|
+
}
|
|
12978
|
+
|
|
12979
|
+
// src/lib/skill-resolver.ts
|
|
13385
12980
|
function getAllSkills(options) {
|
|
13386
12981
|
const { bundledSkillsDir, disabledSkills } = options;
|
|
13387
12982
|
return findSkillsInDir(bundledSkillsDir).filter((s) => !disabledSkills.includes(s.name)).map((skillInfo) => loadSkill(skillInfo)).filter((s) => s !== null).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -13416,8 +13011,8 @@ function buildSkillToolParameterHint(options) {
|
|
|
13416
13011
|
}
|
|
13417
13012
|
function buildSkillContentOutput(matchedSkill) {
|
|
13418
13013
|
const body2 = extractSkillBody(matchedSkill.wrappedTemplate);
|
|
13419
|
-
const dir =
|
|
13420
|
-
const base =
|
|
13014
|
+
const dir = path7.dirname(matchedSkill.skillFile);
|
|
13015
|
+
const base = pathToFileURL(dir).href;
|
|
13421
13016
|
const files = discoverSkillFiles(dir);
|
|
13422
13017
|
const lines = [
|
|
13423
13018
|
`<skill_content name="${matchedSkill.prefixedName}">`,
|
|
@@ -13447,17 +13042,17 @@ function discoverSkillFiles(dir, limit = 10) {
|
|
|
13447
13042
|
function handleEntry(entry, currentDir) {
|
|
13448
13043
|
if (entry.isDirectory()) {
|
|
13449
13044
|
if (!shouldSkipDirectory(entry.name)) {
|
|
13450
|
-
recurse(
|
|
13045
|
+
recurse(path7.resolve(currentDir, entry.name));
|
|
13451
13046
|
}
|
|
13452
13047
|
} else if (shouldIncludeFile(entry.name)) {
|
|
13453
|
-
files.push(
|
|
13048
|
+
files.push(path7.resolve(currentDir, entry.name));
|
|
13454
13049
|
}
|
|
13455
13050
|
}
|
|
13456
13051
|
function recurse(currentDir) {
|
|
13457
13052
|
if (files.length >= limit)
|
|
13458
13053
|
return;
|
|
13459
13054
|
try {
|
|
13460
|
-
const entries =
|
|
13055
|
+
const entries = fs9.readdirSync(currentDir, { withFileTypes: true });
|
|
13461
13056
|
for (const entry of entries) {
|
|
13462
13057
|
if (files.length >= limit)
|
|
13463
13058
|
break;
|
|
@@ -13469,6 +13064,7 @@ function discoverSkillFiles(dir, limit = 10) {
|
|
|
13469
13064
|
return files.map((file) => ` <file>${file}</file>`).join(`
|
|
13470
13065
|
`);
|
|
13471
13066
|
}
|
|
13067
|
+
|
|
13472
13068
|
// src/lib/skill-tool.ts
|
|
13473
13069
|
function createSkillTool(options) {
|
|
13474
13070
|
const { bundledSkillsDir, disabledSkills } = options;
|
|
@@ -13514,19 +13110,19 @@ function createSkillTool(options) {
|
|
|
13514
13110
|
}
|
|
13515
13111
|
|
|
13516
13112
|
// src/index.ts
|
|
13517
|
-
var
|
|
13518
|
-
var
|
|
13519
|
-
var bundledSkillsDir =
|
|
13520
|
-
var
|
|
13521
|
-
var bundledCommandsDir =
|
|
13522
|
-
var packageJsonPath =
|
|
13523
|
-
var canonicalPackageSource =
|
|
13524
|
-
var registrationSourceIdentity =
|
|
13113
|
+
var __dirname2 = path8.dirname(fileURLToPath2(import.meta.url));
|
|
13114
|
+
var packageRoot = path8.resolve(__dirname2, "..");
|
|
13115
|
+
var bundledSkillsDir = path8.join(packageRoot, "skills");
|
|
13116
|
+
var bundledAgentsDir = path8.join(packageRoot, "agents");
|
|
13117
|
+
var bundledCommandsDir = path8.join(packageRoot, "commands");
|
|
13118
|
+
var packageJsonPath = path8.join(packageRoot, "package.json");
|
|
13119
|
+
var canonicalPackageSource = pathToFileURL2(fs10.realpathSync(packageRoot)).href;
|
|
13120
|
+
var registrationSourceIdentity = createHash4("sha256").update(`systematic/opencode-registration-source/v1/${canonicalPackageSource}`).digest("hex");
|
|
13525
13121
|
var getPackageVersion = () => {
|
|
13526
13122
|
try {
|
|
13527
|
-
if (!
|
|
13123
|
+
if (!fs10.existsSync(packageJsonPath))
|
|
13528
13124
|
return "unknown";
|
|
13529
|
-
const content =
|
|
13125
|
+
const content = fs10.readFileSync(packageJsonPath, "utf8");
|
|
13530
13126
|
const parsed = JSON.parse(content);
|
|
13531
13127
|
return parsed.version ?? "unknown";
|
|
13532
13128
|
} catch {
|
|
@@ -13547,9 +13143,8 @@ var initializePlugin = async ({
|
|
|
13547
13143
|
const configHandler = createConfigHandler({
|
|
13548
13144
|
directory,
|
|
13549
13145
|
bundledSkillsDir,
|
|
13550
|
-
bundledAgentsDir
|
|
13551
|
-
bundledCommandsDir
|
|
13552
|
-
client
|
|
13146
|
+
bundledAgentsDir,
|
|
13147
|
+
bundledCommandsDir
|
|
13553
13148
|
});
|
|
13554
13149
|
const observer = createOpencodeOperationObserver({
|
|
13555
13150
|
targetDirectory: typeof worktree === "string" ? worktree : directory
|