@gafj/gafj 0.1.1

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.
Files changed (121) hide show
  1. package/README.md +99 -0
  2. package/bin/cli.js +628 -0
  3. package/bridge/byo_key.js +50 -0
  4. package/bridge/credits.js +72 -0
  5. package/bridge/paste.js +44 -0
  6. package/bridge/providers/anthropic.js +16 -0
  7. package/bridge/providers/gemini.js +15 -0
  8. package/bridge/providers/index.js +39 -0
  9. package/bridge/providers/openai_compatible.js +17 -0
  10. package/core/extract.js +55 -0
  11. package/core/generators/excerpt.js +64 -0
  12. package/core/generators/packet.js +197 -0
  13. package/core/generators/serialize.js +56 -0
  14. package/core/generators/slice.js +61 -0
  15. package/core/house_rules.js +49 -0
  16. package/core/kb_import.js +257 -0
  17. package/core/migrations/002-profile.sql +3 -0
  18. package/core/migrations/003-discover.sql +49 -0
  19. package/core/parsers.js +79 -0
  20. package/core/practice.js +75 -0
  21. package/core/provenance.js +90 -0
  22. package/core/qr.js +185 -0
  23. package/core/rules/categories.md +5 -0
  24. package/core/rules/deep_answers.md +7 -0
  25. package/core/rules/discover.md +30 -0
  26. package/core/rules/extract.md +31 -0
  27. package/core/rules/followup.md +9 -0
  28. package/core/rules/index.js +32 -0
  29. package/core/rules/onepager.md +15 -0
  30. package/core/rules/practice.md +10 -0
  31. package/core/rules/prep.md +47 -0
  32. package/core/rules/resume.md +49 -0
  33. package/core/rules/rubric.md +120 -0
  34. package/core/schema.sql +342 -0
  35. package/core/schemas/categories.js +16 -0
  36. package/core/schemas/cover.js +10 -0
  37. package/core/schemas/deep_answers.js +16 -0
  38. package/core/schemas/discover.js +19 -0
  39. package/core/schemas/extract.js +32 -0
  40. package/core/schemas/followup.js +10 -0
  41. package/core/schemas/index.js +32 -0
  42. package/core/schemas/lang.js +111 -0
  43. package/core/schemas/onepager.js +26 -0
  44. package/core/schemas/practice.js +18 -0
  45. package/core/schemas/prep.js +15 -0
  46. package/core/schemas/resume.js +25 -0
  47. package/core/scoring.js +586 -0
  48. package/core/text.js +98 -0
  49. package/core/tracker_map.js +94 -0
  50. package/core/ulid.js +38 -0
  51. package/core/validators/highlight_body_match.js +49 -0
  52. package/core/validators/index.js +43 -0
  53. package/core/validators/metric_pairing.js +52 -0
  54. package/core/validators/no_confabulated_history.js +63 -0
  55. package/core/validators/no_dashes.js +31 -0
  56. package/core/validators/no_derived_tenure.js +60 -0
  57. package/core/validators/style_config.js +18 -0
  58. package/core/validators/verb_ladder.js +51 -0
  59. package/core/verbs.js +43 -0
  60. package/http/api.js +260 -0
  61. package/http/guard.js +106 -0
  62. package/http/headers.js +17 -0
  63. package/http/hosted.js +273 -0
  64. package/http/identity.js +70 -0
  65. package/http/server.js +136 -0
  66. package/http/sse.js +41 -0
  67. package/mcp/server.js +25 -0
  68. package/mcp/tools.js +237 -0
  69. package/package.json +59 -0
  70. package/store/backup.js +63 -0
  71. package/store/backup_dir.js +60 -0
  72. package/store/config.js +32 -0
  73. package/store/context.js +74 -0
  74. package/store/db.js +37 -0
  75. package/store/discover.js +120 -0
  76. package/store/events.js +28 -0
  77. package/store/export_xlsx.js +62 -0
  78. package/store/home.js +36 -0
  79. package/store/import_kb.js +88 -0
  80. package/store/import_tracker.js +195 -0
  81. package/store/ingest.js +140 -0
  82. package/store/kb.js +202 -0
  83. package/store/log.js +88 -0
  84. package/store/migrate.js +55 -0
  85. package/store/onboarding.js +267 -0
  86. package/store/packets.js +110 -0
  87. package/store/practice.js +52 -0
  88. package/store/proposals.js +119 -0
  89. package/store/reads.js +110 -0
  90. package/store/render.js +111 -0
  91. package/store/reply.js +19 -0
  92. package/store/review.js +64 -0
  93. package/store/scoring.js +134 -0
  94. package/store/settings.js +142 -0
  95. package/store/snapshot.js +88 -0
  96. package/store/transitions.js +391 -0
  97. package/store/tx.js +71 -0
  98. package/store/tz.js +24 -0
  99. package/ui/app.css +137 -0
  100. package/ui/app.js +75 -0
  101. package/ui/fonts/plexmono-400.woff2 +0 -0
  102. package/ui/fonts/plexmono-500.woff2 +0 -0
  103. package/ui/fonts/plexmono-600.woff2 +0 -0
  104. package/ui/fonts/spacegrotesk-var.woff2 +0 -0
  105. package/ui/hosted/signin.css +8 -0
  106. package/ui/hosted/signin.html +30 -0
  107. package/ui/hosted/signin.js +28 -0
  108. package/ui/icon-192.png +0 -0
  109. package/ui/icon-512.png +0 -0
  110. package/ui/index.html +20 -0
  111. package/ui/lib.js +92 -0
  112. package/ui/manifest.webmanifest +18 -0
  113. package/ui/screens/application.js +73 -0
  114. package/ui/screens/dashboard.js +37 -0
  115. package/ui/screens/document.js +64 -0
  116. package/ui/screens/interview.js +85 -0
  117. package/ui/screens/kb.js +142 -0
  118. package/ui/screens/packet.js +122 -0
  119. package/ui/screens/postings.js +92 -0
  120. package/ui/screens/settings.js +115 -0
  121. package/ui/vendor/preact.mjs +1 -0
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+
3
+ /** Schema language helpers; see index.js for the roster. */
4
+
5
+ // C0 controls other than tab, newline, and carriage return, plus DEL and the C1 range: nothing a document needs, everything a terminal escape needs.
6
+ // eslint-disable-next-line no-control-regex
7
+ const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/;
8
+
9
+ const CLASSES = new Set(["prose", "identifier", "quoted_source", "url", "email"]);
10
+ const CLAIM_KINDS = ["record", "metric", "ownership", "tenure", "history", "quote"];
11
+
12
+ const CLAIM = {
13
+ t: "object",
14
+ fields: {
15
+ kind: { t: "string", class: "identifier", enum: CLAIM_KINDS },
16
+ source_id: { t: "string", class: "identifier" },
17
+ value: { t: "string", class: "identifier" },
18
+ outcome_key: { t: "string", class: "identifier" },
19
+ verb: { t: "string", class: "identifier" },
20
+ subject: { t: "string", class: "identifier" },
21
+ interview_id: { t: "string", class: "identifier" },
22
+ document_id: { t: "string", class: "identifier" },
23
+ person_id: { t: "string", class: "identifier" },
24
+ },
25
+ required: ["kind"],
26
+ };
27
+
28
+ function str(cls, opts = {}) {
29
+ if (!CLASSES.has(cls)) throw new Error("bad class " + cls);
30
+ return { t: "string", class: cls, ...opts };
31
+ }
32
+ function claimed(max) {
33
+ return { t: "object", fields: { text: str("prose", { max, claims: true }), claims: { t: "array", items: CLAIM } }, required: ["text", "claims"], claimed: true };
34
+ }
35
+ function arr(items, min, max) {
36
+ return { t: "array", items, min, max };
37
+ }
38
+ function obj(fields, required) {
39
+ return { t: "object", fields, required: required || Object.keys(fields) };
40
+ }
41
+
42
+ function validate(value, node, path = "$", errors = []) {
43
+ const fail = (msg) => errors.push({ path, message: msg });
44
+ if (node.t === "object") {
45
+ if (value === null || typeof value !== "object" || Array.isArray(value)) { fail("expected an object"); return errors; }
46
+ for (const r of node.required || []) if (!(r in value)) fail(`missing required field ${r}`);
47
+ for (const [k, v] of Object.entries(value)) {
48
+ if (!Object.prototype.hasOwnProperty.call(node.fields, k)) { fail(`unknown field ${k}`); continue; }
49
+ validate(v, node.fields[k], `${path}.${k}`, errors);
50
+ }
51
+ } else if (node.t === "array") {
52
+ if (!Array.isArray(value)) { fail("expected an array"); return errors; }
53
+ if (node.min != null && value.length < node.min) fail(`expected at least ${node.min} items`);
54
+ if (node.max != null && value.length > node.max) fail(`expected at most ${node.max} items`);
55
+ value.forEach((v, i) => validate(v, node.items, `${path}[${i}]`, errors));
56
+ } else if (node.t === "string") {
57
+ if (typeof value !== "string") { fail("expected a string"); return errors; }
58
+ if (node.max != null && value.length > node.max) fail(`longer than ${node.max} characters`);
59
+ if (CONTROL.test(value)) fail("control characters are not allowed");
60
+ if (node.enum && !node.enum.includes(value)) fail(`must be one of ${node.enum.join(", ")}`);
61
+ } else if (node.t === "number") {
62
+ if (typeof value !== "number" || !isFinite(value)) fail("expected a number");
63
+ if (node.min != null && value < node.min) fail(`below ${node.min}`);
64
+ if (node.max != null && value > node.max) fail(`above ${node.max}`);
65
+ } else if (node.t === "boolean") {
66
+ if (typeof value !== "boolean") fail("expected a boolean");
67
+ }
68
+ return errors;
69
+ }
70
+
71
+ /**
72
+ * Every string field in the content with its class; claim-bearing text
73
+ * fields carry their sibling claims array.
74
+ */
75
+ function walk(value, node, path = "$", out = []) {
76
+ if (node.t === "object" && value && typeof value === "object") {
77
+ if (node.claimed) {
78
+ out.push({ path: `${path}.text`, value: String(value.text ?? ""), class: "prose", claims: Array.isArray(value.claims) ? value.claims : [], claimed: true });
79
+ return out;
80
+ }
81
+ for (const [k, sub] of Object.entries(node.fields)) if (Object.prototype.hasOwnProperty.call(value, k)) walk(value[k], sub, `${path}.${k}`, out);
82
+ } else if (node.t === "array" && Array.isArray(value)) {
83
+ value.forEach((v, i) => walk(v, node.items, `${path}[${i}]`, out));
84
+ } else if (node.t === "string" && typeof value === "string") {
85
+ out.push({ path, value, class: node.class, claims: null, claimed: false });
86
+ }
87
+ return out;
88
+ }
89
+
90
+ /** Field names in a schema, for the no-date and no-floor tests. */
91
+ function fieldNames(node, out = new Set()) {
92
+ if (node.t === "object") for (const [k, v] of Object.entries(node.fields)) { out.add(k); fieldNames(v, out); }
93
+ else if (node.t === "array") fieldNames(node.items, out);
94
+ return out;
95
+ }
96
+
97
+ function toJsonSchema(node) {
98
+ if (node.t === "object") {
99
+ const properties = {};
100
+ for (const [k, v] of Object.entries(node.fields)) properties[k] = toJsonSchema(v);
101
+ return { type: "object", properties, required: node.required || [], additionalProperties: false, ...(node.claimed ? { "x-claim-bearing": true } : {}) };
102
+ }
103
+ if (node.t === "array") return { type: "array", items: toJsonSchema(node.items), ...(node.min != null ? { minItems: node.min } : {}), ...(node.max != null ? { maxItems: node.max } : {}) };
104
+ if (node.t === "string") return { type: "string", "x-class": node.class, ...(node.max != null ? { maxLength: node.max } : {}), ...(node.enum ? { enum: node.enum } : {}) };
105
+ if (node.t === "number") return { type: "number", ...(node.min != null ? { minimum: node.min } : {}), ...(node.max != null ? { maximum: node.max } : {}) };
106
+ if (node.t === "boolean") return { type: "boolean" };
107
+ throw new Error("unknown node " + node.t);
108
+ }
109
+
110
+
111
+ module.exports = { str, claimed, arr, obj, CLAIM, CLAIM_KINDS, CLASSES, validate, walk, fieldNames, toJsonSchema };
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ const { str, claimed, arr, obj } = require("./lang");
3
+
4
+ /**
5
+ * The one-pager, Codification 3.4. One page is a content budget enforced
6
+ * here, not a measurement: the browser prints it. Round, interviewer, and
7
+ * datetime come from the store at render time, never from the model.
8
+ */
9
+ module.exports = obj({
10
+ posture: str("prose", { max: 90 }),
11
+ hard_stop: str("prose", { max: 90 }),
12
+ stories: arr(obj({ lead: str("prose", { max: 40 }), line: claimed(150) }), 3, 5),
13
+ amber_gaps: arr(claimed(170), 1, 4),
14
+ answer_keys: arr(obj({ trigger: str("prose", { max: 40 }), key: str("prose", { max: 90 }) }), 3, 6),
15
+ questions: arr(str("prose", { max: 120 }), 3, 6),
16
+ do_nots: arr(str("prose", { max: 80 }), 2, 5),
17
+ technical_page: obj({
18
+ formulas: arr(str("identifier", { max: 160 }), 0, 8),
19
+ click_paths: arr(str("prose", { max: 160 }), 0, 6),
20
+ rules: arr(str("prose", { max: 120 }), 0, 6),
21
+ recovery_script: str("prose", { max: 240 }),
22
+ closing_line: str("prose", { max: 200 }),
23
+ }, []),
24
+ }, ["posture", "stories", "amber_gaps", "answer_keys", "questions", "do_nots"]);
25
+
26
+ module.exports.BUDGET = { stories: 5, amber_gaps: 4, answer_keys: 6, questions: 6, do_nots: 5, max_chars_total: 2600 };
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ const { str, arr, obj } = require("./lang");
3
+
4
+ /**
5
+ * A drill workbook spec, Codification 3.6. The manifest (planted rows,
6
+ * expected flags, formulas) stays on the document so a later grader can
7
+ * check a drilled workbook against it. No candidate claims live here.
8
+ */
9
+ module.exports = obj({
10
+ scenario: str("prose", { max: 800 }),
11
+ domain: str("prose", { max: 80 }),
12
+ sheets: arr(obj({ name: str("identifier", { max: 40 }), columns: arr(str("identifier", { max: 40 }), 2, 20), rows: { t: "number", min: 10, max: 5000 } }), 1, 6),
13
+ anomalies: arr(obj({ kind: str("identifier", { enum: ["overbill", "underbill", "duplicate", "orphan_key", "whitespace", "nbsp", "text_number", "mislabel", "wrong_tier"] }),
14
+ sheet: str("identifier", { max: 40 }), row: { t: "number", min: 1 }, description: str("prose", { max: 200 }) }), 3, 40),
15
+ tasks: arr(obj({ text: str("prose", { max: 300 }), minutes: { t: "number", min: 1, max: 60 } }), 3, 12),
16
+ answer_key: arr(obj({ task: { t: "number", min: 1 }, formula: str("identifier", { max: 300 }), expected: str("identifier", { max: 200 }) }), 3, 40),
17
+ lesson: str("prose", { max: 400 }),
18
+ }, ["scenario", "domain", "sheets", "anomalies", "tasks", "answer_key"]);
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ const { str, claimed, arr, obj } = require("./lang");
3
+
4
+ /** Interview prep, Codification 3.2. posture is required (ledger rule 10). */
5
+ module.exports = obj({
6
+ posture: str("prose", { max: 160 }),
7
+ one_thing: claimed(600),
8
+ register_note: str("prose", { max: 400 }),
9
+ stories: arr(obj({ title: str("prose", { max: 80 }), body: claimed(900), why_this_room: str("prose", { max: 300 }) }), 3, 5),
10
+ deciding_question: obj({ question: str("prose", { max: 200 }), answer: claimed(1200) }),
11
+ likely_questions: arr(obj({ question: str("prose", { max: 200 }), answer_frame: claimed(900) }), 4, 12),
12
+ gaps_in_the_open: arr(obj({ gap: str("prose", { max: 120 }), phrasing: claimed(500) }), 0, 5),
13
+ questions_to_ask: arr(str("prose", { max: 200 }), 6, 8),
14
+ do_nots: arr(str("prose", { max: 160 }), 3, 10),
15
+ }, ["posture", "one_thing", "stories", "deciding_question", "likely_questions", "gaps_in_the_open", "questions_to_ask", "do_nots"]);
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ const { str, claimed, arr, obj } = require("./lang");
3
+
4
+ /**
5
+ * Resume output. Identity fields (company, role, dates) are identifiers
6
+ * copied from the candidate profile's skeleton; the store rejects any that
7
+ * do not match. Highlights and bullets are claim-bearing.
8
+ */
9
+ module.exports = obj({
10
+ headline: str("prose", { max: 80 }),
11
+ specialties: arr(str("prose", { max: 40 }), 3, 3),
12
+ headerLinks: arr(str("url"), 0, 3),
13
+ experienceSectionTitle: str("identifier", { max: 60 }),
14
+ careerHighlights: arr(obj({ leadIn: str("prose", { max: 60 }), proof: claimed(200) }), 5, 5),
15
+ experience: arr(obj({
16
+ company: str("identifier", { max: 120 }),
17
+ role: str("identifier", { max: 120 }),
18
+ dates: str("identifier", { max: 40 }),
19
+ section: str("identifier", { enum: ["experience", "concurrent"] }),
20
+ scope: str("prose", { max: 220 }),
21
+ bullets: arr(obj({ text: claimed(320), sourceVariationId: str("identifier"), rewritten: { t: "boolean" } }, ["text"]), 0, 8),
22
+ }), 1, 14),
23
+ skills: arr(str("identifier", { max: 60 }), 0, 30),
24
+ selectionNotes: str("prose", { max: 1200 }),
25
+ }, ["headline", "specialties", "experienceSectionTitle", "careerHighlights", "experience", "skills"]);