thoth-plugin 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -491,36 +491,125 @@ function buildSkillRoutingSection() {
491
491
  return lines.join(`
492
492
  `);
493
493
  }
494
+ var THOTH_INTENT_GATE = `<Phase_0_Intent_Gate>
495
+ ## Phase 0: Intent Gate (EVERY prompt)
496
+
497
+ Before ANY action, classify the incoming request:
498
+
499
+ ### Step 0: Check for Skills
500
+ | Trigger | Skill | Action |
501
+ |---------|-------|--------|
502
+ | "Run morning boot", "Start my day" | morning-boot | Fire skill immediately |
503
+ | "End of day", "Close out" | evening-close | Fire skill immediately |
504
+ | "Dump:", "Quick thought:" | thought-router | Fire skill immediately |
505
+ | "Drill meeting notes" | post-meeting-drill | Fire skill immediately |
506
+
507
+ ### Step 1: Identify Hemisphere(s)
508
+ | Signal | Hemisphere |
509
+ |--------|------------|
510
+ | Code, technical, development, bugs, features, git | **coding/** |
511
+ | Work, job, colleagues, projects, meetings, career, stakeholders | **work/** |
512
+ | Personal, health, family, friends, home, finance, feelings | **life/** |
513
+ | System, settings, preferences, onboarding, meta, Thoth | **kernel/** |
514
+ | Ambiguous or cross-domain | Multiple or ask |
515
+
516
+ ### Step 2: Check Permissions
517
+ Before any action, verify against kernel/config/permissions.md:
518
+ - **Autonomous**: Read, analyze, create knowledge, fire background agents
519
+ - **Requires Approval**: Send communications, financial, delete, modify shared files
520
+
521
+ ### Step 3: Check Trust Level
522
+ Read kernel/state/trust.md for current trust level:
523
+ - **Level 1**: Read only, all writes require approval
524
+ - **Level 2**: Code edits with evidence, knowledge updates
525
+ - **Level 3**: Routine communications, calendar changes
526
+
527
+ ### Step 4: Classify Request Type
528
+ | Type | Signal | Action |
529
+ |------|--------|--------|
530
+ | **Information** | "What is...", "Who is...", "Tell me about..." | Retrieve context \u2192 answer |
531
+ | **Action** | "Send...", "Schedule...", "Create...", "Update..." | Check permissions \u2192 execute or delegate |
532
+ | **Planning** | "Help me plan...", "How should I..." | Retrieve context \u2192 think \u2192 propose |
533
+ | **Coaching** | "Reflect", "How am I doing" | Thoughtful dialogue |
534
+ | **Onboarding** | "Let's onboard...", "Learn about my..." | Enter onboarding mode |
535
+ | **Meta** | "Update your prompt", "Change how you..." | Collaborative self-modification |
536
+
537
+ ### Step 5: Determine Routing
538
+ | Complexity | Action |
539
+ |------------|--------|
540
+ | Simple, single-hemisphere | Handle directly with context from that hemisphere |
541
+ | Complex, single-hemisphere | Delegate to hemisphere Master |
542
+ | Cross-hemisphere | Orchestrate: gather context from multiple, synthesize |
543
+ | Requires research | Fire parallel background_task agents |
544
+ </Phase_0_Intent_Gate>`;
494
545
  var THOTH_CORE_CAPABILITIES = `<Core_Capabilities>
495
- ## Core Capabilities
546
+ ## Core Capabilities (Built-In)
496
547
 
497
- ### When to Delegate vs Execute Directly
548
+ You have these capabilities built into your core function. Do NOT delegate these to sub-agents \u2014 execute them directly with full session context.
498
549
 
499
- | Situation | Action |
500
- |-----------|--------|
501
- | Simple lookup, single file read | Execute directly |
502
- | Knowledge base update | Execute directly |
503
- | Parallel research (multiple sources) | Fire background agents |
504
- | Complex workflow with defined steps | Invoke a skill |
505
- | Deep domain work requiring focus | Delegate to sub-agent |
550
+ ### Knowledge Retrieval (You Do This Directly)
506
551
 
507
- ### Background Agents
552
+ When Zeus asks "What do I know about X?", "Who is Y?", "Context on Z?":
508
553
 
509
- For parallel research or data gathering. They run independently and return results:
554
+ 1. **Read \`kernel/paths.json\`** to locate files
555
+ 2. **Follow Circle System** (see Phase 1 below)
556
+ 3. **Synthesize** the relevant information
557
+ 4. **Cite sources** \u2014 always reference which files you found information in
558
+ 5. **Acknowledge gaps** \u2014 if information is missing, say so
510
559
 
511
- \`\`\`typescript
512
- background_task(agent="general", prompt="[Specific task instructions]...")
513
- \`\`\`
560
+ ### Knowledge Persistence (You Do This Directly)
561
+
562
+ When new information emerges that should be remembered:
563
+
564
+ 1. **Read before write** \u2014 Always check existing content first
565
+ 2. **Smart Merge** \u2014 Integrate into existing sections, don't just append
566
+ 3. **Use templates** from \`kernel/templates/\` for new files
567
+ 4. **Update registries** \u2014 Add new files to relevant \`_index.md\` and \`registry.md\`
568
+ 5. **Bidirectional links** \u2014 If A references B, add A to B's related section
569
+ 6. **Chronicle significant events** \u2014 Append to \`chronicle.md\` with date stamp
570
+ 7. **Frontmatter required** \u2014 Every file needs type, hemisphere, dates, tags, summary
571
+
572
+ ### Deduplication Check (Before Creating Files)
573
+
574
+ 1. Grep for entity name across knowledge base
575
+ 2. Check if file already exists
576
+ 3. If exists \u2192 UPDATE, not CREATE
577
+ 4. If similar exists \u2192 ASK for clarification
514
578
 
515
- Use for: parallel scans, research, data gathering that doesn't need session context.
579
+ ## Functional Agents (For Specialized Tasks)
516
580
 
517
- ### Skills
581
+ These agents handle tasks that DON'T require session context:
518
582
 
519
- Invoke with skill() tool. They provide detailed instructions for complex workflows. Available skills discovered at runtime \u2014 check .opencode/skill/ for what's available.
583
+ | Agent | Function | When to Use |
584
+ |-------|----------|-------------|
585
+ | **coach** | Reflection & thinking partner | "Help me think through X", "I'm stuck on Y", "Should I do A or B?" |
586
+ | **sentinel** | Proactive monitoring | "What needs my attention?", "Any overdue items?", "Check my calendar" |
587
+ | **diplomat** | Communication drafting | "Draft an email to X", "Help me respond to Y", "How should I say Z?" |
588
+ | **chronicler** | Meeting/event processing | "Process this meeting", "Extract action items" (from provided notes) |
520
589
 
521
- ### Sub-Agents
590
+ ### When to Delegate vs Execute Directly
591
+
592
+ | Task | Action | Why |
593
+ |------|--------|-----|
594
+ | "What do I know about Sarah?" | **Execute directly** | Needs session context for relevance |
595
+ | "Remember that Sarah prefers async" | **Execute directly** | Needs session context for smart merge |
596
+ | "Help me think through this decision" | Delegate to **coach** | Specialized coaching framework |
597
+ | "What needs my attention today?" | Delegate to **sentinel** | Independent calendar/task scan |
598
+ | "Draft an email to Sarah" | Delegate to **diplomat** | Specialized communication drafting |
599
+ | "Process these meeting notes" | Delegate to **chronicler** | Structured extraction from provided text |
600
+
601
+ ### Background Agents (Parallel, Independent)
602
+
603
+ For parallel research or data gathering, fire background agents:
604
+
605
+ \`\`\`typescript
606
+ // Example: Morning boot parallel scans
607
+ background_task(agent="general", prompt="[Email scan instructions]...")
608
+ background_task(agent="general", prompt="[Calendar scan instructions]...")
609
+ background_task(agent="general", prompt="[Slack scan instructions]...")
610
+ \`\`\`
522
611
 
523
- For complex tasks requiring deep expertise, delegate to specialized sub-agents (Work-Master, Life-Master, Code-Master). Use task() tool with the 7-Section Format from Execution section.
612
+ These are appropriate because they gather independent data and return facts \u2014 they don't need session context.
524
613
  </Core_Capabilities>`;
