@open-tomato/rafa 0.18.0 → 0.20.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 +36 -14
- package/dist/PROMPT.md +2 -1
- package/dist/bundled/skills/dev-planner/SKILL.md +9 -0
- package/dist/cli.js +3937 -1705
- package/dist/{describe-vq037dyt.js → describe-0tefrkk7.js} +4 -3
- package/dist/effort/store/index.js +6 -4
- package/dist/{index-6vkd153j.js → index-02fx0mzf.js} +4713 -3848
- package/dist/{index-0pdsq2hy.js → index-21qkxk63.js} +11 -224
- package/dist/{index-ddf85qac.js → index-28jzpyks.js} +1 -1
- package/dist/{index-kvnggvyk.js → index-4a45ffqh.js} +2 -2
- package/dist/{index-at9f1z2z.js → index-afersvp3.js} +1028 -51
- package/dist/index-d6d0942q.js +218 -0
- package/dist/{index-944pncd8.js → index-pmkgvtfe.js} +14 -2
- package/dist/{index-jva32qe6.js → index-twbp38kz.js} +290 -39
- package/dist/index-wp8c28fd.js +18 -0
- package/dist/{index-cvpdvmtg.js → index-zhh1h3h9.js} +1023 -935
- package/dist/index.js +13 -11
- package/dist/learning/index.js +25 -0
- package/dist/plan/index.js +8 -7
- package/dist/plan-prompt.md +2 -0
- package/package.json +4 -3
|
@@ -1,20 +1,38 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
+
CONFIG_DEFAULTS,
|
|
4
|
+
FINDING_DOMAINS,
|
|
5
|
+
FINDING_KINDS,
|
|
6
|
+
FINDING_SIGNALS,
|
|
3
7
|
activeOutput,
|
|
4
8
|
activeOutputMode,
|
|
5
9
|
createTextOutput,
|
|
6
10
|
readRafaBlocks
|
|
7
|
-
} from "./index-
|
|
11
|
+
} from "./index-twbp38kz.js";
|
|
12
|
+
import {
|
|
13
|
+
bless,
|
|
14
|
+
merge
|
|
15
|
+
} from "./index-d6d0942q.js";
|
|
8
16
|
import {
|
|
9
17
|
commandSpelling
|
|
10
|
-
} from "./index-
|
|
18
|
+
} from "./index-28jzpyks.js";
|
|
11
19
|
import {
|
|
20
|
+
RAFA_INSTINCTS_PATH,
|
|
12
21
|
STORE_BACKENDS,
|
|
13
22
|
bundledBinDirectory,
|
|
14
23
|
describeValue,
|
|
15
24
|
isMapping,
|
|
16
|
-
messageOf
|
|
17
|
-
|
|
25
|
+
messageOf,
|
|
26
|
+
resolveInstinctScopes,
|
|
27
|
+
tierExists
|
|
28
|
+
} from "./index-pmkgvtfe.js";
|
|
29
|
+
import {
|
|
30
|
+
actionHash,
|
|
31
|
+
triggerKey
|
|
32
|
+
} from "./index-wp8c28fd.js";
|
|
33
|
+
|
|
34
|
+
// src/adapters/registry.ts
|
|
35
|
+
import { homedir } from "os";
|
|
18
36
|
|
|
19
37
|
// src/effort/store.ts
|
|
20
38
|
import {
|
|
@@ -316,6 +334,27 @@ var SQLITE_MIGRATIONS = [
|
|
|
316
334
|
`
|
|
317
335
|
ALTER TABLE out_of_scope_bugs
|
|
318
336
|
ADD COLUMN scope TEXT CHECK (scope IN ('machine', 'rafa'));
|
|
337
|
+
`,
|
|
338
|
+
`
|
|
339
|
+
ALTER TABLE dispatches
|
|
340
|
+
ADD COLUMN resolver TEXT CHECK (resolver IN ('planner', 'tag', 'none'));
|
|
341
|
+
ALTER TABLE dispatches
|
|
342
|
+
ADD COLUMN skills_offered TEXT CHECK (skills_offered IS NULL OR json_type(skills_offered) = 'array');
|
|
343
|
+
ALTER TABLE dispatches
|
|
344
|
+
ADD COLUMN lessons_offered TEXT CHECK (lessons_offered IS NULL OR json_type(lessons_offered) = 'array');
|
|
345
|
+
`,
|
|
346
|
+
`
|
|
347
|
+
CREATE TABLE skill_invocations (
|
|
348
|
+
seq INTEGER PRIMARY KEY,
|
|
349
|
+
session_id TEXT NOT NULL CHECK (session_id <> ''),
|
|
350
|
+
name TEXT CHECK (name <> ''),
|
|
351
|
+
sidechain INTEGER CHECK (sidechain IN (0, 1)),
|
|
352
|
+
count INTEGER CHECK (count IS NULL OR (typeof(count) = 'integer' AND count > 0)),
|
|
353
|
+
CHECK ((name IS NULL) = (count IS NULL) AND (name IS NULL) = (sidechain IS NULL))
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
CREATE UNIQUE INDEX skill_invocations_by_use
|
|
357
|
+
ON skill_invocations (session_id, ifnull(name, ''), ifnull(sidechain, -1));
|
|
319
358
|
`
|
|
320
359
|
];
|
|
321
360
|
var SQLITE_SCHEMA_VERSION = SQLITE_MIGRATIONS.length;
|
|
@@ -440,11 +479,812 @@ function openSqliteStore(repoRoot) {
|
|
|
440
479
|
}
|
|
441
480
|
|
|
442
481
|
// src/adapters/learning/local.ts
|
|
443
|
-
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
444
|
-
import { join as
|
|
482
|
+
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
483
|
+
import { join as join5 } from "path";
|
|
484
|
+
|
|
485
|
+
// src/commands/instinct/instinct-records.ts
|
|
486
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
487
|
+
import { join as join4 } from "path";
|
|
488
|
+
|
|
489
|
+
// src/check/layout.ts
|
|
490
|
+
import { readdirSync, statSync } from "fs";
|
|
491
|
+
import { join as join3, relative } from "path";
|
|
492
|
+
var SKILL_FILE = "SKILL.md";
|
|
493
|
+
var MARKDOWN_EXTENSION = ".md";
|
|
494
|
+
var LAYOUT_SEVERITY = Object.freeze({
|
|
495
|
+
"flat-grouped-layout": "failure",
|
|
496
|
+
"nested-group-layout": "failure",
|
|
497
|
+
"loose-file": "failure",
|
|
498
|
+
"missing-skill-file": "failure",
|
|
499
|
+
"nested-instinct-file": "failure",
|
|
500
|
+
"unreadable-entry": "failure",
|
|
501
|
+
"name-mismatch": "failure",
|
|
502
|
+
"dotfile-only-directory": "warning"
|
|
503
|
+
});
|
|
504
|
+
function layoutIssue(code, message) {
|
|
505
|
+
return { code, severity: LAYOUT_SEVERITY[code], message };
|
|
506
|
+
}
|
|
507
|
+
function entryKind(path) {
|
|
508
|
+
try {
|
|
509
|
+
const stats = statSync(path);
|
|
510
|
+
if (stats.isFile())
|
|
511
|
+
return "file";
|
|
512
|
+
if (stats.isDirectory())
|
|
513
|
+
return "directory";
|
|
514
|
+
return null;
|
|
515
|
+
} catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
function visibleNames(dir) {
|
|
520
|
+
return readdirSync(dir).filter((name) => !name.startsWith(".")).sort((left, right) => left.localeCompare(right));
|
|
521
|
+
}
|
|
522
|
+
function isMarkdown(name) {
|
|
523
|
+
return name.endsWith(MARKDOWN_EXTENSION);
|
|
524
|
+
}
|
|
525
|
+
function markdownStem(name) {
|
|
526
|
+
return name.slice(0, -MARKDOWN_EXTENSION.length);
|
|
527
|
+
}
|
|
528
|
+
function shown(root, path) {
|
|
529
|
+
return relative(root, path) || ".";
|
|
530
|
+
}
|
|
531
|
+
function dotfileOnlyEntry(kind, root, dir) {
|
|
532
|
+
return {
|
|
533
|
+
kind,
|
|
534
|
+
path: dir,
|
|
535
|
+
isFile: false,
|
|
536
|
+
shape: "dotfile-only",
|
|
537
|
+
name: null,
|
|
538
|
+
issues: [layoutIssue("dotfile-only-directory", `${shown(root, dir)}/ holds nothing but dotfiles, so it registers nothing`)]
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
function unreadableEntry(kind, root, path) {
|
|
542
|
+
return {
|
|
543
|
+
kind,
|
|
544
|
+
path,
|
|
545
|
+
isFile: false,
|
|
546
|
+
shape: "unreadable",
|
|
547
|
+
name: null,
|
|
548
|
+
issues: [layoutIssue("unreadable-entry", `${shown(root, path)} cannot be read (a broken symlink, or a path stat refuses)`)]
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function skillFileEntry(path, name) {
|
|
552
|
+
return { kind: "skill", path, isFile: true, shape: "skill-file", name, issues: [] };
|
|
553
|
+
}
|
|
554
|
+
function flatGroupedEntry(root, path, name) {
|
|
555
|
+
return {
|
|
556
|
+
kind: "skill",
|
|
557
|
+
path,
|
|
558
|
+
isFile: true,
|
|
559
|
+
shape: "flat-grouped",
|
|
560
|
+
name,
|
|
561
|
+
issues: [layoutIssue("flat-grouped-layout", `${shown(root, path)} is the flat grouped layout <dir>/<group>/<name>.md,` + ` which does not register; move it to <dir>/${name}/${SKILL_FILE}`)]
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function nestedGroupEntry(root, path, name) {
|
|
565
|
+
return {
|
|
566
|
+
kind: "skill",
|
|
567
|
+
path,
|
|
568
|
+
isFile: true,
|
|
569
|
+
shape: "nested-group",
|
|
570
|
+
name,
|
|
571
|
+
issues: [layoutIssue("nested-group-layout", `${shown(root, path)} is the nested layout <dir>/<group>/<name>/${SKILL_FILE},` + ` which does not register; move it to <dir>/${name}/${SKILL_FILE}`)]
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
function looseFileEntry(root, path, name) {
|
|
575
|
+
return {
|
|
576
|
+
kind: "skill",
|
|
577
|
+
path,
|
|
578
|
+
isFile: true,
|
|
579
|
+
shape: "loose-file",
|
|
580
|
+
name,
|
|
581
|
+
issues: [layoutIssue("loose-file", `${shown(root, path)} sits directly under the skills directory, which registers` + ` only <dir>/<name>/${SKILL_FILE}; move it to <dir>/${name}/${SKILL_FILE}`)]
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function noSkillFileEntry(root, dir) {
|
|
585
|
+
return {
|
|
586
|
+
kind: "skill",
|
|
587
|
+
path: dir,
|
|
588
|
+
isFile: false,
|
|
589
|
+
shape: "no-skill-file",
|
|
590
|
+
name: null,
|
|
591
|
+
issues: [layoutIssue("missing-skill-file", `${shown(root, dir)}/ holds no ${SKILL_FILE}, so it registers no skill`)]
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
function nestedInstinctEntry(root, path, name) {
|
|
595
|
+
return {
|
|
596
|
+
kind: "instinct",
|
|
597
|
+
path,
|
|
598
|
+
isFile: true,
|
|
599
|
+
shape: "nested-instinct",
|
|
600
|
+
name,
|
|
601
|
+
issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${name}${MARKDOWN_EXTENSION}`)]
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function scanGroupMember(root, dir, name) {
|
|
605
|
+
const children = visibleNames(dir);
|
|
606
|
+
if (children.length === 0)
|
|
607
|
+
return [dotfileOnlyEntry("skill", root, dir)];
|
|
608
|
+
const skillPath = join3(dir, SKILL_FILE);
|
|
609
|
+
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
610
|
+
return [nestedGroupEntry(root, skillPath, name)];
|
|
611
|
+
}
|
|
612
|
+
return [noSkillFileEntry(root, dir)];
|
|
613
|
+
}
|
|
614
|
+
function scanSkillGroup(root, dir, children) {
|
|
615
|
+
const entries = [];
|
|
616
|
+
for (const child of children) {
|
|
617
|
+
const path = join3(dir, child);
|
|
618
|
+
const found = entryKind(path);
|
|
619
|
+
if (found === null) {
|
|
620
|
+
entries.push(unreadableEntry("skill", root, path));
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (found === "directory") {
|
|
624
|
+
entries.push(...scanGroupMember(root, path, child));
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (isMarkdown(child))
|
|
628
|
+
entries.push(flatGroupedEntry(root, path, markdownStem(child)));
|
|
629
|
+
}
|
|
630
|
+
if (entries.length === 0)
|
|
631
|
+
return [noSkillFileEntry(root, dir)];
|
|
632
|
+
return entries;
|
|
633
|
+
}
|
|
634
|
+
function scanSkillDirectory(root, dir, name) {
|
|
635
|
+
const children = visibleNames(dir);
|
|
636
|
+
if (children.length === 0)
|
|
637
|
+
return [dotfileOnlyEntry("skill", root, dir)];
|
|
638
|
+
const skillPath = join3(dir, SKILL_FILE);
|
|
639
|
+
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
640
|
+
return [skillFileEntry(skillPath, name)];
|
|
641
|
+
}
|
|
642
|
+
return scanSkillGroup(root, dir, children);
|
|
643
|
+
}
|
|
644
|
+
function scanSkillRoot(root) {
|
|
645
|
+
const entries = [];
|
|
646
|
+
for (const name of visibleNames(root)) {
|
|
647
|
+
const path = join3(root, name);
|
|
648
|
+
const found = entryKind(path);
|
|
649
|
+
if (found === null) {
|
|
650
|
+
entries.push(unreadableEntry("skill", root, path));
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (found === "directory") {
|
|
654
|
+
entries.push(...scanSkillDirectory(root, path, name));
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (isMarkdown(name))
|
|
658
|
+
entries.push(looseFileEntry(root, path, markdownStem(name)));
|
|
659
|
+
}
|
|
660
|
+
return entries;
|
|
661
|
+
}
|
|
662
|
+
function scanInstinctSubdirectory(root, dir) {
|
|
663
|
+
const children = visibleNames(dir);
|
|
664
|
+
if (children.length === 0)
|
|
665
|
+
return [dotfileOnlyEntry("instinct", root, dir)];
|
|
666
|
+
const entries = [];
|
|
667
|
+
for (const child of children) {
|
|
668
|
+
const path = join3(dir, child);
|
|
669
|
+
const found = entryKind(path);
|
|
670
|
+
if (found === null) {
|
|
671
|
+
entries.push(unreadableEntry("instinct", root, path));
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
if (found === "directory") {
|
|
675
|
+
entries.push(...scanInstinctSubdirectory(root, path));
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
if (isMarkdown(child))
|
|
679
|
+
entries.push(nestedInstinctEntry(root, path, markdownStem(child)));
|
|
680
|
+
}
|
|
681
|
+
return entries;
|
|
682
|
+
}
|
|
683
|
+
function scanInstinctRoot(root) {
|
|
684
|
+
const entries = [];
|
|
685
|
+
for (const name of visibleNames(root)) {
|
|
686
|
+
const path = join3(root, name);
|
|
687
|
+
const found = entryKind(path);
|
|
688
|
+
if (found === null) {
|
|
689
|
+
entries.push(unreadableEntry("instinct", root, path));
|
|
690
|
+
continue;
|
|
691
|
+
}
|
|
692
|
+
if (found === "directory") {
|
|
693
|
+
entries.push(...scanInstinctSubdirectory(root, path));
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (!isMarkdown(name))
|
|
697
|
+
continue;
|
|
698
|
+
entries.push({
|
|
699
|
+
kind: "instinct",
|
|
700
|
+
path,
|
|
701
|
+
isFile: true,
|
|
702
|
+
shape: "instinct-file",
|
|
703
|
+
name: markdownStem(name),
|
|
704
|
+
issues: []
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
return entries;
|
|
708
|
+
}
|
|
709
|
+
function scanLayout(root, kind) {
|
|
710
|
+
if (entryKind(root) !== "directory") {
|
|
711
|
+
throw new Error(`check layout: no ${kind} directory at ${root}`);
|
|
712
|
+
}
|
|
713
|
+
const entries = kind === "skill" ? scanSkillRoot(root) : scanInstinctRoot(root);
|
|
714
|
+
return {
|
|
715
|
+
kind,
|
|
716
|
+
root,
|
|
717
|
+
entries: entries.sort((left, right) => left.path.localeCompare(right.path))
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
function declaredNameIssue(entry, declared) {
|
|
721
|
+
if (entry.name === null || declared === null || declared === entry.name)
|
|
722
|
+
return null;
|
|
723
|
+
const field = entry.kind === "skill" ? "name" : "id";
|
|
724
|
+
const source = entry.kind === "skill" ? "directory name" : "file name";
|
|
725
|
+
return layoutIssue("name-mismatch", `frontmatter ${field} "${declared}" is not the ${source} "${entry.name}"`);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/schema/frontmatter.ts
|
|
729
|
+
var FRONTMATTER_FENCE = "---";
|
|
730
|
+
var FRONTMATTER_INDENT = 2;
|
|
731
|
+
function scanLine(text, offset) {
|
|
732
|
+
const index = text.indexOf(`
|
|
733
|
+
`, offset);
|
|
734
|
+
if (index === -1) {
|
|
735
|
+
return { line: text.slice(offset), newline: "", next: text.length };
|
|
736
|
+
}
|
|
737
|
+
const carriage = index > offset && text[index - 1] === "\r";
|
|
738
|
+
const end = carriage ? index - 1 : index;
|
|
739
|
+
return {
|
|
740
|
+
line: text.slice(offset, end),
|
|
741
|
+
newline: text.slice(end, index + 1),
|
|
742
|
+
next: index + 1
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
function isRecord(value) {
|
|
746
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
747
|
+
}
|
|
748
|
+
function parseBlock(yaml) {
|
|
749
|
+
let parsed;
|
|
750
|
+
try {
|
|
751
|
+
parsed = Bun.YAML.parse(yaml.replace(/\r\n/g, `
|
|
752
|
+
`));
|
|
753
|
+
} catch {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
return isRecord(parsed) ? parsed : null;
|
|
757
|
+
}
|
|
758
|
+
function readFrontmatterDocument(text) {
|
|
759
|
+
const opening = scanLine(text, 0);
|
|
760
|
+
if (opening.line !== FRONTMATTER_FENCE || opening.newline === "")
|
|
761
|
+
return null;
|
|
762
|
+
let offset = opening.next;
|
|
763
|
+
while (offset < text.length) {
|
|
764
|
+
const scanned = scanLine(text, offset);
|
|
765
|
+
if (scanned.line === FRONTMATTER_FENCE) {
|
|
766
|
+
const data = parseBlock(text.slice(opening.next, offset));
|
|
767
|
+
return data === null ? null : {
|
|
768
|
+
data,
|
|
769
|
+
yaml: text.slice(opening.next, offset),
|
|
770
|
+
body: text.slice(scanned.next),
|
|
771
|
+
newline: opening.newline,
|
|
772
|
+
closingNewline: scanned.newline
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
offset = scanned.next;
|
|
776
|
+
}
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
function readFrontmatter(text) {
|
|
780
|
+
return readFrontmatterDocument(text)?.data ?? null;
|
|
781
|
+
}
|
|
782
|
+
function renderFrontmatter(data, newline = `
|
|
783
|
+
`) {
|
|
784
|
+
return Bun.YAML.stringify(data, null, FRONTMATTER_INDENT).split(`
|
|
785
|
+
`).map((line) => line.replace(/[ \t]+$/, "")).join(newline);
|
|
786
|
+
}
|
|
787
|
+
function mergeFrontmatter(data, changes) {
|
|
788
|
+
const merged = {};
|
|
789
|
+
for (const [key, value] of Object.entries(data)) {
|
|
790
|
+
if (!Object.hasOwn(changes, key))
|
|
791
|
+
merged[key] = value;
|
|
792
|
+
else if (changes[key] !== undefined)
|
|
793
|
+
merged[key] = changes[key];
|
|
794
|
+
}
|
|
795
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
796
|
+
if (value !== undefined && !Object.hasOwn(data, key))
|
|
797
|
+
merged[key] = value;
|
|
798
|
+
}
|
|
799
|
+
return merged;
|
|
800
|
+
}
|
|
801
|
+
function writeFrontmatter(document, data) {
|
|
802
|
+
const block = data === undefined ? document.yaml : `${renderFrontmatter(data, document.newline)}${document.newline}`;
|
|
803
|
+
return [
|
|
804
|
+
FRONTMATTER_FENCE,
|
|
805
|
+
document.newline,
|
|
806
|
+
block,
|
|
807
|
+
FRONTMATTER_FENCE,
|
|
808
|
+
document.closingNewline,
|
|
809
|
+
document.body
|
|
810
|
+
].join("");
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// src/schema/project-id.ts
|
|
814
|
+
import { spawnSync } from "child_process";
|
|
815
|
+
var PROJECT_ID_LENGTH = 12;
|
|
816
|
+
var PROJECT_ID_PATTERN = new RegExp(`^[0-9a-f]{${PROJECT_ID_LENGTH}}$`);
|
|
817
|
+
function hasColonAfterAt(url) {
|
|
818
|
+
const at = url.indexOf("@");
|
|
819
|
+
return at !== -1 && url.lastIndexOf(":") > at;
|
|
820
|
+
}
|
|
821
|
+
function normalizeRemote(url) {
|
|
822
|
+
const stripped = url.replace(/:\/\/[^@]+@/, "://");
|
|
823
|
+
const network = !stripped.startsWith("file://") && (stripped.includes("://") || hasColonAfterAt(stripped));
|
|
824
|
+
const trimmed = stripped.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "").replace(/^[^@/:]+@([^:/]+):/, "$1/").replace(/\.git\/?$/, "").replace(/\/+$/, "");
|
|
825
|
+
return network ? trimmed.toLowerCase() : trimmed;
|
|
826
|
+
}
|
|
827
|
+
function gitRemoteUrl(dir) {
|
|
828
|
+
const result = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
829
|
+
cwd: dir,
|
|
830
|
+
encoding: "utf8",
|
|
831
|
+
env: { ...process.env, LC_ALL: "C" }
|
|
832
|
+
});
|
|
833
|
+
if (result.error !== undefined || result.status !== 0)
|
|
834
|
+
return null;
|
|
835
|
+
const url = result.stdout.trim();
|
|
836
|
+
return url === "" ? null : url;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// src/schema/instinct.ts
|
|
840
|
+
var INSTINCT_KINDS = FINDING_KINDS;
|
|
841
|
+
var INSTINCT_SIGNALS = FINDING_SIGNALS;
|
|
842
|
+
var INSTINCT_DOMAINS = FINDING_DOMAINS;
|
|
843
|
+
var INSTINCT_SCOPES = ["project", "user"];
|
|
844
|
+
var INSTINCT_SOURCES = ["task-report", "loop-observed", "imported", "demoted"];
|
|
845
|
+
var CONFIDENCE_MIN = 0.3;
|
|
846
|
+
var CONFIDENCE_MAX = 0.9;
|
|
847
|
+
var DEFAULT_USAGE_COUNT = 1;
|
|
848
|
+
var EVIDENCE_REQUIRED_SOURCE = "task-report";
|
|
849
|
+
var ACTION_HEADING = "## Action";
|
|
850
|
+
var CAUSE_HEADING = "## Cause";
|
|
851
|
+
var INSTINCT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
852
|
+
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
853
|
+
var INSTINCT_KEY_ORDER = [
|
|
854
|
+
"id",
|
|
855
|
+
"trigger",
|
|
856
|
+
"kind",
|
|
857
|
+
"domain",
|
|
858
|
+
"confidence",
|
|
859
|
+
"usage_count",
|
|
860
|
+
"sources",
|
|
861
|
+
"artifact",
|
|
862
|
+
"signal",
|
|
863
|
+
"scope",
|
|
864
|
+
"project_id",
|
|
865
|
+
"source",
|
|
866
|
+
"evidence",
|
|
867
|
+
"promoted_to",
|
|
868
|
+
"created_at",
|
|
869
|
+
"updated_at"
|
|
870
|
+
];
|
|
871
|
+
function issue(code, field, message) {
|
|
872
|
+
return { code, field, message };
|
|
873
|
+
}
|
|
874
|
+
function typeName(value) {
|
|
875
|
+
if (value === null)
|
|
876
|
+
return "an empty value";
|
|
877
|
+
if (Array.isArray(value))
|
|
878
|
+
return "a list";
|
|
879
|
+
if (typeof value === "object")
|
|
880
|
+
return "a mapping";
|
|
881
|
+
if (typeof value === "boolean")
|
|
882
|
+
return "a boolean";
|
|
883
|
+
if (typeof value === "number")
|
|
884
|
+
return "a number";
|
|
885
|
+
return `a ${typeof value}`;
|
|
886
|
+
}
|
|
887
|
+
function requiredString(data, field) {
|
|
888
|
+
const value = data[field];
|
|
889
|
+
if (value === undefined || value === null) {
|
|
890
|
+
return [issue("missing-field", field, `${field} is required and is absent`)];
|
|
891
|
+
}
|
|
892
|
+
if (typeof value !== "string") {
|
|
893
|
+
return [issue("wrong-type", field, `${field} must be a string, not ${typeName(value)}`)];
|
|
894
|
+
}
|
|
895
|
+
if (value.trim() === "") {
|
|
896
|
+
return [issue("missing-field", field, `${field} is required and is empty`)];
|
|
897
|
+
}
|
|
898
|
+
return [];
|
|
899
|
+
}
|
|
900
|
+
function requiredChoice(data, field, allowed, code) {
|
|
901
|
+
const issues = requiredString(data, field);
|
|
902
|
+
if (issues.length > 0)
|
|
903
|
+
return issues;
|
|
904
|
+
const value = data[field];
|
|
905
|
+
return allowed.includes(value) ? [] : [issue(code, field, `${field} "${value}" is not one of ${allowed.join(", ")}`)];
|
|
906
|
+
}
|
|
907
|
+
function checkId(data) {
|
|
908
|
+
const issues = requiredString(data, "id");
|
|
909
|
+
if (issues.length > 0)
|
|
910
|
+
return issues;
|
|
911
|
+
const id = data["id"];
|
|
912
|
+
return INSTINCT_ID_PATTERN.test(id) ? [] : [issue("invalid-id", "id", `id "${id}" is not lowercase words joined by single hyphens`)];
|
|
913
|
+
}
|
|
914
|
+
function checkConfidence(data) {
|
|
915
|
+
const value = data["confidence"];
|
|
916
|
+
if (value === undefined || value === null) {
|
|
917
|
+
return [issue("missing-field", "confidence", "confidence is required and is absent")];
|
|
918
|
+
}
|
|
919
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
920
|
+
return [issue("wrong-type", "confidence", `confidence must be a number, not ${typeName(value)}`)];
|
|
921
|
+
}
|
|
922
|
+
return value >= CONFIDENCE_MIN && value <= CONFIDENCE_MAX ? [] : [issue("confidence-out-of-range", "confidence", `confidence ${value} is outside ${CONFIDENCE_MIN} to ${CONFIDENCE_MAX}`)];
|
|
923
|
+
}
|
|
924
|
+
function checkUsageCount(data) {
|
|
925
|
+
const value = data["usage_count"];
|
|
926
|
+
if (value === undefined)
|
|
927
|
+
return [];
|
|
928
|
+
if (typeof value !== "number") {
|
|
929
|
+
return [issue("wrong-type", "usage_count", `usage_count must be a number, not ${typeName(value)}`)];
|
|
930
|
+
}
|
|
931
|
+
return Number.isInteger(value) && value >= DEFAULT_USAGE_COUNT ? [] : [issue("invalid-usage-count", "usage_count", `usage_count ${value} is not a whole number of at least ${DEFAULT_USAGE_COUNT}`)];
|
|
932
|
+
}
|
|
933
|
+
function checkSources(data) {
|
|
934
|
+
const value = data["sources"];
|
|
935
|
+
if (value === undefined)
|
|
936
|
+
return [];
|
|
937
|
+
if (!Array.isArray(value)) {
|
|
938
|
+
return [issue("wrong-type", "sources", `sources must be a list, not ${typeName(value)}`)];
|
|
939
|
+
}
|
|
940
|
+
return value.flatMap((entry, index) => {
|
|
941
|
+
const field = `sources[${index}]`;
|
|
942
|
+
if (typeof entry !== "string") {
|
|
943
|
+
return [issue("wrong-type", field, `sources entries must be strings, and this one is ${typeName(entry)}`)];
|
|
944
|
+
}
|
|
945
|
+
return entry.trim() === "" ? [issue("wrong-type", field, "sources entries must name a source, and this one is blank")] : [];
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
function checkPromotedTo(data) {
|
|
949
|
+
const value = data["promoted_to"];
|
|
950
|
+
if (value === undefined)
|
|
951
|
+
return [];
|
|
952
|
+
if (typeof value !== "string") {
|
|
953
|
+
return [issue("wrong-type", "promoted_to", `promoted_to must be a string, not ${typeName(value)}`)];
|
|
954
|
+
}
|
|
955
|
+
const absolute = /^(?:[/\\]|[A-Za-z]:)/.test(value);
|
|
956
|
+
const climbs = value.split(/[/\\]/).includes("..");
|
|
957
|
+
return value.trim() !== "" && !absolute && !climbs ? [] : [issue("invalid-promoted-to", "promoted_to", `promoted_to "${value}" is not a path inside the repository such as context/source.md`)];
|
|
958
|
+
}
|
|
959
|
+
function checkArtifact(data) {
|
|
960
|
+
const value = data["artifact"];
|
|
961
|
+
if (value === undefined || typeof value === "string")
|
|
962
|
+
return [];
|
|
963
|
+
return [issue("wrong-type", "artifact", `artifact must be a string, not ${typeName(value)}`)];
|
|
964
|
+
}
|
|
965
|
+
function checkProjectId(data) {
|
|
966
|
+
const value = data["project_id"];
|
|
967
|
+
if (value === undefined)
|
|
968
|
+
return [];
|
|
969
|
+
if (typeof value !== "string") {
|
|
970
|
+
return [issue("wrong-type", "project_id", `project_id must be a string, not ${typeName(value)}`)];
|
|
971
|
+
}
|
|
972
|
+
return PROJECT_ID_PATTERN.test(value) ? [] : [issue("invalid-project-id", "project_id", `project_id "${value}" is not ${PROJECT_ID_LENGTH} lowercase hex characters`)];
|
|
973
|
+
}
|
|
974
|
+
function checkTimestamp(data, field) {
|
|
975
|
+
const issues = requiredString(data, field);
|
|
976
|
+
if (issues.length > 0)
|
|
977
|
+
return issues;
|
|
978
|
+
const value = data[field];
|
|
979
|
+
return TIMESTAMP_PATTERN.test(value) && !Number.isNaN(Date.parse(value)) ? [] : [issue("invalid-timestamp", field, `${field} "${value}" is not an ISO 8601 instant such as 2026-09-11T10:00:00Z`)];
|
|
980
|
+
}
|
|
981
|
+
function isScalar(value) {
|
|
982
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
983
|
+
}
|
|
984
|
+
function checkEvidenceEntry(entry, index) {
|
|
985
|
+
const field = `evidence[${index}]`;
|
|
986
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
987
|
+
return [issue("wrong-type", field, `evidence entries must be mappings, and this one is ${typeName(entry)}`)];
|
|
988
|
+
}
|
|
989
|
+
const entries = Object.entries(entry);
|
|
990
|
+
if (entries.length === 0) {
|
|
991
|
+
return [issue("wrong-type", field, "evidence entries must say something, and this one is empty")];
|
|
992
|
+
}
|
|
993
|
+
return entries.flatMap(([key, value]) => isScalar(value) ? [] : [issue("wrong-type", `${field}.${key}`, `evidence values must be text, numbers or booleans, and this one is ${typeName(value)}`)]);
|
|
994
|
+
}
|
|
995
|
+
function checkEvidence(data) {
|
|
996
|
+
const value = data["evidence"];
|
|
997
|
+
const owed = data["source"] === EVIDENCE_REQUIRED_SOURCE;
|
|
998
|
+
if (value === undefined || value === null)
|
|
999
|
+
return owed ? [missingEvidence("is absent")] : [];
|
|
1000
|
+
if (!Array.isArray(value)) {
|
|
1001
|
+
return [issue("wrong-type", "evidence", `evidence must be a list, not ${typeName(value)}`)];
|
|
1002
|
+
}
|
|
1003
|
+
if (value.length === 0)
|
|
1004
|
+
return owed ? [missingEvidence("is an empty list")] : [];
|
|
1005
|
+
return value.flatMap((entry, index) => checkEvidenceEntry(entry, index));
|
|
1006
|
+
}
|
|
1007
|
+
function missingEvidence(state) {
|
|
1008
|
+
return issue("missing-evidence", "evidence", `a ${EVIDENCE_REQUIRED_SOURCE} record must name what it was learned from, and evidence ${state}`);
|
|
1009
|
+
}
|
|
1010
|
+
function checkStoredHash(data) {
|
|
1011
|
+
if (!Object.hasOwn(data, "action_hash"))
|
|
1012
|
+
return [];
|
|
1013
|
+
return [issue("stored-action-hash", "action_hash", "action_hash is computed from the Action section at read time and is never stored, " + "because a hand edit would leave a stored one stale")];
|
|
1014
|
+
}
|
|
1015
|
+
function checkInstinctFrontmatter(data) {
|
|
1016
|
+
return [
|
|
1017
|
+
...checkId(data),
|
|
1018
|
+
...requiredString(data, "trigger"),
|
|
1019
|
+
...requiredChoice(data, "kind", INSTINCT_KINDS, "unknown-kind"),
|
|
1020
|
+
...requiredChoice(data, "domain", INSTINCT_DOMAINS, "unknown-domain"),
|
|
1021
|
+
...checkConfidence(data),
|
|
1022
|
+
...checkUsageCount(data),
|
|
1023
|
+
...checkSources(data),
|
|
1024
|
+
...checkArtifact(data),
|
|
1025
|
+
...requiredChoice(data, "signal", INSTINCT_SIGNALS, "unknown-signal"),
|
|
1026
|
+
...requiredChoice(data, "scope", INSTINCT_SCOPES, "unknown-scope"),
|
|
1027
|
+
...checkProjectId(data),
|
|
1028
|
+
...requiredChoice(data, "source", INSTINCT_SOURCES, "unknown-source"),
|
|
1029
|
+
...checkEvidence(data),
|
|
1030
|
+
...checkPromotedTo(data),
|
|
1031
|
+
...checkTimestamp(data, "created_at"),
|
|
1032
|
+
...checkTimestamp(data, "updated_at"),
|
|
1033
|
+
...checkStoredHash(data)
|
|
1034
|
+
];
|
|
1035
|
+
}
|
|
1036
|
+
function isFence(line) {
|
|
1037
|
+
return /^\s*(?:```|~~~)/.test(line);
|
|
1038
|
+
}
|
|
1039
|
+
function sectionText(body, heading) {
|
|
1040
|
+
const lines = body.split(`
|
|
1041
|
+
`).map((line) => line.replace(/\r$/, ""));
|
|
1042
|
+
const collected = [];
|
|
1043
|
+
let fenced = false;
|
|
1044
|
+
let inside = false;
|
|
1045
|
+
for (const line of lines) {
|
|
1046
|
+
if (isFence(line))
|
|
1047
|
+
fenced = !fenced;
|
|
1048
|
+
else if (!fenced && /^##\s/.test(line)) {
|
|
1049
|
+
if (inside)
|
|
1050
|
+
break;
|
|
1051
|
+
inside = line.trim() === heading;
|
|
1052
|
+
continue;
|
|
1053
|
+
}
|
|
1054
|
+
if (inside)
|
|
1055
|
+
collected.push(line);
|
|
1056
|
+
}
|
|
1057
|
+
return inside ? collected.join(`
|
|
1058
|
+
`).trim() : null;
|
|
1059
|
+
}
|
|
1060
|
+
function checkSection(body, heading) {
|
|
1061
|
+
const text = sectionText(body, heading);
|
|
1062
|
+
if (text === null) {
|
|
1063
|
+
return [issue("missing-section", heading, `the body carries no ${heading} section`)];
|
|
1064
|
+
}
|
|
1065
|
+
return text === "" ? [issue("missing-section", heading, `the ${heading} section is empty`)] : [];
|
|
1066
|
+
}
|
|
1067
|
+
function checkInstinctBody(body) {
|
|
1068
|
+
return [...checkSection(body, ACTION_HEADING), ...checkSection(body, CAUSE_HEADING)];
|
|
1069
|
+
}
|
|
1070
|
+
function parseInstinct(text) {
|
|
1071
|
+
const document = readFrontmatterDocument(text);
|
|
1072
|
+
if (document === null) {
|
|
1073
|
+
return {
|
|
1074
|
+
issues: [issue("missing-frontmatter", "frontmatter", "the file opens with no --- block holding a YAML mapping")],
|
|
1075
|
+
instinct: null
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
const issues = [
|
|
1079
|
+
...checkInstinctFrontmatter(document.data),
|
|
1080
|
+
...checkInstinctBody(document.body)
|
|
1081
|
+
];
|
|
1082
|
+
return issues.length > 0 ? { issues, instinct: null } : { issues, instinct: buildInstinct(document.data, document.body) };
|
|
1083
|
+
}
|
|
1084
|
+
function buildInstinct(data, body) {
|
|
1085
|
+
const action = sectionText(body, ACTION_HEADING) ?? "";
|
|
1086
|
+
const artifact = typeof data["artifact"] === "string" && data["artifact"].trim() !== "" ? data["artifact"] : null;
|
|
1087
|
+
return {
|
|
1088
|
+
id: data["id"],
|
|
1089
|
+
trigger: data["trigger"],
|
|
1090
|
+
kind: data["kind"],
|
|
1091
|
+
domain: data["domain"],
|
|
1092
|
+
confidence: data["confidence"],
|
|
1093
|
+
usageCount: typeof data["usage_count"] === "number" ? data["usage_count"] : DEFAULT_USAGE_COUNT,
|
|
1094
|
+
sources: Array.isArray(data["sources"]) ? data["sources"] : [],
|
|
1095
|
+
artifact,
|
|
1096
|
+
signal: data["signal"],
|
|
1097
|
+
scope: data["scope"],
|
|
1098
|
+
projectId: typeof data["project_id"] === "string" ? data["project_id"] : null,
|
|
1099
|
+
source: data["source"],
|
|
1100
|
+
evidence: Array.isArray(data["evidence"]) ? data["evidence"] : [],
|
|
1101
|
+
promotedTo: typeof data["promoted_to"] === "string" ? data["promoted_to"] : null,
|
|
1102
|
+
createdAt: data["created_at"],
|
|
1103
|
+
updatedAt: data["updated_at"],
|
|
1104
|
+
action,
|
|
1105
|
+
cause: sectionText(body, CAUSE_HEADING) ?? "",
|
|
1106
|
+
actionHash: actionHash(action)
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
function instinctFrontmatter(instinct) {
|
|
1110
|
+
const written = {
|
|
1111
|
+
id: instinct.id,
|
|
1112
|
+
trigger: instinct.trigger,
|
|
1113
|
+
kind: instinct.kind,
|
|
1114
|
+
domain: instinct.domain,
|
|
1115
|
+
confidence: instinct.confidence,
|
|
1116
|
+
usage_count: instinct.usageCount,
|
|
1117
|
+
signal: instinct.signal,
|
|
1118
|
+
scope: instinct.scope,
|
|
1119
|
+
source: instinct.source,
|
|
1120
|
+
created_at: instinct.createdAt,
|
|
1121
|
+
updated_at: instinct.updatedAt
|
|
1122
|
+
};
|
|
1123
|
+
if (instinct.artifact !== null)
|
|
1124
|
+
written["artifact"] = instinct.artifact;
|
|
1125
|
+
if (instinct.projectId !== null)
|
|
1126
|
+
written["project_id"] = instinct.projectId;
|
|
1127
|
+
if (instinct.evidence.length > 0)
|
|
1128
|
+
written["evidence"] = instinct.evidence;
|
|
1129
|
+
if (instinct.sources.length > 0)
|
|
1130
|
+
written["sources"] = instinct.sources;
|
|
1131
|
+
if (instinct.promotedTo !== null)
|
|
1132
|
+
written["promoted_to"] = instinct.promotedTo;
|
|
1133
|
+
return Object.fromEntries(INSTINCT_KEY_ORDER.filter((key) => Object.hasOwn(written, key)).map((key) => [key, written[key]]));
|
|
1134
|
+
}
|
|
1135
|
+
function writeInstinct(instinct, newline = `
|
|
1136
|
+
`) {
|
|
1137
|
+
const block = renderFrontmatter(instinctFrontmatter(instinct), newline);
|
|
1138
|
+
return [
|
|
1139
|
+
"---",
|
|
1140
|
+
block,
|
|
1141
|
+
"---",
|
|
1142
|
+
"",
|
|
1143
|
+
ACTION_HEADING,
|
|
1144
|
+
instinct.action,
|
|
1145
|
+
"",
|
|
1146
|
+
CAUSE_HEADING,
|
|
1147
|
+
instinct.cause,
|
|
1148
|
+
""
|
|
1149
|
+
].join(newline);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// src/commands/instinct/instinct-records.ts
|
|
1153
|
+
function isRecordFile(name) {
|
|
1154
|
+
return !name.startsWith(".") && name.endsWith(MARKDOWN_EXTENSION) && name !== MARKDOWN_EXTENSION;
|
|
1155
|
+
}
|
|
1156
|
+
function recordId(name) {
|
|
1157
|
+
return name.slice(0, -MARKDOWN_EXTENSION.length);
|
|
1158
|
+
}
|
|
1159
|
+
function unreadableIssue(message) {
|
|
1160
|
+
return { code: "missing-frontmatter", field: "file", message: `the file could not be read: ${message}` };
|
|
1161
|
+
}
|
|
1162
|
+
function readRecord(path, scope, id) {
|
|
1163
|
+
let text;
|
|
1164
|
+
try {
|
|
1165
|
+
text = readFileSync2(path, "utf8");
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1168
|
+
return { scope, path, id, instinct: null, issues: [unreadableIssue(message)] };
|
|
1169
|
+
}
|
|
1170
|
+
const parsed = parseInstinct(text);
|
|
1171
|
+
return { scope, path, id, instinct: parsed.instinct, issues: parsed.issues };
|
|
1172
|
+
}
|
|
1173
|
+
function isFile(path) {
|
|
1174
|
+
try {
|
|
1175
|
+
return statSync2(path).isFile();
|
|
1176
|
+
} catch {
|
|
1177
|
+
return false;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
function recordFiles(dir) {
|
|
1181
|
+
return readdirSync2(dir).filter((name) => isRecordFile(name) && isFile(join4(dir, name))).sort((left, right) => left.localeCompare(right));
|
|
1182
|
+
}
|
|
1183
|
+
function readScope(scope, dir) {
|
|
1184
|
+
if (!tierExists(dir))
|
|
1185
|
+
return { scope, dir, exists: false, records: [] };
|
|
1186
|
+
return {
|
|
1187
|
+
scope,
|
|
1188
|
+
dir,
|
|
1189
|
+
exists: true,
|
|
1190
|
+
records: recordFiles(dir).map((name) => readRecord(join4(dir, name), scope, recordId(name)))
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
function readScopes(seams) {
|
|
1194
|
+
return resolveInstinctScopes(seams).map((location) => readScope(location.scope, location.dir));
|
|
1195
|
+
}
|
|
1196
|
+
function allRecords(listings) {
|
|
1197
|
+
return listings.flatMap((listing) => listing.records);
|
|
1198
|
+
}
|
|
1199
|
+
function findRecords(listings, id) {
|
|
1200
|
+
return allRecords(listings).filter((entry) => entry.id === id);
|
|
1201
|
+
}
|
|
1202
|
+
function instinctProject(context, name) {
|
|
1203
|
+
if (context.project === null)
|
|
1204
|
+
throw new Error(`${name} runs inside a project, and was handed none`);
|
|
1205
|
+
return context.project;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// src/adapters/learning/held.ts
|
|
1209
|
+
function compareText(left, right) {
|
|
1210
|
+
if (left < right) {
|
|
1211
|
+
return -1;
|
|
1212
|
+
}
|
|
1213
|
+
if (left > right) {
|
|
1214
|
+
return 1;
|
|
1215
|
+
}
|
|
1216
|
+
return 0;
|
|
1217
|
+
}
|
|
1218
|
+
function byAge(left, right) {
|
|
1219
|
+
return compareText(left.createdAt, right.createdAt) || compareText(left.id, right.id);
|
|
1220
|
+
}
|
|
1221
|
+
function toHeldRecord(instinct) {
|
|
1222
|
+
return {
|
|
1223
|
+
id: instinct.id,
|
|
1224
|
+
trigger: instinct.trigger,
|
|
1225
|
+
action: instinct.action,
|
|
1226
|
+
action_hash: instinct.actionHash,
|
|
1227
|
+
confidence: instinct.confidence,
|
|
1228
|
+
usage_count: instinct.usageCount,
|
|
1229
|
+
...instinct.sources.length > 0 ? { sources: [...instinct.sources] } : {},
|
|
1230
|
+
...instinct.artifact === null ? {} : { artifact: instinct.artifact },
|
|
1231
|
+
signal: instinct.signal,
|
|
1232
|
+
status: "active",
|
|
1233
|
+
...instinct.promotedTo === null ? {} : { promoted_to: instinct.promotedTo },
|
|
1234
|
+
created_at: instinct.createdAt,
|
|
1235
|
+
updated_at: instinct.updatedAt
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
1238
|
+
function toHeldRecords(held) {
|
|
1239
|
+
const actions = new Map;
|
|
1240
|
+
for (const instinct of held) {
|
|
1241
|
+
const key = triggerKey(instinct.trigger);
|
|
1242
|
+
actions.set(key, (actions.get(key) ?? new Set).add(instinct.actionHash));
|
|
1243
|
+
}
|
|
1244
|
+
return held.map((instinct) => {
|
|
1245
|
+
const record = toHeldRecord(instinct);
|
|
1246
|
+
return actions.get(triggerKey(instinct.trigger)).size > 1 ? { ...record, status: "flagged" } : record;
|
|
1247
|
+
});
|
|
1248
|
+
}
|
|
1249
|
+
function earliestHeldMember(record, held) {
|
|
1250
|
+
const key = triggerKey(record.trigger);
|
|
1251
|
+
const members = held.filter((instinct) => instinct.actionHash === record.action_hash && triggerKey(instinct.trigger) === key);
|
|
1252
|
+
return [...members].sort(byAge)[0] ?? null;
|
|
1253
|
+
}
|
|
1254
|
+
function toHeldInstinct(record, held, fallback) {
|
|
1255
|
+
const description = earliestHeldMember(record, held) ?? fallback;
|
|
1256
|
+
if (description === undefined) {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
id: record.id,
|
|
1261
|
+
trigger: record.trigger,
|
|
1262
|
+
kind: description.kind,
|
|
1263
|
+
domain: description.domain,
|
|
1264
|
+
confidence: record.confidence,
|
|
1265
|
+
usageCount: record.usage_count,
|
|
1266
|
+
sources: [...record.sources ?? []],
|
|
1267
|
+
artifact: record.artifact ?? null,
|
|
1268
|
+
signal: record.signal,
|
|
1269
|
+
scope: description.scope,
|
|
1270
|
+
projectId: description.projectId,
|
|
1271
|
+
source: description.source,
|
|
1272
|
+
evidence: [...description.evidence],
|
|
1273
|
+
promotedTo: record.promoted_to ?? null,
|
|
1274
|
+
createdAt: record.created_at,
|
|
1275
|
+
updatedAt: record.updated_at,
|
|
1276
|
+
action: record.action,
|
|
1277
|
+
cause: description.cause,
|
|
1278
|
+
actionHash: actionHash(record.action)
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/adapters/learning/local.ts
|
|
445
1283
|
var PREFIX = "local learning";
|
|
1284
|
+
var PUSH_REFUSAL = `${PREFIX}: refused to store a push`;
|
|
446
1285
|
var INSTINCTS_FILE = "instincts.ndjson";
|
|
447
1286
|
var FLAGS_FILE = "flags.ndjson";
|
|
1287
|
+
var RECORD_EXTENSION = ".md";
|
|
448
1288
|
var MIN_CONFIDENCE = 0.3;
|
|
449
1289
|
var MAX_CONFIDENCE = 0.9;
|
|
450
1290
|
var SIGNALS = ["loud", "silent"];
|
|
@@ -471,20 +1311,30 @@ var INSTINCT_CHECKS = {
|
|
|
471
1311
|
(value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0,
|
|
472
1312
|
"a whole number from 0"
|
|
473
1313
|
],
|
|
1314
|
+
sources: [
|
|
1315
|
+
(value) => value === undefined || Array.isArray(value) && value.every(isNonEmptyString),
|
|
1316
|
+
"a list of non-empty strings when present"
|
|
1317
|
+
],
|
|
474
1318
|
artifact: [(value) => value === undefined || typeof value === "string", "a string when present"],
|
|
475
1319
|
signal: [oneOf(SIGNALS), `one of: ${SIGNALS.join(", ")}`],
|
|
476
1320
|
status: [oneOf(STATUSES), `one of: ${STATUSES.join(", ")}`],
|
|
1321
|
+
promoted_to: [(value) => value === undefined || typeof value === "string", "a string when present"],
|
|
477
1322
|
created_at: [isString, "a string"],
|
|
478
1323
|
updated_at: [isString, "a string"]
|
|
479
1324
|
};
|
|
1325
|
+
var DESCRIPTION_CHECKS = {
|
|
1326
|
+
kind: [oneOf(INSTINCT_KINDS), `one of: ${INSTINCT_KINDS.join(", ")}`],
|
|
1327
|
+
domain: [oneOf(INSTINCT_DOMAINS), `one of: ${INSTINCT_DOMAINS.join(", ")}`],
|
|
1328
|
+
scope: [oneOf(INSTINCT_SCOPES), `one of: ${INSTINCT_SCOPES.join(", ")}`],
|
|
1329
|
+
source: [oneOf(INSTINCT_SOURCES), `one of: ${INSTINCT_SOURCES.join(", ")}`],
|
|
1330
|
+
evidence: [(value) => Array.isArray(value) && value.every(isMapping), "a list of mappings"],
|
|
1331
|
+
cause: [isString, "a string"],
|
|
1332
|
+
projectId: [(value) => value === null || typeof value === "string", "a string or null"]
|
|
1333
|
+
};
|
|
480
1334
|
var PAYLOAD_CHECKS = {
|
|
481
1335
|
source_id: [isNonEmptyString, "a non-empty string"],
|
|
482
1336
|
instincts: [Array.isArray, "a list"]
|
|
483
1337
|
};
|
|
484
|
-
var HELD_INSTINCT_CHECKS = {
|
|
485
|
-
source_id: [isNonEmptyString, "a non-empty string"],
|
|
486
|
-
instinct: [isMapping, "a mapping"]
|
|
487
|
-
};
|
|
488
1338
|
var FLAG_CHECKS = {
|
|
489
1339
|
id: [isNonEmptyString, "a non-empty string"],
|
|
490
1340
|
reason: [isString, "a string"],
|
|
@@ -501,21 +1351,29 @@ function fieldsProblem(value, checks, at) {
|
|
|
501
1351
|
}
|
|
502
1352
|
return null;
|
|
503
1353
|
}
|
|
1354
|
+
function recordProblem(record, index) {
|
|
1355
|
+
const at = `instincts[${index}]`;
|
|
1356
|
+
const problem = fieldsProblem(record, INSTINCT_CHECKS, at);
|
|
1357
|
+
if (problem !== null)
|
|
1358
|
+
return problem;
|
|
1359
|
+
const { action, action_hash: hash, description } = record;
|
|
1360
|
+
if (hash !== actionHash(action)) {
|
|
1361
|
+
return `action_hash is ${describeValue(hash)} in ${at}, expected the sha256 of its trimmed, lower-cased action`;
|
|
1362
|
+
}
|
|
1363
|
+
return description === undefined ? null : fieldsProblem(description, DESCRIPTION_CHECKS, `${at}.description`);
|
|
1364
|
+
}
|
|
504
1365
|
function payloadProblem(payload) {
|
|
505
1366
|
const problem = fieldsProblem(payload, PAYLOAD_CHECKS, "the payload");
|
|
506
1367
|
if (problem !== null)
|
|
507
1368
|
return problem;
|
|
508
1369
|
const { instincts } = payload;
|
|
509
|
-
return instincts.map(
|
|
510
|
-
}
|
|
511
|
-
function heldInstinctProblem(value) {
|
|
512
|
-
return fieldsProblem(value, HELD_INSTINCT_CHECKS, "the line") ?? fieldsProblem(value.instinct, INSTINCT_CHECKS, "the instinct");
|
|
1370
|
+
return instincts.map(recordProblem).find((found) => found !== null) ?? null;
|
|
513
1371
|
}
|
|
514
1372
|
function flagProblem(value) {
|
|
515
1373
|
return fieldsProblem(value, FLAG_CHECKS, "the flag");
|
|
516
1374
|
}
|
|
517
1375
|
function copyInstinct(record) {
|
|
518
|
-
const { artifact } = record;
|
|
1376
|
+
const { sources, artifact, promoted_to: promotedTo } = record;
|
|
519
1377
|
return {
|
|
520
1378
|
id: record.id,
|
|
521
1379
|
trigger: record.trigger,
|
|
@@ -523,13 +1381,29 @@ function copyInstinct(record) {
|
|
|
523
1381
|
action_hash: record.action_hash,
|
|
524
1382
|
confidence: record.confidence,
|
|
525
1383
|
usage_count: record.usage_count,
|
|
1384
|
+
...sources === undefined ? {} : { sources: [...sources] },
|
|
526
1385
|
...artifact === undefined ? {} : { artifact },
|
|
527
1386
|
signal: record.signal,
|
|
528
1387
|
status: record.status,
|
|
1388
|
+
...promotedTo === undefined ? {} : { promoted_to: promotedTo },
|
|
529
1389
|
created_at: record.created_at,
|
|
530
1390
|
updated_at: record.updated_at
|
|
531
1391
|
};
|
|
532
1392
|
}
|
|
1393
|
+
function copyDescription(description) {
|
|
1394
|
+
return {
|
|
1395
|
+
kind: description.kind,
|
|
1396
|
+
domain: description.domain,
|
|
1397
|
+
scope: description.scope,
|
|
1398
|
+
source: description.source,
|
|
1399
|
+
evidence: description.evidence.map((entry) => ({ ...entry })),
|
|
1400
|
+
cause: description.cause,
|
|
1401
|
+
projectId: description.projectId
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
function loggedIncoming(record) {
|
|
1405
|
+
return record.description === undefined ? copyInstinct(record) : { ...copyInstinct(record), description: copyDescription(record.description) };
|
|
1406
|
+
}
|
|
533
1407
|
function hasCode(error, code) {
|
|
534
1408
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
535
1409
|
}
|
|
@@ -568,24 +1442,116 @@ function readLine(line, problemOf) {
|
|
|
568
1442
|
function warnThroughActiveOutput(message) {
|
|
569
1443
|
activeOutput().warn(message);
|
|
570
1444
|
}
|
|
1445
|
+
function unflagged(records, flagged) {
|
|
1446
|
+
return records.filter((record) => !flagged.has(record.id));
|
|
1447
|
+
}
|
|
1448
|
+
function userAdditions(userBlessed, projectHeld, flagged) {
|
|
1449
|
+
const heldTriggers = new Set(projectHeld.map((record) => triggerKey(record.trigger)));
|
|
1450
|
+
const heldIds = new Set(projectHeld.map((record) => record.id));
|
|
1451
|
+
return unflagged(userBlessed, flagged).filter((record) => !heldTriggers.has(triggerKey(record.trigger)) && !heldIds.has(record.id));
|
|
1452
|
+
}
|
|
1453
|
+
function byAge2(left, right) {
|
|
1454
|
+
if (left.created_at !== right.created_at) {
|
|
1455
|
+
return left.created_at < right.created_at ? -1 : 1;
|
|
1456
|
+
}
|
|
1457
|
+
if (left.id === right.id)
|
|
1458
|
+
return 0;
|
|
1459
|
+
return left.id < right.id ? -1 : 1;
|
|
1460
|
+
}
|
|
1461
|
+
function payloadDescription(record, described) {
|
|
1462
|
+
const key = triggerKey(record.trigger);
|
|
1463
|
+
const members = described.filter((candidate) => candidate.description !== undefined && candidate.action_hash === record.action_hash && triggerKey(candidate.trigger) === key);
|
|
1464
|
+
return [...members].sort(byAge2)[0]?.description;
|
|
1465
|
+
}
|
|
1466
|
+
function producedRecords(result) {
|
|
1467
|
+
const byId = new Map;
|
|
1468
|
+
for (const record of result.decisions.flatMap((decision) => decision.produced)) {
|
|
1469
|
+
const found = byId.get(record.id);
|
|
1470
|
+
if (found !== undefined && found !== record) {
|
|
1471
|
+
throw new Error(`${PUSH_REFUSAL}: two records would be written to ${record.id}${RECORD_EXTENSION}`);
|
|
1472
|
+
}
|
|
1473
|
+
byId.set(record.id, record);
|
|
1474
|
+
}
|
|
1475
|
+
return [...byId.values()];
|
|
1476
|
+
}
|
|
1477
|
+
function heldChanges(dir, held, result, described) {
|
|
1478
|
+
const heldInstincts = held.files.map((file) => file.instinct);
|
|
1479
|
+
const heldAt = new Map(held.files.map((file) => [file.path, file.instinct]));
|
|
1480
|
+
const writes = [];
|
|
1481
|
+
const kept = new Set;
|
|
1482
|
+
for (const record of producedRecords(result)) {
|
|
1483
|
+
const file = `${record.id}${RECORD_EXTENSION}`;
|
|
1484
|
+
const instinct = toHeldInstinct(record, heldInstincts, payloadDescription(record, described));
|
|
1485
|
+
if (instinct === null) {
|
|
1486
|
+
throw new Error(`${PUSH_REFUSAL}: ${file} has no held member and no payload description to be written with`);
|
|
1487
|
+
}
|
|
1488
|
+
const text = writeInstinct(instinct);
|
|
1489
|
+
const [issue2] = parseInstinct(text).issues;
|
|
1490
|
+
if (issue2 !== undefined) {
|
|
1491
|
+
throw new Error(`${PUSH_REFUSAL}: ${file} would not read back: ${issue2.field}: ${issue2.message}`);
|
|
1492
|
+
}
|
|
1493
|
+
const path = join5(dir, file);
|
|
1494
|
+
if (held.unreadable.has(path)) {
|
|
1495
|
+
throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which could not be read`);
|
|
1496
|
+
}
|
|
1497
|
+
const before = heldAt.get(path);
|
|
1498
|
+
if (before !== undefined && triggerKey(before.trigger) !== triggerKey(record.trigger)) {
|
|
1499
|
+
throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which holds another trigger`);
|
|
1500
|
+
}
|
|
1501
|
+
kept.add(path);
|
|
1502
|
+
if (before === undefined || writeInstinct(before) !== text)
|
|
1503
|
+
writes.push({ path, text });
|
|
1504
|
+
}
|
|
1505
|
+
const touched = new Set(result.decisions.map((decision) => triggerKey(decision.incoming.trigger)));
|
|
1506
|
+
const removals = held.files.filter((file) => touched.has(triggerKey(file.instinct.trigger)) && !kept.has(file.path)).map((file) => file.path);
|
|
1507
|
+
return { writes, removals };
|
|
1508
|
+
}
|
|
1509
|
+
function pushLogLines(sourceId, described, result) {
|
|
1510
|
+
return result.decisions.map((decision, index) => {
|
|
1511
|
+
const key = triggerKey(decision.incoming.trigger);
|
|
1512
|
+
const line = {
|
|
1513
|
+
source_id: sourceId,
|
|
1514
|
+
rule: decision.rule,
|
|
1515
|
+
incoming: loggedIncoming(described[index]),
|
|
1516
|
+
produced: decision.produced.map((record) => record.id),
|
|
1517
|
+
discarded: result.discarded.filter((record) => triggerKey(record.trigger) === key).map((record) => record.id)
|
|
1518
|
+
};
|
|
1519
|
+
return JSON.stringify(line);
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
571
1522
|
function localInstinctsDir(repoRoot) {
|
|
572
|
-
return
|
|
1523
|
+
return join5(repoRoot, RAFA_INSTINCTS_PATH);
|
|
1524
|
+
}
|
|
1525
|
+
function userInstinctsDir(home) {
|
|
1526
|
+
return join5(home, RAFA_INSTINCTS_PATH);
|
|
573
1527
|
}
|
|
574
1528
|
function createLocalLearning(options) {
|
|
575
|
-
const { instinctsDir } = options;
|
|
1529
|
+
const { instinctsDir, minConfidence } = options;
|
|
1530
|
+
const userDir = userInstinctsDir(options.home);
|
|
576
1531
|
const now = options.now ?? (() => new Date().toISOString());
|
|
577
1532
|
const warn = options.warn ?? warnThroughActiveOutput;
|
|
578
|
-
const instinctsPath =
|
|
579
|
-
const flagsPath =
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
1533
|
+
const instinctsPath = join5(instinctsDir, INSTINCTS_FILE);
|
|
1534
|
+
const flagsPath = join5(instinctsDir, FLAGS_FILE);
|
|
1535
|
+
function readHeld(scope, dir) {
|
|
1536
|
+
const { records } = readScope(scope, dir);
|
|
1537
|
+
const unreadable = new Set;
|
|
1538
|
+
const files = [];
|
|
1539
|
+
for (const entry of records) {
|
|
1540
|
+
if (entry.instinct === null) {
|
|
1541
|
+
unreadable.add(entry.path);
|
|
1542
|
+
const problems = entry.issues.map((issue2) => `${issue2.field}: ${issue2.message}`).join("; ");
|
|
1543
|
+
warn(`${PREFIX}: ${entry.path} is not a held instinct, so it was skipped: ${problems}`);
|
|
1544
|
+
} else {
|
|
1545
|
+
files.push({ path: entry.path, instinct: entry.instinct });
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
return { files, unreadable };
|
|
1549
|
+
}
|
|
1550
|
+
function heldSet() {
|
|
1551
|
+
return readHeld("project", instinctsDir);
|
|
1552
|
+
}
|
|
1553
|
+
function heldRecords(held) {
|
|
1554
|
+
return toHeldRecords(held.files.map((file) => file.instinct));
|
|
589
1555
|
}
|
|
590
1556
|
async function flaggedIds() {
|
|
591
1557
|
const lines = numberedLines(await readIfPresent(flagsPath));
|
|
@@ -600,27 +1566,31 @@ function createLocalLearning(options) {
|
|
|
600
1566
|
push: async (payload) => {
|
|
601
1567
|
const problem = payloadProblem(payload);
|
|
602
1568
|
if (problem !== null) {
|
|
603
|
-
throw new TypeError(`${
|
|
1569
|
+
throw new TypeError(`${PUSH_REFUSAL}: ${problem}`);
|
|
604
1570
|
}
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const lines = decisions.flatMap(({ produced }) => produced.map((instinct) => JSON.stringify({
|
|
611
|
-
source_id: payload.source_id,
|
|
612
|
-
instinct
|
|
613
|
-
})));
|
|
1571
|
+
const described = payload.instincts;
|
|
1572
|
+
const held = heldSet();
|
|
1573
|
+
const result = merge(toHeldRecords(held.files.map((file) => file.instinct)), { source_id: payload.source_id, instincts: described.map(copyInstinct) }, now());
|
|
1574
|
+
const changes = heldChanges(instinctsDir, held, result, described);
|
|
1575
|
+
const lines = pushLogLines(payload.source_id, described, result);
|
|
614
1576
|
if (lines.length > 0) {
|
|
615
1577
|
await mkdir(instinctsDir, { recursive: true });
|
|
616
1578
|
await appendLines(instinctsPath, lines);
|
|
617
1579
|
}
|
|
618
|
-
|
|
1580
|
+
for (const { path, text } of changes.writes) {
|
|
1581
|
+
await writeFile(path, text, "utf8");
|
|
1582
|
+
}
|
|
1583
|
+
for (const path of changes.removals) {
|
|
1584
|
+
await rm(path, { force: true });
|
|
1585
|
+
}
|
|
1586
|
+
return result;
|
|
619
1587
|
},
|
|
620
1588
|
pullBlessed: async () => {
|
|
621
1589
|
const flagged = await flaggedIds();
|
|
622
|
-
const
|
|
623
|
-
|
|
1590
|
+
const projectHeld = heldRecords(heldSet());
|
|
1591
|
+
const project = bless(unflagged(projectHeld, flagged), { minConfidence }).instincts;
|
|
1592
|
+
const user = bless(heldRecords(readHeld("user", userDir)), { minConfidence }).instincts;
|
|
1593
|
+
return { version: now(), instincts: [...project, ...userAdditions(user, projectHeld, flagged)] };
|
|
624
1594
|
},
|
|
625
1595
|
flag: async (id, reason) => {
|
|
626
1596
|
const flag = { id, reason, flagged_at: now() };
|
|
@@ -628,8 +1598,7 @@ function createLocalLearning(options) {
|
|
|
628
1598
|
if (problem !== null) {
|
|
629
1599
|
throw new TypeError(`${PREFIX}: refused to write an invalid flag: ${problem}`);
|
|
630
1600
|
}
|
|
631
|
-
|
|
632
|
-
if (!held.some((instinct) => instinct.id === id)) {
|
|
1601
|
+
if (!heldSet().files.some((file) => file.instinct.id === id)) {
|
|
633
1602
|
throw new Error(`${PREFIX}: no instinct ${describeValue(id)} is held under ${instinctsDir}`);
|
|
634
1603
|
}
|
|
635
1604
|
await appendLines(flagsPath, [JSON.stringify(flag)]);
|
|
@@ -1465,8 +2434,8 @@ function createGithubTracker(options) {
|
|
|
1465
2434
|
}
|
|
1466
2435
|
|
|
1467
2436
|
// src/adapters/tracker/local.ts
|
|
1468
|
-
import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile } from "fs/promises";
|
|
1469
|
-
import { join as
|
|
2437
|
+
import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
2438
|
+
import { join as join6 } from "path";
|
|
1470
2439
|
var PREFIX3 = "local tracker";
|
|
1471
2440
|
var FRONTMATTER = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
1472
2441
|
var ISSUE_NUMBER2 = /^[1-9]\d*$/;
|
|
@@ -1523,7 +2492,7 @@ function issueNumberOfFile(name) {
|
|
|
1523
2492
|
return name.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name.slice(0, -ISSUE_EXTENSION.length)) : null;
|
|
1524
2493
|
}
|
|
1525
2494
|
function issuePath(issuesDir, number) {
|
|
1526
|
-
return
|
|
2495
|
+
return join6(issuesDir, `${number}${ISSUE_EXTENSION}`);
|
|
1527
2496
|
}
|
|
1528
2497
|
async function issueNumbers(issuesDir) {
|
|
1529
2498
|
let names;
|
|
@@ -1537,7 +2506,7 @@ async function issueNumbers(issuesDir) {
|
|
|
1537
2506
|
return names.map(issueNumberOfFile).filter((number) => number !== null).sort((a, b) => a - b);
|
|
1538
2507
|
}
|
|
1539
2508
|
function localIssuesDir(repoRoot) {
|
|
1540
|
-
return
|
|
2509
|
+
return join6(repoRoot, ".rafa", "issues");
|
|
1541
2510
|
}
|
|
1542
2511
|
function renderLocalIssue(record) {
|
|
1543
2512
|
const { draft } = record;
|
|
@@ -1663,7 +2632,7 @@ function createLocalTracker(options) {
|
|
|
1663
2632
|
}
|
|
1664
2633
|
async function rewrite(number, record) {
|
|
1665
2634
|
const contents = renderLocalIssue(record);
|
|
1666
|
-
await
|
|
2635
|
+
await writeFile2(issuePath(issuesDir, number), contents, "utf8");
|
|
1667
2636
|
}
|
|
1668
2637
|
const tracker = {
|
|
1669
2638
|
kind: "local",
|
|
@@ -1685,7 +2654,7 @@ function createLocalTracker(options) {
|
|
|
1685
2654
|
const held = await issueNumbers(issuesDir);
|
|
1686
2655
|
for (let number = (held.at(-1) ?? 0) + 1;Number.isSafeInteger(number); number += 1) {
|
|
1687
2656
|
try {
|
|
1688
|
-
await
|
|
2657
|
+
await writeFile2(issuePath(issuesDir, number), contents, { encoding: "utf8", flag: "wx" });
|
|
1689
2658
|
return refFor(number, draft.opt);
|
|
1690
2659
|
} catch (error) {
|
|
1691
2660
|
if (!hasCode2(error, "EEXIST"))
|
|
@@ -1827,7 +2796,15 @@ var CORE_ADAPTERS = [
|
|
|
1827
2796
|
port: "learning",
|
|
1828
2797
|
kind: "local",
|
|
1829
2798
|
portVersion: PORT_VERSIONS.learning,
|
|
1830
|
-
create: ({
|
|
2799
|
+
create: ({
|
|
2800
|
+
repoRoot,
|
|
2801
|
+
home = homedir(),
|
|
2802
|
+
learningBlessMinConfidence = CONFIG_DEFAULTS.learningBlessMinConfidence
|
|
2803
|
+
}) => createLocalLearning({
|
|
2804
|
+
instinctsDir: localInstinctsDir(repoRoot),
|
|
2805
|
+
home,
|
|
2806
|
+
minConfidence: learningBlessMinConfidence
|
|
2807
|
+
})
|
|
1831
2808
|
},
|
|
1832
2809
|
{
|
|
1833
2810
|
port: "planner",
|
|
@@ -1866,4 +2843,4 @@ function selectEffortStore(repoRoot, config) {
|
|
|
1866
2843
|
return adapter.create({ repoRoot });
|
|
1867
2844
|
}
|
|
1868
2845
|
|
|
1869
|
-
export { EFFORT_STORE_DIR, effortStorePath, EFFORT_KEY_PROJECTIONS, openNdjsonStore, sqliteStorePath, LONE_SURROGATE, withSqliteStore, writeSqliteStore, openSqliteStore, sessionSpawnEnv, getClaudeUsagePercent, checkUsage, CLAUDE_BIN, claudeArgs, interruptClaudeSessions, runClaude, spawnClaudeCaptured, runClaudeCaptured, planFilePath, ClaudePlannerError, ISSUE_TYPES, ISSUE_PRIORITIES, ISSUE_STATES, GITHUB_LABELS, createGhRunner, typeOfLabels, moduleOfLabels, createLocalTracker, PORT_VERSIONS, createAdapterRegistry, CORE_ADAPTER_REGISTRY, selectEffortStore };
|
|
2846
|
+
export { EFFORT_STORE_DIR, effortStorePath, EFFORT_KEY_PROJECTIONS, openNdjsonStore, sqliteStorePath, SQLITE_MIGRATIONS, LONE_SURROGATE, migrateSchema, withSqliteStore, writeSqliteStore, openSqliteStore, SKILL_FILE, MARKDOWN_EXTENSION, scanLayout, declaredNameIssue, readFrontmatterDocument, readFrontmatter, renderFrontmatter, mergeFrontmatter, writeFrontmatter, normalizeRemote, gitRemoteUrl, ACTION_HEADING, CAUSE_HEADING, parseInstinct, writeInstinct, readScopes, allRecords, findRecords, instinctProject, toHeldRecords, sessionSpawnEnv, getClaudeUsagePercent, checkUsage, CLAUDE_BIN, claudeArgs, interruptClaudeSessions, runClaude, spawnClaudeCaptured, runClaudeCaptured, planFilePath, ClaudePlannerError, ISSUE_TYPES, ISSUE_PRIORITIES, ISSUE_STATES, GITHUB_LABELS, createGhRunner, typeOfLabels, moduleOfLabels, createLocalTracker, PORT_VERSIONS, createAdapterRegistry, CORE_ADAPTER_REGISTRY, selectEffortStore };
|