@fro.bot/systematic 1.11.1 → 1.13.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.
@@ -138,7 +138,7 @@ function extractBashPermission(data) {
138
138
  }
139
139
  return null;
140
140
  }
141
- function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory) {
141
+ function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill) {
142
142
  const permission = {};
143
143
  if (edit)
144
144
  permission.edit = edit;
@@ -150,6 +150,10 @@ function buildPermissionObject(edit, bash, webfetch, doom_loop, external_directo
150
150
  permission.doom_loop = doom_loop;
151
151
  if (external_directory)
152
152
  permission.external_directory = external_directory;
153
+ if (task)
154
+ permission.task = task;
155
+ if (skill)
156
+ permission.skill = skill;
153
157
  return Object.keys(permission).length > 0 ? permission : undefined;
154
158
  }
155
159
  function normalizePermission(value) {
@@ -170,7 +174,13 @@ function normalizePermission(value) {
170
174
  const external_directory = extractSimplePermission(value, "external_directory");
171
175
  if (external_directory === null)
172
176
  return;
173
- return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory);
177
+ const task = extractSimplePermission(value, "task");
178
+ if (task === null)
179
+ return;
180
+ const skill = extractSimplePermission(value, "skill");
181
+ if (skill === null)
182
+ return;
183
+ return buildPermissionObject(edit, bash, webfetch, doom_loop, external_directory, task, skill);
174
184
  }
175
185
  function extractString(data, key, fallback = "") {
176
186
  const value = data[key];
@@ -262,7 +272,8 @@ function extractAgentFrontmatter(content) {
262
272
  disable: extractBoolean(data, "disable"),
263
273
  mode: isAgentMode(data.mode) ? data.mode : undefined,
264
274
  color: extractNonEmptyString(data, "color"),
265
- maxSteps: extractNumber(data, "maxSteps"),
275
+ steps: extractNumber(data, "steps"),
276
+ hidden: extractBoolean(data, "hidden") ?? undefined,
266
277
  permission: normalizePermission(data.permission)
267
278
  };
268
279
  }
@@ -308,13 +319,14 @@ function extractCommandFrontmatter(content) {
308
319
 
309
320
  // src/lib/converter.ts
310
321
  import fs3 from "fs";
322
+ var CONVERTER_VERSION = 2;
311
323
  var cache = new Map;
312
324
  var TOOL_MAPPINGS = [
313
- [/\bTask\s+tool\b/gi, "delegate_task tool"],
314
- [/\bTask\s+([\w-]+)\s*:/g, "delegate_task $1:"],
315
- [/\bTask\s+([\w-]+)\s*\(/g, "delegate_task $1("],
316
- [/\bTask\s*\(/g, "delegate_task("],
317
- [/\bTask\b(?=\s+to\s+\w)/g, "delegate_task"],
325
+ [/\bTask\s+tool\b/gi, "task tool"],
326
+ [/\bTask\s+([\w-]+)\s*:/g, "task $1:"],
327
+ [/\bTask\s+([\w-]+)\s*\(/g, "task $1("],
328
+ [/\bTask\s*\(/g, "task("],
329
+ [/\bTask\b(?=\s+to\s+\w)/g, "task"],
318
330
  [/\bTodoWrite\b/g, "todowrite"],
319
331
  [/\bAskUserQuestion\b/g, "question"],
320
332
  [/\bWebSearch\b/g, "google_search"],
@@ -336,18 +348,42 @@ var PATH_REPLACEMENTS = [
336
348
  [/\/compound-engineering:/g, "/systematic:"],
337
349
  [/compound-engineering:/g, "systematic:"]
338
350
  ];
339
- var CC_ONLY_SKILL_FIELDS = [
340
- "model",
341
- "allowed-tools",
342
- "allowedTools",
343
- "disable-model-invocation",
344
- "disableModelInvocation",
345
- "user-invocable",
346
- "userInvocable",
347
- "context",
348
- "agent"
349
- ];
350
- var CC_ONLY_COMMAND_FIELDS = ["argument-hint", "argumentHint"];
351
+ var TOOL_NAME_MAP = {
352
+ task: "task",
353
+ todowrite: "todowrite",
354
+ askuserquestion: "question",
355
+ websearch: "google_search",
356
+ webfetch: "webfetch",
357
+ skill: "skill",
358
+ read: "read",
359
+ write: "write",
360
+ edit: "edit",
361
+ bash: "bash",
362
+ grep: "grep",
363
+ glob: "glob"
364
+ };
365
+ var PERMISSION_MODE_MAP = {
366
+ full: {
367
+ edit: "allow",
368
+ bash: "allow",
369
+ webfetch: "allow"
370
+ },
371
+ default: {
372
+ edit: "ask",
373
+ bash: "ask",
374
+ webfetch: "ask"
375
+ },
376
+ plan: {
377
+ edit: "deny",
378
+ bash: "deny",
379
+ webfetch: "ask"
380
+ },
381
+ bypassPermissions: {
382
+ edit: "allow",
383
+ bash: "allow",
384
+ webfetch: "allow"
385
+ }
386
+ };
351
387
  function inferTemperature(name, description) {
352
388
  const sample = `${name} ${description ?? ""}`.toLowerCase();
353
389
  if (/(review|audit|security|sentinel|oracle|lint|verification|guardian)/.test(sample)) {
@@ -384,27 +420,6 @@ function transformBody(body) {
384
420
  }
385
421
  return result;
386
422
  }
387
- function removeFields(data, fieldsToRemove) {
388
- const result = {};
389
- for (const [key, value] of Object.entries(data)) {
390
- if (!fieldsToRemove.includes(key)) {
391
- result[key] = value;
392
- }
393
- }
394
- return result;
395
- }
396
- function transformSkillFrontmatter(data) {
397
- return removeFields(data, CC_ONLY_SKILL_FIELDS);
398
- }
399
- function transformCommandFrontmatter(data) {
400
- const cleaned = removeFields(data, CC_ONLY_COMMAND_FIELDS);
401
- if (typeof cleaned.model === "string" && cleaned.model !== "inherit") {
402
- cleaned.model = normalizeModel(cleaned.model);
403
- } else if (cleaned.model === "inherit") {
404
- delete cleaned.model;
405
- }
406
- return cleaned;
407
- }
408
423
  function normalizeModel(model) {
409
424
  if (model.includes("/"))
410
425
  return model;
@@ -418,37 +433,127 @@ function normalizeModel(model) {
418
433
  return `google/${model}`;
419
434
  return `anthropic/${model}`;
420
435
  }
421
- function addOptionalFields(target, data) {
422
- if (typeof data.top_p === "number")
423
- target.top_p = data.top_p;
424
- if (isToolsMap(data.tools))
425
- target.tools = data.tools;
426
- if (typeof data.disable === "boolean")
427
- target.disable = data.disable;
428
- if (typeof data.color === "string")
429
- target.color = data.color;
430
- if (typeof data.maxSteps === "number")
431
- target.maxSteps = data.maxSteps;
432
- const permission = normalizePermission(data.permission);
433
- if (permission)
434
- target.permission = permission;
436
+ function canonicalizeToolName(name) {
437
+ const lower = name.trim().toLowerCase();
438
+ return TOOL_NAME_MAP[lower] ?? lower;
439
+ }
440
+ function isValidSteps(value) {
441
+ return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
442
+ }
443
+ function mapStepsField(data) {
444
+ if (data.steps !== undefined) {
445
+ if (isValidSteps(data.steps)) {
446
+ delete data.maxTurns;
447
+ delete data.maxSteps;
448
+ }
449
+ return;
450
+ }
451
+ const candidates = [];
452
+ if (isValidSteps(data.maxTurns))
453
+ candidates.push(data.maxTurns);
454
+ if (isValidSteps(data.maxSteps))
455
+ candidates.push(data.maxSteps);
456
+ if (candidates.length > 0) {
457
+ data.steps = Math.min(...candidates);
458
+ delete data.maxTurns;
459
+ delete data.maxSteps;
460
+ }
461
+ }
462
+ function mapToolsField(data) {
463
+ if (data.tools !== undefined && !Array.isArray(data.tools)) {
464
+ if (isToolsMap(data.tools)) {
465
+ mergeDisallowedTools(data);
466
+ }
467
+ return;
468
+ }
469
+ if (Array.isArray(data.tools)) {
470
+ const toolsMap = {};
471
+ for (const tool of data.tools) {
472
+ if (typeof tool === "string") {
473
+ toolsMap[canonicalizeToolName(tool)] = true;
474
+ }
475
+ }
476
+ if (Object.keys(toolsMap).length > 0) {
477
+ data.tools = toolsMap;
478
+ } else {
479
+ delete data.tools;
480
+ }
481
+ }
482
+ mergeDisallowedTools(data);
483
+ }
484
+ function mergeDisallowedTools(data) {
485
+ if (!Array.isArray(data.disallowedTools))
486
+ return;
487
+ const existing = isToolsMap(data.tools) ? data.tools : {};
488
+ for (const tool of data.disallowedTools) {
489
+ if (typeof tool === "string") {
490
+ existing[canonicalizeToolName(tool)] = false;
491
+ }
492
+ }
493
+ if (Object.keys(existing).length > 0) {
494
+ data.tools = existing;
495
+ }
496
+ delete data.disallowedTools;
497
+ }
498
+ function mapPermissionMode(data) {
499
+ if (data.permission !== undefined) {
500
+ const normalized = normalizePermission(data.permission);
501
+ if (normalized) {
502
+ data.permission = normalized;
503
+ delete data.permissionMode;
504
+ return;
505
+ }
506
+ }
507
+ if (typeof data.permissionMode !== "string")
508
+ return;
509
+ const mapped = PERMISSION_MODE_MAP[data.permissionMode];
510
+ data.permission = mapped ?? { edit: "ask", bash: "ask", webfetch: "ask" };
511
+ delete data.permissionMode;
512
+ }
513
+ function mapHiddenField(data) {
514
+ if (data["disable-model-invocation"] === true || data.disableModelInvocation === true) {
515
+ data.hidden = true;
516
+ delete data["disable-model-invocation"];
517
+ delete data.disableModelInvocation;
518
+ }
519
+ }
520
+ function normalizeModelField(data) {
521
+ if (typeof data.model === "string" && data.model !== "inherit") {
522
+ data.model = normalizeModel(data.model);
523
+ } else if (data.model === "inherit") {
524
+ delete data.model;
525
+ }
435
526
  }
436
527
  function transformAgentFrontmatter(data, agentMode) {
528
+ const result = { ...data };
529
+ result.mode = isAgentMode(data.mode) ? data.mode : agentMode;
437
530
  const name = typeof data.name === "string" ? data.name : "";
438
531
  const description = typeof data.description === "string" ? data.description : "";
439
- const mode = isAgentMode(data.mode) ? data.mode : agentMode;
440
- const newData = { mode };
441
532
  if (description) {
442
- newData.description = description;
533
+ result.description = description;
443
534
  } else if (name) {
444
- newData.description = `${name} agent`;
535
+ result.description = `${name} agent`;
445
536
  }
446
- if (typeof data.model === "string" && data.model !== "inherit") {
447
- newData.model = normalizeModel(data.model);
537
+ normalizeModelField(result);
538
+ result.temperature = typeof data.temperature === "number" ? data.temperature : inferTemperature(name, description);
539
+ mapStepsField(result);
540
+ mapToolsField(result);
541
+ mapPermissionMode(result);
542
+ mapHiddenField(result);
543
+ return result;
544
+ }
545
+ function transformSkillFrontmatter(data) {
546
+ const result = { ...data };
547
+ normalizeModelField(result);
548
+ if (result.context === "fork") {
549
+ result.subtask = true;
448
550
  }
449
- newData.temperature = typeof data.temperature === "number" ? data.temperature : inferTemperature(name, description);
450
- addOptionalFields(newData, data);
451
- return newData;
551
+ return result;
552
+ }
553
+ function transformCommandFrontmatter(data) {
554
+ const result = { ...data };
555
+ normalizeModelField(result);
556
+ return result;
452
557
  }
453
558
  function convertContent(content, type, options = {}) {
454
559
  if (content === "")
@@ -458,7 +563,7 @@ function convertContent(content, type, options = {}) {
458
563
  return options.skipBodyTransform ? content : transformBody(content);
459
564
  }
460
565
  if (parseError) {
461
- return content;
566
+ return options.skipBodyTransform ? content : transformBody(content);
462
567
  }
463
568
  const shouldTransformBody = !options.skipBodyTransform;
464
569
  const transformedBody = shouldTransformBody ? transformBody(body) : body;
@@ -484,7 +589,7 @@ function convertFileWithCache(filePath, type, options = {}) {
484
589
  const fd = fs3.openSync(filePath, "r");
485
590
  try {
486
591
  const stats = fs3.fstatSync(fd);
487
- const cacheKey = `${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}:${options.skipBodyTransform ?? false}`;
592
+ const cacheKey = `${CONVERTER_VERSION}:${filePath}:${type}:${options.source ?? "bundled"}:${options.agentMode ?? "subagent"}:${options.skipBodyTransform ?? false}`;
488
593
  const cached = cache.get(cacheKey);
489
594
  if (cached != null && cached.mtimeMs === stats.mtimeMs) {
490
595
  return cached.converted;
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  findSkillsInDir,
9
9
  loadConfig,
10
10
  parseFrontmatter
11
- } from "./index-we4f9de3.js";
11
+ } from "./index-bky4p9gw.js";
12
12
 
13
13
  // src/index.ts
14
14
  import fs3 from "fs";
@@ -22,7 +22,7 @@ import path from "path";
22
22
  function getToolMappingTemplate(bundledSkillsDir) {
23
23
  return `**Tool Mapping for OpenCode:**
24
24
  When skills reference tools you don't have, substitute OpenCode equivalents:
25
- - \`TodoWrite\` \u2192 \`update_plan\`
25
+ - \`TodoWrite\` \u2192 \`todowrite\`
26
26
  - \`Task\` tool with subagents \u2192 Use OpenCode's subagent system (@mention)
27
27
  - \`Skill\` tool \u2192 OpenCode's native \`skill\` tool
28
28
  - \`SystematicSkill\` tool \u2192 \`systematic_skill\` (Systematic plugin skills)
@@ -155,7 +155,8 @@ function loadAgentAsConfig(agentInfo) {
155
155
  disable,
156
156
  mode,
157
157
  color,
158
- maxSteps,
158
+ steps,
159
+ hidden,
159
160
  permission
160
161
  } = extractAgentFrontmatter(converted);
161
162
  const config = {
@@ -176,8 +177,10 @@ function loadAgentAsConfig(agentInfo) {
176
177
  config.mode = mode;
177
178
  if (color !== undefined)
178
179
  config.color = color;
179
- if (maxSteps !== undefined)
180
- config.maxSteps = maxSteps;
180
+ if (steps !== undefined)
181
+ config.steps = steps;
182
+ if (hidden !== undefined)
183
+ config.hidden = hidden;
181
184
  if (permission !== undefined)
182
185
  config.permission = permission;
183
186
  return config;
@@ -21,7 +21,9 @@ export interface AgentFrontmatter {
21
21
  /** Hex color code */
22
22
  color?: string;
23
23
  /** Max agentic iterations */
24
- maxSteps?: number;
24
+ steps?: number;
25
+ /** Whether this agent is hidden from model invocation */
26
+ hidden?: boolean;
25
27
  /** Permission settings */
26
28
  permission?: PermissionConfig;
27
29
  }
@@ -0,0 +1,35 @@
1
+ export interface ManifestSource {
2
+ repo: string;
3
+ branch: string;
4
+ url: string;
5
+ }
6
+ export interface ManifestRewrite {
7
+ field: string;
8
+ reason: string;
9
+ original?: string;
10
+ }
11
+ export interface ManualOverride {
12
+ field: string;
13
+ reason: string;
14
+ original?: string;
15
+ overridden_at: string;
16
+ }
17
+ export interface ManifestDefinition {
18
+ source: string;
19
+ upstream_path: string;
20
+ upstream_commit: string;
21
+ synced_at: string;
22
+ notes: string;
23
+ upstream_content_hash?: string;
24
+ rewrites?: ManifestRewrite[];
25
+ manual_overrides?: ManualOverride[];
26
+ }
27
+ export interface SyncManifest {
28
+ $schema?: string;
29
+ sources: Record<string, ManifestSource>;
30
+ definitions: Record<string, ManifestDefinition>;
31
+ }
32
+ export declare function validateManifest(data: unknown): data is SyncManifest;
33
+ export declare function readManifest(filePath: string): SyncManifest | null;
34
+ export declare function writeManifest(filePath: string, manifest: SyncManifest): void;
35
+ export declare function findStaleEntries(manifest: SyncManifest, existingPaths: string[]): string[];
@@ -9,6 +9,8 @@ export interface PermissionConfig {
9
9
  webfetch?: PermissionSetting;
10
10
  doom_loop?: PermissionSetting;
11
11
  external_directory?: PermissionSetting;
12
+ task?: PermissionSetting;
13
+ skill?: PermissionSetting;
12
14
  }
13
15
  export declare function isRecord(value: unknown): value is Record<string, unknown>;
14
16
  export declare function isPermissionSetting(value: unknown): value is PermissionSetting;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fro.bot/systematic",
3
- "version": "1.11.1",
3
+ "version": "1.13.0",
4
4
  "description": "Structured engineering workflows for OpenCode",
5
5
  "type": "module",
6
6
  "homepage": "https://fro.bot/systematic",