job-forge 2.14.32 → 2.14.34

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.
@@ -0,0 +1,125 @@
1
+ {
2
+ "version": 1,
3
+ "defaults": {
4
+ "profile": "jobforge-next-action",
5
+ "limit": 3
6
+ },
7
+ "profiles": [
8
+ {
9
+ "name": "jobforge-next-action",
10
+ "description": "Rank JobForge follow-up, pipeline, and apply work by fit, urgency, age, and source quality.",
11
+ "criteria": [
12
+ {
13
+ "id": "fit-score",
14
+ "label": "Fit score",
15
+ "field": "score",
16
+ "weight": 45,
17
+ "direction": "desc",
18
+ "min": 0,
19
+ "max": 5,
20
+ "required": true
21
+ },
22
+ {
23
+ "id": "urgency",
24
+ "label": "Urgency",
25
+ "field": "urgency",
26
+ "weight": 30,
27
+ "direction": "desc",
28
+ "min": 0,
29
+ "max": 10,
30
+ "default": 0
31
+ },
32
+ {
33
+ "id": "age",
34
+ "label": "Age in days",
35
+ "field": "ageDays",
36
+ "weight": 15,
37
+ "direction": "desc",
38
+ "min": 0,
39
+ "max": 30,
40
+ "default": 0
41
+ },
42
+ {
43
+ "id": "source-quality",
44
+ "label": "Source quality",
45
+ "field": "sourceQuality",
46
+ "weight": 10,
47
+ "direction": "desc",
48
+ "min": 0,
49
+ "max": 1,
50
+ "default": 0.5
51
+ }
52
+ ],
53
+ "gates": [
54
+ {
55
+ "id": "already-rejected",
56
+ "action": "skip",
57
+ "reason": "terminal tracker state",
58
+ "when": {
59
+ "where": {
60
+ "status": [
61
+ "Rejected",
62
+ "Discarded",
63
+ "SKIP"
64
+ ]
65
+ }
66
+ }
67
+ },
68
+ {
69
+ "id": "duplicate",
70
+ "action": "skip",
71
+ "reason": "duplicate candidate",
72
+ "when": {
73
+ "where": {
74
+ "duplicate": true
75
+ }
76
+ }
77
+ }
78
+ ],
79
+ "adjustments": [
80
+ {
81
+ "id": "due-follow-up",
82
+ "value": 6,
83
+ "reason": "follow-up is due now",
84
+ "when": {
85
+ "type": "followup",
86
+ "where": {
87
+ "timelineState": [
88
+ "due",
89
+ "overdue"
90
+ ]
91
+ }
92
+ }
93
+ },
94
+ {
95
+ "id": "dream-company",
96
+ "value": 8,
97
+ "reason": "company is on the priority list",
98
+ "when": {
99
+ "where": {
100
+ "priorityCompany": true
101
+ }
102
+ }
103
+ },
104
+ {
105
+ "id": "weak-source",
106
+ "value": -12,
107
+ "reason": "source lacks a report or score provenance",
108
+ "when": {
109
+ "where": {
110
+ "sourceQuality": 0
111
+ }
112
+ }
113
+ }
114
+ ],
115
+ "quotas": [
116
+ {
117
+ "id": "one-per-company",
118
+ "field": "company",
119
+ "max": 1,
120
+ "reason": "keep the selected queue diverse"
121
+ }
122
+ ]
123
+ }
124
+ ]
125
+ }
@@ -0,0 +1,86 @@
1
+ {
2
+ "version": 1,
3
+ "defaults": {
4
+ "overdueAfter": "7d",
5
+ "latestOnly": true
6
+ },
7
+ "rules": [
8
+ {
9
+ "id": "applied-follow-up",
10
+ "label": "Applied follow-up",
11
+ "description": "Applications in Applied state should receive a follow-up after one week.",
12
+ "action": "send_follow_up",
13
+ "match": {
14
+ "type": "application.status",
15
+ "where": {
16
+ "data.status": "Applied"
17
+ }
18
+ },
19
+ "after": "7d"
20
+ },
21
+ {
22
+ "id": "contacted-follow-up",
23
+ "label": "Contacted follow-up",
24
+ "description": "Outreach/contacted rows should receive a shorter follow-up after five days.",
25
+ "action": "send_contact_follow_up",
26
+ "match": {
27
+ "type": "application.status",
28
+ "where": {
29
+ "data.status": "Contacted"
30
+ }
31
+ },
32
+ "after": "5d",
33
+ "overdueAfter": "7d"
34
+ },
35
+ {
36
+ "id": "interview-thank-you",
37
+ "label": "Interview thank-you",
38
+ "description": "Interview rows should prompt a thank-you note the next day.",
39
+ "action": "send_thank_you",
40
+ "match": {
41
+ "type": "application.status",
42
+ "where": {
43
+ "data.status": "Interview"
44
+ }
45
+ },
46
+ "after": "1d",
47
+ "overdueAfter": "2d"
48
+ },
49
+ {
50
+ "id": "interview-follow-up",
51
+ "label": "Interview follow-up",
52
+ "description": "Interview rows without a later status should receive a recruiter nudge after one week.",
53
+ "action": "follow_up_after_interview",
54
+ "match": {
55
+ "type": "application.status",
56
+ "where": {
57
+ "data.status": "Interview"
58
+ }
59
+ },
60
+ "after": "7d",
61
+ "overdueAfter": "10d"
62
+ },
63
+ {
64
+ "id": "stale-pipeline-item",
65
+ "label": "Stale pending pipeline item",
66
+ "description": "Pending pipeline URLs with a known source date should be processed or discarded after three days.",
67
+ "action": "process_or_discard_pipeline_item",
68
+ "match": {
69
+ "type": "pipeline.item",
70
+ "where": {
71
+ "data.status": "pending"
72
+ }
73
+ },
74
+ "after": "3d",
75
+ "overdueAfter": "4d",
76
+ "suppressWhen": [
77
+ {
78
+ "type": [
79
+ "pipeline.processed",
80
+ "application.status"
81
+ ]
82
+ }
83
+ ]
84
+ }
85
+ ]
86
+ }
@@ -19,6 +19,9 @@
19
19
  * 10. Ledger file verifies if .jobforge-ledger/events.jsonl exists