525
614
  var THOTH_EXECUTION = `<Execution>
526
615
  ## Execution
@@ -553,6 +642,22 @@ A task is NOT complete without evidence:
553
642
  | Test run | Pass (or note pre-existing failures) |
554
643
  | Delegation | Agent result received and verified |
555
644
  </Execution>`;
645
+ var THOTH_TEMPORAL_AWARENESS = `<Temporal_Awareness>
646
+ ## Temporal Awareness (Chronos Protocol)
647
+
648
+ Operate with deep time awareness, respecting Zeus's biological and professional cycles.
649
+
650
+ ### Executive Calendar (Work)
651
+ - **Monday**: Launch Mode \u2014 Prioritize planning, alignment, P0 definition
652
+ - **Tue-Thu**: Execution Mode \u2014 Protect deep work blocks, minimize admin
653
+ - **Friday**: Closure Mode \u2014 Wrap up, delegation follow-ups, weekly review
654
+ - **Weekend**: Sanctuary \u2014 Block work unless Emergency P0
655
+
656
+ ### Biological Clock (Life)
657
+ - **Morning (08:00-11:00)**: High Cognitive \u2014 Protect from triage hell
658
+ - **Afternoon (14:00-17:00)**: Collaborative \u2014 Good for meetings and emails
659
+ - **Evening (19:00+)**: Restoration \u2014 Block work notifications, prompt journaling
660
+ </Temporal_Awareness>`;
556
661
  var THOTH_PERMISSIONS = `<Permission_System>
557
662
  ## Permission System
558
663
 
@@ -628,9 +733,11 @@ function buildThothPrompt(spec) {
628
733
  if (skillRouting) {
629
734
  sections.push(skillRouting);
630
735
  }
736
+ sections.push(THOTH_INTENT_GATE);
631
737
  sections.push(THOTH_CORE_CAPABILITIES);
632
738
  sections.push(THOTH_EXECUTION);
633
739
  sections.push(THOTH_PERMISSIONS);
740
+ sections.push(THOTH_TEMPORAL_AWARENESS);
634
741
  sections.push(THOTH_COMMUNICATION);
635
742
  sections.push(THOTH_CLOSING);
636
743
  return sections.join(`
