@open-tomato/rafa 0.17.0 → 0.19.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/NOTICE +9 -1
- package/README.md +40 -26
- package/dist/PROMPT.md +2 -1
- package/dist/bundled/agents/build-error-resolver.md +144 -0
- package/dist/bundled/agents/code-reviewer.md +335 -0
- package/dist/bundled/agents/doc-updater.md +108 -0
- package/dist/bundled/agents/loop-implementer.md +57 -0
- package/dist/bundled/agents/qa-bug-reporter.md +164 -0
- package/dist/bundled/agents/tdd-guide.md +121 -0
- package/dist/bundled/bin/ts-symbols +272 -0
- package/dist/{SKILL.md → bundled/skills/dev-planner/SKILL.md} +7 -5
- package/dist/bundled/skills/documentation/SKILL.md +93 -0
- package/dist/bundled/skills/git-workflow/SKILL.md +242 -0
- package/dist/bundled/skills/ts-symbols/SKILL.md +82 -0
- package/dist/cli.js +6952 -4721
- package/dist/{describe-wbn759vf.js → describe-cenk70n5.js} +4 -3
- package/dist/effort/store/index.js +6 -4
- package/dist/{index-64wec0k7.js → index-09p41s0n.js} +11 -224
- package/dist/{index-qzhpqmfy.js → index-c5cecsga.js} +2 -2
- package/dist/{index-4qd537z6.js → index-ca0fccvr.js} +3527 -1402
- package/dist/{index-n3rw8rhr.js → index-cedghpva.js} +78 -2
- package/dist/index-d6d0942q.js +218 -0
- package/dist/{index-7t0qz29r.js → index-gb51x34c.js} +1020 -58
- package/dist/{index-r7cnr44m.js → index-mh5hyw8g.js} +1681 -3101
- package/dist/index-pqrpcfaz.js +1020 -0
- package/dist/{index-p901zs2p.js → index-vg7ycs94.js} +1 -1
- package/dist/index-wp8c28fd.js +18 -0
- 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
- package/dist/index-ps5sac9p.js +0 -531
|
@@ -1,19 +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-pqrpcfaz.js";
|
|
12
|
+
import {
|
|
13
|
+
bless,
|
|
14
|
+
merge
|
|
15
|
+
} from "./index-d6d0942q.js";
|
|
8
16
|
import {
|
|
9
17
|
commandSpelling
|
|
10
|
-
} from "./index-
|
|
18
|
+
} from "./index-vg7ycs94.js";
|
|
11
19
|
import {
|
|
20
|
+
RAFA_INSTINCTS_PATH,
|
|
12
21
|
STORE_BACKENDS,
|
|
22
|
+
bundledBinDirectory,
|
|
13
23
|
describeValue,
|
|
14
24
|
isMapping,
|
|
15
|
-
messageOf
|
|
16
|
-
|
|
25
|
+
messageOf,
|
|
26
|
+
resolveInstinctScopes,
|
|
27
|
+
tierExists
|
|
28
|
+
} from "./index-cedghpva.js";
|
|
29
|
+
import {
|
|
30
|
+
actionHash,
|
|
31
|
+
triggerKey
|
|
32
|
+
} from "./index-wp8c28fd.js";
|
|
33
|
+
|
|
34
|
+
// src/adapters/registry.ts
|
|
35
|
+
import { homedir } from "os";
|
|
17
36
|
|
|
18
37
|
// src/effort/store.ts
|
|
19
38
|
import {
|
|
@@ -439,11 +458,812 @@ function openSqliteStore(repoRoot) {
|
|
|
439
458
|
}
|
|
440
459
|
|
|
441
460
|
// src/adapters/learning/local.ts
|
|
442
|
-
import { appendFile, mkdir, readFile } from "fs/promises";
|
|
443
|
-
import { join as
|
|
461
|
+
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
462
|
+
import { join as join5 } from "path";
|
|
463
|
+
|
|
464
|
+
// src/commands/instinct/instinct-records.ts
|
|
465
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
466
|
+
import { join as join4 } from "path";
|
|
467
|
+
|
|
468
|
+
// src/check/layout.ts
|
|
469
|
+
import { readdirSync, statSync } from "fs";
|
|
470
|
+
import { join as join3, relative } from "path";
|
|
471
|
+
var SKILL_FILE = "SKILL.md";
|
|
472
|
+
var MARKDOWN_EXTENSION = ".md";
|
|
473
|
+
var LAYOUT_SEVERITY = Object.freeze({
|
|
474
|
+
"flat-grouped-layout": "failure",
|
|
475
|
+
"nested-group-layout": "failure",
|
|
476
|
+
"loose-file": "failure",
|
|
477
|
+
"missing-skill-file": "failure",
|
|
478
|
+
"nested-instinct-file": "failure",
|
|
479
|
+
"unreadable-entry": "failure",
|
|
480
|
+
"name-mismatch": "failure",
|
|
481
|
+
"dotfile-only-directory": "warning"
|
|
482
|
+
});
|
|
483
|
+
function layoutIssue(code, message) {
|
|
484
|
+
return { code, severity: LAYOUT_SEVERITY[code], message };
|
|
485
|
+
}
|
|
486
|
+
function entryKind(path) {
|
|
487
|
+
try {
|
|
488
|
+
const stats = statSync(path);
|
|
489
|
+
if (stats.isFile())
|
|
490
|
+
return "file";
|
|
491
|
+
if (stats.isDirectory())
|
|
492
|
+
return "directory";
|
|
493
|
+
return null;
|
|
494
|
+
} catch {
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
function visibleNames(dir) {
|
|
499
|
+
return readdirSync(dir).filter((name) => !name.startsWith(".")).sort((left, right) => left.localeCompare(right));
|
|
500
|
+
}
|
|
501
|
+
function isMarkdown(name) {
|
|
502
|
+
return name.endsWith(MARKDOWN_EXTENSION);
|
|
503
|
+
}
|
|
504
|
+
function markdownStem(name) {
|
|
505
|
+
return name.slice(0, -MARKDOWN_EXTENSION.length);
|
|
506
|
+
}
|
|
507
|
+
function shown(root, path) {
|
|
508
|
+
return relative(root, path) || ".";
|
|
509
|
+
}
|
|
510
|
+
function dotfileOnlyEntry(kind, root, dir) {
|
|
511
|
+
return {
|
|
512
|
+
kind,
|
|
513
|
+
path: dir,
|
|
514
|
+
isFile: false,
|
|
515
|
+
shape: "dotfile-only",
|
|
516
|
+
name: null,
|
|
517
|
+
issues: [layoutIssue("dotfile-only-directory", `${shown(root, dir)}/ holds nothing but dotfiles, so it registers nothing`)]
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
function unreadableEntry(kind, root, path) {
|
|
521
|
+
return {
|
|
522
|
+
kind,
|
|
523
|
+
path,
|
|
524
|
+
isFile: false,
|
|
525
|
+
shape: "unreadable",
|
|
526
|
+
name: null,
|
|
527
|
+
issues: [layoutIssue("unreadable-entry", `${shown(root, path)} cannot be read (a broken symlink, or a path stat refuses)`)]
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function skillFileEntry(path, name) {
|
|
531
|
+
return { kind: "skill", path, isFile: true, shape: "skill-file", name, issues: [] };
|
|
532
|
+
}
|
|
533
|
+
function flatGroupedEntry(root, path, name) {
|
|
534
|
+
return {
|
|
535
|
+
kind: "skill",
|
|
536
|
+
path,
|
|
537
|
+
isFile: true,
|
|
538
|
+
shape: "flat-grouped",
|
|
539
|
+
name,
|
|
540
|
+
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}`)]
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function nestedGroupEntry(root, path, name) {
|
|
544
|
+
return {
|
|
545
|
+
kind: "skill",
|
|
546
|
+
path,
|
|
547
|
+
isFile: true,
|
|
548
|
+
shape: "nested-group",
|
|
549
|
+
name,
|
|
550
|
+
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}`)]
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function looseFileEntry(root, path, name) {
|
|
554
|
+
return {
|
|
555
|
+
kind: "skill",
|
|
556
|
+
path,
|
|
557
|
+
isFile: true,
|
|
558
|
+
shape: "loose-file",
|
|
559
|
+
name,
|
|
560
|
+
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}`)]
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function noSkillFileEntry(root, dir) {
|
|
564
|
+
return {
|
|
565
|
+
kind: "skill",
|
|
566
|
+
path: dir,
|
|
567
|
+
isFile: false,
|
|
568
|
+
shape: "no-skill-file",
|
|
569
|
+
name: null,
|
|
570
|
+
issues: [layoutIssue("missing-skill-file", `${shown(root, dir)}/ holds no ${SKILL_FILE}, so it registers no skill`)]
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
function nestedInstinctEntry(root, path, name) {
|
|
574
|
+
return {
|
|
575
|
+
kind: "instinct",
|
|
576
|
+
path,
|
|
577
|
+
isFile: true,
|
|
578
|
+
shape: "nested-instinct",
|
|
579
|
+
name,
|
|
580
|
+
issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${name}${MARKDOWN_EXTENSION}`)]
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
function scanGroupMember(root, dir, name) {
|
|
584
|
+
const children = visibleNames(dir);
|
|
585
|
+
if (children.length === 0)
|
|
586
|
+
return [dotfileOnlyEntry("skill", root, dir)];
|
|
587
|
+
const skillPath = join3(dir, SKILL_FILE);
|
|
588
|
+
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
589
|
+
return [nestedGroupEntry(root, skillPath, name)];
|
|
590
|
+
}
|
|
591
|
+
return [noSkillFileEntry(root, dir)];
|
|
592
|
+
}
|
|
593
|
+
function scanSkillGroup(root, dir, children) {
|
|
594
|
+
const entries = [];
|
|
595
|
+
for (const child of children) {
|
|
596
|
+
const path = join3(dir, child);
|
|
597
|
+
const found = entryKind(path);
|
|
598
|
+
if (found === null) {
|
|
599
|
+
entries.push(unreadableEntry("skill", root, path));
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
if (found === "directory") {
|
|
603
|
+
entries.push(...scanGroupMember(root, path, child));
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (isMarkdown(child))
|
|
607
|
+
entries.push(flatGroupedEntry(root, path, markdownStem(child)));
|
|
608
|
+
}
|
|
609
|
+
if (entries.length === 0)
|
|
610
|
+
return [noSkillFileEntry(root, dir)];
|
|
611
|
+
return entries;
|
|
612
|
+
}
|
|
613
|
+
function scanSkillDirectory(root, dir, name) {
|
|
614
|
+
const children = visibleNames(dir);
|
|
615
|
+
if (children.length === 0)
|
|
616
|
+
return [dotfileOnlyEntry("skill", root, dir)];
|
|
617
|
+
const skillPath = join3(dir, SKILL_FILE);
|
|
618
|
+
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
619
|
+
return [skillFileEntry(skillPath, name)];
|
|
620
|
+
}
|
|
621
|
+
return scanSkillGroup(root, dir, children);
|
|
622
|
+
}
|
|
623
|
+
function scanSkillRoot(root) {
|
|
624
|
+
const entries = [];
|
|
625
|
+
for (const name of visibleNames(root)) {
|
|
626
|
+
const path = join3(root, name);
|
|
627
|
+
const found = entryKind(path);
|
|
628
|
+
if (found === null) {
|
|
629
|
+
entries.push(unreadableEntry("skill", root, path));
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (found === "directory") {
|
|
633
|
+
entries.push(...scanSkillDirectory(root, path, name));
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (isMarkdown(name))
|
|
637
|
+
entries.push(looseFileEntry(root, path, markdownStem(name)));
|
|
638
|
+
}
|
|
639
|
+
return entries;
|
|
640
|
+
}
|
|
641
|
+
function scanInstinctSubdirectory(root, dir) {
|
|
642
|
+
const children = visibleNames(dir);
|
|
643
|
+
if (children.length === 0)
|
|
644
|
+
return [dotfileOnlyEntry("instinct", root, dir)];
|
|
645
|
+
const entries = [];
|
|
646
|
+
for (const child of children) {
|
|
647
|
+
const path = join3(dir, child);
|
|
648
|
+
const found = entryKind(path);
|
|
649
|
+
if (found === null) {
|
|
650
|
+
entries.push(unreadableEntry("instinct", root, path));
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (found === "directory") {
|
|
654
|
+
entries.push(...scanInstinctSubdirectory(root, path));
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (isMarkdown(child))
|
|
658
|
+
entries.push(nestedInstinctEntry(root, path, markdownStem(child)));
|
|
659
|
+
}
|
|
660
|
+
return entries;
|
|
661
|
+
}
|
|
662
|
+
function scanInstinctRoot(root) {
|
|
663
|
+
const entries = [];
|
|
664
|
+
for (const name of visibleNames(root)) {
|
|
665
|
+
const path = join3(root, name);
|
|
666
|
+
const found = entryKind(path);
|
|
667
|
+
if (found === null) {
|
|
668
|
+
entries.push(unreadableEntry("instinct", root, path));
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (found === "directory") {
|
|
672
|
+
entries.push(...scanInstinctSubdirectory(root, path));
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (!isMarkdown(name))
|
|
676
|
+
continue;
|
|
677
|
+
entries.push({
|
|
678
|
+
kind: "instinct",
|
|
679
|
+
path,
|
|
680
|
+
isFile: true,
|
|
681
|
+
shape: "instinct-file",
|
|
682
|
+
name: markdownStem(name),
|
|
683
|
+
issues: []
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
return entries;
|
|
687
|
+
}
|
|
688
|
+
function scanLayout(root, kind) {
|
|
689
|
+
if (entryKind(root) !== "directory") {
|
|
690
|
+
throw new Error(`check layout: no ${kind} directory at ${root}`);
|
|
691
|
+
}
|
|
692
|
+
const entries = kind === "skill" ? scanSkillRoot(root) : scanInstinctRoot(root);
|
|
693
|
+
return {
|
|
694
|
+
kind,
|
|
695
|
+
root,
|
|
696
|
+
entries: entries.sort((left, right) => left.path.localeCompare(right.path))
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function declaredNameIssue(entry, declared) {
|
|
700
|
+
if (entry.name === null || declared === null || declared === entry.name)
|
|
701
|
+
return null;
|
|
702
|
+
const field = entry.kind === "skill" ? "name" : "id";
|
|
703
|
+
const source = entry.kind === "skill" ? "directory name" : "file name";
|
|
704
|
+
return layoutIssue("name-mismatch", `frontmatter ${field} "${declared}" is not the ${source} "${entry.name}"`);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/schema/frontmatter.ts
|
|
708
|
+
var FRONTMATTER_FENCE = "---";
|
|
709
|
+
var FRONTMATTER_INDENT = 2;
|
|
710
|
+
function scanLine(text, offset) {
|
|
711
|
+
const index = text.indexOf(`
|
|
712
|
+
`, offset);
|
|
713
|
+
if (index === -1) {
|
|
714
|
+
return { line: text.slice(offset), newline: "", next: text.length };
|
|
715
|
+
}
|
|
716
|
+
const carriage = index > offset && text[index - 1] === "\r";
|
|
717
|
+
const end = carriage ? index - 1 : index;
|
|
718
|
+
return {
|
|
719
|
+
line: text.slice(offset, end),
|
|
720
|
+
newline: text.slice(end, index + 1),
|
|
721
|
+
next: index + 1
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
function isRecord(value) {
|
|
725
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
726
|
+
}
|
|
727
|
+
function parseBlock(yaml) {
|
|
728
|
+
let parsed;
|
|
729
|
+
try {
|
|
730
|
+
parsed = Bun.YAML.parse(yaml.replace(/\r\n/g, `
|
|
731
|
+
`));
|
|
732
|
+
} catch {
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
return isRecord(parsed) ? parsed : null;
|
|
736
|
+
}
|
|
737
|
+
function readFrontmatterDocument(text) {
|
|
738
|
+
const opening = scanLine(text, 0);
|
|
739
|
+
if (opening.line !== FRONTMATTER_FENCE || opening.newline === "")
|
|
740
|
+
return null;
|
|
741
|
+
let offset = opening.next;
|
|
742
|
+
while (offset < text.length) {
|
|
743
|
+
const scanned = scanLine(text, offset);
|
|
744
|
+
if (scanned.line === FRONTMATTER_FENCE) {
|
|
745
|
+
const data = parseBlock(text.slice(opening.next, offset));
|
|
746
|
+
return data === null ? null : {
|
|
747
|
+
data,
|
|
748
|
+
yaml: text.slice(opening.next, offset),
|
|
749
|
+
body: text.slice(scanned.next),
|
|
750
|
+
newline: opening.newline,
|
|
751
|
+
closingNewline: scanned.newline
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
offset = scanned.next;
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
function readFrontmatter(text) {
|
|
759
|
+
return readFrontmatterDocument(text)?.data ?? null;
|
|
760
|
+
}
|
|
761
|
+
function renderFrontmatter(data, newline = `
|
|
762
|
+
`) {
|
|
763
|
+
return Bun.YAML.stringify(data, null, FRONTMATTER_INDENT).split(`
|
|
764
|
+
`).map((line) => line.replace(/[ \t]+$/, "")).join(newline);
|
|
765
|
+
}
|
|
766
|
+
function mergeFrontmatter(data, changes) {
|
|
767
|
+
const merged = {};
|
|
768
|
+
for (const [key, value] of Object.entries(data)) {
|
|
769
|
+
if (!Object.hasOwn(changes, key))
|
|
770
|
+
merged[key] = value;
|
|
771
|
+
else if (changes[key] !== undefined)
|
|
772
|
+
merged[key] = changes[key];
|
|
773
|
+
}
|
|
774
|
+
for (const [key, value] of Object.entries(changes)) {
|
|
775
|
+
if (value !== undefined && !Object.hasOwn(data, key))
|
|
776
|
+
merged[key] = value;
|
|
777
|
+
}
|
|
778
|
+
return merged;
|
|
779
|
+
}
|
|
780
|
+
function writeFrontmatter(document, data) {
|
|
781
|
+
const block = data === undefined ? document.yaml : `${renderFrontmatter(data, document.newline)}${document.newline}`;
|
|
782
|
+
return [
|
|
783
|
+
FRONTMATTER_FENCE,
|
|
784
|
+
document.newline,
|
|
785
|
+
block,
|
|
786
|
+
FRONTMATTER_FENCE,
|
|
787
|
+
document.closingNewline,
|
|
788
|
+
document.body
|
|
789
|
+
].join("");
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/schema/project-id.ts
|
|
793
|
+
import { spawnSync } from "child_process";
|
|
794
|
+
var PROJECT_ID_LENGTH = 12;
|
|
795
|
+
var PROJECT_ID_PATTERN = new RegExp(`^[0-9a-f]{${PROJECT_ID_LENGTH}}$`);
|
|
796
|
+
function hasColonAfterAt(url) {
|
|
797
|
+
const at = url.indexOf("@");
|
|
798
|
+
return at !== -1 && url.lastIndexOf(":") > at;
|
|
799
|
+
}
|
|
800
|
+
function normalizeRemote(url) {
|
|
801
|
+
const stripped = url.replace(/:\/\/[^@]+@/, "://");
|
|
802
|
+
const network = !stripped.startsWith("file://") && (stripped.includes("://") || hasColonAfterAt(stripped));
|
|
803
|
+
const trimmed = stripped.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "").replace(/^[^@/:]+@([^:/]+):/, "$1/").replace(/\.git\/?$/, "").replace(/\/+$/, "");
|
|
804
|
+
return network ? trimmed.toLowerCase() : trimmed;
|
|
805
|
+
}
|
|
806
|
+
function gitRemoteUrl(dir) {
|
|
807
|
+
const result = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
808
|
+
cwd: dir,
|
|
809
|
+
encoding: "utf8",
|
|
810
|
+
env: { ...process.env, LC_ALL: "C" }
|
|
811
|
+
});
|
|
812
|
+
if (result.error !== undefined || result.status !== 0)
|
|
813
|
+
return null;
|
|
814
|
+
const url = result.stdout.trim();
|
|
815
|
+
return url === "" ? null : url;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
// src/schema/instinct.ts
|
|
819
|
+
var INSTINCT_KINDS = FINDING_KINDS;
|
|
820
|
+
var INSTINCT_SIGNALS = FINDING_SIGNALS;
|
|
821
|
+
var INSTINCT_DOMAINS = FINDING_DOMAINS;
|
|
822
|
+
var INSTINCT_SCOPES = ["project", "user"];
|
|
823
|
+
var INSTINCT_SOURCES = ["task-report", "loop-observed", "imported", "demoted"];
|
|
824
|
+
var CONFIDENCE_MIN = 0.3;
|
|
825
|
+
var CONFIDENCE_MAX = 0.9;
|
|
826
|
+
var DEFAULT_USAGE_COUNT = 1;
|
|
827
|
+
var EVIDENCE_REQUIRED_SOURCE = "task-report";
|
|
828
|
+
var ACTION_HEADING = "## Action";
|
|
829
|
+
var CAUSE_HEADING = "## Cause";
|
|
830
|
+
var INSTINCT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
831
|
+
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
832
|
+
var INSTINCT_KEY_ORDER = [
|
|
833
|
+
"id",
|
|
834
|
+
"trigger",
|
|
835
|
+
"kind",
|
|
836
|
+
"domain",
|
|
837
|
+
"confidence",
|
|
838
|
+
"usage_count",
|
|
839
|
+
"sources",
|
|
840
|
+
"artifact",
|
|
841
|
+
"signal",
|
|
842
|
+
"scope",
|
|
843
|
+
"project_id",
|
|
844
|
+
"source",
|
|
845
|
+
"evidence",
|
|
846
|
+
"promoted_to",
|
|
847
|
+
"created_at",
|
|
848
|
+
"updated_at"
|
|
849
|
+
];
|
|
850
|
+
function issue(code, field, message) {
|
|
851
|
+
return { code, field, message };
|
|
852
|
+
}
|
|
853
|
+
function typeName(value) {
|
|
854
|
+
if (value === null)
|
|
855
|
+
return "an empty value";
|
|
856
|
+
if (Array.isArray(value))
|
|
857
|
+
return "a list";
|
|
858
|
+
if (typeof value === "object")
|
|
859
|
+
return "a mapping";
|
|
860
|
+
if (typeof value === "boolean")
|
|
861
|
+
return "a boolean";
|
|
862
|
+
if (typeof value === "number")
|
|
863
|
+
return "a number";
|
|
864
|
+
return `a ${typeof value}`;
|
|
865
|
+
}
|
|
866
|
+
function requiredString(data, field) {
|
|
867
|
+
const value = data[field];
|
|
868
|
+
if (value === undefined || value === null) {
|
|
869
|
+
return [issue("missing-field", field, `${field} is required and is absent`)];
|
|
870
|
+
}
|
|
871
|
+
if (typeof value !== "string") {
|
|
872
|
+
return [issue("wrong-type", field, `${field} must be a string, not ${typeName(value)}`)];
|
|
873
|
+
}
|
|
874
|
+
if (value.trim() === "") {
|
|
875
|
+
return [issue("missing-field", field, `${field} is required and is empty`)];
|
|
876
|
+
}
|
|
877
|
+
return [];
|
|
878
|
+
}
|
|
879
|
+
function requiredChoice(data, field, allowed, code) {
|
|
880
|
+
const issues = requiredString(data, field);
|
|
881
|
+
if (issues.length > 0)
|
|
882
|
+
return issues;
|
|
883
|
+
const value = data[field];
|
|
884
|
+
return allowed.includes(value) ? [] : [issue(code, field, `${field} "${value}" is not one of ${allowed.join(", ")}`)];
|
|
885
|
+
}
|
|
886
|
+
function checkId(data) {
|
|
887
|
+
const issues = requiredString(data, "id");
|
|
888
|
+
if (issues.length > 0)
|
|
889
|
+
return issues;
|
|
890
|
+
const id = data["id"];
|
|
891
|
+
return INSTINCT_ID_PATTERN.test(id) ? [] : [issue("invalid-id", "id", `id "${id}" is not lowercase words joined by single hyphens`)];
|
|
892
|
+
}
|
|
893
|
+
function checkConfidence(data) {
|
|
894
|
+
const value = data["confidence"];
|
|
895
|
+
if (value === undefined || value === null) {
|
|
896
|
+
return [issue("missing-field", "confidence", "confidence is required and is absent")];
|
|
897
|
+
}
|
|
898
|
+
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
899
|
+
return [issue("wrong-type", "confidence", `confidence must be a number, not ${typeName(value)}`)];
|
|
900
|
+
}
|
|
901
|
+
return value >= CONFIDENCE_MIN && value <= CONFIDENCE_MAX ? [] : [issue("confidence-out-of-range", "confidence", `confidence ${value} is outside ${CONFIDENCE_MIN} to ${CONFIDENCE_MAX}`)];
|
|
902
|
+
}
|
|
903
|
+
function checkUsageCount(data) {
|
|
904
|
+
const value = data["usage_count"];
|
|
905
|
+
if (value === undefined)
|
|
906
|
+
return [];
|
|
907
|
+
if (typeof value !== "number") {
|
|
908
|
+
return [issue("wrong-type", "usage_count", `usage_count must be a number, not ${typeName(value)}`)];
|
|
909
|
+
}
|
|
910
|
+
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}`)];
|
|
911
|
+
}
|
|
912
|
+
function checkSources(data) {
|
|
913
|
+
const value = data["sources"];
|
|
914
|
+
if (value === undefined)
|
|
915
|
+
return [];
|
|
916
|
+
if (!Array.isArray(value)) {
|
|
917
|
+
return [issue("wrong-type", "sources", `sources must be a list, not ${typeName(value)}`)];
|
|
918
|
+
}
|
|
919
|
+
return value.flatMap((entry, index) => {
|
|
920
|
+
const field = `sources[${index}]`;
|
|
921
|
+
if (typeof entry !== "string") {
|
|
922
|
+
return [issue("wrong-type", field, `sources entries must be strings, and this one is ${typeName(entry)}`)];
|
|
923
|
+
}
|
|
924
|
+
return entry.trim() === "" ? [issue("wrong-type", field, "sources entries must name a source, and this one is blank")] : [];
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
function checkPromotedTo(data) {
|
|
928
|
+
const value = data["promoted_to"];
|
|
929
|
+
if (value === undefined)
|
|
930
|
+
return [];
|
|
931
|
+
if (typeof value !== "string") {
|
|
932
|
+
return [issue("wrong-type", "promoted_to", `promoted_to must be a string, not ${typeName(value)}`)];
|
|
933
|
+
}
|
|
934
|
+
const absolute = /^(?:[/\\]|[A-Za-z]:)/.test(value);
|
|
935
|
+
const climbs = value.split(/[/\\]/).includes("..");
|
|
936
|
+
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`)];
|
|
937
|
+
}
|
|
938
|
+
function checkArtifact(data) {
|
|
939
|
+
const value = data["artifact"];
|
|
940
|
+
if (value === undefined || typeof value === "string")
|
|
941
|
+
return [];
|
|
942
|
+
return [issue("wrong-type", "artifact", `artifact must be a string, not ${typeName(value)}`)];
|
|
943
|
+
}
|
|
944
|
+
function checkProjectId(data) {
|
|
945
|
+
const value = data["project_id"];
|
|
946
|
+
if (value === undefined)
|
|
947
|
+
return [];
|
|
948
|
+
if (typeof value !== "string") {
|
|
949
|
+
return [issue("wrong-type", "project_id", `project_id must be a string, not ${typeName(value)}`)];
|
|
950
|
+
}
|
|
951
|
+
return PROJECT_ID_PATTERN.test(value) ? [] : [issue("invalid-project-id", "project_id", `project_id "${value}" is not ${PROJECT_ID_LENGTH} lowercase hex characters`)];
|
|
952
|
+
}
|
|
953
|
+
function checkTimestamp(data, field) {
|
|
954
|
+
const issues = requiredString(data, field);
|
|
955
|
+
if (issues.length > 0)
|
|
956
|
+
return issues;
|
|
957
|
+
const value = data[field];
|
|
958
|
+
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`)];
|
|
959
|
+
}
|
|
960
|
+
function isScalar(value) {
|
|
961
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
962
|
+
}
|
|
963
|
+
function checkEvidenceEntry(entry, index) {
|
|
964
|
+
const field = `evidence[${index}]`;
|
|
965
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
966
|
+
return [issue("wrong-type", field, `evidence entries must be mappings, and this one is ${typeName(entry)}`)];
|
|
967
|
+
}
|
|
968
|
+
const entries = Object.entries(entry);
|
|
969
|
+
if (entries.length === 0) {
|
|
970
|
+
return [issue("wrong-type", field, "evidence entries must say something, and this one is empty")];
|
|
971
|
+
}
|
|
972
|
+
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)}`)]);
|
|
973
|
+
}
|
|
974
|
+
function checkEvidence(data) {
|
|
975
|
+
const value = data["evidence"];
|
|
976
|
+
const owed = data["source"] === EVIDENCE_REQUIRED_SOURCE;
|
|
977
|
+
if (value === undefined || value === null)
|
|
978
|
+
return owed ? [missingEvidence("is absent")] : [];
|
|
979
|
+
if (!Array.isArray(value)) {
|
|
980
|
+
return [issue("wrong-type", "evidence", `evidence must be a list, not ${typeName(value)}`)];
|
|
981
|
+
}
|
|
982
|
+
if (value.length === 0)
|
|
983
|
+
return owed ? [missingEvidence("is an empty list")] : [];
|
|
984
|
+
return value.flatMap((entry, index) => checkEvidenceEntry(entry, index));
|
|
985
|
+
}
|
|
986
|
+
function missingEvidence(state) {
|
|
987
|
+
return issue("missing-evidence", "evidence", `a ${EVIDENCE_REQUIRED_SOURCE} record must name what it was learned from, and evidence ${state}`);
|
|
988
|
+
}
|
|
989
|
+
function checkStoredHash(data) {
|
|
990
|
+
if (!Object.hasOwn(data, "action_hash"))
|
|
991
|
+
return [];
|
|
992
|
+
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")];
|
|
993
|
+
}
|
|
994
|
+
function checkInstinctFrontmatter(data) {
|
|
995
|
+
return [
|
|
996
|
+
...checkId(data),
|
|
997
|
+
...requiredString(data, "trigger"),
|
|
998
|
+
...requiredChoice(data, "kind", INSTINCT_KINDS, "unknown-kind"),
|
|
999
|
+
...requiredChoice(data, "domain", INSTINCT_DOMAINS, "unknown-domain"),
|
|
1000
|
+
...checkConfidence(data),
|
|
1001
|
+
...checkUsageCount(data),
|
|
1002
|
+
...checkSources(data),
|
|
1003
|
+
...checkArtifact(data),
|
|
1004
|
+
...requiredChoice(data, "signal", INSTINCT_SIGNALS, "unknown-signal"),
|
|
1005
|
+
...requiredChoice(data, "scope", INSTINCT_SCOPES, "unknown-scope"),
|
|
1006
|
+
...checkProjectId(data),
|
|
1007
|
+
...requiredChoice(data, "source", INSTINCT_SOURCES, "unknown-source"),
|
|
1008
|
+
...checkEvidence(data),
|
|
1009
|
+
...checkPromotedTo(data),
|
|
1010
|
+
...checkTimestamp(data, "created_at"),
|
|
1011
|
+
...checkTimestamp(data, "updated_at"),
|
|
1012
|
+
...checkStoredHash(data)
|
|
1013
|
+
];
|
|
1014
|
+
}
|
|
1015
|
+
function isFence(line) {
|
|
1016
|
+
return /^\s*(?:```|~~~)/.test(line);
|
|
1017
|
+
}
|
|
1018
|
+
function sectionText(body, heading) {
|
|
1019
|
+
const lines = body.split(`
|
|
1020
|
+
`).map((line) => line.replace(/\r$/, ""));
|
|
1021
|
+
const collected = [];
|
|
1022
|
+
let fenced = false;
|
|
1023
|
+
let inside = false;
|
|
1024
|
+
for (const line of lines) {
|
|
1025
|
+
if (isFence(line))
|
|
1026
|
+
fenced = !fenced;
|
|
1027
|
+
else if (!fenced && /^##\s/.test(line)) {
|
|
1028
|
+
if (inside)
|
|
1029
|
+
break;
|
|
1030
|
+
inside = line.trim() === heading;
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
if (inside)
|
|
1034
|
+
collected.push(line);
|
|
1035
|
+
}
|
|
1036
|
+
return inside ? collected.join(`
|
|
1037
|
+
`).trim() : null;
|
|
1038
|
+
}
|
|
1039
|
+
function checkSection(body, heading) {
|
|
1040
|
+
const text = sectionText(body, heading);
|
|
1041
|
+
if (text === null) {
|
|
1042
|
+
return [issue("missing-section", heading, `the body carries no ${heading} section`)];
|
|
1043
|
+
}
|
|
1044
|
+
return text === "" ? [issue("missing-section", heading, `the ${heading} section is empty`)] : [];
|
|
1045
|
+
}
|
|
1046
|
+
function checkInstinctBody(body) {
|
|
1047
|
+
return [...checkSection(body, ACTION_HEADING), ...checkSection(body, CAUSE_HEADING)];
|
|
1048
|
+
}
|
|
1049
|
+
function parseInstinct(text) {
|
|
1050
|
+
const document = readFrontmatterDocument(text);
|
|
1051
|
+
if (document === null) {
|
|
1052
|
+
return {
|
|
1053
|
+
issues: [issue("missing-frontmatter", "frontmatter", "the file opens with no --- block holding a YAML mapping")],
|
|
1054
|
+
instinct: null
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
const issues = [
|
|
1058
|
+
...checkInstinctFrontmatter(document.data),
|
|
1059
|
+
...checkInstinctBody(document.body)
|
|
1060
|
+
];
|
|
1061
|
+
return issues.length > 0 ? { issues, instinct: null } : { issues, instinct: buildInstinct(document.data, document.body) };
|
|
1062
|
+
}
|
|
1063
|
+
function buildInstinct(data, body) {
|
|
1064
|
+
const action = sectionText(body, ACTION_HEADING) ?? "";
|
|
1065
|
+
const artifact = typeof data["artifact"] === "string" && data["artifact"].trim() !== "" ? data["artifact"] : null;
|
|
1066
|
+
return {
|
|
1067
|
+
id: data["id"],
|
|
1068
|
+
trigger: data["trigger"],
|
|
1069
|
+
kind: data["kind"],
|
|
1070
|
+
domain: data["domain"],
|
|
1071
|
+
confidence: data["confidence"],
|
|
1072
|
+
usageCount: typeof data["usage_count"] === "number" ? data["usage_count"] : DEFAULT_USAGE_COUNT,
|
|
1073
|
+
sources: Array.isArray(data["sources"]) ? data["sources"] : [],
|
|
1074
|
+
artifact,
|
|
1075
|
+
signal: data["signal"],
|
|
1076
|
+
scope: data["scope"],
|
|
1077
|
+
projectId: typeof data["project_id"] === "string" ? data["project_id"] : null,
|
|
1078
|
+
source: data["source"],
|
|
1079
|
+
evidence: Array.isArray(data["evidence"]) ? data["evidence"] : [],
|
|
1080
|
+
promotedTo: typeof data["promoted_to"] === "string" ? data["promoted_to"] : null,
|
|
1081
|
+
createdAt: data["created_at"],
|
|
1082
|
+
updatedAt: data["updated_at"],
|
|
1083
|
+
action,
|
|
1084
|
+
cause: sectionText(body, CAUSE_HEADING) ?? "",
|
|
1085
|
+
actionHash: actionHash(action)
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
function instinctFrontmatter(instinct) {
|
|
1089
|
+
const written = {
|
|
1090
|
+
id: instinct.id,
|
|
1091
|
+
trigger: instinct.trigger,
|
|
1092
|
+
kind: instinct.kind,
|
|
1093
|
+
domain: instinct.domain,
|
|
1094
|
+
confidence: instinct.confidence,
|
|
1095
|
+
usage_count: instinct.usageCount,
|
|
1096
|
+
signal: instinct.signal,
|
|
1097
|
+
scope: instinct.scope,
|
|
1098
|
+
source: instinct.source,
|
|
1099
|
+
created_at: instinct.createdAt,
|
|
1100
|
+
updated_at: instinct.updatedAt
|
|
1101
|
+
};
|
|
1102
|
+
if (instinct.artifact !== null)
|
|
1103
|
+
written["artifact"] = instinct.artifact;
|
|
1104
|
+
if (instinct.projectId !== null)
|
|
1105
|
+
written["project_id"] = instinct.projectId;
|
|
1106
|
+
if (instinct.evidence.length > 0)
|
|
1107
|
+
written["evidence"] = instinct.evidence;
|
|
1108
|
+
if (instinct.sources.length > 0)
|
|
1109
|
+
written["sources"] = instinct.sources;
|
|
1110
|
+
if (instinct.promotedTo !== null)
|
|
1111
|
+
written["promoted_to"] = instinct.promotedTo;
|
|
1112
|
+
return Object.fromEntries(INSTINCT_KEY_ORDER.filter((key) => Object.hasOwn(written, key)).map((key) => [key, written[key]]));
|
|
1113
|
+
}
|
|
1114
|
+
function writeInstinct(instinct, newline = `
|
|
1115
|
+
`) {
|
|
1116
|
+
const block = renderFrontmatter(instinctFrontmatter(instinct), newline);
|
|
1117
|
+
return [
|
|
1118
|
+
"---",
|
|
1119
|
+
block,
|
|
1120
|
+
"---",
|
|
1121
|
+
"",
|
|
1122
|
+
ACTION_HEADING,
|
|
1123
|
+
instinct.action,
|
|
1124
|
+
"",
|
|
1125
|
+
CAUSE_HEADING,
|
|
1126
|
+
instinct.cause,
|
|
1127
|
+
""
|
|
1128
|
+
].join(newline);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/commands/instinct/instinct-records.ts
|
|
1132
|
+
function isRecordFile(name) {
|
|
1133
|
+
return !name.startsWith(".") && name.endsWith(MARKDOWN_EXTENSION) && name !== MARKDOWN_EXTENSION;
|
|
1134
|
+
}
|
|
1135
|
+
function recordId(name) {
|
|
1136
|
+
return name.slice(0, -MARKDOWN_EXTENSION.length);
|
|
1137
|
+
}
|
|
1138
|
+
function unreadableIssue(message) {
|
|
1139
|
+
return { code: "missing-frontmatter", field: "file", message: `the file could not be read: ${message}` };
|
|
1140
|
+
}
|
|
1141
|
+
function readRecord(path, scope, id) {
|
|
1142
|
+
let text;
|
|
1143
|
+
try {
|
|
1144
|
+
text = readFileSync2(path, "utf8");
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1147
|
+
return { scope, path, id, instinct: null, issues: [unreadableIssue(message)] };
|
|
1148
|
+
}
|
|
1149
|
+
const parsed = parseInstinct(text);
|
|
1150
|
+
return { scope, path, id, instinct: parsed.instinct, issues: parsed.issues };
|
|
1151
|
+
}
|
|
1152
|
+
function isFile(path) {
|
|
1153
|
+
try {
|
|
1154
|
+
return statSync2(path).isFile();
|
|
1155
|
+
} catch {
|
|
1156
|
+
return false;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
function recordFiles(dir) {
|
|
1160
|
+
return readdirSync2(dir).filter((name) => isRecordFile(name) && isFile(join4(dir, name))).sort((left, right) => left.localeCompare(right));
|
|
1161
|
+
}
|
|
1162
|
+
function readScope(scope, dir) {
|
|
1163
|
+
if (!tierExists(dir))
|
|
1164
|
+
return { scope, dir, exists: false, records: [] };
|
|
1165
|
+
return {
|
|
1166
|
+
scope,
|
|
1167
|
+
dir,
|
|
1168
|
+
exists: true,
|
|
1169
|
+
records: recordFiles(dir).map((name) => readRecord(join4(dir, name), scope, recordId(name)))
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
function readScopes(seams) {
|
|
1173
|
+
return resolveInstinctScopes(seams).map((location) => readScope(location.scope, location.dir));
|
|
1174
|
+
}
|
|
1175
|
+
function allRecords(listings) {
|
|
1176
|
+
return listings.flatMap((listing) => listing.records);
|
|
1177
|
+
}
|
|
1178
|
+
function findRecords(listings, id) {
|
|
1179
|
+
return allRecords(listings).filter((entry) => entry.id === id);
|
|
1180
|
+
}
|
|
1181
|
+
function instinctProject(context, name) {
|
|
1182
|
+
if (context.project === null)
|
|
1183
|
+
throw new Error(`${name} runs inside a project, and was handed none`);
|
|
1184
|
+
return context.project;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// src/adapters/learning/held.ts
|
|
1188
|
+
function compareText(left, right) {
|
|
1189
|
+
if (left < right) {
|
|
1190
|
+
return -1;
|
|
1191
|
+
}
|
|
1192
|
+
if (left > right) {
|
|
1193
|
+
return 1;
|
|
1194
|
+
}
|
|
1195
|
+
return 0;
|
|
1196
|
+
}
|
|
1197
|
+
function byAge(left, right) {
|
|
1198
|
+
return compareText(left.createdAt, right.createdAt) || compareText(left.id, right.id);
|
|
1199
|
+
}
|
|
1200
|
+
function toHeldRecord(instinct) {
|
|
1201
|
+
return {
|
|
1202
|
+
id: instinct.id,
|
|
1203
|
+
trigger: instinct.trigger,
|
|
1204
|
+
action: instinct.action,
|
|
1205
|
+
action_hash: instinct.actionHash,
|
|
1206
|
+
confidence: instinct.confidence,
|
|
1207
|
+
usage_count: instinct.usageCount,
|
|
1208
|
+
...instinct.sources.length > 0 ? { sources: [...instinct.sources] } : {},
|
|
1209
|
+
...instinct.artifact === null ? {} : { artifact: instinct.artifact },
|
|
1210
|
+
signal: instinct.signal,
|
|
1211
|
+
status: "active",
|
|
1212
|
+
...instinct.promotedTo === null ? {} : { promoted_to: instinct.promotedTo },
|
|
1213
|
+
created_at: instinct.createdAt,
|
|
1214
|
+
updated_at: instinct.updatedAt
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function toHeldRecords(held) {
|
|
1218
|
+
const actions = new Map;
|
|
1219
|
+
for (const instinct of held) {
|
|
1220
|
+
const key = triggerKey(instinct.trigger);
|
|
1221
|
+
actions.set(key, (actions.get(key) ?? new Set).add(instinct.actionHash));
|
|
1222
|
+
}
|
|
1223
|
+
return held.map((instinct) => {
|
|
1224
|
+
const record = toHeldRecord(instinct);
|
|
1225
|
+
return actions.get(triggerKey(instinct.trigger)).size > 1 ? { ...record, status: "flagged" } : record;
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
function earliestHeldMember(record, held) {
|
|
1229
|
+
const key = triggerKey(record.trigger);
|
|
1230
|
+
const members = held.filter((instinct) => instinct.actionHash === record.action_hash && triggerKey(instinct.trigger) === key);
|
|
1231
|
+
return [...members].sort(byAge)[0] ?? null;
|
|
1232
|
+
}
|
|
1233
|
+
function toHeldInstinct(record, held, fallback) {
|
|
1234
|
+
const description = earliestHeldMember(record, held) ?? fallback;
|
|
1235
|
+
if (description === undefined) {
|
|
1236
|
+
return null;
|
|
1237
|
+
}
|
|
1238
|
+
return {
|
|
1239
|
+
id: record.id,
|
|
1240
|
+
trigger: record.trigger,
|
|
1241
|
+
kind: description.kind,
|
|
1242
|
+
domain: description.domain,
|
|
1243
|
+
confidence: record.confidence,
|
|
1244
|
+
usageCount: record.usage_count,
|
|
1245
|
+
sources: [...record.sources ?? []],
|
|
1246
|
+
artifact: record.artifact ?? null,
|
|
1247
|
+
signal: record.signal,
|
|
1248
|
+
scope: description.scope,
|
|
1249
|
+
projectId: description.projectId,
|
|
1250
|
+
source: description.source,
|
|
1251
|
+
evidence: [...description.evidence],
|
|
1252
|
+
promotedTo: record.promoted_to ?? null,
|
|
1253
|
+
createdAt: record.created_at,
|
|
1254
|
+
updatedAt: record.updated_at,
|
|
1255
|
+
action: record.action,
|
|
1256
|
+
cause: description.cause,
|
|
1257
|
+
actionHash: actionHash(record.action)
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/adapters/learning/local.ts
|
|
444
1262
|
var PREFIX = "local learning";
|
|
1263
|
+
var PUSH_REFUSAL = `${PREFIX}: refused to store a push`;
|
|
445
1264
|
var INSTINCTS_FILE = "instincts.ndjson";
|
|
446
1265
|
var FLAGS_FILE = "flags.ndjson";
|
|
1266
|
+
var RECORD_EXTENSION = ".md";
|
|
447
1267
|
var MIN_CONFIDENCE = 0.3;
|
|
448
1268
|
var MAX_CONFIDENCE = 0.9;
|
|
449
1269
|
var SIGNALS = ["loud", "silent"];
|
|
@@ -470,20 +1290,30 @@ var INSTINCT_CHECKS = {
|
|
|
470
1290
|
(value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0,
|
|
471
1291
|
"a whole number from 0"
|
|
472
1292
|
],
|
|
1293
|
+
sources: [
|
|
1294
|
+
(value) => value === undefined || Array.isArray(value) && value.every(isNonEmptyString),
|
|
1295
|
+
"a list of non-empty strings when present"
|
|
1296
|
+
],
|
|
473
1297
|
artifact: [(value) => value === undefined || typeof value === "string", "a string when present"],
|
|
474
1298
|
signal: [oneOf(SIGNALS), `one of: ${SIGNALS.join(", ")}`],
|
|
475
1299
|
status: [oneOf(STATUSES), `one of: ${STATUSES.join(", ")}`],
|
|
1300
|
+
promoted_to: [(value) => value === undefined || typeof value === "string", "a string when present"],
|
|
476
1301
|
created_at: [isString, "a string"],
|
|
477
1302
|
updated_at: [isString, "a string"]
|
|
478
1303
|
};
|
|
1304
|
+
var DESCRIPTION_CHECKS = {
|
|
1305
|
+
kind: [oneOf(INSTINCT_KINDS), `one of: ${INSTINCT_KINDS.join(", ")}`],
|
|
1306
|
+
domain: [oneOf(INSTINCT_DOMAINS), `one of: ${INSTINCT_DOMAINS.join(", ")}`],
|
|
1307
|
+
scope: [oneOf(INSTINCT_SCOPES), `one of: ${INSTINCT_SCOPES.join(", ")}`],
|
|
1308
|
+
source: [oneOf(INSTINCT_SOURCES), `one of: ${INSTINCT_SOURCES.join(", ")}`],
|
|
1309
|
+
evidence: [(value) => Array.isArray(value) && value.every(isMapping), "a list of mappings"],
|
|
1310
|
+
cause: [isString, "a string"],
|
|
1311
|
+
projectId: [(value) => value === null || typeof value === "string", "a string or null"]
|
|
1312
|
+
};
|
|
479
1313
|
var PAYLOAD_CHECKS = {
|
|
480
1314
|
source_id: [isNonEmptyString, "a non-empty string"],
|
|
481
1315
|
instincts: [Array.isArray, "a list"]
|
|
482
1316
|
};
|
|
483
|
-
var HELD_INSTINCT_CHECKS = {
|
|
484
|
-
source_id: [isNonEmptyString, "a non-empty string"],
|
|
485
|
-
instinct: [isMapping, "a mapping"]
|
|
486
|
-
};
|
|
487
1317
|
var FLAG_CHECKS = {
|
|
488
1318
|
id: [isNonEmptyString, "a non-empty string"],
|
|
489
1319
|
reason: [isString, "a string"],
|
|
@@ -500,21 +1330,29 @@ function fieldsProblem(value, checks, at) {
|
|
|
500
1330
|
}
|
|
501
1331
|
return null;
|
|
502
1332
|
}
|
|
1333
|
+
function recordProblem(record, index) {
|
|
1334
|
+
const at = `instincts[${index}]`;
|
|
1335
|
+
const problem = fieldsProblem(record, INSTINCT_CHECKS, at);
|
|
1336
|
+
if (problem !== null)
|
|
1337
|
+
return problem;
|
|
1338
|
+
const { action, action_hash: hash, description } = record;
|
|
1339
|
+
if (hash !== actionHash(action)) {
|
|
1340
|
+
return `action_hash is ${describeValue(hash)} in ${at}, expected the sha256 of its trimmed, lower-cased action`;
|
|
1341
|
+
}
|
|
1342
|
+
return description === undefined ? null : fieldsProblem(description, DESCRIPTION_CHECKS, `${at}.description`);
|
|
1343
|
+
}
|
|
503
1344
|
function payloadProblem(payload) {
|
|
504
1345
|
const problem = fieldsProblem(payload, PAYLOAD_CHECKS, "the payload");
|
|
505
1346
|
if (problem !== null)
|
|
506
1347
|
return problem;
|
|
507
1348
|
const { instincts } = payload;
|
|
508
|
-
return instincts.map(
|
|
509
|
-
}
|
|
510
|
-
function heldInstinctProblem(value) {
|
|
511
|
-
return fieldsProblem(value, HELD_INSTINCT_CHECKS, "the line") ?? fieldsProblem(value.instinct, INSTINCT_CHECKS, "the instinct");
|
|
1349
|
+
return instincts.map(recordProblem).find((found) => found !== null) ?? null;
|
|
512
1350
|
}
|
|
513
1351
|
function flagProblem(value) {
|
|
514
1352
|
return fieldsProblem(value, FLAG_CHECKS, "the flag");
|
|
515
1353
|
}
|
|
516
1354
|
function copyInstinct(record) {
|
|
517
|
-
const { artifact } = record;
|
|
1355
|
+
const { sources, artifact, promoted_to: promotedTo } = record;
|
|
518
1356
|
return {
|
|
519
1357
|
id: record.id,
|
|
520
1358
|
trigger: record.trigger,
|
|
@@ -522,13 +1360,29 @@ function copyInstinct(record) {
|
|
|
522
1360
|
action_hash: record.action_hash,
|
|
523
1361
|
confidence: record.confidence,
|
|
524
1362
|
usage_count: record.usage_count,
|
|
1363
|
+
...sources === undefined ? {} : { sources: [...sources] },
|
|
525
1364
|
...artifact === undefined ? {} : { artifact },
|
|
526
1365
|
signal: record.signal,
|
|
527
1366
|
status: record.status,
|
|
1367
|
+
...promotedTo === undefined ? {} : { promoted_to: promotedTo },
|
|
528
1368
|
created_at: record.created_at,
|
|
529
1369
|
updated_at: record.updated_at
|
|
530
1370
|
};
|
|
531
1371
|
}
|
|
1372
|
+
function copyDescription(description) {
|
|
1373
|
+
return {
|
|
1374
|
+
kind: description.kind,
|
|
1375
|
+
domain: description.domain,
|
|
1376
|
+
scope: description.scope,
|
|
1377
|
+
source: description.source,
|
|
1378
|
+
evidence: description.evidence.map((entry) => ({ ...entry })),
|
|
1379
|
+
cause: description.cause,
|
|
1380
|
+
projectId: description.projectId
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function loggedIncoming(record) {
|
|
1384
|
+
return record.description === undefined ? copyInstinct(record) : { ...copyInstinct(record), description: copyDescription(record.description) };
|
|
1385
|
+
}
|
|
532
1386
|
function hasCode(error, code) {
|
|
533
1387
|
return typeof error === "object" && error !== null && "code" in error && error.code === code;
|
|
534
1388
|
}
|
|
@@ -567,24 +1421,116 @@ function readLine(line, problemOf) {
|
|
|
567
1421
|
function warnThroughActiveOutput(message) {
|
|
568
1422
|
activeOutput().warn(message);
|
|
569
1423
|
}
|
|
1424
|
+
function unflagged(records, flagged) {
|
|
1425
|
+
return records.filter((record) => !flagged.has(record.id));
|
|
1426
|
+
}
|
|
1427
|
+
function userAdditions(userBlessed, projectHeld, flagged) {
|
|
1428
|
+
const heldTriggers = new Set(projectHeld.map((record) => triggerKey(record.trigger)));
|
|
1429
|
+
const heldIds = new Set(projectHeld.map((record) => record.id));
|
|
1430
|
+
return unflagged(userBlessed, flagged).filter((record) => !heldTriggers.has(triggerKey(record.trigger)) && !heldIds.has(record.id));
|
|
1431
|
+
}
|
|
1432
|
+
function byAge2(left, right) {
|
|
1433
|
+
if (left.created_at !== right.created_at) {
|
|
1434
|
+
return left.created_at < right.created_at ? -1 : 1;
|
|
1435
|
+
}
|
|
1436
|
+
if (left.id === right.id)
|
|
1437
|
+
return 0;
|
|
1438
|
+
return left.id < right.id ? -1 : 1;
|
|
1439
|
+
}
|
|
1440
|
+
function payloadDescription(record, described) {
|
|
1441
|
+
const key = triggerKey(record.trigger);
|
|
1442
|
+
const members = described.filter((candidate) => candidate.description !== undefined && candidate.action_hash === record.action_hash && triggerKey(candidate.trigger) === key);
|
|
1443
|
+
return [...members].sort(byAge2)[0]?.description;
|
|
1444
|
+
}
|
|
1445
|
+
function producedRecords(result) {
|
|
1446
|
+
const byId = new Map;
|
|
1447
|
+
for (const record of result.decisions.flatMap((decision) => decision.produced)) {
|
|
1448
|
+
const found = byId.get(record.id);
|
|
1449
|
+
if (found !== undefined && found !== record) {
|
|
1450
|
+
throw new Error(`${PUSH_REFUSAL}: two records would be written to ${record.id}${RECORD_EXTENSION}`);
|
|
1451
|
+
}
|
|
1452
|
+
byId.set(record.id, record);
|
|
1453
|
+
}
|
|
1454
|
+
return [...byId.values()];
|
|
1455
|
+
}
|
|
1456
|
+
function heldChanges(dir, held, result, described) {
|
|
1457
|
+
const heldInstincts = held.files.map((file) => file.instinct);
|
|
1458
|
+
const heldAt = new Map(held.files.map((file) => [file.path, file.instinct]));
|
|
1459
|
+
const writes = [];
|
|
1460
|
+
const kept = new Set;
|
|
1461
|
+
for (const record of producedRecords(result)) {
|
|
1462
|
+
const file = `${record.id}${RECORD_EXTENSION}`;
|
|
1463
|
+
const instinct = toHeldInstinct(record, heldInstincts, payloadDescription(record, described));
|
|
1464
|
+
if (instinct === null) {
|
|
1465
|
+
throw new Error(`${PUSH_REFUSAL}: ${file} has no held member and no payload description to be written with`);
|
|
1466
|
+
}
|
|
1467
|
+
const text = writeInstinct(instinct);
|
|
1468
|
+
const [issue2] = parseInstinct(text).issues;
|
|
1469
|
+
if (issue2 !== undefined) {
|
|
1470
|
+
throw new Error(`${PUSH_REFUSAL}: ${file} would not read back: ${issue2.field}: ${issue2.message}`);
|
|
1471
|
+
}
|
|
1472
|
+
const path = join5(dir, file);
|
|
1473
|
+
if (held.unreadable.has(path)) {
|
|
1474
|
+
throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which could not be read`);
|
|
1475
|
+
}
|
|
1476
|
+
const before = heldAt.get(path);
|
|
1477
|
+
if (before !== undefined && triggerKey(before.trigger) !== triggerKey(record.trigger)) {
|
|
1478
|
+
throw new Error(`${PUSH_REFUSAL}: it would overwrite ${path}, which holds another trigger`);
|
|
1479
|
+
}
|
|
1480
|
+
kept.add(path);
|
|
1481
|
+
if (before === undefined || writeInstinct(before) !== text)
|
|
1482
|
+
writes.push({ path, text });
|
|
1483
|
+
}
|
|
1484
|
+
const touched = new Set(result.decisions.map((decision) => triggerKey(decision.incoming.trigger)));
|
|
1485
|
+
const removals = held.files.filter((file) => touched.has(triggerKey(file.instinct.trigger)) && !kept.has(file.path)).map((file) => file.path);
|
|
1486
|
+
return { writes, removals };
|
|
1487
|
+
}
|
|
1488
|
+
function pushLogLines(sourceId, described, result) {
|
|
1489
|
+
return result.decisions.map((decision, index) => {
|
|
1490
|
+
const key = triggerKey(decision.incoming.trigger);
|
|
1491
|
+
const line = {
|
|
1492
|
+
source_id: sourceId,
|
|
1493
|
+
rule: decision.rule,
|
|
1494
|
+
incoming: loggedIncoming(described[index]),
|
|
1495
|
+
produced: decision.produced.map((record) => record.id),
|
|
1496
|
+
discarded: result.discarded.filter((record) => triggerKey(record.trigger) === key).map((record) => record.id)
|
|
1497
|
+
};
|
|
1498
|
+
return JSON.stringify(line);
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
570
1501
|
function localInstinctsDir(repoRoot) {
|
|
571
|
-
return
|
|
1502
|
+
return join5(repoRoot, RAFA_INSTINCTS_PATH);
|
|
1503
|
+
}
|
|
1504
|
+
function userInstinctsDir(home) {
|
|
1505
|
+
return join5(home, RAFA_INSTINCTS_PATH);
|
|
572
1506
|
}
|
|
573
1507
|
function createLocalLearning(options) {
|
|
574
|
-
const { instinctsDir } = options;
|
|
1508
|
+
const { instinctsDir, minConfidence } = options;
|
|
1509
|
+
const userDir = userInstinctsDir(options.home);
|
|
575
1510
|
const now = options.now ?? (() => new Date().toISOString());
|
|
576
1511
|
const warn = options.warn ?? warnThroughActiveOutput;
|
|
577
|
-
const instinctsPath =
|
|
578
|
-
const flagsPath =
|
|
579
|
-
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
1512
|
+
const instinctsPath = join5(instinctsDir, INSTINCTS_FILE);
|
|
1513
|
+
const flagsPath = join5(instinctsDir, FLAGS_FILE);
|
|
1514
|
+
function readHeld(scope, dir) {
|
|
1515
|
+
const { records } = readScope(scope, dir);
|
|
1516
|
+
const unreadable = new Set;
|
|
1517
|
+
const files = [];
|
|
1518
|
+
for (const entry of records) {
|
|
1519
|
+
if (entry.instinct === null) {
|
|
1520
|
+
unreadable.add(entry.path);
|
|
1521
|
+
const problems = entry.issues.map((issue2) => `${issue2.field}: ${issue2.message}`).join("; ");
|
|
1522
|
+
warn(`${PREFIX}: ${entry.path} is not a held instinct, so it was skipped: ${problems}`);
|
|
1523
|
+
} else {
|
|
1524
|
+
files.push({ path: entry.path, instinct: entry.instinct });
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return { files, unreadable };
|
|
1528
|
+
}
|
|
1529
|
+
function heldSet() {
|
|
1530
|
+
return readHeld("project", instinctsDir);
|
|
1531
|
+
}
|
|
1532
|
+
function heldRecords(held) {
|
|
1533
|
+
return toHeldRecords(held.files.map((file) => file.instinct));
|
|
588
1534
|
}
|
|
589
1535
|
async function flaggedIds() {
|
|
590
1536
|
const lines = numberedLines(await readIfPresent(flagsPath));
|
|
@@ -599,27 +1545,31 @@ function createLocalLearning(options) {
|
|
|
599
1545
|
push: async (payload) => {
|
|
600
1546
|
const problem = payloadProblem(payload);
|
|
601
1547
|
if (problem !== null) {
|
|
602
|
-
throw new TypeError(`${
|
|
1548
|
+
throw new TypeError(`${PUSH_REFUSAL}: ${problem}`);
|
|
603
1549
|
}
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const lines = decisions.flatMap(({ produced }) => produced.map((instinct) => JSON.stringify({
|
|
610
|
-
source_id: payload.source_id,
|
|
611
|
-
instinct
|
|
612
|
-
})));
|
|
1550
|
+
const described = payload.instincts;
|
|
1551
|
+
const held = heldSet();
|
|
1552
|
+
const result = merge(toHeldRecords(held.files.map((file) => file.instinct)), { source_id: payload.source_id, instincts: described.map(copyInstinct) }, now());
|
|
1553
|
+
const changes = heldChanges(instinctsDir, held, result, described);
|
|
1554
|
+
const lines = pushLogLines(payload.source_id, described, result);
|
|
613
1555
|
if (lines.length > 0) {
|
|
614
1556
|
await mkdir(instinctsDir, { recursive: true });
|
|
615
1557
|
await appendLines(instinctsPath, lines);
|
|
616
1558
|
}
|
|
617
|
-
|
|
1559
|
+
for (const { path, text } of changes.writes) {
|
|
1560
|
+
await writeFile(path, text, "utf8");
|
|
1561
|
+
}
|
|
1562
|
+
for (const path of changes.removals) {
|
|
1563
|
+
await rm(path, { force: true });
|
|
1564
|
+
}
|
|
1565
|
+
return result;
|
|
618
1566
|
},
|
|
619
1567
|
pullBlessed: async () => {
|
|
620
1568
|
const flagged = await flaggedIds();
|
|
621
|
-
const
|
|
622
|
-
|
|
1569
|
+
const projectHeld = heldRecords(heldSet());
|
|
1570
|
+
const project = bless(unflagged(projectHeld, flagged), { minConfidence }).instincts;
|
|
1571
|
+
const user = bless(heldRecords(readHeld("user", userDir)), { minConfidence }).instincts;
|
|
1572
|
+
return { version: now(), instincts: [...project, ...userAdditions(user, projectHeld, flagged)] };
|
|
623
1573
|
},
|
|
624
1574
|
flag: async (id, reason) => {
|
|
625
1575
|
const flag = { id, reason, flagged_at: now() };
|
|
@@ -627,8 +1577,7 @@ function createLocalLearning(options) {
|
|
|
627
1577
|
if (problem !== null) {
|
|
628
1578
|
throw new TypeError(`${PREFIX}: refused to write an invalid flag: ${problem}`);
|
|
629
1579
|
}
|
|
630
|
-
|
|
631
|
-
if (!held.some((instinct) => instinct.id === id)) {
|
|
1580
|
+
if (!heldSet().files.some((file) => file.instinct.id === id)) {
|
|
632
1581
|
throw new Error(`${PREFIX}: no instinct ${describeValue(id)} is held under ${instinctsDir}`);
|
|
633
1582
|
}
|
|
634
1583
|
await appendLines(flagsPath, [JSON.stringify(flag)]);
|
|
@@ -852,9 +1801,14 @@ function runningCommand() {
|
|
|
852
1801
|
}
|
|
853
1802
|
|
|
854
1803
|
// src/utils/session-env.ts
|
|
855
|
-
|
|
1804
|
+
import { delimiter } from "path";
|
|
1805
|
+
function sessionPath(base, binDir) {
|
|
1806
|
+
return base === undefined || base === "" ? binDir : `${binDir}${delimiter}${base}`;
|
|
1807
|
+
}
|
|
1808
|
+
function sessionSpawnEnv(base, binDir = bundledBinDirectory()) {
|
|
856
1809
|
return {
|
|
857
1810
|
...base,
|
|
1811
|
+
PATH: sessionPath(base["PATH"], binDir),
|
|
858
1812
|
CLAUDE_CODE_ENTRYPOINT: "cli"
|
|
859
1813
|
};
|
|
860
1814
|
}
|
|
@@ -895,8 +1849,8 @@ var CLAUDE_BASE_ARGS = [
|
|
|
895
1849
|
"--dangerously-skip-permissions"
|
|
896
1850
|
];
|
|
897
1851
|
var SETTING_SOURCES_FLAG = "--setting-sources";
|
|
898
|
-
function claudeArgs(settingSources, flags = []) {
|
|
899
|
-
return [...CLAUDE_BASE_ARGS, SETTING_SOURCES_FLAG, settingSources.join(","), ...flags];
|
|
1852
|
+
function claudeArgs(settingSources, flags = [], served = []) {
|
|
1853
|
+
return [...CLAUDE_BASE_ARGS, SETTING_SOURCES_FLAG, settingSources.join(","), ...served, ...flags];
|
|
900
1854
|
}
|
|
901
1855
|
var liveSessions = new Set;
|
|
902
1856
|
function interruptClaudeSessions() {
|
|
@@ -957,8 +1911,8 @@ async function spawnClaude(args, prompt) {
|
|
|
957
1911
|
liveSessions.delete(proc);
|
|
958
1912
|
}
|
|
959
1913
|
}
|
|
960
|
-
function runClaude(prompt, settingSources, flags = [], spawn = spawnClaude) {
|
|
961
|
-
return spawn(claudeArgs(settingSources, flags), prompt);
|
|
1914
|
+
function runClaude(prompt, settingSources, flags = [], spawn = spawnClaude, served = []) {
|
|
1915
|
+
return spawn(claudeArgs(settingSources, flags, served), prompt);
|
|
962
1916
|
}
|
|
963
1917
|
function writeWholeLines(text, write) {
|
|
964
1918
|
const lines = text.split(`
|
|
@@ -1014,8 +1968,8 @@ async function spawnCaptured(args, prompt, options = {}) {
|
|
|
1014
1968
|
}
|
|
1015
1969
|
return { exitCode: await proc.exited, stdout };
|
|
1016
1970
|
}
|
|
1017
|
-
function runClaudeCaptured(prompt, settingSources, flags = [], spawn = spawnClaudeCaptured) {
|
|
1018
|
-
return spawn(claudeArgs(settingSources, flags), prompt);
|
|
1971
|
+
function runClaudeCaptured(prompt, settingSources, flags = [], spawn = spawnClaudeCaptured, served = []) {
|
|
1972
|
+
return spawn(claudeArgs(settingSources, flags, served), prompt);
|
|
1019
1973
|
}
|
|
1020
1974
|
|
|
1021
1975
|
// src/adapters/planner/claude.ts
|
|
@@ -1459,8 +2413,8 @@ function createGithubTracker(options) {
|
|
|
1459
2413
|
}
|
|
1460
2414
|
|
|
1461
2415
|
// src/adapters/tracker/local.ts
|
|
1462
|
-
import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile } from "fs/promises";
|
|
1463
|
-
import { join as
|
|
2416
|
+
import { mkdir as mkdir3, readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
2417
|
+
import { join as join6 } from "path";
|
|
1464
2418
|
var PREFIX3 = "local tracker";
|
|
1465
2419
|
var FRONTMATTER = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
|
|
1466
2420
|
var ISSUE_NUMBER2 = /^[1-9]\d*$/;
|
|
@@ -1517,7 +2471,7 @@ function issueNumberOfFile(name) {
|
|
|
1517
2471
|
return name.endsWith(ISSUE_EXTENSION) ? parseIssueNumber(name.slice(0, -ISSUE_EXTENSION.length)) : null;
|
|
1518
2472
|
}
|
|
1519
2473
|
function issuePath(issuesDir, number) {
|
|
1520
|
-
return
|
|
2474
|
+
return join6(issuesDir, `${number}${ISSUE_EXTENSION}`);
|
|
1521
2475
|
}
|
|
1522
2476
|
async function issueNumbers(issuesDir) {
|
|
1523
2477
|
let names;
|
|
@@ -1531,7 +2485,7 @@ async function issueNumbers(issuesDir) {
|
|
|
1531
2485
|
return names.map(issueNumberOfFile).filter((number) => number !== null).sort((a, b) => a - b);
|
|
1532
2486
|
}
|
|
1533
2487
|
function localIssuesDir(repoRoot) {
|
|
1534
|
-
return
|
|
2488
|
+
return join6(repoRoot, ".rafa", "issues");
|
|
1535
2489
|
}
|
|
1536
2490
|
function renderLocalIssue(record) {
|
|
1537
2491
|
const { draft } = record;
|
|
@@ -1657,7 +2611,7 @@ function createLocalTracker(options) {
|
|
|
1657
2611
|
}
|
|
1658
2612
|
async function rewrite(number, record) {
|
|
1659
2613
|
const contents = renderLocalIssue(record);
|
|
1660
|
-
await
|
|
2614
|
+
await writeFile2(issuePath(issuesDir, number), contents, "utf8");
|
|
1661
2615
|
}
|
|
1662
2616
|
const tracker = {
|
|
1663
2617
|
kind: "local",
|
|
@@ -1679,7 +2633,7 @@ function createLocalTracker(options) {
|
|
|
1679
2633
|
const held = await issueNumbers(issuesDir);
|
|
1680
2634
|
for (let number = (held.at(-1) ?? 0) + 1;Number.isSafeInteger(number); number += 1) {
|
|
1681
2635
|
try {
|
|
1682
|
-
await
|
|
2636
|
+
await writeFile2(issuePath(issuesDir, number), contents, { encoding: "utf8", flag: "wx" });
|
|
1683
2637
|
return refFor(number, draft.opt);
|
|
1684
2638
|
} catch (error) {
|
|
1685
2639
|
if (!hasCode2(error, "EEXIST"))
|
|
@@ -1821,7 +2775,15 @@ var CORE_ADAPTERS = [
|
|
|
1821
2775
|
port: "learning",
|
|
1822
2776
|
kind: "local",
|
|
1823
2777
|
portVersion: PORT_VERSIONS.learning,
|
|
1824
|
-
create: ({
|
|
2778
|
+
create: ({
|
|
2779
|
+
repoRoot,
|
|
2780
|
+
home = homedir(),
|
|
2781
|
+
learningBlessMinConfidence = CONFIG_DEFAULTS.learningBlessMinConfidence
|
|
2782
|
+
}) => createLocalLearning({
|
|
2783
|
+
instinctsDir: localInstinctsDir(repoRoot),
|
|
2784
|
+
home,
|
|
2785
|
+
minConfidence: learningBlessMinConfidence
|
|
2786
|
+
})
|
|
1825
2787
|
},
|
|
1826
2788
|
{
|
|
1827
2789
|
port: "planner",
|
|
@@ -1860,4 +2822,4 @@ function selectEffortStore(repoRoot, config) {
|
|
|
1860
2822
|
return adapter.create({ repoRoot });
|
|
1861
2823
|
}
|
|
1862
2824
|
|
|
1863
|
-
export { EFFORT_STORE_DIR, effortStorePath, EFFORT_KEY_PROJECTIONS, openNdjsonStore, sqliteStorePath, LONE_SURROGATE, withSqliteStore, writeSqliteStore, openSqliteStore, sessionSpawnEnv, getClaudeUsagePercent, checkUsage, 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 };
|
|
2825
|
+
export { EFFORT_STORE_DIR, effortStorePath, EFFORT_KEY_PROJECTIONS, openNdjsonStore, sqliteStorePath, LONE_SURROGATE, 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 };
|