20
20
  * 11. Artifact index verifies if .jobforge-index.json exists
21
21
  * 12. Fact set verifies if .jobforge-facts.json exists
22
+ * 13. Timeline verifies if .jobforge-timeline.json exists
23
+ * 14. Priority queue verifies if .jobforge-prioritize.json exists
24
+ * 15. Artifact lineage verifies/checks if .jobforge-lineage.json exists
22
25
  *
23
26
  * Run: node verify-pipeline.mjs (from repo root; same as npm run verify)
24
27
  */
@@ -33,6 +36,9 @@ import {
33
36
  import { jobForgeLedgerPath, ledgerExists, verifyJobForgeLedger } from './lib/jobforge-ledger.mjs';
34
37
  import { indexExists, jobForgeIndexPath, verifyJobForgeIndex } from './lib/jobforge-index.mjs';
35
38
  import { factsExist, jobForgeFactsPath, verifyJobForgeFacts } from './lib/jobforge-facts.mjs';
39
+ import { jobForgeTimelinePath, timelineExists, verifyJobForgeTimeline } from './lib/jobforge-timeline.mjs';
40
+ import { jobForgePrioritizePath, prioritizeExists, verifyJobForgePrioritize } from './lib/jobforge-prioritize.mjs';
41
+ import { checkJobForgeLineage, jobForgeLineagePath, lineageExists, verifyJobForgeLineage } from './lib/jobforge-lineage.mjs';
36
42
  import {
37
43
  canonicalStatusValues,
38
44
  formatContractIssues,
@@ -189,6 +195,62 @@ function verifyFactsIfPresent() {
189
195
  }
190
196
  }
191
197
 
198
+ function verifyTimelineIfPresent() {
199
+ if (!timelineExists(PROJECT_DIR)) {
200
+ ok('Timeline not initialized');
201
+ return;
202
+ }
203
+ const result = verifyJobForgeTimeline({}, PROJECT_DIR);
204
+ for (const issue of result.issues) {
205
+ const msg = `timeline: ${issue.code}: ${issue.message}`;
206
+ if (issue.severity === 'error') error(msg);
207
+ else warn(msg);
208
+ }
209
+ if (result.ok) {
210
+ ok(`Timeline valid (${relative(PROJECT_DIR, jobForgeTimelinePath(PROJECT_DIR))})`);
211
+ }
212
+ }
213
+
214
+ function verifyPrioritizeIfPresent() {
215
+ if (!prioritizeExists(PROJECT_DIR)) {
216
+ ok('Priority queue not initialized');
217
+ return;
218
+ }
219
+ const result = verifyJobForgePrioritize({}, PROJECT_DIR);
220
+ for (const issue of result.issues) {
221
+ const msg = `prioritize: ${issue.code}: ${issue.message}`;
222
+ if (issue.severity === 'error') error(msg);
223
+ else warn(msg);
224
+ }
225
+ if (result.ok) {
226
+ ok(`Priority queue valid (${relative(PROJECT_DIR, jobForgePrioritizePath(PROJECT_DIR))})`);
227
+ }
228
+ }
229
+
230
+ function verifyLineageIfPresent() {
231
+ if (!lineageExists(PROJECT_DIR)) {
232
+ ok('Artifact lineage not initialized');
233
+ return;
234
+ }
235
+ const verifyResult = verifyJobForgeLineage({}, PROJECT_DIR);
236
+ for (const issue of verifyResult.issues) {
237
+ const msg = `lineage: ${issue.code}: ${issue.message}`;
238
+ if (issue.severity === 'error') error(msg);
239
+ else warn(msg);
240
+ }
241
+
242
+ const checkResult = checkJobForgeLineage({}, PROJECT_DIR);
243
+ for (const issue of checkResult.issues) {
244
+ const msg = `lineage: ${issue.code}: ${issue.message}`;
245
+ if (issue.severity === 'error') error(msg);
246
+ else warn(msg);
247
+ }
248
+
249
+ if (verifyResult.ok && checkResult.ok) {
250
+ ok(`Artifact lineage current (${checkResult.current}/${checkResult.total} records at ${relative(PROJECT_DIR, jobForgeLineagePath(PROJECT_DIR))})`);
251
+ }
252
+ }
253
+
192
254
  // --- Read entries ---
193
255
  const { entries, source } = readAllEntries();
194
256
 
@@ -200,6 +262,9 @@ if (entries.length === 0) {
200
262
  verifyLedgerIfPresent();
201
263
  verifyIndexIfPresent();
202
264
  verifyFactsIfPresent();
265
+ verifyTimelineIfPresent();
266
+ verifyPrioritizeIfPresent();
267
+ verifyLineageIfPresent();
203
268
  console.log('\n' + '='.repeat(50));
204
269
  console.log(`📊 Pipeline Health: ${errors} errors, ${warnings} warnings`);
205
270
  if (errors === 0 && warnings === 0) console.log('🟢 Pipeline is clean!');
@@ -337,6 +402,9 @@ verifyStatesYamlDrift();
337
402
  verifyLedgerIfPresent();
338
403
  verifyIndexIfPresent();
339
404
  verifyFactsIfPresent();
405
+ verifyTimelineIfPresent();
406
+ verifyPrioritizeIfPresent();
407
+ verifyLineageIfPresent();
340
408
 
341
409
  console.log('\n' + '='.repeat(50));
342
410
  console.log(`📊 Pipeline Health: ${errors} errors, ${warnings} warnings`);