@@ -3325,9 +3432,216 @@ To proceed, mark this as Emergency/P0 or wait until work hours.`
3325
3432
  getDayModeRecommendation
3326
3433
  };
3327
3434
  }
3435
+ // src/hooks/frontmatter-enforcer.ts
3436
+ import * as path5 from "path";
3437
+ var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
3438
+ function getToday() {
3439
+ return new Date().toISOString().split("T")[0];
3440
+ }
3441
+ function parseMarkdown(content) {
3442
+ const match = content.match(FRONTMATTER_REGEX);
3443
+ if (!match) {
3444
+ return { hasFrontmatter: false, frontmatter: {}, body: content };
3445
+ }
3446
+ const frontmatter = {};
3447
+ for (const line of match[1].split(`
3448
+ `)) {
3449
+ const colonIdx = line.indexOf(":");
3450
+ if (colonIdx === -1)
3451
+ continue;
3452
+ const key = line.slice(0, colonIdx).trim();
3453
+ let value = line.slice(colonIdx + 1).trim();
3454
+ if (value === "[]") {
3455
+ value = [];
3456
+ } else if (typeof value === "string") {
3457
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3458
+ value = value.slice(1, -1);
3459
+ }
3460
+ }
3461
+ frontmatter[key] = value;
3462
+ }
3463
+ return {
3464
+ hasFrontmatter: true,
3465
+ frontmatter,
3466
+ body: content.slice(match[0].length)
3467
+ };
3468
+ }
3469
+ function serializeFrontmatter(data) {
3470
+ const lines = [];
3471
+ for (const [key, value] of Object.entries(data)) {
3472
+ if (Array.isArray(value)) {
3473
+ if (value.length === 0) {
3474
+ lines.push(`${key}: []`);
3475
+ } else {
3476
+ lines.push(`${key}:`);
3477
+ for (const item of value) {
3478
+ lines.push(` - ${item}`);
3479
+ }
3480
+ }
3481
+ } else if (typeof value === "string" && value.includes(":")) {
3482
+ lines.push(`${key}: "${value}"`);
3483
+ } else {
3484
+ lines.push(`${key}: ${value}`);
3485
+ }
3486
+ }
3487
+ return lines.join(`
3488
+ `);
3489
+ }
3490
+ function reconstructMarkdown(frontmatter, body) {
3491
+ return `---
3492
+ ${serializeFrontmatter(frontmatter)}
3493
+ ---
3494
+
3495
+ ${body.replace(/^\n+/, "")}`;
3496
+ }
3497
+ function ensureDates(content, created, updated) {
3498
+ const parsed = parseMarkdown(content);
3499
+ if (!parsed.hasFrontmatter) {
3500
+ return reconstructMarkdown({ created, updated }, content);
3501
+ }
3502
+ if (!parsed.frontmatter.created) {
3503
+ parsed.frontmatter.created = created;
3504
+ }
3505
+ parsed.frontmatter.updated = updated;
3506
+ return reconstructMarkdown(parsed.frontmatter, parsed.body);
3507
+ }
3508
+ function createFrontmatterEnforcerHook(config) {
3509
+ const { knowledgeBasePath, enabled = true } = config;
3510
+ if (!enabled)
3511
+ return null;
3512
+ const kbPath = expandPath(knowledgeBasePath);
3513
+ const pendingEdits = new Map;
3514
+ function isKbMarkdownFile(filePath) {
3515
+ if (!filePath.endsWith(".md"))
3516
+ return false;
3517
+ return path5.resolve(filePath).startsWith(path5.resolve(kbPath));
3518
+ }
3519
+ return {
3520
+ "tool.execute.before": async (input, output) => {
3521
+ const filePath = output.args.filePath;
3522
+ if (!filePath || !isKbMarkdownFile(filePath))
3523
+ return;
3524
+ if (input.tool === "write") {
3525
+ const content = output.args.content;
3526
+ if (!content)
3527
+ return;
3528
+ const today = getToday();
3529
+ const existing = readFileSync4(filePath);
3530
+ const createdDate = existing ? parseMarkdown(existing).frontmatter.created || today : today;
3531
+ output.args.content = ensureDates(content, createdDate, today);
3532
+ log(`Frontmatter: dates enforced for ${path5.basename(filePath)}`);
3533
+ }
3534
+ if (input.tool === "edit" && input.callID) {
3535
+ pendingEdits.set(input.callID, filePath);
3536
+ }
3537
+ },
3538
+ "tool.execute.after": async (input, _output) => {
3539
+ if (input.tool !== "edit")
3540
+ return;
3541
+ const filePath = pendingEdits.get(input.callID);
3542
+ pendingEdits.delete(input.callID);
3543
+ if (!filePath)
3544
+ return;
3545
+ const content = readFileSync4(filePath);
3546
+ if (!content)
3547
+ return;
3548
+ const parsed = parseMarkdown(content);
3549
+ if (!parsed.hasFrontmatter)
3550
+ return;
3551
+ parsed.frontmatter.updated = getToday();
3552
+ writeFileSync2(filePath, reconstructMarkdown(parsed.frontmatter, parsed.body));
3553
+ log(`Frontmatter: updated timestamp for ${path5.basename(filePath)}`);
3554
+ }
3555
+ };
3556
+ }
3557
+ // src/hooks/read-confirmation.ts
3558
+ import * as path6 from "path";
3559
+ function createReadConfirmationHook(config) {
3560
+ const { knowledgeBasePath, enabled = true, kbOnly = true } = config;
3561
+ if (!enabled) {
3562
+ return null;
3563
+ }
3564
+ const kbPath = expandPath(knowledgeBasePath);
3565
+ const tracker = {
3566
+ pendingReadPaths: new Map
3567
+ };
3568
+ return {
3569
+ "tool.execute.before": async (input, output) => {
3570
+ if (input.tool !== "read")
3571
+ return;
3572
+ const filePath = output.args?.filePath;
3573
+ if (filePath && input.callID) {
3574
+ tracker.pendingReadPaths.set(input.callID, filePath);
3575
+ }
3576
+ },
3577
+ "tool.execute.after": async (input, output) => {
3578
+ if (input.tool !== "read")
3579
+ return;
3580
+ const filePath = tracker.pendingReadPaths.get(input.callID);
3581
+ tracker.pendingReadPaths.delete(input.callID);
3582
+ if (!filePath)
3583
+ return;
3584
+ if (kbOnly && !filePath.startsWith(kbPath)) {
3585
+ return;
3586
+ }
3587
+ const lineCount = output.output?.split(`
3588
+ `).length || 0;
3589
+ const relativePath = filePath.startsWith(kbPath) ? filePath.slice(kbPath.length + 1) : path6.basename(filePath);
3590
+ log(`[Read confirmed: ${relativePath} (${lineCount} lines)]`);
3591
+ }
3592
+ };
3593
+ }
3594
+ // src/hooks/write-confirmation.ts
3595
+ import * as path7 from "path";
3596
+ function createWriteConfirmationHook(config) {
3597
+ const { knowledgeBasePath, enabled = true, kbOnly = true } = config;
3598
+ if (!enabled) {
3599
+ return null;
3600
+ }
3601
+ const kbPath = expandPath(knowledgeBasePath);
3602
+ const tracker = {
3603
+ pendingWritePaths: new Map
3604
+ };
3605
+ return {
3606
+ "tool.execute.before": async (input, output) => {
3607
+ if (input.tool !== "write" && input.tool !== "edit")
3608
+ return;
3609
+ const filePath = output.args?.filePath;
3610
+ if (filePath && input.callID) {
3611
+ tracker.pendingWritePaths.set(input.callID, {
3612
+ filePath,
3613
+ action: input.tool
3614
+ });
3615
+ }
3616
+ },
3617
+ "tool.execute.after": async (input, output) => {
3618
+ if (input.tool !== "write" && input.tool !== "edit")
3619
+ return;
3620
+ const pending = tracker.pendingWritePaths.get(input.callID);
3621
+ tracker.pendingWritePaths.delete(input.callID);
3622
+ if (!pending)
3623
+ return;
3624
+ const { filePath, action } = pending;
3625
+ if (kbOnly && !filePath.startsWith(kbPath)) {
3626
+ return;
3627
+ }
3628
+ const relativePath = filePath.startsWith(kbPath) ? filePath.slice(kbPath.length + 1) : path7.basename(filePath);
3629
+ const actionLabel = action === "write" ? "Created/Overwrote" : "Edited";
3630
+ const isNewFile = action === "write";
3631
+ const isMarkdownFile = filePath.endsWith(".md");
3632
+ const isIndexFile = relativePath.includes("_index.md") || relativePath.includes("registry.md");
3633
+ let message = `[${actionLabel}: ${relativePath}]`;
3634
+ if (isNewFile && isMarkdownFile && !isIndexFile) {
3635
+ message += `
3636
+ Reminder: Update _index.md if this is a new file. Check bidirectional links.`;
3637
+ }
3638
+ log(message);
3639
+ }
3640
+ };
3641
+ }
3328
3642
  // src/hooks/directory-agents-injector/index.ts
3329
3643
  import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
3330
- import { dirname as dirname4, join as join9, resolve } from "path";
3644
+ import { dirname as dirname4, join as join9, resolve as resolve2 } from "path";
3331
3645
 
3332
3646
  // src/hooks/directory-agents-injector/storage.ts
3333
3647
  import {
@@ -3397,7 +3711,7 @@ function createDirectoryAgentsInjectorHook(options) {
3397
3711
  return null;
3398
3712
  if (title.startsWith("/"))
3399
3713
  return title;
3400
- return resolve(directory, title);
3714
+ return resolve2(directory, title);
3401
3715
  }
3402
3716
  function findAgentsMdUp(startDir) {
3403
3717
  const found = [];
@@ -3449,8 +3763,8 @@ function createDirectoryAgentsInjectorHook(options) {
3449
3763
  }
3450
3764
  if (toInject.length === 0)
3451
3765
  return;
3452
- for (const { path: path5, content } of toInject) {
3453
- const relativePath = path5.replace(knowledgeBasePath, "").replace(/^\//, "");
3766
+ for (const { path: path8, content } of toInject) {
3767
+ const relativePath = path8.replace(knowledgeBasePath, "").replace(/^\//, "");
3454
3768
  output.output += `
