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/README.md +237 -31
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +248 -5
- package/dist/config/schema.d.ts +6 -0
- package/dist/defaults/skill/email-draft/skill.md +134 -0
- package/dist/defaults/skill/evening-close/SKILL.md +7 -0
- package/dist/defaults/skill/gardener/SKILL.md +36 -1
- package/dist/defaults/skill/leadership-coach/SKILL.md +7 -0
- package/dist/defaults/skill/mail-triage/SKILL.md +7 -0
- package/dist/defaults/skill/morning-boot/SKILL.md +8 -0
- package/dist/defaults/skill/post-meeting-drill/SKILL.md +6 -0
- package/dist/defaults/skill/skill-generator/SKILL.md +150 -28
- package/dist/defaults/skill/slack-pulse/SKILL.md +6 -0
- package/dist/defaults/skill/thought-router/SKILL.md +7 -0
- package/dist/hooks/frontmatter-enforcer.d.ts +24 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/read-confirmation.d.ts +34 -0
- package/dist/hooks/write-confirmation.d.ts +34 -0
- package/dist/index.js +387 -61
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
552
|
+
When Zeus asks "What do I know about X?", "Who is Y?", "Context on Z?":
|
|
508
553
|
|
|
509
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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
|
-
|
|
579
|
+
## Functional Agents (For Specialized Tasks)
|
|
516
580
|
|
|
517
|
-
|
|
581
|
+
These agents handle tasks that DON'T require session context:
|
|
518
582
|
|
|
519
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
|
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:
|
|
3453
|
-
const relativePath =
|
|
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
|
|
3541
|
-
var logFile =
|
|
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,
|
|
5342
|
-
if (!
|
|
5655
|
+
function getElementAtPath(obj, path9) {
|
|
5656
|
+
if (!path9)
|
|
5343
5657
|
return obj;
|
|
5344
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
5918
|
-
for (const seg of
|
|
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((
|
|
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
|
|
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
|
|
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 ||
|
|
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,
|
|
18299
|
-
if (!
|
|
18612
|
+
function getElementAtPath2(obj, path9) {
|
|
18613
|
+
if (!path9)
|
|
18300
18614
|
return obj;
|
|
18301
|
-
return
|
|
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(
|
|
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(
|
|
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,
|
|
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 = [...
|
|
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
|
|
18912
|
-
for (const seg of
|
|
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
|
|
30660
|
-
if (
|
|
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 (
|
|
30665
|
-
const key =
|
|
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
|
|
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 =
|
|
31149
|
-
const projectConfigPath =
|
|
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
|
-
|
|
31164
|
-
|
|
31165
|
-
|
|
31166
|
-
|
|
31167
|
-
|
|
31168
|
-
|
|
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 =
|
|
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.
|
|
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",
|