Parent directory

planner.ts

1800 bytes
 1import type { FileSystem } from "../ports/fs.ts";
 2import type { FrontmatterCodec } from "../frontmatter/parser.ts";
 3import type { FrontmatterPatcher } from "../frontmatter/patcher.ts";
 4import type { SkillChange, SkillDraft, SkillRecord } from "../types.ts";
 5import { hasDuplicateDisableModelInvocation } from "../frontmatter/validation.ts";
 6import { classifyInvocationMode } from "../inventory/classifier.ts";
 7
 8export interface SkillTogglePlanner {
 9  plan(records: SkillRecord[], drafts: SkillDraft[]): Promise<SkillChange[]>;
10}
11
12export class DefaultSkillTogglePlanner implements SkillTogglePlanner {
13  constructor(
14    private readonly fs: FileSystem,
15    private readonly codec: FrontmatterCodec,
16    private readonly patcher: FrontmatterPatcher,
17  ) {}
18
19  async plan(records: SkillRecord[], drafts: SkillDraft[]): Promise<SkillChange[]> {
20    const recordById = new Map(records.map((record) => [record.id, record]));
21    const changes: SkillChange[] = [];
22
23    for (const draft of drafts) {
24      const record = recordById.get(draft.skill.id);
25      if (!record || !record.editable) continue;
26
27      const raw = await this.fs.readFile(record.filePath);
28      const doc = this.codec.parse(raw);
29      if (!doc.hasFrontmatter) continue;
30
31      const currentMode = classifyInvocationMode(doc);
32      const needsNormalization = hasDuplicateDisableModelInvocation(doc);
33      if (currentMode === draft.desiredMode && !needsNormalization) continue;
34
35      const patch = this.patcher.patchInvocationMode(doc, draft.desiredMode);
36      if (patch.oldText === patch.newText) continue;
37
38      changes.push({
39        skill: { ...record, mode: currentMode },
40        filePath: record.filePath,
41        from: currentMode,
42        to: draft.desiredMode,
43        patch,
44      });
45    }
46
47    return changes;
48  }
49}