3455
3769
 
3456
3770
  [Directory Context: ${relativePath}]
@@ -3537,8 +3851,8 @@ function findNearestMessageWithFields(messageDir) {
3537
3851
  // src/shared-hooks/utils/logger.ts
3538
3852
  import * as fs2 from "fs";
3539
3853
  import * as os2 from "os";
3540
- import * as path5 from "path";
3541
- var logFile = path5.join(os2.tmpdir(), "thoth-plugin.log");
3854
+ import * as path8 from "path";
3855
+ var logFile = path8.join(os2.tmpdir(), "thoth-plugin.log");
3542
3856
  function log2(message, data) {
3543
3857
  try {
3544
3858
  const timestamp = new Date().toISOString();
@@ -5338,10 +5652,10 @@ function mergeDefs(...defs) {
5338
5652
  function cloneDef(schema) {
5339
5653
  return mergeDefs(schema._zod.def);
5340
5654
  }
5341
- function getElementAtPath(obj, path6) {
5342
- if (!path6)
5655
+ function getElementAtPath(obj, path9) {
5656
+ if (!path9)
5343
5657
  return obj;
5344
- return path6.reduce((acc, key) => acc?.[key], obj);
5658
+ return path9.reduce((acc, key) => acc?.[key], obj);
5345
5659
  }
5346
5660
  function promiseAllObject(promisesObj) {
5347
5661
  const keys = Object.keys(promisesObj);
@@ -5700,11 +6014,11 @@ function aborted(x, startIndex = 0) {
5700
6014
  }
5701
6015
  return false;
5702
6016
  }
5703
- function prefixIssues(path6, issues) {
6017
+ function prefixIssues(path9, issues) {
5704
6018
  return issues.map((iss) => {
5705
6019
  var _a;
5706
6020
  (_a = iss).path ?? (_a.path = []);
5707
- iss.path.unshift(path6);
6021
+ iss.path.unshift(path9);
5708
6022
  return iss;
5709
6023
  });
5710
6024
  }
@@ -5872,7 +6186,7 @@ function treeifyError(error, _mapper) {
5872
6186
  return issue2.message;
5873
6187
  };
5874
6188
  const result = { errors: [] };
5875
- const processError = (error2, path6 = []) => {
6189
+ const processError = (error2, path9 = []) => {
5876
6190
  var _a, _b;
5877
6191
  for (const issue2 of error2.issues) {
5878
6192
  if (issue2.code === "invalid_union" && issue2.errors.length) {
@@ -5882,7 +6196,7 @@ function treeifyError(error, _mapper) {
5882
6196
  } else if (issue2.code === "invalid_element") {
5883
6197
  processError({ issues: issue2.issues }, issue2.path);
5884
6198
  } else {
5885
- const fullpath = [...path6, ...issue2.path];
6199
+ const fullpath = [...path9, ...issue2.path];
5886
6200
  if (fullpath.length === 0) {
5887
6201
  result.errors.push(mapper(issue2));
5888
6202
  continue;
@@ -5914,8 +6228,8 @@ function treeifyError(error, _mapper) {
5914
6228
  }
5915
6229
  function toDotPath(_path) {
5916
6230
  const segs = [];
5917
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
5918
- for (const seg of path6) {
6231
+ const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
6232
+ for (const seg of path9) {
5919
6233
  if (typeof seg === "number")
5920
6234
  segs.push(`[${seg}]`);
5921
6235
  else if (typeof seg === "symbol")
@@ -16990,7 +17304,7 @@ Use \`background_output\` tool with task_id="${task.id}" to check progress:
16990
17304
  });
16991
17305
  }
16992
17306
  function delay(ms) {
16993
- return new Promise((resolve2) => setTimeout(resolve2, ms));
17307
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
16994
17308
  }
16995
17309
  function truncateText(text, maxLength) {
16996
17310
  if (text.length <= maxLength)
@@ -17206,7 +17520,7 @@ Status: ${task.status}`;
17206
17520
  // src/tools/skill/tools.ts
17207
17521
  import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9, lstatSync, readlinkSync } from "fs";
17208
17522
  import { homedir as homedir5 } from "os";
17209
- import { join as join17, basename as basename2, resolve as resolve2, dirname as dirname5 } from "path";
17523
+ import { join as join17, basename as basename5, resolve as resolve3, dirname as dirname5 } from "path";
17210
17524
  import { fileURLToPath as fileURLToPath2 } from "url";
17211
17525
  var __filename3 = fileURLToPath2(import.meta.url);
17212
17526
  var __dirname3 = dirname5(__filename3);
@@ -17270,7 +17584,7 @@ function resolveSymlink(filePath) {
17270
17584
  try {
17271
17585
  const stats = lstatSync(filePath, { throwIfNoEntry: false });
17272
17586
  if (stats?.isSymbolicLink()) {
17273
- return resolve2(filePath, "..", readlinkSync(filePath));
17587
+ return resolve3(filePath, "..", readlinkSync(filePath));
17274
17588
  }
17275
17589
  return filePath;
17276
17590
  } catch {
@@ -17356,7 +17670,7 @@ async function parseSkillMd(skillPath) {
17356
17670
  const { data, body } = parseFrontmatter(content);
17357
17671
  const frontmatter = parseSkillFrontmatter(data);
17358
17672
  const metadata = {
17359
- name: frontmatter.name || basename2(skillPath),
17673
+ name: frontmatter.name || basename5(skillPath),
17360
17674
  description: frontmatter.description,
17361
17675
  license: frontmatter.license,
17362
17676
  allowedTools: frontmatter["allowed-tools"],
@@ -18295,10 +18609,10 @@ function mergeDefs2(...defs) {
18295
18609
  function cloneDef2(schema) {
18296
18610
  return mergeDefs2(schema._zod.def);
18297
18611
  }
18298
- function getElementAtPath2(obj, path6) {
18299
- if (!path6)
18612
+ function getElementAtPath2(obj, path9) {
18613
+ if (!path9)
18300
18614
  return obj;
18301
- return path6.reduce((acc, key) => acc?.[key], obj);
18615
+ return path9.reduce((acc, key) => acc?.[key], obj);
18302
18616
  }
18303
18617
  function promiseAllObject2(promisesObj) {
18304
18618
  const keys = Object.keys(promisesObj);
@@ -18679,11 +18993,11 @@ function aborted2(x, startIndex = 0) {
18679
18993
  }
18680
18994
  return false;
18681
18995
  }
18682
- function prefixIssues2(path6, issues) {
18996
+ function prefixIssues2(path9, issues) {
18683
18997
  return issues.map((iss) => {
18684
18998
  var _a;
18685
18999
  (_a = iss).path ?? (_a.path = []);
18686
- iss.path.unshift(path6);
19000
+ iss.path.unshift(path9);
18687
19001
  return iss;
18688
19002
  });
18689
19003
  }
@@ -18866,7 +19180,7 @@ function formatError2(error45, mapper = (issue3) => issue3.message) {
18866
19180
  }
18867
19181
  function treeifyError2(error45, mapper = (issue3) => issue3.message) {
18868
19182
  const result = { errors: [] };
18869
- const processError = (error46, path6 = []) => {
19183
+ const processError = (error46, path9 = []) => {
18870
19184
  var _a, _b;
18871
19185
  for (const issue3 of error46.issues) {
18872
19186
  if (issue3.code === "invalid_union" && issue3.errors.length) {
@@ -18876,7 +19190,7 @@ function treeifyError2(error45, mapper = (issue3) => issue3.message) {
18876
19190
  } else if (issue3.code === "invalid_element") {
18877
19191
  processError({ issues: issue3.issues }, issue3.path);
18878
19192
  } else {
18879
- const fullpath = [...path6, ...issue3.path];
19193
+ const fullpath = [...path9, ...issue3.path];
18880
19194
  if (fullpath.length === 0) {
18881
19195
  result.errors.push(mapper(issue3));
18882
19196
  continue;
@@ -18908,8 +19222,8 @@ function treeifyError2(error45, mapper = (issue3) => issue3.message) {
18908
19222
  }
18909
19223
  function toDotPath2(_path) {
18910
19224
  const segs = [];
18911
- const path6 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
18912
- for (const seg of path6) {
19225
+ const path9 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
19226
+ for (const seg of path9) {
18913
19227
  if (typeof seg === "number")
18914
19228
  segs.push(`[${seg}]`);
18915
19229
  else if (typeof seg === "symbol")
@@ -30656,13 +30970,13 @@ function resolveRef(ref, ctx) {
30656
30970
  if (!ref.startsWith("#")) {
30657
30971
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
30658
30972
  }
30659
- const path6 = ref.slice(1).split("/").filter(Boolean);
30660
- if (path6.length === 0) {
30973
+ const path9 = ref.slice(1).split("/").filter(Boolean);
30974
+ if (path9.length === 0) {
30661
30975
  return ctx.rootSchema;
30662
30976
  }
30663
30977
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
30664
- if (path6[0] === defsKey) {
30665
- const key = path6[1];
30978
+ if (path9[0] === defsKey) {
30979
+ const key = path9[1];
30666
30980
  if (!key || !ctx.defs[key]) {
30667
30981
  throw new Error(`Reference not found: ${ref}`);
30668
30982
  }
@@ -31076,6 +31390,9 @@ var HooksConfigSchema = exports_external2.object({
31076
31390
  "temporal-awareness": exports_external2.boolean().optional(),
31077
31391
  "knowledge-persistence": exports_external2.boolean().optional(),
31078
31392
  "directory-agents-injector": exports_external2.boolean().optional(),
31393
+ "frontmatter-enforcer": exports_external2.boolean().optional(),
31394
+ "read-confirmation": exports_external2.boolean().optional(),
31395
+ "write-confirmation": exports_external2.boolean().optional(),
31079
31396
  "todo-continuation": exports_external2.boolean().optional(),
31080
31397
  "session-recovery": exports_external2.boolean().optional(),
31081
31398
  "context-window-monitor": exports_external2.boolean().optional(),
@@ -31113,7 +31430,7 @@ var ThothPluginConfigSchema = exports_external2.object({
31113
31430
  }).strict();
31114
31431
  // src/index.ts
31115
31432
  import * as fs3 from "fs";
31116
- import * as path6 from "path";
31433
+ import * as path9 from "path";
31117
31434
  var sessionSpecializations = new Map;
31118
31435
  function loadConfigFromPath(configPath) {
31119
31436
  try {
@@ -31145,8 +31462,8 @@ function mergeConfigs(base, override) {
31145
31462
  };
31146
31463
  }
31147
31464
  function loadPluginConfig(directory) {
31148
- const userConfigPath = path6.join(getUserConfigDir(), "opencode", "thoth-plugin.json");
31149
- const projectConfigPath = path6.join(directory, ".opencode", "thoth-plugin.json");
31465
+ const userConfigPath = path9.join(getUserConfigDir(), "opencode", "thoth-plugin.json");
31466
+ const projectConfigPath = path9.join(directory, ".opencode", "thoth-plugin.json");
31150
31467
  let config3 = loadConfigFromPath(userConfigPath) ?? {};
31151
31468
  const projectConfig = loadConfigFromPath(projectConfigPath);
31152
31469
  if (projectConfig) {
@@ -31160,15 +31477,15 @@ function resolveKnowledgeBasePath(config3, directory) {
31160
31477
  return expandPath(config3.knowledge_base);
31161
31478
  }
31162
31479
  const commonLocations = [
31163
- path6.join(process.env.HOME || "", "Repos", "thoth"),
31164
- path6.join(process.env.HOME || "", "repos", "thoth"),
31165
- path6.join(process.env.HOME || "", "Projects", "thoth"),
31166
- path6.join(process.env.HOME || "", "projects", "thoth"),
31167
- path6.join(process.env.HOME || "", "thoth"),
31168
- path6.join(directory, "thoth")
31480
+ path9.join(process.env.HOME || "", "Repos", "thoth"),
31481
+ path9.join(process.env.HOME || "", "repos", "thoth"),
31482
+ path9.join(process.env.HOME || "", "Projects", "thoth"),
31483
+ path9.join(process.env.HOME || "", "projects", "thoth"),
31484
+ path9.join(process.env.HOME || "", "thoth"),
31485
+ path9.join(directory, "thoth")
31169
31486
  ];
31170
31487
  for (const location of commonLocations) {
31171
- const kernelPath = path6.join(location, "kernel");
31488
+ const kernelPath = path9.join(location, "kernel");
31172
31489
  if (fs3.existsSync(kernelPath)) {
31173
31490
  log(`Found knowledge base at: ${location}`);
31174
31491
  return location;
@@ -31189,6 +31506,9 @@ var ThothPlugin = async (ctx) => {
31189
31506
  const trustLevelTracker = hooksConfig["trust-level-tracker"] !== false ? createTrustLevelTrackerHook({ knowledgeBasePath }) : null;
31190
31507
  const contextAperture = hooksConfig["context-aperture"] !== false ? createContextApertureHook({ knowledgeBasePath }) : null;
31191
31508
  const temporalAwareness = hooksConfig["temporal-awareness"] !== false ? createTemporalAwarenessHook() : null;
31509
+ const frontmatterEnforcer = hooksConfig["frontmatter-enforcer"] !== false ? createFrontmatterEnforcerHook({ knowledgeBasePath }) : null;
31510
+ const readConfirmation = hooksConfig["read-confirmation"] !== false ? createReadConfirmationHook({ knowledgeBasePath }) : null;
31511
+ const writeConfirmation = hooksConfig["write-confirmation"] !== false ? createWriteConfirmationHook({ knowledgeBasePath }) : null;
31192
31512
  const todoContinuationEnforcer = hooksConfig["todo-continuation"] !== false ? createTodoContinuationEnforcer(ctx) : null;
31193
31513
  const sessionRecoveryHook = hooksConfig["session-recovery"] !== false ? createSessionRecoveryHook(ctx, { experimental: { auto_resume: true } }) : null;
31194
31514
  const contextWindowMonitor = hooksConfig["context-window-monitor"] !== false ? createContextWindowMonitorHook(ctx) : null;
@@ -31293,10 +31613,16 @@ var ThothPlugin = async (ctx) => {
31293
31613
  await temporalAwareness?.["tool.execute.before"]?.(input, output);
31294
31614
  await contextAperture?.["tool.execute.before"]?.(input, output);
31295
31615
  await trustLevelTracker?.["tool.execute.before"]?.(input, output);
31616
+ await frontmatterEnforcer?.["tool.execute.before"]?.(input, output);
31617
+ await readConfirmation?.["tool.execute.before"]?.(input, output);
31618
+ await writeConfirmation?.["tool.execute.before"]?.(input, output);
31296
31619
  },
31297
31620
  "tool.execute.after": async (input, output) => {
31298
31621
  await trustLevelTracker?.["tool.execute.after"]?.(input, output);
31299
31622
  await contextAperture?.["tool.execute.after"]?.(input, output);
31623
+ await frontmatterEnforcer?.["tool.execute.after"]?.(input, output);
31624
+ await readConfirmation?.["tool.execute.after"]?.(input, output);
31625
+ await writeConfirmation?.["tool.execute.after"]?.(input, output);
31300
31626
  await directoryAgentsInjector?.["tool.execute.after"]?.(input, output);
31301
31627
  await contextWindowMonitor?.["tool.execute.after"]?.(input, output);
31302
31628
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thoth-plugin",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "Thoth - Root-level life orchestrator for OpenCode. Unified AI chief of staff combining Sisyphus execution quality, Personal-OS rhythms, and Thoth relationship model.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",